import { useEffect, useRef, useState } from 'react';
import { Alert, Badge, Button, Group, Modal, MultiSelect, NumberInput, SegmentedControl, Select, SimpleGrid, Stack, Text, TextInput } from '@mantine/core';
import { Info, Scale, Truck } from 'lucide-react';

import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';

import { api } from '@/services/api';
import { useToast } from '@/hooks/use-toast';
import { isBackdated, nowLocalDateTimeInput } from '@/lib/no-backdate';
import { warehouseService } from '@/services/warehouse.service';
import type { WarehouseInventoryItem } from '@/types/warehouse';
import { extractErrorMessage } from './options';
import { openPdfBlob } from './pdf';

interface ReleaseOrderModalProps {
  opened: boolean;
  onClose: () => void;
  item: WarehouseInventoryItem | null;
  truckPrefill?: ReleaseOrderTruckPrefill | null;
}

export interface ReleaseOrderTruckPrefill {
  truckPlateNumber?: string | null;
  trailerPlateNumber?: string | null;
  driverName?: string | null;
  driverLicense?: string | null;
  driverPhone?: string | null;
  truckType?: string | null;
  containerNumber?: string | null;
}

const EXIT_INSPECTION_MARKER = '[Exit Inspection]';

const toIsoDateTime = (value: string) => {
  if (!value) return undefined;
  const date = new Date(value);
  return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
};

const toLocalDateTimeInput = (value?: string | null) => {
  if (!value) return '';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return '';
  const offsetMs = date.getTimezoneOffset() * 60_000;
  return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16);
};

const generateReleaseReference = (item: WarehouseInventoryItem | null) => {
  const bookingReference = item?.booking?.reference;
  if (bookingReference) return `REL-${bookingReference.replace(/^BK-?/i, '')}`;
  if (item?.bookingId) return `REL-${item.bookingId.replace(/-/g, '').slice(0, 8).toUpperCase()}`;
  return '';
};

const lineValue = (notes: string | null | undefined, label: string) => {
  const match = notes?.match(new RegExp(`^${label}:\\s*(.+)$`, 'im'));
  return match?.[1]?.trim() ?? '';
};

const lineNumber = (notes: string | null | undefined, label: string): number | '' => {
  const value = lineValue(notes, label).replace(/\s*(kg|t)$/i, '');
  if (!value) return '';
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : '';
};

const splitContainerNumbers = (value: string | null | undefined) =>
  (value ?? '')
    .split(/[,;\n]+/)
    .map((number) => number.trim())
    .filter(Boolean);

const getItemContainerNumber = (item: WarehouseInventoryItem | null) =>
  (item as (WarehouseInventoryItem & { containerNumber?: string | null }) | null)?.containerNumber ?? '';

const isContainerInventory = (item: WarehouseInventoryItem | null, containerCount: number) => {
  const freightType = (item as (WarehouseInventoryItem & { booking?: { freightType?: string | null } | null }) | null)
    ?.booking?.freightType;
  return Boolean(item?.containerId || containerCount > 0 || freightType === 'CONTAINER');
};

const initialContainerNumbers = (item: WarehouseInventoryItem | null, savedContainerNumber: string) => {
  const savedNumbers = splitContainerNumbers(savedContainerNumber);
  const itemNumbers = splitContainerNumbers(getItemContainerNumber(item));
  const sourceNumbers = savedNumbers.length ? savedNumbers : itemNumbers;
  const quantityCount = isContainerInventory(item, sourceNumbers.length) ? Number(item?.quantity ?? 0) : 0;
  const expectedCount = Math.max(1, sourceNumbers.length, quantityCount);
  return Array.from({ length: expectedCount }, (_, index) => sourceNumbers[index] ?? '');
};

/** One truck's saved arrival/exit weighing, parsed from its inspection block. */
interface InspectionBlock {
  truckPlateNumber: string;
  trailerPlateNumber: string;
  driverName: string;
  driverLicense: string;
  driverPhone: string;
  truckType: string;
  containerNumber: string;
  gateInTime: string;
  tareWeight: number | '';
  grossWeight: number | '';
  netWeight: number | '';
  gateOutTime: string;
  weighingSkipped: boolean;
}

