import { t, tDyn, getLang, type I18nKey } from "../i18n";
import type { ListRowAction, RenderSpec, ViewRow } from "./types";
import { formatDate, formatRelative, parseDateOnly } from "./format";

// shape-list: renders ViewRows as a table (density=comfortable) or a
// compact one-line stream (density=compact). The "activity feed" look
// is just density=compact + actor/time columns — see Q4 lock-in
// 2026-05-07 (3 shapes; no separate "activity").
//
// Row interaction is controlled by render.list.row_action
// (t-paliad-163 schema bump). Default "navigate" keeps every existing
// caller's contract — clicking a row goes to the per-kind detail
// page. "approve" produces the approval-list layout for /inbox.
// "complete_toggle" is wired in Phase 3 (/events). "none" suppresses
// any row interaction (audit views).

export function renderListShape(host: HTMLElement, rows: ViewRow[], render: RenderSpec): void {
  host.innerHTML = "";
  const list = render.list ?? {};
  const density = list.density ?? "comfortable";
  const sort = list.sort ?? "date_asc";
  const rowAction: ListRowAction = list.row_action ?? "navigate";

  const sorted = [...rows].sort((a, b) => {
    const aT = sortKey(a.event_date);
    const bT = sortKey(b.event_date);
    return sort === "date_asc" ? aT - bT : bT - aT;
  });

  if (rowAction === "approve") {
    host.appendChild(renderApprovalList(sorted));
    return;
  }

  if (rowAction === "inbox") {
    host.appendChild(renderInboxList(sorted));
    return;
  }

  if (density === "compact") {
    host.appendChild(renderCompact(sorted));
  } else {
    host.appendChild(renderTable(sorted, list.columns ?? defaultColumns(rows)));
  }
}

function renderCompact(rows: ViewRow[]): HTMLElement {
  const ul = document.createElement("ul");
  ul.className = "views-list views-list--compact";
  for (const row of rows) {
    const li = document.createElement("li");
    li.className = `views-list-row views-list-row--${row.kind}`;

    const time = document.createElement("span");
    time.className = "views-list-time";
    time.textContent = formatRelative(row.event_date, row.kind);
    li.appendChild(time);

    const kindIcon = document.createElement("span");
    kindIcon.className = "views-list-kind";
    kindIcon.textContent = kindLabel(row.kind);
    li.appendChild(kindIcon);

    const title = document.createElement("span");
    title.className = "views-list-title";
    title.textContent = row.title;
    li.appendChild(title);

    if (row.project_title) {
      const proj = document.createElement("span");
      proj.className = "views-list-project";
      proj.textContent = row.project_title;
      li.appendChild(proj);
    }

    if (row.actor_name) {
      const actor = document.createElement("span");
      actor.className = "views-list-actor";
      actor.textContent = row.actor_name;
      li.appendChild(actor);
    }

    if (row.subtitle) {
      const sub = document.createElement("span");
      sub.className = "views-list-subtitle";
      sub.textContent = row.subtitle;
      li.appendChild(sub);
    }
    ul.appendChild(li);
  }
  return ul;
}

function renderTable(rows: ViewRow[], columns: string[]): HTMLElement {
  const wrap = document.createElement("div");
  wrap.className = "entity-table-wrap";
  const table = document.createElement("table");
  table.className = "entity-table views-list views-list--table entity-table--readonly";
  const thead = document.createElement("thead");
  const trHead = document.createElement("tr");
  for (const col of columns) {
    const th = document.createElement("th");
    th.textContent = t(("views.col." + col) as I18nKey);
    trHead.appendChild(th);
  }
  thead.appendChild(trHead);
  table.appendChild(thead);

  const tbody = document.createElement("tbody");
  for (const row of rows) {
    const tr = document.createElement("tr");
    tr.className = `views-table-row views-table-row--${row.kind}`;
    for (const col of columns) {
      const td = document.createElement("td");
      td.textContent = formatColumn(row, col);
      tr.appendChild(td);
    }
    tbody.appendChild(tr);
  }
  table.appendChild(tbody);
  wrap.appendChild(table);
  return wrap;
}

