import { useEffect, useMemo, useState } from "react";
import {
  Alert,
  Badge,
  Button,
  Group,
  NumberInput,
  Paper,
  SegmentedControl,
  Select,
  Stack,
  Stepper,
  Switch,
  Text,
  TextInput,
} from "@mantine/core";
import { PhasedFileDropzone, PhasedMultiFileDropzone } from "@/components/contracts/PhasedFileDropzone";
import { TransitAssigneePanel } from "@/components/contracts/TransitAssigneePanel";
import {
  TransitPermitMultiUpload,
  type TransitPermitUploadedRow,
} from "@/components/contracts/TransitPermitMultiUpload";
import {
  AlertTriangle,
  CheckCircle2,
  Clock,
  FileText,
  MessageSquareWarning,
  PackageCheck,
  Receipt,
  ShieldAlert,
  Ship,
  Truck,
  Upload,
} from "lucide-react";
import type { Freight } from "@edr/types";
import toast from "react-hot-toast";

import { SectionCard } from "@/components/bookings/detail/SectionCard";
import { ExportClearanceStepper } from "@/components/contracts/ExportClearanceStepper";
import { PhasedDocumentUploadField } from "@/components/contracts/PhasedDocumentUploadField";
import {
  DoCollectionDateFields,
  doDatesComplete,
  toIsoDate,
  useDoCollectionDates,
} from "@/components/contracts/DoCollectionDateFields";
import {
  findWorkflowFile,
  PhasedUploadedFileRow,
} from "@/components/contracts/PhasedUploadedFileRow";
import { contractsService } from "@/services/contracts.service";
import { bookingsService } from "@/services/bookings.service";
import { useAuth } from "@/auth/useAuth";
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";

type RoleMode = "ET" | "DJ" | "ALL";

export type ClearanceViewLike = Pick<
  Freight.ContractClearanceView,
  | "nextAction"
  | "dutyRequired"
  | "dutyAdvice"
  | "dutyDispute"
  | "transitAssignee"
  | "roHold"
  | "roHoldReason"
  | "milestones"
  | "bookingReady"
  | "preClearanceFinalized"
  | "exportClearanceFinalized"
  | "allApproved"
  | "t1"
  | "train"
  | "gatepassGranted"
  | "gatepassAt"
  | "t1Closed"
  | "t1ClosedAt"
  | "offloaded"
  | "finalInvoice"
  | "vesselDepartureDate"
  | "vesselArrivalDate"
  | "doCollectedDate"
  | "linkedBookingId"
  | "riskLevel"
  | "riskAssignedAt"
  | "riskHistory"
  | "secondDuty"
  | "importReleaseGranted"
> & { operationReady?: boolean };

export type MilestoneRow = NonNullable<ClearanceViewLike["milestones"]>[number];

export function isMilestoneDone(
  milestones: MilestoneRow[] | undefined,
  code: string,
): boolean {
  const m = milestones?.find((x) => x.milestoneCode === code);
  return m?.status === "COMPLETED" || m?.status === "SKIPPED";
}

export function isBookingMilestoneDone(
  milestones: MilestoneRow[] | undefined,
  code: string,
): boolean {
  const m = milestones?.find((x) => x.milestoneCode === code);
  return m?.status === "COMPLETED" || m?.status === "SKIPPED";
}

function computeImportActiveStep(
  clearance: ClearanceViewLike,
  bookingCreated: boolean,
  bookingMilestones: MilestoneRow[],
  t1Uploaded: boolean,
  freightPaid: boolean,
): number {
  if (!isMilestoneDone(clearance.milestones, "DOCUMENTS_APPROVED")) return 0;
  if (!isMilestoneDone(clearance.milestones, "DECLARED")) return 1;
  if (
    clearance.dutyRequired === null ||
    clearance.dutyRequired === undefined ||
    (clearance.dutyRequired && !isMilestoneDone(clearance.milestones, "DUTY_TAXES_ADVISED"))
  ) {
    return 2;
  }
  if (
    clearance.dutyRequired &&
    !isMilestoneDone(clearance.milestones, "DUTY_TAX_PAID")
  ) {
    return 3;
  }
  if (!isMilestoneDone(clearance.milestones, "TRANSIT_PERMIT_UPLOADED")) return 4;
  if (!clearance.preClearanceFinalized) return 5;
  if (!isMilestoneDone(clearance.milestones, "DO_COLLECTED")) return 6;
  if (!bookingCreated) return 7;
  // The customer pays the train/freight charges on the booking. Until that
  // settles the gate pass is not granted for this booking, so the flow stops here.
  if (!freightPaid) return 8;
  if (!clearance.gatepassGranted) return 9;
  if (!t1Uploaded && !clearance.t1?.closed) return 10;
  if (!clearance.t1?.closed) return 11;
  // Risk is "assigned" when the booking milestone says so OR the clearance view
  // already carries a riskLevel. The ET page derives its bookingMilestones from a
  // separately-fetched booking id that can lag or mismatch the booking carrying
  // the milestone — `clearance.riskLevel` is server truth and matches the badge.
  const riskAssigned =
    Boolean(clearance.riskLevel) ||
    isBookingMilestoneDone(bookingMilestones, "RISK_ASSIGNED");
  if (!riskAssigned) return 12;
  // Additional duty round is optional — resolved once skipped or paid.
  const secondDutyResolved =
    clearance.secondDuty?.skipped ||
    clearance.secondDuty?.paid ||
    isBookingMilestoneDone(bookingMilestones, "SECOND_DUTY_PAID");
  if (!secondDutyResolved) return 13;
  if (!clearance.importReleaseGranted) return 14;
  return 15;
}

function t1FilesFromWorkflow(
  workflowFiles: Freight.ClearanceWorkflowFile[],
): Array<{ code: string; label: string; file: { id: string; name: string } }> {
  return workflowFiles
    .filter(
      (f) => f.code.toLowerCase().startsWith("t1_transport_document") && f.file,
    )
    .map((f) => ({
      code: f.code,
      label: f.label,
      file: f.file!,
    }));
}

function declarationFilesFromWorkflow(
  workflowFiles: Freight.ClearanceWorkflowFile[],
): Array<{ code: string; label: string; file: { id: string; name: string } }> {
  return workflowFiles
    .filter(
      (f) => f.category === "declaration" && f.code !== "import_release" && f.file,
    )
    .map((f) => ({
      code: f.code,
      label: f.label,
      file: f.file!,
    }));
}

function importTransitFilesFromWorkflow(
  workflowFiles: Freight.ClearanceWorkflowFile[],
): Array<{ code: string; label: string; file: { id: string; name: string } }> {
  return workflowFiles
    .filter(
      (f) =>
        f.category === "transit" &&
        f.code !== "export_transport_document" &&
        f.file,
    )
    .map((f) => ({
      code: f.code,
      label: f.label,
      file: f.file!,
    }));
}

