import {
  ActionIcon,
  Alert,
  Badge,
  Box,
  Button,
  Checkbox,
  Group,
  List,
  Loader,
  Menu,
  Modal,
  Paper,
  RingProgress,
  Stack,
  Tabs,
  Text,
  ThemeIcon,
  Title,
} from "@mantine/core";
import { isAxiosError } from "axios";
import {
  AlertTriangle,
  CalendarClock,
  CheckCircle2,
  Clock,
  Merge,
  Container as ContainerIcon,
  Eye,
  FileText,
  History as HistoryIcon,
  LayoutGrid,
  MoreHorizontal,
  Navigation,
  Package,
  PackageCheck,
  Grid3x3,
  Route as RouteIcon,
  Ruler,
  Send,
  Train,
  Weight,
  Workflow as WorkflowIcon,
} from "lucide-react";
import { DateTimePicker } from "@mantine/dates";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Link, useParams } from "react-router-dom";

import { KpiStrip, PageContainer, PageHeader } from "@/components/page";
import { useAuth } from "@/auth/useAuth";
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
import {
  autoFillPlacements,
  mergePlacementsWithSaved,
  placementsFromScheduleWagons,
  validateLocalPlacements,
} from "@/components/trainScheduling/containerPlacement.util";
import { ContainerPlacementGrid } from "@/components/trainScheduling/ContainerPlacementGrid";
import { FleetAvailabilitySummary } from "@/components/trainScheduling/FleetAvailabilitySummary";
import { IntercityRideAlongPanel } from "@/components/trainScheduling/IntercityRideAlongPanel";
import { LegCapacityPanel } from "@/components/trainScheduling/LegCapacityPanel";
import { LegLoadBoardPanel } from "@/components/trainScheduling/LegLoadBoardPanel";
import { LoadEmptyContainersModal } from "@/components/trainScheduling/LoadEmptyContainersModal";
import MergeScheduleTrainModal from "@/components/trainScheduling/MergeScheduleTrainModal";
import ScheduleHistoryPanel from "@/components/trainScheduling/ScheduleHistoryPanel";
// import { ImportLoadingConfirmationPanel } from "@/components/trainScheduling/ImportLoadingConfirmationPanel";
import { RescheduleTrainDialog } from "@/components/trainScheduling/RescheduleTrainDialog";
import BookingWindowSettingsModal from "@/components/trainScheduling/BookingWindowSettingsModal";
// import { ScheduleBatchPanel } from "@/components/trainScheduling/ScheduleBatchPanel";
import { ScheduleBookingsStep } from "@/components/trainScheduling/ScheduleBookingsStep";
import { SwitchGovernmentBookingModal } from "@/components/trainScheduling/SwitchGovernmentBookingModal";
import { ScheduleWorkspacePanel } from "@/components/trainScheduling/ScheduleWorkspacePanel";
import { FreightTypeBadge } from "@/components/trainScheduling/ScheduleStatusBadge";
import {
  RouteCorridor,
  SegmentOccupancyStrip,
  StatusPill,
  scheduleBrand,
} from "@/components/trainScheduling/scheduleVisuals";
import {
  PreviewSummary,
  ScheduleWarningsAlert,
} from "@/components/trainScheduling/ScheduleWarningsAlert";
import { Train3DVisualization } from "@/components/trainScheduling/Train3DVisualization";
import { TrainCompositionDiagram } from "@/components/trainScheduling/TrainCompositionDiagram";
import { TrainConsistView } from "@/components/trainScheduling/compositionEditor";
import { WagonPlanGrid } from "@/components/trainScheduling/WagonPlanGrid";
import { WorkflowRail, WorkflowStep } from "@/components/trainScheduling/WorkflowStep";
import { openPdfBlob } from "@/components/warehouses/pdf";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useBookingWindowSocket } from "@/features/bookingWindows/useBookingWindowSocket";
import { api } from "@/services/api";
import { trainSchedulingService } from "@/services/trainScheduling.service";
import { useToast } from "@/hooks/use-toast";
import type {
  ContainerPlacement,
  EligibleContainerBooking,
  FreightType,
  TrainSchedulePreviewResponse,
} from "@/types/trainScheduling";

const parseError = (error: unknown, fallback: string) => {
  if (isAxiosError(error)) {
    const data = error.response?.data as Record<string, unknown> | undefined;
    const message = data?.message;
    if (Array.isArray(message)) return message.join(", ");
    if (typeof message === "string") return message;
    const violations = data?.violations;
    if (Array.isArray(violations)) return violations.join(", ");
  }
  return fallback;
};

