import React, { useEffect, useMemo, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import {
  Autocomplete,
  Backdrop,
  Box,
  Button,
  Card,
  Chip,
  Dialog,
  DialogActions,
  DialogContent,
  DialogTitle,
  IconButton,
  Stack,
  Tab,
  Tabs,
  TextField,
  Tooltip,
  Typography,
  useMediaQuery,
  useTheme
} from '@mui/material';
import { useMutation, useQuery } from '@tanstack/react-query';
import dayjs from 'dayjs';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { HiOutlineArrowLeft } from 'react-icons/hi';
import { IoReload } from 'react-icons/io5';
import { MdTwoWheeler, MdSwapHoriz } from 'react-icons/md';

import {
  createAutomationDeliveries,
  createOptimisationDeliveries,
  fetchRidersList,
  finalCreatedeliveries,
  notifyRider,
  reconcileSteps
} from '../../api/api';
import { OpenToast } from 'components/third-party/OpenToast';
import CSVExport from 'components/third-party/ReactTable';
import CircularLoader from 'components/CircularLoader';
import Dispatch from './Dispatch';
import { stepColor } from './dispatchShared';

const tuningTypes = [
  { tuneid: 1, type: 'Balanced', value: 'balanced' },
  { tuneid: 2, type: 'Aggressive Speed', value: 'aggressive_speed' },
  { tuneid: 3, type: 'Fuel Saver', value: 'fuel_saver' },
  { tuneid: 4, type: 'Zone Strict', value: 'zone_strict' }
];

// Flatten the API's zoned shape into [{ rider_id, rider_name, orders }] for
// the Reconcile tab UI and the reconcile-API payload.
const extractRiders = (previewData) => {
  if (!previewData) return [];
  const map = new Map();
  // De-dupe by orderid across the whole tree. A rider can legitimately appear
  // in multiple zones (one per delivery suburb), so the same rider_id is
  // visited more than once. Without this guard, any stale copy left behind
  // by applyReconcileResponse gets concatenated into the rider's orders and
  // the same orderid is sent twice to /deliveries/createdeliveries.
  const seenOrderIds = new Set();
  const push = (riderId, riderName, orders) => {
    if (riderId == null) return;
    const key = String(riderId);
    if (!map.has(key)) {
      map.set(key, { rider_id: riderId, rider_name: riderName, orders: [] });
    }
    const entry = map.get(key);
    (orders || []).forEach((o) => {
      const oid = o?.orderid != null ? String(o.orderid) : null;
      if (oid) {
        if (seenOrderIds.has(oid)) return;
        seenOrderIds.add(oid);
      }
      entry.orders.push(o);
    });
    if (!entry.rider_name && riderName) entry.rider_name = riderName;
  };

  if (Array.isArray(previewData.zones) && previewData.zones.length) {
    previewData.zones.forEach((z) => {
      (z.riders || []).forEach((r) => {
        const id = r.rider_id ?? r.userid;
        const name = r.rider_name || r.username || `Rider ${id}`;
        push(id, name, r.orders);
      });
    });
  } else if (Array.isArray(previewData.details)) {
    previewData.details.forEach((o) => {
      const id = o.rider_id ?? o.userid;
      const name = o.rider_name || o.ridername || `Rider ${id}`;
      push(id, name, [o]);
    });
  }
  return Array.from(map.values());
};

// Reverse of extractRiders — flatten rider-grouped list into a details-style
// array (used as the Assign Orders payload).
const flattenRiders = (riders) => {
  const out = [];
  riders.forEach((r) => {
    // Go backend types Deliveries.userid as int — coerce here so any
    // upstream string (AI response, riders API, change-rider edit) gets
    // normalised before the JSON body is built.
    const ridNum = Number(r.rider_id);
    const rid = Number.isFinite(ridNum) ? ridNum : r.rider_id;
    (r.orders || []).forEach((o) => {
      out.push({
        ...o,
        rider_id: rid,
        userid: rid,
        rider_name: r.rider_name,
        rider: r.rider_name
      });
    });
  });
  return out;
};

// Move one order from oldRiderId -> newRiderId inside dispatchPreviewData.
// Mutates both the zones[].riders[].orders[] tree (so the Dispatch tab
// renders the change) AND the flat details[] list (so Assign Orders picks
// it up). Returns a NEW preview object (immutable update).
const moveOrderInPreviewData = (preview, { orderId, newRiderId, newRiderName }) => {
  if (!preview) return preview;
  const next = JSON.parse(JSON.stringify(preview));

  // 1) Update flat details list
  if (Array.isArray(next.details)) {
    next.details = next.details.map((o) =>
      String(o.orderid) === String(orderId)
        ? { ...o, rider_id: newRiderId, userid: newRiderId, rider_name: newRiderName, rider: newRiderName }
        : o
    );
  }

  // 2) Move within zones[].riders[].orders[]
  if (Array.isArray(next.zones)) {
    let movedOrder = null;
    let homeZoneIdx = -1;

    for (let zi = 0; zi < next.zones.length && !movedOrder; zi++) {
      const zone = next.zones[zi];
      if (!Array.isArray(zone.riders)) continue;
      for (let ri = 0; ri < zone.riders.length && !movedOrder; ri++) {
        const r = zone.riders[ri];
        if (!Array.isArray(r.orders)) continue;
        const oi = r.orders.findIndex((o) => String(o.orderid) === String(orderId));
        if (oi !== -1) {
          movedOrder = r.orders[oi];
          r.orders.splice(oi, 1);
          homeZoneIdx = zi;
        }
      }
    }

    if (movedOrder) {
      const updated = {
        ...movedOrder,
        rider_id: newRiderId,
        userid: newRiderId,
        rider_name: newRiderName,
        rider: newRiderName
      };
      let placed = false;
      for (const zone of next.zones) {
        if (!Array.isArray(zone.riders)) continue;
        const target = zone.riders.find(
          (r) => String(r.rider_id ?? r.userid) === String(newRiderId)
        );
        if (target) {
          target.orders = target.orders || [];
          target.orders.push(updated);
          placed = true;
          break;
        }
      }
      if (!placed && homeZoneIdx >= 0) {
        next.zones[homeZoneIdx].riders.push({
          rider_id: newRiderId,
          userid: newRiderId,
          rider_name: newRiderName,
          orders: [updated]
        });
      }
    }
  }

  return next;
};

// Merge a reconcile-API response { riders:[{rider_id, orders}] } back into
// dispatchPreviewData. Replaces each rider's orders[] in zones (preserving
// zone containment), then rebuilds the flat details list from the new tree.
const applyReconcileResponse = (preview, response) => {
  if (!preview || !Array.isArray(response?.riders)) return preview;
  const next = JSON.parse(JSON.stringify(preview));

  const newOrdersByRider = new Map(
    response.riders.map((r) => [String(r.rider_id), r.orders || []])
  );

  if (Array.isArray(next.zones) && next.zones.length) {
    // Pass 1: wipe every existing copy of a responding rider's orders across
    // ALL zones. The server's reconciled list is the single source of truth,
    // and a rider can be present in multiple zones (one per delivery suburb).
    // The previous "update first match, delete from map" loop left stale
    // copies in the other zones, which extractRiders then concatenated into
    // duplicate orderids — surfacing as duplicate deliveries on Assign.
    next.zones.forEach((zone) => {
      if (!Array.isArray(zone.riders)) return;
      zone.riders.forEach((r) => {
        const key = String(r.rider_id ?? r.userid);
        if (newOrdersByRider.has(key)) r.orders = [];
      });
    });

    // Pass 2: drop the reconciled orders onto the first zone that already
    // lists the rider. If the rider isn't anywhere in the tree, append a
    // fresh rider entry to zone[0].
    newOrdersByRider.forEach((orders, riderKey) => {
      let placed = false;
      for (const zone of next.zones) {
        if (!Array.isArray(zone.riders)) continue;
        const target = zone.riders.find(
          (r) => String(r.rider_id ?? r.userid) === riderKey
        );
        if (target) {
          target.orders = orders;
          placed = true;
          break;
        }
      }
      if (!placed) {
        const target = next.zones[0];
        target.riders = target.riders || [];
        target.riders.push({
          rider_id: Number(riderKey) || riderKey,
          rider_name: orders[0]?.rider_name || `Rider ${riderKey}`,
          orders
        });
      }
    });
  } else {
    next.zones = [
      {
        zone_name: 'Reconciled',
        riders: response.riders.map((r) => ({
          rider_id: r.rider_id,
          rider_name: r.rider_name || `Rider ${r.rider_id}`,
          orders: r.orders || []
        }))
      }
    ];
  }

  // Rebuild flat details from the updated zones->riders->orders tree.
  const flatDetails = [];
  next.zones.forEach((zone) => {
    (zone.riders || []).forEach((r) => {
      (r.orders || []).forEach((o) => {
        flatDetails.push({
          ...o,
          rider_id: r.rider_id,
          userid: r.rider_id,
          rider_name: r.rider_name,
          rider: r.rider_name
        });
      });
    });
  });
  next.details = flatDetails;

  return next;
};

const Preview = () => {
  const navigate = useNavigate();
  const location = useLocation();
  const stateData = location.state || {};
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('md'));

  // SINGLE SOURCE OF TRUTH: every Change Rider / Reconcile / Re-Assign goes
  // through this state. The Dispatch tab renders from it, the Reconcile tab
  // derives its rider list from it, and Assign Orders sends a flattened copy
  // of it to the API.
  const [dispatchPreviewData, setDispatchPreviewData] = useState(stateData.dispatchPreviewData || null);

  // The AI response arrives via location.state, which the browser stores in
  // history.state and persists across reloads. That means a reload of
  // /dispatch/preview would re-hydrate the stale snapshot — including any
  // pending edits the user thought they discarded. Bounce to /orders when
  // there's no fresh response, and wipe the history snapshot once consumed
  // so a later reload / back-forward also bounces instead of re-using it.
  useEffect(() => {
    if (!stateData.dispatchPreviewData) {
      navigate('/doormile/orders', { replace: true });
      return;
    }
    if (typeof window !== 'undefined' && window.history?.state) {
      window.history.replaceState({ ...window.history.state, usr: null }, '');
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  const [csvExportData, setCsvExportData] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [tabValue, setTabValue] = useState(0);

  const [reconcileLoading, setReconcileLoading] = useState(false);
  const [hasReconciled, setHasReconciled] = useState(false);

  // Tracks riders whose orders have been edited since the last AI response
  // or successful reconcile. Only these are sent to the reconcile API — the
  // server-side step re-ordering only needs to see what actually changed.
  const [dirtyRiderIds, setDirtyRiderIds] = useState(() => new Set());

  // Change-rider dialog state
  const [changeDialogOpen, setChangeDialogOpen] = useState(false);
  const [selectedOrder, setSelectedOrder] = useState(null);
  const [selectedOldRiderId, setSelectedOldRiderId] = useState(null);
  const [selectedNewRider, setSelectedNewRider] = useState(null);

  const aiMode = stateData.aiMode ?? 1;
  const selectedMode = stateData.selectedMode || null;
  const deliveryData = stateData.deliveryData || [];
  const autoRiders = stateData.autoRiders || [];
  const absentRidersPayload = stateData.absentRidersPayload || [];
  const rider = stateData.rider || null;

  const appId = useMemo(() => {
    if (stateData.appId) return stateData.appId;
    if (typeof window !== 'undefined') {
      const v = localStorage.getItem('applocationid');
      return v ? Number(v) : 0;
    }
    return 0;
  }, [stateData.appId]);

  const { data: ridersList } = useQuery({
    queryKey: ['ridersList', appId],
    queryFn: fetchRidersList,
    enabled: !!appId,
    staleTime: 5 * 60 * 1000
  });

  // Derived: rider list for the Reconcile tab. Recomputes whenever the cache
  // (dispatchPreviewData) changes — so Change Rider / Reconcile both reflect
  // here without a separate state.
  const reconcileRiders = useMemo(() => extractRiders(dispatchPreviewData), [dispatchPreviewData]);

  // Derived: flat orders list used for the Assign Orders payload + CSV export.
  // Always reflects the latest cache state.
  const finaldeliveryList = useMemo(() => {
    const flat = flattenRiders(reconcileRiders);
    if (flat.length) return computeDeliveryAmounts(flat);
    if (Array.isArray(dispatchPreviewData?.details)) {
      return computeDeliveryAmounts(dispatchPreviewData.details);
    }
    return [];
  }, [reconcileRiders, dispatchPreviewData]);

  useEffect(() => {
    const filtered = finaldeliveryList.map((item) => ({
      zone_name: item.zone_name,
      ordernotes: item.ordernotes,
      rider: item.rider,
      step: item.step,
      ordertype: item.ordertype,
      orderamount: item.orderamount,
      riderkms: item.riderkms,
      cumulativekms: item.cumulativekms,
      baseprice: item.baseprice,
      minkm: item.minkm,
      priceperkm: item.priceperkm,
      kms: item.kms,
      actualkms: item.actualkms,
      rider_charge: item.rider_charge,
      deliveryamt: item.deliveryamt,
      deliverycharges: item.deliverycharges,
      profit: item.profit
    }));
    setCsvExportData(filtered);
  }, [finaldeliveryList]);

  const notifyRiderMutation = useMutation({
    mutationFn: notifyRider,
    onSuccess: () => OpenToast('Notification sent Successfully', 'success', 2000),
    onError: (error) => OpenToast(error.message, 'error', 2000)
  });

  const createDeliveryMutation = useMutation({
    mutationFn: aiMode == 0 ? createOptimisationDeliveries : createAutomationDeliveries,
    onSuccess: (data) => {
      OpenToast('Orders Optimised  Successfully', 'success', 2000);
      // Brand new response = brand new source of truth.
      setDispatchPreviewData(data);
      setHasReconciled(false);
      setDirtyRiderIds(new Set());
      setIsLoading(false);
    },
    onError: (error) => {
      OpenToast(error.message, 'error', 4000);
      setIsLoading(false);
    },
    onSettled: () => setIsLoading(false)
  });

  const createFinalDeliveryMutation = useMutation({
    mutationFn: finalCreatedeliveries,
    onSuccess: () => {
      OpenToast('Delivery Created Successfully', 'success', 2000);
      setIsLoading(false);
      if (rider?.userfcmtoken) notifyRiderMutation.mutate(rider.userfcmtoken);
      navigate('/doormile/deliveries');
    },
    onError: (error) => {
      OpenToast(error.message, 'error', 4000);
      setIsLoading(false);
    },
    onSettled: () => setIsLoading(false)
  });

  const reconcileMutation = useMutation({
    mutationFn: reconcileSteps,
    onMutate: () => setReconcileLoading(true),
    onSuccess: (data) => {
      if (Array.isArray(data?.riders)) {
        // Merge: applyReconcileResponse replaces orders for riders present
        // in the response and leaves the rest of the cache untouched.
        setDispatchPreviewData((prev) => applyReconcileResponse(prev, data));
        setHasReconciled(true);
        // Clear only the riders we just reconciled from the dirty set, so
        // any unrelated edits made meanwhile are preserved.
        setDirtyRiderIds((prev) => {
          const next = new Set(prev);
          data.riders.forEach((r) => next.delete(String(r.rider_id)));
          return next;
        });
        OpenToast('Steps reconciled — preview updated', 'success', 2000);
      } else {
        OpenToast('Reconcile returned no rider data', 'warning', 3000);
      }
    },
    onError: (error) => {
      OpenToast(error.message || 'Reconcile failed', 'error', 4000);
    },
    onSettled: () => setReconcileLoading(false)
  });

  const handleCreateDelivery = (tune) => {
    setIsLoading(true);
    if (aiMode == 0) {
      createDeliveryMutation.mutate({ deliveries: deliveryData });
    } else if (selectedMode && selectedMode?.value == 1) {
      createDeliveryMutation.mutate({
        deliveries: deliveryData,
        hypertuning_params: tune || null,
        selectedMode,
        absent_riders: absentRidersPayload
      });
    } else {
      createDeliveryMutation.mutate({
        data: {
          orders: deliveryData,
          riders: autoRiders,
          config: { pay_type: 'hourly', base_pay: 300.0, strategy: 'multi_trip' },
          absent_riders: absentRidersPayload
        },
        selectedMode
      });
    }
  };

  const handleFinalCreateDelivery = () => {
    if (!finaldeliveryList?.length) {
      OpenToast('No deliveries to assign', 'error', 3000);
      return;
    }
    setIsLoading(true);
    createFinalDeliveryMutation.mutate({ deliveries: finaldeliveryList });
  };

  const handleReconcile = () => {
    if (!reconcileRiders.length) {
      OpenToast('No riders to reconcile', 'warning', 3000);
      return;
    }
    // Only send riders that were edited since the last AI response / reconcile.
    // Their step ordering is the only thing that can be stale — untouched
    // riders are skipped to keep the payload small.
    const dirty = reconcileRiders.filter((r) =>
      dirtyRiderIds.has(String(r.rider_id))
    );
    if (!dirty.length) {
      OpenToast('No edits to reconcile', 'info', 2500);
      return;
    }
    reconcileMutation.mutate({
      riders: dirty.map((r) => ({
        rider_id: r.rider_id,
        orders: r.orders
      }))
    });
  };

  const openChangeRider = (oldRider, order) => {
    const oldId =
      oldRider?.rider_id ?? oldRider?.id ?? order?.rider_id ?? order?.userid ?? null;
    setSelectedOldRiderId(oldId);
    setSelectedOrder(order);
    setSelectedNewRider(null);
    setChangeDialogOpen(true);
  };

  const confirmChangeRider = () => {
    if (!selectedNewRider || !selectedOrder) return;
    // Backend expects an int — coerce at the boundary so a string from the
    // riders API doesn't propagate into the Assign Orders payload.
    const newRiderId = Number(selectedNewRider.userid);
    const newRiderName =
      selectedNewRider.label ||
      `${selectedNewRider.firstname || ''} ${selectedNewRider.lastname || ''}`.trim() ||
      `Rider ${newRiderId}`;

    setDispatchPreviewData((prev) =>
      moveOrderInPreviewData(prev, {
        orderId: selectedOrder.orderid,
        oldRiderId: selectedOldRiderId,
        newRiderId,
        newRiderName
      })
    );
    // Both riders' step sequences are now potentially stale: the old rider
    // lost a stop, the new rider gained one. Mark both as dirty so the next
    // Reconcile sends exactly these two.
    setDirtyRiderIds((prev) => {
      const next = new Set(prev);
      if (selectedOldRiderId != null) next.add(String(selectedOldRiderId));
      if (newRiderId != null && Number.isFinite(newRiderId)) next.add(String(newRiderId));
      return next;
    });
    setHasReconciled(false);
    setChangeDialogOpen(false);
    OpenToast('Rider changed — click Reconcile to verify steps', 'info', 2500);
  };

  return (
    <Box sx={{ display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden', position: 'relative' }}>
      <Backdrop
        sx={{ position: 'absolute', color: '#fff', zIndex: (theme) => theme.zIndex.modal + 1 }}
        open={isLoading}
      >
        <CircularLoader color="inherit" />
      </Backdrop>

      <Box sx={{ py: 1.25, px: 2, borderBottom: '1px solid #eef2f6' }}>
        <Stack
          direction={{ xs: 'column', md: 'row' }}
          alignItems={{ xs: 'stretch', md: 'center' }}
          justifyContent="space-between"
          spacing={1.25}
        >
          <Stack direction="row" alignItems="center" spacing={1}>
            <Tooltip title="Back to orders" placement="top">
              <IconButton
                onClick={() => navigate('/doormile/orders')}
                sx={{ bgcolor: 'action.hover', '&:hover': { bgcolor: 'action.selected' } }}
              >
                <HiOutlineArrowLeft size={20} />
              </IconButton>
            </Tooltip>
            <Typography variant="h3" fontWeight={600}>
              Assign Orders
            </Typography>
          </Stack>
          <Stack
            direction={{ xs: 'column', sm: 'row' }}
            alignItems={{ xs: 'stretch', sm: 'center' }}
            spacing={1}
            sx={{ width: { xs: '100%', md: 'auto' } }}
          >
            <Autocomplete
              options={tuningTypes || []}
              getOptionLabel={(option) => option.type}
              sx={{ minWidth: { xs: 0, sm: 250 }, maxWidth: 600, flex: 1, width: { xs: '100%', sm: 'auto' } }}
              renderInput={(params) => <TextField {...params} label="Hyper Tuning" />}
              onChange={(e, val, reason) => {
                if (reason === 'clear') handleCreateDelivery(null);
                else handleCreateDelivery(val.value);
              }}
            />
            <Button
              variant="contained"
              color="primary"
              startIcon={<IoReload />}
              fullWidth={isMobile}
              onClick={() => {
                setIsLoading(true);
                handleCreateDelivery('reshuffle');
              }}
            >
              Re-Assign
            </Button>
            <CSVExport
              data={csvExportData}
              filename={`Orders_Detail_${dayjs().format('YYYY-MM-DD_HHmmss')}.csv`}
              label=" CSV"
              style={{ m: 1 }}
            />
          </Stack>
        </Stack>
      </Box>

      <Box sx={{ px: 2, borderBottom: '1px solid #eef2f6' }}>
        <Tabs
          value={tabValue}
          onChange={(e, v) => setTabValue(v)}
          variant={isMobile ? 'fullWidth' : 'standard'}
          sx={{ minHeight: 40 }}
        >
          <Tab label="Dispatch" sx={{ minHeight: 40, textTransform: 'none', fontWeight: 600 }} />
          <Tab label="Reconcile" sx={{ minHeight: 40, textTransform: 'none', fontWeight: 600 }} />
        </Tabs>
      </Box>

      <Box sx={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
        {tabValue === 0 && dispatchPreviewData && (
          <Dispatch
            // The key forces a full re-mount when the cache reference changes
            // (after Change Rider / Reconcile / Re-Assign) so Dispatch's
            // internal state (focused rider, view mode, etc.) recomputes
            // against the new orders. Without this, internal memos can stick
            // to the previous data shape.
            key={dispatchPreviewData?.__cacheKey || JSON.stringify(reconcileRiders.length)}
            data={dispatchPreviewData}
            embedded
            onChangeRider={(order, focusedRider) => openChangeRider(focusedRider, order)}
          />
        )}
        {tabValue === 1 && (
          <Box sx={{ flex: 1, overflow: 'auto', p: 2, bgcolor: '#f8fafc' }}>
            {reconcileRiders.length === 0 ? (
              <Typography sx={{ color: '#94a3b8', textAlign: 'center', mt: 4 }}>
                No rider data available to reconcile.
              </Typography>
            ) : (
              <Stack spacing={1.75}>
                <Box
                  sx={{
                    bgcolor: hasReconciled ? '#ecfdf5' : '#fffbeb',
                    border: `1px solid ${hasReconciled ? '#a7f3d0' : '#fde68a'}`,
                    color: hasReconciled ? '#065f46' : '#92400e',
                    borderRadius: '10px',
                    px: 1.5,
                    py: 1,
                    fontSize: 13
                  }}
                >
                  {hasReconciled
                    ? 'Steps have been reconciled. The Dispatch tab and Assign payload are updated.'
                    : 'Click a numbered step to change its rider. Hit Reconcile to verify the corrected steps with the server.'}
                </Box>

                {reconcileRiders.map((r) => {
                  const totalKms = r.orders.reduce((s, o) => s + parseFloat(o.actualkms || o.kms || 0), 0);
                  return (
                    <Card key={r.rider_id} sx={{ p: { xs: 1.5, md: 2 }, borderRadius: '12px', boxShadow: '0 1px 3px rgba(15,23,42,0.06)' }}>
                      <Stack
                        direction="row"
                        justifyContent="space-between"
                        alignItems="center"
                        sx={{ mb: 1.25, flexWrap: 'wrap', gap: 1 }}
                      >
                        <Stack direction="row" alignItems="center" gap={1.25}>
                          <Box
                            sx={{
                              width: 32,
                              height: 32,
                              borderRadius: '8px',
                              bgcolor: '#eef2ff',
                              color: '#4f46e5',
                              display: 'inline-flex',
                              alignItems: 'center',
                              justifyContent: 'center'
                            }}
                          >
                            <MdTwoWheeler size={18} />
                          </Box>
                          <Box>
                            <Typography sx={{ fontWeight: 700, fontSize: 14, color: '#1e293b' }}>
                              {r.rider_name}
                            </Typography>
                            <Typography sx={{ fontSize: 11.5, color: '#64748b' }}>
                              ID: {r.rider_id}
                            </Typography>
                          </Box>
                        </Stack>
                        <Stack direction="row" gap={1}>
                          <Chip size="small" label={`${r.orders.length} stops`} sx={{ fontWeight: 600 }} />
                          <Chip size="small" label={`${totalKms.toFixed(1)} km`} variant="outlined" />
                        </Stack>
                      </Stack>

                      <Stack direction="row" gap={1.25} sx={{ flexWrap: 'wrap', alignItems: 'center' }}>
                        {r.orders.map((o, idx) => {
                          const stepNum = o.step ?? idx + 1;
                          const color = stepColor(Number(stepNum) - 1);
                          return (
                            <Tooltip
                              key={`${o.orderid}-${idx}`}
                              title={
                                <Box>
                                  <div>Order #{o.orderid}</div>
                                  <div>{o.deliveryaddress || o.deliverysuburb || ''}</div>
                                  <div style={{ marginTop: 4, opacity: 0.8 }}>Click to change rider</div>
                                </Box>
                              }
                            >
                              <Box
                                onClick={() => openChangeRider(r, o)}
                                sx={{
                                  width: 36,
                                  height: 36,
                                  borderRadius: '50%',
                                  bgcolor: color,
                                  color: '#fff',
                                  display: 'inline-flex',
                                  alignItems: 'center',
                                  justifyContent: 'center',
                                  fontWeight: 800,
                                  fontSize: 14,
                                  cursor: 'pointer',
                                  boxShadow:
                                    '0 0 0 2px rgba(255,255,255,0.6), 0 1px 3px rgba(15,23,42,0.15)',
                                  transition: 'transform 0.15s',
                                  '&:hover': { transform: 'scale(1.08)' }
                                }}
                              >
                                {stepNum}
                              </Box>
                            </Tooltip>
                          );
                        })}
                      </Stack>
                    </Card>
                  );
                })}

                <Box sx={{ display: 'flex', justifyContent: 'center', pt: 1.5, pb: 2 }}>
                  <Button
                    variant="contained"
                    color="primary"
                    size="large"
                    startIcon={<MdSwapHoriz />}
                    onClick={handleReconcile}
                    disabled={reconcileLoading || dirtyRiderIds.size === 0}
                    fullWidth={isMobile}
                    sx={{ minWidth: { xs: 0, sm: 220 }, borderRadius: '10px', textTransform: 'none', fontWeight: 700 }}
                  >
                    {reconcileLoading
                      ? 'Reconciling...'
                      : dirtyRiderIds.size === 0
                        ? 'Reconcile'
                        : `Reconcile (${dirtyRiderIds.size})`}
                  </Button>
                </Box>
              </Stack>
            )}
          </Box>
        )}
      </Box>

      <Box sx={{ px: 2, py: 1.25, borderTop: '1px solid #eef2f6' }}>
        <Stack
          direction={{ xs: 'column-reverse', sm: 'row' }}
          gap={{ xs: 1, sm: 2 }}
          alignItems={{ xs: 'stretch', sm: 'center' }}
          justifyContent="end"
        >
          <Button
            variant="contained"
            color="secondary"
            startIcon={<ArrowBackIcon />}
            fullWidth={isMobile}
            onClick={() => navigate(-1)}
          >
            Back
          </Button>
          <Button variant="contained" fullWidth={isMobile} onClick={handleFinalCreateDelivery}>
            Assign Orders
          </Button>
        </Stack>
      </Box>

      <Dialog
        open={changeDialogOpen}
        onClose={() => setChangeDialogOpen(false)}
        maxWidth="xs"
        fullWidth
        fullScreen={isMobile}
        PaperProps={{ sx: { borderRadius: { xs: 0, sm: 3 } } }}
      >
        <DialogTitle sx={{ fontWeight: 700 }}>Change Rider</DialogTitle>
        <DialogContent>
          <Typography sx={{ mb: 2, fontSize: 13, color: 'text.secondary' }}>
            Move order #{selectedOrder?.orderid} (step {selectedOrder?.step ?? '—'}) to:
          </Typography>
          <Autocomplete
            options={ridersList || []}
            getOptionLabel={(o) =>
              o?.label || `${o?.firstname || ''} ${o?.lastname || ''}`.trim() || ''
            }
            value={selectedNewRider}
            onChange={(e, val) => setSelectedNewRider(val)}
            renderInput={(params) => <TextField {...params} label="New rider" placeholder="Pick a rider" />}
          />
        </DialogContent>
        <DialogActions sx={{ px: 3, pb: 2, flexDirection: { xs: 'column-reverse', sm: 'row' }, gap: { xs: 1, sm: 0 } }}>
          <Button fullWidth={isMobile} onClick={() => setChangeDialogOpen(false)}>
            Cancel
          </Button>
          <Button variant="contained" fullWidth={isMobile} disabled={!selectedNewRider} onClick={confirmChangeRider}>
            Change Rider
          </Button>
        </DialogActions>
      </Dialog>
    </Box>
  );
};

// Mirrors the orders.js deliveryamt recalc — applied at render-time so the
// Assign payload always reflects the current cache without a useEffect.
function computeDeliveryAmounts(list) {
  return list.map((item) => {
    const cumulativeKms = Number(item.cumulativekms || 0);
    const minKm = Number(item.minkm || 0);
    const basePrice = Number(item.baseprice || 0);
    const pricePerKm = Number(item.priceperkm || 0);
    if (cumulativeKms <= minKm) return { ...item, deliveryamt: basePrice };
    return { ...item, deliveryamt: (cumulativeKms - minKm) * pricePerKm + basePrice };
  });
}

export default Preview;
