// Submissions panel — fetches the full submission catalog across every
// proceeding and renders it grouped by proceeding, with the project's
// own proceeding pinned at the top.
//
// t-paliad-215 Slice 1 introduced the per-project list. t-paliad-242
// broadened it to the catalog: from any project a lawyer can pick a
// Statement of Defence under UPC.INF.CFI, a Klageerwiderung under
// DE.INF.LG, an Opposition under EPO, etc. — the editor (t-paliad-238)
// handles missing variables gracefully via the [KEIN WERT: …] marker,
// so cross-proceeding picks still render cleanly.

function escapeHtml(s: string): string {
  return s
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

interface SubmissionEntry {
  submission_code: string;
  name: string;
  name_en: string;
  event_type?: string;
  primary_party?: string;
  legal_source?: string;
  has_template: boolean;
  proceeding_code: string;
  proceeding_name: string;
  proceeding_name_en: string;
}

interface SubmissionListResponse {
  project_id: string;
  proceeding_type_id?: number;
  project_proceeding_code?: string;
  entries: SubmissionEntry[];
}

// Module state — set once per page load when the user first opens the
// tab. Subsequent activations re-use the cached result so the lawyer
// doesn't pay for repeat list calls flipping between tabs.
let cached: { projectID: string; data: SubmissionListResponse } | null = null;
let loading = false;

/**
 * Load + render the submissions panel for the given project.
 *
 * Idempotent: safe to call on every tab activation. The second call
 * paints from cache instantly; the first call shows a loading state
 * until the list response arrives.
 */
export async function loadAndRenderSubmissions(projectID: string): Promise<void> {
  if (loading) return;
  if (cached && cached.projectID === projectID) {
    render(cached.data);
    return;
  }
  loading = true;
  try {
    const resp = await fetch(`/api/projects/${projectID}/submissions`);
    if (!resp.ok) {
      renderError();
      return;
    }
    const data = (await resp.json()) as SubmissionListResponse;
    cached = { projectID, data };
    render(data);
  } catch {
    renderError();
  } finally {
    loading = false;
  }
}

function render(data: SubmissionListResponse): void {
  const empty = document.getElementById("project-submissions-empty");
  const noProc = document.getElementById("project-submissions-no-proceeding");
  const wrap = document.getElementById("project-submissions-tablewrap");
  const body = document.getElementById("project-submissions-body");
  if (!empty || !noProc || !wrap || !body) return;

  // t-paliad-242: the catalog is shown to every project regardless of
  // whether a proceeding is bound — the no-proceeding hint stays as a
  // soft nudge above the table, but no longer hides the catalog.
  noProc.style.display = data.proceeding_type_id == null || data.proceeding_type_id === 0
    ? ""
    : "none";

  if (data.entries.length === 0) {
    empty.style.display = "";
    wrap.style.display = "none";
    return;
  }
  empty.style.display = "none";
  wrap.style.display = "";

  const isEN = document.documentElement.lang === "en";

  // Group entries by proceeding_code. Build a stable group order:
  // project's own proceeding first (when present), then alphabetical
  // by proceeding_code for the rest.
  const groups = new Map<string, { name: string; entries: SubmissionEntry[] }>();
  for (const entry of data.entries) {
    const key = entry.proceeding_code || "";
    const groupName = isEN && entry.proceeding_name_en
      ? entry.proceeding_name_en
      : entry.proceeding_name;
    const bucket = groups.get(key);
    if (bucket) {
      bucket.entries.push(entry);
    } else {
      groups.set(key, { name: groupName, entries: [entry] });
    }
  }

  const ownCode = data.project_proceeding_code ?? "";
  const orderedCodes: string[] = [];
  if (ownCode && groups.has(ownCode)) orderedCodes.push(ownCode);
  for (const code of Array.from(groups.keys()).sort()) {
    if (code !== ownCode) orderedCodes.push(code);
  }

  const ownSuffix = isEN ? " (this project)" : " (dieses Projekt)";
  const colspan = 4;

  const html: string[] = [];
  for (const code of orderedCodes) {
    const group = groups.get(code);
    if (!group) continue;
    const isOwn = code === ownCode;
    const label = group.name + (isOwn ? ownSuffix : "");
    const headerClass = isOwn
      ? "entity-table-group-header entity-table-group-header--own"
      : "entity-table-group-header";
    html.push(`<tr class="${headerClass}">`
      + `<th colspan="${colspan}" scope="colgroup">`
      + `<span class="entity-table-group-header__name">${escapeHtml(label)}</span>`
      + ` <span class="entity-table-group-header__code">${escapeHtml(code)}</span>`
      + `</th></tr>`);
    for (const entry of group.entries) {
      html.push(renderRow(entry, data.project_id, isEN));
    }
  }
  body.innerHTML = html.join("");

  // Wire button clicks. One handler per render to avoid stale closures
  // from the previous render's data.
  body.querySelectorAll<HTMLButtonElement>(".submission-generate-btn").forEach((btn) => {
    btn.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();
      void onGenerateClick(btn);
    });
  });
}