export function PhasedClearanceActionPanel({
  contractId,
  bookingId,
  clearance,
  tradeDirection,
  workflowFiles = [],
  roleMode = "ALL",
  onChanged,
  bookingCreateHref,
  onViewFile,
  onDownloadFile,
  useUploadModals = false,
  onUploadDoRequest,
  onUploadRoRequest,
  bookingCreated = false,
  bookingMilestones = [],
}: {
  contractId?: string;
  bookingId?: string;
  clearance: ClearanceViewLike;
  tradeDirection: string;
  workflowFiles?: Freight.ClearanceWorkflowFile[];
  roleMode?: RoleMode;
  onChanged?: () => void;
  bookingCreateHref?: string;
  onViewFile?: (file: { name: string; url: string }) => void;
  onDownloadFile?: (file: { id: string; name: string }) => void;
  /** GL Djibouti: open modal uploads instead of inline forms for DO/RO. */
  useUploadModals?: boolean;
  onUploadDoRequest?: () => void;
  onUploadRoRequest?: () => void;
  /** Hide create-booking CTA once GL has already created the shipment booking. */
  bookingCreated?: boolean;
  /** Post-booking milestones (export transit permit after wagon allocation). */
  bookingMilestones?: MilestoneRow[];
}) {
  const entityId = bookingId ?? contractId ?? "";
  const isBooking = Boolean(bookingId);
  const { user } = useAuth();
  const canEt = hasPermission(user, FREIGHT_PERMS.contracts.clearanceEtActions);
  const canDj = hasPermission(user, FREIGHT_PERMS.contracts.clearanceDjActions);
  const showEt = roleMode === "ET" || roleMode === "ALL";
  const showDj = roleMode === "DJ" || roleMode === "ALL";
  const isImport = tradeDirection === "IMPORT";
  // The server only builds the t1 block once a booking is linked — use it as the
  // booking-created signal on pages that don't pass bookingCreated (GL DJ detail).
  const effectiveBookingCreated = bookingCreated || Boolean(clearance.t1);
  // The booking that carries the post-booking steps (gate pass, risk, duty, release).
  const actionBookingId =
    clearance.t1?.bookingId ?? clearance.linkedBookingId ?? bookingId ?? null;
  const t1Uploaded = t1FilesFromWorkflow(workflowFiles).length > 0;
  // Risk is assigned when the clearance view carries a riskLevel (server truth,
  // drives the badge) OR the fetched booking milestone confirms it. Kept in sync
  // with computeImportActiveStep so the stepper never freezes on a page whose
  // bookingMilestones lag/mismatch the booking that holds the milestone.
  const riskAssigned =
    Boolean(clearance.riskLevel) ||
    isBookingMilestoneDone(bookingMilestones, "RISK_ASSIGNED");
  // Freight (train + service) charges settled on the booking. The gate pass is
  // only granted to a booking that has paid, so a granted gate pass is server
  // proof of payment — it keeps the stepper moving on a page whose
  // bookingMilestones have not loaded yet or point at a different booking.
  const freightPaid =
    isBookingMilestoneDone(bookingMilestones, "FREIGHT_PAYMENT_SETTLED") ||
    Boolean(clearance.gatepassGranted);
  const activeStep = useMemo(
    () =>
      isImport
        ? computeImportActiveStep(
            clearance,
            effectiveBookingCreated,
            bookingMilestones,
            t1Uploaded,
            freightPaid,
          )
        : 0,
    [
      clearance,
      isImport,
      effectiveBookingCreated,
      bookingMilestones,
      t1Uploaded,
      freightPaid,
    ],
  );

  if (isImport) {
    return (
      <Stack gap="md">
        {clearance.roHold && clearance.roHoldReason ? (
          <Alert color="orange" icon={<AlertTriangle size={16} />} title="Release Order on hold">
            {clearance.roHoldReason}
          </Alert>
        ) : null}

        {clearance.nextAction ? (
          <Alert color="blue" variant="light" title="Next step">
            <Text size="sm">
              <strong>{clearance.nextAction.actor.replace("_", " ")}</strong> —{" "}
              {clearance.nextAction.action}
            </Text>
          </Alert>
        ) : null}

        <Paper withBorder radius="md" p="md">
          <Text fw={600} size="sm" mb="md">
            Import pre-booking clearance
          </Text>
          <Stepper
            active={activeStep}
            orientation="vertical"
            size="sm"
            iconSize={26}
            allowNextStepsSelect={false}
          >
            <Stepper.Step
              label="Customer documents"
              description="Review and approve in the panel on the left"
              icon={
                isMilestoneDone(clearance.milestones, "DOCUMENTS_APPROVED") ? (
                  <CheckCircle2 size={14} />
                ) : undefined
              }
            >
              <StepStatus
                done={isMilestoneDone(clearance.milestones, "DOCUMENTS_APPROVED")}
                pendingLabel="Approve every required customer document."
                doneLabel="All required documents approved."
              />
            </Stepper.Step>

            <Stepper.Step
              label="Customs declaration"
              description="Upload declaration documents"
              icon={
                isMilestoneDone(clearance.milestones, "DECLARED") ? (
                  <CheckCircle2 size={14} />
                ) : (
                  <FileText size={14} />
                )
              }
            >
              {/* Djibouti must name the transit officer first — the declaration
                  is filed against whoever handles the shipment there, and the
                  API refuses the upload until the name is in. */}
              {showEt &&
              canEt &&
              !isBooking &&
              !clearance.transitAssignee?.name &&
              !isMilestoneDone(clearance.milestones, "DECLARED") ? (
                <TransitAssigneePanel
                  contractId={entityId}
                  transitAssignee={clearance.transitAssignee}
                  side="ET"
                  onChanged={onChanged}
                />
              ) : showEt &&
              canEt &&
              !clearance.bookingReady &&
              (activeStep >= 1 ||
                isMilestoneDone(clearance.milestones, "DECLARED")) ? (
                <DeclarationStep
                  entityId={entityId}
                  isBooking={isBooking}
                  replaceMode={isMilestoneDone(clearance.milestones, "DECLARED")}
                  workflowFiles={workflowFiles}
                  onChanged={onChanged}
                  onViewFile={onViewFile}
                  onDownloadFile={onDownloadFile}
                />
              ) : (
                <Stack gap="sm">
                  {declarationFilesFromWorkflow(workflowFiles).map((row) => (
                    <PhasedUploadedFileRow
                      key={row.code}
                      label={row.label}
                      file={row.file}
                      onView={onViewFile}
                      onDownload={onDownloadFile}
                      compact
                    />
                  ))}
                  <StepStatus
                    done={isMilestoneDone(clearance.milestones, "DECLARED")}
                    pendingLabel="Waiting for GL Ethiopia to upload customs declaration documents."
                    doneLabel="Declaration uploaded."
                  />
                </Stack>
              )}
            </Stepper.Step>

            <Stepper.Step
              label="Duty & tax"
              description="Advise amount and attach notice"
              icon={<Receipt size={14} />}
            >
              {showEt && canEt && activeStep === 2 ? (
                <DutyStep
                  entityId={entityId}
                  isBooking={isBooking}
                  clearance={clearance}
                  workflowFiles={workflowFiles}
                  onChanged={onChanged}
                  onViewFile={onViewFile}
                  onDownloadFile={onDownloadFile}
                />
              ) : (
                <StepStatus
                  done={
                    clearance.dutyRequired === false ||
                    isMilestoneDone(clearance.milestones, "DUTY_TAXES_ADVISED")
                  }
                  pendingLabel="Waiting for GL Ethiopia to advise duty/tax."
                  doneLabel={
                    clearance.dutyRequired === false
                      ? "Duty/tax not required."
                      : "Duty/tax advised to customer."
                  }
                />
              )}
            </Stepper.Step>

            <Stepper.Step
              label="Customer payment"
              description="Customer uploads payment slip"
              icon={<Clock size={14} />}
            >
              {findWorkflowFile(workflowFiles, "duty_tax_receipt") ? (
                <PhasedUploadedFileRow
                  label="Duty / Tax Payment Slip"
                  file={findWorkflowFile(workflowFiles, "duty_tax_receipt")!}
                  onView={onViewFile}
                  onDownload={onDownloadFile}
                />
              ) : (
                <StepStatus
                  done={
                    clearance.dutyRequired === false ||
                    isMilestoneDone(clearance.milestones, "DUTY_TAX_PAID")
                  }
                  pendingLabel={
                    clearance.dutyRequired === false
                      ? "Skipped — duty not required."
                      : "Waiting for the customer to upload the duty/tax payment slip in the portal."
                  }
                  doneLabel="Customer payment slip received."
                />
              )}
            </Stepper.Step>

            <Stepper.Step
              label="Transit Permit"
              description="Upload transit permit documents"
              icon={<Upload size={14} />}
            >
              {showEt &&
              canEt &&
              !bookingCreated &&
              (activeStep >= 4 ||
                isMilestoneDone(clearance.milestones, "TRANSIT_PERMIT_UPLOADED")) ? (
                <TransitPermitStep
                  entityId={entityId}
                  isBooking={isBooking}
                  workflowFiles={workflowFiles}
                  replaceMode={isMilestoneDone(
                    clearance.milestones,
                    "TRANSIT_PERMIT_UPLOADED",
                  )}
                  onChanged={onChanged}
                  onViewFile={onViewFile}
                  onDownloadFile={onDownloadFile}
                />
              ) : importTransitFilesFromWorkflow(workflowFiles).length > 0 ? (
                <Stack gap="sm">
                  <Text size="sm" fw={700}>
                    Transit Permit
                  </Text>
                  {importTransitFilesFromWorkflow(workflowFiles).map((row) => (
                    <PhasedUploadedFileRow
                      key={row.code}
                      label={row.label}
                      file={row.file}
                      onView={onViewFile}
                      onDownload={onDownloadFile}
                      compact
                    />
                  ))}
                </Stack>
              ) : (
                <StepStatus
                  done={isMilestoneDone(clearance.milestones, "TRANSIT_PERMIT_UPLOADED")}
                  pendingLabel="Waiting for GL Ethiopia to upload the transit permit."
                  doneLabel="Transit permit uploaded."
                />
              )}
            </Stepper.Step>

            <Stepper.Step
              label="Finalize pre-clearance"
              description="Hand off to GL Djibouti"
              icon={<PackageCheck size={14} />}
            >
              {showEt && canEt && activeStep === 5 ? (
                <FinalizePreClearanceStep
                  entityId={entityId}
                  isBooking={isBooking}
                  onChanged={onChanged}
                />
              ) : (
                <StepStatus
                  done={Boolean(clearance.preClearanceFinalized)}
                  pendingLabel="Waiting for GL Ethiopia to finalize pre-clearance."
                  doneLabel="Pre-clearance finalized — Djibouti may upload the DO."
                />
              )}
            </Stepper.Step>

            <Stepper.Step
              label="Delivery Order"
              description="GL Djibouti uploads DO"
              icon={<Ship size={14} />}
            >
              {showDj && canDj && !useUploadModals ? (
                <DeliveryOrderStep
                  entityId={entityId}
                  isBooking={isBooking}
                  workflowFiles={workflowFiles}
                  replaceMode={isMilestoneDone(clearance.milestones, "DO_COLLECTED")}
                  vesselArrivalDate={clearance.vesselArrivalDate}
                  doCollectedDate={clearance.doCollectedDate}
                  onChanged={onChanged}
                  onViewFile={onViewFile}
                  onDownloadFile={onDownloadFile}
                />
              ) : (
                <Stack gap="sm">
                  {findWorkflowFile(workflowFiles, "delivery_order") ? (
                    <PhasedUploadedFileRow
                      label="Delivery Order"
                      file={findWorkflowFile(workflowFiles, "delivery_order")!}
                      onView={onViewFile}
                      onDownload={onDownloadFile}
                    />
                  ) : null}
                  <StepStatus
                    done={isMilestoneDone(clearance.milestones, "DO_COLLECTED")}
                    pendingLabel="Waiting for GL Djibouti to upload the Delivery Order (can be uploaded at any time)."
                    doneLabel="Delivery Order collected."
                  />
                  {useUploadModals && showDj && canDj && onUploadDoRequest ? (
                    <Button
                      color="edr-green"
                      leftSection={<Upload size={16} />}
                      onClick={onUploadDoRequest}
                    >
                      {findWorkflowFile(workflowFiles, "delivery_order")
                        ? "Replace DO"
                        : "Upload DO"}
                    </Button>
                  ) : null}
                </Stack>
              )}
            </Stepper.Step>

            <Stepper.Step
              label="Create booking"
              description="GL Ethiopia books for the customer"
              icon={<Ship size={14} />}
            >
              {(clearance.bookingReady || clearance.operationReady) &&
              bookingCreateHref &&
              !bookingCreated &&
              showEt &&
              canEt ? (
                <SectionCard icon={Ship} title="Create booking" accent="edr-green">
                  <Text size="sm" c="dimmed" mb="sm">
                    Pre-booking clearance is complete. Create the shipment booking for the
                    customer.
                  </Text>
                  <Button component="a" href={bookingCreateHref} color="edr-green">
                    Create shipment booking
                  </Button>
                </SectionCard>
              ) : (
                <StepStatus
                  done={Boolean(
                    effectiveBookingCreated ||
                      clearance.bookingReady ||
                      clearance.operationReady,
                  )}
                  pendingLabel="Complete the Delivery Order step first."
                  doneLabel={
                    effectiveBookingCreated
                      ? "Shipment booking created."
                      : "Ready — create the shipment booking."
                  }
                />
              )}
            </Stepper.Step>

            <Stepper.Step
              label="Freight payment"
              description="Customer pays the train and service charges"
              icon={freightPaid ? <CheckCircle2 size={14} /> : <Receipt size={14} />}
            >
              <StepStatus
                done={freightPaid}
                pendingLabel="Waiting for the customer to pay the train and service charges. The gate pass is not granted until this settles."
                doneLabel="Train and service charges settled."
              />
            </Stepper.Step>

            <Stepper.Step
              label="Gate pass"
              description="Secured on the train schedule after payment and wagon allocation"
              icon={
                clearance.gatepassGranted ? <CheckCircle2 size={14} /> : <Truck size={14} />
              }
            >
              <ImportGatepassStep clearance={clearance} freightPaid={freightPaid} />
            </Stepper.Step>

            <Stepper.Step
              label="T1 transport documents"
              description="GL Djibouti uploads after the gate pass is secured"
              icon={
                t1Uploaded || clearance.t1?.closed ? (
                  <CheckCircle2 size={14} />
                ) : (
                  <Truck size={14} />
                )
              }
            >
              <ImportT1UploadStep
                t1={clearance.t1 ?? null}
                gatepassGranted={Boolean(clearance.gatepassGranted)}
                workflowFiles={workflowFiles}
                canDjAct={showDj && canDj}
                onChanged={onChanged}
                onViewFile={onViewFile}
                onDownloadFile={onDownloadFile}
              />
            </Stepper.Step>

            <Stepper.Step
              label="Close T1"
              description="GL Ethiopia closes once the train arrives"
              icon={
                clearance.t1?.closed ? (
                  <CheckCircle2 size={14} />
                ) : (
                  <PackageCheck size={14} />
                )
              }
            >
              <ImportT1CloseStep
                t1={clearance.t1 ?? null}
                t1Uploaded={t1Uploaded}
                canEtAct={showEt && canEt}
                onChanged={onChanged}
              />
            </Stepper.Step>

            <Stepper.Step
              label="Customs risk"
              description="GL Ethiopia assigns Green / Yellow / Red"
              icon={
                riskAssigned ? (
                  <CheckCircle2 size={14} />
                ) : (
                  <ShieldAlert size={14} />
                )
              }
            >
              <RiskStep
                bookingId={actionBookingId}
                clearance={clearance}
                canAct={showEt && canEt}
                done={riskAssigned}
                onChanged={onChanged}
              />
            </Stepper.Step>

            <Stepper.Step
              label="Additional duty & tax"
              description="GL Ethiopia advises if more duty applies"
              icon={<Receipt size={14} />}
            >
              <SecondDutyStep
                bookingId={actionBookingId}
                clearance={clearance}
                canAct={showEt && canEt}
                riskAssigned={riskAssigned}
                onChanged={onChanged}
                onViewFile={onViewFile}
                onDownloadFile={onDownloadFile}
              />
            </Stepper.Step>

            <Stepper.Step
              label="Import release"
              description="GL Ethiopia uploads the release document"
              icon={
                clearance.importReleaseGranted ? (
                  <CheckCircle2 size={14} />
                ) : (
                  <FileText size={14} />
                )
              }
            >
              <ImportReleaseStep
                bookingId={actionBookingId}
                clearance={clearance}
                canAct={showEt && canEt}
                workflowFiles={workflowFiles}
                onChanged={onChanged}
                onViewFile={onViewFile}
                onDownloadFile={onDownloadFile}
              />
            </Stepper.Step>
          </Stepper>
        </Paper>
      </Stack>
    );
  }

  return (
    <ExportClearanceStepper
      contractId={contractId}
      bookingId={bookingId}
      clearance={clearance}
      workflowFiles={workflowFiles}
      showEt={showEt}
      canEt={canEt}
      showDj={showDj}
      canDj={canDj}
      onChanged={onChanged}
      bookingCreateHref={bookingCreateHref}
      onViewFile={onViewFile}
      onDownloadFile={onDownloadFile}
      useUploadModals={useUploadModals}
      onUploadRoRequest={onUploadRoRequest}
      bookingCreated={bookingCreated}
      bookingMilestones={bookingMilestones}
    />
  );
}

