import {
  useEffect,
  useMemo,
  useRef,
  useState,
  type KeyboardEvent,
  type ReactNode,
} from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useNavigate, useParams } from "react-router-dom";
import {
  ActionIcon,
  Alert,
  Badge,
  Box,
  Button,
  Center,
  Divider,
  FileButton,
  Group,
  List,
  Loader,
  Modal,
  Paper,
  Select,
  Stack,
  Switch,
  Text,
  TextInput,
  Textarea,
  ThemeIcon,
  Title,
} from "@mantine/core";
import {
  AlertCircle,
  AlertTriangle,
  CalendarDays,
  CheckCircle2,
  ChevronLeft,
  FileDown,
  FileUp,
  Flame,
  MapPin,
  Package,
  PackageCheck,
  Receipt,
  Snowflake,
  Train,
  X,
} from "lucide-react";

import { OperationDatePicker } from "@edr/ui-common";
import {
  StepCard,
  StepHeader,
  StepLabel,
  fieldStyles,
} from "../contracts/new-contract-form/shared";
import { formatRateUnit } from "../contracts/new-contract-form/unit-rates";
import {
  ShipmentFormInputValues,
  ShipmentFormValues,
  createShipmentFormSchema,
  initialShipmentFormValues,
} from "../contracts/new-shipment-form/schema";
import {
  downloadContainerImportTemplate,
  parseContainerExcel,
} from "../contracts/new-shipment-form/container-excel";
import { formatAmount } from "../contracts/new-shipment-form/total";
import {
  shippingLineBookingsService,
  type CompleteBookingContainerLine,
  type CompleteShippingLineBookingPayload,
  type ShippingLineBooking,
  type ShippingLinePriceQuote,
} from "@/services/shipping-line-bookings.service";
import { extractApiError } from "@/utils/result";

type ShipmentForm = ReturnType<
  typeof useForm<ShipmentFormInputValues, any, ShipmentFormValues>
>;

const SIZES = ["20ft", "40ft"] as const;

/**
 * Every quantity on this form is a non-negative magnitude. A native number
 * input's `min` only constrains its stepper, so swallow the minus key before it
 * can put a negative into the field at all.
 */
const blockNegative = (event: KeyboardEvent<HTMLInputElement>) => {
  if (event.key === "-") event.preventDefault();
};

const COMPLETABLE_STATUSES = new Set([
  "CLEARANCE_READY",
  "OPERATION_CHANGES_REQUESTED",
]);

/**
 * Shipping-line booking completion page — a faithful duplicate of the customer
 * shipment-booking page (contracts/NewShipmentPage): the same Excel
 * import/template flow, the same per-container rows with hazardous/refrigerated
 * switches, the same day-picker calendar. Only the plumbing differs: no
 * contract — cargo posts to the shipping-line completion endpoint, days come
 * from the line's dedicated trains (or the shared pool when it has none), and
 * the price lands on the credit ledger instead of an invoice, so there is no
 * price-confirmation step.
 */
export default function ShippingLineCompletePage() {
  const { id = "" } = useParams();
  const navigate = useNavigate();

  const bookingQuery = useQuery({
    queryKey: ["shipping-line-bookings", id],
    queryFn: () => shippingLineBookingsService.getById(id),
    enabled: Boolean(id),
  });

  if (bookingQuery.isLoading) {
    return (
      <Center mih={400} p="xl">
        <Loader color="edr-green" />
      </Center>
    );
  }

  const booking = bookingQuery.data;
  if (!booking) {
    return (
      <Box p="xl">
        <Paper withBorder radius="lg" p="xl">
          <Text fw={700} mb="xs">
            Booking not found
          </Text>
          <Button
            variant="default"
            onClick={() => navigate("/shipping-line/bookings")}
          >
            Back to bookings
          </Button>
        </Paper>
      </Box>
    );
  }

  if (!COMPLETABLE_STATUSES.has(booking.status as string)) {
    return (
      <Box p="xl">
        <Alert color="blue" icon={<AlertCircle size={18} />} radius="md">
          <Text fw={700} mb="xs">
            This booking cannot be completed yet
          </Text>
          <Text size="sm" mb="md">
            Your documents must be approved by Operations before the cargo and
            shipment day can be entered.
          </Text>
          <Button
            color="edr-green"
            radius="md"
            onClick={() => navigate(`/shipping-line/bookings/${booking.id}`)}
          >
            Back to booking
          </Button>
        </Alert>
      </Box>
    );
  }

  return <CompleteBookingForm booking={booking} />;
}

