import {
  Alert,
  Badge,
  Button,
  Checkbox,
  Group,
  Loader,
  Modal,
  NumberInput,
  Pagination,
  Paper,
  ScrollArea,
  Select,
  SimpleGrid,
  Stack,
  Table,
  Text,
  TextInput,
  Tooltip,
} from "@mantine/core";
import { useDebouncedValue } from "@mantine/hooks";
import { useMutation, useQuery } from "@tanstack/react-query";
import { Freight } from "@edr/types";
import { isAxiosError } from "axios";
import { AlertTriangle, Link2, Lock, MapPin, Plus, Search } from "lucide-react";
import { useMemo, useState } from "react";

import { useToast } from "@/hooks/use-toast";
import { api } from "@/services/api";
import type { ScheduleWagonYardRow } from "@/services/trainBuilder.service";

/**
 * Schedule yards tab: where THIS departure plans to board each consist wagon,
 * side by side with where the wagon physically stands (the train builder's
 * truth). Booking capacity per origin reads the plan; dispatch refuses to
 * leave until plan and physical yards agree. Edits are queued locally and
 * saved in one PATCH.
 */

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;
};

interface Props {
  scheduleId: string;
  canEdit: boolean;
}

export function ScheduleWagonYardPanel({ scheduleId, canEdit }: Props) {
  const { toast } = useToast();
  const query = useQuery(
    api.trainScheduling.scheduleWagonYards.queryOptions({ input: { scheduleId } }),
  );
  const save = useMutation(api.trainScheduling.updateScheduleWagonYards.mutationOptions());
  const data = query.data;

  /** wagonId → yardId queued but not yet saved. */
  const [pending, setPending] = useState<Record<string, string>>({});
  /** wagonId → cut yard queued but not yet saved; null = queued clear (rides to destination). */
  const [pendingCut, setPendingCut] = useState<Record<string, string | null>>({});
  /** wagonId → real-cut flag queued but not yet saved. */
  const [pendingRealCut, setPendingRealCut] = useState<Record<string, boolean>>({});
  /** Loose wagons queued to couple: wagonId → couple stop + display data. */
  const [pendingCouples, setPendingCouples] = useState<
    Record<string, { yardId: string; wagonNumber: string; typeCode: string }>
  >({});
  /** Already-planned couples queued for removal. */
  const [pendingUncouple, setPendingUncouple] = useState<string[]>([]);
  // "Add wagon" modal + its filters.
  const [coupleModalOpen, setCoupleModalOpen] = useState(false);
  const [coupleYardFilter, setCoupleYardFilter] = useState<string | null>(null);
  const [coupleType, setCoupleType] = useState<string | null>(null);
  const [coupleSearch, setCoupleSearch] = useState("");
  const [couplePage, setCouplePage] = useState(1);
  const [debouncedCoupleSearch] = useDebouncedValue(coupleSearch, 300);
  const [bulkType, setBulkType] = useState<string | null>(null);
  const [bulkFrom, setBulkFrom] = useState<string | null>(null);
  const [bulkTo, setBulkTo] = useState<string | null>(null);
  const [bulkCount, setBulkCount] = useState<number | string>(1);

  const editable = Boolean(canEdit && data?.editable);
  const pickupStops = useMemo(() => (data?.stops ?? []).filter((s) => s.pickup), [data]);
  /** Mid-route stops only — wagons are coupled between the origin and the destination. */
  const intermediateStops = useMemo(() => {
    const stops = data?.stops ?? [];
    return stops.slice(1, -1).filter((s) => s.pickup);
  }, [data]);
  // Loose-wagon list for the "Add wagon" modal. A wagon can only be coupled
  // where it physically stands, and only at a pickup stop of this route — the
  // Add button carries that yard; off-route wagons render disabled.
  const coupleListQuery = useQuery(
    api.wagons.listPaged.queryOptions({
      input: {
        filters: {
          status: Freight.WagonStatus.Available,
          unassigned: true,
          currentYardId: coupleYardFilter ?? undefined,
          wagonTypeId: coupleType ?? undefined,
          search: debouncedCoupleSearch || undefined,
          page: couplePage,
          pageSize: 8,
        },
      },
      enabled: editable && coupleModalOpen,
      placeholderData: (prev) => prev,
    }),
  );
  const coupleCandidates = coupleListQuery.data?.items ?? [];
  const coupleTotalPages = Math.max(1, coupleListQuery.data?.meta.totalPages ?? 1);
  const yardsQuery = useQuery(
    api.routes.yards.queryOptions({ staleTime: 5 * 60_000, enabled: coupleModalOpen }),
  );
  const wagonTypesQuery = useQuery(
    api.wagonTypes.list.queryOptions({ staleTime: 5 * 60_000, enabled: coupleModalOpen }),
  );
  const yardOptions = pickupStops.map((s) => ({ value: s.yardId, label: s.label }));
  const yardLabel = (id: string | null) =>
    (data?.stops ?? []).find((s) => s.yardId === id)?.label ??
    data?.wagons.find((w) => w.plannedYardId === id)?.plannedYardLabel ??
    data?.wagons.find((w) => w.physicalYardId === id)?.physicalYardLabel ??
    id ??
    "—";

  const effectiveYard = (w: ScheduleWagonYardRow) => pending[w.id] ?? w.plannedYardId;
  const effectiveCut = (w: ScheduleWagonYardRow) =>
    w.id in pendingCut ? pendingCut[w.id] : w.cutYardId;
  const effectiveRealCut = (w: ScheduleWagonYardRow) =>
    (pendingRealCut[w.id] ?? w.realCut) && effectiveCut(w) != null;
  const stopIndexOf = (yardId: string | null) =>
    yardId == null ? -1 : (data?.stops ?? []).findIndex((s) => s.yardId === yardId);
  /** Drop stops a wagon boarding at `boardYardId` can be cut at — strictly after
   *  boarding, excluding the destination (that's the cleared/default state). */
  const cutOptionsFor = (boardYardId: string | null) => {
    const stops = data?.stops ?? [];
    const boardIdx = Math.max(0, stopIndexOf(boardYardId));
    return stops
      .slice(boardIdx + 1, stops.length - 1)
      .map((s) => ({ value: s.yardId, label: s.label }));
  };
  /** Board-yard changes can invalidate a cut (server rejects cut ≤ board) — queue a clear. */
  const clearInvalidCut = (
    next: Record<string, string | null>,
    w: ScheduleWagonYardRow,
    boardYardId: string | null,
  ) => {
    const cut = w.id in next ? next[w.id] : w.cutYardId;
    if (cut != null && stopIndexOf(cut) <= stopIndexOf(boardYardId)) {
      if (w.cutYardId == null) delete next[w.id];
      else next[w.id] = null;
    }
    return next;
  };

  const perStop = useMemo(
    () =>
      (data?.stops ?? []).map((s) => ({
        ...s,
        planned: (data?.wagons ?? []).filter((w) => (pending[w.id] ?? w.plannedYardId) === s.yardId)
          .length,
        cut: (data?.wagons ?? []).filter(
          (w) => (w.id in pendingCut ? pendingCut[w.id] : w.cutYardId) === s.yardId,
        ).length,
        coupled:
          (data?.wagons ?? []).filter(
            (w) => w.coupledYardId === s.yardId && !pendingUncouple.includes(w.id),
          ).length +
          Object.values(pendingCouples).filter((c) => c.yardId === s.yardId).length,
      })),
    [data, pending, pendingCut, pendingCouples, pendingUncouple],
  );
  const typeOptions = useMemo(() => {
    const seen = new Map<string, string>();
    for (const w of data?.wagons ?? []) seen.set(w.wagonType.id, w.wagonType.code);
    return [...seen].map(([value, label]) => ({ value, label }));
  }, [data]);

  const pendingCount =
    new Set([
      ...Object.keys(pending),
      ...Object.keys(pendingCut),
      ...Object.keys(pendingRealCut),
    ]).size +
    Object.keys(pendingCouples).length +
    pendingUncouple.length;

  const queueBulk = () => {
    if (!data || !bulkFrom || !bulkTo || bulkFrom === bulkTo) return;
    const n = Number(bulkCount) || 0;
    const picked = data.wagons
      .filter(
        (w) =>
          !w.locked &&
          effectiveYard(w) === bulkFrom &&
          (!bulkType || w.wagonType.id === bulkType),
      )
      .slice(0, n);
    if (!picked.length) {
      toast({ title: "No free wagons match", variant: "destructive" });
      return;
    }
    setPending((prev) => {
      const next = { ...prev };
      for (const w of picked) {
        if (w.plannedYardId === bulkTo) delete next[w.id];
        else next[w.id] = bulkTo;
      }
      return next;
    });
    setPendingCut((prev) => {
      let next = { ...prev };
      for (const w of picked) next = clearInvalidCut(next, w, bulkTo);
      return next;
    });
  };

  const handleSave = async () => {
    if (!pendingCount) return;
    try {
      const wagonIds = [
        ...new Set([
          ...Object.keys(pending),
          ...Object.keys(pendingCut),
          ...Object.keys(pendingRealCut),
        ]),
      ];
      const result = await save.mutateAsync({
        scheduleId,
        payload: {
          moves: wagonIds.map((wagonId) => ({
            wagonId,
            ...(wagonId in pending ? { yardId: pending[wagonId] } : {}),
            ...(wagonId in pendingCut ? { cutYardId: pendingCut[wagonId] } : {}),
            ...(wagonId in pendingRealCut ? { realCut: pendingRealCut[wagonId] } : {}),
          })),
          ...(Object.keys(pendingCouples).length
            ? {
                couple: Object.entries(pendingCouples).map(([wagonId, c]) => ({
                  wagonId,
                  yardId: c.yardId,
                })),
              }
            : {}),
          ...(pendingUncouple.length ? { uncouple: pendingUncouple } : {}),
        },
      });
      setPending({});
      setPendingCut({});
      setPendingRealCut({});
      setPendingCouples({});
      setPendingUncouple([]);
      toast({
        title: `Schedule yards updated — ${pendingCount} wagon(s) re-planned`,
        description: result.warnings.length ? result.warnings.join(" ") : undefined,
        variant: result.warnings.length ? "destructive" : undefined,
      });
    } catch (err) {
      toast({
        title: "Update failed",
        description: parseError(err, "Could not update the schedule's wagon yards"),
        variant: "destructive",
      });
    }
  };

  if (query.isLoading) return <Loader size="sm" />;
  if (query.isError || !data) {
    return (
      <Alert color="red" icon={<AlertTriangle size={16} />}>
        {parseError(
          query.error,
          "This schedule has no wagon yard plan (not created from a built train).",
        )}
      </Alert>
    );
  }

  return (
    <Stack gap="md">
      <Alert color="blue" icon={<MapPin size={16} />} variant="light">
        <b>Planned</b> = where this departure boards the wagon (what customers can book per
        origin). <b>Physical</b> = where the wagon stands now (train builder). <b>Cut at</b> ={" "}
        where this departure detaches the wagon and leaves it — blank means it rides to the
        destination; booking capacity past the cut shrinks accordingly. Tick <b>Real cut</b> to
        remove the wagon from the train build permanently at that yard (untick = it sits out this
        trip only). <b>Coupled</b> wagons are loose wagons joining the train at a stop — they
        become part of the build for good. Dispatch is blocked until every wagon stands at its
        planned yard.
        {data.misaligned > 0 ? (
          <Text component="span" c="orange" fw={600}>
            {" "}
            {data.misaligned} wagon(s) currently misaligned.
          </Text>
        ) : null}
      </Alert>

      <SimpleGrid cols={{ base: 2, sm: 3, md: Math.min(5, Math.max(2, perStop.length)) }}>
        {perStop.map((s) => (
          <Paper key={s.yardId} withBorder p="sm" radius="md">
            <Group justify="space-between" mb={4}>
              <Text fw={600} size="sm">
                {s.label}
              </Text>
              {!s.pickup ? (
                <Badge size="xs" color="gray" variant="light">
                  destination
                </Badge>
              ) : null}
            </Group>
            <Group gap="xs">
              <Badge color="edr-green" variant="filled">
                Planned {s.planned}
              </Badge>
              <Badge color={s.physical === s.planned ? "gray" : "orange"} variant="light">
                Physical {s.physical}
              </Badge>
              {s.cut > 0 ? (
                <Badge color="red" variant="light">
                  Cut {s.cut}
                </Badge>
              ) : null}
              {s.coupled > 0 ? (
                <Badge color="blue" variant="light">
                  +{s.coupled} coupled
                </Badge>
              ) : null}
              {!s.pickup ? (
                <Badge color="blue" variant="light">
                  Through{" "}
                  {data.wagons.filter(
                    (w) => !w.coupledYardId || !pendingUncouple.includes(w.id),
                  ).length +
                    Object.keys(pendingCouples).length -
                    perStop.reduce((sum, p) => sum + p.cut, 0)}
                </Badge>
              ) : null}
            </Group>
          </Paper>
        ))}
      </SimpleGrid>

      {editable ? (
        <Paper withBorder p="sm" radius="md">
          <Group align="end" gap="sm" wrap="wrap">
            <NumberInput
              label="Move"
              min={1}
              max={data.wagons.length}
              value={bulkCount}
              onChange={setBulkCount}
              w={90}
            />
            <Select
              label="Wagon type"
              placeholder="Any"
              clearable
              data={typeOptions}
              value={bulkType}
              onChange={setBulkType}
              w={140}
            />
            <Select label="From" data={yardOptions} value={bulkFrom} onChange={setBulkFrom} w={170} />
            <Select label="To" data={yardOptions} value={bulkTo} onChange={setBulkTo} w={170} />
            <Button
              variant="light"
              onClick={queueBulk}
              disabled={!bulkFrom || !bulkTo || bulkFrom === bulkTo}
            >
              Queue
            </Button>
          </Group>
        </Paper>
      ) : null}

      {editable ? (
        <Group justify="space-between">
          <Group gap={6}>
            <Link2 size={16} />
            <Text fw={600} size="sm">
              Consist plan for this trip
            </Text>
          </Group>
          <Button
            leftSection={<Plus size={16} />}
            variant="light"
            onClick={() => setCoupleModalOpen(true)}
          >
            Add wagon
          </Button>
        </Group>
      ) : null}

      <Modal
        opened={coupleModalOpen}
        onClose={() => setCoupleModalOpen(false)}
        size="xl"
        radius="md"
        title={
          <Group gap={8}>
            <Link2 size={18} />
            <Text fw={700}>Add wagons to this trip</Text>
          </Group>
        }
      >
        <Stack gap="sm">
          <Alert color="blue" variant="light" p="xs">
            A wagon is coupled where it physically stands, so it must be waiting at one of this
            route&apos;s stops between the origin and the destination. Wagons elsewhere are listed
            but cannot be added until they are moved.
          </Alert>
          <Group align="end" gap="sm" wrap="wrap">
            <Select
              label="Yard"
              placeholder="All yards"
              clearable
              searchable
              data={(yardsQuery.data ?? [])
                .filter(
                  (y) =>
                    y.id !== data.stops[0]?.yardId &&
                    y.id !== data.stops[data.stops.length - 1]?.yardId,
                )
                .slice()
                .sort((a, b) => a.label.localeCompare(b.label))
                .map((y) => ({
                  value: y.id,
                  label: intermediateStops.some((s) => s.yardId === y.id)
                    ? `${y.label} · route stop`
                    : y.label,
                }))}
              value={coupleYardFilter}
              onChange={(v) => {
                setCoupleYardFilter(v);
                setCouplePage(1);
              }}
              w={220}
            />
            <Select
              label="Wagon type"
              placeholder="Any type"
              clearable
              data={(wagonTypesQuery.data ?? []).map((t) => ({
                value: t.id,
                label: t.code ? `${t.name} (${t.code})` : t.name,
              }))}
              value={coupleType}
              onChange={(v) => {
                setCoupleType(v);
                setCouplePage(1);
              }}
              w={200}
            />
            <TextInput
              label="Search"
              placeholder="Wagon number…"
              leftSection={<Search size={14} />}
              value={coupleSearch}
              onChange={(e) => {
                setCoupleSearch(e.currentTarget.value);
                setCouplePage(1);
              }}
              w={200}
            />
          </Group>
          {coupleListQuery.isLoading ? (
            <Group justify="center" p="md">
              <Loader size="sm" />
            </Group>
          ) : (
            <ScrollArea.Autosize mah={380}>
              <Table striped highlightOnHover withTableBorder>
                <Table.Thead>
                  <Table.Tr>
                    <Table.Th>Wagon</Table.Th>
                    <Table.Th>Type</Table.Th>
                    <Table.Th>Standing at</Table.Th>
                    <Table.Th ta="right">Couple</Table.Th>
                  </Table.Tr>
                </Table.Thead>
                <Table.Tbody>
                  {coupleCandidates.map((w) => {
                    const onTrip = data.wagons.some((row) => row.id === w.id);
                    const queued = w.id in pendingCouples;
                    const stop = intermediateStops.find((s) => s.yardId === w.currentYardId);
                    return (
                      <Table.Tr key={w.id}>
                        <Table.Td>
                          <Text fw={600} size="sm">
                            {w.wagonNumber}
                          </Text>
                        </Table.Td>
                        <Table.Td>
                          <Badge size="sm" variant="light" color="gray">
                            {w.wagonType?.code ?? w.wagonTypeId}
                          </Badge>
                        </Table.Td>
                        <Table.Td>
                          <Text size="sm">{w.currentYard?.label ?? "No yard"}</Text>
                        </Table.Td>
                        <Table.Td ta="right">
                          {onTrip ? (
                            <Badge size="sm" variant="light" color="gray">
                              On this trip
                            </Badge>
                          ) : queued ? (
                            <Button
                              size="compact-xs"
                              variant="subtle"
                              color="red"
                              onClick={() =>
                                setPendingCouples((prev) => {
                                  const next = { ...prev };
                                  delete next[w.id];
                                  return next;
                                })
                              }
                            >
                              Queued — remove
                            </Button>
                          ) : stop ? (
                            <Button
                              size="compact-xs"
                              variant="light"
                              leftSection={<Plus size={12} />}
                              onClick={() =>
                                setPendingCouples((prev) => ({
                                  ...prev,
                                  [w.id]: {
                                    yardId: stop.yardId,
                                    wagonNumber: w.wagonNumber,
                                    typeCode: w.wagonType?.code ?? w.wagonTypeId,
                                  },
                                }))
                              }
                            >
                              Couple at {stop.label}
                            </Button>
                          ) : (
                            <Tooltip label="Not standing at a mid-route stop of this schedule (origin and destination excluded)">
                              <Button size="compact-xs" variant="default" disabled>
                                Off route
                              </Button>
                            </Tooltip>
                          )}
                        </Table.Td>
                      </Table.Tr>
                    );
                  })}
                  {coupleCandidates.length === 0 ? (
                    <Table.Tr>
                      <Table.Td colSpan={4}>
                        <Text size="sm" c="dimmed" ta="center" py="sm">
                          No loose wagons match the filters.
                        </Text>
                      </Table.Td>
                    </Table.Tr>
                  ) : null}
                </Table.Tbody>
              </Table>
            </ScrollArea.Autosize>
          )}
          <Group justify="space-between">
            {coupleTotalPages > 1 ? (
              <Pagination
                size="sm"
                value={couplePage}
                onChange={setCouplePage}
                total={coupleTotalPages}
              />
            ) : (
              <span />
            )}
            <Group gap="sm">
              <Text size="sm" c="dimmed">
                {Object.keys(pendingCouples).length} wagon(s) queued — save the plan to apply
              </Text>
              <Button onClick={() => setCoupleModalOpen(false)}>Done</Button>
            </Group>
          </Group>
        </Stack>
      </Modal>

      <Table striped highlightOnHover withTableBorder>
        <Table.Thead>
          <Table.Tr>
            <Table.Th>#</Table.Th>
            <Table.Th>Wagon</Table.Th>
            <Table.Th>Type</Table.Th>
            <Table.Th>Physical yard</Table.Th>
            <Table.Th>Planned yard (this schedule)</Table.Th>
            <Table.Th>Cut at (rides to)</Table.Th>
            <Table.Th>Status</Table.Th>
          </Table.Tr>
        </Table.Thead>
        <Table.Tbody>
          {data.wagons
            .filter((w) => !w.coupledYardId)
            .map((w) => {
              const planned = effectiveYard(w);
              const cut = effectiveCut(w);
              const changed = w.id in pending || w.id in pendingCut || w.id in pendingRealCut;
              return (
                <Table.Tr key={w.id} bg={changed ? "var(--mantine-color-yellow-light)" : undefined}>
                  <Table.Td>{w.sequenceNumber ?? "—"}</Table.Td>
                  <Table.Td>
                    <Text fw={600} size="sm">
                      {w.wagonNumber}
                    </Text>
                  </Table.Td>
                  <Table.Td>{w.wagonType.code}</Table.Td>
                  <Table.Td>{w.physicalYardLabel ?? "No yard"}</Table.Td>
                  <Table.Td>
                    {editable && !w.locked ? (
                      <Select
                        size="xs"
                        data={yardOptions}
                        value={planned}
                        onChange={(v) => {
                          setPending((prev) => {
                            const next = { ...prev };
                            if (!v || v === w.plannedYardId) delete next[w.id];
                            else next[w.id] = v;
                            return next;
                          });
                          setPendingCut((prev) =>
                            clearInvalidCut({ ...prev }, w, v ?? w.plannedYardId),
                          );
                        }}
                        w={180}
                      />
                    ) : (
                      <Group gap={4}>
                        <Text size="sm">{yardLabel(planned)}</Text>
                        {w.locked ? (
                          <Tooltip label={w.lockReason ?? "Locked"}>
                            <Lock size={14} />
                          </Tooltip>
                        ) : null}
                      </Group>
                    )}
                  </Table.Td>
                  <Table.Td>
                    {editable ? (
                      // Locked wagons stay editable here — the server enforces the
                      // cargo-destination floor and the toast explains a 409.
                      <Stack gap={4}>
                        <Select
                          size="xs"
                          clearable
                          placeholder="Destination"
                          data={cutOptionsFor(planned)}
                          value={cut}
                          onChange={(v) => {
                            setPendingCut((prev) => {
                              const next = { ...prev };
                              if ((v ?? null) === w.cutYardId) delete next[w.id];
                              else next[w.id] = v ?? null;
                              return next;
                            });
                            if (!v) {
                              // No cut → no real-cut flag to keep.
                              setPendingRealCut((prev) => {
                                const next = { ...prev };
                                if (w.realCut) next[w.id] = false;
                                else delete next[w.id];
                                return next;
                              });
                            }
                          }}
                          w={180}
                        />
                        {cut ? (
                          <Checkbox
                            size="xs"
                            label="Real cut (train loses wagon)"
                            checked={effectiveRealCut(w)}
                            onChange={(e) => {
                              const v = e.currentTarget.checked;
                              setPendingRealCut((prev) => {
                                const next = { ...prev };
                                if (v === w.realCut) delete next[w.id];
                                else next[w.id] = v;
                                return next;
                              });
                            }}
                          />
                        ) : null}
                      </Stack>
                    ) : (
                      <Text size="sm">
                        {cut
                          ? `${yardLabel(cut)}${effectiveRealCut(w) ? " (real cut)" : ""}`
                          : "Destination"}
                      </Text>
                    )}
                  </Table.Td>
                  <Table.Td>
                    {planned === w.physicalYardId ? (
                      <Badge color="teal" variant="light" size="sm">
                        Aligned
                      </Badge>
                    ) : (
                      <Badge color="orange" variant="light" size="sm">
                        Needs move
                      </Badge>
                    )}
                  </Table.Td>
                </Table.Tr>
              );
            })}
          {data.wagons
            .filter((w) => w.coupledYardId)
            .map((w) => {
              const queuedOff = pendingUncouple.includes(w.id);
              return (
                <Table.Tr
                  key={w.id}
                  bg={queuedOff ? "var(--mantine-color-yellow-light)" : undefined}
                  opacity={queuedOff ? 0.5 : undefined}
                >
                  <Table.Td>—</Table.Td>
                  <Table.Td>
                    <Text fw={600} size="sm">
                      {w.wagonNumber}
                    </Text>
                  </Table.Td>
                  <Table.Td>{w.wagonType.code}</Table.Td>
                  <Table.Td>{w.physicalYardLabel ?? "No yard"}</Table.Td>
                  <Table.Td>
                    <Badge size="sm" variant="light" color="blue" leftSection={<Link2 size={12} />}>
                      Coupled at {w.coupledYardLabel ?? w.coupledYardId}
                    </Badge>
                  </Table.Td>
                  <Table.Td>
                    <Text size="sm">Destination</Text>
                  </Table.Td>
                  <Table.Td>
                    <Group gap={6}>
                      {w.aligned ? (
                        <Badge color="teal" variant="light" size="sm">
                          At couple yard
                        </Badge>
                      ) : (
                        <Badge color="orange" variant="light" size="sm">
                          Not at couple yard
                        </Badge>
                      )}
                      {editable ? (
                        <Tooltip
                          label={w.locked ? w.lockReason ?? "Locked" : "Remove from couple plan"}
                        >
                          <Button
                            size="compact-xs"
                            variant="subtle"
                            color="red"
                            disabled={w.locked}
                            onClick={() =>
                              setPendingUncouple((prev) =>
                                queuedOff ? prev.filter((id) => id !== w.id) : [...prev, w.id],
                              )
                            }
                          >
                            {queuedOff ? "Keep" : "Uncouple"}
                          </Button>
                        </Tooltip>
                      ) : null}
                    </Group>
                  </Table.Td>
                </Table.Tr>
              );
            })}
          {Object.entries(pendingCouples).map(([wagonId, c]) => (
            <Table.Tr key={wagonId} bg="var(--mantine-color-yellow-light)">
              <Table.Td>—</Table.Td>
              <Table.Td>
                <Text fw={600} size="sm">
                  {c.wagonNumber}
                </Text>
              </Table.Td>
              <Table.Td>{c.typeCode}</Table.Td>
              <Table.Td>{yardLabel(c.yardId)}</Table.Td>
              <Table.Td>
                <Badge size="sm" variant="light" color="blue" leftSection={<Link2 size={12} />}>
                  Coupled at {yardLabel(c.yardId)} (pending)
                </Badge>
              </Table.Td>
              <Table.Td>
                <Text size="sm">Destination</Text>
              </Table.Td>
              <Table.Td>
                <Button
                  size="compact-xs"
                  variant="subtle"
                  color="red"
                  onClick={() =>
                    setPendingCouples((prev) => {
                      const next = { ...prev };
                      delete next[wagonId];
                      return next;
                    })
                  }
                >
                  Remove
                </Button>
              </Table.Td>
            </Table.Tr>
          ))}
        </Table.Tbody>
      </Table>

      {editable ? (
        <Group justify="flex-end">
          <Text size="sm" c="dimmed">
            {pendingCount} pending change(s)
          </Text>
          <Button
            variant="default"
            onClick={() => {
              setPending({});
              setPendingCut({});
              setPendingRealCut({});
              setPendingCouples({});
              setPendingUncouple([]);
            }}
            disabled={!pendingCount}
          >
            Discard
          </Button>
          <Button
            onClick={() => void handleSave()}
            loading={save.isPending}
            disabled={!pendingCount}
          >
            Save plan
          </Button>
        </Group>
      ) : null}
    </Stack>
  );
}
