import * as pdfjsLib from "pdfjs-dist";
import type { PDFDocumentProxy, PageViewport } from "pdfjs-dist";

pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdf.worker.mjs";

// ─── Types ────────────────────────────────────────────────────────────────────

type AntwortFormat = "einzelfrage" | "mehrfachfrage_ein_antwortfeld";

interface FrageVorlage {
  id: string;
  text: string;
}

interface FieldEntry {
  id: string;
  fieldName: string;      // PDF AcroForm field name, or user-defined for manual
  jsonKey: string;        // Target nested JSON path
  rect: [number, number, number, number]; // PDF user-space [x1, y1, x2, y2]
  page: number;           // 1-indexed
  type: "acroform" | "manual";
  label?: string;
  antwortFormat?: AntwortFormat;
  fragen?: FrageVorlage[];
}

// ─── State ────────────────────────────────────────────────────────────────────

let pdfDoc: PDFDocumentProxy | null = null;
let currentPage = 1;
let viewport: PageViewport | null = null;
let fields: FieldEntry[] = [];
let selectedFieldId: string | null = null;
let isDrawMode = false;
let drawStart: { x: number; y: number } | null = null;
let pendingManualRect: [number, number, number, number] | null = null;
let currentFilename = "";

// ─── DOM refs ─────────────────────────────────────────────────────────────────

const fileInput    = q<HTMLInputElement>("#file-input");
const uploadBtn    = q<HTMLButtonElement>("#upload-btn");
const drawBtn      = q<HTMLButtonElement>("#draw-btn");
const saveBtn      = q<HTMLButtonElement>("#save-btn");
const prevPageBtn  = q<HTMLButtonElement>("#prev-page");
const nextPageBtn  = q<HTMLButtonElement>("#next-page");
const pageInfo     = q<HTMLSpanElement>("#page-info");
const fieldList    = q<HTMLDivElement>("#field-list");
const fieldCount   = q<HTMLSpanElement>("#field-count");
const filenameDisp = q<HTMLSpanElement>("#filename-display");
const uploadPrompt = q<HTMLDivElement>("#upload-prompt");
const canvases     = q<HTMLDivElement>("#canvases");
const pdfCanvas    = q<HTMLCanvasElement>("#pdf-canvas");
const overlayCanvas = q<HTMLCanvasElement>("#overlay-canvas");

// Key panel
const keyPanel    = q<HTMLDivElement>("#key-panel");
const kpFieldName = q<HTMLSpanElement>("#kp-field-name");
const kpTypeBadge = q<HTMLSpanElement>("#kp-type-badge");
const kpNameRow   = q<HTMLDivElement>("#kp-name-row");
const kpFieldId   = q<HTMLInputElement>("#kp-field-id");
const kpJsonKey   = q<HTMLInputElement>("#kp-json-key");
const kpLabel         = q<HTMLInputElement>("#kp-label");
const kpAntwortFormat = q<HTMLSelectElement>("#kp-antwort-format");
const kpFragen        = q<HTMLTextAreaElement>("#kp-fragen");
const kpAssign        = q<HTMLButtonElement>("#kp-assign");
const kpCancel        = q<HTMLButtonElement>("#kp-cancel");
const kpRemove        = q<HTMLButtonElement>("#kp-remove");

function q<T extends Element>(sel: string): T {
  return document.querySelector(sel) as T;
}

function csrfHeaders(extra: Record<string, string> = {}): Record<string, string> {
  const csrf = document.cookie
    .split("; ")
    .find((part) => part.startsWith("bmp_demo_csrf="))
    ?.slice("bmp_demo_csrf=".length);
  return csrf ? { ...extra, "X-BMP-CSRF": decodeURIComponent(csrf) } : extra;
}

// ─── PDF Loading ──────────────────────────────────────────────────────────────

uploadBtn.addEventListener("click", () => fileInput.click());

fileInput.addEventListener("change", async () => {
  const file = fileInput.files?.[0];
  if (file) await loadPDF(file);
});

