/* global React, Icon, IconBtn, db */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ============================================================
// Keyboard tester — press every key, watch it light up.
// Mac and Windows layouts. Tested keys stay green; the live key
// shows its event.key / event.code / keyCode. Identity is the
// physical `event.code`, so it works regardless of remaps.
// ============================================================

// Rows shared by both layouts. `w` is a flex weight (1 = one unit).
const KBT_FUNCTION = [
  { code: 'Escape', label: 'esc', w: 1.3 },
  { code: 'F1', label: 'F1' }, { code: 'F2', label: 'F2' }, { code: 'F3', label: 'F3' }, { code: 'F4', label: 'F4' },
  { code: 'F5', label: 'F5' }, { code: 'F6', label: 'F6' }, { code: 'F7', label: 'F7' }, { code: 'F8', label: 'F8' },
  { code: 'F9', label: 'F9' }, { code: 'F10', label: 'F10' }, { code: 'F11', label: 'F11' }, { code: 'F12', label: 'F12' },
];
const KBT_NUMBER = [
  { code: 'Backquote', glyph: '`', sub: '~' },
  { code: 'Digit1', glyph: '1', sub: '!' }, { code: 'Digit2', glyph: '2', sub: '@' }, { code: 'Digit3', glyph: '3', sub: '#' },
  { code: 'Digit4', glyph: '4', sub: '$' }, { code: 'Digit5', glyph: '5', sub: '%' }, { code: 'Digit6', glyph: '6', sub: '^' },
  { code: 'Digit7', glyph: '7', sub: '&' }, { code: 'Digit8', glyph: '8', sub: '*' }, { code: 'Digit9', glyph: '9', sub: '(' },
  { code: 'Digit0', glyph: '0', sub: ')' }, { code: 'Minus', glyph: '-', sub: '_' }, { code: 'Equal', glyph: '=', sub: '+' },
  { code: 'Backspace', label: 'delete', w: 2 },
];
const KBT_QWERTY = [
  { code: 'Tab', label: 'tab', w: 1.5 },
  { code: 'KeyQ', glyph: 'Q' }, { code: 'KeyW', glyph: 'W' }, { code: 'KeyE', glyph: 'E' }, { code: 'KeyR', glyph: 'R' },
  { code: 'KeyT', glyph: 'T' }, { code: 'KeyY', glyph: 'Y' }, { code: 'KeyU', glyph: 'U' }, { code: 'KeyI', glyph: 'I' },
  { code: 'KeyO', glyph: 'O' }, { code: 'KeyP', glyph: 'P' },
  { code: 'BracketLeft', glyph: '[', sub: '{' }, { code: 'BracketRight', glyph: ']', sub: '}' },
  { code: 'Backslash', glyph: '\\', sub: '|', w: 1.5 },
];
const KBT_ASDF = [
  { code: 'CapsLock', label: 'caps', w: 1.8 },
  { code: 'KeyA', glyph: 'A' }, { code: 'KeyS', glyph: 'S' }, { code: 'KeyD', glyph: 'D' }, { code: 'KeyF', glyph: 'F' },
  { code: 'KeyG', glyph: 'G' }, { code: 'KeyH', glyph: 'H' }, { code: 'KeyJ', glyph: 'J' }, { code: 'KeyK', glyph: 'K' },
  { code: 'KeyL', glyph: 'L' },
  { code: 'Semicolon', glyph: ';', sub: ':' }, { code: 'Quote', glyph: "'", sub: '"' },
  { code: 'Enter', label: 'return', w: 2 },
];
const KBT_ZXCV = [
  { code: 'ShiftLeft', label: 'shift', w: 2.3 },
  { code: 'KeyZ', glyph: 'Z' }, { code: 'KeyX', glyph: 'X' }, { code: 'KeyC', glyph: 'C' }, { code: 'KeyV', glyph: 'V' },
  { code: 'KeyB', glyph: 'B' }, { code: 'KeyN', glyph: 'N' }, { code: 'KeyM', glyph: 'M' },
  { code: 'Comma', glyph: ',', sub: '<' }, { code: 'Period', glyph: '.', sub: '>' }, { code: 'Slash', glyph: '/', sub: '?' },
  { code: 'ShiftRight', label: 'shift', w: 2.3 },
];

