import { initI18n } from "./i18n";
import { initSidebar } from "./sidebar";
import { escapeHtml } from "../lib/docforge-editor/dom";
import { fetchVariableCatalogue, type VariableEntry } from "../lib/docforge-editor/catalogue";

// t-paliad-349 docforge slice 6 — client for the template authoring page.
//
// Flow: list templates → upload a .docx (or open one) → the carrier renders
// as run spans (<span class="docforge-run" data-run="N">) → the admin
// selects text within one run, then clicks a variable in the palette → the
// server injects {{slot}} at the selection and returns the updated view.
//
// The select-then-pick gesture keys on the run index (data-run) + the
// selected text, matching the server's text-based InjectSlot so umlauts
// can't desync the selection from the slice. Selections that span more than
// one run are rejected with a hint (v1 scope: single-run text slots).

interface TemplateMeta {
  id: string;
  slug?: string;
  name_de: string;
  name_en: string;
  kind: string;
  source_format: string;
  firm?: string;
  is_active: boolean;
  version: number;
}

interface TemplateSlot {
  key: string;
  anchor: string;
  label?: string;
  order_index: number;
}

interface AuthoringView {
  template: TemplateMeta;
  preview_html: string;
  slots: TemplateSlot[];
}

interface Selection1Run {
  runIndex: number;
  text: string;
}

interface State {
  catalogue: VariableEntry[];
  openID: string | null;
  activeSlotKey: string | null;
  selection: Selection1Run | null;
}

const state: State = {
  catalogue: [],
  openID: null,
  activeSlotKey: null,
  selection: null,
};

function isEN(): boolean {
  return (document.documentElement.lang || "de").toLowerCase().startsWith("en");
}

function labelOf(e: VariableEntry): string {
  return isEN() ? e.label_en : e.label_de;
}

async function boot(): Promise<void> {
  initI18n();
  initSidebar();

  try {
    state.catalogue = await fetchVariableCatalogue();
  } catch (err) {
    console.warn("templates-authoring: catalogue fetch failed", err);
  }

  wireUploadForm();
  await loadList();
}

async function loadList(): Promise<void> {
  const host = document.getElementById("docforge-template-list");
  if (!host) return;
  let metas: TemplateMeta[] = [];
  try {
    const res = await fetch("/api/admin/templates", { headers: { Accept: "application/json" } });
    if (res.ok) {
      const body = (await res.json()) as { templates: TemplateMeta[] };
      metas = body.templates ?? [];
    }
  } catch (err) {
    console.warn("templates-authoring: list fetch failed", err);
  }
  if (metas.length === 0) {
    host.innerHTML = `<li class="docforge-template-empty">${escapeHtml(isEN() ? "No templates yet." : "Noch keine Vorlagen.")}</li>`;
    return;
  }
  host.innerHTML = metas
    .map((m) => {
      const name = isEN() ? m.name_en : m.name_de;
      const firm = m.firm ? ` · ${escapeHtml(m.firm)}` : "";
      return `<li class="docforge-template-row" data-template-id="${escapeHtml(m.id)}">
        <span class="docforge-template-name">${escapeHtml(name)}</span>
        <span class="docforge-template-meta">v${m.version}${firm}</span>
      </li>`;
    })
    .join("");
  host.querySelectorAll<HTMLLIElement>(".docforge-template-row").forEach((li) => {
    li.addEventListener("click", () => {
      const id = li.dataset.templateId;
      if (id) void openTemplate(id);
    });
  });
}

function wireUploadForm(): void {
  const form = document.getElementById("docforge-upload-form") as HTMLFormElement | null;
  if (!form) return;
  form.addEventListener("submit", async (ev) => {
    ev.preventDefault();
    const status = document.getElementById("docforge-upload-status");
    const data = new FormData(form);
    setText(status, isEN() ? "Uploading…" : "Lädt hoch…");
    try {
      const res = await fetch("/api/admin/templates", { method: "POST", body: data });
      if (!res.ok) {
        const body = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
        setText(status, (isEN() ? "Error: " : "Fehler: ") + (body.error ?? res.status));
        return;
      }
      const view = (await res.json()) as AuthoringView;
      setText(status, "");
      form.reset();
      await loadList();
      openView(view);
    } catch (err) {
      setText(status, (isEN() ? "Error: " : "Fehler: ") + String(err));
    }
  });
}

async function openTemplate(id: string): Promise<void> {
  try {
    const res = await fetch(`/api/admin/templates/${encodeURIComponent(id)}`, {
      headers: { Accept: "application/json" },
    });
    if (!res.ok) return;
    openView((await res.json()) as AuthoringView);
  } catch (err) {
    console.warn("templates-authoring: open failed", err);
  }
}

function openView(view: AuthoringView): void {
  state.openID = view.template.id;
  state.activeSlotKey = null;
  state.selection = null;

  const workspace = document.getElementById("docforge-workspace");
  if (workspace) workspace.hidden = false;

  const title = document.getElementById("docforge-workspace-title");
  if (title) {
    const name = isEN() ? view.template.name_en : view.template.name_de;
    title.textContent = `${name} · v${view.template.version}`;
  }

  renderPreview(view.preview_html);
  renderSlots(view.slots);
  renderPalette();
  setWorkspaceStatus("");
}

