import { api } from "@/services/api";
import { Freight } from "@edr/types";
import type {
  CreateContractPayload,
  ContractDocuments as ServiceContractDocuments,
  GenerateContractPriceResponse,
  SubmitContractResponse,
} from "@/services/contracts.service";
import { zodResolver } from "@hookform/resolvers/zod";
import {
  Alert,
  Box,
  Button,
  FileInput,
  Group,
  Modal,
  Stack,
  Text,
  Title,
} from "@mantine/core";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { isAxiosError } from "axios";
import {
  AlertCircle,
  Check,
  ChevronLeft,
  ChevronRight,
  Send,
  Upload,
  XCircle,
} from "lucide-react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import {
  Navigate,
  useLocation,
  useNavigate,
  useParams,
} from "react-router-dom";
import useAuth from "@/hooks/useAuth";
import {
  CONTAINER_SIZES,
  CONTRACT_STEPS,
  ContractFormInputValues,
  contractFormSchema,
  contractStepFields,
  initialContractFormValues,
  OPERATION_TYPES,
  type ContractFormValues,
  type OperationType,
} from "./new-contract-form/schema";
import {
  getRouteDirection,
  isForwarderOperation,
  operationToProfileType,
  operationToTradeDirection,
} from "./new-contract-form/helpers";
import { contractToFormValues } from "./new-contract-form/contractToForm";
import { PROFILE_TYPE_LABELS } from "@/constants/profileMode";
import type { ProfileTypeValue } from "@/services/companies.service";
import { StepIndicator } from "./new-contract-form/StepIndicator";
import {
  clearContractDraft,
  useContractDraft,
} from "./new-contract-form/useContractDraft";
import {
  Step1ContractType,
  Step2ServiceType,
  Step3CargoScope,
  Step4Route,
  Step8Review,
} from "./new-contract-form/steps";
import {
  NotFinalPriceNotice,
  StepCard,
  StepHeader,
} from "./new-contract-form/shared";
import { formatRateUnit } from "./new-contract-form/unit-rates";

type PriceModalMode = "submit" | "draft";

/**
 * The contract wizard, used both to create a new contract and — in `edit` mode —
 * to continue an unsubmitted DRAFT or edit & resubmit a contract staff returned
 * with CHANGES_REQUESTED. Edit mode hydrates the form from the saved contract,
 * lets the customer change any term, then runs the same update → price →
 * submit flow. Same 3 steps as create — there is no separate documents step.
 */
