"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { ArrowRightIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
import {
  useAddressMatches,
  useBulkUploadProgress,
  useConfirmMultiEntryOrdering,
  useConfirmVerification,
  useEditClassification,
  useFinalize,
  useRequestCombine,
  useSampleClassifications,
  type SampleClassifications,
} from "@/lib/bulkUpload/client";
import AddressesPanel from "./AddressesPanel";
import {
  partLabel,
  isPermutation,
  assignmentToPermutation,
  type MultiEntrySample,
} from "@/lib/bulkUpload/multiEntry";
import type { MultiEntryOrdering } from "@/app/db/schema/bulk_address_uploads";
import { CATEGORY_VALUES } from "@/lib/landlordOverrides/categories";
import { CLASSIFIER_FIELDS } from "@/lib/bulkUpload/columnFields";
import { statusLabel, isTerminalStatus } from "@/lib/bulkUpload/types";

// Our category label per classifier field (e.g. built_form_type → "Built Form").
// Distinguishes the categories when several read from the same source column —
// Property Type and Built Form both come from one column, so labelling by the
// customer's header alone is ambiguous.
const FIELD_LABEL: Record<string, string> = Object.fromEntries(
  CLASSIFIER_FIELDS.map((f) => [f.value, f.label]),
);

interface Props {
  portfolioSlug: string;
  portfolioId: string;
  uploadId: string;
  // The status at the last server render. Used to refresh the server page exactly
  // once when polling first observes a terminal status (async finalise, ADR-0005),
  // so the page advances from "Uploading to ARA" to the "Processing complete" card.
  serverStatus: string;
  isDomnaUser: boolean;
}

const TASK_TERMINAL_STATUSES = new Set(["complete", "completed", "failed", "failure", "error"]);
const TASK_FAILED_STATUSES = new Set(["failed", "failure", "error"]);