/**
 * GL Djibouti uploads the T1 transport documents (multi-file) once the gate
 * pass is secured on the train schedule; locked on departure or T1 close.
 */
function ImportT1UploadStep({
  t1,
  gatepassGranted,
  workflowFiles = [],
  canDjAct,
  onChanged,
  onViewFile,
  onDownloadFile,
}: {
  t1: Freight.ClearanceT1State | null;
  gatepassGranted: boolean;
  workflowFiles?: Freight.ClearanceWorkflowFile[];
  canDjAct: boolean;
  onChanged?: () => void;
  onViewFile?: (file: { name: string; url: string }) => void;
  onDownloadFile?: (file: { id: string; name: string }) => void;
}) {
  const [files, setFiles] = useState<File[]>([]);
  const [uploading, setUploading] = useState(false);

  const uploaded = t1FilesFromWorkflow(workflowFiles);
  const replaceMode = uploaded.length > 0;

  if (!t1) {
    return (
      <StepStatus
        done={false}
        pendingLabel="Available once the shipment booking is created."
        doneLabel=""
      />
    );
  }

  // Departure no longer locks T1 docs — GL DJ may replace them until GL Ethiopia
  // closes/accepts the T1.
  const canUpload = canDjAct && t1.wagonAllocated && gatepassGranted && !t1.closed;

  return (
    <Stack gap="sm">
      {uploaded.length > 0 ? (
        <Stack gap={8}>
          <Text size="xs" fw={700} c="dimmed" tt="uppercase">
            T1 document{uploaded.length > 1 ? "s" : ""}
          </Text>
          {uploaded.map((row) => (
            <PhasedUploadedFileRow
              key={row.code}
              label={row.label}
              file={row.file}
              onView={onViewFile}
              onDownload={onDownloadFile}
              compact
            />
          ))}
        </Stack>
      ) : null}

      {t1.closed ? (
        <StepStatus
          done
          pendingLabel=""
          doneLabel="T1 documents are final — closed by GL Ethiopia."
        />
      ) : !t1.wagonAllocated ? (
        <StepStatus
          done={false}
          pendingLabel="Waiting for operations to allocate wagons."
          doneLabel=""
        />
      ) : !gatepassGranted ? (
        <StepStatus
          done={false}
          pendingLabel="Waiting for the gate pass to be secured on the train schedule."
          doneLabel=""
        />
      ) : uploaded.length === 0 && !canUpload ? (
        <StepStatus
          done={false}
          pendingLabel="Waiting for GL Djibouti to upload T1 transport documents."
          doneLabel=""
        />
      ) : uploaded.length > 0 && !canUpload ? (
        <StepStatus done pendingLabel="" doneLabel="T1 documents uploaded." />
      ) : null}

      {canUpload ? (
        <>
          <Paper withBorder radius="md" p="md" bg="var(--mantine-color-gray-0)">
            <PhasedMultiFileDropzone
              label="T1 transport documents"
              description={
                replaceMode
                  ? "Replace T1 files — upload one or more documents (any file type)."
                  : "Upload one or more T1 transport documents (any file type)."
              }
              accept="*/*"
              value={files}
              onChange={setFiles}
              replaceMode={replaceMode}
              disabled={uploading}
            />
          </Paper>
          <Button
            color="edr-green"
            loading={uploading}
            disabled={files.length === 0}
            leftSection={<Upload size={16} />}
            fullWidth
            onClick={async () => {
              setUploading(true);
              try {
                const payload = Object.fromEntries(
                  files.map((file, index) => [`t1_transport_document_${index}`, file]),
                ) as Record<string, File>;
                await contractsService.uploadT1Documents(t1.bookingId, payload);
                setFiles([]);
                toast.success(replaceMode ? "T1 documents updated" : "T1 documents uploaded");
                onChanged?.();
              } catch (e) {
                toast.error(e instanceof Error ? e.message : "Upload failed");
              } finally {
                setUploading(false);
              }
            }}
          >
            {replaceMode ? "Replace T1 documents" : "Upload T1 documents"}
          </Button>
        </>
      ) : null}
    </Stack>
  );
}

