import { initI18n, t, tDyn } from "./i18n";
import { initSidebar } from "./sidebar";
import {
  attachEventTypePicker,
  eventTypeLabel,
  fetchEventTypes,
  type EventType,
  type PickerHandle,
} from "./event-types";
import { projectIndent } from "./project-indent";

let eventTypePicker: PickerHandle | null = null;
let currentUserAdmin = false;
let eventTypesByID = new Map<string, EventType>();
// expandedOverride flips to true when the user clicks "Anderen Typ
// wählen" on the collapsed inline summary. Sticky for the rest of the
// form session — cleared only when the user reverts the rule to "Keine
// Regel". When true, the picker stays visible regardless of whether
// the chip matches the rule's canonical default.
let expandedOverride = false;

interface Project {
  id: string;
  reference?: string | null;
  title: string;
  path: string;
}

interface DeadlineRule {
  id: string;
  code?: string;
  name: string;
  name_en: string;
  rule_code?: string;
  // t-paliad-165 — canonical event_type for this rule's concept,
  // hydrated server-side from paliad.deadline_concept_event_types.
  // Drives auto-fill of the Typ chip when the user picks this rule.
  concept_default_event_type_id?: string | null;
}

// Rules indexed by id so the Regel-change handler can look up the
// concept's canonical event_type without re-fetching.
let rulesByID = new Map<string, DeadlineRule>();

// Last event_type the rule auto-filled. Tracked so we can tell whether
// the picker still reflects the rule's suggestion (replace silently on
// new rule pick) or whether the user has manually edited (leave alone,
// surface the mismatch warning instead).
let lastAutoFilledEventTypeID: string | null = null;

let preselectedProjectID = "";

function esc(s: string): string {
  const d = document.createElement("div");
  d.textContent = s;
  return d.innerHTML;
}

function showError(msg: string) {
  const el = document.getElementById("deadline-new-msg")!;
  el.textContent = msg;
  el.className = "form-msg form-msg-error";
}

async function loadProjects() {
  const sel = document.getElementById("deadline-project") as HTMLSelectElement;
  const hint = document.getElementById("deadline-project-empty-hint")!;
  try {
    const resp = await fetch("/api/projects");
    if (!resp.ok) return;
    const projects: Project[] = await resp.json();
    if (projects.length === 0) {
      hint.style.display = "";
      hint.innerHTML = `${esc(t("deadlines.field.akte.empty"))} <a href="/projects/new">${esc(t("deadlines.field.akte.empty.link"))}</a>`;
      return;
    }
    const options: string[] = [
      `<option value="" disabled${preselectedProjectID ? "" : " selected"} data-i18n="deadlines.field.akte.choose">${esc(t("deadlines.field.akte.choose"))}</option>`,
    ];
    for (const p of projects) {
      const isSelected = preselectedProjectID === p.id ? " selected" : "";
      const ref = p.reference || "";
      const indent = projectIndent(p.path);
      options.push(
        `<option value="${esc(p.id)}"${isSelected}>${indent}${esc(ref)} \u2014 ${esc(p.title)}</option>`,
      );
    }
    sel.innerHTML = options.join("");
  } catch {
    /* non-fatal */
  }
}

async function loadRules() {
  // Optional: load rules so user can attach. We pull all rules; small set.
  const sel = document.getElementById("deadline-rule") as HTMLSelectElement;
  try {
    const resp = await fetch("/api/deadline-rules");
    if (!resp.ok) return;
    const rules: DeadlineRule[] = await resp.json();
    rulesByID = new Map(rules.map((r) => [r.id, r]));
    const opts: string[] = [
      `<option value="" data-i18n="deadlines.field.rule.none">${esc(t("deadlines.field.rule.none"))}</option>`,
    ];
    for (const r of rules) {
      const code = r.rule_code || r.code || "";
      const label = code ? `${code} \u2014 ${r.name}` : r.name;
      opts.push(`<option value="${esc(r.id)}">${esc(label)}</option>`);
    }
    sel.innerHTML = opts.join("");
  } catch {
    /* non-fatal — rule select stays at "no rule" */
  }
}