function defaultColumns(rows: ViewRow[]): string[] {
  // Pick a sensible default column set from the kinds present in the
  // result. Keeps the UI honest when a user lands on a saved view that
  // has no explicit list.columns.
  const kinds = new Set(rows.map((r) => r.kind));
  if (kinds.has("project_event") || kinds.has("approval_request")) {
    return ["time", "actor", "title", "project"];
  }
  if (kinds.has("appointment")) {
    return ["date", "title", "project", "location"];
  }
  return ["date", "title", "project", "status"];
}

function formatColumn(row: ViewRow, col: string): string {
  switch (col) {
    case "date":
      return formatDate(row.event_date);
    case "time":
      return formatRelative(row.event_date, row.kind);
    case "title":
      return row.title;
    case "project":
      return row.project_title ?? "—";
    case "actor":
      return row.actor_name ?? "—";
    case "status": {
      const s = (row.detail.status as string | undefined) ?? "";
      return s ? t(("deadlines.status." + s) as I18nKey) : "—";
    }
    case "rule": {
      // t-paliad-258 — canonical "Name · Citation" pattern; fall back
      // to custom_rule_text + " · Custom" for Custom-mode deadlines.
      const lang = getLang();
      const nameKey = lang === "en" ? "rule_name_en" : "rule_name";
      const name = (row.detail[nameKey] as string | undefined)
        || (row.detail.rule_name as string | undefined)
        || "";
      const cite = (row.detail.rule_code as string | undefined) ?? "";
      if (name && cite) return `${name} · ${cite}`;
      if (name) return name;
      if (cite) return cite;
      const custom = (row.detail.custom_rule_text as string | undefined) ?? "";
      if (custom.trim()) return `${custom} · Custom`;
      return "—";
    }
    case "event_type":
      return (row.detail.event_type as string | undefined) ?? "—";
    case "location":
      return (row.detail.location as string | undefined) ?? "—";
    case "appointment_type":
      return (row.detail.appointment_type as string | undefined) ?? "—";
    case "approval_status":
      return (row.detail.approval_status as string | undefined) ?? "—";
    case "decided_by":
      return (row.detail.decider_name as string | undefined) ?? "—";
    case "kind":
      return kindLabel(row.kind);
    default:
      return "";
  }
}

function kindLabel(kind: string): string {
  return t(("views.kind." + kind) as I18nKey);
}

function sortKey(iso: string): number {
  const dateOnly = parseDateOnly(iso);
  if (dateOnly) return dateOnly.getTime();
  return Date.parse(iso);
}

// ----------------------------------------------------------------------
// row_action = "approve" — approval inbox layout
//
// Stamps the markup the /inbox surface needs (data attrs + classes);
// the surface (client/inbox.ts) wires the action handlers in onResult.
// This keeps shape-list independent of any specific surface's wiring.
// ----------------------------------------------------------------------

interface ApprovalDetail {
  status?: string;
  lifecycle_event?: string;
  entity_type?: string;
  entity_title?: string;
  pre_image?: Record<string, unknown> | null;
  payload?: Record<string, unknown> | null;
  required_role?: string;
  requester_name?: string;
  requester_kind?: "user" | "agent";
  decider_name?: string;
  decision_note?: string;
  // counter_payload + next_request_id — populated on the OLD row of a
  // suggest-changes pair (t-paliad-216). The new row's id lets us
  // render a back-link "→ Neuer Vorschlag von {decider}". Both stay
  // unset on any non-changes_requested status.
  counter_payload?: Record<string, unknown> | null;
  next_request_id?: string;
  // Per-viewer eligibility flags resolved server-side against the caller
  // (t-paliad-202). Used to grey out actions the server would reject.
  // Optional so an older payload still renders — falsy means "treat as
  // disabled" for the safety side (no false enables).
  viewer_can_approve?: boolean;
  viewer_is_requester?: boolean;
}