export default function OnboardingProgress({
  portfolioSlug,
  portfolioId,
  uploadId,
  serverStatus,
  isDomnaUser,
}: Props) {
  const router = useRouter();
  const progress = useBulkUploadProgress(portfolioId, uploadId, {
    // When the async finaliser finishes, the poll flips the status to a terminal
    // value while the server page is still on `finalising`. Refresh once so the
    // server re-renders the "Processing complete" / "failed" card. Guarding on the
    // non-terminal serverStatus prevents a refresh loop: after the refresh the
    // prop is terminal, so this no-ops.
    onSuccess: (data) => {
      if (!isTerminalStatus(serverStatus) && isTerminalStatus(data.upload.status)) {
        router.refresh();
      }
    },
  });
  const combine = useRequestCombine(portfolioId, uploadId);
  const finalize = useFinalize(portfolioId, uploadId);
  const [reviewTab, setReviewTab] = useState<"classification" | "addresses">(
    "classification",
  );

  // Read-only classifications for the multi-entry sample (issue #298). Fetched
  // only once a sample exists at awaiting_review. Hook stays above the early
  // returns so its order is stable.
  const sampleReady =
    progress.data?.upload.status === "awaiting_review" &&
    !!progress.data.upload.multiEntrySummary?.sample;
  const classifications = useSampleClassifications(portfolioId, uploadId, sampleReady);
  // Combiner rows address2uprn could not confidently match (ADR-0057). Fetched
  // once at awaiting_review; drives the Addresses tab + the Finalise gate.
  const addressMatches = useAddressMatches(
    portfolioId,
    uploadId,
    progress.data?.upload.status === "awaiting_review",
  );

  if (progress.isError) return null;
  if (!progress.data) {
    return (
      <div className="mt-4 flex items-center gap-2 text-sm text-gray-400">
        <span className="w-4 h-4 rounded-full border-2 border-gray-300 border-t-transparent animate-spin" />
        Loading progress…
      </div>
    );
  }

  const { task, upload } = progress.data;
  // Address-matching batches drive the bar; classification is shown separately.
  const total = task?.addressTotal ?? 0;
  const completedSubtasks = task?.addressCompleted ?? 0;
  const failedSubtasks = task?.addressFailed ?? 0;
  const percent = total > 0 ? Math.round((completedSubtasks / total) * 100) : 0;

  const classifierTotal = task?.classifierTotal ?? 0;
  const classifierCompleted = task?.classifierCompleted ?? 0;
  const classifierFailed = task?.classifierFailed ?? 0;

  const taskStatus = task?.status.toLowerCase() ?? "";
  const taskDone = TASK_TERMINAL_STATUSES.has(taskStatus);
  const taskFailed = TASK_FAILED_STATUSES.has(taskStatus);
  const isCombining = upload.status === "combining";
  const isImporting = upload.status === "awaiting_review";
  // Async finaliser in flight (ADR-0005). Polling continues (non-terminal) until
  // the backend writes complete/failed. Surfaced to the user as "Uploading to ARA".
  const isFinalising = upload.status === "finalising";

  const canRunCombiner = taskDone && !taskFailed && upload.status === "processing";
  const isAwaitingReview = upload.status === "awaiting_review";

  // Two-step review on the awaiting_review surface (ADR-0004). The sample exists
  // whenever classifier columns were mapped; multi-entry is largestCount >= 2.
  // Step 1 (verify) applies whenever there's a sample; Step 2 (order) only when
  // multi-entry. Each gates Finalise where it applies.
  const sample = isAwaitingReview ? (upload.multiEntrySummary?.sample ?? null) : null;
  const isMultiEntry = (upload.multiEntrySummary?.largestCount ?? 0) >= 2;
  const verifyAck = upload.verifyAck ?? false;
  const orderingConfirmed = upload.multiEntryOrdering?.confirmed ?? false;
  const needsVerify = !!sample;
  const needsOrdering = !!sample && isMultiEntry;
  // One ordering panel per distinct count ≥ 2, ascending (ADR-0004 amendment).
  // Fall back to the single Step-1 sample for uploads detected before per-count
  // capture existed (samplesByCount absent).
  const samplesByCount = upload.multiEntrySummary?.samplesByCount;
  const orderingSamples: Array<[string, MultiEntrySample]> =
    samplesByCount && Object.keys(samplesByCount).length > 0
      ? Object.entries(samplesByCount).sort(([a], [b]) => Number(a) - Number(b))
      : sample && isMultiEntry
        ? [[String(sample.count), sample]]
        : [];
  const showStepNumbers = needsVerify && needsOrdering;
  // Descriptions still classified `Unknown` block Finalise — the user must map
  // every one to a real value, else the finaliser fails loudly (ADR-0006).
  const unknownByField = classifications.data?.unknown ?? {};
  const unknownTotal = Object.values(unknownByField).reduce(
    (n, descriptions) => n + descriptions.length,
    0,
  );
  // Flagged addresses (no confident UPRN) must all be resolved before finalise,
  // else a withheld row lands as an unintended no-UPRN property (ADR-0057).
  const flaggedCount = addressMatches.data?.flagged.length ?? 0;
  const unresolvedAddresses = addressMatches.data?.unresolved ?? 0;
  const showAddressTab = isAwaitingReview && flaggedCount > 0;
  const canFinalize =
    isAwaitingReview &&
    (!needsVerify || verifyAck) &&
    (!needsOrdering || orderingConfirmed) &&
    unknownTotal === 0 &&
    unresolvedAddresses === 0;

  return (
    <div className="mt-6 space-y-3">
      <div className="w-full bg-gray-100 rounded-full h-2 overflow-hidden">
        <div
          className={`h-2 rounded-full transition-all duration-500 ${taskFailed ? "bg-red-400" : "bg-[#14163d]"}`}
          style={{ width: total > 0 ? `${percent}%` : "4%" }}
        />
      </div>

      <div className="flex items-center gap-4 text-xs text-gray-500">
        {/* Address matching: standardises addresses against the OS lookup, in batches. */}
        <span className="flex items-center gap-1">
          <span className="text-gray-400">Address matching:</span>
          {failedSubtasks > 0 ? (
            <span className="flex items-center gap-1 text-red-500 font-semibold">
              <span className="w-1.5 h-1.5 rounded-full bg-red-400" />
              {failedSubtasks} of {total} batches failed
            </span>
          ) : total > 0 && completedSubtasks >= total ? (
            <span className="font-semibold text-green-600">complete</span>
          ) : (
            <span className="flex items-center gap-1 text-blue-500">
              <span className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse" />
              running{total > 0 ? ` · ${completedSubtasks} / ${total} batches` : ""}
            </span>
          )}
        </span>
        {/* Classification: turns the landlord's free-text descriptions into EPC categories. */}
        {classifierTotal > 0 && (
          <span className="flex items-center gap-1">
            <span className="text-gray-400">Classification:</span>
            {classifierFailed > 0 ? (
              <span className="flex items-center gap-1 text-red-500 font-semibold">
                <span className="w-1.5 h-1.5 rounded-full bg-red-400" />
                failed
              </span>
            ) : classifierCompleted >= classifierTotal ? (
              <span className="font-semibold text-green-600">complete</span>
            ) : (
              <span className="flex items-center gap-1 text-blue-500">
                <span className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse" />
                running
              </span>
            )}
          </span>
        )}
        {isCombining && (
          <span className="flex items-center gap-1 text-blue-500">
            <span className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse" />
            Combining results…
          </span>
        )}
        {isImporting && (
          <span className="flex items-center gap-1 text-blue-500">
            <span className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse" />
            Awaiting import
          </span>
        )}
        {isFinalising && (
          <span className="flex items-center gap-1 text-blue-500">
            <span className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse" />
            {statusLabel("finalising")}…
          </span>
        )}
      </div>

      {/* Two-tab review at awaiting_review: classification checks | address/UPRN
          confirmation (ADR-0057). The Addresses tab only appears when there are
          flagged rows; otherwise the classification panels render as before. */}
      {showAddressTab && (
        <div className="flex gap-4 border-b border-gray-200 text-sm">
          <button
            type="button"
            onClick={() => setReviewTab("classification")}
            className={`-mb-px border-b-2 px-1 pb-2 transition-colors ${
              reviewTab === "classification"
                ? "border-[#14163d] font-medium text-[#14163d]"
                : "border-transparent text-gray-500 hover:text-gray-700"
            }`}
          >
            Classification
          </button>
          <button
            type="button"
            onClick={() => setReviewTab("addresses")}
            className={`-mb-px flex items-center gap-1.5 border-b-2 px-1 pb-2 transition-colors ${
              reviewTab === "addresses"
                ? "border-[#14163d] font-medium text-[#14163d]"
                : "border-transparent text-gray-500 hover:text-gray-700"
            }`}
          >
            Addresses
            {unresolvedAddresses > 0 && (
              <span className="rounded-full bg-amber-500 px-1.5 text-[10px] font-semibold text-white">
                {unresolvedAddresses}
              </span>
            )}
          </button>
        </div>
      )}

      {(!showAddressTab || reviewTab === "classification") && (
        <>
          {needsVerify && sample && (
            <VerifyClassificationPanel
              sample={sample}
              classifications={classifications.data?.classifications ?? {}}
              verified={verifyAck}
              stepLabel={showStepNumbers ? "Step 1" : undefined}
              portfolioId={portfolioId}
              uploadId={uploadId}
            />
          )}

          {isAwaitingReview && unknownTotal > 0 && (
            <UnresolvedClassificationsPanel
              unknown={unknownByField}
              portfolioId={portfolioId}
              uploadId={uploadId}
            />
          )}

          {needsOrdering && orderingSamples.length > 0 && (
            <div className="space-y-3">
              {orderingSamples.map(([count, orderSample], i) => (
                <MultiEntryOrderingPanel
                  key={count}
                  sample={orderSample}
                  ordering={upload.multiEntryOrdering ?? null}
                  classifications={classifications.data?.classifications ?? {}}
                  // Number the panels only when there's also a verify step or more
                  // than one count, so a lone ordering panel stays unnumbered.
                  stepLabel={
                    showStepNumbers
                      ? `Step ${i + 2}`
                      : orderingSamples.length > 1
                        ? `Part group ${i + 1}`
                        : undefined
                  }
                  portfolioId={portfolioId}
                  uploadId={uploadId}
                />
              ))}
            </div>
          )}
        </>
      )}

      {showAddressTab && reviewTab === "addresses" && (
        <AddressesPanel
          flagged={addressMatches.data?.flagged ?? []}
          portfolioId={portfolioId}
          uploadId={uploadId}
        />
      )}

      {(canRunCombiner || isAwaitingReview) && (
        <div className="flex flex-col gap-2 pt-2">
          {canRunCombiner && (
            <StageButton
              label="Run Combiner"
              activeLabel="Starting combiner…"
              isPending={combine.isPending}
              onClick={() => combine.mutate()}
            />
          )}
          {isAwaitingReview && (
            <StageButton
              label="Finalise"
              activeLabel="Finalising…"
              isPending={finalize.isPending}
              disabled={!canFinalize}
              disabledReason={
                unresolvedAddresses > 0
                  ? `Resolve ${unresolvedAddresses} address${unresolvedAddresses === 1 ? "" : "es"} on the Addresses tab first`
                  : unknownTotal > 0
                    ? `Resolve ${unknownTotal} unclassified description${unknownTotal === 1 ? "" : "s"} first`
                    : needsVerify && !verifyAck
                      ? "Verify the classification first"
                      : "Confirm the building-part order first"
              }
              onClick={() =>
                finalize.mutate(undefined, { onSuccess: () => router.refresh() })
              }
            />
          )}
        </div>
      )}

      {combine.error && (
        <p className="text-xs text-red-500">{combine.error.message}</p>
      )}
      {finalize.error && (
        <div className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700">
          <p className="font-semibold">Import failed</p>
          <p className="text-red-600 mt-0.5 break-words">{finalize.error.message}</p>
        </div>
      )}

      {isDomnaUser && (
        <Link
          href={`/portfolio/${portfolioSlug}/settings/logs`}
          className="text-xs text-gray-400 hover:text-gray-700 underline underline-offset-2 transition-colors"
        >
          View detailed logs
        </Link>
      )}
    </div>
  );
}