// t-paliad-165 follow-up — drive the collapsed/expanded view of the Typ
// picker. The two modes are mutually exclusive:
//
//   collapsed: rule selected + canonical event_type known + picker
//     contains exactly [default] + user hasn't clicked "Anderen Typ
//     wählen". Hides the chip cluster, surfaces a single inline
//     summary "Klageerwiderung (vorgegeben durch Regel)" + an
//     override link.
//
//   expanded: every other case — no rule, no default for the rule,
//     picker has been edited, or expandedOverride is sticky after the
//     user clicked the override link. Picker visible; mismatch warning
//     surfaces yellow when the rule expected a different event_type.
function refreshRuleView(): void {
  const collapsed = document.getElementById("deadline-event-type-collapsed");
  const collapsedLabel = document.getElementById("deadline-event-type-collapsed-label");
  const pickerHost = document.getElementById("deadline-event-types");
  const warn = document.getElementById("deadline-event-type-rule-mismatch");
  if (!collapsed || !collapsedLabel || !pickerHost || !warn) return;

  const ruleID = (document.getElementById("deadline-rule") as HTMLSelectElement | null)?.value || "";
  const rule = ruleID ? rulesByID.get(ruleID) : undefined;
  const expected = rule?.concept_default_event_type_id ?? null;
  const picked = eventTypePicker?.getIDs() ?? [];

  const pickerMatchesDefault =
    expected !== null && picked.length === 1 && picked[0] === expected;
  const wantsCollapsed =
    !expandedOverride && ruleID !== "" && expected !== null && pickerMatchesDefault;

  if (wantsCollapsed) {
    const et = eventTypesByID.get(expected!);
    collapsedLabel.textContent = et ? eventTypeLabel(et) : "";
    collapsed.style.display = "";
    pickerHost.style.display = "none";
    warn.style.display = "none";
    return;
  }

  collapsed.style.display = "none";
  pickerHost.style.display = "";
  // Mismatch warning: rule expected an event_type AND the picker
  // doesn't contain it. (When the picker is empty + no override, no
  // warning — user is free to leave it blank.)
  if (expected && picked.length > 0 && !picked.includes(expected)) {
    warn.style.display = "";
  } else {
    warn.style.display = "none";
  }
}

// applyRuleAutoFill replaces the picker silently when it still reflects
// the previous rule's suggestion (or is empty); leaves a manually-edited
// picker alone. Called whenever the Regel select changes.
function applyRuleAutoFill(): void {
  if (!eventTypePicker) return;
  const ruleID = (document.getElementById("deadline-rule") as HTMLSelectElement | null)?.value || "";
  const rule = ruleID ? rulesByID.get(ruleID) : undefined;
  const expected = rule?.concept_default_event_type_id ?? null;
  const current = eventTypePicker.getIDs();

  // Reset the override on transition to "Keine Regel" — fresh form
  // session. Otherwise expandedOverride stays sticky.
  if (ruleID === "") {
    expandedOverride = false;
  }

  const pickerStillReflectsLastSuggestion =
    lastAutoFilledEventTypeID !== null &&
    current.length === 1 &&
    current[0] === lastAutoFilledEventTypeID;
  const pickerIsEmpty = current.length === 0;

  if (expected) {
    if (pickerIsEmpty || pickerStillReflectsLastSuggestion) {
      eventTypePicker.setIDs([expected]);
      lastAutoFilledEventTypeID = expected;
    }
  } else if (pickerStillReflectsLastSuggestion) {
    // New rule has no canonical event_type — clear the stale auto-fill
    // so the picker doesn't carry a chip from the old rule.
    eventTypePicker.setIDs([]);
    lastAutoFilledEventTypeID = null;
  }
  refreshRuleView();
}

function initBackLinks() {
  if (preselectedProjectID) {
    const back = document.getElementById("deadline-new-back") as HTMLAnchorElement;
    const cancel = document.getElementById("deadline-new-cancel") as HTMLAnchorElement;
    back.href = `/projects/${preselectedProjectID}/deadlines`;
    cancel.href = `/projects/${preselectedProjectID}/deadlines`;
  }
}

async function submitForm(e: Event) {
  e.preventDefault();
  const submitBtn = document.querySelector<HTMLButtonElement>("#deadline-new-form button[type=submit]")!;
  const msg = document.getElementById("deadline-new-msg")!;

  const projectID = (document.getElementById("deadline-project") as HTMLSelectElement).value;
  const title = (document.getElementById("deadline-title") as HTMLInputElement).value.trim();
  const due = (document.getElementById("deadline-due") as HTMLInputElement).value;
  const ruleID = (document.getElementById("deadline-rule") as HTMLSelectElement).value;
  const notes = (document.getElementById("deadline-notes") as HTMLTextAreaElement).value.trim();

  if (!projectID || !title || !due) {
    showError(t("deadlines.error.required"));
    return;
  }

  msg.textContent = "";
  msg.className = "form-msg";
  submitBtn.disabled = true;

  const payload: Record<string, unknown> = {
    title,
    due_date: due,
    source: "manual",
  };
  if (ruleID) payload.rule_id = ruleID;
  if (notes) payload.notes = notes;
  const eventTypeIDs = eventTypePicker?.getIDs() ?? [];
  if (eventTypeIDs.length > 0) payload.event_type_ids = eventTypeIDs;

  try {
    const resp = await fetch(`/api/projects/${encodeURIComponent(projectID)}/deadlines`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    });
    if (!resp.ok) {
      const data = await resp.json().catch(() => ({}) as { error?: string });
      showError(data.error || t("deadlines.error.generic"));
      submitBtn.disabled = false;
      return;
    }
    const created = await resp.json();
    if (preselectedProjectID) {
      window.location.href = `/projects/${preselectedProjectID}/deadlines`;
    } else {
      window.location.href = `/deadlines/${created.id}`;
    }
  } catch {
    showError(t("deadlines.error.generic"));
    submitBtn.disabled = false;
  }
}