async function loadPDF(file: File) {
  currentFilename = file.name;
  filenameDisp.textContent = file.name;
  selectedFieldId = null;
  hideKeyPanel();

  const buf = await file.arrayBuffer();
  pdfDoc = await pdfjsLib.getDocument({ data: buf }).promise;

  const acroFields = await extractAcroFields(pdfDoc);
  fields = mergeWithSaved(file.name, acroFields);

  currentPage = 1;
  uploadPrompt.style.display = "none";
  canvases.style.display = "inline-block";
  drawBtn.disabled = false;
  saveBtn.disabled = false;

  await renderPage(1);
  renderFieldList();
}

async function extractAcroFields(doc: PDFDocumentProxy): Promise<FieldEntry[]> {
  const result: FieldEntry[] = [];
  const seen = new Set<string>();

  for (let p = 1; p <= doc.numPages; p++) {
    const page = await doc.getPage(p);
    const annotations = await page.getAnnotations();

    for (const ann of annotations) {
      if (ann.subtype === "Widget" && ann.fieldName) {
        const key = `${ann.fieldName}::p${p}`;
        if (!seen.has(key)) {
          seen.add(key);
          result.push({
            id: `acro-p${p}-${ann.id ?? ann.fieldName}`,
            fieldName: ann.fieldName,
            jsonKey: "",
            rect: ann.rect as [number, number, number, number],
            page: p,
            type: "acroform",
          });
        }
      }
    }
  }
  return result;
}

function mergeWithSaved(filename: string, acroFields: FieldEntry[]): FieldEntry[] {
  const raw = localStorage.getItem(`tb:${filename}`);
  if (!raw) return acroFields;

  const saved: FieldEntry[] = JSON.parse(raw);
  const savedByKey = new Map(saved.map(f => [`${f.fieldName}::p${f.page}`, f]));

  const merged = acroFields.map(f => {
    const s = savedByKey.get(`${f.fieldName}::p${f.page}`);
    return s
      ? {
          ...f,
          jsonKey: s.jsonKey,
          label: s.label,
          antwortFormat: s.antwortFormat,
          fragen: s.fragen,
        }
      : f;
  });

  const manuals = saved.filter(f => f.type === "manual");
  return [...merged, ...manuals];
}

// ─── Page Rendering ───────────────────────────────────────────────────────────

async function renderPage(pageNum: number) {
  if (!pdfDoc) return;

  const page = await pdfDoc.getPage(pageNum);
  viewport = page.getViewport({ scale: 1.5 });

  pdfCanvas.width  = viewport.width;
  pdfCanvas.height = viewport.height;
  overlayCanvas.width  = viewport.width;
  overlayCanvas.height = viewport.height;

  const ctx = pdfCanvas.getContext("2d")!;
  await page.render({ canvasContext: ctx, viewport, canvas: pdfCanvas }).promise;

  renderOverlay();
  updatePagination();
}

function updatePagination() {
  const total = pdfDoc?.numPages ?? 0;
  pageInfo.textContent = total ? `Page ${currentPage} of ${total}` : "—";
  prevPageBtn.disabled = currentPage <= 1;
  nextPageBtn.disabled = !total || currentPage >= total;
}

prevPageBtn.addEventListener("click", async () => {
  if (currentPage > 1) {
    currentPage--;
    selectedFieldId = null;
    hideKeyPanel();
    await renderPage(currentPage);
  }
});

nextPageBtn.addEventListener("click", async () => {
  if (pdfDoc && currentPage < pdfDoc.numPages) {
    currentPage++;
    selectedFieldId = null;
    hideKeyPanel();
    await renderPage(currentPage);
  }
});

// ─── Overlay ─────────────────────────────────────────────────────────────────

