/* global React, CryptoJS, Icon, IconBtn, useReceiveFrom, SendToButton, copyText, useCopy, db */
const { useState, useEffect, useMemo, useCallback, useRef } = React;

// ============================================================
// Encryption tools — each op is its own sidebar entry (group: 'crypto')
// Mirrors the IMAGE_NAV pattern: one master toggle, many nav entries.
// All math runs locally via CryptoJS — nothing leaves the page.
// ============================================================

// Algorithm lookups -----------------------------------------
const HASHERS = {
  MD5:       (s) => CryptoJS.MD5(s),
  SHA1:      (s) => CryptoJS.SHA1(s),
  SHA256:    (s) => CryptoJS.SHA256(s),
  SHA384:    (s) => CryptoJS.SHA384(s),
  SHA512:    (s) => CryptoJS.SHA512(s),
  RIPEMD160: (s) => CryptoJS.RIPEMD160(s),
};
const HMACS = {
  MD5:       (s, k) => CryptoJS.HmacMD5(s, k),
  SHA1:      (s, k) => CryptoJS.HmacSHA1(s, k),
  SHA256:    (s, k) => CryptoJS.HmacSHA256(s, k),
  SHA512:    (s, k) => CryptoJS.HmacSHA512(s, k),
  RIPEMD160: (s, k) => CryptoJS.HmacRIPEMD160(s, k),
};
// Hex length per algorithm (for the decrypt validators)
const HEX_LEN = { MD5: 32, SHA1: 40, SHA256: 64, SHA384: 96, SHA512: 128, RIPEMD160: 40 };

function hashHex(algo, text, { hmacKey } = {}) {
  if (!text) return '';
  const wa = (hmacKey && HMACS[algo]) ? HMACS[algo](text, hmacKey) : HASHERS[algo](text);
  return wa.toString(CryptoJS.enc.Hex);
}

// Built-in reverse-lookup dictionary (common passwords + words) ----------
const WORDLIST = `123456 password 123456789 12345678 12345 1234567 qwerty abc123 password1
111111 1234567890 123123 admin letmein welcome monkey 1234 dragon master sunshine
princess football iloveyou shadow superman michael ninja mustang access flower
hello hottie loveme zaq1zaq1 password123 qwerty123 696969 batman trustno1 baseball
000000 qazwsx login starwars whatever passw0rd freedom test guest root user
hello123 charlie aa123456 donald qwertyuiop solo loveyou 654321 7777777 121212
secret summer ginger george computer michelle daniel jordan jessica pepper 11111111
amanda andrew tigger joshua pokemon maggie cookie thomas hannah jennifer hunter
buster soccer harley ranger iwantu thunder taylor matthew bandit angel killer
scooter samantha 1qaz2wsx asdfgh zxcvbnm 123qwe abcdef abcd1234 changeme default
crypto bitcoin ethereum lifehub orange purple silver golden coffee guitar
apple banana cherry mango lemon grape melon peach
hash encrypt decrypt secure private public token secret123 admin123
john mike david sarah emma olivia liam noah ava
cat dog fish bird lion tiger bear wolf fox owl
red blue green yellow black white pink brown gray
spring autumn winter january february march april may june july august
monday tuesday friday today tomorrow yesterday
one two three four five six seven eight nine ten
yes no maybe ok okay true false null undefined
foo bar baz qux hello world example sample demo`.trim().split(/\s+/);

function buildCandidates(extra) {
  const set = new Set();
  for (const w of WORDLIST) set.add(w);
  if (extra) for (const w of extra.split(/[\s,]+/)) if (w) set.add(w);
  // Cheap variants that catch a few more: capitalised + with trailing digits
  const base = [...set];
  for (const w of base) {
    set.add(w[0].toUpperCase() + w.slice(1));
    set.add(w + '1'); set.add(w + '123'); set.add(w + '!');
  }
  // Small number range
  for (let i = 0; i <= 9999; i++) set.add(String(i));
  return [...set];
}

