"use client";

import { Fragment, useState, useMemo, useCallback } from "react";
import { keepPreviousData } from "@tanstack/react-query";
import { trpc } from "~/lib/trpc/client.js";
import { clsx } from "clsx";
import { downloadWorkbookSheets } from "~/lib/workbook-export.js";
import {
  buildReportWorkbookSheets,
  type ReportExplainability,
} from "./reportBuilderExplainability.js";

// ─── Types ──────────────────────────────────────────────────────────────────

type EntityType = "resource" | "project" | "assignment" | "resource_month";
type FilterOp = "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "contains" | "in";

interface FilterRow {
  id: string;
  field: string;
  op: FilterOp;
  value: string;
}

interface AvailableColumn {
  key: string;
  label: string;
  dataType: "string" | "number" | "date" | "boolean";
}

interface TemplateConfig {
  entity: EntityType;
  columns: string[];
  filters: Omit<FilterRow, "id">[];
  groupBy?: string;
  sortBy?: string;
  sortDir?: "asc" | "desc";
  periodMonth?: string;
}

interface ReportTemplateSummary {
  id: string;
  name: string;
  description?: string | null;
  entity: EntityType;
  config: TemplateConfig;
  isShared: boolean;
  isOwner: boolean;
  completeness: ResourceMonthTemplateCompleteness | null;
  updatedAt: string | Date;
}

interface ResourceMonthTemplateCompleteness {
  scope: "resource_month";
  isAuditReady: boolean;
  isRecommendedComplete: boolean;
  recommendedColumnCount: number;
  selectedRecommendedColumnCount: number;
  minimumAuditColumnCount: number;
  selectedMinimumAuditColumnCount: number;
  missingRecommendedColumns: string[];
  missingMinimumAuditColumns: string[];
}

interface ReportGroupSummary {
  key: string;
  label: string;
  rowCount: number;
  startIndex: number;
}

interface ExportReportResult {
  csv: string;
  rowCount: number;
  rows: Record<string, unknown>[];
  columns: string[];
  groups: ReportGroupSummary[];
  explainability?: ReportExplainability;
}

interface ReportBlueprint {
  id: string;
  label: string;
  description: string;
  entity: EntityType;
  templateName: string;
  config: TemplateConfig;
}

const ENTITY_OPTIONS: { value: EntityType; label: string }[] = [
  { value: "resource", label: "Resources" },
  { value: "project", label: "Projects" },
  { value: "assignment", label: "Assignments" },
  { value: "resource_month", label: "Resource Months" },
];

const OPERATOR_OPTIONS: { value: FilterOp; label: string }[] = [
  { value: "eq", label: "equals" },
  { value: "neq", label: "not equals" },
  { value: "gt", label: "greater than" },
  { value: "lt", label: "less than" },
  { value: "gte", label: ">= (gte)" },
  { value: "lte", label: "<= (lte)" },
  { value: "contains", label: "contains" },
  { value: "in", label: "in (comma-sep)" },
];

const PAGE_SIZE = 50;

const RESOURCE_MONTH_RECOMMENDED_COLUMNS = [
  "monthKey",
  "displayName",
  "eid",
  "chapter",
  "countryCode",
  "countryName",
  "federalState",
  "metroCityName",
  "orgUnitName",
  "managementLevelGroupName",
  "monthlyBaseWorkingDays",
  "monthlyEffectiveWorkingDays",
  "monthlyBaseAvailableHours",
  "monthlyPublicHolidayCount",
  "monthlyPublicHolidayWorkdayCount",
  "monthlyPublicHolidayHoursDeduction",
  "monthlyAbsenceDayEquivalent",
  "monthlyAbsenceHoursDeduction",
  "monthlySahHours",
  "monthlyChargeabilityTargetPct",
  "monthlyTargetHours",
  "monthlyActualBookedHours",
  "monthlyExpectedBookedHours",
  "monthlyActualChargeabilityPct",
  "monthlyExpectedChargeabilityPct",
  "monthlyUnassignedHours",
] as const;

const RESOURCE_MONTH_MINIMUM_AUDIT_COLUMNS = [
  "monthKey",
  "displayName",
  "countryName",
  "federalState",
  "metroCityName",
  "monthlyPublicHolidayCount",
  "monthlyPublicHolidayHoursDeduction",
  "monthlyAbsenceDayEquivalent",
  "monthlyAbsenceHoursDeduction",
  "monthlySahHours",
  "monthlyTargetHours",
  "monthlyActualBookedHours",
  "monthlyUnassignedHours",
] as const;

function generateId(): string {
  return Math.random().toString(36).slice(2, 10);
}

function getCurrentPeriodMonth(): string {
  const now = new Date();
  return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
}

function normalizeTemplateConfig(config: TemplateConfig): TemplateConfig {
  return {
    entity: config.entity,
    columns: [...config.columns].sort(),
    filters: [...config.filters]
      .map((filter) => ({
        field: filter.field,
        op: filter.op,
        value: filter.value,
      }))
      .sort((left, right) =>
        `${left.field}:${left.op}:${left.value}`.localeCompare(
          `${right.field}:${right.op}:${right.value}`,
        )),
    ...(config.groupBy ? { groupBy: config.groupBy } : {}),
    ...(config.sortBy ? { sortBy: config.sortBy } : {}),
    ...(config.sortBy ? { sortDir: config.sortDir ?? "asc" } : {}),
    ...(config.entity === "resource_month" ? { periodMonth: config.periodMonth ?? "" } : {}),
  };
}

function serializeTemplateConfig(config: TemplateConfig): string {
  return JSON.stringify(normalizeTemplateConfig(config));
}