function detectPreselect() {
  // Path /projects/{id}/deadlines/new pre-selects that project.
  const parts = window.location.pathname.split("/").filter(Boolean);
  if (parts[0] === "projects" && parts[1] && parts[2] === "deadlines" && parts[3] === "new") {
    preselectedProjectID = parts[1];
  }
  // Or ?project_id= query string
  const qp = new URLSearchParams(window.location.search);
  const fromQuery = qp.get("project_id");
  if (fromQuery) preselectedProjectID = fromQuery;
}

async function loadMe() {
  try {
    const resp = await fetch("/api/me");
    if (!resp.ok) return;
    const me = await resp.json();
    currentUserAdmin = me?.global_role === "global_admin";
  } catch {
    /* non-fatal */
  }
}

// t-paliad-154 — fetch the effective approval policy for (project,
// deadline, create) and reveal the form-time hint when it applies.
// Hidden when no policy applies. Re-runs on project change so the hint
// updates if the user picks a different project mid-form.
async function refreshApprovalHint(): Promise<void> {
  const hint = document.getElementById("deadline-approval-hint");
  const text = document.getElementById("deadline-approval-hint-text");
  if (!hint || !text) return;
  const projectID = (document.getElementById("deadline-project") as HTMLSelectElement | null)?.value || "";
  if (!projectID) {
    hint.style.display = "none";
    return;
  }
  try {
    const resp = await fetch(
      `/api/projects/${encodeURIComponent(projectID)}/approval-policies/effective?entity_type=deadline&lifecycle=create`,
      { credentials: "include" },
    );
    if (!resp.ok) {
      hint.style.display = "none";
      return;
    }
    // t-paliad-160 split-grammar (with M1 legacy fallback).
    const eff = await resp.json() as {
      requires_approval?: boolean;
      min_role?: string | null;
      required_role?: string | null;
      source?: string | null;
      source_name?: string | null;
    };
    const role = eff.min_role || eff.required_role || null;
    const required = (eff.requires_approval === true) || (role !== null && role !== "none");
    if (!required || !role) {
      hint.style.display = "none";
      return;
    }
    const roleLabel = tDyn("admin.approval_policies.role." + role) || role;
    const sourceLabel = eff.source_name
      ? ` · ${tDyn("admin.approval_policies.source." + (eff.source || "")) || ""}: ${eff.source_name}`
      : "";
    text.textContent = (t("deadlines.form.approval_hint") || "4-Augen-Prüfung erforderlich")
      + ` · ${roleLabel}${sourceLabel}`;
    hint.style.display = "";
  } catch {
    hint.style.display = "none";
  }
}

document.addEventListener("DOMContentLoaded", async () => {
  initI18n();
  initSidebar();
  detectPreselect();
  initBackLinks();
  document.getElementById("deadline-new-form")!.addEventListener("submit", submitForm);
  // Default due to today
  const dueInput = document.getElementById("deadline-due") as HTMLInputElement;
  if (!dueInput.value) dueInput.value = new Date().toISOString().split("T")[0];
  await Promise.all([loadProjects(), loadRules(), loadMe()]);
  const pickerHost = document.getElementById("deadline-event-types");
  if (pickerHost) {
    eventTypePicker = attachEventTypePicker(pickerHost, {
      currentUserAdmin,
      onChange: () => refreshRuleView(),
    });
  }
  // t-paliad-165 follow-up — preload event_types so the collapsed
  // summary can render the type's label inline without an extra round
  // trip when the user picks a Regel.
  fetchEventTypes()
    .then((types) => {
      eventTypesByID = new Map(types.map((et) => [et.id, et]));
      refreshRuleView();
    })
    .catch(() => {/* non-fatal — collapsed view falls back to empty label */});
  // t-paliad-165 — Regel change auto-fills the Typ chip from the rule's
  // concept's canonical event_type, when the picker hasn't been
  // manually edited away from the previous rule's suggestion.
  document.getElementById("deadline-rule")?.addEventListener("change", () => {
    applyRuleAutoFill();
  });
  // "Anderen Typ wählen" — sticky expanded mode so the picker stays
  // visible even when the chip still matches the rule's default.
  document.getElementById("deadline-event-type-override-btn")?.addEventListener("click", () => {
    expandedOverride = true;
    refreshRuleView();
    // Move focus into the picker's search box so the user can type
    // immediately without an extra click.
    const search = document.querySelector<HTMLInputElement>(
      "#deadline-event-types .event-type-search",
    );
    search?.focus();
  });
  // Wire approval-hint refresh: on first render + on project change.
  void refreshApprovalHint();
  document.getElementById("deadline-project")?.addEventListener("change", () => {
    void refreshApprovalHint();
  });
});
