import { t, tDyn, getLang } from "./i18n";

// Shared logic for the Project form rendered by ProjectFormFields.tsx.
// Used by /projects/new and the edit modal on /projects/{id}.

export interface ProceedingTypeRow {
  id: number;
  code: string;
  name: string;
  name_en: string;
  jurisdiction?: string;
  is_active: boolean;
}

let proceedingTypesCache: ProceedingTypeRow[] | null = null;

// loadProceedingTypes fetches active fristenrechner-category proceeding
// types — the only set a project may bind to (mig 087/088 + service
// validation guard `validateProceedingTypeCategory`). Cached at module
// level so the page only pays for one fetch even when both the new-
// project page and the edit modal exercise the picker.
export async function loadProceedingTypes(): Promise<ProceedingTypeRow[]> {
  if (proceedingTypesCache) return proceedingTypesCache;
  try {
    const resp = await fetch("/api/proceeding-types-db?category=fristenrechner");
    if (!resp.ok) return [];
    const rows = ((await resp.json()) ?? []) as ProceedingTypeRow[];
    proceedingTypesCache = rows.filter((r) => r.is_active);
    return proceedingTypesCache;
  } catch {
    return [];
  }
}

export interface ProjectMini {
  id: string;
  title: string;
  type: string;
  reference?: string | null;
  // t-paliad-222 / m/paliad#50: auto-derived dotted project code from
  // the ancestor tree. Populated by the service projection on every
  // /api/projects response, so the picker can show the code without an
  // extra fetch.
  code?: string;
}

export interface ProjectFormState {
  type: string;
  parentID: string;
  parentTitle: string;
  title: string;
  reference: string;
  description: string;
  status: string;
  clientNumber: string;
  matterNumber: string;
  billingReference: string;
  netDocumentsURL: string;
  industry: string;
  country: string;
  patentNumber: string;
  filingDate: string;
  grantDate: string;
  court: string;
  caseNumber: string;
  ourSide: string;
}

let parentCandidates: ProjectMini[] = [];

function $(id: string): HTMLElement {
  const el = document.getElementById(id);
  if (!el) throw new Error("missing form element: " + id);
  return el;
}

function tryGet(id: string): HTMLElement | null {
  return document.getElementById(id);
}

// showFieldsForType toggles parent-picker + type-specific blocks.
export function showFieldsForType(typeSel: string) {
  const parentWrap = tryGet("projekt-parent-wrap") as HTMLDivElement | null;
  const clientFields = tryGet("fields-client") as HTMLDivElement | null;
  const litigationFields = tryGet("fields-litigation") as HTMLDivElement | null;
  const patentFields = tryGet("fields-patent") as HTMLDivElement | null;
  const caseFields = tryGet("fields-case") as HTMLDivElement | null;
  if (clientFields) clientFields.style.display = typeSel === "client" ? "block" : "none";
  if (litigationFields) litigationFields.style.display = typeSel === "litigation" ? "block" : "none";
  if (patentFields) patentFields.style.display = typeSel === "patent" ? "block" : "none";
  if (caseFields) caseFields.style.display = typeSel === "case" ? "block" : "none";
  if (parentWrap) parentWrap.style.display = typeSel === "client" ? "none" : "block";
}

export async function loadParentCandidates(excludeID?: string) {
  try {
    const resp = await fetch("/api/projects");
    if (!resp.ok) return;
    const all = (await resp.json()) as ProjectMini[];
    parentCandidates = excludeID ? all.filter((p) => p.id !== excludeID) : all;
  } catch {
    /* network — leave empty */
  }
}

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

export function initParentPicker() {
  const input = tryGet("projekt-parent-input") as HTMLInputElement | null;
  const hidden = tryGet("projekt-parent-id") as HTMLInputElement | null;
  const sugs = tryGet("projekt-parent-suggestions") as HTMLDivElement | null;
  if (!input || !hidden || !sugs) return;

  input.addEventListener("input", () => {
    const q = input.value.trim().toLowerCase();
    hidden.value = "";
    if (!q) {
      sugs.innerHTML = "";
      return;
    }
    const matches = parentCandidates
      .filter((p) => {
        // Search across title + manual reference + auto-derived code
        // so the user can type "EXMPL" or "INF.CFI" and find the row.
        const hay = (p.title + " " + (p.reference || "") + " " + (p.code || "")).toLowerCase();
        return hay.includes(q);
      })
      .slice(0, 8);
    sugs.innerHTML = matches
      .map((p) => {
        // Render the auto-derived code (if any, and distinct from
        // reference) as a small mono badge on the right so the user
        // can disambiguate two same-titled projects by their tree
        // position. Single template literal kept readable inline.
        const code = p.code && p.code !== (p.reference || "") ? p.code : "";
        const codeBadge = code
          ? `<span class="entity-ref entity-ref-code">${esc(code)}</span>`
          : "";
        return `<div class="collab-suggestion" data-id="${esc(p.id)}" data-title="${esc(p.title)}">
            <strong>${esc(p.title)}</strong>
            <span class="entity-type-chip entity-type-${esc(p.type)}">${esc(tDyn("projects.type." + p.type) || p.type)}</span>
            ${codeBadge}
          </div>`;
      })
      .join("");
    sugs.querySelectorAll<HTMLDivElement>(".collab-suggestion").forEach((el) => {
      el.addEventListener("click", () => {
        hidden.value = el.dataset.id!;
        input.value = el.dataset.title!;
        sugs.innerHTML = "";
      });
    });
  });
}