function buildResourceMonthCompleteness(columns: Iterable<string>): ResourceMonthTemplateCompleteness {
  const selectedColumns = new Set(columns);
  const missingRecommendedColumns = RESOURCE_MONTH_RECOMMENDED_COLUMNS
    .filter((column) => !selectedColumns.has(column));
  const missingMinimumAuditColumns = RESOURCE_MONTH_MINIMUM_AUDIT_COLUMNS
    .filter((column) => !selectedColumns.has(column));

  return {
    scope: "resource_month",
    isAuditReady: missingMinimumAuditColumns.length === 0,
    isRecommendedComplete: missingRecommendedColumns.length === 0,
    recommendedColumnCount: RESOURCE_MONTH_RECOMMENDED_COLUMNS.length,
    selectedRecommendedColumnCount: RESOURCE_MONTH_RECOMMENDED_COLUMNS.length - missingRecommendedColumns.length,
    minimumAuditColumnCount: RESOURCE_MONTH_MINIMUM_AUDIT_COLUMNS.length,
    selectedMinimumAuditColumnCount:
      RESOURCE_MONTH_MINIMUM_AUDIT_COLUMNS.length - missingMinimumAuditColumns.length,
    missingRecommendedColumns,
    missingMinimumAuditColumns,
  };
}

function summarizeMissingColumns(
  columns: string[],
  columnLabelMap: Map<string, string>,
  limit = 6,
): string {
  const labels = columns.map((column) => columnLabelMap.get(column) ?? column);
  if (labels.length <= limit) {
    return labels.join(", ");
  }
  return `${labels.slice(0, limit).join(", ")} +${labels.length - limit} more`;
}

// ─── Component ──────────────────────────────────────────────────────────────

