// /tools/verfahrensablauf client (t-paliad-179 Slice 1)
//
// Abstract-browse surface: pick a proceeding, pick a trigger date,
// see the typical timeline. No Akte, no save-to-project, no anchor
// override editing, no Pathway B cascade. Variant chips + lane view
// (Slice 3) and compare (Slice 4) layer on top of this in later
// slices. Court picker + view toggle + calc fetch + renderers all
// come from ./views/verfahrensablauf-core, which fristenrechner.ts
// shares.

import { initI18n, t, tDyn, getLang, onLangChange } from "./i18n";
import { initSidebar } from "./sidebar";
import {
  type DeadlineResponse,
  calculateDeadlines,
  formatDate,
  populateCourtPicker,
  renderColumnsBody,
  renderTimelineBody,
  wireDateEditClicks,
} from "./views/verfahrensablauf-core";

let selectedType = "";
let lastResponse: DeadlineResponse | null = null;

// Per-rule anchor overrides set by the click-to-edit affordance on
// timeline / column date cells. Posted as `anchorOverrides` to the
// /api/tools/fristenrechner calc so downstream rules re-anchor off the
// user's chosen date. Cleared whenever the trigger changes (proceeding,
// trigger date, flag toggle) so a fresh calc starts unanchored — same
// semantic as /tools/fristenrechner.
const anchorOverrides = new Map<string, string>();
function clearAnchorOverrides() { anchorOverrides.clear(); }

type ProcedureView = "timeline" | "columns";
let procedureView: ProcedureView = "columns";

// Notes toggle — when off (default), per-rule descriptive notes render
// as a compact ⓘ icon next to the meta line (hover for full text). When
// on, the full notes block expands under each card. Choice persists in
// localStorage so a reload or recalc keeps the user's preference.
const NOTES_PREF_KEY = "paliad.fristen.notes-show";
function readNotesPref(): boolean {
  try { return localStorage.getItem(NOTES_PREF_KEY) === "1"; } catch { return false; }
}
function writeNotesPref(on: boolean): void {
  try { localStorage.setItem(NOTES_PREF_KEY, on ? "1" : "0"); } catch { /* no-op */ }
}
let showNotes = readNotesPref();

// Jurisdiction display prefix for the proceeding-summary chip + the
// trigger-event placeholder. Same forum slugs the .proceeding-group
// `data-forum` attribute carries in verfahrensablauf.tsx /
// fristenrechner.tsx (upc / de / epa / dpma). Disambiguates the
// 4 redundancies in the corpus (UPC Verletzungsverfahren vs DE
// Verletzungsklage etc.) once the picker collapses.
const FORUM_LABEL: Record<string, string> = {
  upc: "UPC",
  de: "DE",
  epa: "EPA",
  dpma: "DPMA",
};

function jurisdictionFor(btn: HTMLButtonElement): string {
  const group = btn.closest<HTMLElement>(".proceeding-group");
  const forum = group?.dataset.forum || "";
  return FORUM_LABEL[forum] || "";
}

function proceedingDisplayName(btn: HTMLButtonElement): string {
  const name = btn.querySelector("strong")?.textContent || "";
  const jur = jurisdictionFor(btn);
  return jur ? `${jur} ${name}` : name;
}

function activeProceedingButton(): HTMLButtonElement | null {
  return document.querySelector<HTMLButtonElement>(".proceeding-btn.active");
}

// Auto-calc plumbing — sequence + debounce mirror /tools/fristenrechner
// so rapid input changes never let a stale response overwrite a fresh
// one.
let calcSeq = 0;
let calcTimer: ReturnType<typeof setTimeout> | null = null;

function scheduleCalc(delayMs = 200) {
  if (calcTimer !== null) clearTimeout(calcTimer);
  calcTimer = setTimeout(() => {
    calcTimer = null;
    void doCalc();
  }, delayMs);
}

function showStep(n: number) {
  for (let i = 1; i <= 3; i++) {
    const el = document.getElementById(`step-${i}`);
    if (el) el.style.display = i <= n ? "block" : "none";
  }
}