function CompleteBookingForm({ booking }: { booking: ShippingLineBooking }) {
  const navigate = useNavigate();
  const queryClient = useQueryClient();
  const isContainer = (booking.freightType ?? "CONTAINER") === "CONTAINER";
  const isResubmit = booking.status === "OPERATION_CHANGES_REQUESTED";

  // Bulk cargo type lives outside the shared schema (customers get it from
  // their contract) — validated at submit time instead.
  const [cargoTypeId, setCargoTypeId] = useState<string | null>(null);

  const form = useForm<ShipmentFormInputValues, any, ShipmentFormValues>({
    defaultValues: {
      ...initialShipmentFormValues,
      // Shipping lines are always invoiced in ETB — no currency choice.
      paymentCurrency: "ETB",
    },
    resolver: zodResolver(
      createShipmentFormSchema({
        isContainer,
        // Both handling services are always offered — there is no contract
        // scope to restrict them; charges apply only to ticked containers.
        isHazardous: true,
        isReefer: true,
        withReturnService: false,
        unitOfMeasure: "PER_TON",
        requiresDate: true,
        requiresTrain: false,
      }),
    ),
    mode: "onChange",
  });

  const referenceQuery = useQuery({
    queryKey: ["shipping-line-reference-data"],
    queryFn: shippingLineBookingsService.referenceData,
  });

  // 20ft containers ride two per wagon — an odd total can never be planned.
  const watchedContainers = form.watch("containers");
  const ft20Total = isContainer
    ? (watchedContainers ?? [])
        .filter((l) => l.containerSize === "20ft")
        .reduce((sum, l) => sum + Number(l.quantity || 0), 0)
    : 0;
  const hasOdd20ft = ft20Total % 2 === 1;

  // Two-step submit, same shape as the customer shipment form: the confirm
  // modal opens at once with the payload pending, the server prices it (and
  // runs the 20ft pairing check) while the modal shows a loader, the shipping
  // line confirms the figure, and only then does the booking submit.
  const [pendingPayload, setPendingPayload] =
    useState<CompleteShippingLineBookingPayload | null>(null);

  const previewMutation = useMutation({
    mutationFn: (payload: CompleteShippingLineBookingPayload) =>
      shippingLineBookingsService.pricePreview(booking.id, payload),
    // A pricing failure (no rate configured) closes the confirm dialog — the
    // error modal takes over with the server's message.
    onError: () => setPendingPayload(null),
  });
  const quote = previewMutation.data ?? null;

  const submitMutation = useMutation({
    mutationFn: (payload: CompleteShippingLineBookingPayload) =>
      shippingLineBookingsService.complete(booking.id, payload),
    onSuccess: () => {
      void queryClient.invalidateQueries({
        queryKey: ["shipping-line-bookings"],
      });
      navigate(`/shipping-line/bookings/${booking.id}`);
    },
    // A submit failure (day filled up, window closed meanwhile) must not leave
    // a stale confirm dialog on screen — the error modal takes over.
    onError: () => {
      previewMutation.reset();
      setPendingPayload(null);
    },
  });

  const closeConfirm = () => {
    if (submitMutation.isPending) return;
    previewMutation.reset();
    setPendingPayload(null);
  };

  /**
   * Map a container size to the configured container type: reefer type when
   * any container on the line is refrigerated, standard type otherwise —
   * mirroring the server's own size→type resolution for customers.
   */
  function buildPayload(
    values: ShipmentFormValues,
  ): CompleteShippingLineBookingPayload | { error: string } {
    const base = {
      scheduledDate: values.scheduledDate,
      // Which of the line's trains the booking rides — required by the server
      // when more than one departs on the chosen day.
      ...(values.trainScheduleId
        ? { trainScheduleId: values.trainScheduleId }
        : {}),
      ...(values.paymentCurrency
        ? { paymentCurrency: values.paymentCurrency }
        : {}),
      ...(values.cargoDescription?.trim()
        ? { cargoFreeText: values.cargoDescription.trim() }
        : {}),
    };
    if (!isContainer) {
      if (!cargoTypeId) return { error: "Select the cargo type." };
      return {
        ...base,
        cargoTypeId,
        cargoWeightTons: Number(values.cargoWeightTons),
        bulkHazardousQuantity: Number(values.bulkHazardousQuantity || 0),
        bulkReeferQuantity: Number(values.bulkReeferQuantity || 0),
      };
    }
    const containers: CompleteBookingContainerLine[] = [];
    for (const l of values.containers.filter(
      (l) => Number(l.quantity) >= 1,
    )) {
      const totalVgm = l.units.reduce((s, u) => s + Number(u.vgmTons || 0), 0);
      containers.push({
        // The size string alone — the server maps it to the configured
        // container type, so a slow/failed catalog fetch can never block
        // the submit with a phantom "type not configured" error.
        containerSize: l.containerSize,
        quantity: Number(l.quantity),
        hazardousQuantity: l.units.filter((u) => u.isHazardous).length,
        reeferQuantity: l.units.filter((u) => u.isReefer).length,
        vgmPerUnitTons: l.units.length ? totalVgm / l.units.length : 0,
        units: l.units.map((u) => ({
          containerNumber: u.containerNumber.trim().toUpperCase(),
          sealNumber: u.sealNumber || undefined,
          vgmTons: Number(u.vgmTons),
          isHazardous: Boolean(u.isHazardous),
          isReefer: Boolean(u.isReefer),
        })),
      });
    }
    return { ...base, containers };
  }

  const [payloadError, setPayloadError] = useState<string | null>(null);
  const handleSubmit = form.handleSubmit((values) => {
    if (hasOdd20ft) return;
    const payload = buildPayload(values);
    if ("error" in payload) {
      setPayloadError(payload.error);
      return;
    }
    setPayloadError(null);
    // Open the confirm dialog now and price into it; the booking submits only
    // after the shipping line confirms the figure.
    setPendingPayload(payload);
    previewMutation.reset();
    previewMutation.mutate(payload);
  });

  const handleConfirm = () => {
    if (!pendingPayload || !quote) return;
    // Guard: never let unresolved 20ft pairing errors submit — the server
    // rejects them anyway; the disabled button just says so first.
    if (quote.pairingErrors.length > 0) return;
    submitMutation.mutate(pendingPayload);
  };

  const showValidationSummary =
    form.formState.isSubmitted && !form.formState.isValid;

  return (
    <Box
      style={{
        padding: "28px 0 0",
        minHeight: "calc(100dvh - var(--app-shell-header-height, 56px))",
        display: "flex",
        flexDirection: "column",
      }}
    >
      <Group
        justify="space-between"
        px="24px"
        align="flex-end"
        wrap="wrap"
        gap="md"
        mb="lg"
      >
        <Box>
          <Title order={1} fw={800} fz={26} style={{ letterSpacing: "-0.01em" }}>
            {isResubmit ? "Change Your Booking" : "Complete Your Booking"}
          </Title>
          <Text size="sm" c="edr-muted" mt={4}>
            {isResubmit
              ? `Update the details below and pick a new shipment day, then resubmit booking ${booking.reference}.`
              : `Your documents are approved — enter the cargo details and shipment day for booking ${booking.reference}. The charge goes on your credit account.`}
          </Text>
        </Box>
        <Button
          variant="default"
          radius="md"
          leftSection={<ChevronLeft size={16} />}
          onClick={() => navigate(`/shipping-line/bookings/${booking.id}`)}
        >
          Back to booking
        </Button>
      </Group>

      <form
        className="flex flex-col"
        style={{ flex: 1 }}
        onSubmit={(e) => e.preventDefault()}
      >
        {/* Errors surface in a modal, not an inline alert: the submit button
            sits at the bottom of a long form, so a banner at the top scrolls
            out of sight exactly when the user needs it. */}
        <Modal
          opened={Boolean(
            submitMutation.isError || previewMutation.isError || payloadError,
          )}
          onClose={() => {
            setPayloadError(null);
            submitMutation.reset();
            previewMutation.reset();
          }}
          centered
          radius="md"
          size="lg"
          title={
            <Group gap={8}>
              <AlertCircle size={18} color="var(--mantine-color-red-6)" />
              <Text fw={700} fz={16}>
                Booking could not be completed
              </Text>
            </Group>
          }
          overlayProps={{ blur: 2, backgroundOpacity: 0.55 }}
        >
          <Stack gap="md">
            {(() => {
              // The server's message, never axios's generic "Request failed
              // with status code 400" — extractApiError digs it out of the
              // response body.
              const message =
                payloadError ??
                extractApiError(submitMutation.error ?? previewMutation.error)
                  .message;
              // Missing-rate blocks are an EDR setup gap, not a mistake the
              // shipping line made — lead with plain words and where to go,
              // then the specifics of what is not priced.
              const isPricingGap = message.includes("rate is configured");
              // The server joins multiple pricing blocks with "; " — a list
              // reads far better than one wall of text.
              const parts = message
                .split("; ")
                .map((p) => p.trim())
                .filter(Boolean);
              return (
                <>
                  {isPricingGap && (
                    <Text size="sm" fw={600}>
                      This booking cannot be completed yet — the price for
                      your shipment has not been set up. Please contact EDR
                      support to configure it, or change the options below:
                    </Text>
                  )}
                  {parts.length > 1 ? (
                    <List spacing="sm" size="sm" c="red.8">
                      {parts.map((part, i) => (
                        <List.Item key={i}>{part}</List.Item>
                      ))}
                    </List>
                  ) : (
                    <Text size="sm" c="red.8">
                      {message}
                    </Text>
                  )}
                </>
              );
            })()}
            <Group justify="flex-end">
              <Button
                variant="default"
                radius="md"
                onClick={() => {
                  setPayloadError(null);
                  submitMutation.reset();
                  previewMutation.reset();
                }}
              >
                Close
              </Button>
            </Group>
          </Stack>
        </Modal>

        <PriceConfirmModal
          opened={Boolean(pendingPayload)}
          quote={quote}
          quoteLoading={previewMutation.isPending}
          loading={submitMutation.isPending}
          onConfirm={handleConfirm}
          onReject={closeConfirm}
        />

        <Box flex={1} p="24px">
          <Stack gap="lg" className="mx-auto max-w-4xl">
            <RouteCard booking={booking} />
            {isContainer ? (
              <ContainerCargoStep form={form} />
            ) : (
              <BulkCargoStep
                form={form}
                cargoTypeId={cargoTypeId}
                onCargoTypeChange={setCargoTypeId}
                cargoTypes={referenceQuery.data?.cargoTypes ?? []}
              />
            )}
            <ScheduleStep
              form={form}
              booking={booking}
              cargoTypeId={isContainer ? null : cargoTypeId}
            />
          </Stack>
        </Box>

        <Box
          style={{
            position: "sticky",
            bottom: 0,
            zIndex: 20,
            borderTop: "1px solid var(--mantine-color-edr-border-0)",
            backgroundColor: "rgba(255,255,255,0.94)",
            backdropFilter: "blur(14px)",
            padding: "16px 24px",
            marginTop: "auto",
          }}
        >
          <Box className="mx-auto max-w-4xl">
            {showValidationSummary ? (
              <Alert
                color="red"
                variant="light"
                radius="md"
                icon={<AlertCircle size={16} />}
                mb="sm"
              >
                Fix the highlighted fields before completing the booking.
              </Alert>
            ) : null}
            <Group justify="flex-end">
              <Button
                type="button"
                color="edr-green"
                radius="md"
                leftSection={<PackageCheck size={16} />}
                onClick={handleSubmit}
                loading={previewMutation.isPending || submitMutation.isPending}
                disabled={hasOdd20ft}
              >
                {isResubmit ? "Resubmit booking" : "Complete booking"}
              </Button>
            </Group>
          </Box>
        </Box>
      </form>
    </Box>
  );
}