/**
 * GL Ethiopia closes (accepts) the T1 set with one click once the train has
 * arrived. Separate step from the GL Djibouti upload.
 */
function ImportT1CloseStep({
  t1,
  t1Uploaded,
  canEtAct,
  onChanged,
}: {
  t1: Freight.ClearanceT1State | null;
  t1Uploaded: boolean;
  canEtAct: boolean;
  onChanged?: () => void;
}) {
  const [closing, setClosing] = useState(false);

  if (!t1) {
    return (
      <StepStatus
        done={false}
        pendingLabel="Available once the shipment booking is created."
        doneLabel=""
      />
    );
  }

  if (t1.closed) {
    return (
      <StepStatus
        done
        pendingLabel=""
        doneLabel={`T1 accepted and closed by GL Ethiopia${
          t1.closedAt ? ` · ${new Date(t1.closedAt).toLocaleString()}` : ""
        }`}
      />
    );
  }

  if (!t1Uploaded) {
    return (
      <StepStatus
        done={false}
        pendingLabel="Waiting for GL Djibouti to upload T1 transport documents."
        doneLabel=""
      />
    );
  }

  if (!t1.trainArrivedAt) {
    return (
      <Alert color="blue" variant="light" icon={<Clock size={16} />}>
        {t1.trainDepartedAt
          ? "Train en route — T1 can be closed once it arrives in Ethiopia."
          : "T1 can be closed once the train arrives in Ethiopia."}
      </Alert>
    );
  }

  if (!canEtAct) {
    return (
      <StepStatus
        done={false}
        pendingLabel="Train arrived — waiting for GL Ethiopia to close the T1."
        doneLabel=""
      />
    );
  }

  return (
    <Stack gap="xs">
      <Text size="sm" c="dimmed">
        The train has arrived — review the T1 documents and close (accept) them.
      </Text>
      <Button
        color="edr-green"
        loading={closing}
        leftSection={<PackageCheck size={16} />}
        onClick={async () => {
          setClosing(true);
          try {
            await contractsService.closeT1(t1.bookingId);
            toast.success("T1 closed");
            onChanged?.();
          } catch (e) {
            toast.error(e instanceof Error ? e.message : "Failed");
          } finally {
            setClosing(false);
          }
        }}
      >
        Accept &amp; close T1
      </Button>
    </Stack>
  );
}