// ============================================================
// Reusable two-pane IO (reuses encode.css classes for visual parity)
// ============================================================
function CryptoIO({ inLabel, outLabel, input, setInput, output, error, mono, sendTitle, placeholder }) {
  const [copied, copy] = useCopy();
  return (
    <div className="enc-io-pair">
      <div className="enc-io-side">
        <div className="enc-io-head">
          <span>{inLabel}</span>
          <span className="enc-io-count">{input.length} chars</span>
          <div style={{ flex: 1 }} />
          {input && <button className="lh-chip" onClick={() => setInput('')}><Icon name="close" />Clear</button>}
        </div>
        <textarea
          className="enc-input"
          placeholder={placeholder || 'Paste or type here…'}
          value={input}
          onChange={e => setInput(e.target.value)}
          spellCheck={false}
        />
      </div>
      <div className="enc-io-side">
        <div className="enc-io-head">
          <span>{outLabel}</span>
          <span className="enc-io-count">{(output || '').length} chars</span>
          <div style={{ flex: 1 }} />
          {output && !error && (
            <>
              <SendToButton payload={{ text: output, title: sendTitle }} compact />
              <button className="lh-chip" onClick={() => copy(output)}>
                <Icon name={copied ? 'check' : 'content_copy'} />{copied ? 'Copied' : 'Copy'}
              </button>
            </>
          )}
        </div>
        {error ? (
          <div className="enc-error inline"><Icon name="error" /><span>{error}</span></div>
        ) : (
          <textarea
            className={`enc-input is-output ${mono ? 'is-mono' : ''}`}
            value={output || ''}
            readOnly
            spellCheck={false}
            placeholder="Output appears here"
          />
        )}
      </div>
    </div>
  );
}

// Standalone page header shared by every op -----------------
function CryptoHeader({ op }) {
  return (
    <div className="cry-head">
      <span className="cry-head-icon"><Icon name={op.icon} /></span>
      <div className="cry-head-titles">
        <h2>{op.label}</h2>
        <p>{op.sub}</p>
      </div>
      <span className="cry-head-badge"><Icon name="lock" />Local only</span>
    </div>
  );
}

// ============================================================
// HASH panel (MD5 / SHA* / RIPEMD160)
// ============================================================
function HashPanel({ op }) {
  const [input, setInput] = useState('');
  const [upper, setUpper] = useState(false);
  const [useHmac, setUseHmac] = useState(false);
  const [hmacKey, setHmacKey] = useState('');

  useReceiveFrom('cry-' + op.id, useCallback((p) => { if (p?.text) setInput(p.text); }, [op.id]));

  const [output, error] = useMemo(() => {
    if (!input) return ['', null];
    try {
      let h = hashHex(op.algo, input, { hmacKey: useHmac ? hmacKey : '' });
      return [upper ? h.toUpperCase() : h, null];
    } catch (e) { return ['', e.message]; }
  }, [input, op.algo, upper, useHmac, hmacKey]);

  return (
    <div className="enc-panel">
      <div className="cry-controls">
        <label className="enc-toggle">
          <input type="checkbox" checked={upper} onChange={e => setUpper(e.target.checked)} />
          <span>Uppercase</span>
        </label>
        <label className="enc-toggle">
          <input type="checkbox" checked={useHmac} onChange={e => setUseHmac(e.target.checked)} />
          <span>HMAC{HMACS[op.algo] ? '' : ' (n/a)'}</span>
        </label>
        {useHmac && HMACS[op.algo] && (
          <input
            className="lh-input cry-key-inline"
            placeholder="HMAC secret key"
            value={hmacKey}
            onChange={e => setHmacKey(e.target.value)}
            spellCheck={false}
          />
        )}
        <div style={{ flex: 1 }} />
        <span className="cry-meta">{op.algo} · {HEX_LEN[op.algo] * 4}-bit · {HEX_LEN[op.algo]} hex chars</span>
      </div>
      <CryptoIO
        inLabel="Text to hash"
        outLabel={`${op.algo} digest`}
        input={input}
        setInput={setInput}
        output={output}
        error={error}
        mono
        sendTitle={`${op.algo} hash`}
      />
    </div>
  );
}

