/**
 * CodeMirrorEditor.tsx — Gap 4: CodeMirror 6 via esm.sh CDN
 *
 * Zero bundle impact: caricato dinamicamente al primo mount.
 * Fallback graceful a textarea mentre il CDN carica (o in caso di errore).
 * Safari/iPhone-safe: no backdrop-filter, no SharedArrayBuffer, distrugge view on unmount.
 *
 * Lingue supportate: Python, JS, JSX, TS, TSX, HTML, CSS, JSON, Markdown.
 * Funzionalità: line numbers, syntax highlight, auto-indentazione, bracket matching,
 *               fold gutter, history (undo/redo), autocompletion, Cmd/Ctrl+S → save.
 */

import { useEffect, useRef, useState, useCallback, memo } from "react";
import { Z_INDEX }         from "@/lib/zindex";
import type * as React     from "react"; // FIX11: React namespace types
import type { VfsTsError } from "@/lib/vfsTsChecker"; // GAP-3

// ── Language detection from filename extension ────────────────────────────────

function detectLang(filename: string): string {
  const ext = filename.split(".").pop()?.toLowerCase() ?? "";
  if (ext === "py")                       return "python";
  if (ext === "js" || ext === "mjs" || ext === "cjs") return "javascript";
  if (ext === "jsx")                      return "jsx";
  if (ext === "ts")                       return "typescript";
  if (ext === "tsx")                      return "tsx";
  if (ext === "html" || ext === "htm")    return "html";
  if (ext === "css" || ext === "scss")    return "css";
  if (ext === "json")                     return "json";
  if (ext === "md" || ext === "markdown") return "markdown";
  return "plain";
}

// ── Singleton CDN loader ──────────────────────────────────────────────────────
// Tutti i pacchetti dallo stesso host esm.sh con target=es2020 per Safari 15+.
// Il browser condivide le istanze dei moduli via URL cache → identità dei tipi preservata.

const ESM = "https://esm.sh";
const Q   = "?target=es2020";

// S762: typed CDN interface — eliminates 18 ':any' in CMBundle
type CMExt = object; // Extension opaque type — used as array element

interface CMEditorView {
  dom:   HTMLElement;
  state: { doc: { toString(): string }; replaceSelection(text: string): object };
  dispatch(tr: object): void;
  focus(): void;
  destroy(): void;
}

interface CMEditorViewCtor {
  new (cfg: { state?: object; parent?: HTMLElement }): CMEditorView;
  updateListener: { of(fn: (upd: { docChanged: boolean; state: { doc: { toString(): string } } }) => void): CMExt };
  theme(spec: Record<string, unknown>): CMExt;
}

interface CMBundle {
  EditorState: { create(cfg: { doc?: string; extensions?: CMExt[] }): object };
  EditorView:  CMEditorViewCtor;
  keymap:      { of(bindings: readonly object[]): CMExt };
  lineNumbers:               () => CMExt;
  highlightActiveLine:       () => CMExt;
  highlightActiveLineGutter: () => CMExt;
  drawSelection:   () => CMExt;
  rectangularSelection: (() => CMExt) | null | undefined;
  crosshairCursor:      (() => CMExt) | null | undefined;
  history:        () => CMExt;
  defaultKeymap:  readonly object[];
  historyKeymap:  readonly object[];
  indentWithTab:  object;
  foldKeymap:     readonly object[];
  indentOnInput:  CMExt;
  syntaxHighlighting:    (style: object, opts?: Record<string, unknown>) => CMExt;
  defaultHighlightStyle: object;
  bracketMatching: () => CMExt;
  foldGutter:      () => CMExt;
  codeFolding:     (() => CMExt) | null | undefined;
  closeBrackets:       () => CMExt;
  closeBracketsKeymap: readonly object[];
  autocompletion:  (cfg?: Record<string, unknown>) => CMExt;
  completionKeymap: readonly object[];
  oneDark: CMExt;
  py:   { python(): CMExt } | null;
  js:   { javascript(cfg?: Record<string, unknown>): CMExt } | null;
  html: { html(): CMExt } | null;
  css:  { css(): CMExt } | null;
  json: { json(): CMExt } | null;
  md:   { markdown(): CMExt } | null;
}