// Pending-row action set. suggest_changes was added in t-paliad-216 as
// the fourth action — the approver authors a counter-proposal which
// becomes a NEW pending row authored by them.
type ApprovalAction = "approve" | "reject" | "revoke" | "suggest_changes";

function renderApprovalList(rows: ViewRow[]): HTMLElement {
  const ul = document.createElement("ul");
  ul.className = "inbox-list views-approval-list";
  for (const row of rows) {
    ul.appendChild(renderApprovalRow(row));
  }
  return ul;
}

// renderApprovalRow stamps one <li> for an approval_request row.
// Factored out of renderApprovalList in t-paliad-249 so the unified
// inbox dispatch (renderInboxList) can reuse the exact same markup for
// approval rows interleaved with project_event rows.
export function renderApprovalRow(row: ViewRow): HTMLLIElement {
  const detail = (row.detail || {}) as ApprovalDetail;
  const li = document.createElement("li");
  li.className = "inbox-row views-approval-row";
  li.dataset.requestId = row.id;
  li.dataset.status = detail.status ?? "";

  // Header: entity / lifecycle
  const head = document.createElement("div");
  head.className = "inbox-row-head";
  const title = document.createElement("div");
  title.className = "inbox-row-title";
  const entityLabel = detail.entity_type ? t(("approvals.entity." + detail.entity_type) as I18nKey) : "";
  const lifecycleLabel = detail.lifecycle_event ? t(("approvals.lifecycle." + detail.lifecycle_event) as I18nKey) : "";
  const entityTitle = detail.entity_title || row.title || "—";
  title.textContent = `${entityLabel}: ${entityTitle} — ${lifecycleLabel}`;
  head.appendChild(title);

  const meta = document.createElement("div");
  meta.className = "inbox-row-meta";
  const reqByLabel = t("approvals.requested_by");
  const roleLabel = detail.required_role
    ? t(("approvals.required_role." + detail.required_role) as I18nKey)
    : "";
  const requester = detail.requester_name || row.actor_name || "";
  const requesterTag = detail.requester_kind === "agent"
    ? `${requester} ✨ ${t("approvals.agent.byline")}`
    : requester;
  const projectTitle = row.project_title ?? "";
  const parts = [
    projectTitle,
    `${reqByLabel} ${requesterTag}`,
  ];
  if (roleLabel) parts.push(`${roleLabel}+`);
  parts.push(formatRelativeTime(row.event_date));
  meta.textContent = parts.filter(Boolean).join(" · ");
  head.appendChild(meta);
  li.appendChild(head);

  // Diff for update / complete
  const diff = renderDiff(detail);
  if (diff) li.appendChild(diff);

  if (detail.decision_note) {
    const note = document.createElement("div");
    note.className = "inbox-row-note";
    note.textContent = detail.decision_note;
    li.appendChild(note);
  }

  // Action row — surface attaches handlers via data-attrs.
  const actions = document.createElement("div");
  actions.className = "inbox-row-actions";

  if (detail.status === "pending") {
    // All four actions are stamped on every pending row; the per-viewer
    // viewer_can_approve / viewer_is_requester flags (resolved server-side)
    // decide which are enabled vs. greyed out with a tooltip. m's ask
    // (2026-05-17): show what's possible but disable what isn't, rather
    // than alert-after-click. The server still enforces — disabled buttons
    // are a UI hint, not a security gate.
    //
    // suggest_changes is hidden for non-update lifecycles (the backend
    // returns ErrSuggestionLifecycleInvalid for create/complete/delete,
    // so we don't even render the button for them).
    actions.appendChild(approvalActionBtn("approve", detail));
    if (detail.lifecycle_event === "update") {
      actions.appendChild(approvalActionBtn("suggest_changes", detail));
    }
    actions.appendChild(approvalActionBtn("reject", detail));
    actions.appendChild(approvalActionBtn("revoke", detail));
  } else if (detail.status) {
    const pill = document.createElement("span");
    pill.className = "approval-pill approval-pill--historic";
    pill.textContent = t(("approvals.status." + detail.status) as I18nKey);
    if (detail.decider_name && detail.status !== "revoked") {
      const decided = document.createElement("span");
      decided.className = "inbox-row-decided";
      decided.textContent = ` · ${t("approvals.decided_by")} ${detail.decider_name}`;
      pill.appendChild(decided);
    }
    actions.appendChild(pill);
  }
  li.appendChild(actions);

  // Back-link from the OLD changes_requested row to the NEW pending
  // counter row (t-paliad-216). Hydrated server-side as
  // detail.next_request_id; the surface renders a link that scrolls
  // / filters to the new row. Falsy next_request_id = no link (e.g.
  // older rows pre-mig-103, or rows where the server hasn't joined the
  // back-pointer).
  if (detail.status === "changes_requested" && detail.next_request_id) {
    const link = document.createElement("a");
    link.className = "inbox-row-next-request";
    link.href = `#request-${detail.next_request_id}`;
    link.dataset.nextRequestId = detail.next_request_id;
    const deciderName = detail.decider_name || "";
    link.textContent = t("approvals.suggest.next_request_link").replace("{name}", deciderName);
    li.appendChild(link);
  }

  return li;
}