// ============================================================
// DECRYPT panel — honest offline reverse-lookup
// ============================================================
function DecryptPanel({ op }) {
  const [target, setTarget] = useState('');
  const [guess, setGuess] = useState('');
  const [extra, setExtra] = useState('');
  const [busy, setBusy] = useState(false);
  const [result, setResult] = useState(null); // { found, plain, tried }

  // Load saved custom wordlist
  useEffect(() => {
    db.kv.get('cryptoWordlist').then(r => { if (typeof r?.v === 'string') setExtra(r.v); });
  }, []);
  useReceiveFrom('cry-' + op.id, useCallback((p) => { if (p?.text) setTarget(p.text.trim()); }, [op.id]));

  const norm = target.trim().toLowerCase().replace(/\s+/g, '');
  const expectedLen = HEX_LEN[op.algo];
  const lenOk = !norm || norm.length === expectedLen;
  const isHex = /^[0-9a-f]*$/.test(norm);

  const guessHash = useMemo(() => guess ? hashHex(op.algo, guess) : '', [guess, op.algo]);
  const guessMatch = guess && norm && guessHash === norm;

  const runLookup = useCallback(() => {
    if (!norm || !lenOk || !isHex) return;
    setBusy(true);
    setResult(null);
    db.kv.put({ k: 'cryptoWordlist', v: extra });
    // Defer so the spinner can paint, then crunch synchronously.
    setTimeout(() => {
      const candidates = buildCandidates(extra);
      let found = null;
      let tried = 0;
      for (const c of candidates) {
        tried++;
        if (hashHex(op.algo, c) === norm) { found = c; break; }
      }
      setResult({ found, plain: found, tried });
      setBusy(false);
    }, 30);
  }, [norm, lenOk, isHex, extra, op.algo]);

  return (
    <div className="enc-panel">
      <div className="cry-note">
        <Icon name="info" />
        <span>
          Hashes are <strong>one-way</strong> — they can’t be mathematically reversed. This tool
          tries a built-in list of common words &amp; passwords (plus your own) and checks each against
          the hash. It only “cracks” inputs that happen to be on the list. Verifying a specific guess
          below always works.
        </span>
      </div>

      <div className="cry-field">
        <div className="cry-field-label">
          {op.algo} hash to look up
          <span className={`cry-len ${norm && !lenOk ? 'is-bad' : ''}`}>
            {norm ? `${norm.length}/${expectedLen} hex` : `expects ${expectedLen} hex chars`}
          </span>
        </div>
        <input
          className="lh-input cry-mono"
          placeholder={`Paste a ${op.algo} hash…`}
          value={target}
          onChange={e => setTarget(e.target.value)}
          spellCheck={false}
        />
        {norm && !isHex && <div className="cry-inline-err">Not a valid hex hash.</div>}
      </div>

      <div className="cry-row">
        <button className="lh-btn primary" onClick={runLookup} disabled={!norm || !lenOk || !isHex || busy}>
          <Icon name={busy ? 'hourglass_top' : 'travel_explore'} />
          {busy ? 'Searching…' : 'Reverse-lookup'}
        </button>
        <label className="enc-toggle" style={{ marginLeft: 'auto' }}>
          <Icon name="list_alt" />
          <span>{WORDLIST.length}+ built-in entries</span>
        </label>
      </div>

      {result && (
        <div className={`cry-result ${result.found ? 'is-hit' : 'is-miss'}`}>
          <Icon name={result.found ? 'lock_open' : 'search_off'} />
          {result.found ? (
            <div>
              <div className="cry-result-title">Match found</div>
              <div className="cry-result-body">
                The hash corresponds to <code>{result.plain}</code>
              </div>
            </div>
          ) : (
            <div>
              <div className="cry-result-title">No match</div>
              <div className="cry-result-body">
                Tried {result.tried.toLocaleString()} candidates — none produced this hash. The original
                text isn’t in the list. Add candidates below or verify a specific guess.
              </div>
            </div>
          )}
          {result.found && <SendToButton payload={{ text: result.plain, title: 'Recovered text' }} compact />}
        </div>
      )}

      <div className="cry-split">
        <div className="cry-field">
          <div className="cry-field-label">Verify a specific guess</div>
          <input
            className="lh-input"
            placeholder="Type a candidate to test against the hash…"
            value={guess}
            onChange={e => setGuess(e.target.value)}
            spellCheck={false}
          />
          {guess && (
            <div className={`cry-verify ${guessMatch ? 'is-hit' : (norm ? 'is-miss' : '')}`}>
              <Icon name={guessMatch ? 'check_circle' : (norm ? 'cancel' : 'fingerprint')} />
              <div>
                <code className="cry-mono">{guessHash}</code>
                <div className="cry-verify-sub">
                  {!norm ? 'Enter a hash above to compare' : guessMatch ? 'Matches the hash above' : 'Does not match the hash above'}
                </div>
              </div>
            </div>
          )}
        </div>
        <div className="cry-field">
          <div className="cry-field-label">Extra candidates (space- or comma-separated)</div>
          <textarea
            className="enc-input cry-extra"
            placeholder="hunter2 my-old-password staging-key …"
            value={extra}
            onChange={e => setExtra(e.target.value)}
            spellCheck={false}
          />
        </div>
      </div>
    </div>
  );
}