let _cm: CMBundle | null = null;
let _cmP: Promise<CMBundle> | null = null;

function loadCM(): Promise<CMBundle> {
  if (_cm)  return Promise.resolve(_cm);
  if (_cmP) return _cmP;

  _cmP = (async (): Promise<CMBundle> => {
    const [
      stateMod, viewMod, cmdMod, langMod, acMod, themeMod,
      pyMod, jsMod, htmlMod, cssMod, jsonMod, mdMod,
    ] = await Promise.all([
      import(`${ESM}/@codemirror/state${Q}`),
      import(`${ESM}/@codemirror/view${Q}`),
      import(`${ESM}/@codemirror/commands${Q}`),
      import(`${ESM}/@codemirror/language${Q}`),
      import(`${ESM}/@codemirror/autocomplete${Q}`),
      import(`${ESM}/@codemirror/theme-one-dark${Q}`),
      import(`${ESM}/@codemirror/lang-python${Q}`).catch(() => null),
      import(`${ESM}/@codemirror/lang-javascript${Q}`).catch(() => null),
      import(`${ESM}/@codemirror/lang-html${Q}`).catch(() => null),
      import(`${ESM}/@codemirror/lang-css${Q}`).catch(() => null),
      import(`${ESM}/@codemirror/lang-json${Q}`).catch(() => null),
      import(`${ESM}/@codemirror/lang-markdown${Q}`).catch(() => null),
    ]);

    _cm = {
      EditorState: stateMod.EditorState,
      EditorView:  viewMod.EditorView,
      keymap:      viewMod.keymap,
      lineNumbers:               viewMod.lineNumbers,
      highlightActiveLine:       viewMod.highlightActiveLine,
      highlightActiveLineGutter: viewMod.highlightActiveLineGutter,
      drawSelection:    viewMod.drawSelection,
      rectangularSelection: viewMod.rectangularSelection ?? null,
      crosshairCursor:  viewMod.crosshairCursor ?? null,
      history:      cmdMod.history,
      defaultKeymap: cmdMod.defaultKeymap,
      historyKeymap: cmdMod.historyKeymap,
      indentWithTab: cmdMod.indentWithTab,
      foldKeymap:    cmdMod.foldKeymap ?? [],
      indentOnInput: langMod.indentOnInput,
      syntaxHighlighting:    langMod.syntaxHighlighting,
      defaultHighlightStyle: langMod.defaultHighlightStyle,
      bracketMatching: langMod.bracketMatching,
      foldGutter:    langMod.foldGutter,
      codeFolding:   langMod.codeFolding ?? null,
      closeBrackets:       acMod.closeBrackets,
      closeBracketsKeymap: acMod.closeBracketsKeymap,
      autocompletion:  acMod.autocompletion,
      completionKeymap: acMod.completionKeymap,
      oneDark: themeMod.oneDark,
      py:   pyMod,
      js:   jsMod,
      html: htmlMod,
      css:  cssMod,
      json: jsonMod,
      md:   mdMod,
    };
    return _cm!;
  })();

  return _cmP;
}

// ── iOS detection (module-level, sync, zero cost) ─────────────────────────────
const _isIOS = typeof navigator !== "undefined"
  && /iPhone|iPad|iPod/.test(navigator.userAgent);

// ── Simboli toolbar mobile ────────────────────────────────────────────────────
// Ordinati per frequenza d'uso nel codice. Tab è separato (inserisce "  ").
const MOBILE_SYMBOLS = [
  { label: "⇥",   insert: "  "   },  // Tab (2 spazi)
  { label: "{",   insert: "{"    },
  { label: "}",   insert: "}"    },
  { label: "(",   insert: "("    },
  { label: ")",   insert: ")"    },
  { label: "[",   insert: "["    },
  { label: "]",   insert: "]"    },
  { label: "=",   insert: "="    },
  { label: ":",   insert: ":"    },
  { label: "->",  insert: "->"   },
  { label: "//",  insert: "//"   },
  { label: '"',   insert: '"'    },
  { label: "'",   insert: "'"    },
  { label: ";",   insert: ";"    },
] as const;