// Bottom row differs by platform.
const KBT_BOTTOM = {
  mac: [
    { code: 'ControlLeft', glyph: '⌃', sub: 'control', w: 1.3 },
    { code: 'AltLeft', glyph: '⌥', sub: 'option', w: 1.3 },
    { code: 'MetaLeft', glyph: '⌘', sub: 'command', w: 1.5 },
    { code: 'Space', label: '', w: 6 },
    { code: 'MetaRight', glyph: '⌘', sub: 'command', w: 1.5 },
    { code: 'AltRight', glyph: '⌥', sub: 'option', w: 1.3 },
  ],
  windows: [
    { code: 'ControlLeft', label: 'Ctrl', w: 1.4 },
    { code: 'MetaLeft', glyph: '⊞', sub: 'Win', w: 1.3 },
    { code: 'AltLeft', label: 'Alt', w: 1.3 },
    { code: 'Space', label: '', w: 6 },
    { code: 'AltRight', label: 'Alt', w: 1.3 },
    { code: 'ContextMenu', glyph: '☰', sub: 'menu', w: 1.3 },
    { code: 'ControlRight', label: 'Ctrl', w: 1.4 },
  ],
};

// Keys we suppress default behaviour for, so testing doesn't scroll
// the page, move focus, or navigate back.
const KBT_PREVENT = new Set([
  'Tab', 'Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight',
  'Backspace', 'Enter', "'", '/',
]);

function kbtAllCodes(layout) {
  const rows = [KBT_FUNCTION, KBT_NUMBER, KBT_QWERTY, KBT_ASDF, KBT_ZXCV, KBT_BOTTOM[layout]];
  const codes = new Set();
  rows.forEach(r => r.forEach(k => codes.add(k.code)));
  ['ArrowLeft', 'ArrowUp', 'ArrowDown', 'ArrowRight'].forEach(c => codes.add(c));
  return codes;
}

function KbtKey({ k, down, tested }) {
  const cls = `kbt-key${down ? ' is-down' : ''}${tested && !down ? ' is-tested' : ''}`;
  return (
    <div className={cls} style={{ flexGrow: k.w || 1 }} title={k.code}>
      {k.glyph
        ? <span className="kbt-glyph">{k.glyph}</span>
        : <span>{k.label}</span>}
      {k.sub && <span className="kbt-sub">{k.sub}</span>}
    </div>
  );
}

