import { useMemo, useState } from "react";
import { isAxiosError } from "axios";
import {
  Badge,
  Box,
  Button,
  Group,
  Modal,
  Paper,
  Progress,
  ScrollArea,
  Select,
  Stack,
  Text,
  ThemeIcon,
  Tooltip,
} from "@mantine/core";
import { useMutation, useQuery } from "@tanstack/react-query";
import {
  AlertTriangle,
  ArrowLeftRight,
  ArrowRight,
  CheckCircle2,
  Inbox,
  Landmark,
  MapPin,
  PackageCheck,
  PackageOpen,
  // Repeat, // used by the hidden Move (reassign) button
  Train,
  TrainFront,
  Truck,
  Weight,
  X,
} from "lucide-react";

import { CountdownTimer } from "@edr/ui-common";

import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
import { EntityLink } from "@/components/detail";
import { useAuth } from "@/auth/useAuth";
import { FREIGHT_PERMS, hasPermission as hasFreightPermission } from "@/lib/permissions";
import { api } from "@/services/api";
import { bookingsService } from "@/services/bookings.service";
import { useToast } from "@/hooks/use-toast";
import type {
  EligibleContainerBooking,
  FreightType,
  TrainScheduleDetail,
  YardWorkBookingRow,
} from "@/types/trainScheduling";

interface ScheduleWorkspacePanelProps {
  schedule: TrainScheduleDetail;
  /** Refetch the schedule detail after a mutation so both panels refresh. */
  onChanged: () => void;
}

const GREEN = "var(--mantine-color-edr-green-6)";

/** Pull the API's violation detail out of an error (e.g. "No CW3 wagon available…"). */
function apiErrorMessage(error: unknown, fallback: string): string {
  if (isAxiosError(error)) {
    const data = error.response?.data as Record<string, unknown> | undefined;
    const violations = data?.violations;
    if (Array.isArray(violations) && violations.length) return violations.join(", ");
    if (typeof data?.message === "string") return data.message;
    if (Array.isArray(data?.message)) return (data.message as string[]).join(", ");
  }
  return fallback;
}

/**
 * Deadline + label for the window phase this schedule is currently in.
 * Phases run: window open (windowClosesAt) → document review (docReviewEndsAt)
 * → payment (paymentPhaseEndsAt). Display only. Returns null off-phase.
 */
function phaseCountdown(
  schedule: TrainScheduleDetail,
): { label: string; deadline: string; expiredText: string } | null {
  switch (schedule.windowPhase) {
    case "PRE_WINDOW":
      return schedule.windowOpensAt
        ? {
            label: "Booking window opens in",
            deadline: schedule.windowOpensAt,
            expiredText: "Booking opening now…",
          }
        : null;
    case "OPEN":
      return schedule.windowClosesAt
        ? {
            label: "Booking window closes in",
            deadline: schedule.windowClosesAt,
            expiredText: "Document review starting…",
          }
        : null;
    case "DOC_REVIEW":
      return schedule.docReviewEndsAt
        ? {
            label: "Document review ends in",
            deadline: schedule.docReviewEndsAt,
            expiredText: "Payment starting…",
          }
        : null;
    case "PAYMENT":
      return schedule.paymentPhaseEndsAt
        ? {
            label: "Payment window ends in",
            deadline: schedule.paymentPhaseEndsAt,
            expiredText: "Payment window closing…",
          }
        : null;
    default:
      return null;
  }
}

/**
 * GROSS weight the locomotives actually haul: the HEAVIEST LEG, never the
 * whole-route sum — disjoint legs (Mojo→Dire + Dire→Doraleh) are pulled one
 * at a time, so summing every booking over-reports a multi-stop train.
 * Prefers the API's consist-derived heaviestLeg; before allocation it falls
 * back to a per-leg max over the bookings (same span math as the header strip).
 */
function usedWeight(schedule: TrainScheduleDetail): number {
  const consist = schedule.trainSet?.heaviestLeg?.grossWeightTons;
  if (consist != null) return Number(consist) || 0;

  const bookings = schedule.bookings ?? [];
  const stops = schedule.stops ?? [];
  if (stops.length <= 2) {
    return bookings.reduce((sum, b) => sum + (Number(b.weightTons) || 0), 0);
  }
  const indexOf = new Map(stops.map((s, i) => [s.yardId, i]));
  const lastIdx = stops.length - 1;
  let heaviest = 0;
  for (let edge = 0; edge < lastIdx; edge += 1) {
    let legTons = 0;
    for (const b of bookings) {
      const from = (b.originYardId ? indexOf.get(b.originYardId) : undefined) ?? 0;
      const toRaw = b.destinationYardId ? indexOf.get(b.destinationYardId) : lastIdx;
      const to = toRaw != null && toRaw > from ? toRaw : lastIdx;
      if (from <= edge && edge < to) legTons += Number(b.weightTons) || 0;
    }
    heaviest = Math.max(heaviest, legTons);
  }
  return heaviest;
}

/**
 * Pull capacity of the set. Locomotive pull weights ADD UP (they haul
 * together), so prefer the API's maxGrossWeightTons — the combined set limit
 * incl. overage tolerance, the same ceiling the validator holds each leg to —
 * and fall back to summing the locos' own limits.
 */