// ── Props ─────────────────────────────────────────────────────────────────────

interface CodeMirrorEditorProps {
  value:     string;
  onChange:  (v: string) => void;
  filename?: string;
  onSave?:   () => void;
  style?:    React.CSSProperties;
  tsErrors?: VfsTsError[]; // GAP-3
  /** S800: posizione cursore (line, col 1-based) — aggiornata ad ogni keypress */
  onCursorChange?: (line: number, col: number) => void;
  /** S800: dimensione font in px (default 13) — controllata da MobileEditorStatusBar */
  fontSize?: number;
  /** S800: ref per scrollare l'editor a un offset specifico (usato da MobileEditorFindBar) */
  scrollToOffsetRef?: React.MutableRefObject<((offset: number) => void) | null>;
}

// ── Shared textarea style ─────────────────────────────────────────────────────

const TA_STYLE: React.CSSProperties = {
  position: "absolute", inset: 0,
  width: "100%", height: "100%",
  padding: "14px 16px",
  background: "hsl(222 22% 7%)", border: "none", outline: "none",
  color: "#e4e4e7", fontSize: "0.83rem", lineHeight: 1.7,
  fontFamily: "ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace",
  resize: "none", boxSizing: "border-box", zIndex: 1,
};


// ── N7: SelectionTooltip — Inline AI su testo selezionato ────────────────────
// Appare quando l'utente seleziona testo nell'editor CodeMirror.
// Pulsanti: Spiega / Fixa → pre-compilano ChatInputBar via useChatStore.setInput().

interface _TooltipState { top: number; left: number; text: string; visible: boolean; }

function _SelectionTooltip({ hostRef }: { hostRef: React.RefObject<HTMLDivElement | null> }) {
  const [tip, setTip] = useState<_TooltipState>({ top: 0, left: 0, text: "", visible: false });
  const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);

  // Ascolta il DOM event "selectionchange" sul contenitore CM
  useEffect(() => {
    const host = hostRef.current;
    if (!host) return;

    const onSel = () => {
      const sel = window.getSelection();
      const txt = sel?.toString().trim() ?? "";
      if (!txt || txt.length < 5) { setTip(p => ({ ...p, visible: false })); return; }
      if (hideTimer.current) { clearTimeout(hideTimer.current); hideTimer.current = null; }
      const range = sel?.getRangeAt(0);
      if (!range) return;
      const rect  = range.getBoundingClientRect();
      const hRect = host.getBoundingClientRect();
      setTip({
        visible: true,
        text:    txt,
        top:     rect.top  - hRect.top  - 42,
        left:    rect.left - hRect.left + rect.width / 2,
      });
    };

    document.addEventListener("selectionchange", onSel, { passive: true });
    return () => document.removeEventListener("selectionchange", onSel);
  }, [hostRef]);

  const send = useCallback((prefix: string) => {
    const msg = `${prefix}:\n\`\`\`\n${tip.text}\n\`\`\``;
    try {
      // Importa useChatStore in modo lazy per evitare dipendenza circolare al caricamento CDN
      import("@/store/chatStore").then(({ useChatStore: cs }) => {
        cs.getState().setInput?.(msg);
      }).catch(() => {});
    } catch { /* non-blocking */ }
    setTip(p => ({ ...p, visible: false }));
  }, [tip.text]);

  if (!tip.visible || !tip.text) return null;

  return (
    <div
      style={{
        position: "absolute",
        top:  tip.top,
        left: tip.left,
        transform: "translateX(-50%)",
        zIndex: Z_INDEX.BANNER,
        display: "flex", gap: 4,
        background: "rgba(14,16,40,0.97)",
        border: "1px solid rgba(99,102,241,0.35)",
        borderRadius: 8,
        padding: "4px 6px",
        boxShadow: "0 8px 24px rgba(0,0,0,0.6)",
        pointerEvents: "auto",
        userSelect: "none",
      }}
      onMouseDown={e => e.preventDefault()} // evita blur dell'editor
    >
      {[
        { label: "🔍 Spiega", prefix: "Spiega questo codice" },
        { label: "🔧 Fixa",   prefix: "Fixa questo codice" },
      ].map(({ label, prefix }) => (
        <button
          key={label}
          onPointerDown={e => { e.preventDefault(); send(prefix); }}
          style={{
            all: "unset",
            cursor: "pointer",
            padding: "3px 9px",
            borderRadius: 6,
            fontSize: "0.7rem",
            fontWeight: 600,
            color: "#c7d2fe",
            background: "rgba(99,102,241,0.12)",
            border: "1px solid rgba(99,102,241,0.22)",
            touchAction: "manipulation",
            transition: "background 0.1s",
          }}
          onMouseEnter={e => (e.currentTarget.style.background = "rgba(99,102,241,0.25)")}
          onMouseLeave={e => (e.currentTarget.style.background = "rgba(99,102,241,0.12)")}
        >
          {label}
        </button>
      ))}
    </div>
  );
}

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