function renderOverlay(drawPreview?: { x: number; y: number; w: number; h: number }) {
  if (!viewport) return;
  const ctx = overlayCanvas.getContext("2d")!;
  ctx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);

  for (const field of fields.filter(f => f.page === currentPage)) {
    const [l, t, w, h] = rectToCanvas(field.rect, viewport!);
    const sel    = field.id === selectedFieldId;
    const mapped = field.jsonKey.trim() !== "";

    if (sel) {
      ctx.fillStyle = "rgba(37,99,235,0.12)";
      ctx.fillRect(l, t, w, h);
      ctx.strokeStyle = "#2563eb";
      ctx.lineWidth = 2;
    } else {
      ctx.strokeStyle = mapped ? "#16a34a" : "#ea580c";
      ctx.lineWidth = 1.5;
    }
    ctx.strokeRect(l, t, w, h);

    // Label
    const label = field.jsonKey || field.fieldName;
    ctx.font = "10px monospace";
    ctx.fillStyle = sel ? "#2563eb" : mapped ? "#16a34a" : "#ea580c";
    const labelY = t > 14 ? t - 3 : t + h + 11;
    ctx.fillText(label, l + 2, labelY);
  }

  if (drawPreview) {
    ctx.strokeStyle = "#2563eb";
    ctx.lineWidth = 2;
    ctx.setLineDash([5, 4]);
    ctx.strokeRect(drawPreview.x, drawPreview.y, drawPreview.w, drawPreview.h);
    ctx.setLineDash([]);
  }
}

function rectToCanvas(
  rect: [number, number, number, number],
  vp: PageViewport,
): [number, number, number, number] {
  const [cx1, cy1, cx2, cy2] = vp.convertToViewportRectangle(rect);
  const l = Math.min(cx1, cx2);
  const t = Math.min(cy1, cy2);
  return [l, t, Math.abs(cx2 - cx1), Math.abs(cy2 - cy1)];
}

function canvasToRect(
  x1: number, y1: number, x2: number, y2: number,
  vp: PageViewport,
): [number, number, number, number] {
  const [a, b, c, d, e, f] = vp.transform as [number, number, number, number, number, number];
  const det = a * d - b * c;

  function inv(vx: number, vy: number): [number, number] {
    return [
      (d * vx - c * vy + (c * f - d * e)) / det,
      (-b * vx + a * vy + (b * e - a * f)) / det,
    ];
  }

  const [px1, py1] = inv(x1, y1);
  const [px2, py2] = inv(x2, y2);
  return [
    Math.min(px1, px2), Math.min(py1, py2),
    Math.max(px1, px2), Math.max(py1, py2),
  ];
}

// ─── Mouse on overlay ────────────────────────────────────────────────────────

function canvasPos(e: MouseEvent) {
  const r = overlayCanvas.getBoundingClientRect();
  return {
    x: (e.clientX - r.left) * (overlayCanvas.width / r.width),
    y: (e.clientY - r.top)  * (overlayCanvas.height / r.height),
  };
}

overlayCanvas.addEventListener("click", (e) => {
  if (isDrawMode || !viewport) return;
  const { x, y } = canvasPos(e);

  for (const field of fields.filter(f => f.page === currentPage)) {
    const [l, t, w, h] = rectToCanvas(field.rect, viewport!);
    if (x >= l && x <= l + w && y >= t && y <= t + h) {
      selectField(field.id);
      return;
    }
  }

  selectedFieldId = null;
  renderOverlay();
  hideKeyPanel();
});

overlayCanvas.addEventListener("mousedown", (e) => {
  if (!isDrawMode) return;
  drawStart = canvasPos(e);
});

overlayCanvas.addEventListener("mousemove", (e) => {
  if (!isDrawMode || !drawStart) return;
  const { x, y } = canvasPos(e);
  renderOverlay({
    x: Math.min(drawStart.x, x),
    y: Math.min(drawStart.y, y),
    w: Math.abs(x - drawStart.x),
    h: Math.abs(y - drawStart.y),
  });
});

overlayCanvas.addEventListener("mouseup", (e) => {
  if (!isDrawMode || !drawStart || !viewport) return;
  const { x, y } = canvasPos(e);
  const dx = Math.abs(x - drawStart.x);
  const dy = Math.abs(y - drawStart.y);

  if (dx > 8 && dy > 8) {
    pendingManualRect = canvasToRect(
      Math.min(drawStart.x, x), Math.min(drawStart.y, y),
      Math.max(drawStart.x, x), Math.max(drawStart.y, y),
      viewport,
    );
    exitDrawMode();
    showNewManualPanel();
  } else {
    drawStart = null;
    renderOverlay();
  }

  drawStart = null;
});

// ─── Draw mode ────────────────────────────────────────────────────────────────