const parseInspectionSection = (note: string): InspectionBlock => ({
  truckPlateNumber: lineValue(note, 'Truck Plate'),
  trailerPlateNumber: lineValue(note, 'Trailer Plate'),
  driverName: lineValue(note, 'Driver'),
  driverLicense: lineValue(note, 'Driver License'),
  driverPhone: lineValue(note, 'Driver Phone'),
  truckType: lineValue(note, 'Truck Type'),
  containerNumber: lineValue(note, 'Container Number'),
  gateInTime: toLocalDateTimeInput(lineValue(note, 'Gate In Time')),
  tareWeight: lineNumber(note, 'Tare Weight'),
  grossWeight: lineNumber(note, 'Gross Weight'),
  netWeight: lineNumber(note, 'Net Weight'),
  gateOutTime: toLocalDateTimeInput(lineValue(note, 'Gate Out Time')),
  weighingSkipped: /^Weighing:\s*SKIPPED/im.test(note),
});

/** Every truck's saved block — multi-truck bookings weigh each truck separately. */
const parseInspectionBlocks = (notes: string | null | undefined): InspectionBlock[] =>
  (notes ?? '')
    .split(EXIT_INSPECTION_MARKER)
    .slice(1)
    .map(parseInspectionSection)
    .filter((block) => block.truckPlateNumber);

/** Match by plate; a legacy block may hold a comma-joined plate list. */
const blockForPlate = (blocks: InspectionBlock[], plate: string): InspectionBlock | undefined => {
  const key = plate.trim().toUpperCase();
  if (!key) return undefined;
  return blocks.find((block) => {
    const stored = block.truckPlateNumber.toUpperCase();
    return stored === key || stored.split(/[,;]+/).map((p) => p.trim()).includes(key);
  });
};

const blockArrived = (block: InspectionBlock | undefined) =>
  Boolean(block && (block.tareWeight !== '' || block.weighingSkipped));

const blockLeft = (block: InspectionBlock | undefined) =>
  Boolean(block?.gateOutTime && (block.grossWeight !== '' || block.weighingSkipped));