function renderPreview(html: string): void {
  const host = document.getElementById("docforge-preview");
  if (!host) return;
  host.innerHTML = html;
  host.addEventListener("mouseup", onPreviewSelect);
}

// onPreviewSelect captures a selection that lies entirely within one run
// span; otherwise it clears the pending selection and hints.
function onPreviewSelect(): void {
  const sel = window.getSelection();
  if (!sel || sel.isCollapsed || sel.rangeCount === 0) {
    state.selection = null;
    return;
  }
  const text = sel.toString();
  if (text === "") {
    state.selection = null;
    return;
  }
  const anchorRun = closestRun(sel.anchorNode);
  const focusRun = closestRun(sel.focusNode);
  if (!anchorRun || anchorRun !== focusRun) {
    state.selection = null;
    setWorkspaceStatus(isEN()
      ? "Select within a single text span."
      : "Bitte innerhalb einer Textstelle markieren.");
    return;
  }
  const runIndex = Number(anchorRun.dataset.run);
  if (Number.isNaN(runIndex)) {
    state.selection = null;
    return;
  }
  state.selection = { runIndex, text };
  setWorkspaceStatus(state.activeSlotKey
    ? (isEN() ? `Click to bind “${text}” → ${state.activeSlotKey}` : `Variable wählen, um „${text}“ zu setzen`)
    : (isEN() ? `Selected “${text}” — now pick a variable.` : `„${text}" markiert — jetzt Variable wählen.`));
}

function closestRun(node: Node | null): HTMLElement | null {
  let el: Node | null = node;
  while (el && el !== document.body) {
    if (el instanceof HTMLElement && el.classList.contains("docforge-run")) return el;
    el = el.parentNode;
  }
  return null;
}

// renderPalette groups catalogue entries by their namespace group and wires
// each as a click-to-place control.
function renderPalette(): void {
  const host = document.getElementById("docforge-palette");
  if (!host) return;
  if (state.catalogue.length === 0) {
    host.innerHTML = `<p class="docforge-palette-empty">${escapeHtml(isEN() ? "No variables." : "Keine Variablen.")}</p>`;
    return;
  }
  const groups = new Map<string, VariableEntry[]>();
  for (const e of state.catalogue) {
    const arr = groups.get(e.group) ?? [];
    arr.push(e);
    groups.set(e.group, arr);
  }
  let html = `<h3>${escapeHtml(isEN() ? "Variables" : "Variablen")}</h3>`;
  for (const [group, entries] of groups) {
    html += `<div class="docforge-palette-group"><h4>${escapeHtml(group)}</h4>`;
    for (const e of entries) {
      html += `<button type="button" class="docforge-palette-var" data-slot-key="${escapeHtml(e.key)}" title="{{${escapeHtml(e.key)}}}">${escapeHtml(labelOf(e))}</button>`;
    }
    html += `</div>`;
  }
  host.innerHTML = html;
  host.querySelectorAll<HTMLButtonElement>(".docforge-palette-var").forEach((btn) => {
    btn.addEventListener("click", () => onPaletteClick(btn.dataset.slotKey ?? "", btn));
  });
}

function onPaletteClick(slotKey: string, btn: HTMLButtonElement): void {
  state.activeSlotKey = slotKey;
  const host = document.getElementById("docforge-palette");
  host?.querySelectorAll(".docforge-palette-var--active").forEach((el) => el.classList.remove("docforge-palette-var--active"));
  btn.classList.add("docforge-palette-var--active");

  if (state.selection) {
    void placeSlot(state.selection.runIndex, state.selection.text, slotKey);
  } else {
    setWorkspaceStatus(isEN()
      ? `${slotKey} selected — now highlight the text to replace.`
      : `${slotKey} gewählt — jetzt den zu ersetzenden Text markieren.`);
  }
}

async function placeSlot(runIndex: number, selectedText: string, slotKey: string): Promise<void> {
  if (!state.openID) return;
  setWorkspaceStatus(isEN() ? "Placing…" : "Setze…");
  try {
    const res = await fetch(`/api/admin/templates/${encodeURIComponent(state.openID)}/slots`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ run_index: runIndex, selected_text: selectedText, slot_key: slotKey }),
    });
    if (!res.ok) {
      const body = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
      setWorkspaceStatus((isEN() ? "Error: " : "Fehler: ") + (body.error ?? res.status));
      return;
    }
    openView((await res.json()) as AuthoringView);
  } catch (err) {
    setWorkspaceStatus((isEN() ? "Error: " : "Fehler: ") + String(err));
  }
}

function renderSlots(slots: TemplateSlot[]): void {
  const host = document.getElementById("docforge-slot-list");
  if (!host) return;
  if (slots.length === 0) {
    host.innerHTML = `<li class="docforge-slot-empty">${escapeHtml(isEN() ? "No slots yet." : "Noch keine Platzhalter.")}</li>`;
    return;
  }
  host.innerHTML = slots
    .map((s) => `<li class="docforge-slot-row" data-slot="${escapeHtml(s.key)}"><code>{{${escapeHtml(s.key)}}}</code></li>`)
    .join("");
}

function setWorkspaceStatus(msg: string): void {
  setText(document.getElementById("docforge-workspace-status"), msg);
}

function setText(el: Element | null, msg: string): void {
  if (el) el.textContent = msg;
}

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", () => void boot());
} else {
  void boot();
}