export default function TrainScheduleV2DetailPage() {
  const { user: authUser } = useAuth();
  const { scheduleId } = useParams<{ scheduleId: string }>();
  const { toast } = useToast();
  const [activeStep, setActiveStep] = useState(0);
  const [selectedBookingIds, setSelectedBookingIds] = useState<string[]>([]);
  const [forceAssign, setForceAssign] = useState(false);
  const [previewResult, setPreviewResult] = useState<TrainSchedulePreviewResponse | null>(null);
  const [containerPlacements, setContainerPlacements] = useState<ContainerPlacement[]>([]);
  const [maintenanceOpen, setMaintenanceOpen] = useState(false);
  const [windowSettingsOpen, setWindowSettingsOpen] = useState(false);
  const [mergeModalOpen, setMergeModalOpen] = useState(false);
  const [gatepassSecuredAt, setGatepassSecuredAt] = useState("");
  const [gatepassReference, setGatepassReference] = useState("");
  const [gatepassFileUrl, setGatepassFileUrl] = useState("");
  const [gatepassNotes, setGatepassNotes] = useState("");
  const [dispatchConfirmOpen, setDispatchConfirmOpen] = useState(false);
  // Actual departure — staff often dispatch on paper first and record it later,
  // so the time is picked (defaults to now when the dialog opens).
  const [dispatchAt, setDispatchAt] = useState<Date | null>(null);
  const openDispatchConfirm = () => {
    setDispatchAt(new Date());
    setDispatchConfirmOpen(true);
  };
  const [switchTarget, setSwitchTarget] = useState<EligibleContainerBooking | null>(null);
  const [visualization3DOpen, setVisualization3DOpen] = useState(false);
  const [loadEmptiesOpen, setLoadEmptiesOpen] = useState(false);
  const autoPreviewedRef = useRef(false);

  const detailQuery = useQuery(
    api.trainScheduling.scheduleDetail.queryOptions({
      input: { id: scheduleId ?? "" },
      enabled: Boolean(scheduleId),
      // Live phase updates come from the booking-window socket (PHASE pushes
      // invalidate this query); 60s is the self-heal net for a missed emit so
      // the workspace countdown never freezes on an expired phase.
      refetchInterval: 60_000,
    }),
  );
  useBookingWindowSocket(Boolean(scheduleId));
  const schedule = detailQuery.data;
  const freightType: FreightType | undefined = schedule?.freightType as FreightType | undefined;
  const isDjiboutiPort = (value?: string | null) =>
    ["DJIBOUTI", "DORALEH", "DMP", "DCT", "NAGAD"].some((token) =>
      (value ?? "").toUpperCase().includes(token),
    );
  const gatepassApplies = Boolean(
    schedule &&
      ((schedule.direction === "IMPORT" &&
        isDjiboutiPort(`${schedule.originStation?.code ?? ""} ${schedule.originStation?.label ?? ""}`)) ||
        (schedule.direction === "EXPORT" &&
          isDjiboutiPort(`${schedule.destinationStation?.code ?? ""} ${schedule.destinationStation?.label ?? ""}`))),
  );
  const gatepassQuery = useQuery({
    queryKey: ["train-scheduling", "gatepass", scheduleId],
    queryFn: () => trainSchedulingService.getImportDjiboutiOperation(scheduleId as string),
    enabled: Boolean(scheduleId && gatepassApplies),
  });
  const gatepassSecured = gatepassQuery.data?.gatepassStatus === "SECURED";
  const secureGatepass = useMutation({
    mutationFn: () =>
      trainSchedulingService.grantImportDjiboutiGatepass(scheduleId as string, {
        securedAt: gatepassSecuredAt ? new Date(gatepassSecuredAt).toISOString() : undefined,
        reference: gatepassReference.trim() || undefined,
        fileUrl: gatepassFileUrl.trim() || undefined,
        notes: gatepassNotes.trim() || undefined,
      }),
    onSuccess: () => {
      toast({ title: "Gate pass secured" });
      void gatepassQuery.refetch();
    },
    onError: (error) => {
      toast({
        title: "Gate pass failed",
        description: parseError(error, "Could not secure gate pass"),
        variant: "destructive",
      });
    },
  });

  // Superseded by the Workspace tab Load/Unload toggle — see commented
  // "Import loading confirmation" card below.
  // const importLoadingQuery = useQuery(
  //   api.trainScheduling.importLoadingBookings.queryOptions({
  //     input: { id: scheduleId ?? "" },
  //     enabled: Boolean(scheduleId && schedule?.direction === "IMPORT"),
  //   }),
  // );

  const eligibleFilters = useMemo(
    () =>
      schedule
        ? {
            originStationId: schedule.originStation?.id,
            destinationStationId: schedule.destinationStation?.id,
            // Only this schedule's own bookings are eligible — same rule as the auto batch.
            trainScheduleId: scheduleId,
          }
        : undefined,
    [schedule, scheduleId],
  );

  const eligibleFreightType =
    freightType === "CONTAINER" || freightType === "BULK" ? freightType : undefined;

  const eligibleQuery = useQuery(
    api.trainScheduling.eligibleBookings.queryOptions({
      input: { filters: eligibleFilters, freightType: eligibleFreightType },
      enabled: Boolean(schedule),
    }),
  );
  const preview = useMutation(api.trainScheduling.preview.mutationOptions());
  const assign = useMutation(api.trainScheduling.assignBookings.mutationOptions());
  const unassign = useMutation(api.trainScheduling.unassignBooking.mutationOptions());
  const switchGov = useMutation(api.trainScheduling.switchGovernmentBooking.mutationOptions());
  const dispatch = useMutation(api.trainScheduling.dispatchSchedule.mutationOptions());
  const downloadMarshalling = useMutation({
    mutationFn: ({ id, direction, variant }: { id: string; direction?: string | null; variant?: "INTERCITY" }) =>
      variant === "INTERCITY"
        ? trainSchedulingService.downloadIntercityMarshallingDocument(id)
        : direction === "EXPORT"
          ? trainSchedulingService.downloadExportLoadListDocument(id)
          : trainSchedulingService.downloadImportDjiboutiLoadListDocument(id),
  });

  useEffect(() => {
    const operation = gatepassQuery.data;
    if (!operation) return;
    const secured = operation.gatepassSecuredAt ?? operation.gatepassGrantedAt;
    setGatepassSecuredAt(secured ? new Date(secured).toISOString().slice(0, 16) : "");
    setGatepassReference(operation.documents?.GATE_PASS?.reference ?? "");
    setGatepassFileUrl(operation.documents?.GATE_PASS?.fileUrl ?? "");
    setGatepassNotes(operation.documents?.GATE_PASS?.notes ?? operation.notes ?? "");
  }, [gatepassQuery.data]);

  const assignedIds = useMemo(
    () => (schedule?.bookings ?? []).map((b) => b.id),
    [schedule?.bookings],
  );

  const allSelectedIds = useMemo(() => {
    const merged = new Set([...assignedIds, ...selectedBookingIds]);
    return [...merged];
  }, [assignedIds, selectedBookingIds]);

  const containerUnits = previewResult?.containerUnits ?? [];
  const containerSlots = previewResult?.containerSlotSequenceNos ?? [];
  // Container-number placement step removed — the customer enters container
  // numbers when booking, so scheduling skips straight from the wagon plan to
  // finalize. Steps: select bookings → review wagons → finalize.
  const hasContainerStep = false;

  const displayWagonPlan = useMemo(() => {
    const savedWagons = schedule?.trainSet?.wagons ?? [];
    // Map each slot to its reserved physical wagon number (from the wagon table) so the
    // plan shows real wagon ids (e.g. WGN-DEMO-001) instead of generic "Wagon #1".
    const physicalBySeq = new Map(
      savedWagons.map((w) => [w.sequenceNo, w.physicalWagonNumber ?? null]),
    );
    if (previewResult?.wagonPlan?.length) {
      return previewResult.wagonPlan.map((slot) => ({
        ...slot,
        physicalWagonNumber: physicalBySeq.get(slot.sequenceNo) ?? null,
      }));
    }
    if (savedWagons.length) return savedWagons;
    return [];
  }, [previewResult?.wagonPlan, schedule?.trainSet?.wagons]);

  // EXPORT schedules render the consist back-to-front (the train turns around
  // for the return run) — DISPLAY ONLY: stored sequenceNos, allocations,
  // documents, and the adjust-consist / placement flows keep the as-built order.
  const isExportDisplay = schedule?.direction === "EXPORT";
  const displayWagonPlanOriented = useMemo(
    () => (isExportDisplay ? [...displayWagonPlan].reverse() : displayWagonPlan),
    [displayWagonPlan, isExportDisplay],
  );

  const runPreview = useCallback(
    async (options?: { silent?: boolean; advanceStep?: boolean }) => {
      if (!schedule || !scheduleId) return null;
      if (!allSelectedIds.length) {
        if (!options?.silent) {
          toast({ title: "Select at least one booking", variant: "destructive" });
        }
        return null;
      }
      const originStationId = schedule.originStation?.id;
      const destinationStationId = schedule.destinationStation?.id;
      if (!originStationId || !destinationStationId) {
        if (!options?.silent) {
          toast({ title: "Schedule missing origin or destination", variant: "destructive" });
        }
        return null;
      }
      try {
        const result = await preview.mutateAsync({
          freightType,
          payload: {
            bookingIds: allSelectedIds,
            scheduleDate: schedule.scheduledDepartureDate,
            originStationId,
            destinationStationId,
            targetScheduleId: scheduleId,
          },
        });
        setPreviewResult(result);
        if (result.containerUnits?.length && result.containerSlotSequenceNos?.length) {
          const autoFilled = autoFillPlacements(
            result.containerUnits,
            result.containerSlotSequenceNos,
          );
          const saved = schedule.trainSet?.wagons
            ? placementsFromScheduleWagons(schedule.trainSet.wagons)
            : [];
          setContainerPlacements(
            saved.length ? mergePlacementsWithSaved(autoFilled, saved) : autoFilled,
          );
        } else {
          setContainerPlacements([]);
        }
        if (!options?.silent) {
          if (!result.valid) {
            toast({ title: "Preview has violations", variant: "destructive" });
          } else if (options?.advanceStep !== false) {
            setActiveStep(1);
          }
        }
        return result;
      } catch (err) {
        if (!options?.silent) {
          toast({
            title: "Preview failed",
            description: parseError(err, "Could not preview"),
            variant: "destructive",
          });
        }
        return null;
      }
    },
    [allSelectedIds, freightType, preview, schedule, scheduleId, toast],
  );

  useEffect(() => {
    if (!schedule || !scheduleId || autoPreviewedRef.current) return;
    if (!assignedIds.length) return;
    autoPreviewedRef.current = true;
    void runPreview({ silent: true, advanceStep: false });
  }, [assignedIds.length, runPreview, schedule, scheduleId]);

  const savedPlacementsFromSchedule = useMemo(
    () =>
      schedule?.trainSet?.wagons
        ? placementsFromScheduleWagons(schedule.trainSet.wagons)
        : [],
    [schedule?.trainSet?.wagons],
  );

  useEffect(() => {
    if (!containerUnits.length || !containerSlots.length) return;

    setContainerPlacements((current) => {
      if (current.length && current.some((p) => p.containerNumber?.trim())) {
        return current;
      }
      const autoFilled = autoFillPlacements(containerUnits, containerSlots);
      if (savedPlacementsFromSchedule.length) {
        return mergePlacementsWithSaved(autoFilled, savedPlacementsFromSchedule);
      }
      if (current.length) return current;
      return autoFilled;
    });
  }, [containerUnits, containerSlots, savedPlacementsFromSchedule]);

  if (detailQuery.isLoading) {
    return (
      <PageContainer>
        <Group justify="center" py="xl">
          <Loader size="sm" />
        </Group>
      </PageContainer>
    );
  }

  if (!schedule || !scheduleId) {
    return (
      <PageContainer>
        <Text c="dimmed" py="xl">
          Schedule not found
        </Text>
      </PageContainer>
    );
  }

  // All locomotives pulling the train (≥2), falling back to the legacy single loco.
  const locomotives =
    schedule.trainSet?.locomotives && schedule.trainSet.locomotives.length > 0
      ? schedule.trainSet.locomotives
      : schedule.trainSet?.locomotive
        ? [schedule.trainSet.locomotive]
        : [];

  const canEditBookings = ["DRAFT", "SCHEDULED"].includes(schedule.status);
  const canDispatch =
    schedule.status === "SCHEDULED" &&
    hasPermission(authUser, FREIGHT_PERMS.trainScheduling.dispatch);
  // Dispatch readiness: bookings with no wagon, and wagon-loaded bookings whose
  // cargo staff never marked loaded. Both are warnings, not blockers — staff can
  // still dispatch after confirming.
  const dispatchBookings = schedule.bookings ?? [];
  const unassignedCount = dispatchBookings.filter((b) => !b.wagonAssigned).length;
  const unloadedCount = dispatchBookings.filter(
    (b) => b.wagonAssigned && (b.loadingStatus ?? "UNLOADED") !== "LOADED",
  ).length;
  // Intercity ride-alongs load through the journey flow (Load at their origin
  // yard), not the workspace toggle — dispatching before that leaves paid cargo
  // stranded on the platform while its train departs.
  const intercityNotLoadedCount = dispatchBookings.filter(
    (b) =>
      b.tradeDirection === "DOMESTIC" &&
      !b.loadedAt &&
      !["IN_TRANSIT", "COMPLETED"].includes(b.status ?? ""),
  ).length;
  // No loading hard-block: bookings may board mid-corridor, so loading happens
  // per yard from the track page's log-pass flow. Everything below is advisory.
  const hasDispatchWarnings =
    unassignedCount > 0 || unloadedCount > 0 || intercityNotLoadedCount > 0;

  const finalizeStep = hasContainerStep ? 3 : 2;
  const canModifyBookings = canEditBookings && !["DISPATCHED", "ARRIVED"].includes(schedule.status);
  const canPrintMarshalling =
    (schedule.direction === "IMPORT" || schedule.direction === "EXPORT") &&
    ["DISPATCHED", "ARRIVED"].includes(schedule.status);

  const openMarshallingDocument = async (options?: {
    title?: string;
    successDescription?: string;
    errorTitle?: string;
    variant?: "INTERCITY";
  }) => {
    const pdfWindow = window.open("", "_blank");
    try {
      const blob = await downloadMarshalling.mutateAsync({
        id: scheduleId,
        direction: schedule.direction,
        variant: options?.variant,
      });
      const prefix =
        options?.variant === "INTERCITY"
          ? "intercity-marshalling"
          : schedule.direction === "EXPORT"
            ? "export-marshalling"
            : "import-marshalling";
      const filename = `${prefix}-${schedule.trainNumber ?? scheduleId}.pdf`;
      const opened = openPdfBlob(blob, filename, pdfWindow);
      toast({
        title: options?.title ?? "Marshalling document ready",
        description:
          options?.successDescription ??
          (opened
            ? "The PDF opened in a browser tab for printing or saving."
            : "The browser blocked the preview tab, so the PDF was downloaded."),
      });
    } catch (error) {
      pdfWindow?.close();
      toast({
        title: options?.errorTitle ?? "Could not open marshalling document",
        description: parseError(error, "Make sure the train has wagon allocations, then try again."),
        variant: "destructive",
      });
    }
  };

  const runDispatch = async () => {
    setDispatchConfirmOpen(false);
    try {
      await dispatch.mutateAsync({
        id: scheduleId,
        payload: dispatchAt ? { actualDepartureAt: dispatchAt.toISOString() } : {},
      });
      await openMarshallingDocument({
        title: "Train dispatched",
        successDescription: "Marshalling document generated for the dispatched train.",
        errorTitle: "Train dispatched, but document could not open",
      });
    } catch (err) {
      toast({
        title: "Dispatch failed",
        description: parseError(err, "Could not dispatch"),
        variant: "destructive",
      });
    }
  };

  const handleAssign = async () => {
    if (!allSelectedIds.length) return;

    if (hasContainerStep) {
      const issues = validateLocalPlacements(containerUnits, containerPlacements);
      if (issues.length) {
        toast({
          title: "Complete container assignments",
          description: issues.join(", "),
          variant: "destructive",
        });
        return;
      }
    }

    try {
      const result = await assign.mutateAsync({
        id: scheduleId,
        freightType,
        payload: {
          bookingIds: allSelectedIds,
          forceAssign,
          containerPlacements: hasContainerStep ? containerPlacements : undefined,
        },
      });
      toast({ title: "Bookings assigned — wagons auto-pinned" });
      const refreshed = await detailQuery.refetch();
      const saved = refreshed.data?.trainSet?.wagons
        ? placementsFromScheduleWagons(refreshed.data.trainSet.wagons)
        : [];
      if (saved.length) {
        setContainerPlacements(saved);
      }
      autoPreviewedRef.current = false;
      setActiveStep(finalizeStep);
      if (result.deferredBookings?.length) {
        toast({
          title: "Partial assignment",
          description: `${result.deferredBookings.length} booking(s) deferred to next train`,
        });
      }
    } catch (err) {
      toast({
        title: "Assign failed",
        description: parseError(err, "Could not assign"),
        variant: "destructive",
      });
    }
  };

  const handleUnassign = async (bookingId: string) => {
    // Gov bookings never leave a train by removal — only by switching. The API
    // enforces this too; the guard here just gives the warning without a call.
    if (schedule?.bookings?.some((b) => b.id === bookingId && b.isGovernment)) {
      toast({
        title: "Government booking cannot be removed",
        description:
          "Government bookings cannot be removed from the train. They can only be switched onto another allocation.",
        variant: "destructive",
      });
      return;
    }
    try {
      await unassign.mutateAsync({ id: scheduleId, bookingId });
      toast({ title: "Booking unassigned" });
      setSelectedBookingIds((ids) => ids.filter((id) => id !== bookingId));
      setPreviewResult(null);
      autoPreviewedRef.current = false;
    } catch (err) {
      toast({
        title: "Unassign failed",
        description: parseError(err, "Could not unassign"),
        variant: "destructive",
      });
    }
  };

  const containerComplete =
    hasContainerStep &&
    containerUnits.length > 0 &&
    validateLocalPlacements(containerUnits, containerPlacements).length === 0;
  const finalizeComplete = ["SCHEDULED", "DISPATCHED", "ARRIVED"].includes(
    schedule.status,
  );

  const stepsMeta = [
    {
      key: "bookings",
      icon: Package,
      title: "Bookings",
      subtitle: "Select cargo & preview the plan",
      complete: Boolean(previewResult) || assignedIds.length > 0,
    },
    {
      key: "wagon",
      icon: LayoutGrid,
      title: "Wagon plan",
      subtitle: "Review generated allocations",
      complete: displayWagonPlan.length > 0,
    },
    ...(hasContainerStep
      ? [
          {
            key: "container",
            icon: ContainerIcon,
            title: "Containers",
            subtitle: "Map units to wagon slots",
            complete: containerComplete,
          },
        ]
      : []),
    {
      key: "finalize",
      icon: CheckCircle2,
      title: "Dispatch",
      subtitle: "Review the consist & dispatch",
      complete: finalizeComplete,
    },
  ];
  const completedCount = stepsMeta.filter((s) => s.complete).length;
  const progressPct = Math.round((completedCount / stepsMeta.length) * 100);
  const toggleStep = (i: number) => setActiveStep((cur) => (cur === i ? -1 : i));

  const renderStepRightSlot = (key: string) => {
    if (key === "bookings") {
      if (previewResult) {
        return (
          <Badge
            variant="light"
            color={previewResult.valid ? "edr-green" : "red"}
            radius="sm"
          >
            {previewResult.valid ? "Plan valid" : "Has issues"}
          </Badge>
        );
      }
      return allSelectedIds.length ? (
        <Badge variant="light" color="edr-green" radius="sm">
          {allSelectedIds.length} selected
        </Badge>
      ) : null;
    }
    if (key === "wagon" && displayWagonPlan.length) {
      return (
        <Group gap="xs">
          {schedule.reverseWagonOrder ? (
            <Badge variant="light" color="orange" radius="sm">
              Reversed order
            </Badge>
          ) : null}
          <Badge variant="light" color="edr-green" radius="sm">
            {displayWagonPlan.length} wagons
          </Badge>
        </Group>
      );
    }
    if (key === "container" && containerUnits.length) {
      return (
        <Badge
          variant="light"
          color={containerComplete ? "edr-green" : "yellow"}
          radius="sm"
        >
          {containerUnits.length} units
        </Badge>
      );
    }
    if (key === "finalize") {
      return <StatusPill status={schedule.status} />;
    }
    return null;
  };

  const renderStepBody = (key: string) => {
    if (key === "bookings") {
      return (
        <Stack gap="md">
          <ScheduleBookingsStep
            assignedBookings={(schedule.bookings ?? []).map((b) => ({
              id: b.id,
              reference: b.reference ?? b.id.slice(0, 8),
              customer: b.customer,
              weightTons: b.weightTons,
              isGovernment: b.isGovernment,
              wagonsRequired: b.wagonsRequired,
              contractReference: b.contractReference,
              origin: b.origin,
              destination: b.destination,
            }))}
            eligibleItems={eligibleQuery.data?.items ?? []}
            eligibleLoading={eligibleQuery.isLoading}
            selectedIds={allSelectedIds}
            onSelectionChange={(ids) => {
              const assigned = new Set(assignedIds);
              setSelectedBookingIds(ids.filter((id) => !assigned.has(id)));
            }}
            assignedIds={assignedIds}
            freightType={freightType}
            canRemove={canModifyBookings}
            onRemove={handleUnassign}
            onSwitch={canModifyBookings ? setSwitchTarget : undefined}
          />

          {canEditBookings ? (
            <Group
              align="center"
              justify="space-between"
              wrap="wrap"
              gap="md"
              p="sm"
              style={{
                borderRadius: 12,
                background: "var(--mantine-color-gray-0)",
                border: "1px solid var(--mantine-color-gray-2)",
              }}
            >
              <Checkbox
                label="Force assign (bypass hold / overweight warnings)"
                checked={forceAssign}
                onChange={(e) => setForceAssign(e.currentTarget.checked)}
                size="sm"
              />
              <Button
                color="edr-green"
                radius="md"
                leftSection={<Eye size={16} />}
                loading={preview.isPending}
                onClick={() => void runPreview()}
              >
                Preview plan
              </Button>
            </Group>
          ) : null}

          {previewResult ? (
            <Stack gap="sm">
              <ScheduleWarningsAlert
                violations={previewResult.violations}
                warnings={previewResult.warnings}
              />
              <FleetAvailabilitySummary
                fleetAvailability={previewResult.fleetAvailability}
                deferredBookings={previewResult.deferredBookings}
              />
              <PreviewSummary summary={previewResult.summary} />
            </Stack>
          ) : null}
        </Stack>
      );
    }

    if (key === "wagon") {
      return (
        <Stack gap="md">
          {!displayWagonPlan.length && !previewResult ? (
            <Paper p="md" radius="lg" withBorder bg="gray.0">
              <Text size="sm" c="dimmed">
                Run a preview from the Bookings step to generate the wagon plan.
              </Text>
            </Paper>
          ) : null}
          <FleetAvailabilitySummary
            fleetAvailability={previewResult?.fleetAvailability}
            deferredBookings={previewResult?.deferredBookings}
          />
          {isExportDisplay && displayWagonPlanOriented.length ? (
            <Text size="xs" c="dimmed">
              Shown rear-first (export direction) — positions keep their original numbers.
            </Text>
          ) : null}
          <WagonPlanGrid wagonPlan={displayWagonPlanOriented} freightType={freightType} />
          {canEditBookings && (previewResult || displayWagonPlan.length) ? (
            <Group>
              {hasContainerStep ? (
                <Button
                  color="edr-green"
                  radius="md"
                  rightSection={<ContainerIcon size={16} />}
                  onClick={() => setActiveStep(2)}
                >
                  Continue to containers
                </Button>
              ) : null}
              <Button variant="default" radius="md" onClick={() => void runPreview()}>
                Refresh preview
              </Button>
            </Group>
          ) : null}
        </Stack>
      );
    }

    if (key === "container") {
      return (
        <Stack gap="md">
          {!containerUnits.length ? (
            <Paper p="md" radius="lg" withBorder bg="gray.0">
              <Text size="sm" c="dimmed">
                Run preview from the Bookings step to load container units for numbering.
              </Text>
            </Paper>
          ) : (
            <ContainerPlacementGrid
              units={containerUnits}
              containerSlots={containerSlots}
              placements={containerPlacements}
              onChange={setContainerPlacements}
            />
          )}
          {canEditBookings ? (
            <Group>
              <Button
                color="edr-green"
                radius="md"
                loading={assign.isPending}
                onClick={handleAssign}
              >
                {assignedIds.length ? "Save assignments" : "Assign bookings"}
              </Button>
              <Button
                variant="default"
                radius="md"
                onClick={() => setActiveStep(finalizeStep)}
              >
                Skip to dispatch
              </Button>
            </Group>
          ) : null}
        </Stack>
      );
    }

    // finalize — the train is known here, so draw the full composition the
    // same way the batch board's composition tab does (interactive consist).
    return (
      <Stack gap="md">
        {schedule.trainSet ? (
          <TrainConsistView
            scheduleDetail={schedule}
            scheduleId={scheduleId ?? ""}
            maxWagons={schedule.maxWagons ?? 53}
          />
        ) : (
          <TrainCompositionDiagram
            locomotive={null}
            locomotives={locomotives}
            wagons={[]}
            freightType={freightType}
            trainNumber={schedule.trainNumber ?? schedule.train?.code ?? null}
            totalLengthMeters={null}
          />
        )}
        <Paper
          p="lg"
          radius="lg"
          withBorder
          style={{
            background: scheduleBrand.softSurface,
            borderColor: scheduleBrand.mutedBorder,
          }}
        >
          <Group gap="md" align="flex-start" wrap="nowrap">
            <ThemeIcon size={44} radius="md" variant="light" color="#F2A516">
              <CheckCircle2 size={22} />
            </ThemeIcon>
            <Stack gap={2}>
              <Text fw={600}>Ready to depart</Text>
              <Text size="sm" c="dimmed">
                Dispatch begins rail movement and notifies the yard.
              </Text>
            </Stack>
          </Group>
        </Paper>
        <Group>
          {canDispatch ? (
            <Button
              color="edr-green"
              size="md"
              radius="md"
              leftSection={<Send size={18} />}
              loading={dispatch.isPending}
              onClick={openDispatchConfirm}
            >
              Dispatch train
            </Button>
          ) : null}
          {!canDispatch ? (
            <Text size="sm" c="dimmed">
              No actions available for this schedule status.
            </Text>
          ) : null}
        </Group>
      </Stack>
    );
  };

  return (
    <PageContainer>
      <PageHeader
        title={schedule.route?.name ?? "Train schedule"}
        backTo="/dashboard/operations/train-scheduling-v2"
        breadcrumbs={[
          {
            label: "Train schedules",
            href: "/dashboard/operations/train-scheduling-v2",
          },
          { label: schedule.reference ?? "Schedule" },
        ]}
        subtitle={
          schedule.train ? (
            <Text size="sm" c="dimmed">
              {schedule.train.trainName ?? `Train ${schedule.train.code}`}
              {schedule.train.trainName ? ` · Train ${schedule.train.code}` : ""}
            </Text>
          ) : undefined
        }
        meta={
          <Group gap={6} wrap="wrap">
            {schedule.reference ? (
              <Badge
                variant="filled"
                color="edr-green"
                radius="sm"
                style={{ fontWeight: 700, fontFamily: "monospace" }}
              >
                {schedule.reference}
              </Badge>
            ) : null}
            <FreightTypeBadge freightType={schedule.freightType} />
            <StatusPill status={schedule.status} />
            {gatepassApplies && gatepassSecured ? (
              <Badge
                variant="light"
                color="edr-green"
                radius="sm"
                leftSection={<CheckCircle2 size={12} />}
              >
                Gate pass secured
              </Badge>
            ) : null}
            {previewResult ? (
              <Badge
                radius="sm"
                variant="light"
                color={previewResult.valid ? "edr-green" : "red"}
                leftSection={
                  <Box
                    w={8}
                    h={8}
                    style={{
                      borderRadius: 999,
                      background: previewResult.valid
                        ? "var(--mantine-color-edr-green-6)"
                        : "var(--mantine-color-red-6)",
                    }}
                  />
                }
              >
                Preview {previewResult.valid ? "valid" : "has issues"}
              </Badge>
            ) : null}
          </Group>
        }
        action={
          <Group gap="sm" wrap="nowrap">
            {/* Merging rewrites the consist, so it is offered only while
                the departure can still be edited. */}
            {canEditBookings ? (
              <Button
                variant="light"
                size="compact-sm"
                leftSection={<Merge size={14} />}
                onClick={() => setMergeModalOpen(true)}
              >
                Merge
              </Button>
            ) : null}
            {/* Empties ride an export departure back to Djibouti — offered only
                while the train can still take load. */}
            {schedule.direction === "EXPORT" &&
            ["DRAFT", "SCHEDULED"].includes(schedule.status) ? (
              <Button
                variant="light"
                color="edr-green"
                size="compact-sm"
                leftSection={<ContainerIcon size={14} />}
                onClick={() => setLoadEmptiesOpen(true)}
              >
                Load Empty Container
              </Button>
            ) : null}
            {(schedule.trainSet?.wagons?.length ?? 0) > 0 ? (
              <Button
                variant="gradient"
                gradient={{ from: "#0f172a", to: "#334155" }}
                radius="lg"
                size="compact-sm"
                leftSection={<Eye size={16} />}
                onClick={() => setVisualization3DOpen(true)}
              >
                3D Visualization
              </Button>
            ) : null}
            <Menu position="bottom-end" width={240} withinPortal>
              <Menu.Target>
                <ActionIcon variant="default" size="lg" radius="md" aria-label="More actions">
                  <MoreHorizontal size={16} />
                </ActionIcon>
              </Menu.Target>
              <Menu.Dropdown>
                {canPrintMarshalling ? (
                  <Menu.Item
                    leftSection={<FileText size={15} />}
                    disabled={downloadMarshalling.isPending}
                    onClick={() => void openMarshallingDocument()}
                  >
                    Marshalling PDF
                  </Menu.Item>
                ) : null}
                {["DISPATCHED", "ARRIVED"].includes(schedule.status) ? (
                  <Menu.Item
                    leftSection={<FileText size={15} />}
                    disabled={downloadMarshalling.isPending}
                    onClick={() =>
                      void openMarshallingDocument({
                        title: "Intercity marshalling ready",
                        variant: "INTERCITY",
                      })
                    }
                  >
                    Intercity Marshalling
                  </Menu.Item>
                ) : null}
                {["DISPATCHED", "ARRIVED"].includes(schedule.status) ? (
                  <Menu.Item
                    component={Link}
                    to={`/dashboard/operations/train-scheduling-v2/${scheduleId}/track`}
                    leftSection={<Navigation size={15} />}
                  >
                    Track train
                  </Menu.Item>
                ) : null}
                {schedule.windowPhase === "PRE_WINDOW" ? (
                  <Menu.Item
                    leftSection={<Clock size={15} />}
                    onClick={() => setWindowSettingsOpen(true)}
                  >
                    Window settings
                  </Menu.Item>
                ) : null}
                {["DRAFT", "SCHEDULED"].includes(schedule.status) ? (
                  <Menu.Item onClick={() => setMaintenanceOpen(true)}>
                    Reschedule train
                  </Menu.Item>
                ) : null}
                {gatepassApplies && !gatepassSecured ? (
                  <Menu.Item
                    leftSection={<FileText size={15} />}
                    disabled={secureGatepass.isPending}
                    onClick={() => secureGatepass.mutate()}
                  >
                    Secure gate pass
                  </Menu.Item>
                ) : null}
              </Menu.Dropdown>
            </Menu>
          </Group>
        }
      />

      {/* Ops signage: Train No. / Voyage No. / Direction read at a glance from
          across the room, so these stay large rather than folding into the
          numeric KpiStrip below. */}
      <Paper radius="xl" p="lg">
        <Stack gap="md">
          <Group gap="lg" align="center" wrap="wrap">
            {schedule.trainNumber ? (
              <Box>
                <Text size="xs" c="dimmed" fw={600} tt="uppercase" lh={1.2}>
                  Train No.
                </Text>
                <Text
                  ff="monospace"
                  fw={800}
                  lh={1.1}
                  style={{ fontSize: 32, color: "#0f172a" }}
                >
                  {schedule.trainNumber}
                </Text>
              </Box>
            ) : null}
            {schedule.voyageNumber ? (
              <Box>
                <Text size="xs" c="dimmed" fw={600} tt="uppercase" lh={1.2}>
                  Voyage No.
                </Text>
                <Text
                  ff="monospace"
                  fw={800}
                  lh={1.1}
                  style={{ fontSize: 32, color: "#0f172a" }}
                >
                  {schedule.voyageNumber}
                </Text>
              </Box>
            ) : null}
            {schedule.direction ? (
              <Box>
                <Text size="xs" c="dimmed" fw={600} tt="uppercase" lh={1.2}>
                  Direction
                </Text>
                <Text
                  fw={800}
                  lh={1.1}
                  tt="uppercase"
                  style={{
                    fontSize: 32,
                    letterSpacing: 0.5,
                    color:
                      schedule.direction === "IMPORT"
                        ? "#2E5B96"
                        : schedule.direction === "EXPORT"
                          ? "#0A6F4D"
                          : "#0f172a",
                  }}
                >
                  {schedule.direction}
                </Text>
              </Box>
            ) : null}
          </Group>
          {(schedule.stops?.length ?? 0) >= 3 ||
          (schedule.bookings ?? []).some(
            (b) => b.tradeDirection === "DOMESTIC",
          ) ? (
            <SegmentOccupancyStrip
              stops={schedule.stops ?? []}
              bookings={schedule.bookings ?? []}
              maxWagons={schedule.maxWagons}
              maxGrossTons={schedule.maxGrossWeightTons}
            />
          ) : (
            <Box maw={340}>
              <RouteCorridor
                origin={
                  schedule.originStation?.label ?? schedule.originStation?.code
                }
                destination={
                  schedule.destinationStation?.label ??
                  schedule.destinationStation?.code
                }
              />
            </Box>
          )}
        </Stack>
      </Paper>

      <KpiStrip
        items={[
          ...(schedule.train
            ? [
                {
                  label: "Train",
                  value: schedule.train.code,
                  hint: schedule.train.trainName ?? "Built train (Train Builder)",
                  icon: Train,
                },
              ]
            : []),
          {
            label: locomotives.length > 1 ? "Locomotives" : "Locomotive",
            value: locomotives.length
              ? locomotives.map((l) => l.code).join(" + ")
              : "—",
            hint: locomotives.length
              ? `${locomotives.length} locomotive${locomotives.length > 1 ? "s" : ""}`
              : "No locomotives assigned",
            icon: Train,
          },
          ...(schedule.trainSet?.totalLengthMeters
            ? [
                {
                  label: "Train length",
                  // Physical consist length — the same figure Train Builder shows.
                  value: `${schedule.trainSet.totalLengthMeters}m`,
                  hint: "built consist — matches Train Builder",
                  icon: Ruler,
                },
              ]
            : []),
          {
            label: "Bookings",
            value: schedule.bookings?.length ?? 0,
            hint: (() => {
              const intercity = (schedule.bookings ?? []).filter(
                (b) => b.tradeDirection === "DOMESTIC",
              ).length;
              return intercity > 0
                ? `${intercity} intercity ride-along${intercity === 1 ? "" : "s"}`
                : undefined;
            })(),
            icon: Package,
          },
          {
            label: "Wagons / load",
            // Gross: cargo load + wagon tare — the weight the locomotives
            // actually haul. Heaviest corridor edge when the server sends it;
            // plain consist sums over-report a multi-stop train (cross-leg
            // wagon sharing counts one physical wagon as several slots).
            value: (() => {
              const heaviest = schedule.trainSet?.heaviestLeg;
              const wagonCount =
                heaviest?.loadedWagonCount ??
                schedule.trainSet?.wagonCount ??
                displayWagonPlan.length;
              const grossTons =
                heaviest?.grossWeightTons ??
                Math.round(
                  ((schedule.trainSet?.totalWeightTons ?? 0) +
                    (schedule.trainSet?.wagons ?? []).reduce(
                      (sum, w) => sum + (Number(w.tareWeightTons) || 0),
                      0,
                    )) *
                    100,
                ) / 100;
              return `${wagonCount} · ${grossTons}T`;
            })(),
            hint: schedule.trainSet?.heaviestLeg
              ? "heaviest leg · wagon tare + cargo"
              : "gross · wagon tare + cargo",
            icon: Weight,
          },
          {
            label: "Departure",
            value: new Date(schedule.scheduledDepartureDate).toLocaleDateString("en", {
              month: "short",
              day: "2-digit",
            }),
            hint: new Date(schedule.scheduledDepartureDate).toLocaleTimeString("en", {
              hour: "2-digit",
              minute: "2-digit",
            }),
            icon: CalendarClock,
          },
        ]}
      />

      {/* Import loading confirmation — superseded by the per-booking Load/Unload
          toggle in the Workspace tab (works for all directions). Kept commented
          in case the import-only confirmation flow is needed again.
      {schedule?.direction === "IMPORT" ? (
        <Paper radius="xl" p="lg" withBorder>
          <Stack gap="md">
            <Text fw={600}>Import loading confirmation</Text>
            <Text size="sm" c="dimmed">
              Paid import bookings with a wagon allocated on this schedule. Marking loaded/unloaded
              is tracking only — it does not block dispatch.
            </Text>
            <ImportLoadingConfirmationPanel
              scheduleId={scheduleId as string}
              items={importLoadingQuery.data?.items ?? []}
              isLoading={importLoadingQuery.isLoading}
            />
          </Stack>
        </Paper>
      ) : null}
      */}

      <Tabs defaultValue="workflow" radius="md" color="edr-green" keepMounted={false}>
        <Tabs.List mb="md">
          <Tabs.Tab value="workflow" leftSection={<WorkflowIcon size={16} />}>
            Workflow
          </Tabs.Tab>
          <Tabs.Tab value="workspace" leftSection={<PackageCheck size={16} />}>
            Workspace
          </Tabs.Tab>
          <Tabs.Tab value="legs" leftSection={<RouteIcon size={16} />}>
            Leg capacity
          </Tabs.Tab>
          <Tabs.Tab value="leg-board" leftSection={<Grid3x3 size={16} />}>
            Leg board
          </Tabs.Tab>
          <Tabs.Tab value="history" leftSection={<HistoryIcon size={16} />}>
            History
          </Tabs.Tab>
        </Tabs.List>

        <Tabs.Panel value="workflow">
          <Stack gap="lg">
      <Paper radius="xl" p="lg">
        <Stack gap="lg">
          {/* Workflow header with ring progress */}
          <Group justify="space-between" align="center" wrap="nowrap">
            <Group gap="md" align="center" wrap="nowrap">
              <ThemeIcon size={44} radius="md" variant="light" color="edr-green">
                <RouteIcon size={22} />
              </ThemeIcon>
              <Stack gap={2}>
                <Title order={4} fw={700}>
                  Scheduling workflow
                </Title>
                <Text size="sm" c="dimmed">
                  {completedCount} of {stepsMeta.length} steps complete · expand any
                  step to edit
                </Text>
              </Stack>
            </Group>
            <RingProgress
              size={64}
              thickness={6}
              roundCaps
              sections={[{ value: progressPct, color: "edr-green" }]}
              label={
                <Text ta="center" size="xs" fw={700} c="edr-green.7">
                  {progressPct}%
                </Text>
              }
            />
          </Group>

          <WorkflowRail>
            {stepsMeta.map((step, index) => (
              <WorkflowStep
                key={step.key}
                index={index}
                icon={step.icon}
                title={step.title}
                subtitle={step.subtitle}
                state={
                  activeStep === index
                    ? "active"
                    : step.complete
                      ? "complete"
                      : "upcoming"
                }
                open={activeStep === index}
                onToggle={() => toggleStep(index)}
                rightSlot={renderStepRightSlot(step.key)}
              >
                {renderStepBody(step.key)}
              </WorkflowStep>
            ))}
          </WorkflowRail>
        </Stack>
      </Paper>

            {/* <ScheduleBatchPanel schedule={schedule} /> */}
          </Stack>
        </Tabs.Panel>

        <Tabs.Panel value="workspace">
          <ScheduleWorkspacePanel
            schedule={schedule}
            onChanged={() => {
              autoPreviewedRef.current = false;
              void detailQuery.refetch();
            }}
          />
          {scheduleId ? (
            <IntercityRideAlongPanel
              scheduleId={scheduleId}
              direction={schedule.direction}
            />
          ) : null}
        </Tabs.Panel>

        <Tabs.Panel value="legs">
          <LegCapacityPanel schedule={schedule} />
        </Tabs.Panel>

        <Tabs.Panel value="leg-board">
          <LegLoadBoardPanel
            schedule={schedule}
            onChanged={() => void detailQuery.refetch()}
          />
        </Tabs.Panel>

        <Tabs.Panel value="history">
          {scheduleId ? <ScheduleHistoryPanel scheduleId={scheduleId} /> : null}
        </Tabs.Panel>
      </Tabs>

      {scheduleId ? (
        <RescheduleTrainDialog
          scheduleId={scheduleId}
          currentBookingIds={(schedule.bookings ?? []).map((b) => b.id)}
          opened={maintenanceOpen}
          onClose={() => setMaintenanceOpen(false)}
          onComplete={() => void detailQuery.refetch()}
        />
      ) : null}

      <BookingWindowSettingsModal
        scheduleId={scheduleId ?? null}
        opened={windowSettingsOpen}
        onClose={() => setWindowSettingsOpen(false)}
        onSaved={() => void detailQuery.refetch()}
      />

      <LoadEmptyContainersModal
        opened={loadEmptiesOpen}
        onClose={() => setLoadEmptiesOpen(false)}
        schedule={schedule}
      />

      <MergeScheduleTrainModal
        scheduleId={scheduleId ?? null}
        currentTrainId={schedule.trainSet?.trainId ?? null}
        scheduleReference={schedule.reference ?? null}
        opened={mergeModalOpen}
        onClose={() => setMergeModalOpen(false)}
        onMerged={() => void detailQuery.refetch()}
      />

      <SwitchGovernmentBookingModal
        key={switchTarget?.id ?? "none"}
        opened={Boolean(switchTarget)}
        onClose={() => setSwitchTarget(null)}
        govBooking={switchTarget}
        assignedBookings={schedule.bookings ?? []}
        loading={switchGov.isPending}
        onConfirm={async (removeBookingIds) => {
          if (!scheduleId || !switchTarget) return;
          try {
            await switchGov.mutateAsync({
              id: scheduleId,
              governmentBookingId: switchTarget.id,
              removeBookingIds,
            });
            toast({ title: `Government booking ${switchTarget.reference} switched onto the train` });
            setSwitchTarget(null);
            setSelectedBookingIds([]);
            setPreviewResult(null);
            autoPreviewedRef.current = false;
          } catch (err) {
            toast({
              title: "Switch failed",
              description: parseError(err, "Could not switch the government booking"),
              variant: "destructive",
            });
          }
        }}
      />

      <Modal
        opened={dispatchConfirmOpen}
        onClose={() => setDispatchConfirmOpen(false)}
        centered
        radius="lg"
        title={
          <Group gap={8}>
            <Send size={18} />
            <Text fw={700}>Dispatch this train?</Text>
          </Group>
        }
      >
        <Stack gap="md">
          <Text size="sm" c="dimmed">
            Dispatch locks the composition and begins rail movement. This cannot be
            undone.
          </Text>

          <DateTimePicker
            label="Actual departure"
            description="When the train left — defaults to now; a past time is fine."
            value={dispatchAt}
            onChange={(v) => setDispatchAt(v ? new Date(v) : null)}
            maxDate={new Date()}
            valueFormat="DD MMM YYYY HH:mm"
            clearable={false}
            radius="md"
          />

          {hasDispatchWarnings ? (
            <Alert
              color="orange"
              variant="light"
              radius="md"
              icon={<AlertTriangle size={18} />}
              title="Some bookings are not fully ready"
            >
              <List size="sm" spacing={4}>
                {unassignedCount > 0 ? (
                  <List.Item>
                    <Text span fw={700}>
                      {unassignedCount}
                    </Text>{" "}
                    booking{unassignedCount === 1 ? "" : "s"} not assigned to a wagon
                  </List.Item>
                ) : null}
                {unloadedCount > 0 ? (
                  <List.Item>
                    <Text span fw={700}>
                      {unloadedCount}
                    </Text>{" "}
                    wagon-assigned booking{unloadedCount === 1 ? "" : "s"} not loaded
                    yet — mid-route boarders load from the track page when the train
                    reaches their yard
                  </List.Item>
                ) : null}
                {intercityNotLoadedCount > 0 ? (
                  <List.Item>
                    <Text span fw={700}>
                      {intercityNotLoadedCount}
                    </Text>{" "}
                    intercity ride-along{intercityNotLoadedCount === 1 ? "" : "s"} not
                    loaded yet — load them from the Workspace tab (Yard work) before
                    the train leaves their origin yard
                  </List.Item>
                ) : null}
              </List>
              <Text size="xs" c="dimmed" mt={6}>
                You can still dispatch — confirm to proceed.
              </Text>
            </Alert>
          ) : (
            <Alert
              color="edr-green"
              variant="light"
              radius="md"
              icon={<CheckCircle2 size={18} />}
            >
              All bookings are assigned to a wagon and marked loaded.
            </Alert>
          )}

          <Group justify="flex-end" gap="sm">
            <Button
              variant="default"
              onClick={() => setDispatchConfirmOpen(false)}
            >
              Cancel
            </Button>
            <Button
              color="edr-green"
              leftSection={<Send size={16} />}
              loading={dispatch.isPending}
              onClick={() => void runDispatch()}
            >
              {hasDispatchWarnings ? "Dispatch anyway" : "Dispatch train"}
            </Button>
          </Group>
        </Stack>
      </Modal>
      {visualization3DOpen ? (
        <Train3DVisualization schedule={schedule} onClose={() => setVisualization3DOpen(false)} />
      ) : null}
    </PageContainer>
  );
}