// Step 1 — Verify classification (ADR-0004). Lists how we read the sample
// address's descriptions (per mapped classifier column) and lets the user
// correct any, written back as source='user'. Acknowledging unlocks Finalise.
// Shown whenever classifier columns were mapped, multi-entry or not.
function VerifyClassificationPanel({
  sample,
  classifications,
  verified,
  stepLabel,
  portfolioId,
  uploadId,
}: {
  sample: MultiEntrySample;
  classifications: SampleClassifications;
  verified: boolean;
  stepLabel?: string;
  portfolioId: string;
  uploadId: string;
}) {
  const editClassification = useEditClassification(portfolioId, uploadId);
  const confirm = useConfirmVerification(portfolioId, uploadId);

  return (
    <div className="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3">
      <p className="text-sm font-semibold text-amber-900">
        {stepLabel ? `${stepLabel} — ` : ""}Verify classification
      </p>
      <p className="mt-0.5 text-xs text-amber-800">
        Based on your column mapping, here&apos;s how we read{" "}
        {sample.address ? <span className="font-medium">{sample.address}</span> : "one sample address"}.
        Correct anything that&apos;s wrong, then confirm.
      </p>

      <div className="mt-3 space-y-3">
        {sample.columns.map((column) => {
          // One editable row per distinct description, in file order.
          const seen = new Set<string>();
          const entries = column.entries.filter((entry) => {
            if (seen.has(entry.description)) return false;
            seen.add(entry.description);
            return true;
          });
          const options =
            CATEGORY_VALUES[column.field as keyof typeof CATEGORY_VALUES] ?? [];
          return (
            <div key={column.field}>
              <p className="text-[11px] font-semibold uppercase tracking-wide text-amber-700">
                {FIELD_LABEL[column.field] ?? column.field}
                <span className="ml-1.5 font-normal normal-case tracking-normal text-amber-600">
                  — from your &ldquo;{column.header}&rdquo; column
                </span>
              </p>
              <div className="mt-1 space-y-1">
                {entries.map((entry) => {
                  const classified = classifications[column.field]?.[entry.description] ?? "";
                  return (
                    <div
                      key={entry.description}
                      className="flex items-center gap-2 text-xs text-amber-900"
                    >
                      <span className="min-w-0 flex-1 truncate" title={entry.raw}>
                        {entry.raw}
                      </span>
                      <span className="text-amber-400">→</span>
                      <select
                        value={classified}
                        onChange={(e) =>
                          editClassification.mutate({
                            field: column.field,
                            description: entry.description,
                            value: e.target.value,
                          })
                        }
                        disabled={editClassification.isPending}
                        className="max-w-[14rem] rounded border border-amber-300 bg-white px-1.5 py-0.5 text-[11px] text-amber-900"
                      >
                        <option value="" disabled>
                          {classified ? classified : "not classified"}
                        </option>
                        {options.map((opt) => (
                          <option key={opt} value={opt}>
                            {opt}
                          </option>
                        ))}
                      </select>
                    </div>
                  );
                })}
              </div>
            </div>
          );
        })}
      </div>

      <p className="mt-2 text-[11px] text-amber-700">
        Correcting a classification updates that description for{" "}
        <span className="font-medium">every</span> row across the portfolio, not
        just this address.
      </p>
      {editClassification.error && (
        <p className="mt-1 text-xs text-red-600">{editClassification.error.message}</p>
      )}

      <div className="mt-3 flex items-center gap-3">
        <button
          onClick={() => confirm.mutate()}
          disabled={confirm.isPending}
          className={`inline-flex items-center gap-1.5 rounded-lg px-4 py-1.5 text-xs font-bold text-white transition-opacity ${
            confirm.isPending ? "bg-amber-300 cursor-not-allowed" : "bg-amber-600 hover:opacity-90"
          }`}
        >
          {confirm.isPending ? "Saving…" : verified ? "Re-confirm" : "These look right"}
        </button>
        {verified && !confirm.isPending && (
          <span className="inline-flex items-center gap-1 text-xs font-semibold text-green-600">
            <CheckCircleIcon className="h-4 w-4" />
            Classification verified
          </span>
        )}
      </div>
      {confirm.error && (
        <p className="mt-2 text-xs text-red-600">{confirm.error.message}</p>
      )}
    </div>
  );
}

