// Akte-mode wiring for the Litigation Builder (m/paliad#153 B4,
// t-paliad-347).
//
// PRD §2.3 + §3.1 + §3.2: the page-header Akte picker lists every
// project (`type='case'`) the user can see. Picking one POSTs to
// /api/builder/scenarios/from-project, which mints a project-backed
// scenario (origin_project_id pinned) seeded with the project's
// proceeding + scenario_flags + completed deadlines. Subsequent
// builder edits dual-write through to paliad.deadlines + projects.
// scenario_flags via the server-side dual-write hooks.
//
// The picker is its own module so the builder.ts orchestrator only
// has to expose two hooks:
//
//   - `onProjectChosen(projectId)` — called when the user picks a
//     project. Builder calls the from-project endpoint and loads the
//     returned scenario.
//   - `setSelectedProject(scenario)` — called after a scenario loads
//     so the picker reflects the current Akte (or "— ohne —" for
//     kontextfrei scenarios).
//
// Cross-surface scenario-flag-changed (mig 154 ssoT, m/paliad#149):
// the builder listens to the existing CustomEvent so any peer surface
// that PATCHes /api/projects/{id}/scenario-flags triggers a re-fetch
// on the builder's active proceeding when the projectId matches the
// scenario's origin_project_id. The dispatch direction is already
// covered by patchScenarioFlags inside scenario-flags.ts — the
// builder's own PATCH /api/projects/.../scenario-flags goes through
// that helper so peer surfaces stay in sync without a separate dispatch.

import { t } from "./i18n";

export interface AkteProjectMeta {
  id: string;
  title: string;
  reference?: string | null;
  case_number?: string | null;
  proceeding_type_id?: number | null;
  our_side?: string | null;
}

export type OnProjectChosen = (projectId: string) => void | Promise<void>;

interface State {
  projects: AkteProjectMeta[];
  loaded: boolean;
}

const state: State = {
  projects: [],
  loaded: false,
};

// fetchAkteProjects pulls every type=case project the caller can see.
// Visibility is enforced by /api/projects via the project_teams /
// can_see_project predicate. We filter client-side to projects with a
// proceeding_type_id — those are the ones the builder can render. We
// don't filter server-side because /api/projects' filter param doesn't
// accept proceeding_type_id_not_null and round-tripping for that one
// reason isn't worth a new endpoint.
export async function fetchAkteProjects(): Promise<AkteProjectMeta[]> {
  try {
    const resp = await fetch("/api/projects?type=case", {
      headers: { Accept: "application/json" },
    });
    if (!resp.ok) {
      console.warn("builder-akte: /api/projects", resp.status);
      return [];
    }
    const rows = (await resp.json()) as Array<{
      id: string;
      title: string;
      reference?: string | null;
      case_number?: string | null;
      proceeding_type_id?: number | null;
      our_side?: string | null;
      status?: string;
    }>;
    return rows
      .filter((r) => r.proceeding_type_id != null && (r.status ?? "active") === "active")
      .map((r) => ({
        id: r.id,
        title: r.title,
        reference: r.reference ?? null,
        case_number: r.case_number ?? null,
        proceeding_type_id: r.proceeding_type_id ?? null,
        our_side: r.our_side ?? null,
      }));
  } catch (e) {
    console.error("builder-akte: fetch projects failed", e);
    return [];
  }
}

// formatProjectLabel renders the dropdown row for a project. Reference
// + title are the primary anchors; the case_number tail disambiguates
// when two cases share a reference family.
function formatProjectLabel(p: AkteProjectMeta): string {
  const parts: string[] = [];
  if (p.reference) parts.push(p.reference);
  parts.push(p.title);
  if (p.case_number) parts.push("(" + p.case_number + ")");
  return parts.join(" · ");
}

// renderAktePicker fills the existing <select id="builder-akte-picker">
// with the project list + a "— ohne —" sentinel. Idempotent.
function renderAktePicker(selectedId: string | null): void {
  const sel = document.getElementById("builder-akte-picker") as HTMLSelectElement | null;
  if (!sel) return;
  const none = t("builder.akte.none");
  const opts: string[] = [`<option value="" data-i18n="builder.akte.none">${escHtml(none)}</option>`];
  for (const p of state.projects) {
    const selected = p.id === selectedId ? " selected" : "";
    opts.push(
      `<option value="${escAttr(p.id)}"${selected}>${escHtml(formatProjectLabel(p))}</option>`,
    );
  }
  sel.innerHTML = opts.join("");
}