// Read the proceeding-specific flag checkboxes and assemble the
// payload the calculator expects. Mirrors fristenrechner.ts so the
// gating semantics stay identical: with_amend on upc.inf.cfi is
// nested under with_ccr (R.30 is only available with a CCR);
// upc.rev.cfi exposes with_amend + with_cci as two independent
// gates. R.19 Einspruch is NOT flag-gated (mig 098, m's 2026-05-18
// call): it's just an always-available optional submission, so it
// has no checkbox.
function readFlags(): string[] {
  const ccr      = document.getElementById("ccr-flag")        as HTMLInputElement | null;
  const infAmend = document.getElementById("inf-amend-flag")  as HTMLInputElement | null;
  const revAmend = document.getElementById("rev-amend-flag")  as HTMLInputElement | null;
  const revCci   = document.getElementById("rev-cci-flag")    as HTMLInputElement | null;
  const flags: string[] = [];
  if (selectedType === "upc.inf.cfi") {
    if (ccr?.checked)                       flags.push("with_ccr");
    if (ccr?.checked && infAmend?.checked)  flags.push("with_amend");
  }
  if (selectedType === "upc.rev.cfi") {
    if (revAmend?.checked) flags.push("with_amend");
    if (revCci?.checked)   flags.push("with_cci");
  }
  return flags;
}

async function doCalc() {
  const seq = ++calcSeq;
  const dateInput = document.getElementById("trigger-date") as HTMLInputElement | null;
  const triggerDate = dateInput?.value || "";
  if (!triggerDate || !selectedType) return;

  const courtPickerRow = document.getElementById("court-picker-row");
  const courtPicker = document.getElementById("court-picker") as HTMLSelectElement | null;
  const courtId = courtPickerRow && courtPickerRow.style.display !== "none" && courtPicker?.value
    ? courtPicker.value
    : "";

  const overrides: Record<string, string> = {};
  for (const [code, date] of anchorOverrides) overrides[code] = date;

  const data = await calculateDeadlines({
    proceedingType: selectedType,
    triggerDate,
    flags: readFlags(),
    anchorOverrides: overrides,
    courtId,
  });
  if (seq !== calcSeq) return;
  if (!data) return;
  lastResponse = data;
  renderResults(data);
  showStep(3);
}

// triggerEventLabelFor picks the user-facing "Auslösendes Ereignis"
// label from the calc response. The root rule (isRootEvent=true) is
// the first event in the proceeding — e.g. Klageerhebung for
// upc.inf.cfi, Nichtigkeitsklage for upc.rev.cfi. Falls back to the
// active proceeding name if no root rule fires (shouldn't happen for
// healthy data, but safer than a blank).
function triggerEventLabelFor(data: DeadlineResponse): string {
  const root = data.deadlines.find((d) => d.isRootEvent);
  if (root) {
    return getLang() === "en" ? (root.nameEN || root.name) : (root.name || root.nameEN);
  }
  return data.proceedingName || "";
}

function syncTriggerEventLabel() {
  const triggerEventEl = document.getElementById("trigger-event");
  if (!triggerEventEl) return;
  if (lastResponse) {
    triggerEventEl.textContent = triggerEventLabelFor(lastResponse);
  } else {
    triggerEventEl.textContent = "—";
  }
}