/**
 * Gate pass status, read-only. Secured on the train schedule's "Save as
 * Secured" action (train-scheduling-v2) — clearance no longer grants it directly.
 * The train may be secured while this booking still owes freight charges; the
 * booking only picks the gate pass up once its payment settles.
 */
function ImportGatepassStep({
  clearance,
  freightPaid,
}: {
  clearance: ClearanceViewLike;
  freightPaid: boolean;
}) {
  const scheduleId = clearance.train?.scheduleId ?? null;

  if (clearance.gatepassGranted) {
    return (
      <StepStatus
        done
        pendingLabel=""
        doneLabel={`Gate pass secured${
          clearance.gatepassAt ? ` · ${new Date(clearance.gatepassAt).toLocaleString()}` : ""
        }`}
      />
    );
  }

  if (!freightPaid) {
    return (
      <StepStatus
        done={false}
        pendingLabel="Blocked — the customer must pay the train and service charges before the gate pass is granted for this shipment."
        doneLabel=""
      />
    );
  }

  const wagonAllocated = Boolean(clearance.train?.wagonAllocated);

  return (
    <Stack gap="sm">
      <StepStatus
        done={false}
        pendingLabel={
          wagonAllocated
            ? "Wagons allocated — secure the gate pass on the train schedule."
            : "Available once wagons are allocated."
        }
        doneLabel=""
      />
      {scheduleId ? (
        <Button
          component="a"
          href={`/dashboard/operations/train-scheduling-v2/${scheduleId}`}
          variant="light"
          color="edr-green"
          leftSection={<Truck size={16} />}
        >
          Secure gate pass on train schedule
        </Button>
      ) : null}
    </Stack>
  );
}

const RISK_LEVEL_COLOR: Record<string, string> = {
  GREEN: "green",
  YELLOW: "yellow",
  RED: "red",
};

/** GL ET assigns the customs examination risk (visible to the customer). */
function RiskStep({
  bookingId,
  clearance,
  canAct,
  done,
  onChanged,
}: {
  bookingId: string | null;
  clearance: ClearanceViewLike;
  canAct: boolean;
  done: boolean;
  onChanged?: () => void;
}) {
  const assigned = done || Boolean(clearance.riskLevel);
  // Duty is advised off the risk level, so once that is done the decision is
  // final. Until then a mis-assigned level must stay correctable — the server
  // overwrites the milestone metadata on reassignment. Mirrors AssignRiskCard.
  const locked = isMilestoneDone(clearance.milestones, "DUTY_TAXES_ADVISED");

  const [level, setLevel] = useState<string>(clearance.riskLevel ?? "GREEN");
  const [loading, setLoading] = useState(false);

  // The clearance view loads (and refetches after a reassignment) after first
  // render, so mirror the persisted level onto the control whenever it changes —
  // otherwise reopening the step offers GREEN whatever is actually assigned.
  useEffect(() => {
    if (clearance.riskLevel) setLevel(clearance.riskLevel);
  }, [clearance.riskLevel]);

  // Only the decisions before the current one — the badge above already states
  // the level in force, so repeating it as a trail entry reads as a duplicate.
  const priorDecisions = (clearance.riskHistory ?? []).slice(0, -1);

  const assignedSummary = assigned ? (
    <Stack gap={6}>
      <Group gap="sm">
        <Badge
          color={RISK_LEVEL_COLOR[clearance.riskLevel ?? ""] ?? "gray"}
          variant="filled"
          radius="sm"
        >
          {clearance.riskLevel ?? "Assigned"}
        </Badge>
        <Text size="sm" c="dimmed">
          Customs risk assigned
          {clearance.riskAssignedAt
            ? ` · ${new Date(clearance.riskAssignedAt).toLocaleString()}`
            : ""}
          . The customer can see this level.
        </Text>
      </Group>
      {priorDecisions.length > 0 ? (
        <Stack gap={2} pl="xs">
          <Text size="xs" c="dimmed" fw={600}>
            Previously
          </Text>
          {priorDecisions.map((entry, index) => (
            <Text key={`${entry.assignedAt}-${index}`} size="xs" c="dimmed">
              {entry.level}
              {" · "}
              {new Date(entry.assignedAt).toLocaleString()}
              {entry.assignedBy ? ` · ${entry.assignedBy}` : ""}
              {entry.note ? ` · ${entry.note}` : ""}
            </Text>
          ))}
        </Stack>
      ) : null}
    </Stack>
  ) : null;

  // Assigned and final: the badge is all that is left to show.
  if (assigned && (locked || !canAct || !bookingId)) {
    return assignedSummary;
  }

  // Customs cannot rate cargo still under transit — the server rejects the
  // assignment until the T1 is closed, so do not offer the control yet. Skipped
  // once a level exists: risk cannot have been assigned without a closed T1, so
  // a still-open T1 here is stale data and must not hide the assigned badge.
  if (!assigned && !clearance.t1?.closed) {
    return (
      <StepStatus
        done={false}
        pendingLabel="Available once the T1 is closed."
        doneLabel=""
      />
    );
  }

  if (!canAct || !bookingId) {
    return (
      <StepStatus
        done={false}
        pendingLabel="Waiting for GL Ethiopia to assign the customs risk level."
        doneLabel=""
      />
    );
  }

  return (
    <Stack gap="sm">
      {assignedSummary}
      <SegmentedControl
        fullWidth
        value={level}
        onChange={setLevel}
        data={[
          { label: "Green", value: "GREEN" },
          { label: "Yellow", value: "YELLOW" },
          { label: "Red", value: "RED" },
        ]}
      />
      <Group justify="space-between">
        <Text size="xs" c="dimmed">
          {assigned
            ? "Correctable until duty is advised. The customer sees the assigned risk level."
            : "The customer sees the assigned risk level."}
        </Text>
        <Button
          size="compact-sm"
          color="edr-green"
          loading={loading}
          disabled={assigned && level === clearance.riskLevel}
          onClick={async () => {
            setLoading(true);
            try {
              await contractsService.assignRisk(bookingId, {
                riskLevel: level as Freight.CustomsRiskLevel,
              });
              toast.success(
                assigned ? "Customs risk reassigned" : "Customs risk assigned",
              );
              onChanged?.();
            } catch (e) {
              toast.error(e instanceof Error ? e.message : "Failed");
            } finally {
              setLoading(false);
            }
          }}
        >
          {assigned ? "Reassign risk" : "Assign risk"}
        </Button>
      </Group>
    </Stack>
  );
}