drawBtn.addEventListener("click", () => {
  if (isDrawMode) exitDrawMode();
  else enterDrawMode();
});

function enterDrawMode() {
  isDrawMode = true;
  drawBtn.textContent = "Cancel Draw";
  drawBtn.classList.add("active");
  overlayCanvas.style.cursor = "crosshair";
  hideKeyPanel();
}

function exitDrawMode() {
  isDrawMode = false;
  drawBtn.textContent = "Draw Field";
  drawBtn.classList.remove("active");
  overlayCanvas.style.cursor = "default";
  drawStart = null;
  renderOverlay();
}

// ─── Field selection ─────────────────────────────────────────────────────────

function selectField(id: string) {
  selectedFieldId = id;
  renderOverlay();

  const field = fields.find(f => f.id === id)!;

  kpFieldName.textContent = field.fieldName;
  kpTypeBadge.textContent = field.type === "acroform" ? "AcroForm" : "Manual";
  kpTypeBadge.className   = `kp-badge kp-badge-${field.type}`;
  kpJsonKey.value         = field.jsonKey;
  kpLabel.value           = field.label ?? "";
  kpAntwortFormat.value   = field.antwortFormat ?? "einzelfrage";
  kpFragen.value          = (field.fragen ?? []).map((frage) => frage.text).join("\n");
  kpNameRow.classList.add("hidden");
  kpRemove.style.display  = field.type === "manual" ? "" : "none";
  keyPanel.classList.remove("hidden");
  kpJsonKey.focus();

  // Sync sidebar highlight
  document.querySelectorAll(".field-item").forEach(el =>
    el.classList.toggle("selected", el.getAttribute("data-id") === id));
  document.querySelector(`.field-item[data-id="${id}"]`)
    ?.scrollIntoView({ block: "nearest" });
}

function showNewManualPanel() {
  kpFieldName.textContent = "(new manual field)";
  kpTypeBadge.textContent = "Manual";
  kpTypeBadge.className   = "kp-badge kp-badge-manual";
  kpJsonKey.value         = "";
  kpLabel.value           = "";
  kpAntwortFormat.value   = "einzelfrage";
  kpFragen.value          = "";
  kpNameRow.classList.remove("hidden");
  kpFieldId.value         = "";
  kpRemove.style.display  = "none";
  keyPanel.classList.remove("hidden");
  kpFieldId.focus();
}

function hideKeyPanel() {
  keyPanel.classList.add("hidden");
  pendingManualRect = null;
}

// ─── Key panel actions ────────────────────────────────────────────────────────

function normalizeJsonKey(jsonKey: string): string {
  return jsonKey.replace(/\.antwort$/, "").trim();
}

function parseFragen(lines: string, jsonKey: string): FrageVorlage[] {
  const clean = lines
    .split("\n")
    .map((line) => line.trim())
    .filter(Boolean);

  const base = jsonKey.split(".").filter(Boolean).at(-1) || "frage";
  return clean.map((text, index) => ({
    id: `${base}_${index + 1}`,
    text,
  }));
}

kpAssign.addEventListener("click", () => {
  const jsonKey = normalizeJsonKey(kpJsonKey.value);
  const label = kpLabel.value.trim();
  const antwortFormat = kpAntwortFormat.value as AntwortFormat;
  const fragen = parseFragen(kpFragen.value, jsonKey);

  kpJsonKey.value = jsonKey;

  if (pendingManualRect) {
    const name = kpFieldId.value.trim();
    if (!name) { kpFieldId.focus(); return; }

    const entry: FieldEntry = {
      id: `manual-${Date.now()}`,
      fieldName: name,
      jsonKey,
      rect: pendingManualRect,
      page: currentPage,
      type: "manual",
      label: label || undefined,
      antwortFormat,
      fragen,
    };
    fields.push(entry);
    selectedFieldId = entry.id;
    pendingManualRect = null;
  } else if (selectedFieldId) {
    const field = fields.find(f => f.id === selectedFieldId);
    if (field) {
      field.jsonKey = jsonKey;
      field.label = label || undefined;
      field.antwortFormat = antwortFormat;
      field.fragen = fragen;
    }
  }

  persist();
  hideKeyPanel();
  renderOverlay();
  renderFieldList();
});