export default function NewContractPage({
  mode = "create",
}: {
  mode?: "create" | "edit";
}) {
  const navigate = useNavigate();
  const queryClient = useQueryClient();
  const { id: editId } = useParams<{ id: string }>();
  const isEdit = mode === "edit" && Boolean(editId);
  const [step, setStep] = useState(0);
  const auth = useAuth();
  const { data: referenceData, isLoading: refDataLoading } = useQuery(
    api.bookings.referenceData.queryOptions(),
  );
  const { data: editContract } = useQuery({
    ...api.contracts.get.queryOptions({ input: { id: editId ?? "" } }),
    enabled: isEdit,
  });
  // Contract creation is gated on profile approval, same as bookings.
  if (!auth.isPending && auth.company && !auth.canBook) {
    return <Navigate to="/contracts" replace />;
  }

  if (!auth.isPending && !auth.company) {
    return (
      <GateNotice
        title="Complete Your Company Setup"
        body="You need to complete your company onboarding before you can create contracts."
        actionLabel="Go to Onboarding"
        onAction={() => navigate("/settings")}
      />
    );
  }

  if (!auth.isPending && auth.companyStatus === "pending") {
    return (
      <GateNotice
        title="Awaiting Approval"
        body="Your company is awaiting EDR approval. Creating contracts is disabled until your company has been approved."
        actionLabel="Back to Contracts"
        onAction={() => navigate("/contracts")}
      />
    );
  }

  // A suspended/blacklisted account must be told its real status — falling
  // through to the wizard here only to fail at submit read as "pending" before.
  if (
    !auth.isPending &&
    (auth.companyStatus === "suspended" || auth.companyStatus === "blacklisted")
  ) {
    return (
      <GateNotice
        title={
          auth.companyStatus === "suspended"
            ? "Account Suspended"
            : "Account Blacklisted"
        }
        body={
          auth.companyStatus === "suspended"
            ? "Your company account has been suspended by EDR staff, so new contracts are disabled. Please contact EDR support for details."
            : "Your company account has been blacklisted, so new contracts are disabled. Please contact EDR support."
        }
        actionLabel="Back to Contracts"
        onAction={() => navigate("/contracts")}
      />
    );
  }

  // Profile changes pending review lock out new contract creation too.
  if (!auth.isPending && auth.isUnderReview) {
    return (
      <GateNotice
        title="Profile Changes Under Review"
        body="Your recent profile changes are awaiting administrator approval. Creating contracts is paused until the review is complete."
        actionLabel="Back to Contracts"
        onAction={() => navigate("/contracts")}
      />
    );
  }

  const [pricingData, setPricingData] =
    useState<GenerateContractPriceResponse | null>(null);
  // In edit mode the contract already exists, so seed its id — this makes
  // persistAndPriceMutation take the UPDATE branch instead of creating anew.
  const [priceContractId, setPriceContractId] = useState<string | null>(
    isEdit ? (editId ?? null) : null,
  );
  const [priceModalMode, setPriceModalMode] = useState<PriceModalMode | null>(
    null,
  );
  const [priceChangeResult, setPriceChangeResult] =
    useState<SubmitContractResponse | null>(null);

  const persistAndPriceMutation = useMutation({
    mutationFn: async ({
      payload,
      mode,
      existingContractId,
    }: {
      payload: CreateContractPayload;
      mode: PriceModalMode;
      existingContractId: string | null;
    }) => {
      const documents = (form.getValues("documents") ??
        {}) as ServiceContractDocuments;
      let contractId = existingContractId;

      if (contractId) {
        await api.contracts.update.call({
          id: contractId,
          dto: payload,
          documents,
        });
      } else {
        const contract = await api.contracts.create.call({
          payload,
          documents,
        });
        contractId = contract.id;
      }

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

  // 422 from generate-price = a required rate isn't configured for the chosen
  // route — the customer can't fix it, so it's surfaced as a blocking modal.
  const rateNotConfigured =
    isAxiosError(persistAndPriceMutation.error) &&
    persistAndPriceMutation.error.response?.status === 422;

  // 409 from create = an active contract already covers this service type +
  // route for this customer — surfaced as a blocking modal, same as above.
  const duplicateContract =
    isAxiosError(persistAndPriceMutation.error) &&
    persistAndPriceMutation.error.response?.status === 409;
  // The API bakes the existing contract's reference + valid-until date into
  // this message (global exception filter drops any other response fields).
  const duplicateContractMessage =
    (isAxiosError(persistAndPriceMutation.error) &&
      (persistAndPriceMutation.error.response?.data as { message?: string })
        ?.message) ||
    "An active contract already exists for this service type and route.";

  const confirmMutation = useMutation({
    mutationFn: async () => {
      if (!priceContractId) throw new Error("No contract to confirm");
      return api.contracts.submit.call({ id: priceContractId });
    },
    onSuccess: (result) => {
      if (result.priceChanged) {
        setPriceChangeResult(result);
        return;
      }
      clearContractDraft();
      setPriceModalMode(null);
      queryClient.invalidateQueries({
        queryKey: api.contracts.list.queryKey(),
      });
      navigate("/contracts");
    },
  });

  const confirmSubmitMutation = useMutation({
    mutationFn: async () => {
      if (!priceContractId) throw new Error("No contract to confirm");
      return api.contracts.confirmSubmit.call({ id: priceContractId });
    },
    onSuccess: () => {
      clearContractDraft();
      setPriceChangeResult(null);
      setPriceModalMode(null);
      queryClient.invalidateQueries({
        queryKey: api.contracts.list.queryKey(),
      });
      navigate("/contracts");
    },
  });

  const rejectMutation = useMutation({
    mutationFn: async () => {
      if (!priceContractId) throw new Error("No contract to discard");
      return api.contracts.remove.call({ id: priceContractId });
    },
    onSuccess: () => {
      clearContractDraft();
      setPriceModalMode(null);
      setPriceContractId(null);
      queryClient.invalidateQueries({
        queryKey: api.contracts.list.queryKey(),
      });
      navigate("/contracts");
    },
  });

  const form = useForm<ContractFormInputValues, any, ContractFormValues>({
    defaultValues: initialContractFormValues,
    resolver: zodResolver(contractFormSchema),
    mode: "onChange",
  });

  const location = useLocation();
  const startFresh =
    (location.state as { fresh?: boolean } | null)?.fresh === true;
  useContractDraft({
    form,
    step,
    setStep,
    fresh: startFresh,
    enabled: !isEdit,
  });

  // Edit mode: hydrate the form from the saved contract once both the contract
  // and the reference data (needed to rebuild the cargo-type path) have loaded.
  const hydratedRef = useRef(false);
  useEffect(() => {
    if (!isEdit || hydratedRef.current) return;
    if (!editContract || !referenceData) return;
    hydratedRef.current = true;
    const forwarderProfile =
      (auth.company?.company?.companyProfiles ?? []).find(
        (p) => p.id === editContract.companyProfileId,
      )?.type === "freight_forwarder";
    form.reset(
      contractToFormValues(editContract, referenceData, forwarderProfile),
    );
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isEdit, editContract, referenceData]);

  const originYard = form.watch("originYard");
  const destinationYard = form.watch("destinationYard");
  const operationType = form.watch("operationType");

  // Create and edit share the same 3 steps — documents come along automatically
  // (company profile docs are carried onto the contract by the API on save).
  const visibleSteps = CONTRACT_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,
    );
    return (
      getRouteDirection(origin, destination) ??
      (operationType ? operationToTradeDirection(operationType) : null)
    );
  }, [originYard, destinationYard, operationType, referenceData]);

  // type -> status ("active" = approved | "pending" = awaiting staff approval)
  // for the company's operational profiles. Drives both the select-time gate and
  // the per-option dropdown badges.
  const profileStatusByType = useMemo(() => {
    const m = new Map<string, string>();
    for (const p of auth.company?.company?.companyProfiles ?? [])
      m.set(p.type, p.status);
    return m;
  }, [auth.company]);
  // Full profile per type, so the awaiting/rejected modal can show the reviewer
  // note and offer a reapply for a rejected role.
  const profileByType = useMemo(() => {
    const m = new Map<
      string,
      { id: string; status: string; reviewNote?: string | null }
    >();
    for (const p of auth.company?.company?.companyProfiles ?? [])
      m.set(p.type, { id: p.id, status: p.status, reviewNote: p.reviewNote });
    return m;
  }, [auth.company]);
  const profileTypes = useMemo(
    () => [...profileStatusByType.keys()],
    [profileStatusByType],
  );

  // All operation types are always selectable. Picking one the company has no
  // profile for prompts a license upload that creates the profile on the fly
  // (mirrors the header "Add service" flow); picking one backed by a not-yet-
  // approved profile is blocked with an "awaiting approval" notice.
  const allowedOperations = useMemo<OperationType[]>(
    () => [...OPERATION_TYPES],
    [],
  );

  // Approval state of the profile each operation maps to — used for the dropdown
  // badges. Intercity rides any customer profile, so always "approved".
  const operationStatus = useMemo(
    () =>
      (
        op: OperationType,
      ): "approved" | "pending" | "rejected" | "suspended" | "missing" => {
        if (op === "intercity") return "approved";
        const target = operationToProfileType(op, profileTypes);
        const status = profileStatusByType.get(target);
        if (!status) return "missing";
        if (status === "active") return "approved";
        if (status === "rejected") return "rejected";
        // Suspension is per-role: the customer keeps working under their other
        // roles, so this operation must say "suspended", not "pending".
        if (status === "suspended" || status === "blacklisted")
          return "suspended";
        return "pending";
      },
    [profileStatusByType, profileTypes],
  );

  // Create-profile modal state (license upload → createProfile).
  const [createTarget, setCreateTarget] = useState<ProfileTypeValue | null>(
    null,
  );
  const [pendingOperation, setPendingOperation] =
    useState<OperationType | null>(null);
  const [licenseFiles, setLicenseFiles] = useState<File[]>([]);
  const [createError, setCreateError] = useState<string | null>(null);
  // After a license is uploaded the new profile comes back "pending", so the
  // create-profile modal switches to an "awaiting approval" success state.
  const [licenseSubmitted, setLicenseSubmitted] = useState(false);
  // Set when the user picks an operation whose profile exists but isn't approved
  // yet — drives the "awaiting approval" block modal.
  const [pendingApprovalProfile, setPendingApprovalProfile] =
    useState<ProfileTypeValue | null>(null);

  const createProfileMutation = useMutation({
    mutationFn: async ({
      type,
      files,
    }: {
      type: ProfileTypeValue;
      files: File[];
    }) => {
      const res = await auth.createProfile(type, files);
      if (!res.success) {
        throw new Error(res.error?.message ?? "Failed to create profile");
      }
    },
    onSuccess: () => {
      // The new profile comes back "pending", so the user can't proceed under
      // this operation yet: revert the select and switch the modal to its
      // "awaiting approval" success state (kept open until the user dismisses).
      form.setValue("operationType", undefined as never, { shouldDirty: true });
      setLicenseFiles([]);
      setCreateError(null);
      setLicenseSubmitted(true);
    },
    onError: (err) => {
      setCreateError(
        err instanceof Error ? err.message : "Failed to create profile",
      );
    },
  });

  // Resubmit a rejected operational role for approval (from the block modal).
  const reapplyMutation = useMutation({
    mutationFn: async (profileId: string) => {
      const res = await auth.reapplyProfile(profileId);
      if (!res.success) {
        throw new Error(
          res.error?.message ?? "Failed to resubmit for approval",
        );
      }
    },
    onSuccess: () => setPendingApprovalProfile(null),
  });

  const handleOperationSelect = (op: OperationType) => {
    // Intercity (domestic) runs on any existing customer profile — no switch.
    if (op === "intercity") return;
    const target = operationToProfileType(op, profileTypes) as ProfileTypeValue;
    const status = profileStatusByType.get(target);

    if (!status) {
      // Case 3 — no matching profile: collect a license and create one.
      setPendingOperation(op);
      setCreateTarget(target);
      setLicenseFiles([]);
      setCreateError(null);
      setLicenseSubmitted(false);
      return;
    }
    if (status !== "active") {
      // Case 2 — profile exists but isn't approved yet: block + revert the
      // select so an unusable operation is never left chosen.
      setPendingApprovalProfile(target);
      form.setValue("operationType", undefined as never, { shouldDirty: true });
      return;
    }
    // Case 1 — approved: proceed. The profile is resolved from the operation at
    // submit time (a forwarder operation pins it explicitly), so nothing to set.
  };

  const handleCreateProfileConfirm = () => {
    if (!createTarget) return;
    if (licenseFiles.length === 0) {
      setCreateError("Please upload at least one business license file.");
      return;
    }
    createProfileMutation.mutate({ type: createTarget, files: licenseFiles });
  };

  const handleCreateProfileCancel = () => {
    // Roll back the operation selection that triggered the modal.
    if (pendingOperation) {
      form.setValue("operationType", undefined as never, { shouldDirty: true });
    }
    setCreateTarget(null);
    setPendingOperation(null);
    setLicenseFiles([]);
    setCreateError(null);
    setLicenseSubmitted(false);
  };

  // Dismiss the post-submit "awaiting approval" success state. The select was
  // already reverted on success — just close and reset the modal.
  const handleCreateProfileDone = () => {
    setCreateTarget(null);
    setPendingOperation(null);
    setLicenseFiles([]);
    setCreateError(null);
    setLicenseSubmitted(false);
  };

  const createTargetLabel = createTarget
    ? (PROFILE_TYPE_LABELS[createTarget] ?? createTarget)
    : "";

  async function handleContinue() {
    const fields = contractStepFields[step];
    if (fields.length > 0) {
      const valid = await form.trigger(fields, { shouldFocus: true });
      if (!valid) return;
    }
    goToStep(1);
  }

  function buildApiPayload(data: ContractFormValues): CreateContractPayload {
    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 serviceType = referenceData?.service.find(
      (s) => s.id === data.serviceTypeId,
    )!;

    const isContainer = data.cargoType === "container";
    const isGeneral = data.contractKind === "general_contract";

    // Cargo scope rows — no quantities (doc §5.4). Container: ALWAYS both sizes
    // (rates quoted for both; per-booking quantities can zero a size out) and no
    // description — that moved to booking time. Bulk: a single commodity row.
    // Both GENERAL and ONE_TIME are uncapped (quantityCap omitted → NULL): the
    // customer books repeatedly against a GENERAL contract until its validity
    // expires.
    const cargoScope: Freight.CreateContractCargoScopeDto[] = isContainer
      ? CONTAINER_SIZES.map((size) => ({
        containerSize: size,
      }))
      : [
        {
          cargoTypeId: data.cargoTypePath?.[1] || undefined,
          cargoFreeText: data.cargoFreeText || undefined,
        },
      ];

    // Route — a single origin→destination lane, general contracts included.
    const routes: Freight.CreateContractRouteInputDto[] = [
      {
        originYardId: data.originYard,
        destinationYardId: data.destinationYard,
        sortOrder: 0,
      },
    ];

    // Pin the profile only for a forwarder contract (trade direction can't tell
    // a forwarder apart from a direct import/export).
    const forwarderProfileId =
      data.operationType &&
        isForwarderOperation(data.operationType, profileTypes)
        ? (auth.company?.company?.companyProfiles ?? []).find(
          (p) =>
            p.type ===
            operationToProfileType(data.operationType!, profileTypes),
        )?.id
        : undefined;

    return {
      ...(forwarderProfileId ? { companyProfileId: forwarderProfileId } : {}),
      contractKind: isGeneral
        ? Freight.ContractKind.General
        : Freight.ContractKind.OneTime,
      tradeDirection: direction!,
      freightType: isContainer
        ? Freight.ContractFreightType.Container
        : Freight.ContractFreightType.Bulk,
      serviceTypeId: data.serviceTypeId,
      // Empty-container return is a contract-level opt-in (container freight
      // only) — like hazardous. Per-booking return quantities are still set at
      // booking time, but only on contracts created WITH_RETURN.
      ...(isContainer
        ? {
          equipmentReturn:
            data.equipmentReturn === "with_return"
              ? "WITH_RETURN"
              : "WITHOUT_RETURN",
        }
        : {}),
      isHazardous: data.isHazardous,
      // The dangerous-goods declaration only travels with the flag — the API
      // rejects a hazardous contract that omits either field.
      ...(data.isHazardous
        ? { hazardClass: data.hazardClass, unNumber: data.unNumber }
        : {}),
      // Reefer is a contract-level flag for both container and bulk.
      isReefer: data.isRefrigerated,
      ...(data.previousContractRef
        ? { renewalOfId: 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 bundling is a property of the chosen service, not of a stored
      // form flag — derive it here so stale drafts can't misreport it. A
      // non-bundled contract still records the customer's own clearing agent.
      ...(serviceType?.includesCustoms
        ? { customsClearingEnabled: true }
        : {
          customsClearingEnabled: false,
          customsClearingAgent:
            data.customsClearingAgent?.trim() || undefined,
        }),
      cargoScope,
      routes,
    };
  }

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

  const handleSubmitContract = form.handleSubmit((data) => {
    try {
      const apiPayload = buildApiPayload(data);
      persistAndPriceMutation.mutate({
        payload: apiPayload,
        mode: "submit",
        existingContractId: priceContractId,
      });
    } catch {
      // validation error already surfaced
    }
  });

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

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

  function handleDraftModalOk() {
    setPriceModalMode(null);
    if (priceContractId) navigate(`/contracts/${priceContractId}`);
  }

  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" }}
          >
            {isEdit ? "Edit Contract" : "New Contract"}
          </Title>
          <Text size="sm" c="edr-muted" mt={4}>
            {isEdit
              ? editContract?.status === "CHANGES_REQUESTED"
                ? "Update your contract details, then resubmit it for EDR staff review."
                : "Update your draft contract details, then submit it for EDR staff review."
              : "Define your freight contract — scope, routes, and unit rates. Book shipments against it after signing."}
          </Text>
        </Box>
        <Button
          variant="default"
          radius="md"
          leftSection={<ChevronLeft size={16} />}
          onClick={() =>
            navigate(isEdit && editId ? `/contracts/${editId}` : "/contracts")
          }
        >
          {isEdit ? "Back to Contract" : "Back to Contracts"}
        </Button>
      </Group>

      <form
        id="new-contract-form"
        className="flex flex-col"
        style={{ flex: 1 }}
        onSubmit={(e) => e.preventDefault()}
      >
        <Box flex={1} p="24px">
          {isEdit && editContract?.status === "CHANGES_REQUESTED" && (
            <Alert
              color="orange"
              radius="lg"
              variant="light"
              icon={<AlertCircle size={18} />}
              title="A reviewer asked for changes"
              mb="lg"
            >
              {editContract?.latestChangeRequestNote ? (
                <Stack gap={6}>
                  <Text size="sm" fw={600}>
                    What the reviewer asked for:
                  </Text>
                  <Text size="sm" style={{ whiteSpace: "pre-wrap" }}>
                    {editContract.latestChangeRequestNote}
                  </Text>
                  <Text size="sm" c="dimmed" mt={2}>
                    Update the details below, then resubmit the contract for
                    review.
                  </Text>
                </Stack>
              ) : (
                "Update any contract detail that needs to change, then resubmit the contract for review."
              )}
            </Alert>
          )}

          <Box mb="lg">
            <StepIndicator step={step} steps={visibleSteps} />
          </Box>

          {persistAndPriceMutation.isError &&
            !rateNotConfigured &&
            !duplicateContract && (
            <Alert
              color="red"
              icon={<AlertCircle size={16} />}
              radius="md"
              mb="lg"
            >
              <Text size="sm" fw={600}>
                Failed to save contract 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>
          )}

          {/* Pricing not configured (422) — a rate is missing on the chosen
              route, nothing the customer can fix. Block with a modal. */}
          <Modal
            opened={rateNotConfigured}
            onClose={() => persistAndPriceMutation.reset()}
            title="Contract creation unavailable"
            centered
          >
            <Stack gap="sm">
              <Text size="sm">
                You can&apos;t create a contract right now — pricing hasn&apos;t
                been configured for the selected route yet. Please contact
                support for assistance.
              </Text>
              <Group justify="flex-end">
                <Button onClick={() => persistAndPriceMutation.reset()}>
                  OK
                </Button>
              </Group>
            </Stack>
          </Modal>

          {/* Duplicate contract (409) — same customer already has a non-expired
              contract for this service type + route. Block with a modal. */}
          <Modal
            opened={duplicateContract}
            onClose={() => persistAndPriceMutation.reset()}
            title="Contract creation unavailable"
            centered
          >
            <Stack gap="sm">
              <Text size="sm">{duplicateContractMessage}</Text>
              <Group justify="flex-end">
                <Button onClick={() => persistAndPriceMutation.reset()}>
                  OK
                </Button>
              </Group>
            </Stack>
          </Modal>

          {/* Step 0 — Setup: operation, contract, service, currency, miles. */}
          {step === 0 && (
            <StepCard>
              <StepHeader
                title="Contract Setup"
                description="Define the operation, contract kind, and the service this contract is for."
              />
              <Stack gap={24}>
                <Step1ContractType
                  form={form}
                  referenceData={referenceData}
                  allowedOperations={allowedOperations}
                  onOperationSelect={handleOperationSelect}
                  operationStatus={operationStatus}
                />
                <Step2ServiceType referenceData={referenceData} form={form} />
              </Stack>
            </StepCard>
          )}

          {/* Step 1 — Cargo & Route. */}
          {step === 1 && (
            <StepCard>
              <StepHeader
                title="Cargo & Route"
                description="Define what cargo this contract covers and the routes it runs. Quantities are captured later at booking."
              />
              <Stack gap={24}>
                <Step3CargoScope
                  form={form}
                  referenceData={referenceData}
                  isLoading={refDataLoading}
                />
                <Step4Route
                  form={form}
                  referenceData={referenceData}
                  isLoading={refDataLoading}
                />
              </Stack>
            </StepCard>
          )}

          {/* Step 2 — Review & Submit. */}
          {step === 2 && (
            <Step8Review
              form={form}
              setStep={setStep}
              direction={direction!}
              referenceData={referenceData}
              pricing={
                pricingData
                  ? {
                    currency: pricingData.currency,
                    lineItems: pricingData.lineItems,
                  }
                  : null
              }
              onSaveDraft={handleSaveDraft}
              onSubmit={handleSubmitContract}
              saveDraftPending={
                persistAndPriceMutation.isPending &&
                persistAndPriceMutation.variables?.mode === "draft"
              }
              submitPending={
                persistAndPriceMutation.isPending &&
                persistAndPriceMutation.variables?.mode === "submit"
              }
              isEdit={isEdit}
            />
          )}
        </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)",
            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={handleSubmitContract}
                loading={isPricing}
              >
                Submit
              </Button>
            )}
          </Group>
        </Box>
      </form>

      {/* Unit-rate quotation modal (doc §9.3 — Approve Quotation). */}
      <Modal
        opened={priceModalMode !== null && pricingData !== null}
        onClose={closePriceModal}
        title={
          <Text fw={700}>
            {priceModalMode === "submit"
              ? "Approve your quotation"
              : "Draft saved — unit-rate quotation"}
          </Text>
        }
        radius="lg"
        centered
        size="md"
      >
        {pricingData && (
          <Stack gap="md">
            <Text size="sm" c="dimmed">
              {priceModalMode === "submit"
                ? "Review your unit rates below. Approve to submit the contract for EDR staff review, edit & regenerate to change details, or discard this draft."
                : "Your contract has been saved as a draft. Here are your estimated unit rates."}
            </Text>
            <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"
                mb="md"
                style={{ letterSpacing: "0.06em" }}
              >
                Pricing schedule
              </Text>
              <Box mb="md">
                <NotFinalPriceNotice />
              </Box>
              <Stack gap={10}>
                {pricingData.lineItems.map((item) => (
                  <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.label}
                      </Text>
                      {item.containerSize && (
                        <Text size="xs" c="dimmed">
                          {item.containerSize}
                        </Text>
                      )}
                      {item.isClearance && (
                        <Text size="xs" c="edr-green" fw={600}>
                          Customs service fee — billed on your shipment booking
                          invoice together with the freight
                        </Text>
                      )}
                    </Box>
                    <Text
                      size="sm"
                      fw={700}
                      c="#10202F"
                      style={{ whiteSpace: "nowrap" }}
                    >
                      {item.unitPrice.toLocaleString()} {pricingData.currency}{" "}
                      <Text span fz={12} fw={600} c="edr-muted">
                        / {formatRateUnit(item.unit)}
                      </Text>
                    </Text>
                  </Group>
                ))}
                {pricingData.lineItems.length === 0 && (
                  <Text size="sm" c="dimmed">
                    No unit rates available.
                  </Text>
                )}
              </Stack>
            </Box>
            {pricingData.warnings && 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}
                  >
                    Discard
                  </Button>
                  <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}
                  >
                    Approve &amp; submit
                  </Button>
                </>
              ) : (
                <Button
                  color="edr-green"
                  radius="md"
                  onClick={handleDraftModalOk}
                >
                  OK
                </Button>
              )}
            </Group>
          </Stack>
        )}
      </Modal>

      {/* Re-priced on resubmit — confirm the new unit rates. */}
      <Modal
        opened={priceChangeResult !== null}
        onClose={() => setPriceChangeResult(null)}
        title={<Text fw={700}>Unit rates changed</Text>}
        radius="lg"
        centered
      >
        {priceChangeResult && (
          <Stack gap="md">
            <Text size="sm" c="dimmed">
              {priceChangeResult.message ??
                "The contract unit rates have been updated. Confirm to submit with the new schedule."}
            </Text>
            {priceChangeResult.lineItems &&
              priceChangeResult.lineItems.length > 0 && (
                <Box
                  p="md"
                  style={{
                    borderRadius: 16,
                    border: "1px solid var(--mantine-color-edr-border-0)",
                    background: "#fff",
                  }}
                >
                  <Stack gap={10}>
                    {priceChangeResult.lineItems.map((item) => (
                      <Group
                        key={item.code}
                        justify="space-between"
                        wrap="nowrap"
                        gap="sm"
                      >
                        <Text size="sm" c="#10202F" fw={500}>
                          {item.label}
                        </Text>
                        <Text size="sm" fw={700} c="#10202F">
                          {item.unitPrice.toLocaleString()}{" "}
                          {priceChangeResult.currency} /{" "}
                          {formatRateUnit(item.unit)}
                        </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>

      {/* Create-profile modal — opens when the chosen operation type has no
          matching company profile yet. Collects a license, creates the profile,
          then shows an "awaiting approval" state (the new profile is pending). */}
      <Modal
        opened={createTarget !== null}
        onClose={() => {
          if (createProfileMutation.isPending) return;
          if (licenseSubmitted) handleCreateProfileDone();
          else handleCreateProfileCancel();
        }}
        title={
          licenseSubmitted
            ? "Awaiting approval"
            : `Set up your ${createTargetLabel} profile`
        }
        centered
        radius="lg"
      >
        {licenseSubmitted ? (
          <Stack gap="md">
            <Group gap={10} align="flex-start" wrap="nowrap">
              <Box
                style={{
                  width: 38,
                  height: 38,
                  flexShrink: 0,
                  borderRadius: 11,
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                  background: "#ECF6F1",
                  color: "#0A6F4D",
                }}
              >
                <Check size={18} />
              </Box>
              <Text size="sm" c="dimmed">
                License submitted. Your {createTargetLabel.toLowerCase()}{" "}
                profile is now awaiting staff approval. We'll notify you once
                it's approved — then you can create this contract as{" "}
                {createTargetLabel.toLowerCase()}.
              </Text>
            </Group>
            <Group justify="flex-end" gap="sm">
              <Button color="edr-green" onClick={handleCreateProfileDone}>
                Got it
              </Button>
            </Group>
          </Stack>
        ) : (
          <Stack gap="md">
            <Text size="sm" c="dimmed">
              You don't have a {createTargetLabel.toLowerCase()} profile yet.
              Add your business license to create one. It goes to staff for
              approval before you can use it.
            </Text>
            <FileInput
              label="Business license"
              multiple
              clearable
              accept="application/pdf,image/png,image/jpeg"
              leftSection={<Upload size={16} />}
              placeholder="Select license file(s)"
              value={licenseFiles}
              onChange={(files) => setLicenseFiles(files ?? [])}
              error={createError ?? undefined}
            />
            <Group justify="flex-end" gap="sm">
              <Button
                variant="default"
                onClick={handleCreateProfileCancel}
                disabled={createProfileMutation.isPending}
              >
                Cancel
              </Button>
              <Button
                color="edr-green"
                onClick={handleCreateProfileConfirm}
                loading={createProfileMutation.isPending}
              >
                Submit license
              </Button>
            </Group>
          </Stack>
        )}
      </Modal>

      {/* Awaiting-approval / rejected modal — the chosen operation maps to a
          profile that exists but isn't active. The select was already reverted. */}
      {(() => {
        const target = pendingApprovalProfile
          ? profileByType.get(pendingApprovalProfile)
          : undefined;
        const isRejected = target?.status === "rejected";
        const isSuspended =
          target?.status === "suspended" || target?.status === "blacklisted";
        const label = pendingApprovalProfile
          ? (PROFILE_TYPE_LABELS[pendingApprovalProfile] ??
            pendingApprovalProfile)
          : "";
        return (
          <Modal
            opened={pendingApprovalProfile !== null}
            onClose={() => setPendingApprovalProfile(null)}
            title={
              isRejected
                ? "Profile not approved"
                : isSuspended
                  ? "Role suspended"
                  : "Awaiting approval"
            }
            centered
            radius="lg"
          >
            <Stack gap="md">
              {isSuspended ? (
                <>
                  <Text size="sm" c="dimmed">
                    Your {label} role has been suspended by EDR staff, so you
                    can&apos;t start a contract under it. Your other roles are
                    unaffected. Contact EDR support to resolve this.
                  </Text>
                  {target?.reviewNote && (
                    <Alert color="orange" variant="light" radius="md">
                      <Text size="sm">
                        <strong>Message from EDR staff:</strong>{" "}
                        {target.reviewNote}
                      </Text>
                    </Alert>
                  )}
                  <Group justify="flex-end" gap="sm">
                    <Button
                      color="edr-green"
                      onClick={() => setPendingApprovalProfile(null)}
                    >
                      OK
                    </Button>
                  </Group>
                </>
              ) : isRejected ? (
                <>
                  <Text size="sm" c="dimmed">
                    Your {label} profile was not approved. Fix the issue below
                    and resubmit it for review.
                  </Text>
                  {target?.reviewNote && (
                    <Alert color="red" variant="light" radius="md">
                      <Text size="sm">
                        <strong>Reviewer note:</strong> {target.reviewNote}
                      </Text>
                    </Alert>
                  )}
                  <Group justify="flex-end" gap="sm">
                    <Button
                      variant="default"
                      onClick={() => setPendingApprovalProfile(null)}
                      disabled={reapplyMutation.isPending}
                    >
                      Close
                    </Button>
                    <Button
                      color="edr-green"
                      loading={reapplyMutation.isPending}
                      onClick={() =>
                        target && reapplyMutation.mutate(target.id)
                      }
                    >
                      Resubmit for approval
                    </Button>
                  </Group>
                </>
              ) : (
                <>
                  <Text size="sm" c="dimmed">
                    Your {label} profile was submitted and is under staff
                    review. You can start a contract under it once it's
                    approved.
                  </Text>
                  <Group justify="flex-end" gap="sm">
                    <Button
                      color="edr-green"
                      onClick={() => setPendingApprovalProfile(null)}
                    >
                      OK
                    </Button>
                  </Group>
                </>
              )}
            </Stack>
          </Modal>
        );
      })()}
    </Box>
  );
}

function GateNotice({
  title,
  body,
  actionLabel,
  onAction,
}: {
  title: string;
  body: string;
  actionLabel: string;
  onAction: () => void;
}) {
  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">
          {title}
        </Text>
        <Text size="sm" mb="md">
          {body}
        </Text>
        <Button color="orange" onClick={onAction} mt="md">
          {actionLabel}
        </Button>
      </Alert>
    </Box>
  );
}
