import { api } from "@/services/api";
import { Freight } from "@edr/types";
import type {
  CreateBookingPayload,
  GeneratePriceResponse,
  SubmitBookingResponse,
} from "@/services/bookings.service";
import { zodResolver } from "@hookform/resolvers/zod";
import {
  Alert,
  Box,
  Button,
  Group,
  Modal,
  Stack,
  Text,
  TextInput,
  Title,
} from "@mantine/core";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
  AlertCircle,
  Check,
  ChevronLeft,
  ChevronRight,
  Link2,
  Send,
  XCircle,
} from "lucide-react";
import { useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { Navigate, useLocation, useNavigate } from "react-router-dom";
import useAuth from "@/hooks/useAuth";
import {
  BookingFormInputValues,
  STEPS,
  allowedOperationsForProfiles,
  bookingFormSchema,
  getRouteDirection,
  initialBookingFormValues,
  isForwarderOperation,
  operationToProfileType,
  operationToTradeDirection,
  stepFields,
  type BookingDocuments,
  type BookingFormValues,
  type OperationType,
} from "./new-booking-form/schema";
import { StepIndicator } from "./new-booking-form/StepIndicator";
import {
  clearBookingDraft,
  useBookingDraft,
} from "./new-booking-form/useBookingDraft";
import {
  Step0OperationType,
  Step1ContractType,
  Step2ServiceType,
  Step4Route,
  Step5CargoDetails,
  Step8Review,
  StepDocuments,
} from "./new-booking-form/steps";

type PriceModalMode = "submit" | "draft";

/** Human-readable label for a rate's charge unit (e.g. "per container"). */
function formatPriceUnit(unit: string): string {
  const map: Record<string, string> = {
    PER_CONTAINER: "per container",
    PER_TON: "per ton",
    PER_WAGON: "per wagon",
    PER_KM: "per km",
    FLAT: "flat",
  };
  return map[unit] ?? unit.replace(/_/g, " ").toLowerCase();
}

export default function NewBookingPage() {
  const navigate = useNavigate();
  const queryClient = useQueryClient();
  const [step, setStep] = useState(0);
  const auth = useAuth();
  const { data: referenceData, isLoading: refDataLoading } = useQuery(
    api.bookings.referenceData.queryOptions(),
  );

  // Booking is gated on profile approval: a customer whose active profile isn't
  // approved yet is bounced back to the list, where the gate is explained.
  if (!auth.isPending && auth.company && !auth.canBook) {
    return <Navigate to="/bookings" replace />;
  }

  if (!auth.isPending && !auth.company) {
    return (
      <Box
        style={{
          padding: "28px",
          minHeight: "calc(100dvh - var(--app-shell-header-height, 56px))",
          display: "flex",
          flexDirection: "column",
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        <Alert
          color="orange"
          icon={<AlertCircle size={20} />}
          radius="md"
          style={{ maxWidth: "500px" }}
          mb="lg"
        >
          <Text size="lg" fw={600} mb="md">
            Complete Your Company Setup
          </Text>
          <Text size="sm" mb="md">
            You need to complete your company onboarding before you can create
            bookings. Please follow the onboarding process to get started.
          </Text>
          <Button color="orange" onClick={() => navigate("/settings")} mt="md">
            Go to Onboarding
          </Button>
        </Alert>
      </Box>
    );
  }

  if (!auth.isPending && auth.companyStatus === "pending") {
    return (
      <Box
        style={{
          padding: "28px",
          minHeight: "calc(100dvh - var(--app-shell-header-height, 56px))",
          display: "flex",
          flexDirection: "column",
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        <Alert
          color="orange"
          icon={<AlertCircle size={20} />}
          radius="md"
          style={{ maxWidth: "500px" }}
          mb="lg"
        >
          <Text size="lg" fw={600} mb="md">
            Awaiting Approval
          </Text>
          <Text size="sm" mb="md">
            Your company is awaiting EDR approval. Creating bookings is disabled
            until your company has been approved.
          </Text>
          <Button color="orange" onClick={() => navigate("/bookings")} mt="md">
            Back to Bookings
          </Button>
        </Alert>
      </Box>
    );
  }

  const persistAndPriceMutation = useMutation({
    mutationFn: async ({
      payload,
      mode,
      existingBookingId,
    }: {
      payload: CreateBookingPayload;
      mode: PriceModalMode;
      existingBookingId: string | null;
    }) => {
      const documents = (form.getValues("documents") ?? {}) as BookingDocuments;
      let bookingId = existingBookingId;

      if (bookingId) {
        await api.bookings.update.call({ id: bookingId, dto: payload, documents });
      } else {
        const booking = await api.bookings.create.call({ payload, documents });
        bookingId = booking.id;
      }

      const pricing = await api.bookings.generatePrice.call({ id: bookingId });
      return { bookingId, pricing, mode };
    },
    onSuccess: ({ bookingId, pricing, mode }) => {
      setPriceBookingId(bookingId);
      setPricingData(pricing);
      setPriceModalMode(mode);
      queryClient.invalidateQueries({ queryKey: api.bookings.list.queryKey() });
    },
  });

  const confirmMutation = useMutation({
    mutationFn: async () => {
      if (!priceBookingId) throw new Error("No booking to confirm");
      return api.bookings.submit.call({ id: priceBookingId });
    },
    onSuccess: (result) => {
      if (result.priceChanged) {
        setPriceChangeResult(result);
        return;
      }
      // Booking submitted — the saved wizard draft is no longer needed.
      clearBookingDraft();
      setPriceModalMode(null);
      queryClient.invalidateQueries({ queryKey: api.bookings.list.queryKey() });
      // A partial-wagon booking is parked until a partner is found — explain the
      // wait in a modal before sending the customer to the detail page.
      if (result.status === "PENDING_CONSOLIDATION") {
        setConsolidationPending(true);
        return;
      }
      navigate(`/bookings/${priceBookingId}`);
    },
  });

  const confirmSubmitMutation = useMutation({
    mutationFn: async () => {
      if (!priceBookingId) throw new Error("No booking to confirm");
      return api.bookings.confirmSubmit.call({ id: priceBookingId });
    },
    onSuccess: (result) => {
      // Booking submitted — the saved wizard draft is no longer needed.
      clearBookingDraft();
      setPriceChangeResult(null);
      setPriceModalMode(null);
      queryClient.invalidateQueries({ queryKey: api.bookings.list.queryKey() });
      if (result.status === "PENDING_CONSOLIDATION") {
        setConsolidationPending(true);
        return;
      }
      navigate(`/bookings/${priceBookingId}`);
    },
  });

  // Customer rejects the priced booking → it becomes REJECTED (terminal) and the
  // customer starts a fresh booking.
  const rejectMutation = useMutation({
    mutationFn: async () => {
      if (!priceBookingId) throw new Error("No booking to reject");
      return api.bookings.reject.call({ id: priceBookingId });
    },
    onSuccess: () => {
      // The customer rejected this booking and will start over — drop the draft.
      clearBookingDraft();
      setPriceModalMode(null);
      queryClient.invalidateQueries({ queryKey: api.bookings.list.queryKey() });
      navigate("/bookings");
    },
  });

  const abortMutation = useMutation({
    mutationFn: async (reason: string) => {
      if (!priceBookingId) throw new Error("No booking to abort");
      await api.bookings.cancel.call({ id: priceBookingId, reason });
    },
    onSuccess: () => {
      setCancelDialogOpen(false);
      navigate("/bookings");
    },
  });

  const form = useForm<BookingFormInputValues, any, BookingFormValues>({
    defaultValues: initialBookingFormValues,
    resolver: zodResolver(bookingFormSchema),
    mode: "onChange",
  });

  // Persist the in-progress wizard to localStorage so a refresh doesn't lose it.
  // The explicit "New Booking" entry points navigate with state.fresh = true to
  // force a clean start; a plain refresh (no state) resumes the saved draft.
  const location = useLocation();
  const startFresh = (location.state as { fresh?: boolean } | null)?.fresh === true;
  useBookingDraft({
    form,
    step,
    setStep,
    fresh: startFresh,
  });

  const originYard = form.watch("originYard");
  const destinationYard = form.watch("destinationYard");
  const operationType = form.watch("operationType");
  const watchedCargoKind = form.watch("cargoType");
  const watchedContainers = form.watch("containers");
  const watchedCargoTypePath = form.watch("cargoTypePath");
  const watchedScheduledDate = form.watch("scheduledDate");
  const isGeneralContractBooking =
    form.watch("bookingType") === "general_contract";

  // Wagon-TYPE availability gate: which days have a departure whose train can
  // physically carry the selected cargo/container type. Quantity is NOT part
  // of this gate — an oversized booking is accepted and gets a partial split
  // offer later. Only selectable days reach the UI; no capacity counts.
  const gateContainerTypeIds = useMemo(() => {
    if (watchedCargoKind !== "container") return [];
    const groups = referenceData?.containers ?? [];
    const ids = new Set<string>();
    for (const line of watchedContainers ?? []) {
      if (!line?.containerType) continue;
      for (const group of groups) {
        const ct = group.types.find((t) => t.name === line.containerType);
        if (ct) ids.add(ct.id);
      }
    }
    return [...ids];
  }, [watchedCargoKind, watchedContainers, referenceData]);
  const gateCargoTypeId =
    watchedCargoKind === "bulk" ? watchedCargoTypePath?.[1] : undefined;
  const gateReady =
    !isGeneralContractBooking &&
    !!originYard &&
    !!destinationYard &&
    (watchedCargoKind === "bulk"
      ? !!gateCargoTypeId
      : gateContainerTypeIds.length > 0);
  const availableDaysQuery = useQuery(
    api.bookings.getAvailableDaysForCargo.queryOptions({
      input: {
        originYardId: originYard,
        destinationYardId: destinationYard,
        freightType: watchedCargoKind === "bulk" ? "BULK" : "CONTAINER",
        cargoTypeId: gateCargoTypeId || undefined,
        containerTypeIds: gateContainerTypeIds,
      },
      enabled: gateReady,
    }),
  );
  const availableBookingDays = gateReady ? availableDaysQuery.data : undefined;
  // Block submit only on a POSITIVE answer that the picked day has no wagon
  // for this cargo — a loading/failed availability lookup never bricks the
  // wizard (the backend re-checks at the binding step anyway).
  const noWagonForSelectedDay = Boolean(
    availableBookingDays &&
      watchedScheduledDate &&
      !availableBookingDays.includes(watchedScheduledDate),
  );

  // The estimated shipment date lives in the Route step now; for general
  // contracts that date field is simply hidden there (the date is chosen per
  // order against the contract later). No dedicated schedule step remains.
  const visibleSteps = useMemo(() => STEPS, []);
  const visibleStepIds = useMemo<number[]>(
    () => visibleSteps.map((s) => s.id),
    [visibleSteps],
  );
  const currentStepIndex = visibleStepIds.indexOf(step);
  const isLastStep = currentStepIndex === visibleStepIds.length - 1;
  const isFirstStep = currentStepIndex <= 0;
  const goToStep = (delta: number) => {
    const idx = visibleStepIds.indexOf(step);
    const nextIdx = Math.min(
      visibleStepIds.length - 1,
      Math.max(0, idx + delta),
    );
    setStep(visibleStepIds[nextIdx]);
  };

  const direction = useMemo(() => {
    const origin = referenceData?.yard.find((y) => y.id === originYard);
    const destination = referenceData?.yard.find(
      (y) => y.id === destinationYard,
    );

    // Country-based derivation is authoritative when both yards are tagged, but
    // returns null if a yard is unselected or lacks a country (e.g. intercity
    // yards with no country set → DOMESTIC). The operation chosen in step 0 is
    // the user's explicit intent, so fall back to it to guarantee a valid value
    // and avoid posting tradeDirection: null (which the API rejects with @IsIn).
    return (
      getRouteDirection(origin, destination) ??
      (operationType ? operationToTradeDirection(operationType) : null)
    );
  }, [originYard, destinationYard, operationType, referenceData]);

  // The company's onboarded profile types — drives which operations are offered
  // and which profile each operation stamps the booking to.
  const profileTypes = useMemo(
    () => (auth.company?.company?.companyProfiles ?? []).map((p) => p.type),
    [auth.company],
  );

  // Operations the customer may book, gated by the company's onboarded profiles.
  const allowedOperations = useMemo<OperationType[]>(
    () => allowedOperationsForProfiles(profileTypes),
    [profileTypes],
  );

  // The company_profile this booking belongs to, derived from the selected
  // operation: import→importer, export→exporter, and the "as FF" variants →
  // freight_forwarder. A forwarder booking is pinned explicitly on submit
  // (companyProfileId) because trade direction alone can't distinguish it.
  const selectedProfile = useMemo(() => {
    const profiles = auth.company?.company?.companyProfiles ?? [];
    if (!operationType) return profiles[0] ?? null;
    const targetType = operationToProfileType(operationType, profileTypes);
    return profiles.find((p) => p.type === targetType) ?? profiles[0] ?? null;
  }, [auth.company, operationType, profileTypes]);

  // Onboarding documents for the resolved profile — shown read-only in the
  // Documents step and attached to the booking on submit by the backend.
  const onboardingDocs = useMemo(
    () => selectedProfile?.licenseFiles ?? [],
    [selectedProfile],
  );

  const [pricingData, setPricingData] = useState<GeneratePriceResponse | null>(
    null,
  );
  const [priceBookingId, setPriceBookingId] = useState<string | null>(null);
  const [priceModalMode, setPriceModalMode] = useState<PriceModalMode | null>(
    null,
  );
  const [priceChangeResult, setPriceChangeResult] =
    useState<SubmitBookingResponse | null>(null);
  const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
  const [cancelReason, setCancelReason] = useState("");
  // Shown when a submitted booking is parked waiting for a consolidation partner.
  const [consolidationPending, setConsolidationPending] = useState(false);

  async function handleContinue() {
    const valid = await form.trigger(stepFields[step], { shouldFocus: true });
    if (!valid) return;

    // Documents (step 6) is read-only — the active profile's onboarding files are
    // attached automatically, so there is nothing to validate here.
    goToStep(1);
  }

  function buildApiPayload(data: BookingFormValues): CreateBookingPayload {
    if (data.contractType === "renewal" && !data.previousContractRef) {
      form.setError("previousContractRef", {
        type: "manual",
        message: "Select a previous contract reference.",
      });
      setStep(1);
      throw new Error("Validation failed");
    }

    const cargoTree = referenceData?.cargo_type ?? [];
    const containerGroups = referenceData?.containers ?? [];

    const findContainerTypeId = (name: string): string => {
      for (const group of containerGroups) {
        const ct = group.types.find((t) => t.name === name);
        if (ct) return ct.id;
      }
      return "";
    };

    const cargoTypePath = data.cargoTypePath ?? [];
    const childId = cargoTypePath[1];

    const bulkChild = cargoTree
      .flatMap((g) => g.children ?? [])
      .find((c) => c.id === childId);

    // Bulk amount lives in cargoTotalWeightVgm — tons (estimated) or a whole
    // item count, depending on the commodity's unit_of_measure. Item counts are
    // rounded since fractional items are meaningless. Containers carry NO weight
    // at the wizard — VGM is captured later in operations — so container bookings
    // send 0.
    const isPerItem =
      bulkChild?.unit_of_measure === Freight.CargoUnitOfMeasure.PerItem;
    const isContract = data.bookingType === "general_contract";
    // Both one-time and general contracts take the bulk amount from the cargo
    // step (cargoWeight) — general contracts no longer collect a per-route
    // quantity. Item counts are rounded since fractional items are meaningless.
    const bulkAmountRaw = data.cargoWeight;
    const totalWeight =
      data.cargoType === "container"
        ? 0
        : isPerItem
          ? Math.round(Number(bulkAmountRaw || 0))
          : Number(bulkAmountRaw || 0);

    const cargoTypeId = data.cargoType === "bulk" ? childId : undefined;

    const cargoFreeText =
      data.cargoType === "bulk" ? data.cargoFreeText : undefined;

    const serviceType = referenceData?.service.find(
      (s) => s.id === data.serviceTypeId,
    )!;

    // Pin the profile only for a forwarder booking — trade direction resolves
    // importer/exporter on its own, but can't tell a forwarder apart.
    const forwarderProfileId =
      data.operationType &&
      isForwarderOperation(data.operationType, profileTypes)
        ? selectedProfile?.id
        : undefined;

    return {
      ...(forwarderProfileId ? { companyProfileId: forwarderProfileId } : {}),
      bookingType: isContract
        ? Freight.BookingType.GeneralContract
        : Freight.BookingType.OneTime,
      // The wizard captures a NON-BINDING estimate only — never the binding
      // scheduledDate (that is chosen later at the operation-request step and
      // validated against open departures). General contracts omit even the
      // estimate; the date is chosen per order later.
      ...(isContract || !data.scheduledDate
        ? {}
        : {
            estimatedShipmentDate: new Date(data.scheduledDate).toISOString(),
          }),
      contractType:
        data.contractType.toUpperCase() as CreateBookingPayload["contractType"],
      serviceTypeId: data.serviceTypeId,
      equipmentReturn:
        data.equipmentReturn === "with_return"
          ? "WITH_RETURN"
          : "WITHOUT_RETURN",
      paymentCurrency: data.paymentCurrency,
      originYardId: data.originYard,
      destinationYardId: data.destinationYard,
      tradeDirection: direction!,
      cargoTypeId,
      // Day-level pool: the customer picks only a day (scheduledDate); the batch
      // engine assigns the train, so no trainScheduleId is sent.
      cargoTotalWeightVgm: totalWeight,
      // Booking-level flags drive the HAZARD / REEFER surcharge triggers. For
      // containers they're the OR of the per-container switches; for bulk they
      // come from the cargo-step toggles.
      isHazardous:
        data.cargoType === "container"
          ? data.containers.some((c) => c.isHazardous)
          : data.isHazardous,
      isReefer:
        data.cargoType === "container"
          ? data.containers.some((c) => c.isReefer)
          : data.isRefrigerated,
      // Bulk-only: the hazardous / refrigerated amount in the cargo's unit.
      ...(data.cargoType === "bulk"
        ? {
            bulkHazardousQuantity: data.isHazardous
              ? Number(data.bulkHazardousQty || 0)
              : 0,
            bulkReeferQuantity: data.isRefrigerated
              ? Number(data.bulkReeferQty || 0)
              : 0,
          }
        : {}),
      freightType:
        data.cargoType === "container"
          ? ("CONTAINER" as const)
          : ("BULK" as const),
      containers:
        data.cargoType === "container"
          ? data.containers.map((c) => ({
            containerTypeId: findContainerTypeId(c.containerType),
            quantity: Number(c.qty || 1),
            // Per-line breakdown: how many of this line are hazardous / reefer.
            hazardousQuantity: c.isHazardous ? Number(c.hazardousQty || 0) : 0,
            reeferQuantity: c.isReefer ? Number(c.reeferQty || 0) : 0,
            // Weight (VGM) is not collected at the wizard — captured in operations.
            vgmPerUnitTons: 0,
          }))
          : [],
      ...(data.previousContractRef
        ? { previousContractId: data.previousContractRef }
        : {}),
      ...(data.contractType === "renewal" && data.previousContractRef
        ? { pnrCode: data.previousContractRef }
        : {}),
      ...(serviceType.includesFirstMile && data.firstMile.enabled
        ? {
            firstMilePickupAddress: data.firstMile.pickUpAddress,
            firstMilePickupLat: data.firstMile.lat ?? undefined,
            firstMilePickupLng: data.firstMile.lng ?? undefined,
          }
        : {}),
      ...(serviceType.includesLastMile && data.lastMile.enabled
        ? {
            lastMileDeliveryAddress: data.lastMile.deliveryAddress,
            lastMileDeliveryLat: data.lastMile.lat ?? undefined,
            lastMileDeliveryLng: data.lastMile.lng ?? undefined,
          }
        : {}),
      // Customs clearing: only when the service offers it and the customer opted
      // in; the agent name is required by the form in that case.
      ...(serviceType.includesCustoms && data.customsClearingEnabled
        ? {
            customsClearingEnabled: true,
            customsClearingAgent: data.customsClearingAgent,
          }
        : { customsClearingEnabled: false }),
      ...(cargoFreeText ? { cargoFreeText } : {}),
      // A general contract covers exactly ONE route — the same single
      // origin→destination pair as a one-time booking (multi-route on bookings
      // was dropped). The contracted quantity lives in a single shared pool
      // (container quantities / bulk total), drawn down per order.
      ...(isContract
        ? {
            routes: [
              {
                originYardId: data.originYard,
                destinationYardId: data.destinationYard,
              },
            ],
          }
        : {}),
    };
  }

  const handleSaveDraft = form.handleSubmit((data) => {
    try {
      const apiPayload = buildApiPayload(data);
      persistAndPriceMutation.mutate({
        payload: apiPayload,
        mode: "draft",
        existingBookingId: priceBookingId,
      });
    } catch {
      // validation error already handled
    }
  });

  const handleSubmitBooking = form.handleSubmit((data) => {
    // Documents are reused from onboarding and attached by the backend, so there
    // is no upload requirement to enforce here.
    try {
      const apiPayload = buildApiPayload(data);
      persistAndPriceMutation.mutate({
        payload: apiPayload,
        mode: "submit",
        existingBookingId: priceBookingId,
      });
    } catch {
      // validation error already handled
    }
  });

  const isPricing =
    persistAndPriceMutation.isPending || confirmMutation.isPending;

  function closePriceModal() {
    setPriceModalMode(null);
    if (priceModalMode === "draft" && priceBookingId) {
      navigate(`/bookings/${priceBookingId}`);
    }
  }

  function handleDraftModalOk() {
    setPriceModalMode(null);
    if (priceBookingId) navigate(`/bookings/${priceBookingId}`);
  }

  return (
    <Box
      style={{
        padding: "28px 0 0",
        minHeight: "calc(100dvh - var(--app-shell-header-height, 56px))",
        display: "flex",
        flexDirection: "column",
      }}
    >
      {/* ── Page header ─────────────────────────────────────────────────── */}
      <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" }}
          >
            New Booking
          </Title>
          <Text size="sm" c="edr-muted" mt={4}>
            Fill in the details below to book your freight shipment.
          </Text>
        </Box>
        <Button
          variant="default"
          radius="md"
          leftSection={<ChevronLeft size={16} />}
          onClick={() => navigate("/bookings")}
        >
          Back to Bookings
        </Button>
      </Group>
      <form
        id="new-booking-form"
        className="flex flex-col"
        style={{ flex: 1 }}
        onSubmit={(e) => e.preventDefault()}
      >
        <Box flex={1} p="24px">
          <Box mb="lg">
            <StepIndicator step={step} steps={visibleSteps} />
          </Box>

          {persistAndPriceMutation.isError && (
            <Alert
              color="red"
              icon={<AlertCircle size={16} />}
              radius="md"
              mb="lg"
            >
              <Text size="sm" fw={600}>
                Failed to save booking or generate price
              </Text>
              <Text size="sm" mt={4} c="red.7">
                {persistAndPriceMutation.error instanceof Error
                  ? persistAndPriceMutation.error.message
                  : "An unexpected error occurred. Please try again."}
              </Text>
            </Alert>
          )}

          {step === 0 && (
            <Step0OperationType
              form={form}
              allowedOperations={allowedOperations}
            />
          )}
          {step === 1 && (
            <Step1ContractType form={form} referenceData={referenceData} />
          )}
          {step === 2 && (
            <Step2ServiceType referenceData={referenceData} form={form} />
          )}
          {step === 3 && (
            <Step5CargoDetails
              form={form}
              referenceData={referenceData}
              isLoading={refDataLoading}
            />
          )}
          {step === 4 && (
            <Step4Route
              form={form}
              referenceData={referenceData}
              isLoading={refDataLoading}
              availableDays={availableBookingDays}
              noWagonForSelectedDay={noWagonForSelectedDay}
            />
          )}
          {step === 6 && <StepDocuments form={form} />}
          {step === 7 && (
            <Step8Review
              form={form}
              setStep={setStep}
              direction={direction!}
              referenceData={referenceData}
              onboardingDocs={onboardingDocs}
              onSaveDraft={handleSaveDraft}
              onSubmit={handleSubmitBooking}
              saveDraftPending={
                persistAndPriceMutation.isPending &&
                persistAndPriceMutation.variables?.mode === "draft"
              }
              submitPending={
                persistAndPriceMutation.isPending &&
                persistAndPriceMutation.variables?.mode === "submit"
              }
              noWagonForSelectedDay={noWagonForSelectedDay}
            />
          )}
        </Box>

        {/* Navigation footer */}
        <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)",
            WebkitBackdropFilter: "blur(14px)",
            padding: "16px 24px",
            marginTop: "auto",
          }}
        >
          <Group justify="space-between" className="mx-auto max-w-4xl">
            <Button
              type="button"
              variant="default"
              radius="md"
              leftSection={<ChevronLeft size={16} />}
              onClick={() => goToStep(-1)}
              disabled={isFirstStep}
            >
              Back
            </Button>

            {!isLastStep ? (
              <Button
                type="button"
                color="edr-green"
                radius="md"
                rightSection={<ChevronRight size={16} />}
                onClick={handleContinue}
              >
                Continue
              </Button>
            ) : (
              <Button
                type="button"
                color="edr-green"
                radius="md"
                leftSection={<Send size={16} />}
                onClick={handleSubmitBooking}
                loading={isPricing}
                disabled={noWagonForSelectedDay}
              >
                Submit
              </Button>
            )}
          </Group>
        </Box>
      </form>

      <Modal
        opened={priceModalMode !== null && pricingData !== null}
        onClose={closePriceModal}
        title={
          <Text fw={700}>
            {priceModalMode === "submit"
              ? "Confirm booking submission"
              : "Draft saved — price estimate"}
          </Text>
        }
        radius="lg"
        centered
        size="md"
      >
        {pricingData && (
          <Stack gap="md">
            <Text size="sm" c="dimmed">
              {priceModalMode === "submit"
                ? "Review your total price below. Confirm to submit for EDR staff review, edit & regenerate to change details and re-price, or reject to discard this booking."
                : "Your booking has been saved as a draft. Here is your estimated total price."}
            </Text>
            {pricingData.lineItems.length > 0 && (
              <Box
                p="md"
                style={{
                  borderRadius: 16,
                  border: "1px solid var(--mantine-color-edr-border-0)",
                  background: "#fff",
                }}
              >
                <Text
                  size="xs"
                  fw={700}
                  tt="uppercase"
                  c="edr-muted"
                  mb="xs"
                  style={{ letterSpacing: "0.06em" }}
                >
                  Price breakdown
                </Text>
                <Stack gap={10}>
                  {pricingData.lineItems.map((item) => {
                    const hasUnit =
                      item.unitAmount != null &&
                      item.quantity != null &&
                      item.quantity > 0;
                    return (
                      <Group
                        key={item.code}
                        justify="space-between"
                        align="flex-start"
                        wrap="nowrap"
                        gap="sm"
                      >
                        <Box style={{ minWidth: 0 }}>
                          <Text size="sm" c="#10202F" fw={500}>
                            {item.description}
                          </Text>
                          {hasUnit && (
                            <Text size="xs" c="dimmed">
                              {item.quantity!.toLocaleString()} ×{" "}
                              {item.unitAmount!.toLocaleString()} {item.currency}
                              {item.unit
                                ? ` · ${formatPriceUnit(item.unit)}`
                                : ""}
                            </Text>
                          )}
                        </Box>
                        <Text size="sm" fw={600} c="#10202F" style={{ whiteSpace: "nowrap" }}>
                          {item.amount.toLocaleString()} {item.currency}
                        </Text>
                      </Group>
                    );
                  })}
                </Stack>
              </Box>
            )}
            <Box
              p="lg"
              style={{
                borderRadius: 16,
                border: "1px solid var(--mantine-color-edr-border-0)",
                background:
                  "linear-gradient(135deg, #F4FBF7 0%, #FFFFFF 70%)",
              }}
            >
              <Text size="xs" fw={700} tt="uppercase" c="edr-green" style={{ letterSpacing: "0.06em" }}>
                Total price
              </Text>
              <Text fw={800} fz={30} c="#10202F" mt={4}>
                {pricingData.totalAmount.toLocaleString()}{" "}
                <Text span fz={18} fw={700} c="edr-muted">
                  {pricingData.currency}
                </Text>
              </Text>
            </Box>
            {pricingData.warnings.length > 0 && (
              <Text size="xs" c="orange.7" p="xs" className="rounded bg-orange-50">
                {pricingData.warnings.join(", ")}
              </Text>
            )}
            <Group justify="flex-end" gap="sm" mt="md">
              {priceModalMode === "submit" ? (
                <>
                  <Button
                    variant="outline"
                    color="red"
                    radius="md"
                    leftSection={<XCircle size={16} />}
                    onClick={() => rejectMutation.mutate()}
                    loading={rejectMutation.isPending}
                    disabled={confirmMutation.isPending}
                  >
                    Reject
                  </Button>
                  {/* Go back to the wizard to change details, then Submit again to
                      regenerate the price. The draft booking is kept (priceBookingId
                      stays set), so re-submitting updates it instead of creating a
                      new one. */}
                  <Button
                    variant="default"
                    radius="md"
                    leftSection={<ChevronLeft size={16} />}
                    onClick={() => setPriceModalMode(null)}
                    disabled={
                      rejectMutation.isPending || confirmMutation.isPending
                    }
                  >
                    Edit &amp; regenerate
                  </Button>
                  <Button
                    color="edr-green"
                    radius="md"
                    leftSection={<Check size={16} />}
                    onClick={() => confirmMutation.mutate()}
                    loading={confirmMutation.isPending}
                    disabled={rejectMutation.isPending}
                  >
                    Confirm &amp; submit
                  </Button>
                </>
              ) : (
                <Button color="edr-green" radius="md" onClick={handleDraftModalOk}>
                  OK
                </Button>
              )}
            </Group>
          </Stack>
        )}
      </Modal>

      <Modal
        opened={priceChangeResult !== null}
        onClose={() => setPriceChangeResult(null)}
        title={<Text fw={700}>Price has changed</Text>}
        radius="lg"
        centered
      >
        {priceChangeResult && (
          <Stack gap="md">
            <Text size="sm" c="dimmed">
              {priceChangeResult.message ??
                "The booking price has been updated. Confirm to submit with the new total."}
            </Text>
            {priceChangeResult.previousTotalAmount !== undefined && (
              <Group justify="space-between">
                <Text size="sm" c="dimmed">
                  Previous total
                </Text>
                <Text size="sm" td="line-through">
                  {priceChangeResult.previousTotalAmount.toLocaleString()}{" "}
                  {priceChangeResult.currency}
                </Text>
              </Group>
            )}
            <Group justify="space-between">
              <Text fw={700}>New total</Text>
              <Text fw={800} c="edr-green">
                {priceChangeResult.totalAmount.toLocaleString()}{" "}
                {priceChangeResult.currency}
              </Text>
            </Group>
            {priceChangeResult.lineItems &&
              priceChangeResult.lineItems.length > 0 && (
                <Box
                  p="md"
                  style={{
                    borderRadius: 16,
                    border: "1px solid var(--mantine-color-edr-border-0)",
                    background: "#fff",
                  }}
                >
                  <Text
                    size="xs"
                    fw={700}
                    tt="uppercase"
                    c="edr-muted"
                    mb="xs"
                    style={{ letterSpacing: "0.06em" }}
                  >
                    Price breakdown
                  </Text>
                  <Stack gap={10}>
                    {priceChangeResult.lineItems.map((item) => {
                      const hasUnit =
                        item.unitAmount != null &&
                        item.quantity != null &&
                        item.quantity > 0;
                      return (
                        <Group
                          key={item.code}
                          justify="space-between"
                          align="flex-start"
                          wrap="nowrap"
                          gap="sm"
                        >
                          <Box style={{ minWidth: 0 }}>
                            <Text size="sm" c="#10202F" fw={500}>
                              {item.description}
                            </Text>
                            {hasUnit && (
                              <Text size="xs" c="dimmed">
                                {item.quantity!.toLocaleString()} ×{" "}
                                {item.unitAmount!.toLocaleString()}{" "}
                                {item.currency}
                                {item.unit
                                  ? ` · ${formatPriceUnit(item.unit)}`
                                  : ""}
                              </Text>
                            )}
                          </Box>
                          <Text
                            size="sm"
                            fw={600}
                            c="#10202F"
                            style={{ whiteSpace: "nowrap" }}
                          >
                            {item.amount.toLocaleString()} {item.currency}
                          </Text>
                        </Group>
                      );
                    })}
                  </Stack>
                </Box>
              )}
            <Group justify="flex-end" gap="sm">
              <Button
                variant="default"
                radius="md"
                onClick={() => setPriceChangeResult(null)}
              >
                Cancel
              </Button>
              <Button
                color="edr-green"
                radius="md"
                loading={confirmSubmitMutation.isPending}
                onClick={() => confirmSubmitMutation.mutate()}
              >
                Confirm & submit
              </Button>
            </Group>
          </Stack>
        )}
      </Modal>

      <Modal
        opened={consolidationPending}
        onClose={() => {
          setConsolidationPending(false);
          if (priceBookingId) navigate(`/bookings/${priceBookingId}`);
        }}
        title={
          <Group gap={10} wrap="nowrap">
            <div
              className="flex shrink-0 items-center justify-center rounded-[11px] border border-[#F4D9A8]"
              style={{ width: 38, height: 38, backgroundColor: "#FDF3E0", color: "#C77F12" }}
            >
              <Link2 size={20} />
            </div>
            <Text fw={700}>Waiting to share a wagon</Text>
          </Group>
        }
        radius="lg"
        centered
        size="md"
      >
        <Stack gap="md">
          <Text size="sm" c="dimmed">
            Your booking is submitted, but your cargo only fills part of a wagon.
            We&apos;re pairing it with another shipment on the same route to share
            the space.
          </Text>
          <Box
            p="md"
            style={{
              borderRadius: 12,
              border: "1px solid #F4D9A8",
              background: "#FDF3E0",
            }}
          >
            <Text size="sm" fw={600} c="#10202F">
              What happens next
            </Text>
            <Text size="sm" mt={4} c="#7A6A4E">
              As soon as a matching shipment is found, your booking continues
              automatically — you&apos;ll be notified, and no action is needed
              from you until then. Your acceptance, approval and contract stay
              independent and yours alone.
            </Text>
          </Box>
          <Group justify="flex-end" gap="sm">
            <Button
              color="edr-green"
              radius="md"
              onClick={() => {
                setConsolidationPending(false);
                if (priceBookingId) navigate(`/bookings/${priceBookingId}`);
              }}
            >
              Got it
            </Button>
          </Group>
        </Stack>
      </Modal>

      <Modal
        opened={cancelDialogOpen}
        onClose={() => setCancelDialogOpen(false)}
        title={<Text fw={700}>Abort booking</Text>}
        radius="lg"
        centered
      >
        <Stack gap="md">
          <Text size="sm" c="dimmed">
            Are you sure you want to abort this booking? This action cannot be
            undone.
          </Text>
          <TextInput
            label="Reason for cancellation (optional)"
            placeholder="e.g. Change of plans…"
            value={cancelReason}
            onChange={(e) => setCancelReason(e.currentTarget.value)}
            radius="md"
            data-autofocus
          />
          <Group justify="flex-end" gap="sm">
            <Button
              variant="default"
              radius="md"
              onClick={() => setCancelDialogOpen(false)}
            >
              Keep editing
            </Button>
            <Button
              color="red"
              radius="md"
              onClick={() =>
                abortMutation.mutate(
                  cancelReason.trim() || "Aborted by customer",
                )
              }
              disabled={abortMutation.isPending}
              loading={abortMutation.isPending}
              leftSection={
                !abortMutation.isPending ? <XCircle size={15} /> : undefined
              }
            >
              Yes, abort
            </Button>
          </Group>
        </Stack>
      </Modal>
    </Box>
  );
}