/** Optional post-arrival additional duty/tax round (GL ET advises; customer pays slip). */
function SecondDutyStep({
  bookingId,
  clearance,
  canAct,
  riskAssigned,
  onChanged,
  onViewFile,
  onDownloadFile,
}: {
  bookingId: string | null;
  clearance: ClearanceViewLike;
  canAct: boolean;
  riskAssigned: boolean;
  onChanged?: () => void;
  onViewFile?: (file: { name: string; url: string }) => void;
  onDownloadFile?: (file: { id: string; name: string }) => void;
}) {
  const [dutyRequired, setDutyRequired] = useState(true);
  const [amount, setAmount] = useState<number | string>("");
  const [currency, setCurrency] = useState("ETB");
  const [serial, setSerial] = useState("");
  const [attachment, setAttachment] = useState<File | null>(null);
  const [loading, setLoading] = useState(false);

  const duty = clearance.secondDuty ?? null;

  if (duty?.skipped) {
    return (
      <StepStatus done pendingLabel="" doneLabel="No additional duty or tax applies." />
    );
  }

  if (duty?.advised) {
    return (
      <Stack gap="sm">
        <Text size="sm">
          Additional duty advised:{" "}
          <strong>
            {duty.amount?.toLocaleString()} {duty.currency}
          </strong>
          {duty.declarationSerial ? ` · ${duty.declarationSerial}` : ""}
        </Text>
        {duty.noticeFile ? (
          <PhasedUploadedFileRow
            label="Additional Duty / Tax Notice"
            file={duty.noticeFile}
            onView={onViewFile}
            onDownload={onDownloadFile}
            compact
          />
        ) : null}
        {duty.slipFile ? (
          <PhasedUploadedFileRow
            label="Customer payment slip"
            file={duty.slipFile}
            onView={onViewFile}
            onDownload={onDownloadFile}
            compact
          />
        ) : null}
        <StepStatus
          done={duty.paid}
          pendingLabel="Waiting for the customer to pay and attach the slip in the portal."
          doneLabel="Additional duty paid — slip received."
        />
      </Stack>
    );
  }

  if (!canAct || !bookingId) {
    return (
      <StepStatus
        done={false}
        pendingLabel="GL Ethiopia decides whether additional duty/tax applies."
        doneLabel=""
      />
    );
  }

  return (
    <Stack gap="md">
      {!riskAssigned ? (
        <Alert color="blue" variant="light" icon={<Clock size={16} />}>
          Usually decided after the customs risk is assigned.
        </Alert>
      ) : null}
      <Paper withBorder radius="md" p="md" bg="var(--mantine-color-gray-0)">
        <Stack gap="md">
          <Switch
            label="Additional duty/tax applies"
            description="Turn off if no further duty or tax is due after arrival."
            checked={dutyRequired}
            onChange={(e) => setDutyRequired(e.currentTarget.checked)}
          />
          {dutyRequired ? (
            <>
              <Group grow align="flex-start">
                <NumberInput
                  label="Amount"
                  placeholder="0.00"
                  value={amount}
                  onChange={setAmount}
                  min={0}
                  size="sm"
                  thousandSeparator=","
                />
                <Select
                  label="Currency"
                  data={["ETB", "USD"]}
                  value={currency}
                  onChange={(v) => setCurrency(v ?? "ETB")}
                  size="sm"
                />
              </Group>
              <TextInput
                label="Declaration / payment code"
                placeholder="Customs payment reference"
                value={serial}
                onChange={(e) => setSerial(e.currentTarget.value)}
                size="sm"
              />
              <PhasedFileDropzone
                label="Duty notice attachment"
                description="Any file type — shown to the customer in the portal."
                accept="*/*"
                value={attachment}
                onChange={setAttachment}
                onPreview={onViewFile}
              />
            </>
          ) : null}
        </Stack>
      </Paper>
      <Button
        color="edr-green"
        loading={loading}
        disabled={dutyRequired && (amount === "" || Number(amount) <= 0 || !attachment)}
        fullWidth
        onClick={async () => {
          setLoading(true);
          try {
            await contractsService.adviseSecondDuty(bookingId, {
              dutyRequired,
              amount: dutyRequired ? Number(amount) : undefined,
              currency,
              declarationSerial: serial || undefined,
              attachment: dutyRequired ? attachment : null,
            });
            toast.success(
              dutyRequired ? "Additional duty advised to customer" : "No additional duty recorded",
            );
            onChanged?.();
          } catch (e) {
            toast.error(e instanceof Error ? e.message : "Failed");
          } finally {
            setLoading(false);
          }
        }}
      >
        {dutyRequired ? "Send to customer" : "No additional duty"}
      </Button>
    </Stack>
  );
}