// ============================================================
// CIPHER panel — AES / DES, encrypt or decrypt, advanced controls
// ============================================================
const CIPHER_MODES = ['CBC', 'CFB', 'OFB', 'CTR', 'ECB'];
const CIPHER_PADS = ['Pkcs7', 'Iso97971', 'AnsiX923', 'Iso10126', 'ZeroPadding', 'NoPadding'];
const AES_SIZES = [128, 192, 256];

function randomHex(bytes) {
  return CryptoJS.lib.WordArray.random(bytes).toString(CryptoJS.enc.Hex);
}

function CipherPanel({ op }) {
  const isEnc = op.kind === 'cipher-enc';
  const ivBytes = op.algo === 'AES' ? 16 : 8;

  const [input, setInput] = useState('');
  const [key, setKey] = useState('');
  const [keyType, setKeyType] = useState('passphrase'); // passphrase | utf8 | hex
  const [mode, setMode] = useState('CBC');
  const [iv, setIv] = useState('');
  const [keySize, setKeySize] = useState(256);
  const [padding, setPadding] = useState('Pkcs7');
  const [fmt, setFmt] = useState('Base64'); // Base64 | Hex — encrypt output / decrypt input

  useReceiveFrom('cry-' + op.id, useCallback((p) => { if (p?.text) setInput(p.text); }, [op.id]));

  const raw = keyType !== 'passphrase';
  const needsIv = raw && mode !== 'ECB';

  const [output, error] = useMemo(() => {
    if (!input || !key) return ['', null];
    try {
      const Algo = CryptoJS[op.algo];
      // ---- Passphrase mode: OpenSSL-compatible KDF (salt+IV embedded) ----
      if (!raw) {
        if (isEnc) return [Algo.encrypt(input, key).toString(), null];
        const dec = Algo.decrypt(input.trim(), key).toString(CryptoJS.enc.Utf8);
        if (!dec) return ['', 'Decryption failed — wrong passphrase or corrupt ciphertext.'];
        return [dec, null];
      }
      // ---- Raw key mode: full control ----
      const keyWA = keyType === 'hex' ? CryptoJS.enc.Hex.parse(key) : CryptoJS.enc.Utf8.parse(key);
      const need = op.algo === 'AES' ? keySize / 8 : 8;
      if (keyWA.sigBytes !== need) {
        return ['', `${op.algo} ${op.algo === 'AES' ? 'AES-' + keySize : ''} needs a ${need}-byte key — got ${keyWA.sigBytes} byte(s). ${keyType === 'hex' ? `That's ${need * 2} hex chars.` : ''}`.trim()];
      }
      const cfg = { mode: CryptoJS.mode[mode], padding: CryptoJS.pad[padding] };
      if (mode !== 'ECB') {
        if (!/^[0-9a-fA-F]+$/.test(iv) || iv.length !== ivBytes * 2) {
          return ['', `${mode} needs a ${ivBytes}-byte IV (${ivBytes * 2} hex chars).`];
        }
        cfg.iv = CryptoJS.enc.Hex.parse(iv);
      }
      if (isEnc) {
        const c = Algo.encrypt(CryptoJS.enc.Utf8.parse(input), keyWA, cfg);
        return [fmt === 'Hex' ? c.ciphertext.toString(CryptoJS.enc.Hex) : c.ciphertext.toString(CryptoJS.enc.Base64), null];
      }
      const ct = fmt === 'Hex' ? CryptoJS.enc.Hex.parse(input.trim()) : CryptoJS.enc.Base64.parse(input.trim());
      const params = CryptoJS.lib.CipherParams.create({ ciphertext: ct });
      const dec = Algo.decrypt(params, keyWA, cfg);
      const txt = dec.toString(CryptoJS.enc.Utf8);
      if (!txt) return ['', 'Decryption failed — check key, IV, mode, padding, and input format.'];
      return [txt, null];
    } catch (e) {
      return ['', e.message || 'Could not process input.'];
    }
  }, [input, key, keyType, mode, iv, keySize, padding, fmt, raw, isEnc, op.algo, ivBytes]);

  return (
    <div className="enc-panel">
      <div className="cry-cfg">
        <div className="cry-cfg-row">
          <div className="cry-cfg-field grow">
            <label>Key type</label>
            <div className="cry-seg">
              {['passphrase', 'utf8', 'hex'].map(k => (
                <button key={k} className={keyType === k ? 'is-active' : ''} onClick={() => setKeyType(k)}>
                  {k === 'passphrase' ? 'Passphrase' : k === 'utf8' ? 'Raw (text)' : 'Raw (hex)'}
                </button>
              ))}
            </div>
          </div>
          {op.algo === 'AES' && raw && (
            <div className="cry-cfg-field">
              <label>Key size</label>
              <div className="cry-seg">
                {AES_SIZES.map(s => (
                  <button key={s} className={keySize === s ? 'is-active' : ''} onClick={() => setKeySize(s)}>{s}</button>
                ))}
              </div>
            </div>
          )}
        </div>

        <div className="cry-cfg-field">
          <label>{raw ? `Key (${keyType === 'hex' ? (op.algo === 'AES' ? keySize / 8 * 2 + ' hex chars' : '16 hex chars') : (op.algo === 'AES' ? keySize / 8 + ' bytes' : '8 bytes')})` : 'Passphrase'}</label>
          <input className="lh-input cry-mono" placeholder={raw ? 'Encryption key' : 'Secret passphrase'} value={key} onChange={e => setKey(e.target.value)} spellCheck={false} />
        </div>

        {raw && (
          <div className="cry-cfg-row">
            <div className="cry-cfg-field">
              <label>Mode</label>
              <select className="lh-input" value={mode} onChange={e => setMode(e.target.value)}>
                {CIPHER_MODES.map(m => <option key={m} value={m}>{m}</option>)}
              </select>
            </div>
            <div className="cry-cfg-field">
              <label>Padding</label>
              <select className="lh-input" value={padding} onChange={e => setPadding(e.target.value)}>
                {CIPHER_PADS.map(p => <option key={p} value={p}>{p}</option>)}
              </select>
            </div>
            <div className="cry-cfg-field grow">
              <label>IV ({ivBytes}-byte / {ivBytes * 2} hex){mode === 'ECB' ? ' — unused' : ''}</label>
              <div className="cry-iv">
                <input className="lh-input cry-mono" placeholder={`${ivBytes * 2} hex chars`} value={iv} onChange={e => setIv(e.target.value)} disabled={mode === 'ECB'} spellCheck={false} />
                <button className="lh-chip" onClick={() => setIv(randomHex(ivBytes))} disabled={mode === 'ECB'} title="Random IV"><Icon name="casino" /></button>
              </div>
            </div>
            <div className="cry-cfg-field">
              <label>{isEnc ? 'Output' : 'Input'} format</label>
              <div className="cry-seg">
                {['Base64', 'Hex'].map(f => (
                  <button key={f} className={fmt === f ? 'is-active' : ''} onClick={() => setFmt(f)}>{f}</button>
                ))}
              </div>
            </div>
          </div>
        )}
        {!raw && (
          <div className="cry-note slim">
            <Icon name="info" />
            <span>Passphrase mode uses OpenSSL-compatible key derivation (random salt + IV embedded in the output). For explicit mode / IV / key-size control, switch Key type to a raw key.</span>
          </div>
        )}
      </div>

      <CryptoIO
        inLabel={isEnc ? 'Plain text' : `Ciphertext (${raw ? fmt : 'Base64'})`}
        outLabel={isEnc ? `Ciphertext (${raw ? fmt : 'Base64'})` : 'Plain text'}
        input={input}
        setInput={setInput}
        output={output}
        error={error}
        mono
        sendTitle={`${op.algo} ${isEnc ? 'ciphertext' : 'plaintext'}`}
      />
    </div>
  );
}

// ============================================================
// TRANSFORM panel — Base64 / URL / HTML
// ============================================================
function b64Encode(t) { return btoa(String.fromCharCode(...new TextEncoder().encode(t))); }
function b64Decode(t) {
  const bin = atob(t.replace(/\s+/g, ''));
  const bytes = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
  return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
}
function htmlEncode(t) {
  return t.replace(/[&<>"'`]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '`': '&#96;' }[c]));
}
function htmlDecode(t) {
  const el = document.createElement('textarea');
  el.innerHTML = t;
  return el.value;
}

function TransformPanel({ op }) {
  const [input, setInput] = useState('');
  const [mode, setMode] = useState(op.dir || 'encode'); // base64 toggles; others fixed
  const [whole, setWhole] = useState(false); // url: encodeURI vs encodeURIComponent
  const combined = op.kind === 'base64';

  useReceiveFrom('cry-' + op.id, useCallback((p) => { if (p?.text) setInput(p.text); }, [op.id]));

  const [output, error] = useMemo(() => {
    if (!input) return ['', null];
    try {
      if (op.kind === 'base64') return mode === 'encode' ? [b64Encode(input), null] : [b64Decode(input), null];
      if (op.kind === 'url-enc') return [whole ? encodeURI(input) : encodeURIComponent(input), null];
      if (op.kind === 'url-dec') return [whole ? decodeURI(input) : decodeURIComponent(input), null];
      if (op.kind === 'html-enc') return [htmlEncode(input), null];
      if (op.kind === 'html-dec') return [htmlDecode(input), null];
      return ['', null];
    } catch (e) {
      return ['', op.kind === 'base64' ? 'Not valid Base64.' : (e.message || 'Could not process input.')];
    }
  }, [input, mode, whole, op.kind]);

  const isUrl = op.kind === 'url-enc' || op.kind === 'url-dec';
  const labels = {
    'base64':   mode === 'encode' ? ['Plain text', 'Base64'] : ['Base64', 'Plain text'],
    'url-enc':  ['Raw text', 'Encoded URL'],
    'url-dec':  ['Encoded URL', 'Decoded text'],
    'html-enc': ['Raw HTML / text', 'Escaped HTML'],
    'html-dec': ['Escaped HTML', 'Decoded text'],
  }[op.kind];

  return (
    <div className="enc-panel">
      {(combined || isUrl) && (
        <div className="enc-mode-pick">
          {combined && (
            <>
              <button className={`enc-mode-btn ${mode === 'encode' ? 'is-active' : ''}`} onClick={() => setMode('encode')}><Icon name="lock" />Encode</button>
              <button className={`enc-mode-btn ${mode === 'decode' ? 'is-active' : ''}`} onClick={() => setMode('decode')}><Icon name="lock_open" />Decode</button>
            </>
          )}
          <div style={{ flex: 1 }} />
          {isUrl && (
            <label className="enc-toggle">
              <input type="checkbox" checked={whole} onChange={e => setWhole(e.target.checked)} />
              <span>Whole URL (keep <code>/ ? : #</code>)</span>
            </label>
          )}
        </div>
      )}
      <CryptoIO
        inLabel={labels[0]}
        outLabel={labels[1]}
        input={input}
        setInput={setInput}
        output={output}
        error={error}
        mono={op.kind !== 'html-dec'}
        sendTitle={op.label}
      />
    </div>
  );
}

// ============================================================
// Op registry
// ============================================================
const CRYPTO_OPS = [
  { id: 'base64',     label: 'Base64 Encode/Decode', icon: 'data_array',  kind: 'base64',     sub: 'Encode text to Base64 and decode it back.' },

  { id: 'md5',        label: 'MD5 Hash',     icon: 'fingerprint', kind: 'hash',    algo: 'MD5',    sub: 'Generate MD5 hashes from text.' },
  { id: 'md5-dec',    label: 'MD5 Decrypt',  icon: 'lock_open',   kind: 'decrypt', algo: 'MD5',    sub: 'Reverse-lookup MD5 hashes against a wordlist.' },
  { id: 'sha1',       label: 'SHA1 Hash',    icon: 'fingerprint', kind: 'hash',    algo: 'SHA1',   sub: 'Generate SHA1 hashes from text.' },
  { id: 'sha1-dec',   label: 'SHA1 Decrypt', icon: 'lock_open',   kind: 'decrypt', algo: 'SHA1',   sub: 'Reverse-lookup SHA1 hashes against a wordlist.' },
  { id: 'sha256',     label: 'SHA256 Hash',  icon: 'fingerprint', kind: 'hash',    algo: 'SHA256', sub: 'Generate SHA256 hashes from text.' },
  { id: 'sha256-dec', label: 'SHA256 Decrypt', icon: 'lock_open', kind: 'decrypt', algo: 'SHA256', sub: 'Reverse-lookup SHA256 hashes against a wordlist.' },
  { id: 'sha384',     label: 'SHA384 Hash',  icon: 'fingerprint', kind: 'hash',    algo: 'SHA384', sub: 'Generate SHA384 hashes from text.' },
  { id: 'sha384-dec', label: 'SHA384 Decrypt', icon: 'lock_open', kind: 'decrypt', algo: 'SHA384', sub: 'Reverse-lookup SHA384 hashes against a wordlist.' },
  { id: 'sha512',     label: 'SHA512 Hash',  icon: 'fingerprint', kind: 'hash',    algo: 'SHA512', sub: 'Generate SHA512 hashes from text.' },
  { id: 'sha512-dec', label: 'SHA512 Decrypt', icon: 'lock_open', kind: 'decrypt', algo: 'SHA512', sub: 'Reverse-lookup SHA512 hashes against a wordlist.' },
  { id: 'ripemd160',  label: 'RIPEMD160 Encrypt', icon: 'fingerprint', kind: 'hash', algo: 'RIPEMD160', sub: 'Generate RIPEMD160 hashes from text.' },

  { id: 'aes-enc',    label: 'AES Encrypt',  icon: 'enhanced_encryption', kind: 'cipher-enc', algo: 'AES', sub: 'Encrypt text with AES (passphrase or raw key).' },
  { id: 'aes-dec',    label: 'AES Decrypt',  icon: 'no_encryption',       kind: 'cipher-dec', algo: 'AES', sub: 'Decrypt AES ciphertext.' },
  { id: 'des-enc',    label: 'DES Encrypt',  icon: 'enhanced_encryption', kind: 'cipher-enc', algo: 'DES', sub: 'Encrypt text with DES (passphrase or raw key).' },
  { id: 'des-dec',    label: 'DES Decrypt',  icon: 'no_encryption',       kind: 'cipher-dec', algo: 'DES', sub: 'Decrypt DES ciphertext.' },

  { id: 'url-enc',    label: 'URL Encode',   icon: 'link',     kind: 'url-enc',  sub: 'Percent-encode text for safe use in URLs.' },
  { id: 'url-dec',    label: 'URL Decode',   icon: 'link_off', kind: 'url-dec',  sub: 'Decode percent-encoded URL text.' },
  { id: 'html-enc',   label: 'HTML Encode',  icon: 'code',     kind: 'html-enc', sub: 'Escape characters into HTML entities.' },
  { id: 'html-dec',   label: 'HTML Decode',  icon: 'code_off', kind: 'html-dec', sub: 'Decode HTML entities back to text.' },
];

function PanelFor(op) {
  switch (op.kind) {
    case 'hash':       return <HashPanel op={op} />;
    case 'decrypt':    return <DecryptPanel op={op} />;
    case 'cipher-enc':
    case 'cipher-dec': return <CipherPanel op={op} />;
    default:           return <TransformPanel op={op} />;
  }
}

const NO_LIB_KINDS = ['url-enc', 'url-dec', 'html-enc', 'html-dec'];

function CryptoOpPage({ op }) {
  // Hashes, ciphers and Base64-decode rely on CryptoJS / browser APIs; the
  // transform tools (URL/HTML) don't need the library at all.
  const needsLib = !NO_LIB_KINDS.includes(op.kind);
  if (needsLib && typeof CryptoJS === 'undefined' && op.kind !== 'base64') {
    return (
      <div className="cry-tool">
        <CryptoHeader op={op} />
        <div className="lh-empty"><Icon name="cloud_off" /><div className="title">Crypto library didn’t load</div><div className="sub">Check your connection and refresh.</div></div>
      </div>
    );
  }
  return (
    <div className="cry-tool">
      <CryptoHeader op={op} />
      {PanelFor(op)}
    </div>
  );
}

// Expose one standalone component per op + nav manifest for app.jsx -------
// Each op is INDEPENDENT: no shared `group`, so it gets its own enable/disable
// switch in Settings and its own card in the All-apps catalog.
CRYPTO_OPS.forEach(op => {
  window['CryptoOp_' + op.id] = function CryptoOpComponent() { return <CryptoOpPage op={op} />; };
});
window.CRYPTO_NAV = CRYPTO_OPS.map(op => ({
  id: 'cry-' + op.id,
  label: op.label,
  icon: op.icon,
  section: 'Encryption',
  component: 'CryptoOp_' + op.id,
  sub: op.sub,
}));

// "Often used with" + moment-of-day copy for the catalog ----------------
function cryRelated(op) {
  const id = (x) => 'cry-' + x;
  if (op.kind === 'hash') {
    const dec = CRYPTO_OPS.find(o => o.kind === 'decrypt' && o.algo === op.algo);
    return [dec && id(dec.id), 'cry-sha256', 'cry-aes-enc'].filter(Boolean).filter(x => x !== id(op.id));
  }
  if (op.kind === 'decrypt') {
    const h = CRYPTO_OPS.find(o => o.kind === 'hash' && o.algo === op.algo);
    return [h && id(h.id), 'cry-md5-dec', 'cry-sha256-dec'].filter(Boolean).filter(x => x !== id(op.id));
  }
  if (op.kind === 'cipher-enc') return [id(op.algo.toLowerCase() + '-dec'), op.algo === 'AES' ? 'cry-des-enc' : 'cry-aes-enc'];
  if (op.kind === 'cipher-dec') return [id(op.algo.toLowerCase() + '-enc'), op.algo === 'AES' ? 'cry-des-dec' : 'cry-aes-dec'];
  // transforms
  return ['cry-base64', 'cry-url-enc', 'cry-html-enc'].filter(x => x !== id(op.id));
}
const CRY_MOMENT = {
  'hash':       'Fingerprinting a value · checksums',
  'decrypt':    'Recovering a forgotten value · auditing weak hashes',
  'cipher-enc': 'Encrypting a secret before you store or share it',
  'cipher-dec': 'Decrypting something you received',
  'base64':     'Embedding data · decoding a token',
  'url-enc':    'Building a query string',
  'url-dec':    'Reading a percent-encoded URL',
  'html-enc':   'Escaping user text for a page',
  'html-dec':   'Reading escaped HTML',
};

// Register each op INDEPENDENTLY in Settings (toggle) + Catalog (card),
// and add an additive "Encryption pack". crypto-tools.jsx loads after
// settings.jsx and catalog.jsx, so these globals already exist; we mutate
// them in place (single source of truth = CRYPTO_OPS above).
(function registerCryptoTools() {
  try {
    CRYPTO_OPS.forEach(op => {
      const id = 'cry-' + op.id;
      if (typeof TOGGLEABLE_TOOLS !== 'undefined' && !TOGGLEABLE_TOOLS.some(t => t.id === id)) {
        TOGGLEABLE_TOOLS.push({ id, label: op.label, icon: op.icon, category: 'Encryption', desc: op.sub });
      }
      if (typeof TOOL_OVERLAY !== 'undefined' && !TOOL_OVERLAY[id]) {
        TOOL_OVERLAY[id] = { mood: 'utility', moment: CRY_MOMENT[op.kind] || 'Hashing · encoding · ciphers', related: cryRelated(op) };
      }
    });
    if (typeof STARTER_PACKS !== 'undefined' && !STARTER_PACKS.some(p => p.id === 'encryption')) {
      STARTER_PACKS.push({
        id: 'encryption',
        label: 'Encryption pack',
        blurb: 'Every hash, cipher and encoder in one shot — MD5/SHA/RIPEMD160, AES & DES, Base64/URL/HTML.',
        icon: 'lock',
        accent: 'hsl(150, 55%, 46%)',
        tools: CRYPTO_OPS.map(op => 'cry-' + op.id),
      });
    }
  } catch (e) { console.warn('Crypto tool registration failed:', e); }
})();