function pullCapacity(schedule: TrainScheduleDetail): number {
  if (schedule.maxGrossWeightTons != null) return Number(schedule.maxGrossWeightTons) || 0;
  const set = schedule.trainSet;
  if (!set) return 0;
  const locos =
    set.locomotives && set.locomotives.length > 0
      ? set.locomotives
      : set.locomotive
        ? [set.locomotive]
        : [];
  return locos.reduce((sum, l) => sum + (Number(l.maxPullWeightTons) || 0), 0);
}

export function ScheduleWorkspacePanel({
  schedule,
  onChanged,
}: ScheduleWorkspacePanelProps) {
  const { toast } = useToast();
  const { user } = useAuth();
  const canLoad = hasFreightPermission(user, FREIGHT_PERMS.trainScheduling.load);
  const canUnload = hasFreightPermission(user, FREIGHT_PERMS.trainScheduling.unload);

  const freightType: FreightType | undefined =
    schedule.freightType === "CONTAINER" || schedule.freightType === "BULK"
      ? schedule.freightType
      : undefined;

  const locked = ["DISPATCHED", "ARRIVED"].includes(schedule.status);
  const canManage = ["DRAFT", "SCHEDULED"].includes(schedule.status);
  // Loading follows the train: it keeps working AFTER dispatch, per yard, as
  // checkpoints are logged — only add/remove is closed once the train rolls.
  const canWork = ["DRAFT", "SCHEDULED", "DISPATCHED"].includes(schedule.status);

  // Pool = accepted, ready-to-pay bookings on THIS train's route+day that are not
  // yet linked to any schedule (same filter the auto-batch uses).
  const poolQuery = useQuery(
    api.trainScheduling.eligibleBookings.queryOptions({
      input: {
        filters: {
          originStationId: schedule.originStation?.id,
          destinationStationId: schedule.destinationStation?.id,
          trainScheduleId: schedule.id,
        },
        freightType,
      },
      enabled: Boolean(schedule.originStation?.id && schedule.destinationStation?.id),
    }),
  );

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

  const pool: EligibleContainerBooking[] = useMemo(
    () => (poolQuery.data?.items ?? []).filter((b) => !onTrainIds.has(b.id)),
    [poolQuery.data, onTrainIds],
  );

  const onTrain = schedule.bookings ?? [];

  // ── Corridor position: which yard the train currently stands at ───────────
  // The journey worklist knows the train's latest checkpoint AND per-booking
  // load/unload eligibility — the same server rules that gate the mutations.
  const yardWorkQuery = useQuery(
    api.trainScheduling.yardWork.queryOptions({
      input: { scheduleId: schedule.id },
      refetchInterval: 60_000,
    }),
  );
  const trainAtYardId = yardWorkQuery.data?.trainAtYardId ?? null;
  const journeyById = useMemo(() => {
    const map = new Map<string, YardWorkBookingRow>();
    for (const yard of yardWorkQuery.data?.yards ?? []) {
      for (const row of [...yard.toLoad, ...yard.toUnload]) map.set(row.id, row);
    }
    return map;
  }, [yardWorkQuery.data]);

  // Ordered corridor (origin → stops → destination). Falls back to the two
  // endpoints when the schedule has no stops recorded.
  const stations = useMemo(() => {
    const stops = schedule.stops ?? [];
    if (stops.length) return stops;
    return [
      { yardId: schedule.originStation?.id ?? "origin", label: schedule.originStation?.label ?? "Origin" },
      {
        yardId: schedule.destinationStation?.id ?? "destination",
        label: schedule.destinationStation?.label ?? "Destination",
      },
    ];
  }, [schedule.stops, schedule.originStation, schedule.destinationStation]);
  const stationIdx = useMemo(
    () => new Map(stations.map((s, i) => [s.yardId, i])),
    [stations],
  );
  const trainIdx = trainAtYardId != null ? (stationIdx.get(trainAtYardId) ?? null) : null;
  const trainAtLabel =
    trainIdx != null ? stations[trainIdx]?.label : null;

  // On-train bookings grouped by BOARDING yard, in corridor order. A booking
  // whose origin is off this corridor (through cargo on legacy data) groups
  // under the train's own origin.
  const corridorGroups = useMemo(() => {
    const groups = new Map<string, { yardId: string; label: string; rows: typeof onTrain }>();
    for (const b of onTrain) {
      const yardId =
        b.originYardId && stationIdx.has(b.originYardId)
          ? b.originYardId
          : (stations[0]?.yardId ?? "origin");
      let group = groups.get(yardId);
      if (!group) {
        group = {
          yardId,
          label:
            stations[stationIdx.get(yardId) ?? 0]?.label ?? b.origin ?? "Origin",
          rows: [],
        };
        groups.set(yardId, group);
      }
      group.rows.push(b);
    }
    return [...groups.values()].sort(
      (a, b) => (stationIdx.get(a.yardId) ?? 0) - (stationIdx.get(b.yardId) ?? 0),
    );
  }, [onTrain, stationIdx, stations]);

  // ── Mutations (reuse the existing endpoints) ───────────────────────────────
  const assign = useMutation(api.trainScheduling.assignBookings.mutationOptions());
  const assignUnassigned = useMutation(
    api.trainScheduling.assignUnassignedBooking.mutationOptions(),
  );
  const unassign = useMutation(api.trainScheduling.unassignBooking.mutationOptions());
  const loadJourney = useMutation(
    api.trainScheduling.loadScheduleBooking.mutationOptions(),
  );
  const unloadJourney = useMutation(
    api.trainScheduling.unloadScheduleBooking.mutationOptions(),
  );
  const moveSchedule = useMutation(
    api.trainScheduling.moveBookingSchedule.mutationOptions(),
  );

  const [moveBookingId, setMoveBookingId] = useState<string | null>(null);
  const [moveTarget, setMoveTarget] = useState<string | null>(null);

  // Pool → pick a same-day schedule with free wagons and place the booking there.
  const [poolAssign, setPoolAssign] = useState<{ id: string; reference: string } | null>(
    null,
  );
  const [poolTarget, setPoolTarget] = useState<string | null>(null);

  const { data: targets } = useQuery(
    api.trainScheduling.bookableSchedules.queryOptions({
      input: {
        originYardId: schedule.originStation?.id,
        destinationYardId: schedule.destinationStation?.id,
      },
      enabled: Boolean(
        schedule.originStation?.id && schedule.destinationStation?.id,
      ),
    }),
  );
  const moveOptions = useMemo(
    () =>
      (targets ?? [])
        .filter((s) => s.id !== schedule.id)
        .map((s) => ({
          value: s.id,
          label: `${s.routeName ?? `${s.origin} → ${s.destination}`} · ${new Date(
            s.scheduleDate,
          ).toLocaleString()} · ${s.remainingWagons}/${s.maxWagons} free`,
        })),
    [targets, schedule.id],
  );

  // Every schedule departing on THIS train's day (EAT) — a paid booking waiting
  // for a wagon may board any of them, so staff pick whichever has wagons free.
  const eatDayOf = (iso: string) =>
    new Date(iso).toLocaleDateString("en-CA", { timeZone: "Africa/Addis_Ababa" });
  const sameDayOptions = useMemo(() => {
    const day = eatDayOf(schedule.scheduledDepartureDate);
    return (targets ?? [])
      .filter((s) => eatDayOf(s.scheduleDate) === day)
      .map((s) => ({
        value: s.id,
        label: `${s.id === schedule.id ? "This train · " : ""}${
          s.routeName ?? `${s.origin} → ${s.destination}`
        } · ${s.remainingWagons}/${s.maxWagons} wagons free`,
      }));
  }, [targets, schedule.id, schedule.scheduledDepartureDate]);

  // ── Capacity meter (by cargo weight vs locomotive pull) ────────────────────
  const used = usedWeight(schedule);
  const capacity = pullCapacity(schedule);
  const pct = capacity > 0 ? Math.min(100, Math.round((used / capacity) * 100)) : 0;
  const over = capacity > 0 && used > capacity;

  // One confirmation dialog for every booking action; the action fires only
  // after staff confirm, and the existing toasts report the outcome.
  const [confirmAction, setConfirmAction] = useState<{
    kind: "add" | "load" | "truckToTrain" | "unload" | "remove";
    bookingId: string;
    ref: string;
    weightTons?: number;
  } | null>(null);
  const confirmMeta: Record<
    NonNullable<typeof confirmAction>["kind"],
    { title: string; message: string; color: string; confirmLabel: string }
  > = {
    add: {
      title: "Add booking to this train?",
      message:
        "The booking is assigned to this departure and wagons are auto-pinned. Adding past the pull-weight limit is allowed but flagged for review.",
      color: "edr-green",
      confirmLabel: "Add to train",
    },
    load: {
      title: "Load cargo onto the train?",
      message:
        "Stamps the booking as loaded at this yard. The server checks the train is actually standing here.",
      color: "edr-green",
      confirmLabel: "Load",
    },
    truckToTrain: {
      title: "Load as direct truck-to-train?",
      message:
        "Sets direct truck-to-train handover (no warehouse receipt, no GRN — the carriage acceptance sheet becomes the handover document) and loads the cargo.",
      color: "blue",
      confirmLabel: "Load direct",
    },
    unload: {
      title: "Unload cargo at this yard?",
      message: "Stamps the booking's arrival at this yard and frees its wagons for reuse.",
      color: "orange",
      confirmLabel: "Unload",
    },
    remove: {
      title: "Remove booking from this train?",
      message:
        "Returns the booking to the unassigned pool, writes a removal log entry, and notifies the customer.",
      color: "red",
      confirmLabel: "Remove",
    },
  };
  const runConfirmedAction = () => {
    if (!confirmAction) return;
    const { kind, bookingId, ref, weightTons } = confirmAction;
    setConfirmAction(null);
    if (kind === "add") forceAdd(bookingId, ref, weightTons ?? 0);
    else if (kind === "load") doLoad(bookingId, ref);
    else if (kind === "truckToTrain") doTruckToTrain(bookingId, ref);
    else if (kind === "unload") doUnload(bookingId, ref);
    else removeFromTrain(bookingId, ref);
  };

  const forceAdd = (bookingId: string, ref: string, weightTons: number) => {
    const wouldOverfill = capacity > 0 && used + (weightTons || 0) > capacity;
    assign
      .mutateAsync({
        id: schedule.id,
        freightType,
        payload: {
          bookingIds: [...onTrainIds, bookingId],
          forceAssign: true,
        },
      })
      .then(() => {
        toast({
          title: `${ref} added to train`,
          description: wouldOverfill
            ? "Force-added past the pull-weight limit — review capacity."
            : "Wagons auto-pinned.",
          variant: wouldOverfill ? "destructive" : undefined,
        });
        onChanged();
        void poolQuery.refetch();
      })
      .catch((error) =>
        toast({
          title: "Could not add booking",
          description: apiErrorMessage(error, "Validation failed — check capacity and status."),
          variant: "destructive",
        }),
      );
  };

  const removeFromTrain = (bookingId: string, ref: string) => {
    unassign
      .mutateAsync({ id: schedule.id, bookingId })
      .then(() => {
        toast({ title: `${ref} removed from train` });
        onChanged();
        void poolQuery.refetch();
      })
      .catch((error) =>
        toast({
          title: "Could not remove booking",
          description: apiErrorMessage(error, "Please try again."),
          variant: "destructive",
        }),
      );
  };

  // Journey load/unload — the server checks the train's recorded position, so
  // a stale UI can never load cargo at the wrong yard.
  const doLoad = (bookingId: string, ref: string) => {
    loadJourney
      .mutateAsync({ scheduleId: schedule.id, bookingId })
      .then(() => {
        toast({ title: `${ref} loaded onto the train` });
        onChanged();
        void yardWorkQuery.refetch();
      })
      .catch((error) =>
        toast({
          title: "Could not load cargo",
          description: apiErrorMessage(error, "Train may not be at the boarding yard."),
          variant: "destructive",
        }),
      );
  };

  // Export cargo that skipped the warehouse (customer truck straight onto the
  // wagon) has no GRN and never will — loadBooking's GRN gate would keep
  // rejecting it forever. Setting DIRECT_TO_TRAIN tells that gate the carriage
  // acceptance sheet is the handover document instead, then loads in one click.
  const [truckToTrainPending, setTruckToTrainPending] = useState<string | null>(null);
  const doTruckToTrain = (bookingId: string, ref: string) => {
    setTruckToTrainPending(bookingId);
    bookingsService
      .setExportHandoverMode(bookingId, "DIRECT_TO_TRAIN")
      .then(() => loadJourney.mutateAsync({ scheduleId: schedule.id, bookingId }))
      .then(() => {
        toast({ title: `${ref} loaded — direct truck-to-train handover` });
        onChanged();
        void yardWorkQuery.refetch();
      })
      .catch((error) =>
        toast({
          title: "Could not load as direct truck-to-train",
          description: apiErrorMessage(error, "Please try again."),
          variant: "destructive",
        }),
      )
      .finally(() => setTruckToTrainPending(null));
  };

  const doUnload = (bookingId: string, ref: string) => {
    unloadJourney
      .mutateAsync({ scheduleId: schedule.id, bookingId })
      .then((result) => {
        toast({
          title:
            result.status === "COMPLETED"
              ? `${ref} unloaded — booking completed`
              : `${ref} unloaded — booking arrived`,
        });
        onChanged();
        void yardWorkQuery.refetch();
      })
      .catch((error) =>
        toast({
          title: "Could not unload cargo",
          description: apiErrorMessage(error, "Train may not be at the destination yard."),
          variant: "destructive",
        }),
      );
  };

  // Point the pool booking at the chosen same-day train, then put it on wagons.
  // If the wagon step fails (that train is short too) the booking stays paid &
  // unassigned in the pool — nothing is lost, staff just pick another train.
  const doPoolAssign = () => {
    if (!poolAssign || !poolTarget) return;
    const { id: bookingId, reference } = poolAssign;
    moveSchedule
      .mutateAsync({ bookingId, trainScheduleId: poolTarget })
      .then(() => assignUnassigned.mutateAsync({ id: poolTarget, bookingId }))
      .then(() => {
        toast({
          title: `${reference} assigned`,
          description: "Booking placed on the selected train with wagons pinned.",
        });
        setPoolAssign(null);
        onChanged();
        void poolQuery.refetch();
      })
      .catch((error) =>
        toast({
          title: `Could not assign ${reference}`,
          description: apiErrorMessage(
            error,
            "The selected train has no free wagon of the required type.",
          ),
          variant: "destructive",
        }),
      );
  };

  const doMove = () => {
    if (!moveBookingId || !moveTarget) return;
    moveSchedule
      .mutateAsync({ bookingId: moveBookingId, trainScheduleId: moveTarget })
      .then(() => {
        toast({ title: "Booking reassigned to another train" });
        setMoveBookingId(null);
        onChanged();
        void poolQuery.refetch();
      })
      .catch((error) =>
        toast({
          title: "Could not reassign booking",
          description: apiErrorMessage(error, "Target train may be closed or full."),
          variant: "destructive",
        }),
      );
  };

  return (
    <Paper radius="xl" p="lg" withBorder style={{ borderColor: "var(--mantine-color-gray-2)" }}>
      <Stack gap="lg">
        {/* Header + capacity meter */}
        <Group justify="space-between" align="flex-start" wrap="wrap" gap="md">
          <Group gap="sm" align="center" wrap="nowrap">
            <ThemeIcon size={40} radius="md" variant="light" color="edr-green">
              <PackageCheck size={20} />
            </ThemeIcon>
            <div>
              <Text fw={700}>Allocation workspace</Text>
              <Text size="xs" c="dimmed">
                Add or remove bookings, then load each one when the train is at
                its boarding yard
              </Text>
            </div>
          </Group>

          <Box miw={240} style={{ flex: "0 1 320px" }}>
            <Group justify="space-between" mb={4} gap={4}>
              <Group gap={6} align="center">
                <Weight size={14} color={over ? "#B42318" : undefined} />
                <Text size="xs" fw={600} c={over ? "red" : "dimmed"}>
                  Load {used.toFixed(1)}T
                  {capacity > 0 ? ` / ${capacity.toFixed(0)}T pull` : ""}
                  {(schedule.stops?.length ?? 0) > 2 ? " · heaviest leg" : ""}
                </Text>
              </Group>
              {over ? (
                <Badge color="red" variant="light" size="sm" radius="sm">
                  Over capacity
                </Badge>
              ) : (
                <Text size="xs" c="dimmed">
                  {capacity > 0 ? `${pct}%` : "—"}
                </Text>
              )}
            </Group>
            <Progress
              value={capacity > 0 ? pct : 0}
              color={over ? "red" : pct > 85 ? "orange" : "edr-green"}
              radius="xl"
              size="md"
            />
          </Box>
        </Group>

        {(() => {
          const cd = phaseCountdown(schedule);
          return cd ? (
            <Group
              gap={8}
              p="xs"
              wrap="nowrap"
              align="center"
              style={{
                borderRadius: 10,
                background: "var(--mantine-color-blue-0)",
                border: "1px solid var(--mantine-color-blue-2)",
              }}
            >
              <CountdownTimer
                deadline={cd.deadline}
                label={cd.label}
                expiredText={cd.expiredText}
                size="sm"
              />
            </Group>
          ) : null;
        })()}

        {over ? (
          <Group
            gap={8}
            p="xs"
            wrap="nowrap"
            align="center"
            style={{
              borderRadius: 10,
              background: "var(--mantine-color-red-0)",
              border: "1px solid var(--mantine-color-red-2)",
            }}
          >
            <AlertTriangle size={16} color="#B42318" />
            <Text size="xs" c="red.8" fw={500}>
              This train is loaded beyond its locomotive pull weight. Force-adds are
              allowed, but review before dispatch.
            </Text>
          </Group>
        ) : null}

        {locked ? (
          <Text size="sm" c="dimmed">
            This train is {schedule.status.toLowerCase()} — bookings can no longer be
            added or removed.
            {schedule.status === "DISPATCHED"
              ? " Loading continues per yard as checkpoints are logged on the track page."
              : ""}
          </Text>
        ) : null}

        {/* Loading confirmation gate removed: bookings can board mid-corridor,
            so per-yard loading happens from the track page's log-pass flow. */}

        {/* Two-panel board */}
        <Group align="stretch" gap="lg" grow wrap="wrap">
          {/* Pool */}
          <PanelColumn
            title="Paid · unassigned"
            hint="Paid · this route & day · not on a train"
            count={pool.length}
            accent="#F2A516"
            loading={poolQuery.isLoading}
            emptyIcon={Inbox}
            emptyText="No paid, unassigned bookings waiting for this train."
          >
            {pool.map((b) => (
              <BookingCard
                key={b.id}
                bookingId={b.id}
                reference={b.reference}
                customer={b.customer}
                weightTons={b.weightTons}
                status={b.status}
                waitingForWagon={b.schedulingStatus === "WAITING_FOR_WAGON"}
                government={b.isGovernment}
                leg={
                  b.origin && b.destination ? `${b.origin} → ${b.destination}` : null
                }
                right={
                  canManage ? (
                    <Group gap={6} wrap="nowrap" justify="flex-end">
                      <Tooltip label="Force-add to this train" withArrow>
                        <Button
                          size="compact-sm"
                          color="edr-green"
                          radius="md"
                          rightSection={<ArrowRight size={14} />}
                          loading={assign.isPending}
                          onClick={() =>
                            setConfirmAction({
                              kind: "add",
                              bookingId: b.id,
                              ref: b.reference,
                              weightTons: b.weightTons,
                            })
                          }
                        >
                          Add
                        </Button>
                      </Tooltip>
                      <Tooltip
                        label="Pick any train departing this day that has wagons free"
                        withArrow
                      >
                        <Button
                          size="compact-sm"
                          variant="light"
                          color="edr-green"
                          radius="md"
                          leftSection={<ArrowLeftRight size={13} />}
                          onClick={() => {
                            setPoolAssign({ id: b.id, reference: b.reference });
                            setPoolTarget(null);
                          }}
                        >
                          Add to…
                        </Button>
                      </Tooltip>
                    </Group>
                  ) : null
                }
              />
            ))}
          </PanelColumn>

          {/* On train — grouped by boarding yard, walked in corridor order.
              Load is only offered where the train actually stands; the journey
              endpoints re-validate the position server-side. */}
          <PanelColumn
            title="On this train"
            hint={
              trainAtLabel ? `Train at ${trainAtLabel}` : "Grouped by boarding yard"
            }
            count={onTrain.length}
            accent="#0EA371"
            emptyIcon={Train}
            emptyText="No bookings allocated yet. Add one from the pool."
          >
            {corridorGroups.map((group) => {
              const groupIdx = stationIdx.get(group.yardId) ?? 0;
              const trainHere = trainAtYardId === group.yardId;
              const passed = trainIdx != null && groupIdx < trainIdx;
              return (
                <Stack key={group.yardId} gap={6}>
                  <Group gap={8} align="center" mt={4}>
                    <MapPin size={13} color="var(--mantine-color-gray-6)" />
                    <Text size="xs" fw={700}>
                      {group.label}
                    </Text>
                    {trainHere ? (
                      <Badge
                        size="sm"
                        radius="sm"
                        variant="filled"
                        color="edr-green"
                        leftSection={<TrainFront size={11} />}
                      >
                        Train here
                      </Badge>
                    ) : passed ? (
                      <Badge size="sm" radius="sm" variant="light" color="gray">
                        Passed
                      </Badge>
                    ) : (
                      <Badge size="sm" radius="sm" variant="outline" color="gray">
                        Ahead
                      </Badge>
                    )}
                    <Badge size="sm" radius="sm" variant="light" color="gray">
                      {group.rows.length}
                    </Badge>
                  </Group>
                  {group.rows.map((b) => {
                    const ref = b.reference ?? b.id.slice(0, 8);
                    const journey = journeyById.get(b.id);
                    const riding = b.status === "IN_TRANSIT";
                    const done = ["ARRIVED", "COMPLETED", "DELIVERED"].includes(
                      b.status ?? "",
                    );
                    const boardHere = trainHere;
                    const alightHere = trainAtYardId != null && b.destinationYardId === trainAtYardId;
                    const showLoad = canWork && !riding && !done && (journey?.canLoad ?? false);
                    const showUnload = canWork && riding && (journey?.canUnload ?? false);
                    const showTruckToTrain =
                      canWork &&
                      !riding &&
                      !done &&
                      boardHere &&
                      b.tradeDirection === "EXPORT";
                    return (
                      <BookingCard
                        key={b.id}
                        bookingId={b.id}
                        reference={ref}
                        customer={b.customer}
                        weightTons={b.weightTons}
                        status={b.status}
                        government={journey?.isGovernment ?? false}
                        intercity={b.tradeDirection === "DOMESTIC"}
                        leg={
                          b.origin &&
                          b.destination &&
                          (b.originYardId !== schedule.originStation?.id ||
                            b.destinationYardId !== schedule.destinationStation?.id)
                            ? `${b.origin} → ${b.destination}`
                            : null
                        }
                        loadingStatus={
                          riding || Boolean(b.loadedAt)
                            ? "LOADED"
                            : b.wagonAssigned
                              ? (b.loadingStatus ?? "UNLOADED")
                              : undefined
                        }
                        right={
                          <Group gap={6} wrap="nowrap" justify="flex-end">
                            {showLoad ? (
                              <Tooltip
                                label={
                                  !canLoad
                                    ? "You don't have permission to load cargo"
                                    : boardHere
                                      ? `Load cargo onto the train at ${group.label}`
                                      : passed
                                        ? `Train already passed ${group.label} — this cargo missed its stop`
                                        : `Loads at ${group.label} — train is ${
                                            trainAtLabel ? `at ${trainAtLabel}` : "not there yet"
                                          }`
                                }
                                withArrow
                              >
                                <Button
                                  size="compact-sm"
                                  variant="filled"
                                  color="edr-green"
                                  radius="md"
                                  disabled={!boardHere || !canLoad}
                                  leftSection={<PackageCheck size={13} />}
                                  loading={
                                    loadJourney.isPending &&
                                    loadJourney.variables?.bookingId === b.id
                                  }
                                  onClick={() =>
                                    setConfirmAction({ kind: "load", bookingId: b.id, ref })
                                  }
                                >
                                  Load
                                </Button>
                              </Tooltip>
                            ) : null}
                            {showTruckToTrain ? (
                              <Tooltip
                                label={
                                  canLoad
                                    ? "Customer truck loaded straight onto the wagon — no warehouse receipt, no GRN. Sets direct truck-to-train handover and loads."
                                    : "You don't have permission to load cargo"
                                }
                                withArrow
                              >
                                <Button
                                  size="compact-sm"
                                  variant="light"
                                  color="blue"
                                  radius="md"
                                  disabled={!canLoad}
                                  leftSection={<Truck size={13} />}
                                  loading={truckToTrainPending === b.id}
                                  onClick={() =>
                                    setConfirmAction({
                                      kind: "truckToTrain",
                                      bookingId: b.id,
                                      ref,
                                    })
                                  }
                                >
                                  Truck to Train
                                </Button>
                              </Tooltip>
                            ) : null}
                            {showUnload ? (
                              <Tooltip
                                label={
                                  !canUnload
                                    ? "You don't have permission to unload cargo"
                                    : alightHere
                                      ? "Unload at this yard — stamps the booking's arrival"
                                      : "Unloads when the train reaches its destination yard"
                                }
                                withArrow
                              >
                                <Button
                                  size="compact-sm"
                                  variant="light"
                                  color="orange"
                                  radius="md"
                                  disabled={!alightHere || !canUnload}
                                  leftSection={<PackageOpen size={13} />}
                                  loading={
                                    unloadJourney.isPending &&
                                    unloadJourney.variables?.bookingId === b.id
                                  }
                                  onClick={() =>
                                    setConfirmAction({ kind: "unload", bookingId: b.id, ref })
                                  }
                                >
                                  Unload
                                </Button>
                              </Tooltip>
                            ) : null}
                            {canManage && !riding && !done ? (
                              journey?.isGovernment ? null : (
                                <Tooltip label="Remove from this train" withArrow>
                                  <Button
                                    size="compact-sm"
                                    variant="light"
                                    color="red"
                                    radius="md"
                                    leftSection={<X size={13} />}
                                    loading={
                                      unassign.isPending &&
                                      unassign.variables?.bookingId === b.id
                                    }
                                    onClick={() =>
                                      setConfirmAction({ kind: "remove", bookingId: b.id, ref })
                                    }
                                  >
                                    Remove
                                  </Button>
                                </Tooltip>
                              )
                            ) : null}
                          </Group>
                        }
                      />
                    );
                  })}
                </Stack>
              );
            })}
          </PanelColumn>
        </Group>
      </Stack>

      {/* Pool → same-day train assignment modal */}
      <Modal
        opened={Boolean(poolAssign)}
        onClose={() => setPoolAssign(null)}
        title={
          <Group gap={8}>
            <Train size={18} />
            <Text fw={700}>
              Assign {poolAssign?.reference ?? "booking"} to a train on this day
            </Text>
          </Group>
        }
        centered
        radius="lg"
      >
        <Stack gap="md">
          <Text size="xs" c="dimmed">
            All open trains departing on this schedule&apos;s day. Pick one with
            free wagons — the booking is placed and its wagons pinned in one step.
          </Text>
          <Select
            label="Target train (same day)"
            placeholder="Select a departure"
            data={sameDayOptions}
            value={poolTarget}
            onChange={setPoolTarget}
            searchable
            nothingFoundMessage="No open schedules depart on this day"
          />
          <Group justify="flex-end">
            <Button variant="default" onClick={() => setPoolAssign(null)}>
              Cancel
            </Button>
            <Button
              color="edr-green"
              disabled={!poolTarget}
              loading={moveSchedule.isPending || assignUnassigned.isPending}
              leftSection={<CheckCircle2 size={16} />}
              onClick={doPoolAssign}
            >
              Assign to train
            </Button>
          </Group>
        </Stack>
      </Modal>

      {/* Reassign modal */}
      <Modal
        opened={Boolean(moveBookingId)}
        onClose={() => setMoveBookingId(null)}
        title={
          <Group gap={8}>
            <ArrowLeftRight size={18} />
            <Text fw={700}>Reassign booking to another train</Text>
          </Group>
        }
        centered
        radius="lg"
      >
        <Stack gap="md">
          <Select
            label="Target train (same route, open window)"
            placeholder="Select an open schedule"
            data={moveOptions}
            value={moveTarget}
            onChange={setMoveTarget}
            searchable
            nothingFoundMessage="No other open schedules on this route"
          />
          <Group justify="flex-end">
            <Button variant="default" onClick={() => setMoveBookingId(null)}>
              Cancel
            </Button>
            <Button
              color="edr-green"
              disabled={!moveTarget}
              loading={moveSchedule.isPending}
              leftSection={<CheckCircle2 size={16} />}
              onClick={doMove}
            >
              Reassign
            </Button>
          </Group>
        </Stack>
      </Modal>

      {/* Confirm add / load / unload / remove */}
      <Modal
        opened={Boolean(confirmAction)}
        onClose={() => setConfirmAction(null)}
        centered
        radius="lg"
        size="md"
        withCloseButton={false}
        title={
          confirmAction ? (
            <Group gap={10} wrap="nowrap">
              <ThemeIcon
                size={40}
                radius="md"
                variant="light"
                color={confirmMeta[confirmAction.kind].color}
              >
                {confirmAction.kind === "remove" ? (
                  <X size={21} />
                ) : confirmAction.kind === "unload" ? (
                  <PackageOpen size={21} />
                ) : confirmAction.kind === "truckToTrain" ? (
                  <Truck size={21} />
                ) : (
                  <PackageCheck size={21} />
                )}
              </ThemeIcon>
              <div>
                <Text fw={800}>{confirmMeta[confirmAction.kind].title}</Text>
                <Text size="xs" c="dimmed">
                  {confirmAction.ref}
                </Text>
              </div>
            </Group>
          ) : null
        }
      >
        {confirmAction ? (
          <Stack gap="md">
            <Text size="sm">{confirmMeta[confirmAction.kind].message}</Text>
            {confirmAction.kind === "add" &&
            capacity > 0 &&
            used + (confirmAction.weightTons ?? 0) > capacity ? (
              <Group
                gap={8}
                p="xs"
                wrap="nowrap"
                style={{
                  borderRadius: 10,
                  background: "var(--mantine-color-red-0)",
                  border: "1px solid var(--mantine-color-red-2)",
                }}
              >
                <AlertTriangle size={16} color="#B42318" />
                <Text size="xs" c="red.8" fw={500}>
                  This add pushes the heaviest leg past the locomotive pull weight (
                  {(used + (confirmAction.weightTons ?? 0)).toFixed(1)}T / {capacity.toFixed(0)}T).
                </Text>
              </Group>
            ) : null}
            <Group justify="flex-end" gap="sm">
              <Button variant="default" radius="md" onClick={() => setConfirmAction(null)}>
                Cancel
              </Button>
              <Button
                color={confirmMeta[confirmAction.kind].color}
                radius="md"
                onClick={runConfirmedAction}
              >
                {confirmMeta[confirmAction.kind].confirmLabel}
              </Button>
            </Group>
          </Stack>
        ) : null}
      </Modal>
    </Paper>
  );
}