/** GL ET uploads the import release document (auto-completes IMPORT_RELEASE_GRANTED). */
function ImportReleaseStep({
  bookingId,
  clearance,
  canAct,
  workflowFiles = [],
  onChanged,
  onViewFile,
  onDownloadFile,
}: {
  bookingId: string | null;
  clearance: ClearanceViewLike;
  canAct: boolean;
  workflowFiles?: Freight.ClearanceWorkflowFile[];
  onChanged?: () => void;
  onViewFile?: (file: { name: string; url: string }) => void;
  onDownloadFile?: (file: { id: string; name: string }) => void;
}) {
  const [file, setFile] = useState<File | null>(null);
  const [loading, setLoading] = useState(false);
  const releaseFile = findWorkflowFile(workflowFiles, "import_release");

  return (
    <Stack gap="sm">
      {releaseFile ? (
        <PhasedUploadedFileRow
          label="Import Release"
          file={releaseFile}
          onView={onViewFile}
          onDownload={onDownloadFile}
          compact
        />
      ) : null}
      {clearance.importReleaseGranted ? (
        <StepStatus done pendingLabel="" doneLabel="Import release granted." />
      ) : canAct && bookingId ? (
        <>
          <PhasedFileDropzone
            label="Import release document"
            description="Any file type."
            accept="*/*"
            value={file}
            onChange={setFile}
            replaceMode={Boolean(releaseFile)}
            onPreview={onViewFile}
          />
          <Button
            color="edr-green"
            loading={loading}
            disabled={!file}
            leftSection={<Upload size={16} />}
            onClick={async () => {
              if (!file) return;
              setLoading(true);
              try {
                await contractsService.uploadGlDocuments(bookingId, {
                  import_release: file,
                });
                setFile(null);
                toast.success("Import release uploaded");
                onChanged?.();
              } catch (e) {
                toast.error(e instanceof Error ? e.message : "Upload failed");
              } finally {
                setLoading(false);
              }
            }}
          >
            {releaseFile ? "Replace import release" : "Upload import release"}
          </Button>
        </>
      ) : (
        <StepStatus
          done={false}
          pendingLabel="Waiting for GL Ethiopia to upload the import release document."
          doneLabel=""
        />
      )}
    </Stack>
  );
}

export function StepStatus({
  done,
  pendingLabel,
  doneLabel,
}: {
  done: boolean;
  pendingLabel: string;
  doneLabel: string;
}) {
  return (
    <Paper withBorder radius="md" p="sm" bg={done ? "var(--mantine-color-edr-green-0)" : undefined}>
      <Group gap="xs" wrap="nowrap">
        {done ? (
          <Badge color="edr-green" variant="light" leftSection={<CheckCircle2 size={12} />}>
            Done
          </Badge>
        ) : (
          <Badge color="gray" variant="light" leftSection={<Clock size={12} />}>
            Pending
          </Badge>
        )}
        <Text size="sm" c="dimmed">
          {done ? doneLabel : pendingLabel}
        </Text>
      </Group>
    </Paper>
  );
}

export function DeclarationStep({
  entityId,
  isBooking,
  onChanged,
  replaceMode = false,
  workflowFiles = [],
  onViewFile,
  onDownloadFile,
}: {
  entityId: string;
  isBooking: boolean;
  onChanged?: () => void;
  replaceMode?: boolean;
  workflowFiles?: Freight.ClearanceWorkflowFile[];
  onViewFile?: (file: { name: string; url: string }) => void;
  onDownloadFile?: (file: { id: string; name: string }) => void;
}) {
  const [files, setFiles] = useState<File[]>([]);
  const [loading, setLoading] = useState(false);

  const uploaded = declarationFilesFromWorkflow(workflowFiles);

  return (
    <Stack gap="md">
      {uploaded.length > 0 ? (
        <Stack gap={8}>
          <Text size="xs" fw={700} c="dimmed" tt="uppercase">
            Current file{uploaded.length > 1 ? "s" : ""}
          </Text>
          {uploaded.map((row) => (
            <PhasedUploadedFileRow
              key={row.code}
              label={row.label}
              file={row.file}
              onView={onViewFile}
              onDownload={onDownloadFile}
            />
          ))}
        </Stack>
      ) : null}

      <Paper withBorder radius="md" p="md" bg="var(--mantine-color-gray-0)">
        <PhasedMultiFileDropzone
          label="Customs declaration documents"
          description={
            replaceMode
              ? "Replace declaration files — upload one or more documents."
              : "Upload one or more customs declaration documents (PDF or image)."
          }
          value={files}
          onChange={setFiles}
          replaceMode={replaceMode}
          disabled={loading}
        />
      </Paper>

      <Button
        color="edr-green"
        loading={loading}
        disabled={files.length === 0}
        leftSection={<Upload size={16} />}
        fullWidth
        onClick={async () => {
          setLoading(true);
          try {
            const payload = Object.fromEntries(
              files.map((file, index) => [`declaration_${index}`, file]),
            ) as Record<string, File>;
            if (isBooking) {
              await bookingsService.uploadDeclaration(entityId, payload);
            } else {
              await contractsService.uploadDeclaration(entityId, payload);
            }
            setFiles([]);
            toast.success(replaceMode ? "Declaration updated" : "Declaration uploaded");
            onChanged?.();
          } catch (e) {
            toast.error(e instanceof Error ? e.message : "Upload failed");
          } finally {
            setLoading(false);
          }
        }}
      >
        {replaceMode ? "Replace declaration" : "Submit declaration"}
      </Button>
    </Stack>
  );
}

function DutyStep({
  entityId,
  isBooking,
  clearance,
  workflowFiles = [],
  onChanged,
  onViewFile,
  onDownloadFile,
}: {
  entityId: string;
  isBooking: boolean;
  clearance: ClearanceViewLike;
  workflowFiles?: Freight.ClearanceWorkflowFile[];
  onChanged?: () => void;
  onViewFile?: (file: { name: string; url: string }) => void;
  onDownloadFile?: (file: { id: string; name: string }) => void;
}) {
  const [dutyRequired, setDutyRequired] = useState(clearance.dutyRequired ?? true);
  const [amount, setAmount] = useState<number | string>(
    clearance.dutyAdvice?.amount ?? "",
  );
  const [currency, setCurrency] = useState(clearance.dutyAdvice?.currency ?? "ETB");
  const [serial, setSerial] = useState(clearance.dutyAdvice?.declarationSerial ?? "");
  const [attachment, setAttachment] = useState<File | null>(null);
  const [loading, setLoading] = useState(false);
  const noticeFile = findWorkflowFile(workflowFiles, "duty_tax_notice");

  const hasExistingNotice = Boolean(noticeFile);
  const dispute = clearance.dutyDispute;

  return (
    <Stack gap="md">
      {/* The customer rejected the last advice — their words drive the
          correction, so they lead the step. */}
      {dispute ? (
        <Alert
          color="orange"
          radius="md"
          icon={<MessageSquareWarning size={16} />}
          title={
            dispute.rounds > 1
              ? `Customer asked for a correction (round ${dispute.rounds})`
              : "Customer asked for a correction"
          }
        >
          <Stack gap={4}>
            <Text size="sm" style={{ whiteSpace: "pre-wrap" }}>
              {dispute.note}
            </Text>
            <Text size="xs" c="dimmed">
              Raised {new Date(dispute.raisedAt).toLocaleString()} — re-advise
              below to send a corrected notice.
            </Text>
          </Stack>
        </Alert>
      ) : null}

      {noticeFile ? (
        <Stack gap={8}>
          <Text size="xs" fw={700} c="dimmed" tt="uppercase">
            Current notice
          </Text>
          <PhasedUploadedFileRow
            label="Duty / Tax Notice"
            file={noticeFile}
            onView={onViewFile}
            onDownload={onDownloadFile}
          />
        </Stack>
      ) : null}

      <Paper withBorder radius="md" p="md" bg="var(--mantine-color-gray-0)">
        <Stack gap="md">
          <Switch
            label="Customer must pay duty/tax"
            description="Turn off if no duty or tax applies to this shipment."
            checked={dutyRequired}
            onChange={(e) => setDutyRequired(e.currentTarget.checked)}
          />
          {dutyRequired ? (
            <>
              <Group grow align="flex-start">
                <NumberInput
                  label="Amount"
                  placeholder="0.00"
                  value={amount}
                  onChange={setAmount}
                  min={0}
                  size="sm"
                  thousandSeparator=","
                />
                <Select
                  label="Currency"
                  data={["ETB", "USD"]}
                  value={currency}
                  onChange={(v) => setCurrency(v ?? "ETB")}
                  size="sm"
                />
              </Group>
              <TextInput
                label="Declaration / payment code"
                placeholder="Customs payment reference"
                value={serial}
                onChange={(e) => setSerial(e.currentTarget.value)}
                size="sm"
              />
              <PhasedFileDropzone
                label="Duty notice attachment"
                description="Upload the official duty/tax notice — shown to the customer in the portal."
                value={attachment}
                onChange={setAttachment}
                replaceMode={hasExistingNotice}
                onPreview={onViewFile}
              />
            </>
          ) : null}
        </Stack>
      </Paper>

      <Button
        color="edr-green"
        loading={loading}
        disabled={
          dutyRequired && (amount === "" || (!attachment && !hasExistingNotice))
        }
        fullWidth
        onClick={async () => {
          setLoading(true);
          try {
            const payload = {
              dutyRequired,
              amount: dutyRequired ? Number(amount) : undefined,
              currency,
              declarationSerial: serial || undefined,
              attachment: dutyRequired ? attachment : undefined,
            };
            if (isBooking) {
              await bookingsService.adviseDuty(entityId, payload);
            } else {
              await contractsService.adviseContractDuty(entityId, payload);
            }
            toast.success(dutyRequired ? "Duty advised" : "Duty step skipped");
            onChanged?.();
          } catch (e) {
            toast.error(e instanceof Error ? e.message : "Failed");
          } finally {
            setLoading(false);
          }
        }}
      >
        Save duty settings
      </Button>
    </Stack>
  );
}

