import type { TFunction } from "i18next";
import { relativeTime, formatDate } from "../lib/format";
import i18n from "../i18n";
import type { AuditRow } from "../lib/api";
import type { Column } from "./DataTable";

function actionLabel(t: TFunction, action: string): string {
  // Canonical key → localized label; falls back to the raw key for any unmapped action.
  return t(`auditActions.${action}`, action);
}

function fmt(v: unknown): string {
  return v == null || v === "" ? "—" : String(v);
}

/** A compact before→after (or after-only) rendering of the JSON snapshots, e.g. "role: user → admin". */
function changeText(row: AuditRow): string | null {
  const { before, after } = row;
  if (before && after) {
    return Object.keys(after)
      .map((k) => `${k}: ${fmt(before[k])} → ${fmt(after[k])}`)
      .join(", ");
  }
  if (after) {
    return Object.entries(after)
      .map(([k, v]) => `${k}: ${fmt(v)}`)
      .join(", ");
  }
  return null;
}

/** Columns for the admin audit-log DataTable. `actionOptions` are the distinct actions present in
 * the data (built by the page) so the Action column's select filter only offers real values. */
export function auditColumns(
  t: TFunction,
  actionOptions: { value: string; label: string }[],
): Column<AuditRow>[] {
  return [
    {
      key: "when",
      header: t("audit.cols.when"),
      nowrap: true,
      sortable: true,
      sortValue: (r) => r.created_at ?? "",
      cardPrimary: true,
      render: (r) => (
        <span
          className="text-muted tabular-nums"
          title={r.created_at ? formatDate(r.created_at, i18n.language) : ""}
        >
          {/* `|| "—"` not a null check: relativeTime is empty for a missing OR unreadable date. */}
          {relativeTime(r.created_at) || "—"}
        </span>
      ),
    },
    {
      key: "actor",
      header: t("audit.cols.actor"),
      sortable: true,
      sortValue: (r) => r.actor ?? "",
      filter: { kind: "text", get: (r) => r.actor ?? t("audit.system") },
      render: (r) =>
        r.actor ? (
          <span className="truncate">{r.actor}</span>
        ) : (
          <span className="text-muted italic">{t("audit.system")}</span>
        ),
    },
    {
      key: "action",
      header: t("audit.cols.action"),
      nowrap: true,
      sortable: true,
      sortValue: (r) => actionLabel(t, r.action),
      filter: { kind: "select", options: actionOptions, test: (r, v) => r.action === v },
      render: (r) => <span className="font-medium">{actionLabel(t, r.action)}</span>,
    },
    {
      key: "details",
      header: t("audit.cols.details"),
      render: (r) => {
        const change = changeText(r);
        return (
          // break-words (wrap), NOT truncate: in the auto-layout table a `truncate` (nowrap) cell
          // sizes to its longest row, so one long summary/change stretched the whole table past the
          // card (the row rules bled into the page). Wrapping keeps every row inside the container.
          <div className="min-w-0">
            {r.summary && <div className="break-words">{r.summary}</div>}
            {change && <div className="text-[11px] text-muted break-words">{change}</div>}
          </div>
        );
      },
    },
  ];
}
