import { h } from 'preact';
import { useRef } from 'preact/hooks';

// ── Tokenizer ─────────────────────────────────────────────────────────────────

type TT = 'kw' | 'str' | 'cmt' | 'num' | 'text';

const LUA_KEYWORDS = new Set([
  'if', 'then', 'else', 'elseif', 'end', 'while', 'do', 'for', 'in',
  'return', 'local', 'function', 'not', 'and', 'or', 'nil', 'true',
  'false', 'break', 'repeat', 'until',
]);

interface Tok { t: TT; s: string; }

function isAlpha(c: string) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c === '_'; }
function isAlNum(c: string) { return isAlpha(c) || (c >= '0' && c <= '9'); }
function isDigit(c: string) { return c >= '0' && c <= '9'; }

function tokenize(src: string): Tok[] {
  const out: Tok[] = [];
  let i = 0;

  while (i < src.length) {
    const c = src[i];

    // Single-line comment
    if (c === '-' && src[i + 1] === '-') {
      const end = src.indexOf('\n', i);
      const stop = end < 0 ? src.length : end;
      out.push({ t: 'cmt', s: src.slice(i, stop) });
      i = stop;
      continue;
    }

    // String: "..." or '...'
    if (c === '"' || c === "'") {
      let j = i + 1;
      while (j < src.length) {
        if (src[j] === '\\') { j += 2; continue; }
        if (src[j] === c) { j++; break; }
        j++;
      }
      out.push({ t: 'str', s: src.slice(i, j) });
      i = j;
      continue;
    }

    // Long string [[...]]
    if (c === '[' && src[i + 1] === '[') {
      const end = src.indexOf(']]', i + 2);
      const stop = end < 0 ? src.length : end + 2;
      out.push({ t: 'str', s: src.slice(i, stop) });
      i = stop;
      continue;
    }

    // Number
    if (isDigit(c) || (c === '.' && isDigit(src[i + 1] ?? ''))) {
      let j = i;
      if (src[j] === '0' && (src[j + 1] === 'x' || src[j + 1] === 'X')) {
        j += 2;
        while (j < src.length && /[0-9a-fA-F]/.test(src[j])) j++;
      } else {
        while (j < src.length && (isDigit(src[j]) || src[j] === '.')) j++;
        if (j < src.length && (src[j] === 'e' || src[j] === 'E')) {
          j++;
          if (src[j] === '+' || src[j] === '-') j++;
          while (j < src.length && isDigit(src[j])) j++;
        }
      }
      out.push({ t: 'num', s: src.slice(i, j) });
      i = j;
      continue;
    }

    // Identifier / keyword
    if (isAlpha(c)) {
      let j = i;
      while (j < src.length && isAlNum(src[j])) j++;
      const word = src.slice(i, j);
      out.push({ t: LUA_KEYWORDS.has(word) ? 'kw' : 'text', s: word });
      i = j;
      continue;
    }

    out.push({ t: 'text', s: c });
    i++;
  }

  return out;
}

function renderTokens(src: string) {
  return tokenize(src).map((tok, idx) =>
    tok.t === 'text'
      ? <span key={idx}>{tok.s}</span>
      : <span key={idx} class={`lua-${tok.t}`}>{tok.s}</span>
  );
}

// ── Component ─────────────────────────────────────────────────────────────────

interface Props {
  value: string;
  onChange: (v: string) => void;
  rows?: number;
}

export default function LuaEditor({ value, onChange, rows = 12 }: Props) {
  const preRef = useRef<HTMLPreElement>(null);

  function syncScroll(e: Event) {
    const ta = e.target as HTMLTextAreaElement;
    if (preRef.current) {
      preRef.current.scrollTop = ta.scrollTop;
      preRef.current.scrollLeft = ta.scrollLeft;
    }
  }

  return (
    <div class="lua-editor" style={`height:${rows * 1.5}em;`}>
      <pre ref={preRef} class="lua-pre" aria-hidden="true">
        {renderTokens(value)}
        {'\n'}
      </pre>
      <textarea
        class="lua-textarea"
        value={value}
        spellcheck={false}
        autocomplete="off"
        onInput={(e) => onChange((e.target as HTMLTextAreaElement).value)}
        onScroll={syncScroll}
      />
    </div>
  );
}