// ── Sub-components ───────────────────────────────────────────────────────────

function PanelColumn({
  title,
  hint,
  count,
  accent,
  loading,
  emptyIcon: EmptyIcon,
  emptyText,
  children,
}: {
  title: string;
  hint: string;
  count: number;
  accent: string;
  loading?: boolean;
  emptyIcon: typeof Inbox;
  emptyText: string;
  children: React.ReactNode;
}) {
  const isEmpty = !loading && count === 0;
  return (
    <Paper
      radius="lg"
      withBorder
      p="md"
      miw={280}
      style={{
        flex: 1,
        borderColor: "var(--mantine-color-gray-2)",
        background: `linear-gradient(180deg, ${accent}0A 0%, transparent 90px)`,
      }}
    >
      <Group justify="space-between" align="center" mb="sm">
        <Group gap={8} align="center">
          <Box w={8} h={8} style={{ borderRadius: 999, background: accent }} />
          <Text fw={700} size="sm">
            {title}
          </Text>
          <Badge variant="light" color="gray" radius="sm" size="sm">
            {count}
          </Badge>
        </Group>
        <Text size="xs" c="dimmed">
          {hint}
        </Text>
      </Group>

      {isEmpty ? (
        <Stack align="center" gap={6} py={32}>
          <EmptyIcon size={24} color="var(--mantine-color-gray-4)" />
          <Text size="xs" c="dimmed" ta="center" maw={220}>
            {emptyText}
          </Text>
        </Stack>
      ) : (
        <ScrollArea.Autosize mah={420} type="hover">
          <Stack gap={8} pr={4}>
            {loading ? (
              <Text size="xs" c="dimmed" py="md" ta="center">
                Loading…
              </Text>
            ) : (
              children
            )}
          </Stack>
        </ScrollArea.Autosize>
      )}
    </Paper>
  );
}