function TransitPermitStep({
  entityId,
  isBooking,
  onChanged,
  workflowFiles = [],
  replaceMode = false,
  onViewFile,
  onDownloadFile,
}: {
  entityId: string;
  isBooking: boolean;
  onChanged?: () => void;
  workflowFiles?: Freight.ClearanceWorkflowFile[];
  replaceMode?: boolean;
  onViewFile?: (file: { name: string; url: string }) => void;
  onDownloadFile?: (file: { id: string; name: string }) => void;
}) {
  const uploaded: TransitPermitUploadedRow[] = importTransitFilesFromWorkflow(workflowFiles);

  return (
    <TransitPermitMultiUpload
      uploaded={uploaded}
      replaceMode={replaceMode}
      fileFieldPrefix="transit_permit"
      onViewFile={onViewFile}
      onDownloadFile={onDownloadFile}
      onSubmit={async (payload) => {
        try {
          if (isBooking) {
            await bookingsService.uploadTransitPermit(entityId, payload);
          } else {
            await contractsService.uploadContractTransitPermit(entityId, payload);
          }
          toast.success(replaceMode ? "Transit permit updated" : "Transit permit uploaded");
          onChanged?.();
        } catch (e) {
          toast.error(e instanceof Error ? e.message : "Upload failed");
          throw e;
        }
      }}
    />
  );
}

function FinalizePreClearanceStep({
  entityId,
  isBooking,
  onChanged,
}: {
  entityId: string;
  isBooking: boolean;
  onChanged?: () => void;
}) {
  const [loading, setLoading] = useState(false);

  return (
    <Stack gap="sm">
      <Text size="sm" c="dimmed">
        Confirm that Ethiopia-side pre-clearance is complete. This unlocks GL Djibouti to
        upload the Delivery Order.
      </Text>
      <Button
        color="edr-green"
        loading={loading}
        leftSection={<PackageCheck size={16} />}
        onClick={async () => {
          setLoading(true);
          try {
            if (isBooking) {
              await bookingsService.finalizePreClearance(entityId);
            } else {
              await contractsService.finalizePreClearance(entityId);
            }
            toast.success("Pre-clearance finalized");
            onChanged?.();
          } catch (e) {
            toast.error(e instanceof Error ? e.message : "Failed");
          } finally {
            setLoading(false);
          }
        }}
      >
        Finalize pre-clearance
      </Button>
    </Stack>
  );
}

function DeliveryOrderStep({
  entityId,
  isBooking,
  onChanged,
  workflowFiles = [],
  replaceMode = false,
  vesselArrivalDate,
  doCollectedDate,
  onViewFile,
  onDownloadFile,
}: {
  entityId: string;
  isBooking: boolean;
  onChanged?: () => void;
  workflowFiles?: Freight.ClearanceWorkflowFile[];
  replaceMode?: boolean;
  vesselArrivalDate?: string | null;
  doCollectedDate?: string | null;
  onViewFile?: (file: { name: string; url: string }) => void;
  onDownloadFile?: (file: { id: string; name: string }) => void;
}) {
  const [files, setFiles] = useState<Record<string, File | null>>({
    delivery_order: null,
  });
  const [doDates, setDoDates] = useDoCollectionDates({
    vesselArrivalDate,
    doCollectedDate,
  });
  const [loading, setLoading] = useState(false);
  const hasFile = Boolean(files.delivery_order);

  return (
    <PhasedDocumentUploadField
      fields={[{ key: "delivery_order", label: "Delivery Order" }]}
      files={files}
      onChange={(key, file) => setFiles((prev) => ({ ...prev, [key]: file }))}
      workflowFiles={workflowFiles}
      replaceMode={replaceMode}
      loading={loading}
      disabled={!hasFile || !doDatesComplete(doDates)}
      helperText="Upload the Djibouti Delivery Order (DO) and record when the vessel arrived and when the DO was collected."
      submitLabel={replaceMode ? "Replace DO" : "Upload DO"}
      extraFields={
        <DoCollectionDateFields value={doDates} onChange={setDoDates} />
      }
      onViewFile={onViewFile}
      onDownloadFile={onDownloadFile}
      onSubmit={async () => {
        const file = files.delivery_order;
        if (!file || !doDatesComplete(doDates)) return;
        const dates = {
          vesselArrivalDate: toIsoDate(doDates.vesselArrival)!,
          doCollectedDate: toIsoDate(doDates.doCollected)!,
        };
        setLoading(true);
        try {
          if (isBooking) {
            await bookingsService.uploadDeliveryOrder(entityId, file, dates);
          } else {
            await contractsService.uploadDeliveryOrder(entityId, file, dates);
          }
          setFiles({ delivery_order: null });
          toast.success(replaceMode ? "Delivery Order updated" : "Delivery Order uploaded");
          onChanged?.();
        } catch (e) {
          toast.error(e instanceof Error ? e.message : "Upload failed");
        } finally {
          setLoading(false);
        }
      }}
    />
  );
}

