// deno-lint-ignore-file

import {
  EditorView,
  keymap,
  hoverTooltip,
  Decoration,
  ViewPlugin,
  lineNumbers,
  highlightActiveLineGutter,
  highlightActiveLine
} from "npm:@codemirror/view";

import { EditorState, StateField, Text } from "npm:@codemirror/state";
import { defaultKeymap, history, historyKeymap } from "npm:@codemirror/commands";
import { bracketMatching, indentOnInput } from "npm:@codemirror/language";
import { closeBrackets } from "npm:@codemirror/autocomplete";


import wasm from "./wasm.ts"
import { terminalPlugin } from "./terminal.ts";

import { setAutomaton } from "./visualizer.ts";
import { machine_from_json } from "./automata.ts";
import { sharedText } from "./share.ts";
import { examples } from "./examples.ts";


function tokenize(text: string) {
  try {
    return wasm.lex(text);
  } catch (e) {
    console.log(e)
    return []
  }
}

function compile(text: string): wasm.CompileResult {
  try {
    return wasm.compile(text);
  } catch (e) {
    console.log(e);
    // @ts-expect-error wasm defines extra cleanup 
    return {log: [], log_formatted: "", graph: ""};
  }
}

export const analysisField = StateField.define({
  create(state) {
    const text = state.doc.toString();
    return buildAnalysis(text, state.doc);
  },
  update(value, tr) {
    if (!tr.docChanged) return value;
    const text = tr.state.doc.toString();
    return buildAnalysis(text, tr.state.doc);
  },
  provide: (f) => EditorView.decorations.from(f, (v) => v.deco),
});

function buildAnalysis(text: string, doc: Text) {
  save(text);
  const tokens = tokenize(text);
  const { log, log_formatted, graph } = compile(text);

  if (graph){
    try{
      setAutomaton(machine_from_json(graph))
    }catch(e){
      console.log(e);
    }
  }

  const marks = [];
  const docLen = doc.length;

  for (const tok of tokens) {
    const start = Math.max(0, Math.min(docLen, tok.start));
    const end = Math.max(start, Math.min(docLen, tok.end));
    let tc = tokenClass(tok.kind);
    if (tc === "rb-") {
      tc += tok.scope_level.toString();
    }
    if (end > start) {
      marks.push(Decoration.mark({ class: tc }).range(start, end));
    }
  }

  for (const d of log) {
    if (d.start === undefined || d.end === undefined) continue;
    const start = Math.max(0, Math.min(docLen, d.start));
    const endRaw = d.end == null ? d.start : d.end;
    const end = Math.max(start, Math.min(docLen, endRaw));
    const cls = severityClass(d.level);
    if (end > start) {
      marks.push(Decoration.mark({ class: cls }).range(start, end));
    } else {
      const end = Math.min(docLen, start + 1);
      if (end > start) marks.push(Decoration.mark({ class: cls }).range(start, end));
    }
  }

  const deco = Decoration.set(marks, true);
  return { tokens, log, log_formatted, deco };
}

const tokenClass = (t: string) =>
({
  comment: "tok-comment",
  keyword: "tok-keyword",
  error: "tok-error",
  ident: "tok-ident",
  punc: "tok-punc",
  string: "tok-string",
  lpar: "rb-",
  lbrace: "rb-",
  lbracket: "rb-",

  rpar: "rb-",
  rbrace: "rb-",
  rbracket: "rb-",
}[t] || "tok-ident");


function severityClass(sev: string) {
  const s = (sev || "error").toLowerCase();
  if (s === "warning") return "cm-diag-warning";
  if (s === "info") return "cm-diag-info";
  return "cm-diag-error";
}
function sevRank(sev: string) {
  if (sev === "error") return 3;
  if (sev === "warning") return 2;
  return 1;
}

// ===================== Hover tooltip (uses cached diags) =====================
const diagHover = hoverTooltip((view, pos) => {
  const { log } = view.state.field(analysisField);
  const hits = log.filter((d) => d.start !== undefined && d.end !== undefined && pos >= d.start && pos <= d.end);
  if (hits.length === 0) return null;

  const top = hits.reduce((a, b) => (sevRank(b.level) > sevRank(a.level) ? b : a), hits[0]);

  return {
    pos,
    end: pos,
    above: true,
    create() {
      const dom = document.createElement("div");
      dom.className = "cm-tooltip cm-tooltip-hover";

      const title = document.createElement("div");
      title.className = `tipTitle ${top.level}`;
      title.textContent =
        hits.length === 1 ? top.level.toUpperCase() : `${top.level.toUpperCase()} (${hits.length})`;

      const body = document.createElement("div");
      body.className = "tipBody";
      body.textContent = hits
        .slice()
        .sort((a, b) => sevRank(b.level) - sevRank(a.level))
        .map((h) => `[${h.level.toUpperCase()}] ${h.message}`)
        .join("\n");

      dom.appendChild(title);
      dom.appendChild(body);
      return { dom };
    },
  };
});

function save(text: string){
  globalThis.localStorage.save = text;
}

function getSaved(): string | undefined{
  return globalThis.localStorage.save;
}

export function setText(text: string){
  editor.dispatch({ changes: { from: 0, to: editor.state.doc.length, insert: text } });
}

export function getText(): string{
  return editor.state.doc.toString()
}

const state = EditorState.create({
  doc: sharedText() ?? getSaved() ?? examples[0].machine,
  extensions: [
    lineNumbers(),
    highlightActiveLineGutter(),
    history(),
    indentOnInput(),
    bracketMatching(),
    highlightActiveLine(),
    closeBrackets(),
    keymap.of([...defaultKeymap, ...historyKeymap]),

    analysisField,
    diagHover,
    terminalPlugin,

    EditorView.lineWrapping,
  ],
});

const editor = new EditorView({
  state,
  parent: document.getElementById("editor")!,
});