import { Fragment, useEffect, useMemo, useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { isAxiosError } from "axios";
import {
  Alert,
  Badge,
  Button,
  Group,
  Paper,
  Stack,
  Table,
  Text,
  Tooltip,
} from "@mantine/core";
import { ArrowLeftRight, Boxes, Info, MoveRight, Wheat, X } from "lucide-react";

import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
import type { TrainScheduleDetail } from "@/types/trainScheduling";

type Slot = NonNullable<TrainScheduleDetail["trainSet"]>["wagons"][number];
type Stop = { yardId: string; label: string };
type Span = [number, number];

/** One physical wagon of the consist with every slot (leg load) pinned to it. */
interface WagonRow {
  key: string;
  physicalWagonId: string | null;
  label: string;
  position: number;
  typeCode: string | null;
  capacityTons: number;
  slots: Array<{ slot: Slot; span: Span; loaded: boolean }>;
}

const round1 = (n: number) => Math.round(n * 10) / 10;
const overlaps = (a: Span, b: Span) => a[0] < b[1] && b[0] < a[1];

/**
 * Leg board: rows = physical wagons in coupling order, columns = corridor legs
 * (A→B, B→C, …). A wagon reused on disjoint legs shows one load per leg on the
 * same row, so a "53 full on A→B, 53 full on C→D" train reads at a glance.
 * Loads move by click: pick a load, then click a wagon that is free on that
 * load's legs (move) or another load (swap). Same API as the consist strip.
 */
export function LegLoadBoardPanel({
  schedule,
  onChanged,
}: {
  schedule: TrainScheduleDetail;
  onChanged?: () => void;
}) {
  const { toast } = useToast();
  const stops: Stop[] = schedule.stops ?? [];
  const legs = useMemo(
    () => stops.slice(0, -1).map((from, i) => ({ from, to: stops[i + 1], idx: i })),
    [stops],
  );
  const canRearrange = !["DISPATCHED", "ARRIVED", "CANCELLED"].includes(schedule.status);

  const spanOf = (slot: Slot): Span => {
    const from = slot.boardYardId ? stops.findIndex((s) => s.yardId === slot.boardYardId) : 0;
    const to = slot.alightYardId
      ? stops.findIndex((s) => s.yardId === slot.alightYardId)
      : stops.length - 1;
    return [from < 0 ? 0 : from, to < 0 ? Math.max(1, stops.length - 1) : to];
  };

  const rows: WagonRow[] = useMemo(() => {
    const byKey = new Map<string, WagonRow>();
    for (const slot of schedule.trainSet?.wagons ?? []) {
      const key = slot.physicalWagonId ?? `slot:${slot.id}`;
      let row = byKey.get(key);
      if (!row) {
        row = {
          key,
          physicalWagonId: slot.physicalWagonId ?? null,
          label: slot.physicalWagonNumber ?? `#${slot.position ?? slot.sequenceNo}`,
          position: slot.position ?? slot.sequenceNo,
          typeCode: slot.wagonType?.code ?? null,
          capacityTons: slot.capacityTons ?? 0,
          slots: [],
        };
        byKey.set(key, row);
      }
      row.position = Math.min(row.position, slot.position ?? slot.sequenceNo);
      // Coupled-but-empty consist wagons carry no slot row: they are a target only.
      if (!slot.consistOnly) {
        row.slots.push({
          slot,
          span: spanOf(slot),
          loaded: (slot.allocations?.length ?? 0) > 0,
        });
      }
    }
    return [...byKey.values()].sort((a, b) => a.position - b.position);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [schedule.trainSet?.wagons, stops]);

  const [picked, setPicked] = useState<{ slotId: string; rowKey: string; span: Span } | null>(
    null,
  );
  useEffect(() => {
    if (!picked) return;
    const onKey = (e: KeyboardEvent) => e.key === "Escape" && setPicked(null);
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [picked]);

  const moveMutation = useMutation(api.trainScheduling.moveWagonLoad.mutationOptions());
  const doMove = async (targetWagonId: string, swap: boolean) => {
    if (!picked || moveMutation.isPending) return;
    try {
      await moveMutation.mutateAsync({
        scheduleId: schedule.id,
        wagonId: picked.slotId,
        targetWagonId,
      });
      toast({ title: swap ? "Loads swapped" : "Load moved" });
      setPicked(null);
      onChanged?.();
    } catch (error) {
      const message = isAxiosError(error)
        ? ((error.response?.data as { message?: string | string[] } | undefined)?.message ??
          null)
        : null;
      toast({
        title: "Could not move the load",
        description: Array.isArray(message)
          ? message.join(", ")
          : (message ?? "The move was rejected — check wagon type, payload and leg."),
        variant: "destructive",
      });
    }
  };

  if (stops.length < 2) {
    return (
      <Alert color="gray" icon={<Info size={16} />} radius="md">
        This schedule has no corridor stops yet — the leg board needs a route with at least
        two stops.
      </Alert>
    );
  }
  if (!rows.length) {
    return (
      <Alert color="gray" icon={<Info size={16} />} radius="md">
        No wagons on this train yet.
      </Alert>
    );
  }

  const sharedRows = rows.filter((r) => r.slots.filter((s) => s.loaded).length > 1).length;

  return (
    <Stack gap="md">
      <Group justify="space-between" align="flex-start" wrap="wrap">
        <Stack gap={2}>
          <Text fw={700} size="sm">
            Loads per wagon per leg
          </Text>
          <Text size="xs" c="dimmed">
            One row per physical wagon, one column per leg. A wagon reused on different legs
            shows one load per leg.{" "}
            {canRearrange
              ? "Click a load to pick it up, then click a wagon free on those legs to move it, or another load to swap."
              : "Read-only — the train has departed."}
          </Text>
        </Stack>
        <Group gap="xs">
          {sharedRows > 0 ? (
            <Badge variant="light" color="violet" radius="sm">
              {sharedRows} wagon{sharedRows === 1 ? "" : "s"} shared across legs
            </Badge>
          ) : null}
          {picked ? (
            <Button
              size="xs"
              variant="default"
              leftSection={<X size={14} />}
              onClick={() => setPicked(null)}
            >
              Cancel move (Esc)
            </Button>
          ) : null}
        </Group>
      </Group>

      <Paper withBorder radius="md" style={{ overflowX: "auto" }}>
        <Table verticalSpacing={6} horizontalSpacing="sm" style={{ minWidth: 640 }}>
          <Table.Thead>
            <Table.Tr>
              <Table.Th style={{ position: "sticky", left: 0, background: "var(--mantine-color-body)", zIndex: 1, width: 180 }}>
                Wagon
              </Table.Th>
              {legs.map((leg) => (
                <Table.Th key={leg.idx} style={{ minWidth: 200 }}>
                  <Group gap={4} wrap="nowrap">
                    <Text size="xs" fw={700} truncate>
                      {leg.from.label}
                    </Text>
                    <MoveRight size={12} />
                    <Text size="xs" fw={700} truncate>
                      {leg.to.label}
                    </Text>
                  </Group>
                </Table.Th>
              ))}
              <Table.Th style={{ width: 110 }}>Cargo</Table.Th>
            </Table.Tr>
          </Table.Thead>
          <Table.Tbody>
            {rows.map((row) => {
              const cargoTons = row.slots.reduce(
                (s, x) =>
                  s +
                  ((x.slot.allocations ?? []).reduce(
                    (a, al) => a + (al.allocatedWeightTons ?? 0),
                    0,
                  ) || x.slot.assignedWeightTons || 0),
                0,
              );
              const isPickedRow = picked?.rowKey === row.key;
              // A row can take the picked load when nothing loaded on it rides
              // any of the picked load's legs.
              const rowFreeForPicked =
                !!picked &&
                !isPickedRow &&
                !row.slots.some((s) => s.loaded && overlaps(s.span, picked.span));
              // Where a "move here" lands: an existing empty slot on those legs,
              // else the physical wagon itself (the API mints the slot).
              const emptyTargetSlot = picked
                ? row.slots.find((s) => !s.loaded && overlaps(s.span, picked.span))
                : undefined;
              const moveTargetId = emptyTargetSlot?.slot.id ?? row.physicalWagonId ?? null;

              // Lay slots into leg columns; uncovered legs render as empty cells.
              const cells: React.ReactNode[] = [];
              let col = 0;
              const sorted = [...row.slots].sort((a, b) => a.span[0] - b.span[0]);
              // Empty cell = uncovered leg (target: the physical wagon) or an
              // empty slot (target: that slot). Both take the picked load when
              // the row is free on its legs.
              const emptyCell = (from: number, to: number, targetId = moveTargetId) => {
                const droppable = rowFreeForPicked && canRearrange && !!targetId &&
                  !!picked && overlaps([from, to], picked.span);
                return (
                  <Table.Td
                    key={`e-${from}`}
                    colSpan={Math.max(1, to - from)}
                    onClick={droppable ? () => void doMove(targetId!, false) : undefined}
                    style={{
                      cursor: droppable ? "pointer" : "default",
                      background: droppable ? "var(--mantine-color-teal-0)" : undefined,
                      outline: droppable ? "1px dashed var(--mantine-color-teal-5)" : undefined,
                      outlineOffset: -3,
                      borderRadius: 6,
                    }}
                  >
                    {droppable ? (
                      <Text size="xs" c="teal.7" fw={600} ta="center">
                        Move here
                      </Text>
                    ) : (
                      <Text size="xs" c="dimmed" ta="center">
                        —
                      </Text>
                    )}
                  </Table.Td>
                );
              };
              for (const s of sorted) {
                if (s.span[0] > col) cells.push(emptyCell(col, s.span[0]));
                if (!s.loaded) {
                  cells.push(emptyCell(s.span[0], s.span[1], s.slot.id));
                  col = Math.max(col, s.span[1]);
                  continue;
                }
                const isPicked = picked?.slotId === s.slot.id;
                const swappable =
                  !!picked && !isPicked && !isPickedRow && s.loaded && canRearrange;
                const allocs = s.slot.allocations ?? [];
                const bulk = allocs.some((a) => (a.loadType ?? "CONTAINER").toUpperCase() === "BULK");
                const containers = allocs.flatMap((a) => a.containerItems ?? []);
                cells.push(
                  <Table.Td
                    key={s.slot.id}
                    colSpan={Math.max(1, s.span[1] - s.span[0])}
                    onClick={
                      !canRearrange
                        ? undefined
                        : s.loaded && !picked
                          ? () => setPicked({ slotId: s.slot.id, rowKey: row.key, span: s.span })
                          : swappable
                            ? () => void doMove(s.slot.id, true)
                            : isPicked
                              ? () => setPicked(null)
                              : undefined
                    }
                    style={{
                      cursor: canRearrange && (s.loaded || swappable) ? "pointer" : "default",
                      padding: 4,
                    }}
                  >
                    {s.loaded ? (
                      <Paper
                        radius="sm"
                        px={8}
                        py={6}
                        style={{
                          background: bulk
                            ? "var(--mantine-color-orange-0)"
                            : "var(--mantine-color-cyan-0)",
                          borderLeft: `4px solid ${
                            bulk ? "var(--mantine-color-orange-6)" : "var(--mantine-color-cyan-6)"
                          }`,
                          outline: isPicked
                            ? "2px solid var(--mantine-color-edr-green-6)"
                            : swappable
                              ? "1px dashed var(--mantine-color-orange-6)"
                              : undefined,
                          outlineOffset: 1,
                        }}
                      >
                        <Group gap={6} wrap="nowrap" justify="space-between">
                          <Group gap={6} wrap="nowrap">
                            {bulk ? <Wheat size={13} /> : <Boxes size={13} />}
                            <Text size="xs" fw={700} truncate>
                              {[...new Set(allocs.map((a) => a.bookingReference ?? "—"))].join(", ")}
                            </Text>
                          </Group>
                          <Text size="xs" c="dimmed" style={{ whiteSpace: "nowrap" }}>
                            {round1(
                              allocs.reduce((a, al) => a + (al.allocatedWeightTons ?? 0), 0),
                            )}{" "}
                            t
                          </Text>
                        </Group>
                        <Group gap={4} mt={2} wrap="wrap">
                          {bulk
                            ? allocs.map((a) =>
                                a.bulkLoad ? (
                                  <Badge key={a.id} size="xs" variant="light" color="orange" radius="sm">
                                    {a.bulkLoad.cargoDescription ?? "Bulk"} · {round1(a.bulkLoad.weightTons)} t
                                  </Badge>
                                ) : null,
                              )
                            : containers.map((c) => (
                                <Badge key={c.id} size="xs" variant="light" color="cyan" radius="sm">
                                  {c.containerNumber ?? "no number"}
                                </Badge>
                              ))}
                          {swappable ? (
                            <Badge size="xs" color="orange" radius="sm" leftSection={<ArrowLeftRight size={10} />}>
                              swap
                            </Badge>
                          ) : null}
                        </Group>
                      </Paper>
                    ) : (
                      <Text size="xs" c="dimmed" ta="center">
                        empty
                      </Text>
                    )}
                  </Table.Td>,
                );
                col = Math.max(col, s.span[1]);
              }
              if (col < legs.length) cells.push(emptyCell(col, legs.length));

              return (
                <Table.Tr
                  key={row.key}
                  style={{
                    background: isPickedRow
                      ? "var(--mantine-color-green-0)"
                      : rowFreeForPicked
                        ? undefined
                        : picked
                          ? "var(--mantine-color-gray-0)"
                          : undefined,
                    opacity: picked && !isPickedRow && !rowFreeForPicked ? 0.55 : 1,
                  }}
                >
                  <Table.Td style={{ position: "sticky", left: 0, background: "var(--mantine-color-body)", zIndex: 1 }}>
                    <Group gap={6} wrap="nowrap">
                      <Badge variant="outline" color="gray" radius="sm" size="sm">
                        #{row.position}
                      </Badge>
                      <Stack gap={0}>
                        <Text size="sm" fw={700}>
                          {row.label}
                        </Text>
                        <Text size="xs" c="dimmed">
                          {row.typeCode ?? "—"} · {round1(row.capacityTons)} t
                        </Text>
                      </Stack>
                      {row.slots.filter((s) => s.loaded).length > 1 ? (
                        <Tooltip label="This wagon carries different loads on different legs">
                          <Badge size="xs" color="violet" variant="light" radius="sm">
                            shared
                          </Badge>
                        </Tooltip>
                      ) : null}
                    </Group>
                  </Table.Td>
                  {cells.map((c, i) => (
                    <Fragment key={i}>{c}</Fragment>
                  ))}
                  <Table.Td>
                    <Text size="xs" fw={600} c={cargoTons > row.capacityTons + 0.001 ? "red.7" : undefined}>
                      {round1(cargoTons)} / {round1(row.capacityTons)} t
                    </Text>
                  </Table.Td>
                </Table.Tr>
              );
            })}
          </Table.Tbody>
        </Table>
      </Paper>
    </Stack>
  );
}