function renderRow(entry: SubmissionEntry, projectID: string, isEN: boolean): string {
  const name = isEN && entry.name_en ? entry.name_en : entry.name;
  const party = formatParty(entry.primary_party, isEN);
  const source = entry.legal_source ?? "";
  const draftHref = `/projects/${encodeURIComponent(projectID)}/submissions/${encodeURIComponent(entry.submission_code)}/draft`;
  const templateBadge = entry.has_template
    ? ""
    : ` <span class="submission-template-badge" title="${isEN ? "Uses the universal style template" : "Verwendet die universelle Stilvorlage"}">${isEN ? "universal" : "universell"}</span>`;
  const editBtn = `<a href="${escapeHtml(draftHref)}" class="btn-primary btn-cta-lime btn-small submission-edit-btn"
            data-code="${escapeHtml(entry.submission_code)}"
            data-i18n="projects.detail.submissions.action.edit">${isEN ? "Edit" : "Bearbeiten"}</a>`;
  const generateBtn = `<button type="button" class="btn-secondary btn-small submission-generate-btn"
                data-code="${escapeHtml(entry.submission_code)}"
                data-project="${escapeHtml(projectID)}"
                data-i18n="projects.detail.submissions.action.generate">${isEN ? "Generate" : "Generieren"}</button>`;
  const action = `${editBtn} ${generateBtn}`;
  return `<tr class="submission-row">
    <td>
      <span class="submission-name">${escapeHtml(name)}</span>
      <span class="submission-code">${escapeHtml(entry.submission_code)}</span>${templateBadge}
    </td>
    <td>${escapeHtml(party)}</td>
    <td>${escapeHtml(source)}</td>
    <td class="submission-action-cell">${action}</td>
  </tr>`;
}

function renderError(): void {
  const empty = document.getElementById("project-submissions-empty");
  const noProc = document.getElementById("project-submissions-no-proceeding");
  const wrap = document.getElementById("project-submissions-tablewrap");
  if (!empty || !noProc || !wrap) return;
  noProc.style.display = "none";
  wrap.style.display = "none";
  empty.style.display = "";
  empty.textContent = document.documentElement.lang === "en"
    ? "Failed to load submissions list."
    : "Schriftsatzliste konnte nicht geladen werden.";
}

function formatParty(role: string | undefined, isEN: boolean): string {
  switch ((role ?? "").toLowerCase()) {
    case "claimant": return isEN ? "Claimant" : "Klägerin";
    case "defendant": return isEN ? "Defendant" : "Beklagte";
    case "both": return isEN ? "Both" : "Beide";
    case "court": return isEN ? "Court" : "Gericht";
    default: return "";
  }
}

// onGenerateClick triggers a download. Disables the button while the
// request is in flight to prevent double-submits and surfaces an
// inline error on failure.
async function onGenerateClick(btn: HTMLButtonElement): Promise<void> {
  const code = btn.dataset.code;
  const projectID = btn.dataset.project;
  if (!code || !projectID) return;

  const originalLabel = btn.textContent ?? "";
  btn.disabled = true;
  btn.textContent = document.documentElement.lang === "en" ? "Generating…" : "Wird generiert…";

  try {
    const url = `/api/projects/${projectID}/submissions/${encodeURIComponent(code)}/generate`;
    const resp = await fetch(url, { method: "POST" });
    if (!resp.ok) {
      let detail = "";
      try {
        const data = await resp.json() as { error?: string };
        detail = data.error ?? "";
      } catch {
        // fallthrough
      }
      alert(
        (document.documentElement.lang === "en"
          ? "Generation failed."
          : "Generieren fehlgeschlagen.") + (detail ? `\n\n${detail}` : ""),
      );
      return;
    }
    const blob = await resp.blob();
    const filename = parseFilename(resp.headers.get("Content-Disposition") ?? "")
      ?? `${code}.docx`;
    triggerDownload(blob, filename);
  } finally {
    btn.disabled = false;
    btn.textContent = originalLabel;
  }
}

// parseFilename pulls the filename out of a Content-Disposition
// header. Supports both unquoted and quoted forms.
function parseFilename(header: string): string | null {
  const m = /filename\s*=\s*"?([^";]+)"?/i.exec(header);
  return m ? m[1] : null;
}

// triggerDownload creates an <a> with an object URL, clicks it, and
// revokes the URL. Standard browser-side download pattern.
function triggerDownload(blob: Blob, filename: string): void {
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  // Revoke on next tick so the click actually triggers the download
  // before the URL is gone.
  setTimeout(() => URL.revokeObjectURL(url), 0);
}