function BookingCard({
  bookingId,
  reference,
  customer,
  weightTons,
  status,
  loadingStatus,
  waitingForWagon,
  intercity,
  government,
  leg,
  right,
}: {
  /** When set, the reference links to the booking's detail page. */
  bookingId?: string;
  reference: string;
  customer?: string | null;
  weightTons?: number | null;
  status?: string | null;
  loadingStatus?: "LOADED" | "UNLOADED";
  /** Paid, but no wagon of the required type was free — waiting for one. */
  waitingForWagon?: boolean;
  /** DOMESTIC ride-along riding only part of this train's corridor. */
  intercity?: boolean;
  /** Government booking — remove is blocked, only switch. */
  government?: boolean;
  /** "Origin → Destination" when the booking rides a sub-corridor leg. */
  leg?: string | null;
  right?: React.ReactNode;
}) {
  return (
    <Paper
      radius="md"
      withBorder
      p="sm"
      style={{
        borderColor: "var(--mantine-color-gray-2)",
        transition: "border-color 120ms ease, box-shadow 120ms ease",
      }}
      onMouseEnter={(e) => {
        e.currentTarget.style.borderColor = GREEN;
      }}
      onMouseLeave={(e) => {
        e.currentTarget.style.borderColor = "var(--mantine-color-gray-2)";
      }}
    >
      <Group justify="space-between" align="center" wrap="nowrap" gap="sm">
        <Stack gap={3} style={{ minWidth: 0 }}>
          <Group gap={8} align="center" wrap="nowrap">
            {bookingId ? (
              <EntityLink
                to={`/dashboard/booking-requests/${bookingId}`}
                label={reference}
                size="sm"
                fw={700}
              />
            ) : (
              <Text size="sm" fw={700} truncate>
                {reference}
              </Text>
            )}
            {status ? <BookingStatusBadge status={status} /> : null}
            {intercity ? (
              <Tooltip
                label="Intercity ride-along — rides only its own leg of this train's corridor"
                withArrow
              >
                <Badge size="sm" radius="sm" variant="filled" color="indigo">
                  Intercity
                </Badge>
              </Tooltip>
            ) : null}
            {government ? (
              <Tooltip
                label="Government booking — cannot be removed, only switched"
                withArrow
              >
                <Badge
                  size="sm"
                  radius="sm"
                  variant="light"
                  color="yellow"
                  leftSection={<Landmark size={10} />}
                >
                  Government
                </Badge>
              </Tooltip>
            ) : null}
            {waitingForWagon ? (
              <Tooltip
                label="Paid, but no wagon of the required type was free. Free a wagon or assign it to a same-day train that has one."
                withArrow
              >
                <Badge size="sm" radius="sm" variant="light" color="orange">
                  Waiting for wagon
                </Badge>
              </Tooltip>
            ) : null}
            {loadingStatus ? (
              <Badge
                size="sm"
                radius="sm"
                variant={loadingStatus === "LOADED" ? "filled" : "light"}
                color={loadingStatus === "LOADED" ? "edr-green" : "gray"}
              >
                {loadingStatus === "LOADED" ? "Loaded" : "Unloaded"}
              </Badge>
            ) : null}
          </Group>
          <Group gap={10} align="center" wrap="nowrap">
            <Text size="xs" c="dimmed" truncate>
              {customer ?? "—"}
            </Text>
            {weightTons != null ? (
              <Group gap={3} align="center" wrap="nowrap">
                <Weight size={11} color="var(--mantine-color-gray-5)" />
                <Text size="xs" c="dimmed">
                  {Number(weightTons).toFixed(1)}T
                </Text>
              </Group>
            ) : null}
            {leg ? (
              <Text size="xs" c="indigo.7" fw={600} style={{ whiteSpace: "nowrap" }}>
                {leg}
              </Text>
            ) : null}
          </Group>
        </Stack>
        {right ? <Box style={{ flexShrink: 0 }}>{right}</Box> : null}
      </Group>
    </Paper>
  );
}