// wireTypeChange wires the <select id="project-type"> change handler and runs
// the visibility pass once with the current value.
export function wireTypeChange() {
  const typeSel = $("project-type") as HTMLSelectElement;
  showFieldsForType(typeSel.value);
  typeSel.addEventListener("change", () => showFieldsForType(typeSel.value));
}

// populateProceedingTypeSelect fills #project-proceeding-type-id with one
// option per fristenrechner-category proceeding type, ordered by `code`
// (so the user scans `de.*`, `dpma.*`, `epa.*`, `upc.*` in stable
// jurisdiction-grouped order). The first option is the empty "unset"
// choice already in the markup; this helper only appends rows below it.
// Idempotent — clearing rows[1..] on re-call so a re-open of the edit
// modal doesn't double-render the list.
export async function populateProceedingTypeSelect(): Promise<void> {
  const sel = tryGet("project-proceeding-type-id") as HTMLSelectElement | null;
  if (!sel) return;
  const rows = await loadProceedingTypes();
  rows.sort((a, b) => a.code.localeCompare(b.code));
  while (sel.options.length > 1) sel.remove(1);
  const isEN = getLang() === "en";
  for (const row of rows) {
    const opt = document.createElement("option");
    opt.value = String(row.id);
    const label = isEN && row.name_en ? row.name_en : row.name;
    opt.textContent = `${label} (${row.code})`;
    sel.appendChild(opt);
  }
  // Honour a pre-selection value that prefillForm wrote before the
  // option set existed. dataset.preselect is set to "" or the saved id;
  // restoring it here keeps the edit modal's saved value visible.
  const preselect = sel.dataset.preselect;
  if (preselect !== undefined) sel.value = preselect;
}

