// ============================================================================
//  CodeEditor — monospace IGCSE pseudocode editor with a highlight overlay,
//  line-number gutter, and synced scrolling. Lightweight; no dependencies.
// ============================================================================

const IGCSE_KW = ['DECLARE','CONSTANT','INPUT','OUTPUT','IF','THEN','ELSE','ENDIF','CASE','OF','OTHERWISE','ENDCASE',
  'FOR','TO','STEP','NEXT','WHILE','DO','ENDWHILE','REPEAT','UNTIL','PROCEDURE','ENDPROCEDURE','FUNCTION','RETURNS',
  'RETURN','ENDFUNCTION','CALL','BYREF','BYVAL','OPENFILE','READFILE','WRITEFILE','CLOSEFILE','SEEK','GETRECORD',
  'PUTRECORD','EOF','FOR','READ','WRITE','APPEND','AND','OR','NOT','MOD','DIV','ARRAY'];
const IGCSE_TYPE = ['INTEGER','REAL','STRING','CHAR','BOOLEAN','DATE'];
const IGCSE_FN = ['LENGTH','MID','LEFT','RIGHT','UCASE','LCASE','SUBSTRING','ROUND','RANDOM','INT','EOF'];

const _esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

function highlight(src) {
  const kw = new Set(IGCSE_KW), ty = new Set(IGCSE_TYPE), fn = new Set(IGCSE_FN);
  return src.split('\n').map((line) => {
    // comments
    const ci = line.indexOf('//');
    let code = line, comment = '';
    if (ci >= 0) { code = line.slice(0, ci); comment = line.slice(ci); }
    // tokenise on strings first
    let html = '';
    const re = /("[^"]*"?|\b[0-9]+\b|[A-Za-z_][A-Za-z0-9_]*|<-|[+\-*/<>=]+|.)/g;
    let m;
    while ((m = re.exec(code)) !== null) {
      const t = m[0];
      if (t[0] === '"') html += `<span class="t-str">${_esc(t)}</span>`;
      else if (/^[0-9]+$/.test(t)) html += `<span class="t-num">${t}</span>`;
      else if (t === '<-') html += `<span class="t-op">&lt;-</span>`;
      else if (/^[+\-*/<>=]+$/.test(t)) html += `<span class="t-op">${_esc(t)}</span>`;
      else if (/^[A-Za-z_]/.test(t)) {
        const U = t.toUpperCase();
        if (kw.has(U)) html += `<span class="t-kw">${_esc(t)}</span>`;
        else if (ty.has(U)) html += `<span class="t-ty">${_esc(t)}</span>`;
        else if (fn.has(U)) html += `<span class="t-fn">${_esc(t)}</span>`;
        else html += _esc(t);
      } else html += _esc(t);
    }
    if (comment) html += `<span class="t-cm">${_esc(comment)}</span>`;
    return html || '&nbsp;';
  }).join('\n');
}

function CodeEditor({ value, onChange, disabled }) {
  const taRef = useRef(null);
  const preRef = useRef(null);
  const gutRef = useRef(null);
  const lines = value.split('\n').length;

  const sync = () => {
    if (preRef.current && taRef.current) {
      preRef.current.scrollTop = taRef.current.scrollTop;
      preRef.current.scrollLeft = taRef.current.scrollLeft;
    }
    if (gutRef.current && taRef.current) gutRef.current.scrollTop = taRef.current.scrollTop;
  };

  const onKey = (e) => {
    if (e.key === 'Tab') {
      e.preventDefault();
      const el = e.target, s = el.selectionStart, en = el.selectionEnd;
      const nv = value.slice(0, s) + '    ' + value.slice(en);
      onChange(nv);
      requestAnimationFrame(() => { el.selectionStart = el.selectionEnd = s + 4; });
    }
  };

  return (
    <div className="editor">
      <div className="ed-gutter" ref={gutRef}>
        {Array.from({ length: lines }, (_, i) => <div key={i} className="ed-ln">{i + 1}</div>)}
      </div>
      <div className="ed-body">
        <pre className="ed-pre" ref={preRef} aria-hidden="true" dangerouslySetInnerHTML={{ __html: highlight(value) + '\n' }} />
        <textarea
          ref={taRef}
          className="ed-ta"
          value={value}
          spellCheck={false}
          disabled={disabled}
          onChange={(e) => onChange(e.target.value)}
          onScroll={sync}
          onKeyDown={onKey}
        />
      </div>
      <style>{`
        .editor { display: flex; background: var(--bg); border: 1px solid var(--line); border-radius: 12px; overflow: hidden; height: 100%; min-height: 0; }
        .ed-gutter { flex-shrink: 0; padding: 16px 0; text-align: right; background: oklch(0.18 0.018 252); border-right: 1px solid var(--line); overflow: hidden; user-select: none; }
        .ed-ln { font-family: var(--mono); font-size: 13.5px; line-height: 22px; color: var(--faint); padding: 0 14px 0 16px; }
        .ed-body { position: relative; flex: 1; min-width: 0; }
        .ed-pre, .ed-ta {
          margin: 0; padding: 16px 18px; font-family: var(--mono); font-size: 13.5px; line-height: 22px;
          white-space: pre; tab-size: 4; border: 0;
        }
        .ed-pre { position: absolute; inset: 0; overflow: hidden; pointer-events: none; color: var(--text); }
        .ed-ta {
          position: absolute; inset: 0; width: 100%; height: 100%; resize: none; background: transparent;
          color: transparent; caret-color: var(--acc); overflow: auto; outline: none;
        }
        .ed-ta::selection { background: oklch(0.88 0.19 138 / 0.22); }
        .ed-ta:disabled { opacity: 1; }
        .t-kw { color: oklch(0.82 0.14 300); font-weight: 600; }
        .t-ty { color: oklch(0.80 0.13 235); }
        .t-fn { color: oklch(0.86 0.16 195); }
        .t-str { color: oklch(0.86 0.16 78); }
        .t-num { color: oklch(0.84 0.13 40); }
        .t-op { color: var(--acc); }
        .t-cm { color: var(--faint); font-style: italic; }
      `}</style>
    </div>
  );
}

Object.assign(window, { CodeEditor });