function renderResults(data: DeadlineResponse) {
  const container = document.getElementById("timeline-container");
  if (!container) return;
  const printBtn = document.getElementById("fristen-print-btn");
  const toggle = document.getElementById("fristen-view-toggle");

  // Header shows the picked proceeding with its jurisdiction prefix
  // so the user can tell UPC Verletzungsverfahren apart from DE
  // Verletzungsklage once the picker collapses.
  const activeBtn = activeProceedingButton();
  const procName = activeBtn ? proceedingDisplayName(activeBtn)
    : tDyn(`deadlines.${data.proceedingType.toLowerCase()}`);
  const headerHtml = `<div class="timeline-header">
    <strong>${procName}</strong>
    <span class="timeline-trigger-date">${t("deadlines.trigger.label")}: ${formatDate(data.triggerDate)}</span>
  </div>`;

  const bodyHtml = procedureView === "columns"
    ? renderColumnsBody(data, { editable: true, showNotes })
    : renderTimelineBody(data, { showParty: true, editable: true, showNotes });

  container.innerHTML = headerHtml + bodyHtml;
  if (printBtn) printBtn.style.display = "block";
  if (toggle) toggle.style.display = "";

  syncTriggerEventLabel();
}

function setProceedingPickerCollapsed(collapsed: boolean, displayName?: string) {
  const groups = document.querySelectorAll<HTMLElement>(".proceeding-group");
  const summary = document.getElementById("proceeding-summary") as HTMLElement | null;
  const summaryName = document.getElementById("proceeding-summary-name");
  groups.forEach((g) => { g.style.display = collapsed ? "none" : ""; });
  if (summary) summary.style.display = collapsed ? "" : "none";
  if (summaryName && displayName) summaryName.textContent = displayName;
}

// syncFlagRows shows/hides the proceeding-specific checkbox rows
// based on selectedType. Same disposition as fristenrechner.ts —
// the with_amend nested-under-ccr semantic is enforced via
// syncInfAmendEnabled().
function syncFlagRows() {
  const show = (id: string, when: boolean) => {
    const el = document.getElementById(id);
    if (el) el.style.display = when ? "" : "none";
  };
  show("ccr-flag-row",       selectedType === "upc.inf.cfi");
  show("inf-amend-flag-row", selectedType === "upc.inf.cfi");
  show("rev-amend-flag-row", selectedType === "upc.rev.cfi");
  show("rev-cci-flag-row",   selectedType === "upc.rev.cfi");
  syncInfAmendEnabled();
}

// R.30 amendment-application is only available with a CCR — disable
// (and clear) the nested inf-amend checkbox while ccr is off so the
// calc payload stays coherent. Mirrors fristenrechner.ts.
function syncInfAmendEnabled() {
  const ccr      = document.getElementById("ccr-flag")       as HTMLInputElement | null;
  const infAmend = document.getElementById("inf-amend-flag") as HTMLInputElement | null;
  if (!ccr || !infAmend) return;
  infAmend.disabled = !ccr.checked;
  if (!ccr.checked) infAmend.checked = false;
}

function selectProceeding(btn: HTMLButtonElement) {
  document.querySelectorAll(".proceeding-btn").forEach((b) => b.classList.remove("active"));
  btn.classList.add("active");
  const nextType = btn.dataset.code || "";
  // Different proceeding tree → previously-set overrides reference
  // rule codes that don't exist in the new tree. Clear before the
  // next calc so the fresh proceeding starts unanchored.
  if (selectedType !== nextType) clearAnchorOverrides();
  selectedType = nextType;

  // Trigger-event label fires from the calc response (root rule).
  // Until step 3 renders, fall back to an em-dash placeholder.
  lastResponse = null;
  syncTriggerEventLabel();

  void populateCourtPicker("court-picker-row", "court-picker", selectedType);
  syncFlagRows();

  setProceedingPickerCollapsed(true, proceedingDisplayName(btn));

  showStep(2);
  scheduleCalc(0);
}

function initViewToggle() {
  const toggle = document.getElementById("fristen-view-toggle");
  if (!toggle) return;

  const initial = new URLSearchParams(window.location.search).get("view");
  if (initial === "timeline") procedureView = "timeline";

  toggle.querySelectorAll<HTMLInputElement>("input[name=fristen-view]").forEach((input) => {
    input.checked = input.value === procedureView;
    input.addEventListener("change", () => {
      if (!input.checked) return;
      procedureView = input.value === "columns" ? "columns" : "timeline";
      const url = new URL(window.location.href);
      if (procedureView === "columns") {
        url.searchParams.delete("view");
      } else {
        url.searchParams.set("view", procedureView);
      }
      history.replaceState(null, "", url.pathname + (url.search ? url.search : "") + url.hash);
      if (lastResponse) renderResults(lastResponse);
    });
  });

  toggle.style.display = "none";
}