/**
 * Price confirmation — the customer shipment form's modal, one-to-one: opens
 * the moment the form submits, shows a loader while the server prices and
 * checks 20ft pairing, then the authoritative breakdown. Pairing violations
 * hard-block confirm (the server rejects them on /complete too); overweight
 * containers only warn — the surcharge is already inside the total.
 */
function PriceConfirmModal({
  opened,
  quote,
  quoteLoading,
  loading,
  onConfirm,
  onReject,
}: {
  opened: boolean;
  quote: ShippingLinePriceQuote | null;
  quoteLoading: boolean;
  loading: boolean;
  onConfirm: () => void;
  onReject: () => void;
}) {
  const pairingErrors = quote?.pairingErrors ?? [];
  const hasPairingBlock = pairingErrors.length > 0;
  const overweightLines = quote?.overweightLines ?? [];
  const overweightSurchargeAmount =
    quote?.lineItems.find((li) => li.code === "OVERWEIGHT_PER_TON")?.amount ??
    0;

  // Confirm waits for the authoritative price and a clean pairing check.
  const confirmDisabled =
    loading || quoteLoading || hasPairingBlock || !quote;

  return (
    <Modal
      opened={opened}
      onClose={onReject}
      closeOnClickOutside={!loading}
      closeOnEscape={!loading}
      withCloseButton={!loading}
      centered
      radius="lg"
      size="lg"
      title={
        <Group gap={10}>
          <ThemeIcon variant="light" color="edr-green" radius="md" size={34}>
            <Receipt size={18} />
          </ThemeIcon>
          <Box>
            <Text fw={800} fz={16} c="#10202F">
              Confirm shipment price
            </Text>
            <Text fz="xs" c="dimmed">
              Review the total before booking this shipment.
            </Text>
          </Box>
        </Group>
      }
    >
      <Stack gap="md">
        {quoteLoading && (
          <Group gap={8} c="dimmed">
            <Loader size="xs" color="edr-green" />
            <Text fz="sm" c="dimmed">
              Computing the final price breakdown and checking container
              weights…
            </Text>
          </Group>
        )}

        {hasPairingBlock && (
          <Alert
            color="red"
            variant="light"
            radius="md"
            icon={<AlertCircle size={16} />}
            title="Cannot complete booking — 20ft wagon pairing"
          >
            <Stack gap={6}>
              {pairingErrors.map((msg, i) => (
                <Text key={i} fz="sm" c="red.8">
                  {msg}
                </Text>
              ))}
              <Text fz="xs" c="red.7" mt={2}>
                Adjust the 20ft container weights or quantities so pairs differ
                by no more than 10 tons.
              </Text>
            </Stack>
          </Alert>
        )}

        {overweightLines.length > 0 && (
          <Alert
            color="yellow"
            variant="light"
            radius="md"
            icon={<AlertTriangle size={16} />}
            title="Overweight containers"
          >
            <Stack gap={6}>
              {overweightLines.map((line, i) => (
                <Text key={i} fz="sm" c="#9A5B00">
                  {line.containerLabel || line.containerTypeCode}:{" "}
                  {line.totalVgmTons}t exceeds limit {line.maxAllowedTons}t (+
                  {line.excessTons}t overweight)
                </Text>
              ))}
              <Text fz="xs" c="#9A5B00" mt={2}>
                {overweightSurchargeAmount > 0
                  ? `An overweight surcharge of ${formatAmount(overweightSurchargeAmount)} ${
                      quote?.currency ?? ""
                    } applies (included in the total below). You can still submit, or go back and adjust weights.`
                  : "An overweight surcharge applies. You can still submit, or go back and adjust weights."}
              </Text>
            </Stack>
          </Alert>
        )}

        {quote && quote.warnings.length > 0 && (
          <Alert
            color="yellow"
            variant="light"
            radius="md"
            icon={<AlertTriangle size={16} />}
          >
            {quote.warnings.join(" ")}
          </Alert>
        )}

        {quote && (
          <Paper
            withBorder
            radius={16}
            p="lg"
            style={{ borderColor: "#E6ECF2" }}
          >
            <Stack gap={10}>
              {quote.lineItems.map((line, i) => (
                <Group key={i} justify="space-between" wrap="nowrap" gap="sm">
                  <Box style={{ minWidth: 0 }}>
                    <Text fz="sm" c="#10202F" fw={500}>
                      {line.description}
                    </Text>
                    <Text fz="xs" c="dimmed">
                      {(line.quantity ?? 1).toLocaleString()} ×{" "}
                      {formatAmount(line.unitAmount ?? line.amount)}{" "}
                      {quote.currency}
                      {line.unit
                        ? ` · ${formatRateUnit(line.unit.toLowerCase())}`
                        : ""}
                    </Text>
                  </Box>
                  <Text
                    fz="sm"
                    fw={600}
                    c="#10202F"
                    style={{ whiteSpace: "nowrap" }}
                  >
                    {formatAmount(line.amount)} {quote.currency}
                  </Text>
                </Group>
              ))}
              {quote.lineItems.length === 0 && (
                <Text fz="sm" c="dimmed">
                  No priced lines — check the cargo details.
                </Text>
              )}
            </Stack>
            <Divider my="md" />
            <Group justify="space-between" align="flex-end">
              <Text
                fz="xs"
                fw={700}
                tt="uppercase"
                c="edr-green"
                style={{ letterSpacing: "0.06em" }}
              >
                Total
              </Text>
              <Text fw={800} fz={28} c="#10202F">
                {formatAmount(quote.totalAmount)}{" "}
                <Text span fz={16} fw={700} c="edr-muted">
                  {quote.currency}
                </Text>
              </Text>
            </Group>
            <Text fz="xs" c="dimmed" mt="sm">
              The amount is charged to your credit account — no payment is due
              now. EDR bills your accumulated charges periodically.
            </Text>
          </Paper>
        )}

        <Group justify="space-between" mt="xs">
          <Button
            variant="default"
            radius="md"
            leftSection={<X size={16} />}
            onClick={onReject}
            disabled={loading}
          >
            Reject &amp; edit
          </Button>
          <Button
            color="edr-green"
            radius="md"
            leftSection={<CheckCircle2 size={16} />}
            onClick={onConfirm}
            loading={loading}
            disabled={confirmDisabled}
          >
            Confirm &amp; book
          </Button>
        </Group>
      </Stack>
    </Modal>
  );
}