export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: ReleaseOrderModalProps) {
  const { toast } = useToast();
  const queryClient = useQueryClient();
  const releaseMutation = useMutation(api.warehouses.release.mutationOptions());
  // Some openers (inventory workbench) supply bookingId without the booking
  // relation — fall back to it, or the truck/container-weight queries never run.
  const bookingId = item?.booking?.id ?? item?.bookingId ?? undefined;
  // Customer self-haul trucks assigned to this booking via the portal.
  const { data: customerTrucks = [] } = useQuery({
    queryKey: ['release-customer-trucks', bookingId],
    queryFn: () => warehouseService.getCustomerTrucks(bookingId as string),
    enabled: opened && Boolean(bookingId),
  });
  // EDR last-mile trucks assigned to this booking — surfaced even when the modal
  // is opened from the warehouse flow (which passes no truckPrefill prop), so an
  // assigned EDR truck no longer shows as "not assigned yet".
  const { data: lastMileTrucks = [] } = useQuery({
    queryKey: ['release-last-mile-trucks', bookingId],
    queryFn: () => warehouseService.getLastMileTrucks(bookingId as string),
    enabled: opened && Boolean(bookingId),
  });
  // Per-container cargo weights — the truck's net (gross − tare) must equal the
  // total cargo weight of the containers selected as loaded on it.
  const { data: containerWeights = [] } = useQuery({
    queryKey: ['release-container-weights', bookingId],
    queryFn: () => warehouseService.getContainerWeights(bookingId as string),
    enabled: opened && Boolean(bookingId),
  });
  const [reference, setReference] = useState('');
  const [truckPlateNumber, setTruckPlateNumber] = useState('');
  const [trailerPlateNumber, setTrailerPlateNumber] = useState('');
  const [driverName, setDriverName] = useState('');
  const [driverLicense, setDriverLicense] = useState('');
  const [driverPhone, setDriverPhone] = useState('');
  const [truckType, setTruckType] = useState('');
  const [containerNumbers, setContainerNumbers] = useState<string[]>(['']);
  const [gateInTime, setGateInTime] = useState('');
  const [tareWeight, setTareWeight] = useState<number | ''>('');
  // Containers may skip the weighbridge (decided at arrival, sticks for exit). Bulk always weighs.
  const [weighTruck, setWeighTruck] = useState<'yes' | 'no'>('yes');
  const [grossWeight, setGrossWeight] = useState<number | ''>('');
  const [netWeight, setNetWeight] = useState<number | ''>('');
  const [gateOutTime, setGateOutTime] = useState('');
  const [downloading, setDownloading] = useState(false);
  // Plate whose saved block was last loaded into the form — stops the
  // per-plate loader effect from clobbering operator edits in a loop.
  const loadedPlateRef = useRef<string | null>(null);

  const savedBlocks = parseInspectionBlocks(item?.notes);

  // Registered trucks for THIS booking, from both sources: EDR last-mile
  // (truckPrefill) and the customer portal (customer_truck_assignments).
  const assignedTruckOptions = [
    ...(truckPrefill?.truckPlateNumber && !truckPrefill.truckPlateNumber.includes(',')
      ? [
          {
            value: truckPrefill.truckPlateNumber,
            label: `Last-mile · ${truckPrefill.truckPlateNumber}`,
            trailerPlate: truckPrefill.trailerPlateNumber ?? '',
            driverName: truckPrefill.driverName ?? '',
            driverLicense: truckPrefill.driverLicense ?? '',
            driverPhone: truckPrefill.driverPhone ?? '',
            truckType: truckPrefill.truckType ?? '',
            containerNumbers: splitContainerNumbers(truckPrefill.containerNumber),
            arrived: false,
            left: false,
          },
        ]
      : []),
    ...customerTrucks.map((t) => ({
      value: t.plateNumber,
      label: `Customer · ${t.plateNumber} — ${t.driverName}`,
      trailerPlate: '',
      driverName: t.driverName,
      driverLicense: '',
      driverPhone: '',
      truckType: t.truckType,
      containerNumbers: (t.containers ?? []).map((c) => c.containerNumber).filter(Boolean),
      arrived: Boolean(t.arrivedAt),
      left: Boolean(t.departedAt),
    })),
    ...lastMileTrucks
      .filter((t) => t.truckPlateNumber || t.vehicleId)
      .map((t) => ({
        value: (t.truckPlateNumber || t.vehicleId) as string,
        label: `Last-mile · ${t.truckPlateNumber ?? ''}${t.driverName ? ` — ${t.driverName}` : ''}`,
        trailerPlate: t.trailerPlateNumber ?? '',
        driverName: t.driverName ?? '',
        driverLicense: t.driverLicense ?? '',
        driverPhone: t.driverPhone ?? '',
        truckType: t.truckType ?? '',
        containerNumbers: splitContainerNumbers(t.containerNumber),
        arrived: Boolean(t.arrivedAt),
        left: Boolean(t.departedAt),
      })),
  ];
  // Only trucks actually assigned to THIS booking (last-mile prefill or customer
  // portal) are selectable. No global fleet list — if nothing is assigned, the
  // operator types the plate manually in the field below. Deduped by plate:
  // duplicate option values crash Mantine's Select.
  const truckSelectOptions = [
    ...new Map(assignedTruckOptions.map((t) => [t.value, t])).values(),
  ];
  const isCustomerAssignedTruck = Boolean(item?.booking?.customerTruckAssignedAt);
  // Neither a last-mile truck nor a customer truck has been assigned yet.
  const noTruckAssigned = assignedTruckOptions.length === 0 && !isCustomerAssignedTruck;

  // Per-truck progress: every truck is weighed in and out on its own; the saved
  // blocks also cover walk-in trucks that were never formally assigned.
  const truckProgress = new Map<string, { arrived: boolean; left: boolean }>();
  for (const option of truckSelectOptions) {
    truckProgress.set(option.value.trim().toUpperCase(), { arrived: option.arrived, left: option.left });
  }
  for (const block of savedBlocks) {
    const key = block.truckPlateNumber.trim().toUpperCase();
    const prior = truckProgress.get(key);
    truckProgress.set(key, {
      arrived: Boolean(prior?.arrived) || blockArrived(block),
      left: Boolean(prior?.left) || blockLeft(block),
    });
  }
  const totalTrucks = truckProgress.size;
  const arrivedTrucks = [...truckProgress.values()].filter((t) => t.arrived).length;
  const leftTrucks = [...truckProgress.values()].filter((t) => t.left).length;

  // The step is decided PER TRUCK: the selected plate's saved block. A new plate
  // (or a truck without a saved arrival) starts at the arrival step even when
  // other trucks of the booking are already mid-flow or gone.
  const selectedBlock = blockForPlate(savedBlocks, truckPlateNumber);
  const isExitStep = blockArrived(selectedBlock);
  const hasTruckLeft = blockLeft(selectedBlock);
  const isEntranceLocked = isExitStep;
  const selectedOption = truckSelectOptions.find(
    (option) => option.value.trim().toUpperCase() === truckPlateNumber.trim().toUpperCase(),
  );
  // Identity comes from the arrival record or the assignment — locked either
  // way. A walk-in truck (typed plate, no assignment) stays editable at arrival.
  const isTruckIdentityLocked = isEntranceLocked || Boolean(selectedOption);
  const isDriverNameLocked = isEntranceLocked || Boolean(selectedOption?.driverName);
  // The freight order's truck details are the customer's / fleet's record — the
  // gate may FILL blanks (walk-in license, phone) but never edit shown values.
  const isTrailerLocked = isEntranceLocked || Boolean(selectedOption?.trailerPlate);
  const isDriverLicenseLocked = isEntranceLocked || Boolean(selectedOption?.driverLicense);
  const isDriverPhoneLocked = isEntranceLocked || Boolean(selectedOption?.driverPhone);
  const referenceLocked = Boolean(item?.releaseOrderReference) || savedBlocks.length > 0;

  /** Load a truck into the form: its saved block if any, else its assignment. */
  const applyTruckSelection = (plate: string) => {
    const block = blockForPlate(savedBlocks, plate);
    const option = truckSelectOptions.find(
      (o) => o.value.trim().toUpperCase() === plate.trim().toUpperCase(),
    );
    loadedPlateRef.current = plate.trim().toUpperCase();
    setTruckPlateNumber(plate);
    setTrailerPlateNumber(block?.trailerPlateNumber || option?.trailerPlate || '');
    setDriverName(block?.driverName || option?.driverName || '');
    setDriverLicense(block?.driverLicense || option?.driverLicense || '');
    setDriverPhone(block?.driverPhone || option?.driverPhone || '');
    setTruckType(block?.truckType || option?.truckType || '');
    const loaded = block
      ? splitContainerNumbers(block.containerNumber)
      : (option?.containerNumbers ?? []);
    setContainerNumbers(loaded.length ? loaded : initialContainerNumbers(item, ''));
    setGateInTime(block?.gateInTime ?? '');
    setTareWeight(block?.tareWeight ?? '');
    setWeighTruck(block?.weighingSkipped ? 'no' : 'yes');
    setGrossWeight(block?.grossWeight ?? '');
    setNetWeight(block?.netWeight ?? (item?.weight == null ? '' : Number(item.weight)));
    setGateOutTime(block?.gateOutTime ?? '');
  };

  useEffect(() => {
    if (opened) {
      loadedPlateRef.current = null;
      setReference(item?.releaseOrderReference ?? generateReleaseReference(item));
      // Initial truck: the caller's prefill, else the first truck still mid-flow
      // (arrived but not left) — the operator can switch trucks in the select.
      const prefillPlate =
        truckPrefill?.truckPlateNumber && !truckPrefill.truckPlateNumber.includes(',')
          ? truckPrefill.truckPlateNumber
          : '';
      const blocks = parseInspectionBlocks(item?.notes);
      const inProgress = blocks.find((block) => blockArrived(block) && !blockLeft(block));
      // Legacy single-truck bookings stored the truck on the booking columns; a
      // comma-joined value means several trucks, so the operator picks instead.
      const bookingPlate = item?.booking?.customerTruckPlateNumber ?? '';
      const legacyPlate = bookingPlate && !bookingPlate.includes(',') ? bookingPlate : '';
      const initialPlate = prefillPlate || inProgress?.truckPlateNumber || legacyPlate || '';
      const block = blockForPlate(blocks, initialPlate);
      loadedPlateRef.current = initialPlate ? initialPlate.trim().toUpperCase() : null;
      setTruckPlateNumber(initialPlate);
      setTrailerPlateNumber(block?.trailerPlateNumber || truckPrefill?.trailerPlateNumber || '');
      setDriverName(
        block?.driverName ||
          truckPrefill?.driverName ||
          (legacyPlate && initialPlate === legacyPlate ? (item?.booking?.customerTruckDriverName ?? '') : ''),
      );
      setDriverLicense(block?.driverLicense || truckPrefill?.driverLicense || '');
      setDriverPhone(block?.driverPhone || truckPrefill?.driverPhone || '');
      setTruckType(
        block?.truckType ||
          truckPrefill?.truckType ||
          (legacyPlate && initialPlate === legacyPlate ? (item?.booking?.customerTruckType ?? '') : ''),
      );
      setContainerNumbers(
        initialContainerNumbers(item, block?.containerNumber || truckPrefill?.containerNumber || ''),
      );
      setGateInTime(block?.gateInTime ?? '');
      setTareWeight(block?.tareWeight ?? '');
      setWeighTruck(block?.weighingSkipped ? 'no' : 'yes');
      setGrossWeight(block?.grossWeight ?? '');
      setNetWeight(block?.netWeight ?? (item?.weight == null ? '' : Number(item.weight)));
      setGateOutTime(block?.gateOutTime ?? '');
    }
  }, [opened, item, truckPrefill]);

  // No truck chosen yet and exactly one is assigned — load it. With several
  // trucks the operator picks which one is at the gate.
  useEffect(() => {
    if (!opened || truckPlateNumber || loadedPlateRef.current) return;
    if (truckSelectOptions.length !== 1) return;
    applyTruckSelection(truckSelectOptions[0].value);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [opened, truckPlateNumber, customerTrucks, lastMileTrucks]);

  // A typed plate that matches a saved arrival reloads that truck's record, so
  // the exit step opens with the weigh-in data instead of blank fields.
  useEffect(() => {
    if (!opened) return;
    const key = truckPlateNumber.trim().toUpperCase();
    if (!key || loadedPlateRef.current === key) return;
    if (blockForPlate(savedBlocks, truckPlateNumber)) applyTruckSelection(truckPlateNumber);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [opened, truckPlateNumber]);

  // Which containers ride this truck, and their combined cargo weight. When the
  // booking has container weights, that sum is the authoritative net; the
  // operator selects the containers loaded on the truck at exit.
  const hasContainerWeights = containerWeights.length > 0;
  const containerWeightByNumber = new Map(
    containerWeights.map((c) => [c.containerNumber.toUpperCase(), Number(c.weightTons) || 0]),
  );
  // A truck may only carry out its OWN assigned containers — when the selected
  // truck has an assigned load, other trucks' containers are not offered.
  const assignedLoad = (selectedOption?.containerNumbers ?? []).map((n) => n.toUpperCase());
  // Mantine Selects throw on duplicate option values — legacy bookings can carry
  // the same container number on two lines, so dedupe defensively.
  const containerSelectData = [
    ...new Map(
      containerWeights.map((c) => [
        c.containerNumber,
        {
          value: c.containerNumber,
          label: `${c.containerNumber} · ${(Number(c.weightTons) || 0).toLocaleString()} t`,
        },
      ]),
    ).values(),
  ].filter(
    (option) =>
      assignedLoad.length === 0 ||
      assignedLoad.includes(option.value.toUpperCase()) ||
      containerNumbers.some((n) => n.trim().toUpperCase() === option.value.toUpperCase()),
  );
  const selectedContainerNumbers = containerNumbers.map((n) => n.trim()).filter(Boolean);
  const selectedCargoWeight = Number(
    selectedContainerNumbers
      .reduce((sum, n) => sum + (containerWeightByNumber.get(n.toUpperCase()) ?? 0), 0)
      .toFixed(3),
  );
  // Skip is only offered for container bookings; bulk always weighs.
  const skipWeighing = hasContainerWeights && weighTruck === 'no';
  // Even an unweighed truck records the cargo weight it is holding — the
  // selected containers' sum is the net that goes on the exit record.
  const useContainerNet = hasContainerWeights && selectedContainerNumbers.length > 0;

  const systemNetWeight = useContainerNet
    ? selectedCargoWeight
    : item?.weight == null
      ? netWeight
      : Number(item.weight);
  const computedNetWeight =
    tareWeight !== '' && grossWeight !== '' ? Number((Number(grossWeight) - Number(tareWeight)).toFixed(3)) : null;
  const weightMismatch =
    !skipWeighing &&
    computedNetWeight != null && systemNetWeight !== '' && Math.abs(Number(systemNetWeight) - computedNetWeight) > 0.001;
  const title = isExitStep ? 'Customer truck leaving and exit weighing' : 'Customer truck arrival weighing';

  const handleSubmit = async () => {
    if (!item) return;
    if (!truckPlateNumber.trim() || !driverName.trim()) {
      toast({ variant: 'destructive', title: 'Truck plate and driver name are required' });
      return;
    }
    if (hasTruckLeft) {
      toast({ variant: 'destructive', title: `Truck ${truckPlateNumber} has already left — its exit record is locked` });
      return;
    }
    if (!gateInTime || (!skipWeighing && tareWeight === '')) {
      toast({
        variant: 'destructive',
        title: skipWeighing ? 'Gate in time is required' : 'Gate in time and tare weight are required',
      });
      return;
    }
    // No backdating: gate times are recorded as they happen. The locked
    // entrance (exit step) keeps its original past gate-in untouched.
    if (!isEntranceLocked && isBackdated(gateInTime)) {
      toast({ variant: 'destructive', title: 'Gate in time cannot be in the past' });
      return;
    }
    if (isExitStep && (!gateOutTime || (!skipWeighing && grossWeight === ''))) {
      toast({
        variant: 'destructive',
        title: skipWeighing ? 'Gate out time is required' : 'Gate out time and gross weight are required',
      });
      return;
    }
    if (isExitStep && isBackdated(gateOutTime)) {
      toast({ variant: 'destructive', title: 'Gate out time cannot be in the past' });
      return;
    }
    if (isExitStep && hasContainerWeights && selectedContainerNumbers.length === 0) {
      toast({ variant: 'destructive', title: 'Select the containers loaded on this truck' });
      return;
    }
    if (isExitStep && !skipWeighing && systemNetWeight === '') {
      toast({ variant: 'destructive', title: 'System recorded net weight is missing' });
      return;
    }
    if (isExitStep && weightMismatch) {
      toast({
        variant: 'destructive',
        title: 'Weight mismatch',
        description: 'Gate clearance is blocked. Reassign the item to warehouse if it cannot exit.',
      });
      return;
    }
    const pdfWindow = isExitStep ? window.open('', '_blank') : null;
    try {
      const released = await releaseMutation.mutateAsync({
        id: item.id,
        payload: {
          reference: reference.trim() || undefined,
          bookingId: item.bookingId ?? undefined,
          customerId: undefined,
          truckPlateNumber: truckPlateNumber.trim(),
          trailerPlateNumber: trailerPlateNumber.trim() || undefined,
          driverName: driverName.trim(),
          driverLicense: driverLicense.trim() || undefined,
          driverPhone: driverPhone.trim() || undefined,
          truckType: truckType.trim() || undefined,
          containerNumber: containerNumbers.map((number) => number.trim()).filter(Boolean).join(', ') || undefined,
          gateInTime: toIsoDateTime(gateInTime),
          weighingSkipped: skipWeighing || undefined,
          tareWeight: skipWeighing ? undefined : Number(tareWeight),
          grossWeight: skipWeighing || grossWeight === '' ? undefined : Number(grossWeight),
          // Skipped weighing still records the net from what the truck holds.
          netWeight: isExitStep && systemNetWeight !== '' ? Number(systemNetWeight) : undefined,
          gateOutTime: isExitStep ? toIsoDateTime(gateOutTime) : undefined,
        },
      });
      await queryClient.invalidateQueries({ queryKey: ['release-customer-trucks', bookingId] });
      await queryClient.invalidateQueries({ queryKey: ['release-last-mile-trucks', bookingId] });
      if (!isExitStep) {
        const remaining = totalTrucks > 1 ? ` (${Math.min(arrivedTrucks + 1, totalTrucks)} of ${totalTrucks} trucks arrived)` : '';
        toast({
          title: `Truck ${truckPlateNumber.trim()} arrival saved${remaining}`,
          description: `${released.releaseOrderReference ?? reference} is ready for exit weighing.`,
        });
        onClose();
        return;
      }
      setDownloading(true);
      const response = await warehouseService.downloadReleaseDocument(item.id);
      const blob = response.data;
      const filename = `release-${released.booking?.reference ?? released.bookingId ?? item.id}.pdf`;
      const opened = openPdfBlob(blob, filename, pdfWindow);
      const remainingExit = totalTrucks > 1 ? ` ${Math.min(leftTrucks + 1, totalTrucks)} of ${totalTrucks} trucks have left.` : '';
      toast({
        title: 'Release exit paper issued',
        description: (opened
          ? 'The PDF opened in a browser tab for printing or saving.'
          : 'The browser blocked the preview tab, so the PDF was downloaded.') + remainingExit,
      });
      onClose();
    } catch (error) {
      pdfWindow?.close();
      toast({ variant: 'destructive', title: 'Release failed', description: extractErrorMessage(error) });
    } finally {
      setDownloading(false);
    }
  };

  return (
    <Modal opened={opened} onClose={onClose} title={title} centered size="lg">
      <Stack gap="md">
        <Alert icon={<Info size={16} />} color="orange" variant="light">
          {isExitStep ? (
            <Text size="sm">
              Record the truck leaving time and gross weight. The system recorded net weight is locked,
              and the exit paper is generated only when it equals gross weight minus tare weight.
            </Text>
          ) : (
            <Text size="sm">
              Register the customer truck and driver at arrival, then save gate in time and tare weight.
              Reopen this form when the truck is leaving to complete the exit weighing.
            </Text>
          )}
        </Alert>
        {totalTrucks > 1 && (
          <Alert icon={<Truck size={16} />} color="blue" variant="light">
            <Group gap="xs">
              <Text size="sm">
                {totalTrucks} trucks on this booking — each is weighed in and out separately.
              </Text>
              <Badge size="sm" variant="light" color={arrivedTrucks === totalTrucks ? 'green' : 'blue'}>
                {arrivedTrucks}/{totalTrucks} arrived
              </Badge>
              <Badge size="sm" variant="light" color={leftTrucks === totalTrucks ? 'green' : 'gray'}>
                {leftTrucks}/{totalTrucks} left
              </Badge>
            </Group>
          </Alert>
        )}
        {hasTruckLeft && (
          <Alert icon={<Info size={16} />} color="green" variant="light">
            <Text size="sm">
              Truck {truckPlateNumber} has already left — its exit record is locked. Pick another
              truck to continue the remaining arrivals and exits.
            </Text>
          </Alert>
        )}
        <TextInput
          label="Release document reference"
          placeholder="e.g. REL-2026-001"
          value={reference}
          onChange={(e) => setReference(e.currentTarget.value)}
          readOnly={referenceLocked}
        />
        {noTruckAssigned && (
          <Alert color="orange" variant="light" icon={<Info size={16} />}>
            Truck is not assigned yet — assign a last-mile or customer truck, or enter the plate manually below.
          </Alert>
        )}
        {truckSelectOptions.length > 0 && (
          <Select
            label="Truck at the gate"
            description="Pick which assigned truck is being processed — switching trucks loads that truck's own arrival/exit record."
            placeholder="Select the assigned truck"
            searchable
            clearable
            disabled={releaseMutation.isPending || downloading}
            data={truckSelectOptions.map(({ value, label, arrived, left }) => ({
              value,
              label: `${label}${left ? ' · LEFT' : arrived ? ' · ON SITE' : ''}`,
            }))}
            value={truckSelectOptions.some((truck) => truck.value === truckPlateNumber) ? truckPlateNumber : null}
            onChange={(value) => {
              if (value) applyTruckSelection(value);
            }}
          />
        )}
        <Group grow>
          <TextInput
            label="Truck plate number"
            required
            value={truckPlateNumber}
            onChange={(e) => setTruckPlateNumber(e.currentTarget.value)}
            readOnly={isTruckIdentityLocked}
          />
          <TextInput
            label="Trailer plate number"
            value={trailerPlateNumber}
            onChange={(e) => setTrailerPlateNumber(e.currentTarget.value)}
            readOnly={isTrailerLocked}
          />
        </Group>
        <Group grow>
          <TextInput label="Driver name" required value={driverName} onChange={(e) => setDriverName(e.currentTarget.value)} readOnly={isDriverNameLocked} />
          <TextInput label="Driver license" value={driverLicense} onChange={(e) => setDriverLicense(e.currentTarget.value)} readOnly={isDriverLicenseLocked} />
        </Group>
        <Group grow>
          <TextInput label="Driver phone" value={driverPhone} onChange={(e) => setDriverPhone(e.currentTarget.value)} readOnly={isDriverPhoneLocked} />
          <TextInput label="Truck type" value={truckType} onChange={(e) => setTruckType(e.currentTarget.value)} readOnly={isTruckIdentityLocked} />
        </Group>
        <Group grow align="flex-start">
          {hasContainerWeights ? (
            <MultiSelect
              label="Containers on this truck"
              description={
                isExitStep
                  ? 'Select the containers loaded on this truck — their cargo weight must match gross − tare.'
                  : 'Containers this truck will carry.'
              }
              placeholder="Select containers"
              searchable
              data={containerSelectData}
              value={selectedContainerNumbers}
              onChange={(values) => setContainerNumbers(values.length ? values : [''])}
              disabled={hasTruckLeft}
            />
          ) : (
            <Stack gap={6}>
              <SimpleGrid cols={containerNumbers.length > 1 ? 2 : 1} spacing="sm">
                {containerNumbers.map((containerNumber, index) => (
                  <TextInput
                    key={index}
                    label={containerNumbers.length > 1 ? `Container number ${index + 1}` : 'Container number'}
                    value={containerNumber}
                    onChange={(e) =>
                      setContainerNumbers((numbers) =>
                        numbers.map((number, numberIndex) => (numberIndex === index ? e.currentTarget.value : number)),
                      )
                    }
                    readOnly={isTruckIdentityLocked}
                  />
                ))}
              </SimpleGrid>
            </Stack>
          )}
          <TextInput label="Gate in time" type="datetime-local" min={isEntranceLocked ? undefined : nowLocalDateTimeInput()} value={gateInTime} onChange={(e) => setGateInTime(e.currentTarget.value)} readOnly={isEntranceLocked} />
        </Group>
        {hasContainerWeights && (
          <Group gap="md" align="center">
            <Text size="sm" fw={600}>Weigh truck?</Text>
            <SegmentedControl
              size="xs"
              data={[{ value: 'yes', label: 'Yes — weigh' }, { value: 'no', label: 'No — pass' }]}
              value={weighTruck}
              onChange={(v) => setWeighTruck((v as 'yes' | 'no') ?? 'yes')}
              disabled={isEntranceLocked}
            />
            {skipWeighing && (
              <Text size="xs" c="dimmed">
                Weighbridge skipped — the selected containers' cargo weight is recorded as the net.
              </Text>
            )}
          </Group>
        )}
        <Group grow>
          <NumberInput label="Tare weight (t)" required={!skipWeighing} min={0} value={tareWeight} onChange={(v) => setTareWeight(v === '' ? '' : Number(v))} readOnly={isEntranceLocked} disabled={skipWeighing} />
          <NumberInput label="Gross weight (t)" required={isExitStep && !skipWeighing} min={0} value={grossWeight} onChange={(v) => setGrossWeight(v === '' ? '' : Number(v))} disabled={!isExitStep || skipWeighing || hasTruckLeft} />
          <NumberInput
            label={useContainerNet ? 'Selected cargo net (t)' : 'Recorded net weight (system t)'}
            min={0}
            value={systemNetWeight}
            readOnly
          />
        </Group>
        <Group justify="space-between">
          <Text size="sm" c={weightMismatch ? 'red' : 'dimmed'}>
            Computed net: <b>{computedNetWeight == null ? '-' : `${computedNetWeight.toLocaleString()} t`}</b>
          </Text>
          <TextInput label="Gate out time" type="datetime-local" min={nowLocalDateTimeInput()} value={gateOutTime} onChange={(e) => setGateOutTime(e.currentTarget.value)} disabled={!isExitStep || hasTruckLeft} />
        </Group>
        {weightMismatch && (
          <Alert icon={<Scale size={16} />} color="red" variant="light">
            <Text size="sm">
              Weight mismatch detected. Exit paper and gate clearance are blocked; use Store or Move to
              reassign the item back to warehouse handling.
            </Text>
          </Alert>
        )}
        <Group justify="flex-end" mt="sm">
          <Button variant="default" onClick={onClose} disabled={releaseMutation.isPending || downloading}>
            Cancel
          </Button>
          <Button color="orange" onClick={handleSubmit} loading={releaseMutation.isPending || downloading} disabled={hasTruckLeft}>
            {isExitStep ? 'Save Truck Leaving & View Exit Paper' : 'Save Truck Arrival'}
          </Button>
        </Group>
      </Stack>
    </Modal>
  );
}