document.addEventListener("DOMContentLoaded", () => {
  initI18n();
  initSidebar();

  document.querySelectorAll<HTMLButtonElement>(".proceeding-btn").forEach((btn) => {
    btn.addEventListener("click", () => selectProceeding(btn));
  });

  document.getElementById("proceeding-summary-reselect")?.addEventListener("click", () => {
    setProceedingPickerCollapsed(false);
  });

  document.getElementById("calculate-btn")?.addEventListener("click", () => scheduleCalc(0));

  const dateInput = document.getElementById("trigger-date") as HTMLInputElement | null;
  if (dateInput) {
    dateInput.addEventListener("change", () => scheduleCalc());
    dateInput.addEventListener("input", () => scheduleCalc());
    dateInput.addEventListener("keydown", (e) => {
      if ((e as KeyboardEvent).key === "Enter") scheduleCalc(0);
    });
  }

  const courtPicker = document.getElementById("court-picker") as HTMLSelectElement | null;
  if (courtPicker) courtPicker.addEventListener("change", () => scheduleCalc(0));

  // Flag-checkbox listeners — each flip triggers a fresh calc so the
  // timeline re-projects with the new gating. ccr-flag additionally
  // enables/disables the nested inf-amend row.
  const ccrFlag = document.getElementById("ccr-flag") as HTMLInputElement | null;
  if (ccrFlag) ccrFlag.addEventListener("change", () => {
    syncInfAmendEnabled();
    scheduleCalc(0);
  });
  (["inf-amend-flag", "rev-amend-flag", "rev-cci-flag"]).forEach((id) => {
    const cb = document.getElementById(id) as HTMLInputElement | null;
    if (cb) cb.addEventListener("change", () => scheduleCalc(0));
  });

  document.getElementById("fristen-print-btn")?.addEventListener("click", () => window.print());

  // Click-to-edit on timeline / column date cells — same delegated
  // pattern as /tools/fristenrechner. Survives renderResults()'s
  // innerHTML rewrites because the listener lives on the container.
  const timelineContainer = document.getElementById("timeline-container");
  if (timelineContainer) {
    wireDateEditClicks(timelineContainer, (ruleCode, newValue) => {
      if (newValue === "") {
        anchorOverrides.delete(ruleCode);
      } else {
        anchorOverrides.set(ruleCode, newValue);
      }
      scheduleCalc(0);
    });
  }

  // Notes toggle — restores last preference on load + re-renders when
  // the user flips it. Lives in the same toggle bar as the view picker.
  const notesShowCb = document.getElementById("fristen-notes-show") as HTMLInputElement | null;
  if (notesShowCb) {
    notesShowCb.checked = showNotes;
    notesShowCb.addEventListener("change", () => {
      showNotes = notesShowCb.checked;
      writeNotesPref(showNotes);
      if (lastResponse) renderResults(lastResponse);
    });
  }

  initViewToggle();

  onLangChange(() => {
    // Active-button name updates with language change (the data-i18n
    // pass swaps the inner <strong>'s text). Re-collapse the summary
    // chip and re-derive the trigger event label from the lang-current
    // calc response.
    const activeBtn = activeProceedingButton();
    if (activeBtn) {
      const summary = document.getElementById("proceeding-summary-name");
      if (summary) summary.textContent = proceedingDisplayName(activeBtn);
    }
    if (lastResponse) renderResults(lastResponse);
    syncTriggerEventLabel();
  });

  // Pre-select the first proceeding tile so users see a timeline
  // immediately on landing — matches /tools/fristenrechner behaviour.
  const firstBtn = document.querySelector<HTMLButtonElement>(".proceeding-btn");
  if (firstBtn) selectProceeding(firstBtn);
});