const CodeMirrorEditor = memo(function CodeMirrorEditor({
  value, onChange, filename = "", onSave, style, tsErrors = [],
  onCursorChange, fontSize = 13, scrollToOffsetRef,
}: CodeMirrorEditorProps) {
  const hostRef           = useRef<HTMLDivElement>(null);
  const viewRef           = useRef<CMEditorView | null>(null);
  const taRef             = useRef<HTMLTextAreaElement>(null);
  const onChangeRef       = useRef(onChange);
  const onSaveRef         = useRef(onSave);
  const onCursorChangeRef = useRef(onCursorChange);
  const latestValue       = useRef(value); // sempre aggiornato nel render per catturare il valore corrente al mount
  const [status, setStatus] = useState<"loading" | "ready" | "error">("loading");

  // ── Inserisce testo al cursore (toolbar mobile) ──────────────────────────
  const insertAtCursor = (text: string) => {
    // Caso 1: CodeMirror montato e pronto
    const view = viewRef.current;
    if (view && status === "ready") {
      view.dispatch(view.state.replaceSelection(text));
      view.focus();
      return;
    }
    // Caso 2: textarea di fallback
    const ta = taRef.current;
    if (!ta) return;
    const s   = ta.selectionStart ?? 0;
    const en  = ta.selectionEnd   ?? 0;
    const next = value.slice(0, s) + text + value.slice(en);
    onChange(next);
    requestAnimationFrame(() => {
      ta.focus();
      ta.selectionStart = ta.selectionEnd = s + text.length;
    });
  };

  // Mantieni refs aggiornate senza rimontare l'editor
  useEffect(() => { onChangeRef.current = onChange; }, [onChange]);
  useEffect(() => { onSaveRef.current        = onSave;          }, [onSave]);
  useEffect(() => { onCursorChangeRef.current = onCursorChange ?? undefined; }, [onCursorChange]);

  // Aggiornamento sincrono nel render: al momento del mount effect questo
  // conterrà il valore più recente passato dal parent (es. dopo switch file).
  latestValue.current = value;

  // ── Mount / remount quando cambia il filename (= lingua) ────────────────
  useEffect(() => {
    if (!hostRef.current) return;
    let aborted = false;
    setStatus("loading");

    const lang = detectLang(filename);

    loadCM()
        .then((CM) => {
        if (aborted || !hostRef.current) return;

        // ── Language extension ─────────────────────────────────────────
        let langExt: object[] = [];
        try {
          if (lang === "python"     && CM.py?.python)     langExt = [CM.py.python()];
          if (lang === "javascript" && CM.js?.javascript)  langExt = [CM.js.javascript({ jsx: false })];
          if (lang === "jsx"        && CM.js?.javascript)  langExt = [CM.js.javascript({ jsx: true })];
          if (lang === "typescript" && CM.js?.javascript)  langExt = [CM.js.javascript({ typescript: true })];
          if (lang === "tsx"        && CM.js?.javascript)  langExt = [CM.js.javascript({ jsx: true, typescript: true })];
          if (lang === "html"       && CM.html?.html)     langExt = [CM.html.html()];
          if (lang === "css"        && CM.css?.css)       langExt = [CM.css.css()];
          if (lang === "json"       && CM.json?.json)     langExt = [CM.json.json()];
          if (lang === "markdown"   && CM.md?.markdown)   langExt = [CM.md.markdown()];
        } catch { /* ignore lang load failure */ }

        // ── Estensioni base ────────────────────────────────────────────
        const extras: object[] = [];
        if (CM.rectangularSelection) extras.push(CM.rectangularSelection());
        if (CM.crosshairCursor)      extras.push(CM.crosshairCursor());
        if (CM.codeFolding)          extras.push(CM.codeFolding());

        const keymapBindings = [
          ...CM.closeBracketsKeymap,
          ...CM.defaultKeymap,
          ...CM.historyKeymap,
          ...CM.foldKeymap,
          ...CM.completionKeymap,
          CM.indentWithTab,
          { key: "Mod-s", run() { onSaveRef.current?.(); return true; } },
        ];

        const extensions = [
          CM.lineNumbers(),
          CM.highlightActiveLineGutter(),
          CM.foldGutter(),
          CM.drawSelection(),
          CM.history(),
          // @ts-expect-error CM.indentOnInput runtime callable not in static type
          CM.indentOnInput(),
          CM.syntaxHighlighting(CM.defaultHighlightStyle, { fallback: true }),
          CM.bracketMatching(),
          CM.closeBrackets(),
          CM.autocompletion(),
          CM.highlightActiveLine(),
          CM.oneDark,
          CM.keymap.of(keymapBindings),
          ...extras,
          ...langExt,
          // Listener: propaga modifiche al parent
          // @ts-expect-error CM updateListener upd type
          CM.EditorView.updateListener.of((upd: { docChanged: boolean; state: { doc: { toString(): string }; selection: { main: { head: number } } } }) => {
            if (upd.docChanged) {
              onChangeRef.current(upd.state.doc.toString());
            }
            // S800: cursor tracking per status bar
            if (onCursorChangeRef.current) {
              try {
                const offset = upd.state.selection.main.head;
                const text   = upd.state.doc.toString().slice(0, offset);
                const lines  = text.split("\n");
                const line   = lines.length;
                const col    = (lines[lines.length - 1]?.length ?? 0) + 1;
                onCursorChangeRef.current(line, col);
              } catch { /* non-blocking */ }
            }
          }),
          // Tema custom per adattarsi alla UI esistente
          CM.EditorView.theme({
            "&": {
              height: "100%",
              fontSize: `${fontSize}px`,
              fontFamily: "ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace",
            },
            ".cm-scroller": { overflow: "auto", lineHeight: "1.7" },
            ".cm-content": { padding: "12px 0", minHeight: "100%" },
            ".cm-gutters": {
              background: "hsl(222 22% 7%)",
              border: "none",
              borderRight: "1px solid rgba(255,255,255,0.06)",
            },
            ".cm-lineNumbers .cm-gutterElement": {
              color: "rgba(150,150,175,0.32)",
              minWidth: "38px",
              padding: "0 8px 0 4px",
            },
            ".cm-foldGutter .cm-gutterElement": {
              color: "rgba(150,150,175,0.25)",
            },
            ".cm-activeLine":      { background: "rgba(255,255,255,0.025)" },
            ".cm-activeLineGutter":{ background: "rgba(255,255,255,0.025)" },
            "&.cm-focused":        { outline: "none" },
            "&.cm-focused .cm-cursor": { borderLeftColor: "#60a5fa", borderLeftWidth: "2px" },
            ".cm-selectionBackground":       { background: "rgba(96,165,250,0.20) !important" },
            "&.cm-focused .cm-selectionBackground": { background: "rgba(96,165,250,0.25) !important" },
          }),
        ];

        // Monta l'editor nel host (svuota prima per sicurezza)
        hostRef.current.innerHTML = "";

        const view = new CM.EditorView({
          state: CM.EditorState.create({
            doc: latestValue.current,
            extensions,
          }),
          parent: hostRef.current,
        });

        viewRef.current = view;
        // S800: espone scrollToOffset per MobileEditorFindBar
        if (scrollToOffsetRef) {
          scrollToOffsetRef.current = (offset: number) => {
            try {
              view.dispatch({ selection: { anchor: offset }, scrollIntoView: true });
              view.focus();
            } catch { /* non-blocking */ }
          };
        }
        if (!aborted) setStatus("ready");
      })
      .catch((err) => {
        console.warn("[CodeMirrorEditor] CDN load failed, fallback to textarea:", err);
        if (!aborted) setStatus("error");
      });

    return () => {
      aborted = true;
      viewRef.current?.destroy();
      viewRef.current = null;
      if (scrollToOffsetRef) scrollToOffsetRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [filename]); // rimonta SOLO quando cambia il file (= lingua)

  // S800-FIX: aggiorna font size senza rimontare CM (solo DOM override)
  useEffect(() => {
    if (!hostRef.current || status !== "ready") return;
    const el = hostRef.current.querySelector(".cm-editor") as HTMLElement | null;
    if (el) el.style.fontSize = `${fontSize}px`;
  }, [fontSize, status]);

  // ── Sincronizza valore esterno → CM (es. switch file a stesso nome) ──────
  useEffect(() => {
    const view = viewRef.current;
    if (!view) return;
    const cur = view.state.doc.toString();
    if (cur === value) return;
    view.dispatch({
      changes: { from: 0, to: cur.length, insert: value },
    });
  }, [value]);

  // ── Navigazione a riga specifica (agent:goto-line) ─────────────────────────
  // Pattern CustomEvent coerente con agent:file-changed / agent:merge-ts-errors.
  // Dispatched da ProblemsPanel dopo setRequestOpenPath (200ms delay per Dexie load).
  // Safari-safe: addEventListener con cleanup, typeof window guard, fail-safe try/catch.
  useEffect(() => {
    if (typeof window === "undefined") return;
    const handler = (e: Event) => {
      const { line } = (e as CustomEvent<{ line: number }>).detail ?? {};
      if (!line || line < 1) return;
      const view = viewRef.current;
      if (!view) return;
      try {
        const text  = view.state.doc.toString();
        const texts = text.split("\n");
        let offset  = 0;
        for (let i = 0; i < Math.min(line - 1, texts.length - 1); i++) {
          offset += (texts[i]?.length ?? 0) + 1;
        }
        view.dispatch({ selection: { anchor: offset }, scrollIntoView: true });
        view.focus();
      } catch { /* fail-safe */ }
    };
    window.addEventListener("agent:goto-line", handler);
    return () => window.removeEventListener("agent:goto-line", handler);
  }, []);

  // ── Tastiera nella textarea di fallback ──────────────────────────────────
  const handleFallbackKey = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if ((e.metaKey || e.ctrlKey) && e.key === "s") {
      e.preventDefault();
      onSave?.();
    }
    if (e.key === "Tab") {
      e.preventDefault();
      const ta = e.currentTarget;
      const s  = ta.selectionStart;
      const en = ta.selectionEnd;
      onChange(value.slice(0, s) + "  " + value.slice(en));
      requestAnimationFrame(() => { ta.selectionStart = ta.selectionEnd = s + 2; });
    }
  };

  return (
    <div style={{
      flex: 1,
      display: "flex",
      flexDirection: "column",
      overflow: "hidden",
      background: "hsl(222 22% 7%)",
      ...style,
    }}>
      {/* ── Toolbar simboli mobile — solo iPhone/iPad ───────────────────── */}
      {_isIOS && (
        <div
          style={{
            display:         "flex",
            flexDirection:   "row",
            overflowX:       "auto",
            overflowY:       "hidden",
            gap:             "2px",
            padding:         "4px 6px",
            background:      "hsl(222 22% 10%)",
            borderBottom:    "1px solid rgba(255,255,255,0.07)",
            flexShrink:      0,
            // B7: WebkitOverflowScrolling rimosso — deprecato iOS 13+, causa doppio layer scroll
            scrollbarWidth:  "none",
          }}
          onScroll={e => e.stopPropagation()}
        >
          {MOBILE_SYMBOLS.map(sym => (
            <button
              key={sym.label}
              onPointerDown={e => {
                // pointerDown evita che il focus lasci l'editor su iOS
                e.preventDefault();
                insertAtCursor(sym.insert);
              }}
              style={{
                flexShrink:     0,
                minWidth:       "34px",
                height:         "30px",
                padding:        "0 6px",
                background:     "hsl(222 22% 16%)",
                border:         "1px solid rgba(255,255,255,0.10)",
                borderRadius:   "5px",
                color:          "#e4e4e7",
                fontSize:       "0.78rem",
                fontFamily:     "ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace",
                cursor:         "pointer",
                userSelect:     "none",
                WebkitUserSelect: "none" as React.CSSProperties["WebkitUserSelect"],
                touchAction:    "manipulation",
              }}
            >
              {sym.label}
            </button>
          ))}
        </div>
      )}

      {/* ── Editor area ────────────────────────────────────────────────── */}
      <div style={{ flex: 1, position: "relative", overflow: "hidden" }}>
        {/* N7: Inline AI tooltip su selezione */}
        <_SelectionTooltip hostRef={hostRef} />
        {/* Host CodeMirror — sempre nel DOM con dimensioni reali */}
        <div
          ref={hostRef}
          style={{
            position: "absolute", inset: 0,
            opacity:       status === "ready" ? 1 : 0,
            pointerEvents: status === "ready" ? "auto" : "none",
            transition:    "opacity 0.12s",
          }}
        />

        {/* Textarea di fallback — visibile durante caricamento o in caso di errore */}
        {status !== "ready" && (
          <textarea
            ref={taRef}
            value={value}
            onChange={e => onChange(e.target.value)}
            onKeyDown={handleFallbackKey}
            spellCheck={false}
            placeholder={
              status === "loading"
                ? "Caricamento editor…"
                : "Editor non disponibile (offline?)"
            }
            style={TA_STYLE}
          />
        )}
      </div>

      {/* GAP-3: TS error panel — compact list below editor */}
      {tsErrors.length > 0 && (
        <div style={{
          flexShrink: 0, maxHeight: "22vh", overflowY: "auto",
          borderTop: "1px solid rgba(248,113,113,0.15)",
          background: "rgba(10,8,22,0.97)",
          scrollbarWidth: "none",
        }}>
          {tsErrors.map((e, i) => (
            <div key={i} style={{
              display: "flex", alignItems: "baseline", gap: 6,
              padding: "3px 10px",
              borderBottom: "1px solid rgba(255,255,255,0.03)",
              fontSize: "0.66rem",
              fontFamily: "ui-monospace,SFMono-Regular,monospace",
              color: e.sev === "error" ? "#f87171" : "#fbbf24",
            }}>
              <span style={{ opacity: 0.45, flexShrink: 0 }}>
                {e.sev === "error" ? "✕" : "△"} L{e.line}
              </span>
              <span style={{ color: "#a0a0c0", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                {e.message}
              </span>
              <span style={{ opacity: 0.35, flexShrink: 0 }}>TS{e.code}</span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
});

export default CodeMirrorEditor;