kpCancel.addEventListener("click", () => {
  pendingManualRect = null;
  selectedFieldId = null;
  hideKeyPanel();
  renderOverlay();
});

kpRemove.addEventListener("click", () => {
  if (!selectedFieldId) return;
  fields = fields.filter(f => f.id !== selectedFieldId);
  selectedFieldId = null;
  persist();
  hideKeyPanel();
  renderOverlay();
  renderFieldList();
});

kpJsonKey.addEventListener("keydown", e => {
  if (e.key === "Enter")  kpAssign.click();
  if (e.key === "Escape") kpCancel.click();
});

kpFieldId.addEventListener("keydown", e => {
  if (e.key === "Enter") kpJsonKey.focus();
});

// ─── Global keyboard shortcuts ────────────────────────────────────────────────

document.addEventListener("keydown", e => {
  if (e.key === "Escape") {
    if (isDrawMode) { exitDrawMode(); return; }
    if (!keyPanel.classList.contains("hidden")) kpCancel.click();
  }
});

// ─── Field list sidebar ───────────────────────────────────────────────────────

function renderFieldList() {
  const mapped = fields.filter(f => f.jsonKey).length;
  fieldCount.textContent = `${mapped} / ${fields.length} mapped`;

  if (fields.length === 0) {
    fieldList.innerHTML = '<p class="field-empty">No fields detected.<br>Use "Draw Field" to add manually.</p>';
    return;
  }

  const byPage = new Map<number, FieldEntry[]>();
  for (const f of fields) {
    if (!byPage.has(f.page)) byPage.set(f.page, []);
    byPage.get(f.page)!.push(f);
  }

  fieldList.innerHTML = "";
  for (const [page, pf] of [...byPage.entries()].sort((a, b) => a[0] - b[0])) {
    const label = document.createElement("div");
    label.className = "field-page-label";
    label.textContent = `Page ${page}`;
    fieldList.appendChild(label);

    for (const field of pf) {
      const item = document.createElement("div");
      item.className = "field-item" +
        (field.id === selectedFieldId ? " selected" : "") +
        (field.jsonKey ? " mapped" : "");
      item.dataset.id = field.id;
      item.innerHTML = `
        <span class="fi-name">${field.fieldName}</span>
        <span class="fi-key">${field.jsonKey || "unmapped"}</span>
      `;
      item.addEventListener("click", async () => {
        if (field.page !== currentPage) {
          currentPage = field.page;
          await renderPage(currentPage);
        }
        selectField(field.id);
      });
      fieldList.appendChild(item);
    }
  }
}

// ─── Save ─────────────────────────────────────────────────────────────────────

function setNested(obj: Record<string, unknown>, path: string, value: unknown): void {
  const parts = path.split(".");
  let cur: Record<string, unknown> = obj;
  for (let i = 0; i < parts.length - 1; i++) {
    const key = parts[i]!;
    if (typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
    cur = cur[key] as Record<string, unknown>;
  }
  cur[parts[parts.length - 1]!] = value;
}

saveBtn.addEventListener("click", async () => {
  const template: Record<string, unknown> = {};
  for (const f of fields) {
    if (!f.jsonKey) continue;

    const isAntwortBlock = f.jsonKey.startsWith("fragen.") || f.jsonKey.startsWith("kriterium.");
    setNested(
      template,
      f.jsonKey,
      isAntwortBlock
        ? {
            fieldName: f.fieldName,
            label: f.label,
            antwortFormat: f.antwortFormat ?? "einzelfrage",
            fragen: f.fragen ?? [],
          }
        : f.fieldName,
    );
  }

  try {
    await fetch("/api/save", {
      method: "POST",
      headers: csrfHeaders({ "Content-Type": "application/json" }),
      body: JSON.stringify(template),
    });
    const orig = saveBtn.textContent;
    saveBtn.textContent = "Saved ✓";
    setTimeout(() => { saveBtn.textContent = orig; }, 2000);
  } catch (err) {
    console.error("Save failed:", err);
  }
});

function persist() {
  if (!currentFilename) return;
  localStorage.setItem(`tb:${currentFilename}`, JSON.stringify(fields));
}