// ----------------------------------------------------------------------
// row_action = "inbox" — unified inbox layout (t-paliad-249)
//
// Dispatches per row.kind so approval_request rows reuse the existing
// approve/reject/revoke markup while project_event rows render as a
// compact stream row (timestamp + actor + title + project chip +
// Öffnen link to the underlying entity).
// ----------------------------------------------------------------------

function renderInboxList(rows: ViewRow[]): HTMLElement {
  const ul = document.createElement("ul");
  ul.className = "inbox-list inbox-list--unified";
  for (const row of rows) {
    if (row.kind === "approval_request") {
      ul.appendChild(renderApprovalRow(row));
    } else if (row.kind === "project_event") {
      ul.appendChild(renderProjectEventInboxRow(row));
    }
  }
  return ul;
}

interface ProjectEventDetail {
  event_type?: string | null;
  description?: string | null;
}

function renderProjectEventInboxRow(row: ViewRow): HTMLLIElement {
  const detail = (row.detail || {}) as ProjectEventDetail;
  const li = document.createElement("li");
  li.className = "inbox-row inbox-row--project-event";
  li.dataset.eventId = row.id;
  if (detail.event_type) li.dataset.eventType = detail.event_type;

  const head = document.createElement("div");
  head.className = "inbox-row-head";

  const title = document.createElement("div");
  title.className = "inbox-row-title";
  // Prefer the row.title (server-side authored, project-aware); fall
  // back to a synthesised event-kind label so a malformed row never
  // produces an empty <li>.
  const kindLabelText = detail.event_type ? t(("event.title." + detail.event_type) as I18nKey) : "";
  title.textContent = row.title || kindLabelText || "—";
  head.appendChild(title);

  const meta = document.createElement("div");
  meta.className = "inbox-row-meta";
  const parts: string[] = [];
  if (row.project_title) parts.push(row.project_title);
  if (row.actor_name) parts.push(row.actor_name);
  parts.push(formatRelativeTime(row.event_date));
  meta.textContent = parts.filter(Boolean).join(" · ");
  head.appendChild(meta);
  li.appendChild(head);

  if (detail.description) {
    const desc = document.createElement("div");
    desc.className = "inbox-row-description";
    desc.textContent = detail.description;
    li.appendChild(desc);
  }

  const actions = document.createElement("div");
  actions.className = "inbox-row-actions";
  const openLink = projectEventLink(row, detail);
  if (openLink) actions.appendChild(openLink);
  li.appendChild(actions);

  return li;
}