/** The booking's lane — fixed at initiate time from the chosen route. */
function RouteCard({ booking }: { booking: ShippingLineBooking }) {
  return (
    <StepCard>
      <StepHeader
        icon={<MapPin size={22} />}
        title="Route"
        description="This booking ships on the lane picked when it was initiated."
      />
      <Paper withBorder radius="md" p="md" style={{ borderColor: "#E6ECF2" }}>
        <Text fz={14} fw={600} c="#10202F">
          {booking.originYard?.label ?? booking.originYard?.code ?? "—"} →{" "}
          {booking.destinationYard?.label ??
            booking.destinationYard?.code ??
            "—"}
        </Text>
        <Text fz={12} c="dimmed" mt={2}>
          {booking.tradeDirection ?? "IMPORT"}
        </Text>
      </Paper>
    </StepCard>
  );
}

/** A departure's calendar day in East Africa Time — matches the server's eatDay. */
function eatDayOf(date: string | Date): string {
  return new Date(date).toLocaleDateString("en-CA", {
    timeZone: "Africa/Addis_Ababa",
  });
}

function ScheduleStep({
  form,
  booking,
  cargoTypeId,
}: {
  form: ShipmentForm;
  booking: ShippingLineBooking;
  /** Bulk cargo pick (container bookings pass null) — refines availability. */
  cargoTypeId: string | null;
}) {
  // Days come from the line's dedicated trains (open until each train's
  // close offset), or the shared pool when the lane has none.
  const daysQuery = useQuery({
    queryKey: ["shipping-line-bookings", booking.id, "available-days"],
    queryFn: () => shippingLineBookingsService.availableDays(booking.id),
  });

  // Cargo context makes the per-train availability figures real: the wagon
  // types offered are the ones THIS cargo can ride. Recomputed per render on
  // purpose — react-hook-form mutates the watched array in place.
  const isContainer = (booking.freightType ?? "CONTAINER") === "CONTAINER";
  const containerLines = form.watch("containers") ?? [];
  const cargo = (() => {
    if (isContainer) {
      const sizes = containerLines
        .filter((l) => Number(l.quantity || 0) >= 1)
        .map((l) => l.containerSize);
      const ft20 = containerLines
        .filter((l) => l.containerSize === "20ft")
        .reduce((s, l) => s + Number(l.quantity || 0), 0);
      const ft40 = containerLines
        .filter((l) => l.containerSize === "40ft")
        .reduce((s, l) => s + Number(l.quantity || 0), 0);
      const wagons = Math.ceil(ft20 / 2) + ft40;
      return {
        containerSizes: sizes.length ? sizes : undefined,
        wagons: wagons > 0 ? wagons : undefined,
      };
    }
    return cargoTypeId ? { cargoTypeId } : {};
  })();

  // The line's OWN departures on this lane, each with per-wagon-type free
  // space — the pick is "which train", not an abstract calendar day.
  // Customers never see these trains; the endpoint is shipping-line only.
  const trainsQuery = useQuery({
    queryKey: [
      "shipping-line-bookings",
      booking.id,
      "trains",
      cargo.containerSizes?.join(",") ?? "",
      (cargo as { cargoTypeId?: string }).cargoTypeId ?? "",
      (cargo as { wagons?: number }).wagons ?? 0,
    ],
    queryFn: () =>
      shippingLineBookingsService.trainsForDay(booking.id, undefined, cargo),
  });
  const options = trainsQuery.data ?? [];

  const openDays = new Set(daysQuery.data?.days ?? []);
  const selectedTrainId = form.watch("trainScheduleId");

  return (
    <StepCard>
      <StepHeader
        icon={<CalendarDays size={22} />}
        title="Schedule"
        description={
          options.length
            ? "Pick the train your shipment rides — these departures are dedicated to your company. A booking rides one train."
            : "Pick the binding shipment day. Only days with a train departure on your route can be selected."
        }
      />
      {/* No currency choice: shipping-line charges always land on the ETB
          credit ledger — the form defaults paymentCurrency to ETB and the
          server enforces it. */}
      <Controller
        name="scheduledDate"
        control={form.control}
        render={({ field, fieldState }) => (
          <Box>
            <StepLabel>
              {options.length ? "Your train *" : "Shipment day *"}
            </StepLabel>
            <Box mt={10} w="100%">
              {trainsQuery.isLoading ? (
                <Group gap={8} c="dimmed" py="sm">
                  <Loader size="xs" color="edr-green" />
                  <Text fz="sm" c="dimmed">
                    Checking wagon availability on your trains…
                  </Text>
                </Group>
              ) : options.length ? (
                <Stack gap="sm">
                  {options.map((option) => {
                    const day = eatDayOf(option.departure);
                    const selected = selectedTrainId === option.scheduleId;
                    // Closed when its own cut-off passed OR the day fell out
                    // of the bookable set.
                    const closed = !option.isOpen || !openDays.has(day);
                    const showFit = (option.neededWagons ?? 0) > 0;
                    return (
                      <Paper
                        key={option.scheduleId}
                        withBorder
                        radius="md"
                        p="md"
                        onClick={() => {
                          if (closed) return;
                          field.onChange(day);
                          form.setValue("trainScheduleId", option.scheduleId, {
                            shouldValidate: true,
                          });
                        }}
                        style={{
                          cursor: closed ? "not-allowed" : "pointer",
                          opacity: closed ? 0.55 : 1,
                          borderColor: selected
                            ? "var(--mantine-color-teal-6)"
                            : undefined,
                          borderWidth: selected ? 2 : 1,
                          background: selected
                            ? "var(--mantine-color-teal-0)"
                            : undefined,
                        }}
                      >
                        <Group justify="space-between" wrap="wrap" gap="sm">
                          <Group gap="sm" align="flex-start">
                            <Train
                              size={20}
                              color={
                                selected
                                  ? "var(--mantine-color-teal-7)"
                                  : "var(--mantine-color-gray-6)"
                              }
                            />
                            <Box>
                              <Group gap={8}>
                                <Text fw={700} fz={14}>
                                  {option.trainNumber ??
                                    option.trainName ??
                                    "Train"}
                                </Text>
                                {option.trainName && option.trainNumber ? (
                                  <Text fz={12} c="dimmed">
                                    {option.trainName}
                                  </Text>
                                ) : null}
                                {closed && (
                                  <Badge
                                    size="xs"
                                    radius="sm"
                                    color="red"
                                    variant="light"
                                  >
                                    Booking closed
                                  </Badge>
                                )}
                                {!closed && showFit && (
                                  <Badge
                                    size="xs"
                                    radius="sm"
                                    color={option.fits ? "teal" : "orange"}
                                    variant="light"
                                  >
                                    {option.fits
                                      ? "Fits your cargo"
                                      : "Not enough space"}
                                  </Badge>
                                )}
                              </Group>
                              {/* Free wagons by TYPE — what this train can
                                  still take for the entered cargo. */}
                              <Group gap={6} mt={6} wrap="wrap">
                                {option.byWagonType.length ? (
                                  option.byWagonType.map((t) => (
                                    <Badge
                                      key={t.wagonTypeId ?? "any"}
                                      size="sm"
                                      radius="sm"
                                      variant="outline"
                                      color={
                                        t.freeWagons > 0 ? "teal" : "gray"
                                      }
                                    >
                                      {t.code ?? t.name ?? "Wagons"} ·{" "}
                                      {t.freeWagons} free
                                    </Badge>
                                  ))
                                ) : (
                                  <Text fz={12} c="dimmed">
                                    Enter your cargo to see wagon availability
                                    by type.
                                  </Text>
                                )}
                              </Group>
                            </Box>
                          </Group>
                          <Box ta="right">
                            <Text fz={13} fw={600}>
                              Departs{" "}
                              {new Date(option.departure).toLocaleString(
                                undefined,
                                {
                                  weekday: "short",
                                  month: "short",
                                  day: "numeric",
                                  hour: "2-digit",
                                  minute: "2-digit",
                                },
                              )}
                            </Text>
                            {option.bookingClosesAt && (
                              <Text fz={12} c="dimmed">
                                Book before{" "}
                                {new Date(
                                  option.bookingClosesAt,
                                ).toLocaleString(undefined, {
                                  month: "short",
                                  day: "numeric",
                                  hour: "2-digit",
                                  minute: "2-digit",
                                })}
                              </Text>
                            )}
                          </Box>
                        </Group>
                      </Paper>
                    );
                  })}
                </Stack>
              ) : (
                <OperationDatePicker
                  fullWidth
                  availableDays={daysQuery.data?.days ?? []}
                  isLoading={daysQuery.isLoading}
                  emptyMessage="No departures are currently open on this route. Please check back or contact Operations."
                  value={field.value ?? ""}
                  onChange={(d) => field.onChange(d)}
                />
              )}
            </Box>
            {fieldState.error?.message && (
              <Text fz="xs" c="red" mt={6}>
                {fieldState.error.message}
              </Text>
            )}
          </Box>
        )}
      />
    </StepCard>
  );
}

