import { snippet } from "@codemirror/autocomplete";
import type {
  Completion,
  CompletionContext,
  CompletionResult,
} from "@codemirror/autocomplete";
import type { EditorView } from "@codemirror/view";
import { fetchDslMeta } from "@/api/client";

/**
 * µFrame slash command palette — auto-populated from the backend DSL registry.
 *
 * On first load, uses a minimal fallback set. Once the API responds,
 * the full command list replaces it via loadCommandsFromApi().
 */

interface CmdEntry {
  label: string;
  detail: string;
  section: string;
  apply: Completion["apply"];
}

// Module-level cache — survives re-renders but not full page reload.
let commands: CmdEntry[] | null = null;
let loaded = false;

function insert(text: string): Completion["apply"] {
  return (view: EditorView, _c: Completion, from: number, to: number) => {
    view.dispatch({ changes: { from: from - 1, to, insert: text } });
  };
}

function slashSnippet(template: string): Completion["apply"] {
  const snip = snippet(template);
  return (view: EditorView, c: Completion, from: number, to: number) => {
    snip(view, c, from - 1, to);
  };
}

const FALLBACK_COMMANDS: CmdEntry[] = [
  { label: "page", detail: 'page "Title" 64', section: "Layout", apply: slashSnippet('page "${title}" ${width:64}') },
  { label: "box", detail: 'box light "Title"', section: "Layout", apply: slashSnippet('box ${weight:light} "${title}"') },
  { label: "heading", detail: 'heading 1 "Text"', section: "Content", apply: slashSnippet('heading ${level:1} "${text}"') },
  { label: "text", detail: 'text "Content"', section: "Content", apply: slashSnippet('text "${content}"') },
  { label: "gauge", detail: "gauge label val max", section: "Data", apply: slashSnippet('gauge "${label}" ${value} ${max:100} ${width:28}') },
  { label: "status", detail: "status label state", section: "Data", apply: slashSnippet('status "${label}" ${state:online}') },
];

function getCommands(): CmdEntry[] {
  return commands ?? FALLBACK_COMMANDS;
}

/**
 * Load the full command list from the backend DSL registry.
 * Called once on editor mount.
 */
export async function loadCommandsFromApi(): Promise<void> {
  if (loaded) return;

  try {
    const data = await fetchDslMeta();
    const apiCommands: CmdEntry[] = [];

    for (const cmd of data.commands ?? []) {
      if (!cmd.detail || !cmd.snippet) continue;
      apiCommands.push({
        label: cmd.label,
        detail: cmd.detail,
        section: cmd.section,
        apply: cmd.snippet.includes("${")
          ? slashSnippet(cmd.snippet)
          : insert(cmd.snippet),
      });
    }

    // Dashboard template (not in registry)
    apiCommands.push({
      label: "dashboard",
      detail: "Full dashboard template",
      section: "Template",
      apply: insert(
        `page "Dashboard" 64\n  box double "Node Status"\n    align center\n    text "Reticulum Network Node"\n\n  spacer\n\n  heading 1 "Resources"\n\n  gauge "CPU" 0 100 28 warn=75 crit=90\n  gauge "MEM" 0 100 28 warn=80 crit=95\n\n  spacer\n\n  heading 2 "Network"\n\n  status "Relay East" online\n  status "Bridge South" online\n\n  divider heavy\n  link "Home" "/page/index.mu"`,
      ),
    });

    commands = apiCommands;
    loaded = true;
  } catch {
    // Keep using fallback
  }
}

export function uframeCommandSource(
  ctx: CompletionContext,
): CompletionResult | null {
  const match = ctx.matchBefore(/\/\w*/);
  if (!match || (match.from === match.to && !ctx.explicit)) return null;

  return {
    from: match.from + 1,
    filter: true,
    options: getCommands().map((cmd) => ({
      label: cmd.label,
      detail: cmd.detail,
      section: cmd.section,
      apply: cmd.apply,
      boost: 99,
    })),
  };
}

/**
 * Attribute value hints — suggests valid values based on the keyword
 * on the current line.
 */
const KEYWORD_VALUES: Record<string, { values: string[]; hint: string }> = {
  box:       { values: ["light", "heavy", "double", "rounded"], hint: "border weight" },
  divider:   { values: ["light", "heavy", "double", "dash", "dot"], hint: "divider style" },
  heading:   { values: ["1", "2", "3"], hint: "heading level" },
  list:      { values: ["bullet", "dash", "number", "arrow"], hint: "list style" },
  align:     { values: ["left", "center", "right"], hint: "alignment" },
  status:    { values: ["online", "offline", "degraded", "unknown", "alert"], hint: "state" },
  hnav:      { values: ["bar", "tabs", "pills", "breadcrumb", "underline"], hint: "nav style" },
  vnav:      { values: ["list", "boxed", "tree", "sidebar", "minimal"], hint: "nav style" },
  bigtitle:  { values: ["block", "thin", "pixel"], hint: "font" },
  image:     { values: ["braille", "block", "ascii", "halfblock"], hint: "render mode" },
  dither:    { values: ["floyd", "threshold", "none"], hint: "dither algorithm" },
  source:    { values: ["shell", "file", "json", "python", "rns", "param"], hint: "source type" },
  theme:     { values: ["default", "nouveau", "gothic", "bamboo", "circuit", "brutalist"], hint: "theme" },
};

export function uframeValueHintSource(
  ctx: CompletionContext,
): CompletionResult | null {
  const line = ctx.state.doc.lineAt(ctx.pos);
  const textBefore = line.text.slice(0, ctx.pos - line.from);

  if (textBefore.trimStart().startsWith("/")) return null;

  const kwMatch = textBefore.match(/^\s*(\w+)\s/);
  if (!kwMatch) return null;

  const keyword = kwMatch[1].toLowerCase();
  const entry = KEYWORD_VALUES[keyword];
  if (!entry) return null;

  const quotesBefore = (textBefore.match(/"/g) || []).length;
  if (quotesBefore % 2 !== 0) return null;

  const colonWordMatch = ctx.matchBefore(/\w+:\w*/);
  if (colonWordMatch) {
    return {
      from: colonWordMatch.from,
      filter: false,
      options: entry.values.map((v) => ({
        label: v,
        detail: entry.hint,
        type: "enum" as const,
      })),
    };
  }

  const wordMatch = ctx.matchBefore(/\w+/);
  if (!wordMatch) {
    if (!ctx.explicit) return null;
    return {
      from: ctx.pos,
      options: entry.values.map((v) => ({
        label: v,
        detail: entry.hint,
        type: "enum" as const,
      })),
    };
  }

  const typed = ctx.state.sliceDoc(wordMatch.from, wordMatch.to);
  if (typed.toLowerCase() === keyword) return null;

  return {
    from: wordMatch.from,
    filter: true,
    options: entry.values.map((v) => ({
      label: v,
      detail: entry.hint,
      type: "enum" as const,
      boost: -1,
    })),
  };
}