function KeyboardTesterTool() {
  const guessMac = useMemo(
    () => /Mac|iPhone|iPad/i.test(navigator.platform || navigator.userAgent || ''),
    []
  );
  const [layout, setLayout] = useState(guessMac ? 'mac' : 'windows');
  const [pressed, setPressed] = useState(() => new Set());
  const [tested, setTested] = useState(() => new Set());
  const [last, setLast] = useState(null);
  const [loaded, setLoaded] = useState(false);

  // Load persisted layout preference.
  useEffect(() => {
    let on = true;
    db.kv.get('keytestLayout').then(rec => {
      if (on && (rec?.v === 'mac' || rec?.v === 'windows')) setLayout(rec.v);
      if (on) setLoaded(true);
    });
    return () => { on = false; };
  }, []);

  const setLayoutPersist = (l) => { setLayout(l); db.kv.put({ k: 'keytestLayout', v: l }); };

  const allCodes = useMemo(() => kbtAllCodes(layout), [layout]);

  useEffect(() => {
    const onDown = (e) => {
      if (KBT_PREVENT.has(e.code) || KBT_PREVENT.has(e.key)) e.preventDefault();
      setLast({ key: e.key, code: e.code, keyCode: e.keyCode, location: e.location });
      setPressed(prev => { const n = new Set(prev); n.add(e.code); return n; });
      setTested(prev => {
        if (prev.has(e.code)) return prev;
        const n = new Set(prev); n.add(e.code); return n;
      });
    };
    const onUp = (e) => {
      setPressed(prev => { const n = new Set(prev); n.delete(e.code); return n; });
    };
    // Releasing focus (alt-tab etc.) can drop keyup — clear held keys.
    const onBlur = () => setPressed(new Set());
    window.addEventListener('keydown', onDown);
    window.addEventListener('keyup', onUp);
    window.addEventListener('blur', onBlur);
    return () => {
      window.removeEventListener('keydown', onDown);
      window.removeEventListener('keyup', onUp);
      window.removeEventListener('blur', onBlur);
    };
  }, []);

  const reset = () => { setTested(new Set()); setPressed(new Set()); setLast(null); };

  const testedCount = useMemo(
    () => [...tested].filter(c => allCodes.has(c)).length,
    [tested, allCodes]
  );
  const total = allCodes.size;
  const pct = Math.round((testedCount / total) * 100);

  const rows = [
    KBT_FUNCTION, KBT_NUMBER, KBT_QWERTY, KBT_ASDF, KBT_ZXCV, KBT_BOTTOM[layout],
  ];

  if (!loaded) return null;

  return (
    <div className="hw-tool">
      <div className="hw-head">
        <div>
          <h1>Keyboard tester</h1>
          <p>Press any key and it lights up below. Tested keys turn green, so you can sweep
             the whole board and spot a dead or sticky switch. Nothing is sent anywhere.</p>
        </div>
        <div className="hw-head-actions">
          <div className="hw-seg" role="tablist" aria-label="Keyboard layout">
            <button className={layout === 'mac' ? 'is-active' : ''} onClick={() => setLayoutPersist('mac')}>
              <Icon name="laptop_mac" />Mac
            </button>
            <button className={layout === 'windows' ? 'is-active' : ''} onClick={() => setLayoutPersist('windows')}>
              <Icon name="desktop_windows" />Windows
            </button>
          </div>
          <button className="lh-btn ghost" onClick={reset}>
            <Icon name="restart_alt" />Reset
          </button>
        </div>
      </div>

      <div className="hw-panel">
        <div className="kbt-board">
          {rows.map((row, ri) => (
            <div className="kbt-row" key={ri}>
              {row.map(k => (
                <KbtKey key={k.code} k={k} down={pressed.has(k.code)} tested={tested.has(k.code)} />
              ))}
              {/* Append the arrow cluster to the bottom row */}
              {ri === rows.length - 1 && (
                <div className="kbt-arrows">
                  <KbtKey k={{ code: 'ArrowLeft', glyph: '←' }} down={pressed.has('ArrowLeft')} tested={tested.has('ArrowLeft')} />
                  <div className="kbt-arrow-col">
                    <KbtKey k={{ code: 'ArrowUp', glyph: '↑' }} down={pressed.has('ArrowUp')} tested={tested.has('ArrowUp')} />
                    <KbtKey k={{ code: 'ArrowDown', glyph: '↓' }} down={pressed.has('ArrowDown')} tested={tested.has('ArrowDown')} />
                  </div>
                  <KbtKey k={{ code: 'ArrowRight', glyph: '→' }} down={pressed.has('ArrowRight')} tested={tested.has('ArrowRight')} />
                </div>
              )}
            </div>
          ))}
        </div>

        <div className="kbt-progress-track">
          <div className="kbt-progress-fill" style={{ width: `${pct}%` }} />
        </div>

        <div className="kbt-readout">
          <div className="hw-stat">
            <div className="hw-stat-value">{last ? last.key === ' ' ? 'Space' : last.key : '—'}</div>
            <div className="hw-stat-label">event.key</div>
          </div>
          <div className="hw-stat">
            <div className="hw-stat-value">{last ? last.code : '—'}</div>
            <div className="hw-stat-label">event.code</div>
          </div>
          <div className="hw-stat">
            <div className="hw-stat-value">{last ? last.keyCode : '—'}</div>
            <div className="hw-stat-label">keyCode</div>
          </div>
          <div className="hw-stat">
            <div className="hw-stat-value">{testedCount}/{total}</div>
            <div className="hw-stat-label">keys tested · {pct}%</div>
          </div>
        </div>
      </div>
    </div>
  );
}

window.KeyboardTesterTool = KeyboardTesterTool;