function ContainerCargoStep({ form }: { form: ShipmentForm }) {
  // Seed one shipment line per size exactly once.
  const seededRef = useRef(false);
  useEffect(() => {
    if (seededRef.current) return;
    seededRef.current = true;
    const existing = form.getValues("containers") ?? [];
    if (existing.length > 0) return;
    form.setValue(
      "containers",
      SIZES.map((size) => ({
        containerSize: size,
        quantity: "0",
        hazardousQuantity: "0",
        reeferQuantity: "0",
        returnQuantity: "0",
        units: [],
      })),
      { shouldValidate: false },
    );
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const lines = form.watch("containers") ?? [];

  // Excel import: one row per container. All-or-nothing — a file with any bad
  // row is rejected with row-numbered errors so nothing is silently dropped.
  const [importErrors, setImportErrors] = useState<string[]>([]);
  const [importSummary, setImportSummary] = useState<string | null>(null);
  const importResetRef = useRef<(() => void) | null>(null);
  const excelOpts = {
    allowedSizes: [...SIZES],
    includeHazardous: true,
    includeReefer: true,
    includeReturn: false,
  };

  const handleImportFile = async (file: File | null) => {
    // Reset the hidden input so re-picking the same (fixed) file re-fires.
    importResetRef.current?.();
    if (!file) return;
    const { rows, errors } = await parseContainerExcel(file, excelOpts);
    if (errors.length > 0) {
      setImportSummary(null);
      setImportErrors(errors);
      return;
    }
    // Replace only the lines for sizes present in the file; a size the file
    // omits keeps whatever was already entered for it.
    const current = form.getValues("containers") ?? [];
    const next = SIZES.map((size) => {
      const imported = rows.filter((r) => r.containerSize === size);
      if (imported.length === 0) {
        return (
          current.find((l) => l.containerSize === size) ?? {
            containerSize: size,
            quantity: "0",
            hazardousQuantity: "0",
            reeferQuantity: "0",
            returnQuantity: "0",
            units: [],
          }
        );
      }
      return {
        containerSize: size,
        quantity: String(imported.length),
        hazardousQuantity: String(imported.filter((r) => r.hazardous).length),
        reeferQuantity: String(imported.filter((r) => r.reefer).length),
        returnQuantity: "0",
        // The spreadsheet already marks handling per row — carry it onto the
        // container it belongs to rather than collapsing it to a line count.
        units: imported.map((r) => ({
          containerNumber: r.containerNumber,
          sealNumber: r.sealNumber,
          vgmTons: r.vgmTons,
          isHazardous: Boolean(r.hazardous),
          isReefer: Boolean(r.reefer),
          isReturn: false,
        })),
      };
    });
    form.setValue("containers", next, {
      shouldValidate: true,
      shouldDirty: true,
    });
    setImportErrors([]);
    setImportSummary(`Imported ${rows.length} container(s) from ${file.name}.`);
  };

  return (
    <StepCard>
      <StepHeader
        icon={<Package size={22} />}
        title="Cargo Details"
        description="Enter the quantity and per-container details for each size."
      />
      <Stack gap={18}>
        <Paper withBorder radius="md" p="md" style={{ borderColor: "#E6ECF2" }}>
          <Group justify="space-between" wrap="wrap" gap="sm">
            <Box>
              <Text fz={13} fw={600} c="#10202F">
                Import containers from Excel
              </Text>
              <Text fz={12} c="dimmed">
                One row per container. Importing fills the lines below for the
                sizes in your file.
              </Text>
            </Box>
            <Group gap="sm">
              <Button
                variant="default"
                size="xs"
                radius="md"
                leftSection={<FileDown size={14} />}
                onClick={() => downloadContainerImportTemplate(excelOpts)}
              >
                Download template
              </Button>
              <FileButton
                resetRef={importResetRef}
                accept=".xlsx,.xls"
                onChange={handleImportFile}
              >
                {(props) => (
                  <Button
                    {...props}
                    size="xs"
                    radius="md"
                    color="edr-green"
                    leftSection={<FileUp size={14} />}
                  >
                    Import Excel
                  </Button>
                )}
              </FileButton>
            </Group>
          </Group>
          {importErrors.length > 0 && (
            <Alert
              color="red"
              variant="light"
              radius="md"
              icon={<AlertCircle size={16} />}
              title="Import failed — fix the file and try again"
              mt="sm"
            >
              <Stack gap={4}>
                {importErrors.slice(0, 8).map((msg, i) => (
                  <Text key={i} fz="xs">
                    {msg}
                  </Text>
                ))}
                {importErrors.length > 8 && (
                  <Text fz="xs" c="dimmed">
                    …and {importErrors.length - 8} more.
                  </Text>
                )}
              </Stack>
            </Alert>
          )}
          {importSummary && (
            <Alert
              color="edr-green"
              variant="light"
              radius="md"
              icon={<CheckCircle2 size={16} />}
              mt="sm"
            >
              <Text fz="xs">{importSummary}</Text>
            </Alert>
          )}
        </Paper>
        <Controller
          name="cargoDescription"
          control={form.control}
          render={({ field, fieldState }) => (
            <Textarea
              label="Cargo description *"
              description="What do the containers carry on this shipment?"
              placeholder="e.g. Electronics, garments, machinery spare parts…"
              value={field.value ?? ""}
              onChange={(e) => field.onChange(e.currentTarget.value)}
              onBlur={field.onBlur}
              error={fieldState.error?.message}
              radius={10}
              autosize
              minRows={2}
              maxRows={4}
              styles={fieldStyles}
            />
          )}
        />
        {lines.map((line, index) => (
          <ContainerLineEditor
            key={line.containerSize}
            form={form}
            index={index}
            size={line.containerSize}
          />
        ))}
        {(() => {
          const ft20 = lines
            .filter((l) => l.containerSize === "20ft")
            .reduce((sum, l) => sum + Number(l.quantity || 0), 0);
          if (ft20 % 2 !== 1) return null;
          return (
            <Alert
              color="red"
              variant="light"
              radius="md"
              icon={<AlertCircle size={16} />}
              title={`Odd number of 20ft containers (${ft20})`}
            >
              <Text fz={13}>
                20ft containers travel two per wagon, so they must be booked in
                even numbers. Please add one more 20ft container or remove one
                (e.g. book {ft20 + 1} or {ft20 - 1} instead of {ft20}) — the
                booking cannot be submitted with an unpaired 20ft container.
              </Text>
            </Alert>
          );
        })()}
      </Stack>
    </StepCard>
  );
}

function BulkCargoStep({
  form,
  cargoTypeId,
  onCargoTypeChange,
  cargoTypes,
}: {
  form: ShipmentForm;
  cargoTypeId: string | null;
  onCargoTypeChange: (v: string | null) => void;
  cargoTypes: {
    id: string;
    name: string;
    parentGroupId: string | null;
  }[];
}) {
  // Grouping headers are rows other rows point at via parentGroupId — only
  // leaves are bookable cargo.
  const options = useMemo(() => {
    const parents = new Set(
      cargoTypes
        .map((c) => c.parentGroupId)
        .filter((id): id is string => Boolean(id)),
    );
    return cargoTypes
      .filter((c) => !parents.has(c.id))
      .map((c) => ({ value: c.id, label: c.name }));
  }, [cargoTypes]);

  return (
    <StepCard>
      <StepHeader
        icon={<Package size={22} />}
        title="Cargo Details"
        description="Enter the amount you are shipping for this booking."
      />
      <Stack gap={14}>
        <Select
          label="Cargo type *"
          placeholder="Select cargo..."
          searchable
          data={options}
          value={cargoTypeId}
          onChange={onCargoTypeChange}
          radius={10}
          styles={fieldStyles}
          comboboxProps={{ withinPortal: true }}
        />
        <Controller
          name="cargoWeightTons"
          control={form.control}
          render={({ field, fieldState }) => (
            <TextInput
              {...field}
              type="number"
              onKeyDown={blockNegative}
              label="Total weight (tons) *"
              placeholder="e.g. 1200"
              min={0}
              step={0.01}
              error={fieldState.error?.message}
              radius={10}
              styles={fieldStyles}
            />
          )}
        />
        <Controller
          name="bulkHazardousQuantity"
          control={form.control}
          render={({ field, fieldState }) => (
            <TextInput
              {...field}
              type="number"
              onKeyDown={blockNegative}
              label="Hazardous quantity"
              min={0}
              step={1}
              error={fieldState.error?.message}
              radius={10}
              styles={fieldStyles}
            />
          )}
        />
        <Controller
          name="bulkReeferQuantity"
          control={form.control}
          render={({ field, fieldState }) => (
            <TextInput
              {...field}
              type="number"
              onKeyDown={blockNegative}
              label="Refrigerated quantity"
              min={0}
              step={1}
              error={fieldState.error?.message}
              radius={10}
              styles={fieldStyles}
            />
          )}
        />
        <Controller
          name="cargoDescription"
          control={form.control}
          render={({ field }) => (
            <Textarea
              label="Cargo description (optional)"
              placeholder="What the shipment carries"
              value={field.value ?? ""}
              onChange={(e) => field.onChange(e.currentTarget.value)}
              radius={10}
              autosize
              minRows={1}
              maxRows={3}
              styles={fieldStyles}
            />
          )}
        />
      </Stack>
    </StepCard>
  );
}

function ContainerLineEditor({
  form,
  index,
  size,
}: {
  form: ShipmentForm;
  index: number;
  size: "20ft" | "40ft";
}) {
  const line = form.watch(`containers.${index}`);
  const quantity = Number(line?.quantity || 0);
  const units = line?.units ?? [];

  // Keep the units array length in sync with the entered quantity.
  const syncUnits = (qty: number) => {
    const current = form.getValues(`containers.${index}.units`) ?? [];
    const next = [...current];
    while (next.length < qty)
      next.push({
        containerNumber: "",
        sealNumber: "",
        vgmTons: "",
        isHazardous: false,
        isReefer: false,
        isReturn: false,
      });
    next.length = Math.max(0, qty);
    form.setValue(`containers.${index}.units`, next, { shouldValidate: false });
    syncHandlingCounts(next);
  };

  // Drop one container row and shrink quantity to match — the inverse of
  // syncUnits growing the array when quantity goes up.
  const removeUnit = (unitIdx: number) => {
    const current = form.getValues(`containers.${index}.units`) ?? [];
    const next = current.filter((_, j) => j !== unitIdx);
    form.setValue(`containers.${index}.units`, next, { shouldValidate: false });
    form.setValue(`containers.${index}.quantity`, String(next.length), {
      shouldValidate: true,
    });
    syncHandlingCounts(next);
  };

  /**
   * Line totals are a roll-up of the per-container switches — the count is
   * however many containers ticked each service. Kept in form state so the
   * submitted payload stays in step with the switches.
   */
  const syncHandlingCounts = (
    units: Array<{ isHazardous?: boolean; isReefer?: boolean }>,
  ) => {
    const set = (
      key: "hazardousQuantity" | "reeferQuantity",
      count: number,
    ) =>
      form.setValue(`containers.${index}.${key}`, String(count), {
        shouldDirty: true,
        shouldValidate: true,
      });
    set("hazardousQuantity", units.filter((u) => u.isHazardous).length);
    set("reeferQuantity", units.filter((u) => u.isReefer).length);
  };

  /** Flip one container's handling switch, then re-roll the line totals. */
  const toggleUnitHandling = (
    unitIndex: number,
    key: "isHazardous" | "isReefer",
    on: boolean,
  ) => {
    form.setValue(`containers.${index}.units.${unitIndex}.${key}`, on, {
      shouldDirty: true,
    });
    syncHandlingCounts(form.getValues(`containers.${index}.units`) ?? []);
  };

  const handlingColumns: Array<{
    key: "isHazardous" | "isReefer";
    label: string;
    icon: ReactNode;
    color: string;
  }> = [
    {
      key: "isHazardous",
      label: "Hazardous",
      icon: <Flame size={14} />,
      color: "#C0392B",
    },
    {
      key: "isReefer",
      label: "Refrigerated",
      icon: <Snowflake size={14} />,
      color: "#2E5B96",
    },
  ];

  return (
    <Box
      className="rounded-xl"
      style={{ border: "1px solid #E6ECF2", padding: 16 }}
    >
      <Text fz={14} fw={700} c="#10202F" mb={10}>
        {size} containers
      </Text>
      <Box maw={220} mb={14}>
        <Controller
          name={`containers.${index}.quantity`}
          control={form.control}
          render={({ field, fieldState }) => (
            <TextInput
              {...field}
              type="number"
              onKeyDown={blockNegative}
              label="Quantity *"
              min={0}
              error={fieldState.error?.message}
              radius={10}
              styles={fieldStyles}
              onChange={(e) => {
                field.onChange(e.currentTarget.value);
              }}
              onBlur={(e) => {
                field.onBlur();
                syncUnits(Number(e.currentTarget.value || 0));
              }}
            />
          )}
        />
      </Box>

      <StepLabel>Per-container details</StepLabel>
      {quantity > 0 ? (
        <Text fz={11} c="#5B6B7B" mt={4}>
          Tick the services each individual container needs — charges apply only
          to the containers you tick.
        </Text>
      ) : null}
      <Stack gap={10} mt={8}>
        {/* Header row — input labels + handling-service labels, one aligned
            grid shared by every unit row below. */}
        {Math.max(quantity, units.length) > 0 && (
          <Group gap={10} wrap="nowrap" align="flex-end">
            <Text fz={12} fw={600} c="#10202F" style={{ flex: 1 }}>
              Container number *
            </Text>
            <Text fz={12} fw={600} c="#10202F" style={{ flex: 1 }}>
              Seal number
            </Text>
            <Text fz={12} fw={600} c="#10202F" style={{ flex: 1 }}>
              VGM (tons) *
            </Text>
            {handlingColumns.map((col) => (
              <Group
                key={col.key}
                gap={4}
                wrap="nowrap"
                justify="center"
                style={{ width: 96, flexShrink: 0 }}
              >
                <span style={{ color: col.color, display: "flex" }}>
                  {col.icon}
                </span>
                <Text fz={12} fw={600} c="#10202F">
                  {col.label}
                </Text>
              </Group>
            ))}
          </Group>
        )}
        {Array.from({ length: Math.max(quantity, units.length) }).map(
          (_, u) => (
            <Group key={u} gap={10} wrap="nowrap" align="flex-start">
              <Controller
                name={`containers.${index}.units.${u}.containerNumber`}
                control={form.control}
                render={({ field, fieldState }) => (
                  <TextInput
                    {...field}
                    onChange={(e) =>
                      field.onChange(e.currentTarget.value.toUpperCase())
                    }
                    placeholder="e.g. MSCU1234567"
                    error={fieldState.error?.message}
                    radius={10}
                    styles={fieldStyles}
                    style={{ flex: 1 }}
                  />
                )}
              />
              <Controller
                name={`containers.${index}.units.${u}.sealNumber`}
                control={form.control}
                render={({ field }) => (
                  <TextInput
                    {...field}
                    placeholder="Optional"
                    radius={10}
                    styles={fieldStyles}
                    style={{ flex: 1 }}
                  />
                )}
              />
              <Controller
                name={`containers.${index}.units.${u}.vgmTons`}
                control={form.control}
                render={({ field, fieldState }) => (
                  <TextInput
                    {...field}
                    type="number"
                    onKeyDown={blockNegative}
                    placeholder="e.g. 24.5"
                    min={0}
                    step={0.01}
                    error={fieldState.error?.message}
                    radius={10}
                    styles={fieldStyles}
                    style={{ flex: 1 }}
                  />
                )}
              />
              {handlingColumns.map((col) => (
                <Controller
                  key={col.key}
                  name={`containers.${index}.units.${u}.${col.key}`}
                  control={form.control}
                  render={({ field }) => (
                    <Box
                      style={{
                        width: 96,
                        flexShrink: 0,
                        height: 42,
                        display: "flex",
                        alignItems: "center",
                        justifyContent: "center",
                      }}
                    >
                      <Switch
                        checked={Boolean(field.value)}
                        aria-label={`${col.label} — container ${u + 1}`}
                        onChange={(e) =>
                          toggleUnitHandling(u, col.key, e.currentTarget.checked)
                        }
                        size="sm"
                      />
                    </Box>
                  )}
                />
              ))}
              <ActionIcon
                variant="subtle"
                color="red"
                aria-label={`Remove container ${u + 1}`}
                onClick={() => removeUnit(u)}
              >
                <X size={16} />
              </ActionIcon>
            </Group>
          ),
        )}
      </Stack>
    </Box>
  );
}