// mountAktePicker is the entry point. It fetches the project list once,
// wires the dropdown change event to the supplied callback, and
// returns a controller exposing setSelectedProject so the builder can
// keep the picker reflective of the active scenario's Akte.
//
// The picker re-enables itself the moment projects load. While
// loading, the existing `disabled` attribute (set in procedures.tsx)
// stays so users don't pick during the fetch — but if the user lands
// on the page after the catalog is cached this is essentially
// instantaneous.
export interface AktePickerHandle {
  setSelectedProject: (projectId: string | null) => void;
  isAkteMode: () => boolean;
  reload: () => Promise<void>;
}

export async function mountAktePicker(onChosen: OnProjectChosen): Promise<AktePickerHandle> {
  const sel = document.getElementById("builder-akte-picker") as HTMLSelectElement | null;
  if (!sel) {
    return {
      setSelectedProject: () => {},
      isAkteMode: () => false,
      reload: async () => {},
    };
  }

  // First load — fill the dropdown, enable it, wire change.
  state.projects = await fetchAkteProjects();
  state.loaded = true;
  renderAktePicker(null);
  sel.disabled = false;

  sel.addEventListener("change", () => {
    const id = sel.value;
    if (!id) {
      // "— ohne —" reset is intentional; the builder treats this as
      // "leave the current scenario alone, just clear the picker".
      // Switching the active scenario to a non-Akte one happens via
      // the scenario picker, not by clicking the empty Akte option.
      return;
    }
    void onChosen(id);
  });

  return {
    setSelectedProject: (projectId: string | null) => {
      const next = projectId ?? "";
      // Renderless quick-sync when the option is present; otherwise
      // re-render so the option appears (covers freshly created
      // projects since this picker last loaded).
      const optEl = sel.querySelector<HTMLOptionElement>(`option[value="${cssEscape(next)}"]`);
      if (next && !optEl) {
        renderAktePicker(next);
      } else {
        sel.value = next;
      }
    },
    isAkteMode: () => sel.value !== "",
    reload: async () => {
      state.projects = await fetchAkteProjects();
      renderAktePicker(sel.value || null);
    },
  };
}

// createScenarioFromProject posts to the B4 entry point. Returns the
// new scenario's deep payload on success (id + proceedings + events),
// null on failure. Caller is expected to load the returned scenario
// via the builder's existing fetchScenarioDeep / state.active path.
export async function createScenarioFromProject(projectId: string): Promise<{ id: string } | null> {
  try {
    const resp = await fetch("/api/builder/scenarios/from-project", {
      method: "POST",
      headers: { "Content-Type": "application/json", Accept: "application/json" },
      body: JSON.stringify({ project_id: projectId }),
    });
    if (!resp.ok) {
      console.warn("builder-akte: from-project", resp.status, await resp.text().catch(() => ""));
      return null;
    }
    const out = await resp.json();
    return out && typeof out.id === "string" ? { id: out.id } : null;
  } catch (e) {
    console.error("builder-akte: from-project failed", e);
    return null;
  }
}

// renderAkteBanner toggles the "Aus Akte: <code>" badge next to the
// scenario picker. The badge is a <span class="builder-akte-banner">
// inserted/removed by this helper; CSS gives it a lime tint to match
// the Akte affordance throughout the app. Pass `null` (or omit
// projectId) to hide.
export function renderAkteBanner(projectId: string | null): void {
  const host = document.querySelector(".builder-pageheader") as HTMLElement | null;
  if (!host) return;
  let badge = document.getElementById("builder-akte-banner");
  if (!projectId) {
    if (badge) badge.remove();
    return;
  }
  const meta = state.projects.find((p) => p.id === projectId);
  const label = meta ? formatProjectLabel(meta) : projectId.slice(0, 8);
  const text =
    t("builder.akte.banner.prefix") + " " + label;
  if (!badge) {
    badge = document.createElement("span");
    badge.id = "builder-akte-banner";
    badge.className = "builder-akte-banner";
    badge.setAttribute("role", "note");
    host.appendChild(badge);
  }
  badge.textContent = text;
}

// ────────────────────────────────────────────────────────────────────────────
// helpers
// ────────────────────────────────────────────────────────────────────────────

function escAttr(s: string): string {
  return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
}

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

// cssEscape is a small fallback for browsers that don't yet expose
// CSS.escape. UUIDs only contain [0-9a-f-] so even the naïve replacer
// keeps us safe; the function exists to make intent obvious.
function cssEscape(s: string): string {
  if (typeof CSS !== "undefined" && typeof (CSS as { escape?: (s: string) => string }).escape === "function") {
    return (CSS as { escape: (s: string) => string }).escape(s);
  }
  return s.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
}
