import { useEffect, useRef, useCallback } from "react";
import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightActiveLine, drawSelection, dropCursor } from "@codemirror/view";
import { EditorState, Compartment } from "@codemirror/state";
import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
import { sql, MySQL, PostgreSQL, StandardSQL, MSSQL, SQLite } from "@codemirror/lang-sql";
import { oneDark } from "@codemirror/theme-one-dark";
import { bracketMatching, indentOnInput, syntaxHighlighting, defaultHighlightStyle, foldGutter, foldKeymap } from "@codemirror/language";
import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
import { lintKeymap } from "@codemirror/lint";

interface SqlEditorProps {
  value: string;
  onChange: (value: string) => void;
  onAnalyze: () => void;
  dialect: string;
}

const dialectMap: Record<string, unknown> = {
  mysql: MySQL,
  postgres: PostgreSQL,
  tsql: MSSQL,
  sqlite: SQLite,
  ansi: StandardSQL,
};

const dialectCompartment = new Compartment();

function getDialectExtension(dialect: string) {
  const schema = dialectMap[dialect] ?? StandardSQL;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return sql({ dialect: schema as any });
}

export function SqlEditor({ value, onChange, onAnalyze, dialect }: SqlEditorProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const viewRef = useRef<EditorView | null>(null);

  const onAnalyzeRef = useRef(onAnalyze);
  onAnalyzeRef.current = onAnalyze;

  // Create editor once
  useEffect(() => {
    if (!containerRef.current) return;

    const analyzeKeymap = keymap.of([
      {
        key: "Ctrl-Enter",
        mac: "Cmd-Enter",
        run: () => {
          onAnalyzeRef.current();
          return true;
        },
      },
    ]);

    const state = EditorState.create({
      doc: value,
      extensions: [
        lineNumbers(),
        highlightActiveLineGutter(),
        highlightActiveLine(),
        history(),
        drawSelection(),
        dropCursor(),
        indentOnInput(),
        bracketMatching(),
        closeBrackets(),
        autocompletion(),
        foldGutter(),
        syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
        dialectCompartment.of(getDialectExtension(dialect)),
        oneDark,
        keymap.of([
          ...closeBracketsKeymap,
          ...defaultKeymap,
          ...historyKeymap,
          ...foldKeymap,
          ...completionKeymap,
          ...lintKeymap,
          indentWithTab,
        ]),
        analyzeKeymap,
        EditorView.updateListener.of((update) => {
          if (update.docChanged) {
            onChange(update.state.doc.toString());
          }
        }),
        EditorView.theme({
          "&": {
            height: "100%",
            backgroundColor: "oklch(0.10 0.008 264)",
          },
          ".cm-content": {
            padding: "12px 0",
            caretColor: "oklch(0.68 0.16 210)",
          },
          ".cm-line": {
            padding: "0 16px 0 8px",
          },
          ".cm-cursor": {
            borderLeftColor: "oklch(0.68 0.16 210)",
            borderLeftWidth: "2px",
          },
        }),
      ],
    });

    const view = new EditorView({ state, parent: containerRef.current });
    viewRef.current = view;

    return () => {
      view.destroy();
      viewRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Sync dialect changes
  useEffect(() => {
    const view = viewRef.current;
    if (!view) return;
    view.dispatch({
      effects: dialectCompartment.reconfigure(getDialectExtension(dialect)),
    });
  }, [dialect]);

  // Sync external value changes (e.g. "apply formatted")
  const lastValueRef = useRef(value);
  useEffect(() => {
    const view = viewRef.current;
    if (!view) return;
    const current = view.state.doc.toString();
    if (current !== value && value !== lastValueRef.current) {
      view.dispatch({
        changes: { from: 0, to: current.length, insert: value },
      });
    }
    lastValueRef.current = value;
  }, [value]);

  // Jump to line
  const jumpToLine = useCallback((lineNo: number) => {
    const view = viewRef.current;
    if (!view) return;
    const line = view.state.doc.line(Math.max(1, Math.min(lineNo, view.state.doc.lines)));
    view.dispatch({
      selection: { anchor: line.from },
      scrollIntoView: true,
    });
    view.focus();
  }, []);

  // Expose jumpToLine via a custom event
  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    const handler = (e: Event) => {
      const lineNo = (e as CustomEvent<number>).detail;
      jumpToLine(lineNo);
    };
    el.addEventListener("jump-to-line", handler);
    return () => el.removeEventListener("jump-to-line", handler);
  }, [jumpToLine]);

  return (
    <div
      ref={containerRef}
      className="h-full w-full overflow-hidden"
      style={{ fontFamily: "'JetBrains Mono', monospace" }}
      data-editor-container
    />
  );
}