// Interactive building-part ordering for ONE entry-count's sample (ADR-0004,
// amended 2026-06-05 — one panel per distinct count). The user labels each file
// position with a building part (one Main building + Extensions); the labels
// must form a permutation. Confirming persists this count's ordering (merged
// server-side with the other counts'); Finalise unlocks once every count is
// confirmed.
function MultiEntryOrderingPanel({
  sample,
  ordering,
  classifications,
  stepLabel,
  portfolioId,
  uploadId,
}: {
  sample: MultiEntrySample;
  ordering: MultiEntryOrdering | null;
  classifications: SampleClassifications;
  stepLabel?: string;
  portfolioId: string;
  uploadId: string;
}) {
  const confirm = useConfirmMultiEntryOrdering(portfolioId, uploadId);
  const count = sample.count;
  // Only the multi-valued columns are sliced into building parts; single-value
  // columns are whole-dwelling facts (verified in Step 1, not ordered here).
  const orderColumns = sample.columns.filter((column) => column.entries.length > 1);

  // assignment[filePosition] = building-part slot. Seed from a stored ordering
  // (slot -> position, so invert) or default to identity (main building first).
  const [assignment, setAssignment] = useState<number[]>(() => {
    const stored = ordering?.permutations?.[String(count)];
    if (stored && stored.length === count) {
      const seeded = new Array<number>(count);
      stored.forEach((position, slot) => {
        seeded[position] = slot;
      });
      return seeded;
    }
    return Array.from({ length: count }, (_, i) => i);
  });

  // Per-panel confirmation reflects whether THIS count's permutation is stored,
  // not the global all-counts-confirmed flag — so each panel gives its own
  // feedback as the user works through them.
  const confirmed = Array.isArray(ordering?.permutations?.[String(count)]);
  const valid = isPermutation(assignment);

  const setSlot = (position: number, slot: number) =>
    setAssignment((prev) => prev.map((s, i) => (i === position ? slot : s)));

  const onConfirm = () => {
    if (!valid) return;
    confirm.mutate({
      permutations: { [String(count)]: assignmentToPermutation(assignment) },
    });
  };

  return (
    <div className="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3">
      <p className="text-sm font-semibold text-amber-900">
        {stepLabel ? `${stepLabel} — ` : ""}Confirm building-part order
      </p>
      <p className="mt-0.5 text-xs text-amber-800">
        {sample.address ? <span className="font-medium">{sample.address}</span> : "An address"}{" "}
        has {count} building parts. Tell us which entry is the main building and
        which are extensions — we&apos;ll apply the same order to every{" "}
        {count}-part row in this file.
      </p>

      <div className="mt-3 overflow-x-auto">
        <table className="w-full border-collapse text-xs">
          <thead>
            <tr className="text-left text-amber-700">
              <th className="py-1 pr-3 font-medium">Entry</th>
              {orderColumns.map((column) => (
                <th key={column.field} className="py-1 pr-3 font-medium">
                  {FIELD_LABEL[column.field] ?? column.header}
                  <span className="block text-[10px] font-normal text-amber-500">
                    your &ldquo;{column.header}&rdquo;
                  </span>
                </th>
              ))}
              <th className="py-1 pr-3 font-medium">Building part</th>
            </tr>
          </thead>
          <tbody>
            {Array.from({ length: count }).map((_, position) => (
              <tr key={position} className="border-t border-amber-100 text-amber-900">
                <td className="py-1 pr-3 text-amber-600">{position + 1}</td>
                {orderColumns.map((column) => {
                  const entry = column.entries[position];
                  const classified = entry
                    ? classifications[column.field]?.[entry.description] ?? ""
                    : "";
                  return (
                    <td key={column.field} className="py-1 pr-3 align-top">
                      <div>{entry?.raw ?? "—"}</div>
                      {/* Read-only classification annotation; edit it in Step 1. */}
                      {entry && (
                        <div className="mt-0.5 text-[11px] text-amber-600">
                          {classified || "not classified"}
                        </div>
                      )}
                    </td>
                  );
                })}
                <td className="py-1 pr-3">
                  <select
                    value={assignment[position]}
                    onChange={(e) => setSlot(position, Number(e.target.value))}
                    className="rounded border border-amber-300 bg-white px-2 py-1 text-xs text-amber-900"
                  >
                    {Array.from({ length: count }).map((_, slot) => (
                      <option key={slot} value={slot}>
                        {partLabel(slot)}
                      </option>
                    ))}
                  </select>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {!valid && (
        <p className="mt-2 text-xs text-red-600">
          Each part (Main building, Extension 1, …) must be used exactly once.
        </p>
      )}

      <div className="mt-3 flex items-center gap-3">
        <button
          onClick={onConfirm}
          disabled={!valid || confirm.isPending}
          className={`inline-flex items-center gap-1.5 rounded-lg px-4 py-1.5 text-xs font-bold text-white transition-opacity ${
            !valid || confirm.isPending
              ? "bg-amber-300 cursor-not-allowed"
              : "bg-amber-600 hover:opacity-90"
          }`}
        >
          {confirm.isPending ? "Saving…" : confirmed ? "Update order" : "Confirm order"}
        </button>
        {confirmed && !confirm.isPending && (
          <span className="inline-flex items-center gap-1 text-xs font-semibold text-green-600">
            <CheckCircleIcon className="h-4 w-4" />
            Order confirmed
          </span>
        )}
      </div>

      {confirm.error && (
        <p className="mt-2 text-xs text-red-600">{confirm.error.message}</p>
      )}
    </div>
  );
}

// Unresolved-classification gate (ADR-0006). Lists every description still
// classified `Unknown` portfolio-wide and lets the user map each to a real value
// via the same per-description override path as Step 1 (it applies portfolio-
// wide). Finalise stays blocked until this list is empty — `Unknown` is never a
// final value, and an unresolved one would fail the import loudly.
function UnresolvedClassificationsPanel({
  unknown,
  portfolioId,
  uploadId,
}: {
  unknown: Record<string, string[]>;
  portfolioId: string;
  uploadId: string;
}) {
  const editClassification = useEditClassification(portfolioId, uploadId);
  const total = Object.values(unknown).reduce((n, d) => n + d.length, 0);

  return (
    <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3">
      <p className="text-sm font-semibold text-red-900">
        Resolve unclassified descriptions ({total})
      </p>
      <p className="mt-0.5 text-xs text-red-800">
        We couldn&apos;t classify these automatically. Map each to a category
        before finalising — an unresolved value would fail the import. Edits apply
        to <span className="font-medium">every</span> row across the portfolio.
      </p>

      <div className="mt-3 space-y-3">
        {Object.entries(unknown).map(([field, descriptions]) => {
          const options = (
            CATEGORY_VALUES[field as keyof typeof CATEGORY_VALUES] ?? []
          ).filter((o) => o !== "Unknown");
          return (
            <div key={field}>
              <p className="text-[11px] font-semibold uppercase tracking-wide text-red-700">
                {FIELD_LABEL[field] ?? field}
              </p>
              <div className="mt-1 space-y-1">
                {descriptions.map((description) => (
                  <div
                    key={description}
                    className="flex items-center gap-2 text-xs text-red-900"
                  >
                    <span className="min-w-0 flex-1 truncate" title={description}>
                      {description}
                    </span>
                    <span className="text-red-400">→</span>
                    <select
                      defaultValue=""
                      onChange={(e) =>
                        e.target.value &&
                        editClassification.mutate({
                          field,
                          description,
                          value: e.target.value,
                        })
                      }
                      disabled={editClassification.isPending}
                      className="max-w-[14rem] rounded border border-red-300 bg-white px-1.5 py-0.5 text-[11px] text-red-900"
                    >
                      <option value="" disabled>
                        Choose a category
                      </option>
                      {options.map((opt) => (
                        <option key={opt} value={opt}>
                          {opt}
                        </option>
                      ))}
                    </select>
                  </div>
                ))}
              </div>
            </div>
          );
        })}
      </div>
      {editClassification.error && (
        <p className="mt-1 text-xs text-red-600">{editClassification.error.message}</p>
      )}
    </div>
  );
}

function StageButton({
  label,
  activeLabel,
  isPending,
  disabled = false,
  disabledReason,
  onClick,
}: {
  label: string;
  activeLabel: string;
  isPending: boolean;
  disabled?: boolean;
  disabledReason?: string;
  onClick: () => void;
}) {
  const blocked = isPending || disabled;
  return (
    <button
      onClick={onClick}
      disabled={blocked}
      title={disabled && !isPending ? disabledReason : undefined}
      className={`inline-flex items-center gap-2 self-start px-5 py-2 rounded-xl bg-gradient-to-br from-[#14163d] to-[#15173e] text-white text-sm font-bold transition-opacity ${
        blocked ? "opacity-50 cursor-not-allowed" : "hover:opacity-90"
      }`}
    >
      {isPending ? (
        <>
          <span className="w-4 h-4 rounded-full border-2 border-white border-t-transparent animate-spin" />
          {activeLabel}
        </>
      ) : (
        <>
          {label}
          <ArrowRightIcon className="h-4 w-4" />
        </>
      )}
    </button>
  );
}