export function ReportBuilder() {
  // Config state
  const [entity, setEntity] = useState<EntityType>("resource");
  const [selectedColumns, setSelectedColumns] = useState<Set<string>>(new Set());
  const [filters, setFilters] = useState<FilterRow[]>([]);
  const [groupBy, setGroupBy] = useState<string>("");
  const [sortBy, setSortBy] = useState<string>("");
  const [sortDir, setSortDir] = useState<"asc" | "desc">("asc");
  const [periodMonth, setPeriodMonth] = useState<string>(getCurrentPeriodMonth());
  const [selectedTemplateId, setSelectedTemplateId] = useState<string>("");
  const [templateName, setTemplateName] = useState<string>("");
  const [templateDescription, setTemplateDescription] = useState<string>("");
  const [templateIsShared, setTemplateIsShared] = useState(false);
  const [page, setPage] = useState(0);
  const [runQuery, setRunQuery] = useState(false);

  // Fetch available columns when entity changes
  const columnsQuery = trpc.report.getAvailableColumns.useQuery(
    { entity },
    { placeholderData: keepPreviousData },
  );

  const availableColumns: AvailableColumn[] = columnsQuery.data ?? [];
  const templatesQuery = trpc.report.listTemplates.useQuery();
  const blueprintsQuery = trpc.report.listBlueprints.useQuery({ entity });
  const saveTemplateMutation = trpc.report.saveTemplate.useMutation({
    onSuccess: async (result) => {
      setSelectedTemplateId(result.id);
      await templatesQuery.refetch();
    },
  });
  const deleteTemplateMutation = trpc.report.deleteTemplate.useMutation({
    onSuccess: async () => {
      setSelectedTemplateId("");
      setTemplateName("");
      setTemplateDescription("");
      setTemplateIsShared(false);
      await templatesQuery.refetch();
    },
  });

  // Scalar columns (for filter/sort/group — only non-relation columns)
  const scalarColumns = useMemo(
    () => availableColumns.filter((c) => !c.key.includes(".")),
    [availableColumns],
  );
  const columnLabelMap = useMemo(() => {
    const map = new Map<string, string>();
    for (const col of availableColumns) {
      map.set(col.key, col.label);
    }
    return map;
  }, [availableColumns]);

  // Build query input
  const queryInput = useMemo(() => {
    if (!runQuery || selectedColumns.size === 0) return null;
    return {
      entity,
      columns: Array.from(selectedColumns),
      filters: filters
        .filter((f) => f.field && f.value)
        .map(({ field, op, value }) => ({ field, op, value })),
      ...(entity === "resource_month" ? { periodMonth } : {}),
      ...(groupBy ? { groupBy } : {}),
      ...(sortBy ? { sortBy, sortDir } : {}),
      limit: PAGE_SIZE,
      offset: page * PAGE_SIZE,
    };
  }, [runQuery, entity, selectedColumns, filters, groupBy, sortBy, sortDir, page, periodMonth]);

  // Fetch report data
  const reportQuery = trpc.report.getReportData.useQuery(
    queryInput!,
    { enabled: queryInput !== null, placeholderData: keepPreviousData },
  );

  const exportMutation = trpc.report.exportReport.useMutation();

  // ─── Handlers ───────────────────────────────────────────────────────────

  const handleEntityChange = useCallback((newEntity: EntityType) => {
    setEntity(newEntity);
    setSelectedTemplateId("");
    setTemplateName("");
    setTemplateDescription("");
    setTemplateIsShared(false);
    setSelectedColumns(new Set());
    setFilters([]);
    setGroupBy("");
    setSortBy("");
    if (newEntity === "resource_month") {
      setPeriodMonth((current) => current || getCurrentPeriodMonth());
    }
    setRunQuery(false);
    setPage(0);
  }, []);

  const applyTemplate = useCallback((template: ReportTemplateSummary) => {
    const config = template.config;
    setSelectedTemplateId(template.id);
    setTemplateName(template.name);
    setTemplateDescription(template.description ?? "");
    setTemplateIsShared(template.isShared);
    setEntity(config.entity);
    setSelectedColumns(new Set(config.columns));
    setFilters(config.filters.map((filter: Omit<FilterRow, "id">) => ({ id: generateId(), ...filter })));
    setGroupBy(config.groupBy ?? "");
    setSortBy(config.sortBy ?? "");
    setSortDir(config.sortDir ?? "asc");
    setPeriodMonth(config.periodMonth ?? getCurrentPeriodMonth());
    setRunQuery(false);
    setPage(0);
  }, [templatesQuery.data]);

  const applyBlueprint = useCallback((blueprint: ReportBlueprint) => {
    const config = blueprint.config;
    setSelectedTemplateId("");
    setTemplateName(blueprint.templateName);
    setTemplateDescription(blueprint.description);
    setTemplateIsShared(false);
    setEntity(config.entity);
    setSelectedColumns(new Set(config.columns));
    setFilters(config.filters.map((filter: Omit<FilterRow, "id">) => ({ id: generateId(), ...filter })));
    setGroupBy(config.groupBy ?? "");
    setSortBy(config.sortBy ?? "");
    setSortDir(config.sortDir ?? "asc");
    if (config.entity === "resource_month") {
      setPeriodMonth(config.periodMonth ?? getCurrentPeriodMonth());
    }
    setRunQuery(false);
    setPage(0);
  }, []);

  const toggleColumn = useCallback((key: string) => {
    setSelectedColumns((prev) => {
      const next = new Set(prev);
      if (next.has(key)) {
        next.delete(key);
      } else {
        next.add(key);
      }
      return next;
    });
  }, []);

  const selectAllColumns = useCallback(() => {
    setSelectedColumns(new Set(availableColumns.map((c) => c.key)));
  }, [availableColumns]);

  const clearAllColumns = useCallback(() => {
    setSelectedColumns(new Set());
  }, []);

  const addFilter = useCallback(() => {
    const firstField = scalarColumns[0]?.key ?? "";
    setFilters((prev) => [...prev, { id: generateId(), field: firstField, op: "eq", value: "" }]);
  }, [scalarColumns]);

  const updateFilter = useCallback((id: string, patch: Partial<FilterRow>) => {
    setFilters((prev) => prev.map((f) => (f.id === id ? { ...f, ...patch } : f)));
  }, []);

  const removeFilter = useCallback((id: string) => {
    setFilters((prev) => prev.filter((f) => f.id !== id));
  }, []);

  const handleRun = useCallback(() => {
    setPage(0);
    setRunQuery(true);
  }, []);

  const handleSort = useCallback((column: string) => {
    if (!column.includes(".")) {
      if (sortBy === column) {
        setSortDir((prev) => (prev === "asc" ? "desc" : "asc"));
      } else {
        setSortBy(column);
        setSortDir("asc");
      }
      // Re-run with new sort
      setRunQuery(true);
    }
  }, [sortBy]);

  const handleExport = useCallback(async () => {
    if (selectedColumns.size === 0) return;
    try {
      const result = await exportMutation.mutateAsync({
        entity,
        columns: Array.from(selectedColumns),
        filters: filters
          .filter((f) => f.field && f.value)
          .map(({ field, op, value }) => ({ field, op, value })),
        ...(entity === "resource_month" ? { periodMonth } : {}),
        ...(groupBy ? { groupBy } : {}),
        ...(sortBy ? { sortBy, sortDir } : {}),
        limit: 5000,
      }) as ExportReportResult;

      if (result.explainability?.entity === "resource_month") {
        await downloadWorkbookSheets(
          `report-${entity}-${new Date().toISOString().slice(0, 10)}.xlsx`,
          buildReportWorkbookSheets({
            columns: result.columns,
            rows: result.rows,
            groups: result.groups,
            ...(groupBy ? { groupBy } : {}),
            explainability: result.explainability,
            resolveColumnLabel: (column) => columnLabelMap.get(column) ?? column,
          }),
        );
        return;
      }

      // Download CSV
      const blob = new Blob([result.csv], { type: "text/csv;charset=utf-8;" });
      const url = URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = url;
      link.download = `report-${entity}-${new Date().toISOString().slice(0, 10)}.csv`;
      link.click();
      URL.revokeObjectURL(url);
    } catch {
      // Error handled by tRPC
    }
  }, [columnLabelMap, entity, selectedColumns, filters, groupBy, sortBy, sortDir, exportMutation, periodMonth]);

  const handleSaveTemplate = useCallback(async () => {
    if (!templateName.trim() || selectedColumns.size === 0) return;
    const normalizedDescription = templateDescription.trim();

    await saveTemplateMutation.mutateAsync({
      ...(selectedTemplateId ? { id: selectedTemplateId } : {}),
      name: templateName.trim(),
      description: normalizedDescription,
      isShared: templateIsShared,
      config: {
        entity,
        columns: Array.from(selectedColumns),
        filters: filters
          .filter((filter) => filter.field && filter.value)
          .map(({ field, op, value }) => ({ field, op, value })),
        ...(entity === "resource_month" ? { periodMonth } : {}),
        ...(groupBy ? { groupBy } : {}),
        ...(sortBy ? { sortBy, sortDir } : {}),
      },
    });
  }, [
    entity,
    filters,
    groupBy,
    periodMonth,
    saveTemplateMutation,
    selectedColumns,
    selectedTemplateId,
    sortBy,
    sortDir,
    templateDescription,
    templateIsShared,
    templateName,
  ]);

  const handleDeleteTemplate = useCallback(async () => {
    if (!selectedTemplateId) return;
    await deleteTemplateMutation.mutateAsync({ id: selectedTemplateId });
  }, [deleteTemplateMutation, selectedTemplateId]);

  // ─── Derived ──────────────────────────────────────────────────────────

  const rows = reportQuery.data?.rows ?? [];
  const totalCount = reportQuery.data?.totalCount ?? 0;
  const outputColumns = reportQuery.data?.columns ?? [];
  const reportGroups = (reportQuery.data?.groups ?? []) as ReportGroupSummary[];
  const reportExplainability = reportQuery.data?.explainability as ReportExplainability | undefined;
  const totalPages = Math.ceil(totalCount / PAGE_SIZE);
  const isLoading = reportQuery.isFetching;
  const templates = templatesQuery.data ?? [];
  const selectedTemplate =
    templates.find((template: ReportTemplateSummary) => template.id === selectedTemplateId) ?? null;
  const resourceMonthBlueprints = useMemo(
    () => ((blueprintsQuery.data ?? []) as ReportBlueprint[]).filter((blueprint) => blueprint.entity === entity),
    [blueprintsQuery.data, entity],
  );
  const recommendedColumnSet = useMemo(
    () => entity === "resource_month" ? new Set<string>(RESOURCE_MONTH_RECOMMENDED_COLUMNS) : new Set<string>(),
    [entity],
  );
  const currentTemplateConfig = useMemo<TemplateConfig>(() => ({
    entity,
    columns: Array.from(selectedColumns),
    filters: filters
      .filter((filter) => filter.field && filter.value)
      .map(({ field, op, value }) => ({ field, op, value })),
    ...(entity === "resource_month" ? { periodMonth } : {}),
    ...(groupBy ? { groupBy } : {}),
    ...(sortBy ? { sortBy, sortDir } : {}),
  }), [entity, filters, groupBy, periodMonth, selectedColumns, sortBy, sortDir]);
  const selectedTemplateFingerprint = selectedTemplate
    ? serializeTemplateConfig(selectedTemplate.config)
    : null;
  const currentTemplateFingerprint = serializeTemplateConfig(currentTemplateConfig);
  const selectedTemplateMetadataFingerprint = selectedTemplate
    ? JSON.stringify({
      name: selectedTemplate.name,
      description: selectedTemplate.description ?? "",
      isShared: selectedTemplate.isShared,
    })
    : null;
  const currentTemplateMetadataFingerprint = JSON.stringify({
    name: templateName.trim(),
    description: templateDescription.trim(),
    isShared: templateIsShared,
  });
  const hasTemplateDraftChanges = selectedTemplateFingerprint !== null
    && (
      selectedTemplateFingerprint !== currentTemplateFingerprint
      || selectedTemplateMetadataFingerprint !== currentTemplateMetadataFingerprint
    );
  const hasUnsavedLocalView = selectedTemplate === null
    && (selectedColumns.size > 0 || filters.some((filter) => filter.field && filter.value));
  const currentResourceMonthCompleteness = useMemo(
    () => entity === "resource_month" ? buildResourceMonthCompleteness(selectedColumns) : null,
    [entity, selectedColumns],
  );

  const displayedResourceMonthCompleteness = useMemo(() => {
    if (entity !== "resource_month") {
      return null;
    }
    if (
      selectedTemplate
      && !hasTemplateDraftChanges
      && selectedTemplate.completeness?.scope === "resource_month"
    ) {
      return selectedTemplate.completeness;
    }
    return currentResourceMonthCompleteness;
  }, [currentResourceMonthCompleteness, entity, hasTemplateDraftChanges, selectedTemplate]);
  const groupStartByIndex = useMemo(
    () => new Map(reportGroups.map((group) => [group.startIndex, group] as const)),
    [reportGroups],
  );

  // ─── Render ───────────────────────────────────────────────────────────

  return (
    <div className="mx-auto max-w-[1600px] space-y-6 p-6">
      {/* Header */}
      <div>
        <h1 className="text-2xl font-bold text-gray-900 dark:text-gray-50">Report Builder</h1>
        <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
          Build custom reports by selecting an entity, columns, and filters.
        </p>
      </div>

      {/* Config Panel */}
      <div className="space-y-5 rounded-2xl border border-gray-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-950">
        <div className="grid gap-3 rounded-2xl border border-gray-200 bg-gray-50/70 p-4 dark:border-slate-800 dark:bg-slate-900/60 lg:grid-cols-[minmax(0,1fr)_220px_auto_auto]">
          <div className="space-y-2">
            <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
              Template
            </label>
            <select
              value={selectedTemplateId}
              onChange={(e) => {
                const nextId = e.target.value;
                setSelectedTemplateId(nextId);
                const template = templates.find((entry: ReportTemplateSummary) => entry.id === nextId);
                if (template) {
                  applyTemplate(template);
                }
              }}
              className="w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-700 focus:border-brand-500 focus:ring-brand-500 dark:border-slate-700 dark:bg-slate-950 dark:text-gray-300"
            >
              <option value="">Unsaved view</option>
              {templates.map((template) => (
                <option key={template.id} value={template.id}>
                  {template.name}{template.isShared && !template.isOwner ? " · shared" : ""}
                </option>
              ))}
            </select>
            {selectedTemplate ? (
              <div className="flex flex-wrap gap-2 text-[11px]">
                <span className="rounded-full bg-gray-200 px-2 py-0.5 font-medium text-gray-700 dark:bg-slate-800 dark:text-gray-200">
                  {selectedTemplate.isShared ? "Shared template" : "Private template"}
                </span>
                <span className="rounded-full bg-gray-100 px-2 py-0.5 text-gray-600 dark:bg-slate-900 dark:text-gray-300">
                  {selectedTemplate.isOwner ? "Owned by you" : "Shared by another user"}
                </span>
                <span
                  className={clsx(
                    "rounded-full px-2 py-0.5 font-medium",
                    hasTemplateDraftChanges
                      ? "bg-amber-100 text-amber-800 dark:bg-amber-950/60 dark:text-amber-200"
                      : "bg-emerald-100 text-emerald-800 dark:bg-emerald-950/60 dark:text-emerald-200",
                  )}
                >
                  {hasTemplateDraftChanges ? "Modified locally" : "Saved state"}
                </span>
              </div>
            ) : (
              <div className="flex flex-wrap gap-2 text-[11px]">
                <span className="rounded-full bg-gray-100 px-2 py-0.5 text-gray-600 dark:bg-slate-900 dark:text-gray-300">
                  {hasUnsavedLocalView ? "Unsaved local view" : "No template selected"}
                </span>
                <span className="rounded-full bg-gray-100 px-2 py-0.5 text-gray-600 dark:bg-slate-900 dark:text-gray-300">
                  Save the current builder state as a reusable template or shared view
                </span>
              </div>
            )}
            {selectedTemplate && hasTemplateDraftChanges ? (
              <p className="text-xs text-amber-700 dark:text-amber-300">
                The current builder state differs from the saved template. Use “Update template” to persist these local changes.
              </p>
            ) : null}
          </div>
          <div className="space-y-2">
            <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
              Template name
            </label>
            <input
              type="text"
              value={templateName}
              onChange={(e) => setTemplateName(e.target.value)}
              placeholder="Monthly SAH by location"
              className="w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-700 placeholder:text-gray-400 focus:border-brand-500 focus:ring-brand-500 dark:border-slate-700 dark:bg-slate-950 dark:text-gray-300"
            />
          </div>
          <div className="space-y-2 lg:col-span-2">
            <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
              Description
            </label>
            <input
              type="text"
              value={templateDescription}
              onChange={(e) => setTemplateDescription(e.target.value)}
              placeholder="Explains which basis columns and metrics this template is meant to audit"
              className="w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-700 placeholder:text-gray-400 focus:border-brand-500 focus:ring-brand-500 dark:border-slate-700 dark:bg-slate-950 dark:text-gray-300"
            />
          </div>
          <label className="flex items-center gap-2 self-end rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-700 dark:border-slate-700 dark:bg-slate-950 dark:text-gray-300">
            <input
              type="checkbox"
              checked={templateIsShared}
              onChange={(e) => setTemplateIsShared(e.target.checked)}
              className="h-4 w-4 rounded border-gray-300 text-brand-600 focus:ring-brand-500 dark:border-slate-600"
            />
            Shared with controllers
          </label>
          <button
            type="button"
            onClick={() => void handleSaveTemplate()}
            disabled={!templateName.trim() || selectedColumns.size === 0 || saveTemplateMutation.isPending}
            className="self-end rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-brand-700 disabled:cursor-not-allowed disabled:opacity-50"
          >
            {saveTemplateMutation.isPending ? "Saving..." : selectedTemplateId ? "Update template" : "Save template"}
          </button>
          <button
            type="button"
            onClick={() => void handleDeleteTemplate()}
            disabled={!selectedTemplateId || deleteTemplateMutation.isPending}
            className="self-end rounded-xl border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:bg-slate-950 dark:text-gray-300 dark:hover:bg-slate-900"
          >
            {deleteTemplateMutation.isPending ? "Deleting..." : "Delete"}
          </button>
        </div>

        {/* Entity Selector */}
        <div>
          <label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
            Entity
          </label>
          <div className="flex gap-3">
            {ENTITY_OPTIONS.map((opt) => (
              <button
                key={opt.value}
                type="button"
                onClick={() => handleEntityChange(opt.value)}
                className={clsx(
                  "rounded-xl px-4 py-2 text-sm font-medium transition-colors",
                  entity === opt.value
                    ? "bg-brand-600 text-white shadow-sm"
                    : "bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-slate-800 dark:text-gray-300 dark:hover:bg-slate-700",
                )}
              >
                {opt.label}
              </button>
            ))}
          </div>
          {entity === "resource_month" && (
            <div className="mt-4 space-y-4 rounded-2xl border border-emerald-200 bg-emerald-50/70 p-4 dark:border-emerald-900/60 dark:bg-emerald-950/20">
              <div className="flex flex-wrap items-end gap-4">
                <div>
                  <label className="mb-1 block text-sm font-medium text-emerald-900 dark:text-emerald-200">
                    Period month
                  </label>
                  <input
                    type="month"
                    value={periodMonth}
                    onChange={(e) => setPeriodMonth(e.target.value)}
                    className="rounded-xl border border-emerald-300 bg-white px-3 py-2 text-sm text-gray-700 focus:border-emerald-500 focus:ring-emerald-500 dark:border-emerald-900 dark:bg-slate-950 dark:text-gray-300"
                  />
                </div>
                <p className="max-w-2xl text-sm text-emerald-900/80 dark:text-emerald-200/80">
                  Resource Months uses the CapaKraken holiday and absence logic directly. SAH, booked hours and chargeability are calculated per resource and month with country, state and city context.
                </p>
              </div>

              <div className="grid gap-3 lg:grid-cols-3">
                {resourceMonthBlueprints.map((blueprint) => (
                  <button
                    key={blueprint.id}
                    type="button"
                    onClick={() => applyBlueprint(blueprint)}
                    className="rounded-2xl border border-emerald-200 bg-white/80 p-4 text-left transition hover:border-emerald-400 hover:bg-white dark:border-emerald-900/70 dark:bg-slate-950/60 dark:hover:border-emerald-700"
                  >
                    <div className="text-sm font-semibold text-emerald-950 dark:text-emerald-100">
                      {blueprint.label}
                    </div>
                    <p className="mt-1 text-xs leading-5 text-emerald-900/75 dark:text-emerald-200/75">
                      {blueprint.description}
                    </p>
                  </button>
                ))}
              </div>

              <div className="rounded-2xl border border-emerald-200/80 bg-white/60 p-4 dark:border-emerald-900/60 dark:bg-slate-950/40">
                {displayedResourceMonthCompleteness ? (
                  <div className="mb-4 rounded-2xl border border-emerald-200/80 bg-emerald-50/80 p-4 dark:border-emerald-900/60 dark:bg-emerald-950/20">
                    <div className="flex flex-wrap items-center gap-2">
                      <span
                        className={clsx(
                          "rounded-full px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.14em]",
                          displayedResourceMonthCompleteness.isAuditReady
                            ? "bg-emerald-500 text-white"
                            : "bg-amber-100 text-amber-800 dark:bg-amber-950/60 dark:text-amber-200",
                        )}
                      >
                        {displayedResourceMonthCompleteness.isAuditReady ? "Audit ready" : "Audit gap"}
                      </span>
                      <span className="rounded-full bg-white px-2.5 py-1 text-[11px] font-medium text-emerald-900 dark:bg-slate-950 dark:text-emerald-100">
                        {displayedResourceMonthCompleteness.selectedMinimumAuditColumnCount}/
                        {displayedResourceMonthCompleteness.minimumAuditColumnCount} minimum audit columns
                      </span>
                      <span className="rounded-full bg-white px-2.5 py-1 text-[11px] font-medium text-emerald-900 dark:bg-slate-950 dark:text-emerald-100">
                        {displayedResourceMonthCompleteness.selectedRecommendedColumnCount}/
                        {displayedResourceMonthCompleteness.recommendedColumnCount} recommended columns
                      </span>
                      <span className="rounded-full bg-white px-2.5 py-1 text-[11px] text-emerald-900/80 dark:bg-slate-950 dark:text-emerald-200/80">
                        {selectedTemplate && !hasTemplateDraftChanges ? "Saved template status" : "Current builder status"}
                      </span>
                    </div>
                    {displayedResourceMonthCompleteness.missingMinimumAuditColumns.length > 0 ? (
                      <p className="mt-3 text-xs text-amber-800 dark:text-amber-200">
                        Missing audit/export basis columns: {summarizeMissingColumns(
                          displayedResourceMonthCompleteness.missingMinimumAuditColumns,
                          columnLabelMap,
                        )}
                      </p>
                    ) : displayedResourceMonthCompleteness.missingRecommendedColumns.length > 0 ? (
                      <p className="mt-3 text-xs text-emerald-900/80 dark:text-emerald-200/80">
                        Audit-ready, but still missing recommended basis columns: {summarizeMissingColumns(
                          displayedResourceMonthCompleteness.missingRecommendedColumns,
                          columnLabelMap,
                        )}
                      </p>
                    ) : (
                      <p className="mt-3 text-xs text-emerald-900/80 dark:text-emerald-200/80">
                        This view includes the full recommended audit/export basis set for monthly SAH and chargeability checks.
                      </p>
                    )}
                  </div>
                ) : null}
                <div className="text-sm font-medium text-emerald-950 dark:text-emerald-100">
                  Recommended transparency columns
                </div>
                <div className="mt-2 flex flex-wrap gap-2">
                  {RESOURCE_MONTH_RECOMMENDED_COLUMNS.map((column) => (
                    <button
                      key={column}
                      type="button"
                      onClick={() => toggleColumn(column)}
                      className={clsx(
                        "rounded-full border px-3 py-1 text-xs font-medium transition",
                        selectedColumns.has(column)
                          ? "border-emerald-500 bg-emerald-500 text-white"
                          : "border-emerald-200 bg-white text-emerald-900 hover:border-emerald-400 dark:border-emerald-900 dark:bg-slate-950 dark:text-emerald-200 dark:hover:border-emerald-700",
                      )}
                    >
                      {columnLabelMap.get(column) ?? column}
                    </button>
                  ))}
                </div>
                <p className="mt-3 text-xs text-emerald-900/75 dark:text-emerald-200/75">
                  Formula reference: base available hours - holiday deduction - absence deduction = monthly SAH. Chargeability uses booked hours divided by monthly SAH.
                </p>
                <p className="mt-2 text-xs text-emerald-900/75 dark:text-emerald-200/75">
                  Export recommendation: include both basis columns and computed metrics in the CSV. That keeps Excel as a review layer instead of rebuilding CapaKraken logic outside the product.
                </p>
                <p className="mt-2 text-xs text-emerald-900/75 dark:text-emerald-200/75">
                  Minimum audit set: month, location context, SAH, holiday deductions, absence deductions, target hours, booked hours and unassigned hours.
                </p>
              </div>
            </div>
          )}
        </div>

        {/* Column Picker */}
        <div>
          <div className="mb-2 flex items-center justify-between">
            <label className="text-sm font-medium text-gray-700 dark:text-gray-300">
              Columns ({selectedColumns.size} selected)
            </label>
            <div className="flex gap-2">
              <button
                type="button"
                onClick={selectAllColumns}
                className="text-xs font-medium text-brand-600 hover:text-brand-700 dark:text-brand-400"
              >
                Select all
              </button>
              <span className="text-xs text-gray-300 dark:text-gray-600">|</span>
              <button
                type="button"
                onClick={clearAllColumns}
                className="text-xs font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
              >
                Clear
              </button>
            </div>
          </div>
          {columnsQuery.isLoading ? (
            <div className="py-3 text-sm text-gray-400">Loading columns...</div>
          ) : (
            <div className="grid grid-cols-2 gap-x-4 gap-y-1 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
              {availableColumns.map((col) => (
                <label
                  key={col.key}
                  className="flex cursor-pointer items-center gap-2 rounded-lg px-2 py-1.5 text-sm transition-colors hover:bg-gray-50 dark:hover:bg-slate-900"
                >
                  <input
                    type="checkbox"
                    checked={selectedColumns.has(col.key)}
                    onChange={() => toggleColumn(col.key)}
                    className="h-3.5 w-3.5 rounded border-gray-300 text-brand-600 focus:ring-brand-500 dark:border-slate-600"
                  />
                  <span className="text-gray-700 dark:text-gray-300">{col.label}</span>
                  {recommendedColumnSet.has(col.key) && (
                    <span className="rounded-full bg-emerald-100 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-[0.14em] text-emerald-700 dark:bg-emerald-950/60 dark:text-emerald-300">
                      Rec
                    </span>
                  )}
                  <span className="ml-auto text-[10px] uppercase tracking-wider text-gray-400 dark:text-gray-600">
                    {col.dataType}
                  </span>
                </label>
              ))}
            </div>
          )}
        </div>

        {/* Filter Builder */}
        <div>
          <div className="mb-2 flex items-center justify-between">
            <label className="text-sm font-medium text-gray-700 dark:text-gray-300">
              Filters
            </label>
            <button
              type="button"
              onClick={addFilter}
              className="flex items-center gap-1 text-xs font-medium text-brand-600 hover:text-brand-700 dark:text-brand-400"
            >
              <svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
              </svg>
              Add filter
            </button>
          </div>
          {filters.length === 0 ? (
            <p className="text-sm text-gray-400 dark:text-gray-500">No filters applied.</p>
          ) : (
            <div className="space-y-2">
              {filters.map((filter) => (
                <div key={filter.id} className="flex items-center gap-2">
                  {/* Field */}
                  <select
                    value={filter.field}
                    onChange={(e) => updateFilter(filter.id, { field: e.target.value })}
                    className="w-44 rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-sm text-gray-700 focus:border-brand-500 focus:ring-brand-500 dark:border-slate-700 dark:bg-slate-900 dark:text-gray-300"
                  >
                    {scalarColumns.map((col) => (
                      <option key={col.key} value={col.key}>{col.label}</option>
                    ))}
                  </select>

                  {/* Operator */}
                  <select
                    value={filter.op}
                    onChange={(e) => updateFilter(filter.id, { op: e.target.value as FilterOp })}
                    className="w-36 rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-sm text-gray-700 focus:border-brand-500 focus:ring-brand-500 dark:border-slate-700 dark:bg-slate-900 dark:text-gray-300"
                  >
                    {OPERATOR_OPTIONS.map((op) => (
                      <option key={op.value} value={op.value}>{op.label}</option>
                    ))}
                  </select>

                  {/* Value */}
                  <input
                    type="text"
                    value={filter.value}
                    onChange={(e) => updateFilter(filter.id, { value: e.target.value })}
                    placeholder="Value..."
                    className="min-w-0 flex-1 rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-sm text-gray-700 placeholder:text-gray-400 focus:border-brand-500 focus:ring-brand-500 dark:border-slate-700 dark:bg-slate-900 dark:text-gray-300 dark:placeholder:text-gray-600"
                  />

                  {/* Remove */}
                  <button
                    type="button"
                    onClick={() => removeFilter(filter.id)}
                    className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-950 dark:hover:text-red-400"
                    title="Remove filter"
                  >
                    <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
                    </svg>
                  </button>
                </div>
              ))}
            </div>
          )}
        </div>

        {/* Sort & Group */}
        <div className="flex flex-wrap gap-4">
          <div className="min-w-[160px]">
            <label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
              Group by
            </label>
            <select
              value={groupBy}
              onChange={(e) => setGroupBy(e.target.value)}
              className="w-full rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-sm text-gray-700 focus:border-brand-500 focus:ring-brand-500 dark:border-slate-700 dark:bg-slate-900 dark:text-gray-300"
            >
              <option value="">None</option>
              {scalarColumns.map((col) => (
                <option key={col.key} value={col.key}>{col.label}</option>
              ))}
            </select>
          </div>
          <div className="min-w-[160px]">
            <label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
              Sort by
            </label>
            <select
              value={sortBy}
              onChange={(e) => setSortBy(e.target.value)}
              className="w-full rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-sm text-gray-700 focus:border-brand-500 focus:ring-brand-500 dark:border-slate-700 dark:bg-slate-900 dark:text-gray-300"
            >
              <option value="">Default</option>
              {scalarColumns.map((col) => (
                <option key={col.key} value={col.key}>{col.label}</option>
              ))}
            </select>
          </div>
          <div className="min-w-[120px]">
            <label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
              Direction
            </label>
            <select
              value={sortDir}
              onChange={(e) => setSortDir(e.target.value as "asc" | "desc")}
              className="w-full rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-sm text-gray-700 focus:border-brand-500 focus:ring-brand-500 dark:border-slate-700 dark:bg-slate-900 dark:text-gray-300"
            >
              <option value="asc">Ascending</option>
              <option value="desc">Descending</option>
            </select>
          </div>
        </div>

        {/* Run button */}
        <div className="flex items-center gap-3">
          <button
            type="button"
            onClick={handleRun}
            disabled={selectedColumns.size === 0}
            className="inline-flex items-center gap-2 rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-brand-700 disabled:cursor-not-allowed disabled:opacity-50"
          >
            <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
            </svg>
            Run Report
          </button>
          {selectedColumns.size === 0 && (
            <span className="text-sm text-gray-400 dark:text-gray-500">Select at least one column</span>
          )}
        </div>
      </div>

      {/* Results */}
      {runQuery && (
        <div className="rounded-2xl border border-gray-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-950">
          {/* Results Header */}
          <div className="flex items-center justify-between border-b border-gray-200 px-6 py-4 dark:border-slate-800">
            <div className="space-y-1">
              <div className="flex items-center gap-3">
                <h2 className="text-lg font-semibold text-gray-900 dark:text-gray-50">Results</h2>
                {!isLoading && (
                  <span className="rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 dark:bg-slate-800 dark:text-gray-400">
                    {totalCount.toLocaleString()} row{totalCount !== 1 ? "s" : ""}
                  </span>
                )}
              </div>
              <p className="text-xs text-gray-500 dark:text-gray-400">
                {reportExplainability?.entity === "resource_month"
                  ? "Exports include the report sheet plus an Explainability sheet with location, holiday, absence and SAH basis."
                  : "CSV exports include the selected basis columns and computed CapaKraken metrics exactly as shown here."}
              </p>
              {groupBy && rows.length > 0 ? (
                <p className="text-xs text-gray-500 dark:text-gray-400">
                  Grouped by {columnLabelMap.get(groupBy) ?? groupBy} with page-local section headers.
                </p>
              ) : null}
            </div>
            <button
              type="button"
              onClick={() => void handleExport()}
              disabled={exportMutation.isPending || totalCount === 0}
              className="inline-flex items-center gap-1.5 rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 shadow-sm transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:bg-slate-900 dark:text-gray-300 dark:hover:bg-slate-800"
            >
              <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
              </svg>
              {exportMutation.isPending ? "Exporting..." : reportExplainability?.entity === "resource_month" ? "Export XLSX" : "Export CSV"}
            </button>
          </div>

          {reportExplainability?.entity === "resource_month" ? (
            <div className="border-b border-emerald-100 bg-emerald-50/70 px-6 py-4 text-sm dark:border-emerald-950/60 dark:bg-emerald-950/20">
              <div className="flex flex-wrap gap-2">
                <span className="rounded-full bg-white px-2.5 py-1 text-[11px] font-medium text-emerald-900 dark:bg-slate-950 dark:text-emerald-100">
                  Month: {reportExplainability.periodMonth ?? "current"}
                </span>
                <span className="rounded-full bg-white px-2.5 py-1 text-[11px] font-medium text-emerald-900 dark:bg-slate-950 dark:text-emerald-100">
                  Location: {(reportExplainability.locationContextColumns.length > 0
                    ? reportExplainability.locationContextColumns
                    : ["none"]).map((column) => columnLabelMap.get(column) ?? column).join(", ")}
                </span>
                <span className="rounded-full bg-white px-2.5 py-1 text-[11px] font-medium text-emerald-900 dark:bg-slate-950 dark:text-emerald-100">
                  Holidays: {(reportExplainability.holidayMetricColumns.length > 0
                    ? reportExplainability.holidayMetricColumns
                    : ["none"]).map((column) => columnLabelMap.get(column) ?? column).join(", ")}
                </span>
                <span className="rounded-full bg-white px-2.5 py-1 text-[11px] font-medium text-emerald-900 dark:bg-slate-950 dark:text-emerald-100">
                  Absences: {(reportExplainability.absenceMetricColumns.length > 0
                    ? reportExplainability.absenceMetricColumns
                    : ["none"]).map((column) => columnLabelMap.get(column) ?? column).join(", ")}
                </span>
                <span className="rounded-full bg-white px-2.5 py-1 text-[11px] font-medium text-emerald-900 dark:bg-slate-950 dark:text-emerald-100">
                  Capacity: {(reportExplainability.capacityMetricColumns.length > 0
                    ? reportExplainability.capacityMetricColumns
                    : ["none"]).map((column) => columnLabelMap.get(column) ?? column).join(", ")}
                </span>
              </div>
              <p className="mt-3 text-xs text-emerald-900/80 dark:text-emerald-200/80">
                {reportExplainability.notes.join(" ")}
              </p>
              {reportExplainability.missingRecommendedColumns.length > 0 ? (
                <p className="mt-2 text-xs text-amber-700 dark:text-amber-300">
                  Missing recommended audit columns: {summarizeMissingColumns(
                    reportExplainability.missingRecommendedColumns,
                    columnLabelMap,
                  )}
                </p>
              ) : null}
            </div>
          ) : null}

          {/* Table */}
          <div className="overflow-x-auto">
            {isLoading ? (
              <div className="flex items-center justify-center py-16">
                <div className="h-8 w-8 animate-spin rounded-full border-2 border-brand-600 border-t-transparent" />
              </div>
            ) : rows.length === 0 ? (
              <div className="py-16 text-center text-sm text-gray-400 dark:text-gray-500">
                No data found. Try adjusting your filters.
              </div>
            ) : (
              <table className="w-full text-sm">
                <thead>
                  <tr className="border-b border-gray-200 bg-gray-50/80 dark:border-slate-800 dark:bg-slate-900/50">
                    {outputColumns.map((col) => {
                      const isSortable = !col.includes(".");
                      const isSorted = sortBy === col;
                      return (
                        <th
                          key={col}
                          onClick={isSortable ? () => handleSort(col) : undefined}
                          className={clsx(
                            "whitespace-nowrap px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400",
                            isSortable && "cursor-pointer select-none hover:text-gray-700 dark:hover:text-gray-200",
                          )}
                        >
                          <span className="inline-flex items-center gap-1">
                            {columnLabelMap.get(col) ?? col}
                            {isSorted && (
                              <svg className={clsx("h-3 w-3", sortDir === "desc" && "rotate-180")} fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
                              </svg>
                            )}
                          </span>
                        </th>
                      );
                    })}
                  </tr>
                </thead>
                <tbody className="divide-y divide-gray-100 dark:divide-slate-800/60">
                  {rows.map((row, idx) => {
                    const group = groupStartByIndex.get(idx);

                    return (
                      <Fragment key={`grouped-row:${String(row.id ?? idx)}:${idx}`}>
                        {group ? (
                          <tr key={`${group.key}:${idx}`} className="bg-brand-50/70 dark:bg-brand-950/20">
                            <td
                              colSpan={outputColumns.length}
                              className="px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-brand-700 dark:text-brand-200"
                            >
                              {columnLabelMap.get(groupBy) ?? groupBy}: {group.label} · {group.rowCount} row{group.rowCount === 1 ? "" : "s"}
                            </td>
                          </tr>
                        ) : null}
                        <tr
                          key={(row.id as string) ?? idx}
                          className="transition-colors hover:bg-gray-50/60 dark:hover:bg-slate-900/40"
                        >
                          {outputColumns.map((col) => (
                            <td
                              key={col}
                              className="whitespace-nowrap px-4 py-2.5 text-gray-700 dark:text-gray-300"
                            >
                              {formatCellValue(row[col])}
                            </td>
                          ))}
                        </tr>
                      </Fragment>
                    );
                  })}
                </tbody>
              </table>
            )}
          </div>

          {/* Pagination */}
          {totalPages > 1 && (
            <div className="flex items-center justify-between border-t border-gray-200 px-6 py-3 dark:border-slate-800">
              <span className="text-sm text-gray-500 dark:text-gray-400">
                Page {page + 1} of {totalPages}
              </span>
              <div className="flex gap-2">
                <button
                  type="button"
                  onClick={() => setPage((p) => Math.max(0, p - 1))}
                  disabled={page === 0}
                  className="rounded-lg border border-gray-300 px-3 py-1.5 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-gray-300 dark:hover:bg-slate-800"
                >
                  Previous
                </button>
                <button
                  type="button"
                  onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
                  disabled={page >= totalPages - 1}
                  className="rounded-lg border border-gray-300 px-3 py-1.5 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-gray-300 dark:hover:bg-slate-800"
                >
                  Next
                </button>
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ─── Helpers ────────────────────────────────────────────────────────────────

function formatCellValue(value: unknown): string {
  if (value === null || value === undefined) return "--";
  if (typeof value === "boolean") return value ? "Yes" : "No";
  if (typeof value === "string") {
    // ISO date detection
    if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
      return new Date(value).toLocaleDateString("de-DE", {
        year: "numeric",
        month: "2-digit",
        day: "2-digit",
      });
    }
    return value;
  }
  if (typeof value === "number") {
    return value.toLocaleString("de-DE");
  }
  return String(value);
}
