import type { ColumnDef } from "@edr/ui-common";
import {
  ActionIcon,
  Badge,
  Box,
  Button,
  Card,
  Checkbox,
  Group,
  Menu,
  Modal,
  Select,
  SimpleGrid,
  Stack,
  Text,
  TextInput,
  ThemeIcon,
} from "@mantine/core";
import { useDebouncedValue } from "@mantine/hooks";
import { isAxiosError } from "axios";
import {
  ArrowRight,
  Ban,
  CalendarClock,
  Clock,
  Eye,
  MoreHorizontal,
  Navigation,
  Pencil,
  Play,
  Send,
  Train,
  Weight,
} from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";

import FleetToolbar from "@/components/fleet/FleetToolbar";
import { useFleetViewMode } from "@/components/fleet/useFleetViewMode";
import { KpiStrip, PageContainer, PageHeader } from "@/components/page";
import RuleEngineListFooter from "@/components/ruleEngine/RuleEngineListFooter";
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
import { FreightTypeBadge } from "@/components/trainScheduling/ScheduleStatusBadge";
import {
  directionColor,
  directionRowStyle,
} from "@/components/trainBuilder/trainStatus";
import BookingWindowSettingsModal from "@/components/trainScheduling/BookingWindowSettingsModal";
import EditScheduleDateModal from "@/components/trainScheduling/EditScheduleDateModal";
import { showScheduleWarnings } from "@/components/trainScheduling/locomotiveOptions";
import {
  RouteCorridor,
  StatusPill,
} from "@/components/trainScheduling/scheduleVisuals";
import { keepPreviousData, useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import { formatRouteLabel } from "@/services/routes.service";
import { useToast } from "@/hooks/use-toast";
import { useAuth } from "@/auth/useAuth";
import { canCreateSchedule, canUpdateSchedule } from "@/lib/permissions";
import type {
  FreightType,
  TrainScheduleListFilters,
  TrainScheduleListItem,
  TrainScheduleStatus,
} from "@/types/trainScheduling";
import { DataTable, DataTableFooter, usePagination } from "@edr/ui-common";

/** `min` for a `datetime-local` input: now, in the browser's local zone. */
const nowLocalDateTime = () => {
  const now = new Date();
  now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
  return now.toISOString().slice(0, 16);
};

const splitDate = (value?: string | null) => {
  if (!value) return { day: "—", time: "" };
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return { day: "—", time: "" };
  return {
    day: new Intl.DateTimeFormat("en", {
      month: "short",
      day: "2-digit",
      year: "numeric",
    }).format(date),
    time: new Intl.DateTimeFormat("en", {
      hour: "2-digit",
      minute: "2-digit",
    }).format(date),
  };
};

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

export default function TrainScheduleV2ListPage() {
  const navigate = useNavigate();
  const { toast } = useToast();
  const { user } = useAuth();
  const canCreate = canCreateSchedule(user);
  const canUpdate = canUpdateSchedule(user);
  const { viewMode, setViewMode } = useFleetViewMode("train-scheduling-v2");
  const { pagination, setPagination } = usePagination({ pageSize: 10 });
  const [search, setSearch] = useState("");
  const [debouncedSearch] = useDebouncedValue(search, 300);
  const [statusFilter, setStatusFilter] = useState<"ALL" | TrainScheduleStatus>("ALL");
  const [freightFilter, setFreightFilter] = useState<"ALL" | FreightType>("ALL");
  // Origin/destination hold yard IDs ("ALL" = no filter); the server matches
  // the schedule's origin_station_id / destination_station_id exactly.
  const [originFilter, setOriginFilter] = useState("ALL");
  const [destinationFilter, setDestinationFilter] = useState("ALL");
  // Default: newest-created first, matching the API's default order. Values
  // are the server sort fields (see TRAIN_SCHEDULE_SORT_FIELDS).
  const [sortBy, setSortBy] = useState<
    "createdAt" | "scheduledDepartureDate" | "reference"
  >("createdAt");
  const [sortDir, setSortDir] = useState<"desc" | "asc">("desc");
  const [createOpen, setCreateOpen] = useState(false);
  const [windowSettingsId, setWindowSettingsId] = useState<string | null>(null);
  // Dispatch is irreversible from this screen, so it goes through an explicit
  // confirmation.
  const [dispatchTarget, setDispatchTarget] =
    useState<TrainScheduleListItem | null>(null);
  const [editDateSchedule, setEditDateSchedule] =
    useState<TrainScheduleListItem | null>(null);
  const [routeId, setRouteId] = useState("");
  const [scheduleDate, setScheduleDate] = useState("");
  const [trainId, setTrainId] = useState("");
  const [reverseWagonOrder, setReverseWagonOrder] = useState(false);
  // Recomputed each time the create modal opens so a long-lived tab can't keep
  // offering a stale "now" as the earliest selectable departure.
  const minScheduleDate = useMemo(
    () => (createOpen ? nowLocalDateTime() : ""),
    [createOpen],
  );

  const resetPage = useCallback(() => {
    setPagination((prev) =>
      prev.pageIndex === 0 ? prev : { ...prev, pageIndex: 0 },
    );
  }, [setPagination]);

  // Search resets the page only once the debounced value settles — resetting
  // per keystroke would refetch page 1 mid-typing.
  useEffect(() => {
    resetPage();
  }, [debouncedSearch, resetPage]);

  // Fully server-driven list: pagination, search, filters, and sort all travel
  // as query params; the response envelope carries the page + totals.
  const filters = useMemo<TrainScheduleListFilters>(
    () => ({
      page: pagination.pageIndex + 1,
      pageSize: pagination.pageSize,
      ...(debouncedSearch.trim() ? { search: debouncedSearch.trim() } : {}),
      ...(statusFilter !== "ALL" ? { status: statusFilter } : {}),
      ...(freightFilter !== "ALL" ? { freightType: freightFilter } : {}),
      ...(originFilter !== "ALL" ? { originStationId: originFilter } : {}),
      ...(destinationFilter !== "ALL"
        ? { destinationStationId: destinationFilter }
        : {}),
      sortBy,
      sortOrder: sortDir === "asc" ? "ASC" : "DESC",
    }),
    [
      pagination.pageIndex,
      pagination.pageSize,
      debouncedSearch,
      statusFilter,
      freightFilter,
      originFilter,
      destinationFilter,
      sortBy,
      sortDir,
    ],
  );

  const schedulesQuery = useQuery(
    api.trainScheduling.scheduleList.queryOptions({
      input: { filters },
      // Keep the previous page on screen while the next page/filter result
      // loads instead of flashing the empty state; 30s staleTime spares
      // back-and-forth navigation from refetching an unchanged list.
      placeholderData: keepPreviousData,
      staleTime: 30_000,
    }),
  );
  // Yard options for the origin/destination filters (shared routes reference
  // list, so the choices don't shrink to whatever the current page shows).
  const yardsQuery = useQuery(
    api.routes.yards.queryOptions({ staleTime: 5 * 60_000 }),
  );
  const routesQuery = useQuery(
    api.routes.list.queryOptions({ input: { status: "AVAILABLE" } }),
  );
  const trainsQuery = useQuery(
    api.trainScheduling.availableTrains.queryOptions({
      input: { routeId },
      enabled: Boolean(routeId),
    }),
  );
  const create = useMutation(api.trainScheduling.createSchedule.mutationOptions());
  const dispatchSchedule = useMutation(
    api.trainScheduling.dispatchSchedule.mutationOptions(),
  );
  const cancel = useMutation(api.trainScheduling.cancelSchedule.mutationOptions());

  // Intercity (same-country / DOMESTIC) routes cannot be scheduled yet — the
  // API rejects them, so keep them out of the picker entirely.
  const activeRoutes = useMemo(
    () => (routesQuery.data ?? []).filter((r) => r.direction !== "DOMESTIC"),
    [routesQuery.data],
  );

  const selectedRoute = activeRoutes.find((r) => r.id === routeId);

  const trainYardHint = useMemo(() => {
    if (!selectedRoute) return "Select a route first";
    const originLabel =
      selectedRoute.originYard?.label ??
      selectedRoute.originYard?.code ??
      "the route origin yard";
    return `All schedulable built trains are shown — those not yet at ${originLabel} or already on future schedules are flagged`;
  }, [selectedRoute]);

  useEffect(() => {
    setTrainId("");
  }, [routeId]);

  // Filtering, sorting, and paging all happen server-side — `schedules` IS the
  // current page, and the meta envelope carries the totals.
  const schedules = schedulesQuery.data?.items ?? [];
  const totalSchedules = schedulesQuery.data?.meta.total ?? 0;
  const pageCount = Math.max(1, schedulesQuery.data?.meta.totalPages ?? 1);

  // Status/weight tiles count the visible page only — board-wide numbers would
  // need a dedicated summary endpoint now that the list is server-paginated.
  const stats = useMemo(() => {
    const base = {
      scheduled: 0,
      dispatched: 0,
      draft: 0,
      weight: 0,
    };
    for (const s of schedules) {
      if (s.status === "SCHEDULED") base.scheduled += 1;
      if (s.status === "DISPATCHED") base.dispatched += 1;
      if (s.status === "DRAFT") base.draft += 1;
      base.weight += s.totalWeightTons ?? 0;
    }
    return base;
  }, [schedules]);

  // Corridor filter options: every yard from the shared reference list, sent
  // to the server as origin/destination station IDs.
  const yardOptions = useMemo(
    () =>
      (yardsQuery.data ?? []).map((y) => ({
        value: y.id,
        label: y.label ?? y.code,
      })),
    [yardsQuery.data],
  );

  const columns = useMemo((): ColumnDef<TrainScheduleListItem>[] => {
    const headerClassName = ruleEngineTable.headerCell;
    const cellClassName = ruleEngineTable.bodyCell;
    return [
      {
        id: "reference",
        header: "Ref",
        meta: { headerClassName, cellClassName },
        cell: ({ row }) => (
          <Text size="sm" fw={600} ff="monospace" c="edr-green.8">
            {row.original.reference ?? "—"}
          </Text>
        ),
      },
      {
        id: "date",
        header: "Departure",
        meta: { headerClassName, cellClassName },
        cell: ({ row }) => {
          const { day, time } = splitDate(row.original.scheduleDate);
          return (
            <Group gap="sm" wrap="nowrap">
              <Box
                style={{
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                  width: 34,
                  height: 34,
                  borderRadius: 9,
                  background: "var(--mantine-color-edr-green-0)",
                  color: "var(--mantine-color-edr-green-7)",
                  flexShrink: 0,
                }}
              >
                <CalendarClock size={16} />
              </Box>
              <Stack gap={0}>
                <Text size="sm" fw={600} lh={1.2}>
                  {day}
                </Text>
                <Text size="xs" c="dimmed" lh={1.2}>
                  {time || "—"}
                </Text>
              </Stack>
            </Group>
          );
        },
      },
      {
        id: "route",
        header: "Route",
        meta: { headerClassName, cellClassName },
        cell: ({ row }) => (
          <Stack gap={4}>
            <Group gap={6} wrap="nowrap">
              <Text size="sm" fw={600} lh={1.2}>
                {row.original.routeName ?? "—"}
              </Text>
              {row.original.direction ? (
                <Badge
                  size="xs"
                  variant="light"
                  color={directionColor(row.original.direction)}
                >
                  {row.original.direction}
                </Badge>
              ) : null}
            </Group>
            <Box maw={220}>
              <RouteCorridor
                origin={row.original.origin}
                destination={row.original.destination}
                variant="compact"
              />
            </Box>
          </Stack>
        ),
      },
      {
        id: "freight",
        header: "Freight",
        meta: { headerClassName, cellClassName },
        cell: ({ row }) => <FreightTypeBadge freightType={row.original.freightType} />,
      },
      {
        id: "train",
        header: "Train",
        meta: { headerClassName, cellClassName },
        cell: ({ row }) => {
          // Schedules created from the Train Builder show the direction-matched
          // run number first (falling back to the train code); legacy rows fall
          // back to their locomotive set.
          if (row.original.train) {
            const subtitle = [row.original.trainNumber ? row.original.train.code : null,
              row.original.train.trainName]
              .filter(Boolean)
              .join(" · ");
            return (
              <Group gap={6} wrap="nowrap">
                <Train size={14} color="var(--mantine-color-gray-5)" />
                <Stack gap={0}>
                  <Text size="sm" fw={600} ff="monospace" lh={1.2}>
                    {row.original.trainNumber ?? row.original.train.code}
                  </Text>
                  {subtitle ? (
                    <Text size="xs" c="dimmed" lh={1.2}>
                      {subtitle}
                    </Text>
                  ) : null}
                </Stack>
              </Group>
            );
          }
          const locos =
            row.original.locomotives && row.original.locomotives.length > 0
              ? row.original.locomotives
              : row.original.locomotive
                ? [row.original.locomotive]
                : [];
          if (!locos.length) {
            return (
              <Text size="sm" c="dimmed">
                —
              </Text>
            );
          }
          return (
            <Group gap={6} wrap="nowrap">
              <Train size={14} color="var(--mantine-color-gray-5)" />
              <Text size="sm" fw={500}>
                {locos[0].code}
                {locos.length > 1 ? ` +${locos.length - 1}` : ""}
              </Text>
            </Group>
          );
        },
      },
      {
        id: "metrics",
        header: "Load",
        meta: { headerClassName, cellClassName },
        cell: ({ row }) => (
          <Group gap={6} wrap="nowrap">
            <MetricChip value={row.original.bookingsCount} label="bkg" />
            {/* Wagon SLOTS this schedule's bookings occupy — not the coupled
                consist. A built train shows 0 here until bookings are allocated. */}
            <MetricChip value={row.original.wagonCount} label="wgn used" />
            <MetricChip value={`${row.original.totalWeightTons}T`} label="" subtle />
          </Group>
        ),
      },
      {
        id: "status",
        header: "Status",
        meta: { headerClassName, cellClassName },
        cell: ({ row }) => <StatusPill status={row.original.status} />,
      },
      {
        id: "actions",
        size:32,
        meta: { headerClassName, cellClassName: `${cellClassName} whitespace-nowrap` },
        cell: ({ row }) => {
          const schedule = row.original;
          return (
            <Group justify="flex-end" onClick={(e) => e.stopPropagation()}>
              <Menu position="bottom-end" withinPortal shadow="md" width={190}>
                <Menu.Target>
                  <ActionIcon variant="subtle" color="gray" aria-label="Row actions">
                    <MoreHorizontal size={16} />
                  </ActionIcon>
                </Menu.Target>
                <Menu.Dropdown>
                  <Menu.Item
                    leftSection={<Eye size={15} />}
                    onClick={() =>
                      navigate(`/dashboard/operations/train-scheduling-v2/${schedule.id}`)
                    }
                  >
                    View detail
                  </Menu.Item>
                  {["DISPATCHED", "ARRIVED"].includes(schedule.status) ? (
                    <Menu.Item
                      leftSection={<Navigation size={15} />}
                      onClick={() =>
                        navigate(
                          `/dashboard/operations/train-scheduling-v2/${schedule.id}/track`,
                        )
                      }
                    >
                      Track
                    </Menu.Item>
                  ) : null}
                  {["DRAFT", "SCHEDULED"].includes(schedule.status) ? (
                    <Menu.Item
                      leftSection={<Pencil size={15} />}
                      onClick={() => setEditDateSchedule(schedule)}
                    >
                      Edit departure date
                    </Menu.Item>
                  ) : null}
                  {["DRAFT", "SCHEDULED"].includes(schedule.status) ? (
                    <Menu.Item
                      leftSection={<Clock size={15} />}
                      onClick={() => setWindowSettingsId(schedule.id)}
                    >
                      Booking window settings
                    </Menu.Item>
                  ) : null}
                  {/* Start the run. Same transition as the detail page's
                      Dispatch button — that page also shows unassigned-wagon
                      and not-loaded warnings, so it stays the fuller surface. */}
                  {canUpdate && schedule.status === "SCHEDULED" ? (
                    <Menu.Item
                      leftSection={<Play size={15} />}
                      onClick={() => setDispatchTarget(schedule)}
                    >
                      Start (dispatch) train
                    </Menu.Item>
                  ) : null}
                  {["DRAFT", "SCHEDULED"].includes(schedule.status) ? (
                    <Menu.Item
                      color="red"
                      leftSection={<Ban size={15} />}
                      onClick={async () => {
                        try {
                          await cancel.mutateAsync({
                            id: schedule.id,
                            freightType: schedule.freightType ?? "CONTAINER",
                          });
                          toast({ title: "Schedule cancelled" });
                        } catch (err) {
                          toast({
                            title: "Cancel failed",
                            description: parseError(err, "Could not cancel"),
                            variant: "destructive",
                          });
                        }
                      }}
                    >
                      Cancel schedule
                    </Menu.Item>
                  ) : null}
                </Menu.Dropdown>
              </Menu>
            </Group>
          );
        },
      },
    ];
  }, [navigate, toast]);

  const handleCreate = async () => {
    if (!routeId || !scheduleDate || !trainId) {
      toast({
        title: "Select route, date, and the train to run",
        variant: "destructive",
      });
      return;
    }
    if (new Date(scheduleDate).getTime() < Date.now()) {
      toast({
        title: "Departure date must be in the future",
        variant: "destructive",
      });
      return;
    }
    try {
      const created = await create.mutateAsync({
        payload: {
          routeId,
          scheduleDate: new Date(scheduleDate).toISOString(),
          trainId,
          reverseWagonOrder,
        },
      });
      toast({ title: "Train schedule created" });
      showScheduleWarnings(created.warnings);
      setReverseWagonOrder(false);
      setCreateOpen(false);
      navigate(`/dashboard/operations/train-scheduling-v2/${created.id}`);
    } catch (err) {
      toast({
        title: "Create failed",
        description: parseError(err, "Could not create schedule"),
        variant: "destructive",
      });
    }
  };

  const tableStatus = schedulesQuery.isLoading
    ? "loading"
    : schedulesQuery.isError
      ? "error"
      : "success";

  return (
    <PageContainer>
      <PageHeader
        title="Train Schedules"
        subtitle="Operational train scheduling with full allocation workflow."
        action={
          canCreate ? (
            <Button leftSection={<Train size={18} />} onClick={() => setCreateOpen(true)}>
              New schedule
            </Button>
          ) : undefined
        }
      />

      <KpiStrip
        items={[
          { label: "Total trains", value: totalSchedules, icon: Train },
          { label: "Scheduled", value: stats.scheduled, icon: CalendarClock },
          { label: "Dispatched", value: stats.dispatched, icon: Send },
          { label: "Planned load", value: `${Math.round(stats.weight)}T`, icon: Weight },
        ]}
      />

      <Card p={0}>
        <Stack gap={0}>
          <Box px="md" pt="md" pb="sm" w="100%">
            <FleetToolbar
              search={search}
              onSearchChange={setSearch}
              searchPlaceholder="Search schedules…"
              viewMode={viewMode}
              onViewModeChange={setViewMode}
              filters={
                <>
                  <Select
                    size="sm"
                    radius="lg"
                    value={statusFilter}
                    onChange={(v) => {
                      if (!v) return;
                      setStatusFilter(v as "ALL" | TrainScheduleStatus);
                      resetPage();
                    }}
                    data={[
                      { value: "ALL", label: "All statuses" },
                      { value: "DRAFT", label: "Draft" },
                      { value: "SCHEDULED", label: "Scheduled" },
                      { value: "DISPATCHED", label: "Dispatched" },
                      { value: "ARRIVED", label: "Arrived" },
                      { value: "CANCELLED", label: "Cancelled" },
                    ]}
                    w={150}
                    styles={{ input: { borderColor: "var(--mantine-color-gray-3)" } }}
                  />
                  <Select
                    size="sm"
                    radius="lg"
                    value={freightFilter}
                    onChange={(v) => {
                      if (!v) return;
                      setFreightFilter(v as "ALL" | FreightType);
                      resetPage();
                    }}
                    data={[
                      { value: "ALL", label: "All freight" },
                      { value: "CONTAINER", label: "Container" },
                      { value: "BULK", label: "Bulk" },
                      { value: "MIXED", label: "Mixed" },
                    ]}
                    w={140}
                    styles={{ input: { borderColor: "var(--mantine-color-gray-3)" } }}
                  />
                  <Select
                    size="sm"
                    radius="lg"
                    placeholder="Origin"
                    searchable
                    value={originFilter}
                    onChange={(v) => {
                      setOriginFilter(v ?? "ALL");
                      resetPage();
                    }}
                    data={[
                      { value: "ALL", label: "All origins" },
                      ...yardOptions,
                    ]}
                    w={160}
                    styles={{ input: { borderColor: "var(--mantine-color-gray-3)" } }}
                  />
                  <Select
                    size="sm"
                    radius="lg"
                    placeholder="Destination"
                    searchable
                    value={destinationFilter}
                    onChange={(v) => {
                      setDestinationFilter(v ?? "ALL");
                      resetPage();
                    }}
                    data={[
                      { value: "ALL", label: "All destinations" },
                      ...yardOptions,
                    ]}
                    w={170}
                    styles={{ input: { borderColor: "var(--mantine-color-gray-3)" } }}
                  />
                  <Select
                    size="sm"
                    radius="lg"
                    value={`${sortBy}:${sortDir}`}
                    onChange={(v) => {
                      if (!v) return;
                      const [by, dir] = v.split(":") as [
                        typeof sortBy,
                        typeof sortDir,
                      ];
                      setSortBy(by);
                      setSortDir(dir);
                      resetPage();
                    }}
                    data={[
                      { value: "createdAt:desc", label: "Newest created" },
                      { value: "createdAt:asc", label: "Oldest created" },
                      { value: "scheduledDepartureDate:desc", label: "Departure ↓" },
                      { value: "scheduledDepartureDate:asc", label: "Departure ↑" },
                      { value: "reference:asc", label: "Reference ↑" },
                      { value: "reference:desc", label: "Reference ↓" },
                    ]}
                    w={170}
                    styles={{ input: { borderColor: "var(--mantine-color-gray-3)" } }}
                  />
                </>
              }
            />
          </Box>

          {viewMode === "table" ? (
            <DataTable
              columns={columns}
              data={schedules}
              status={tableStatus}
              onRowClick={(schedule) =>
                navigate(`/dashboard/operations/train-scheduling-v2/${schedule.id}`)
              }
              rowStyle={(schedule) => directionRowStyle(schedule.direction)}
              error={
                schedulesQuery.isError
                  ? {
                      message: "Failed to load train schedules.",
                      onRetry: () => void schedulesQuery.refetch(),
                    }
                  : undefined
              }
              emptyMessage="No train schedules found"
              pagination={{
                pageIndex: pagination.pageIndex,
                pageSize: pagination.pageSize,
                pageCount,
                totalCount: totalSchedules,
              }}
              tableOptions={{
                manualPagination: true,
                pageCount,
                state: { pagination },
                onPaginationChange: setPagination,
              }}
              containerClassName="border-0 shadow-none bg-transparent"
              footer={({ table, pagination: footerPagination }) => (
                <DataTableFooter
                  table={table}
                  pagination={footerPagination}
                  options={{ labels: { items: "schedules" } }}
                />
              )}
            />
          ) : (
            <Stack gap={0}>
              {!schedules.length ? (
                <Text py="xl" ta="center" c="dimmed" size="sm">
                  No train schedules found
                </Text>
              ) : (
                <SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md" p="md">
                  {schedules.map((schedule) => (
                    <ScheduleCard
                      key={schedule.id}
                      schedule={schedule}
                      onOpen={() =>
                        navigate(
                          `/dashboard/operations/train-scheduling-v2/${schedule.id}`,
                        )
                      }
                      onTrack={() =>
                        navigate(
                          `/dashboard/operations/train-scheduling-v2/${schedule.id}/track`,
                        )
                      }
                    />
                  ))}
                </SimpleGrid>
              )}
              <RuleEngineListFooter
                pagination={pagination}
                pageCount={pageCount}
                totalCount={totalSchedules}
                itemLabel="schedules"
                onPaginationChange={setPagination}
              />
            </Stack>
          )}
        </Stack>
      </Card>

      <Modal
        opened={createOpen}
        onClose={() => setCreateOpen(false)}
        title={<Text fw={600}>Create train schedule</Text>}
        radius="lg"
        centered
      >
        <Stack gap="md">
          <Text size="sm" c="dimmed">
            Schedules support both container and bulk bookings once assigned.
          </Text>
          <Select
            label="Route"
            placeholder="Select route"
            data={activeRoutes.map((r) => ({ value: r.id, label: formatRouteLabel(r) }))}
            value={routeId || null}
            onChange={(v) => setRouteId(v ?? "")}
            searchable
          />
          {routeId ? (
            <Text size="xs" c="dimmed">
              {trainYardHint}
            </Text>
          ) : null}
          <TextInput
            label="Departure date"
            type="datetime-local"
            min={minScheduleDate}
            value={scheduleDate}
            onChange={(e) => setScheduleDate(e.currentTarget.value)}
          />
          <Select
            label="Train"
            description="A built train (Train Builder) runs this departure with its locomotives and wagons"
            placeholder={routeId ? "Select a train" : "Select a route first"}
            data={(trainsQuery.data ?? []).map((train) => {
              // Route direction picks which of the train's typed pair this run uses.
              const runNumber =
                selectedRoute?.direction === "IMPORT"
                  ? train.importTrainNumber
                  : train.exportTrainNumber;
              return {
                value: train.id,
                label: `${train.code}${train.trainName ? ` — ${train.trainName}` : ""}${
                  runNumber ? ` · runs as ${runNumber}` : ""
                } · ${train.locomotives.length} locos · ${train.wagonCount} wagons${
                  train.atOriginYard ? "" : " · not at origin yard"
                }${train.futureScheduleCount ? ` · ${train.futureScheduleCount} future run(s)` : ""}`,
              };
            })}
            value={trainId || null}
            onChange={(v) => setTrainId(v ?? "")}
            searchable
            disabled={!routeId}
            nothingFoundMessage={
              routeId
                ? "No built trains yet — assemble one in the Train Builder first"
                : "Select a route first"
            }
          />
          <Checkbox
            label="Reverse wagon order"
            description="Place wagons on the train in reverse — the physically-last wagon becomes position 1. Composition and allocations are unchanged; only the order flips. Applies every time this schedule's wagon plan is built."
            checked={reverseWagonOrder}
            onChange={(e) => setReverseWagonOrder(e.currentTarget.checked)}
          />
          <Group justify="flex-end">
            <Button variant="default" onClick={() => setCreateOpen(false)}>
              Cancel
            </Button>
            <Button loading={create.isPending} onClick={handleCreate}>
              Create
            </Button>
          </Group>
        </Stack>
      </Modal>

      <BookingWindowSettingsModal
        scheduleId={windowSettingsId}
        opened={windowSettingsId != null}
        onClose={() => setWindowSettingsId(null)}
        onSaved={() => void schedulesQuery.refetch()}
      />

      <EditScheduleDateModal
        scheduleId={editDateSchedule?.id ?? null}
        currentDate={editDateSchedule?.scheduleDate ?? null}
        routeName={editDateSchedule?.routeName ?? null}
        opened={editDateSchedule != null}
        onClose={() => setEditDateSchedule(null)}
        onSaved={() => void schedulesQuery.refetch()}
      />

      <Modal
        opened={dispatchTarget != null}
        onClose={() => setDispatchTarget(null)}
        title="Dispatch this train?"
        centered
        radius="md"
      >
        <Stack gap="md">
          <Text size="sm" c="dimmed">
            <Text span fw={600} c="dark">
              {dispatchTarget?.trainNumber ?? dispatchTarget?.reference ?? "This train"}
            </Text>{" "}
            departs {dispatchTarget?.origin ?? "its origin"} for{" "}
            {dispatchTarget?.destination ?? "its destination"} and its booking
            window closes. This cannot be undone.
          </Text>
          <Text size="xs" c="dimmed">
            Open the schedule detail first if you want to check for unassigned
            wagons or cargo not yet marked loaded — those warnings are shown
            there, not here.
          </Text>
          <Group justify="flex-end" gap="sm">
            <Button variant="default" onClick={() => setDispatchTarget(null)}>
              Cancel
            </Button>
            <Button
              color="edr-green"
              leftSection={<Play size={16} />}
              loading={dispatchSchedule.isPending}
              onClick={async () => {
                if (!dispatchTarget) return;
                try {
                  await dispatchSchedule.mutateAsync(dispatchTarget.id);
                  toast({ title: "Train dispatched" });
                  setDispatchTarget(null);
                  void schedulesQuery.refetch();
                } catch (err) {
                  toast({
                    title: "Dispatch failed",
                    description: parseError(err, "Could not dispatch"),
                    variant: "destructive",
                  });
                }
              }}
            >
              Dispatch train
            </Button>
          </Group>
        </Stack>
      </Modal>
    </PageContainer>
  );
}

function MetricChip({
  value,
  label,
  subtle = false,
}: {
  value: string | number;
  label: string;
  subtle?: boolean;
}) {
  return (
    <Group
      gap={4}
      wrap="nowrap"
      style={{
        padding: "2px 8px",
        borderRadius: 8,
        background: subtle
          ? "var(--mantine-color-gray-1)"
          : "var(--mantine-color-edr-green-0)",
        border: `1px solid ${
          subtle ? "var(--mantine-color-gray-2)" : "var(--mantine-color-edr-green-1)"
        }`,
      }}
    >
      <Text size="sm" fw={700} c={subtle ? "gray.7" : "edr-green.8"} lh={1.2}>
        {value}
      </Text>
      {label ? (
        <Text size="xs" c="dimmed" lh={1.2}>
          {label}
        </Text>
      ) : null}
    </Group>
  );
}

function ScheduleCard({
  schedule,
  onOpen,
  onTrack,
}: {
  schedule: TrainScheduleListItem;
  onOpen: () => void;
  onTrack: () => void;
}) {
  const { day, time } = splitDate(schedule.scheduleDate);
  const canTrack = ["DISPATCHED", "ARRIVED"].includes(schedule.status);
  return (
    <Card
      padding={0}
      withBorder
      onClick={onOpen}
      className="cursor-pointer overflow-hidden transition-[transform,border-color] duration-150 hover:-translate-y-0.5 hover:border-edr-primary!"
    >
      <Stack gap="sm" p="md">
        <Group justify="space-between" align="flex-start" wrap="nowrap">
          <Group gap="sm" wrap="nowrap" style={{ minWidth: 0 }}>
            <ThemeIcon size={38} radius="md" variant="light" color="edr-green">
              <Train size={18} />
            </ThemeIcon>
            <Stack gap={0} style={{ minWidth: 0 }}>
              <Group gap={6} wrap="nowrap">
                {schedule.reference ? (
                  <Text size="xs" fw={700} ff="monospace" c="edr-green.8">
                    {schedule.reference}
                  </Text>
                ) : null}
                <Text fw={600} size="sm" lineClamp={1}>
                  {schedule.routeName ?? "Train schedule"}
                </Text>
              </Group>
              <Text size="xs" c="dimmed">
                {day} · {time}
              </Text>
            </Stack>
          </Group>
          <StatusPill status={schedule.status} />
        </Group>

        <Box
          p="xs"
          style={{
            borderRadius: 10,
            background: "var(--mantine-color-gray-0)",
            border: "1px solid var(--mantine-color-gray-1)",
          }}
        >
          <RouteCorridor origin={schedule.origin} destination={schedule.destination} />
        </Box>

        <Group justify="space-between" align="center">
          <Group gap={6} wrap="nowrap">
            <FreightTypeBadge freightType={schedule.freightType} />
            {schedule.direction ? (
              <Badge
                size="xs"
                variant="light"
                color={directionColor(schedule.direction)}
              >
                {schedule.direction}
              </Badge>
            ) : null}
          </Group>
          <Group gap={6} wrap="nowrap">
            <MetricChip value={schedule.bookingsCount} label="bkg" />
            <MetricChip value={schedule.wagonCount} label="wgn used" />
            <MetricChip value={`${schedule.totalWeightTons}T`} label="" subtle />
          </Group>
        </Group>

        <Group gap="xs" wrap="nowrap">
          <Button
            variant="light"
            color="edr-green"
            size="sm"
            radius="md"
            style={{ flex: 1 }}
            rightSection={<ArrowRight size={15} />}
            onClick={(e) => {
              e.stopPropagation();
              onOpen();
            }}
          >
            Open schedule
          </Button>
          {canTrack ? (
            <Button
              variant="light"
              color="teal"
              size="sm"
              radius="md"
              leftSection={<Navigation size={15} />}
              onClick={(e) => {
                e.stopPropagation();
                onTrack();
              }}
            >
              Track
            </Button>
          ) : null}
        </Group>
      </Stack>
    </Card>
  );
}