// projectEventLink builds an "Öffnen" anchor that points to the most
// useful target for the event kind. Falls back to the project detail
// page when the kind doesn't carry a richer pointer.
//
// Slice B can deepen this (e.g. note_created → scroll to note anchor);
// keep it minimal for Slice A.
function projectEventLink(row: ViewRow, detail: ProjectEventDetail): HTMLAnchorElement | null {
  if (!row.project_id) return null;
  const kind = detail.event_type ?? "";
  const a = document.createElement("a");
  a.className = "inbox-row-open";
  a.textContent = t("inbox.action.open");
  if (kind.startsWith("deadline_")) {
    a.href = `/projects/${row.project_id}#deadlines`;
  } else if (kind.startsWith("appointment_")) {
    a.href = `/projects/${row.project_id}#appointments`;
  } else if (kind === "note_created") {
    a.href = `/projects/${row.project_id}#notes`;
  } else {
    a.href = `/projects/${row.project_id}`;
  }
  return a;
}

function renderDiff(detail: ApprovalDetail): HTMLElement | null {
  const before = (detail.pre_image || {}) as Record<string, unknown>;
  const after = (detail.payload || {}) as Record<string, unknown>;
  const keys = Array.from(new Set([...Object.keys(before), ...Object.keys(after)]));
  if (keys.length === 0) return null;
  const wrap = document.createElement("div");
  wrap.className = "inbox-row-diff";
  for (const k of keys) {
    const line = document.createElement("div");
    line.className = "inbox-row-diff-line";
    const label = document.createElement("span");
    label.className = "inbox-row-diff-key";
    label.textContent = k;
    line.appendChild(label);
    const span = document.createElement("span");
    span.className = "inbox-row-diff-values";
    const fmt = (v: unknown) => v === null || v === undefined ? "—" : String(v);
    if (k in before && k in after) {
      span.textContent = `${fmt(before[k])} → ${fmt(after[k])}`;
    } else if (k in before) {
      span.textContent = `${t("approvals.diff.before")}: ${fmt(before[k])}`;
    } else {
      span.textContent = `${t("approvals.diff.after")}: ${fmt(after[k])}`;
    }
    line.appendChild(span);
    wrap.appendChild(line);
  }
  return wrap;
}

function approvalActionBtn(
  action: ApprovalAction,
  detail: ApprovalDetail,
): HTMLButtonElement {
  const btn = document.createElement("button");
  btn.type = "button";
  btn.dataset.action = action;
  // suggest_changes shares the secondary style with revoke; reject is
  // danger (terminal "no"); approve is primary.
  const cls = action === "approve"
    ? "btn-primary"
    : action === "reject"
      ? "btn-danger"
      : "btn-secondary";
  btn.className = `btn ${cls} inbox-row-action views-approval-action`;
  btn.textContent = t(("approvals.action." + action) as I18nKey);

  // approve / reject / suggest_changes share the canApprove eligibility
  // gate; revoke is requester-only.
  const reason = disabledReasonFor(action, detail);
  if (reason) {
    btn.disabled = true;
    btn.title = t(reason);
  }
  return btn;
}

function disabledReasonFor(
  action: ApprovalAction,
  detail: ApprovalDetail,
): I18nKey | null {
  if (action === "revoke") {
    return detail.viewer_is_requester ? null : "approvals.disabled.revoke_not_requester";
  }
  // approve / reject / suggest_changes — same gate as the server's canApprove.
  if (detail.viewer_can_approve) return null;
  if (detail.viewer_is_requester) return "approvals.disabled.self_approval";
  return "approvals.disabled.not_authorized";
}

function formatRelativeTime(iso: string): string {
  const t0 = Date.parse(iso);
  if (isNaN(t0)) return iso;
  const diffMs = Date.now() - t0;
  const sec = Math.floor(diffMs / 1000);
  if (sec < 60) return getLang() === "de" ? `vor ${sec}s` : `${sec}s ago`;
  const min = Math.floor(sec / 60);
  if (min < 60) return getLang() === "de" ? `vor ${min}m` : `${min}m ago`;
  const hr = Math.floor(min / 60);
  if (hr < 24) return getLang() === "de" ? `vor ${hr}h` : `${hr}h ago`;
  const day = Math.floor(hr / 24);
  return getLang() === "de" ? `vor ${day}d` : `${day}d ago`;
}

// Suppress unused warning for tDyn — kept available for future axes.
void tDyn;