// readPayload collects the form's current values into a CreateProjectInput /
// UpdateProjectInput compatible JSON payload. Returns null + sets msg when
// title is missing.
//
// `omitEmpty` controls whether empty-string fields are sent. For Create we
// drop them (server treats absent as default). For Update we send them as
// `""` so the server can clear the column — except for ParentID (handled
// specially because client→non-client requires structural changes the user
// shouldn't trigger from an edit form).
export function readPayload(
  msg: HTMLElement,
  opts: { omitEmpty: boolean; mode: "create" | "edit" },
): Record<string, unknown> | null {
  const type = ($("project-type") as HTMLSelectElement).value;
  const title = ($("project-title") as HTMLInputElement).value.trim();
  if (!title) {
    msg.textContent = t("projects.error.title_required") || "Title required";
    msg.className = "form-msg form-msg-error";
    return null;
  }

  const payload: Record<string, unknown> = {
    type,
    title,
    status: ($("project-status") as HTMLSelectElement).value,
  };

  const parentID = ($("projekt-parent-id") as HTMLInputElement).value;
  if (type !== "client" && parentID) {
    payload.parent_id = parentID;
  }

  const stringField = (id: string, key: string) => {
    const v = ($(id) as HTMLInputElement).value.trim();
    if (v) payload[key] = v;
    else if (!opts.omitEmpty) payload[key] = "";
  };

  stringField("project-ref", "reference");
  stringField("project-client-number", "client_number");
  stringField("project-matter-number", "matter_number");
  stringField("project-billing-ref", "billing_reference");
  stringField("project-netdocs", "netdocuments_url");

  if (type === "client") {
    stringField("project-industry", "industry");
    stringField("project-country", "country");
  }
  if (type === "patent") {
    stringField("project-patent-number", "patent_number");
    const fd = ($("project-filing-date") as HTMLInputElement).value;
    if (fd) payload.filing_date = fd + "T00:00:00Z";
    const gd = ($("project-grant-date") as HTMLInputElement).value;
    if (gd) payload.grant_date = gd + "T00:00:00Z";
  }
  if (type === "litigation") {
    // opponent_code is the litigation-only short slug used as the
    // middle segment when BuildProjectCode auto-derives a project
    // code from the ancestor tree (t-paliad-222 / m/paliad#50).
    // Uppercased on submit so the user can type lowercase comfortably
    // — the DB CHECK enforces the [A-Z0-9-]{1,16} pattern.
    const ocEl = tryGet("project-opponent-code") as HTMLInputElement | null;
    if (ocEl) {
      const v = ocEl.value.trim().toUpperCase();
      if (v) payload.opponent_code = v;
      else if (!opts.omitEmpty) payload.opponent_code = "";
    }
  }
  if (type === "case") {
    stringField("project-court", "court");
    stringField("project-case-number", "case_number");

    // Proceeding type — optional picker. Per t-paliad-232, an empty
    // pick simply omits the key from the payload (create: column stays
    // NULL; edit: server's `omitempty` skips the SET). Clearing a
    // previously-set value isn't supported in this slice; once bound,
    // a project's proceeding type can be swapped but not unset from
    // the form. The server's validateProceedingTypeCategory backs the
    // selected id with a category check.
    const ptSel = tryGet("project-proceeding-type-id") as HTMLSelectElement | null;
    if (ptSel) {
      const v = ptSel.value.trim();
      if (v) {
        const n = parseInt(v, 10);
        if (!isNaN(n)) payload.proceeding_type_id = n;
      }
    }

    // Client Role (DB column: our_side) — case-only after t-paliad-222.
    // The select uses "" for the unset option; the service maps empty
    // string to NULL via nullableOurSide.
    const osSel = tryGet("project-our-side") as HTMLSelectElement | null;
    if (osSel) {
      const v = osSel.value.trim();
      if (v) payload.our_side = v;
      else if (!opts.omitEmpty) payload.our_side = "";
    }
  }

  const desc = ($("project-description") as HTMLTextAreaElement).value.trim();
  if (desc) payload.description = desc;
  else if (!opts.omitEmpty) payload.description = "";

  // The edit modal currently doesn't expose reparenting; keep parent_id off
  // the payload unless the user is creating.
  if (opts.mode === "edit") {
    delete payload.parent_id;
  }

  return payload;
}

// prefillForm hydrates the form fields from an existing Project record.
export function prefillForm(p: Record<string, unknown>) {
  const get = (id: string) => $(id) as HTMLInputElement;
  const getSel = (id: string) => $(id) as HTMLSelectElement;
  const getTA = (id: string) => $(id) as HTMLTextAreaElement;

  const type = String(p.type ?? "project");
  getSel("project-type").value = type;
  showFieldsForType(type);

  get("project-title").value = String(p.title ?? "");
  get("project-ref").value = String(p.reference ?? "");
  get("project-client-number").value = String(p.client_number ?? "");
  get("project-matter-number").value = String(p.matter_number ?? "");
  get("project-billing-ref").value = String(p.billing_reference ?? "");
  get("project-netdocs").value = String(p.netdocuments_url ?? "");
  get("project-industry").value = String(p.industry ?? "");
  get("project-country").value = String(p.country ?? "");
  get("project-patent-number").value = String(p.patent_number ?? "");
  get("project-filing-date").value = isoToDate(p.filing_date as string | null | undefined);
  get("project-grant-date").value = isoToDate(p.grant_date as string | null | undefined);
  get("project-court").value = String(p.court ?? "");
  get("project-case-number").value = String(p.case_number ?? "");
  const osSel = tryGet("project-our-side") as HTMLSelectElement | null;
  if (osSel) osSel.value = String(p.our_side ?? "");
  const ocEl = tryGet("project-opponent-code") as HTMLInputElement | null;
  if (ocEl) ocEl.value = String(p.opponent_code ?? "");
  // Proceeding-type picker — populated lazily by populateProceedingTypeSelect.
  // Set the value here even if the options haven't arrived yet; the post-
  // populate render runs ApplyProceedingTypeValue to re-select the saved id
  // once the option exists.
  const ptSel = tryGet("project-proceeding-type-id") as HTMLSelectElement | null;
  if (ptSel) {
    const v = p.proceeding_type_id == null ? "" : String(p.proceeding_type_id);
    ptSel.dataset.preselect = v;
    ptSel.value = v;
  }
  getTA("project-description").value = String(p.description ?? "");
  getSel("project-status").value = String(p.status ?? "active");
}

function isoToDate(iso: string | null | undefined): string {
  if (!iso) return "";
  // Accept YYYY-MM-DD or full ISO; slice to date.
  return iso.slice(0, 10);
}
