import React, { useState } from 'react';
import { toast } from 'sonner';
import { MapContainer, TileLayer, Marker, Polyline, Popup, useMap } from 'react-leaflet';
import { useTesla, startTeslaConnect, disconnectTesla, sendToTeslaNav, wakeTesla, loginOwner, type TeslaActiveRoute } from '../lib/tesla';
import { isMockEnabled, getMockScenario, setMockScenario, resetMockDrive } from '../lib/teslaMock';
import { detectInCar } from '../lib/incar';
import L from 'leaflet';
import {
  Send, Sparkles, Download, Share2, ChevronDown, ChevronUp, X,
  Plus, ArrowLeftRight, Settings2, AlertTriangle, Gauge, Trash2,
  Zap, Bed, Flag, Home as HomeIcon, Coffee, Utensils, Camera,
  ShoppingBag, Footprints, Wifi, MapPin, Route, Clock, TreePine, Euro,
  CalendarDays, Ship, Users, ExternalLink, Crosshair, ArrowUp, ArrowDown,
  Car, Battery, Navigation,
} from 'lucide-react';

// Fix Leaflet default icons (we still need pins for non-active stops)
delete (L.Icon.Default.prototype as any)._getIconUrl;
L.Icon.Default.mergeOptions({
  iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon-2x.png',
  iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png',
  shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png',
});

// ─── Types ───────────────────────────────────────────────────────────────────
type StopType = 'supercharger' | 'destination-charger' | 'hotel' | 'attraction' | 'restaurant' | 'cafe' | 'viewpoint' | 'custom' | 'origin' | 'destination' | 'tunnel' | 'ferry' | 'crossing';

interface NearbyPlace {
  category: 'food' | 'do' | 'see' | 'shop' | 'rest';
  icon: string;
  name: string;
  detail: string;
}

interface ChargerOption {
  id: string;
  name: string;
  network?: string;
  stalls: number;
  kw: number;
  pricePerKwh: number;
  detourMin: number;
  isCurrent?: boolean;
  badge?: string | null;
}

interface CrossingOption {
  id: string;
  operator: string;             // e.g. "Eurotunnel Le Shuttle", "DFDS", "P&O Ferries", "Brittany Ferries"
  mode: 'tunnel' | 'ferry';
  fromPort: string;             // e.g. "Folkestone, UK"
  toPort: string;               // e.g. "Coquelles (Calais), FR"
  durationMin: number;
  priceEur: number;             // ballpark single-vehicle one-way price
  frequency?: string;           // e.g. "every 30 min, 24/7"
  pros?: string[];
  cons?: string[];
  badge?: 'Fastest' | 'Cheapest' | 'Most scenic' | 'Overnight' | 'Frequent' | null;
  detourMin?: number;           // vs the chosen crossing (in road-driving terms)
  detourKm?: number;
  isCurrent?: boolean;
  bookingUrl?: string;
}

interface RouteVariant {
  id: 'fast' | 'scenic' | 'cheap';
  label: string;
  tone: 'primary' | 'green' | 'blue';
  distanceKm: number;
  driveHours: number;
  chargeHours: number;
  costEur: number;
  highlight?: 'drive' | 'cost' | 'pretty';
  pros: string[];
}

interface AlternativeStop {
  id: string;
  name: string;
  type: StopType;
  lat: number;
  lng: number;
  description?: string;
  combo?: string | null;
  amenities?: string[];
  cuisine?: string | null;
  priceLevel?: number;
  chargeMinutes?: number;
  durationMin?: number;
  deltaKm?: number;
  deltaMin?: number;
  reason?: string;
}

interface Stop {
  id: string;
  name: string;
  type: StopType;
  lat: number;
  lng: number;
  day: number;
  order: number;
  estArrivalBattery?: number;
  chargeMinutes?: number;
  durationMin?: number;
  combo?: string | null;
  description?: string;
  amenities?: string[];
  cuisine?: string | null;
  priceLevel?: number;
  notes?: string;
  alternatives?: AlternativeStop[];
  nearby?: NearbyPlace[];
  chargerOptions?: ChargerOption[];
  crossingOptions?: CrossingOption[];
}

interface TravelDates {
  outbound: string | null;   // ISO yyyy-mm-dd
  return: string | null;     // ISO yyyy-mm-dd (null if one-way)
  travellers?: number;
}

interface Itinerary {
  days: { day: number; title?: string; stops: Stop[] }[];
  summary: {
    totalDistanceKm: number;
    estDriveHours: number;
    estChargeHours: number;
    superchargers: number;
    hotels: number;
    highlights?: string[];
  };
  needsTravelDates?: boolean;
}

interface Leg {
  geometry: [number, number][];
  distanceKm: number | null;
  durationMin: number | null;
  fromId: string;
  toId: string;
}

const EMPTY_ITINERARY: Itinerary = {
  days: [],
  summary: { totalDistanceKm: 0, estDriveHours: 0, estChargeHours: 0, superchargers: 0, hotels: 0, highlights: [] },
};

const STOP_TYPES: StopType[] = ['supercharger', 'destination-charger', 'hotel', 'attraction', 'restaurant', 'cafe', 'viewpoint', 'custom', 'origin', 'destination', 'tunnel', 'ferry', 'crossing'];

interface VehicleTrim {
  id: string;
  name: string;        // e.g. "Long Range AWD"
  rangeKm: number;
  kw: number;
  sec0to60: number;
  topKmh: number;
  badge?: string;
}
interface VehicleModel {
  id: string;
  name: string;        // e.g. "Model Y"
  description: string;
  trims: VehicleTrim[];
}
interface Vehicle {
  modelId: string;
  trimId: string;
  name: string;        // model name
  trim: string;        // trim name
  rangeKm: number;
  kw: number;
  sec0to60: number;
  topKmh: number;
  badge?: string;
}

const TESLA_MODELS: VehicleModel[] = [
  {
    id: 'model-s', name: 'Model S', description: 'Fastback sedan',
    trims: [
      { id: 'lr', name: 'Long Range', rangeKm: 634, kw: 250, sec0to60: 3.1, topKmh: 240 },
      { id: 'plaid', name: 'Plaid', rangeKm: 600, kw: 250, sec0to60: 1.99, topKmh: 322, badge: 'Performance' },
    ],
  },
  {
    id: 'model-3', name: 'Model 3', description: 'Compact sedan',
    trims: [
      { id: 'std', name: 'Standard Range', rangeKm: 438, kw: 175, sec0to60: 5.6, topKmh: 201 },
      { id: 'lr-rwd', name: 'Long Range RWD', rangeKm: 553, kw: 250, sec0to60: 4.9, topKmh: 201, badge: 'Best range' },
      { id: 'lr-awd', name: 'Long Range AWD', rangeKm: 528, kw: 250, sec0to60: 4.2, topKmh: 201 },
      { id: 'perf', name: 'Performance', rangeKm: 528, kw: 250, sec0to60: 2.9, topKmh: 261, badge: 'Performance' },
    ],
  },
  {
    id: 'model-y', name: 'Model Y', description: 'Crossover · best-seller',
    trims: [
      { id: 'std', name: 'Standard Range', rangeKm: 460, kw: 175, sec0to60: 5.6, topKmh: 217 },
      { id: 'lr-rwd', name: 'Long Range RWD', rangeKm: 531, kw: 250, sec0to60: 5.9, topKmh: 217 },
      { id: 'lr-awd', name: 'Long Range AWD', rangeKm: 514, kw: 250, sec0to60: 4.8, topKmh: 217, badge: 'Most popular' },
      { id: 'perf', name: 'Performance', rangeKm: 488, kw: 250, sec0to60: 3.5, topKmh: 250, badge: 'Performance' },
    ],
  },
  {
    id: 'model-x', name: 'Model X', description: 'Three-row SUV · falcon doors',
    trims: [
      { id: 'lr', name: 'Long Range', rangeKm: 543, kw: 250, sec0to60: 3.8, topKmh: 250 },
      { id: 'plaid', name: 'Plaid', rangeKm: 528, kw: 250, sec0to60: 2.5, topKmh: 262, badge: 'Performance' },
    ],
  },
  {
    id: 'cybertruck', name: 'Cybertruck', description: 'Angular pickup',
    trims: [
      { id: 'rwd', name: 'Long Range RWD', rangeKm: 563, kw: 350, sec0to60: 6.5, topKmh: 180 },
      { id: 'awd', name: 'AWD', rangeKm: 547, kw: 350, sec0to60: 4.1, topKmh: 180 },
      { id: 'beast', name: 'Cyberbeast', rangeKm: 515, kw: 350, sec0to60: 2.6, topKmh: 209, badge: 'Performance' },
    ],
  },
];

const DEFAULT_VEHICLE: Vehicle = (() => {
  const m = TESLA_MODELS.find(x => x.id === 'model-y')!;
  const t = m.trims.find(tr => tr.id === 'lr-awd')!;
  return { modelId: m.id, trimId: t.id, name: m.name, trim: t.name, rangeKm: t.rangeKm, kw: t.kw, sec0to60: t.sec0to60, topKmh: t.topKmh, badge: t.badge };
})();

function abbrevTrim(trim: string): string {
  return trim
    .replace('Long Range', 'LR')
    .replace('Standard Range', 'Std')
    .replace('Performance', 'Perf');
}

const QUICK_PROMPTS = [
  'Plan a 2-day trip from London to Edinburgh in my Model Y',
  'I want to drive from Amsterdam to Munich',
  'Help me plan a scenic route from Paris to the Alps',
  'Best way from Glasgow to London avoiding motorways',
];

// ─── Amenity icon map (centralised) ──────────────────────────────────────────
const AMENITY_ICONS: Record<string, string> = {
  restaurant: '🍽️', cafe: '☕', 'fast-food': '🍔', supermarket: '🛒',
  toilets: '🚻', shopping: '🛍️', wifi: '📶', playground: '🧒',
  'ev-charging': '⚡', 'destination-charging': '🔌', hotel: '🛏️',
  coffee: '☕', viewpoint: '🌄', museum: '🏛️', park: '🌳',
  beach: '🏖️', gym: '🏋️', pool: '🏊',
};

// ─── Stop meta (icon + accent colour per type) ───────────────────────────────
type IconComponent = React.ComponentType<{ className?: string; size?: number | string; style?: React.CSSProperties }>;
function stopMeta(type: StopType): { icon: IconComponent; color: string } {
  switch (type) {
    case 'origin': return { icon: HomeIcon, color: '#9ca3af' };
    case 'destination': return { icon: Flag, color: 'var(--gd-red)' };
    case 'tunnel': return { icon: Route, color: '#a78bfa' };
    case 'ferry':
    case 'crossing': return { icon: Ship, color: '#60a5fa' };
    case 'supercharger':
    case 'destination-charger': return { icon: Zap, color: '#4ade80' };
    case 'hotel': return { icon: Bed, color: '#60a5fa' };
    case 'restaurant':
    case 'cafe': return { icon: Utensils, color: '#fbbf24' };
    case 'attraction':
    case 'viewpoint': return { icon: Camera, color: '#c084fc' };
    default: return { icon: MapPin, color: '#9ca3af' };
  }
}

// ─── Geocoding (Nominatim) ───────────────────────────────────────────────────
const geocodeCache = new Map<string, { lat: number; lng: number }>();

async function geocodeLocation(query: string): Promise<{ lat: number; lng: number } | null> {
  const cacheKey = query.toLowerCase().trim();
  if (geocodeCache.has(cacheKey)) return geocodeCache.get(cacheKey)!;
  try {
    const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=1`;
    const res = await fetch(url, { headers: { 'User-Agent': 'TeslaRoadtripPlanner/1.0 (local)' } });
    const data = await res.json();
    if (data && data.length > 0) {
      const result = { lat: parseFloat(data[0].lat), lng: parseFloat(data[0].lon) };
      geocodeCache.set(cacheKey, result);
      await new Promise(r => setTimeout(r, 1100));
      return result;
    }
  } catch {
    console.warn('[TeslaTrip] Geocoding failed for', query);
  }
  return null;
}

// Reverse geocode lat/lng → human-readable place name (Nominatim).
async function reverseGeocode(lat: number, lng: number): Promise<string | null> {
  try {
    const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=14`;
    const res = await fetch(url, { headers: { 'User-Agent': 'TeslaRoadtripPlanner/1.0 (local)' } });
    const data = await res.json();
    if (!data) return null;
    const a = data.address || {};
    // Build something like "Milton Keynes, MK7 8PJ" — town + postcode if available.
    const town = a.city || a.town || a.village || a.hamlet || a.suburb || a.county || '';
    const postcode = a.postcode || '';
    const country = a.country_code ? a.country_code.toUpperCase() : '';
    const parts = [town, postcode, country].filter(Boolean);
    return parts.join(', ') || data.display_name || null;
  } catch {
    console.warn('[TeslaTrip] Reverse geocoding failed', lat, lng);
    return null;
  }
}

// Browser geolocation wrapped in a promise with a short timeout — Tesla's
// in-car browser sometimes hangs forever waiting for a fix.
async function getBrowserLocation(timeoutMs = 8000): Promise<GeolocationCoordinates | null> {
  if (typeof navigator === 'undefined' || !navigator.geolocation) return null;
  return new Promise<GeolocationCoordinates | null>((resolve) => {
    let settled = false;
    const timer = setTimeout(() => { if (!settled) { settled = true; resolve(null); } }, timeoutMs);
    navigator.geolocation.getCurrentPosition(
      (pos) => { if (!settled) { settled = true; clearTimeout(timer); resolve(pos.coords); } },
      () => { if (!settled) { settled = true; clearTimeout(timer); resolve(null); } },
      { enableHighAccuracy: true, timeout: timeoutMs, maximumAge: 30_000 },
    );
  });
}

// ─── Itinerary normalization (preserves all enrichment fields) ───────────────
async function normalizeAndSanitizeItinerary(raw: any): Promise<Itinerary> {
  if (!raw || !Array.isArray(raw.days)) return EMPTY_ITINERARY;
  const normalizedDays: any[] = [];

  for (const day of raw.days) {
    if (!day) continue;
    let rawStops: any[] = [];
    if (Array.isArray(day.stops)) rawStops = day.stops;
    else if (Array.isArray(day.chargeStops)) rawStops = day.chargeStops;
    else if (Array.isArray(raw.pointsOfInterest)) rawStops = raw.pointsOfInterest;

    const validStops: any[] = [];
    for (const s of rawStops) {
      if (!s) continue;
      const name = s.name || s.location;
      if (typeof name !== 'string') continue;
      let lat = typeof s.lat === 'number' ? s.lat : null;
      let lng = typeof s.lng === 'number' ? s.lng : null;
      if ((lat === null || lng === null) && name) {
        const geo = await geocodeLocation(name);
        if (geo) { lat = geo.lat; lng = geo.lng; }
      }

      const rawAlts = Array.isArray(s.alternatives) ? s.alternatives : [];
      const cleanAlts: AlternativeStop[] = rawAlts
        .map((a: any): AlternativeStop | null => {
          if (!a || typeof a.name !== 'string') return null;
          if (typeof a.lat !== 'number' || typeof a.lng !== 'number') return null;
          return {
            id: a.id || `alt-${Date.now()}-${Math.random()}`,
            name: a.name,
            type: STOP_TYPES.includes(a.type) ? a.type : 'custom',
            lat: a.lat,
            lng: a.lng,
            description: typeof a.description === 'string' ? a.description : undefined,
            combo: a.combo ?? null,
            amenities: Array.isArray(a.amenities) ? a.amenities.filter((x: unknown) => typeof x === 'string') : undefined,
            cuisine: typeof a.cuisine === 'string' ? a.cuisine : null,
            priceLevel: typeof a.priceLevel === 'number' ? a.priceLevel : undefined,
            chargeMinutes: typeof a.chargeMinutes === 'number' ? a.chargeMinutes : undefined,
            durationMin: typeof a.durationMin === 'number' ? a.durationMin : undefined,
            deltaKm: typeof a.deltaKm === 'number' ? a.deltaKm : undefined,
            deltaMin: typeof a.deltaMin === 'number' ? a.deltaMin : undefined,
            reason: typeof a.reason === 'string' ? a.reason : undefined,
          };
        })
        .filter((a: AlternativeStop | null): a is AlternativeStop => a !== null);

      const cleanNearby: NearbyPlace[] = Array.isArray(s.nearby)
        ? s.nearby.filter((n: any) => n && typeof n.name === 'string').map((n: any) => ({
            category: ['food', 'do', 'see', 'shop', 'rest'].includes(n.category) ? n.category : 'food',
            icon: typeof n.icon === 'string' ? n.icon : 'coffee',
            name: n.name,
            detail: typeof n.detail === 'string' ? n.detail : '',
          }))
        : [];

      const cleanChargers: ChargerOption[] = Array.isArray(s.chargerOptions)
        ? s.chargerOptions
            .filter((c: any) => c && typeof c.name === 'string')
            .map((c: any): ChargerOption => ({
              id: c.id || `charger-${Date.now()}-${Math.random()}`,
              name: c.name,
              network: typeof c.network === 'string' ? c.network : undefined,
              stalls: typeof c.stalls === 'number' ? c.stalls : 0,
              kw: typeof c.kw === 'number' ? c.kw : 0,
              pricePerKwh: typeof c.pricePerKwh === 'number' ? c.pricePerKwh : 0,
              detourMin: typeof c.detourMin === 'number' ? c.detourMin : 0,
              isCurrent: c.isCurrent === true,
              badge: typeof c.badge === 'string' ? c.badge : null,
            }))
        : [];

      const cleanCrossings: CrossingOption[] = Array.isArray(s.crossingOptions)
        ? s.crossingOptions
            .filter((c: any) => c && typeof c.operator === 'string')
            .map((c: any): CrossingOption => ({
              id: c.id || `crossing-${Date.now()}-${Math.random()}`,
              operator: c.operator,
              mode: c.mode === 'tunnel' ? 'tunnel' : 'ferry',
              fromPort: typeof c.fromPort === 'string' ? c.fromPort : '',
              toPort: typeof c.toPort === 'string' ? c.toPort : '',
              durationMin: typeof c.durationMin === 'number' ? c.durationMin : 0,
              priceEur: typeof c.priceEur === 'number' ? c.priceEur : 0,
              frequency: typeof c.frequency === 'string' ? c.frequency : undefined,
              pros: Array.isArray(c.pros) ? c.pros.filter((p: unknown) => typeof p === 'string') : undefined,
              cons: Array.isArray(c.cons) ? c.cons.filter((p: unknown) => typeof p === 'string') : undefined,
              badge: typeof c.badge === 'string' ? c.badge as CrossingOption['badge'] : null,
              detourMin: typeof c.detourMin === 'number' ? c.detourMin : undefined,
              detourKm: typeof c.detourKm === 'number' ? c.detourKm : undefined,
              isCurrent: c.isCurrent === true,
              bookingUrl: typeof c.bookingUrl === 'string' ? c.bookingUrl : undefined,
            }))
        : [];

      const shared = {
        estArrivalBattery: s.estArrivalBattery,
        chargeMinutes: s.chargeMinutes,
        durationMin: s.durationMin,
        combo: s.combo ?? null,
        description: typeof s.description === 'string' ? s.description : undefined,
        amenities: Array.isArray(s.amenities) ? s.amenities.filter((a: unknown) => typeof a === 'string') : undefined,
        cuisine: typeof s.cuisine === 'string' ? s.cuisine : null,
        priceLevel: typeof s.priceLevel === 'number' ? s.priceLevel : undefined,
        notes: s.notes,
        alternatives: cleanAlts.length > 0 ? cleanAlts : undefined,
        nearby: cleanNearby.length > 0 ? cleanNearby : undefined,
        chargerOptions: cleanChargers.length > 0 ? cleanChargers : undefined,
        crossingOptions: cleanCrossings.length > 0 ? cleanCrossings : undefined,
      };
      const resolvedType: StopType = STOP_TYPES.includes(s.type) ? s.type : 'custom';

      validStops.push({
        id: s.id || `stop-${Date.now()}-${Math.random()}`,
        name, type: resolvedType,
        lat: lat ?? null, lng: lng ?? null,
        day: day.day || 1,
        order: s.order || validStops.length + 1,
        ...shared,
      });
    }

    if (validStops.length > 0) {
      normalizedDays.push({
        day: day.day || normalizedDays.length + 1,
        title: typeof day.title === 'string' ? day.title : undefined,
        stops: validStops.sort((a, b) => a.order - b.order),
      });
    }
  }

  const sortedDays = normalizedDays.sort((a, b) => a.day - b.day);
  const allStops = sortedDays.flatMap(d => d.stops);

  return {
    days: sortedDays,
    summary: {
      totalDistanceKm: raw.summary?.totalDistanceKm ?? 0,
      estDriveHours: raw.summary?.estDriveHours ?? 0,
      estChargeHours: raw.summary?.estChargeHours ?? 0,
      superchargers: allStops.filter(s => s.type === 'supercharger' || s.type === 'destination-charger').length,
      hotels: allStops.filter(s => s.type === 'hotel').length,
      highlights: Array.isArray(raw.summary?.highlights)
        ? raw.summary.highlights.filter((h: unknown) => typeof h === 'string')
        : [],
    },
    needsTravelDates: raw.needsTravelDates === true,
  };
}

// Fast synchronous normalizer used for partial stream events — skips geocoding
// (Grok almost always provides lat/lng inline). Stops missing coords are dropped.
function normalizePartialItinerary(raw: any): Itinerary {
  if (!raw || !Array.isArray(raw.days)) return EMPTY_ITINERARY;
  const normalizedDays: Itinerary['days'] = [];
  for (const day of raw.days) {
    if (!day) continue;
    const rawStops: any[] = Array.isArray(day.stops) ? day.stops : [];
    const validStops: Stop[] = [];
    for (const s of rawStops) {
      if (!s || typeof s.name !== 'string') continue;
      if (typeof s.lat !== 'number' || typeof s.lng !== 'number') continue;
      const resolvedType: StopType = STOP_TYPES.includes(s.type) ? s.type : 'custom';
      validStops.push({
        id: s.id || `stop-${Date.now()}-${Math.random()}`,
        name: s.name,
        type: resolvedType,
        lat: s.lat,
        lng: s.lng,
        day: day.day || 1,
        order: s.order || validStops.length + 1,
        estArrivalBattery: typeof s.estArrivalBattery === 'number' ? s.estArrivalBattery : undefined,
        chargeMinutes: typeof s.chargeMinutes === 'number' ? s.chargeMinutes : undefined,
        durationMin: typeof s.durationMin === 'number' ? s.durationMin : undefined,
        combo: s.combo ?? null,
        description: typeof s.description === 'string' ? s.description : undefined,
        amenities: Array.isArray(s.amenities) ? s.amenities.filter((a: unknown) => typeof a === 'string') : undefined,
        cuisine: typeof s.cuisine === 'string' ? s.cuisine : null,
        priceLevel: typeof s.priceLevel === 'number' ? s.priceLevel : undefined,
        notes: typeof s.notes === 'string' ? s.notes : undefined,
        alternatives: undefined, // skip during partial — final event populates
        nearby: Array.isArray(s.nearby) ? s.nearby.filter((n: any) => n && typeof n.name === 'string') : undefined,
        chargerOptions: Array.isArray(s.chargerOptions) ? s.chargerOptions : undefined,
        crossingOptions: Array.isArray(s.crossingOptions) ? s.crossingOptions : undefined,
      });
    }
    if (validStops.length > 0) {
      normalizedDays.push({
        day: day.day || normalizedDays.length + 1,
        title: typeof day.title === 'string' ? day.title : undefined,
        stops: validStops.sort((a, b) => a.order - b.order),
      });
    }
  }
  const sortedDays = normalizedDays.sort((a, b) => a.day - b.day);
  const allStops = sortedDays.flatMap(d => d.stops);
  return {
    days: sortedDays,
    summary: {
      totalDistanceKm: raw.summary?.totalDistanceKm ?? 0,
      estDriveHours: raw.summary?.estDriveHours ?? 0,
      estChargeHours: raw.summary?.estChargeHours ?? 0,
      superchargers: allStops.filter(s => s.type === 'supercharger' || s.type === 'destination-charger').length,
      hotels: allStops.filter(s => s.type === 'hotel').length,
      highlights: Array.isArray(raw.summary?.highlights)
        ? raw.summary.highlights.filter((h: unknown) => typeof h === 'string')
        : [],
    },
    needsTravelDates: raw.needsTravelDates === true,
  };
}

function normalizeVariants(raw: any): RouteVariant[] {
  if (!Array.isArray(raw)) return [];
  return raw
    .filter((v: any) => v && typeof v.id === 'string')
    .map((v: any): RouteVariant => ({
      id: v.id === 'scenic' || v.id === 'cheap' ? v.id : 'fast',
      label: typeof v.label === 'string' ? v.label : v.id,
      tone: v.tone === 'green' || v.tone === 'blue' ? v.tone : 'primary',
      distanceKm: typeof v.distanceKm === 'number' ? v.distanceKm : 0,
      driveHours: typeof v.driveHours === 'number' ? v.driveHours : 0,
      chargeHours: typeof v.chargeHours === 'number' ? v.chargeHours : 0,
      costEur: typeof v.costEur === 'number' ? v.costEur : 0,
      highlight: ['drive', 'cost', 'pretty'].includes(v.highlight) ? v.highlight : undefined,
      pros: Array.isArray(v.pros) ? v.pros.filter((p: unknown) => typeof p === 'string') : [],
    }));
}

// ─── Routing helpers ─────────────────────────────────────────────────────────
function haversineKm(a: { lat: number; lng: number }, b: { lat: number; lng: number }): number {
  const R = 6371;
  const toRad = (x: number) => (x * Math.PI) / 180;
  const dLat = toRad(b.lat - a.lat);
  const dLng = toRad(b.lng - a.lng);
  const h = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(a.lat)) * Math.cos(toRad(b.lat)) * Math.sin(dLng / 2) ** 2;
  return 2 * R * Math.asin(Math.sqrt(h));
}

async function getRoadLeg(from: Stop, to: Stop): Promise<Leg> {
  try {
    const url = `https://router.project-osrm.org/route/v1/driving/${from.lng},${from.lat};${to.lng},${to.lat}?overview=full&geometries=geojson`;
    const res = await fetch(url);
    const data = await res.json();
    const route = data.routes?.[0];
    if (route) {
      return {
        geometry: route.geometry.coordinates.map((c: number[]) => [c[1], c[0]]),
        distanceKm: route.distance / 1000,
        durationMin: route.duration / 60,
        fromId: from.id, toId: to.id,
      };
    }
  } catch {
    console.warn('[TeslaTrip] OSRM routing failed, falling back to straight line');
  }
  const dist = haversineKm(from, to);
  return {
    geometry: [[from.lat, from.lng], [to.lat, to.lng]],
    distanceKm: dist,
    durationMin: (dist / 80) * 60,
    fromId: from.id, toId: to.id,
  };
}

function formatDuration(min: number | null): string {
  if (min == null || !Number.isFinite(min)) return '–';
  const total = Math.round(min);
  if (total < 60) return `${total}m`;
  const h = Math.floor(total / 60);
  const m = total % 60;
  return m > 0 ? `${h}h ${m}m` : `${h}h`;
}

function formatKm(km: number | null): string {
  if (km == null || !Number.isFinite(km)) return '–';
  return `${Math.round(km).toLocaleString()} km`;
}

function formatDelta(value: number | undefined, unit: 'km' | 'min'): string | null {
  if (typeof value !== 'number' || !Number.isFinite(value)) return null;
  const rounded = Math.round(value);
  if (rounded === 0) return `±0 ${unit}`;
  return `${rounded > 0 ? '+' : ''}${rounded} ${unit}`;
}

function formatDateShort(iso: string | null | undefined): string {
  if (!iso) return '';
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return iso;
  return d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short' });
}

function formatDateRange(outbound: string | null | undefined, ret: string | null | undefined): string {
  if (!outbound) return 'Add dates';
  if (!ret) return `${formatDateShort(outbound)} · one-way`;
  return `${formatDateShort(outbound)} → ${formatDateShort(ret)}`;
}

function nightsBetween(outbound: string | null | undefined, ret: string | null | undefined): number | null {
  if (!outbound || !ret) return null;
  const a = new Date(outbound);
  const b = new Date(ret);
  if (Number.isNaN(a.getTime()) || Number.isNaN(b.getTime())) return null;
  const diff = Math.round((b.getTime() - a.getTime()) / 86400000);
  return diff > 0 ? diff : null;
}

// ─── Error Boundary ──────────────────────────────────────────────────────────
class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean }> {
  constructor(props: any) { super(props); this.state = { hasError: false }; }
  static getDerivedStateFromError() { return { hasError: true }; }
  componentDidCatch(error: Error) { console.error('[TeslaTrip] ErrorBoundary caught:', error); }
  render() {
    if (this.state.hasError) {
      return (
        <div className="h-full flex items-center justify-center bg-red-950/30 text-red-400 p-6">
          <div className="text-center">
            <AlertTriangle className="w-8 h-8 mx-auto mb-3" />
            <div>Something went wrong rendering the planner.</div>
          </div>
        </div>
      );
    }
    return this.props.children;
  }
}

// ─── Reusable bits ───────────────────────────────────────────────────────────
function ChipButton({ children, onClick, className = '' }: { children: React.ReactNode; onClick?: () => void; className?: string }) {
  return (
    <button
      onClick={onClick}
      className={`h-[38px] px-3 inline-flex items-center gap-1.5 rounded-[10px] border text-[12.5px] whitespace-nowrap transition ${className}`}
      style={{
        borderColor: 'var(--gd-border)',
        background: 'var(--gd-panel)',
        color: 'var(--gd-text)',
      }}
    >
      {children}
    </button>
  );
}

function LegRow({ leg }: { leg: Leg | undefined }) {
  if (!leg) return null;
  return (
    <div className="flex items-center gap-3 pl-[52px] pr-3 py-2 text-[11px] num" style={{ color: 'var(--gd-text-3)' }}>
      <Route className="w-3 h-3" />
      <span>{formatKm(leg.distanceKm)}</span>
      <span style={{ opacity: 0.4 }}>·</span>
      <span>{formatDuration(leg.durationMin)} drive</span>
    </div>
  );
}

// ─── Skeleton shimmer ────────────────────────────────────────────────────────
function SkeletonRow({ widthPct = 100 }: { widthPct?: number }) {
  return (
    <div
      className="h-2.5 rounded animate-pulse"
      style={{ width: `${widthPct}%`, background: 'linear-gradient(90deg, var(--gd-border) 0%, var(--gd-border-2) 50%, var(--gd-border) 100%)' }}
    />
  );
}

// ─── Variant strip ───────────────────────────────────────────────────────────
const VARIANT_TONE: Record<RouteVariant['tone'], string> = {
  primary: 'var(--gd-red)',
  green: 'var(--gd-green)',
  blue: 'var(--gd-blue)',
};

const VARIANT_ICON: Record<RouteVariant['id'], IconComponent> = {
  fast: Gauge,
  scenic: TreePine,
  cheap: Euro,
};

function VariantStrip({
  variants, selected, onSelect, switching, cachedIds, showCompare, onToggleCompare,
}: {
  variants: RouteVariant[];
  selected: string;
  onSelect: (id: RouteVariant['id']) => void;
  switching: boolean;
  cachedIds: string[];
  showCompare: boolean;
  onToggleCompare: () => void;
}) {
  if (variants.length === 0) return null;
  const compareEligible = cachedIds.length >= 2;
  return (
    <div
      className="px-6 py-3.5 flex items-center gap-3 flex-shrink-0"
      style={{ borderBottom: '1px solid var(--gd-border)', background: 'var(--gd-bg)' }}
    >
      <div className="grid grid-cols-3 gap-3 flex-1">
      {variants.map(v => {
        const isSel = v.id === selected;
        const tone = VARIANT_TONE[v.tone];
        const Icon = VARIANT_ICON[v.id];
        return (
          <button
            key={v.id}
            onClick={() => onSelect(v.id)}
            disabled={switching}
            className="relative text-left p-3.5 rounded-[14px] overflow-hidden transition disabled:opacity-50 disabled:cursor-wait"
            style={{
              background: isSel ? `color-mix(in srgb, ${tone} 6%, transparent)` : 'var(--gd-panel)',
              border: `1px solid ${isSel ? tone : 'var(--gd-border)'}`,
              cursor: switching ? 'wait' : 'pointer',
            }}
          >
            {isSel && (
              <div className="absolute top-0 left-0 right-0 h-0.5" style={{ background: tone }} />
            )}
            <div className="flex items-center gap-2 mb-3">
              <div className="text-[14px] font-semibold" style={{ letterSpacing: '-0.01em' }}>{v.label}</div>
              {isSel && (
                <span
                  className="text-[9px] font-semibold tracking-wider px-1.5 py-0.5 rounded-full"
                  style={{ border: `1px solid ${tone}`, color: tone }}
                >
                  SELECTED
                </span>
              )}
              <div className="flex-1" />
              <Icon size={16} style={{ color: tone }} />
            </div>
            <div className="flex gap-4 mb-3">
              <VStat label="Drive" value={formatDuration(v.driveHours * 60)} highlight={v.highlight === 'drive'} tone={tone} />
              <VStat label="Charge" value={formatDuration(v.chargeHours * 60)} />
              <VStat label="Cost" value={`€${Math.round(v.costEur)}`} highlight={v.highlight === 'cost'} tone={tone} />
              <VStat label="Distance" value={formatKm(v.distanceKm)} />
            </div>
            <div className="flex flex-wrap gap-1">
              {v.pros.map(p => (
                <span
                  key={p}
                  className="text-[10.5px] px-2 py-0.5 rounded-full"
                  style={{ background: 'rgba(255,255,255,0.04)', color: 'var(--gd-text-2)' }}
                >
                  {p}
                </span>
              ))}
            </div>
          </button>
        );
      })}
      </div>
      <button
        onClick={onToggleCompare}
        disabled={!compareEligible}
        title={compareEligible ? 'Show every cached variant on the map' : 'Visit at least two variants to compare them on the map'}
        className="h-[60px] px-3.5 rounded-[14px] flex flex-col justify-center items-center gap-1 transition flex-shrink-0 disabled:opacity-40 disabled:cursor-not-allowed"
        style={{
          background: showCompare ? 'var(--gd-red-soft)' : 'var(--gd-panel)',
          border: `1px solid ${showCompare ? 'var(--gd-red-line)' : 'var(--gd-border)'}`,
          color: showCompare ? 'var(--gd-red)' : 'var(--gd-text-2)',
        }}
      >
        <ArrowLeftRight className="w-3.5 h-3.5" />
        <span className="text-[10.5px] font-medium whitespace-nowrap">
          Compare {cachedIds.length > 0 ? `(${cachedIds.length})` : ''}
        </span>
      </button>
    </div>
  );
}

function VStat({ label, value, highlight, tone }: { label: string; value: string; highlight?: boolean; tone?: string }) {
  return (
    <div className="min-w-0">
      <div className="text-[9.5px] uppercase tracking-wider" style={{ color: 'var(--gd-text-3)' }}>{label}</div>
      <div
        className="text-[15px] font-medium num mt-px whitespace-nowrap"
        style={{ color: highlight ? tone : 'var(--gd-text)' }}
      >
        {value}
      </div>
    </div>
  );
}

// ─── Day header (sticky) ─────────────────────────────────────────────────────
function DayHeader({ dayNumber, title, distanceKm, driveMin, chargeMin, dateLabel }: {
  dayNumber: number;
  title?: string;
  distanceKm: number;
  driveMin: number;
  chargeMin: number;
  dateLabel?: string;
}) {
  return (
    <div
      className="sticky top-0 z-[2] pt-4 pb-2.5"
      style={{ background: 'var(--gd-bg-2)', borderBottom: '1px solid var(--gd-border)' }}
    >
      <div className="flex items-baseline gap-2">
        <div className="text-[11px] font-bold uppercase tracking-[0.08em]" style={{ color: 'var(--gd-red)' }}>
          DAY {dayNumber}
        </div>
        {dateLabel && <div className="text-[11px]" style={{ color: 'var(--gd-text-3)' }}>· {dateLabel}</div>}
      </div>
      {title && <div className="text-[12.5px] mt-0.5" style={{ color: 'var(--gd-text-2)' }}>{title}</div>}
      <div className="text-[11px] num mt-1" style={{ color: 'var(--gd-text-3)' }}>
        {formatKm(distanceKm)} · {formatDuration(driveMin)} drive · {formatDuration(chargeMin)} charging
      </div>
    </div>
  );
}

// ─── Alternative row (used inside expanded stop) ─────────────────────────────
function AlternativeRow({ alt, onSwap }: { alt: AlternativeStop; onSwap: () => void }) {
  const km = formatDelta(alt.deltaKm, 'km');
  const min = formatDelta(alt.deltaMin, 'min');
  const isFaster = typeof alt.deltaMin === 'number' && alt.deltaMin < 0;
  const isLonger = typeof alt.deltaMin === 'number' && alt.deltaMin > 0;
  const deltaColor = isLonger ? 'var(--gd-amber)' : isFaster ? 'var(--gd-green)' : 'var(--gd-text-3)';
  return (
    <button
      onClick={(e) => { e.stopPropagation(); onSwap(); }}
      className="w-full text-left rounded-lg p-2.5 border transition hover:brightness-110"
      style={{ background: 'var(--gd-bg)', borderColor: 'var(--gd-border)' }}
    >
      <div className="flex justify-between items-start gap-2">
        <div className="min-w-0 flex-1">
          <div className="text-[12px] font-medium truncate" style={{ color: 'var(--gd-text)' }}>{alt.name}</div>
          {alt.combo && (
            <div
              className="inline-block mt-0.5 px-1.5 py-px rounded text-[9px] font-semibold uppercase tracking-wider"
              style={{ background: 'var(--gd-red-soft)', color: 'var(--gd-red)' }}
            >
              {alt.combo}
            </div>
          )}
          {alt.reason && (
            <div className="text-[10.5px] mt-0.5 leading-snug" style={{ color: 'var(--gd-text-3)' }}>{alt.reason}</div>
          )}
        </div>
        <div className="flex flex-col items-end text-[10.5px] num flex-shrink-0" style={{ color: deltaColor }}>
          {km && <span>{km}</span>}
          {min && <span>{min}</span>}
        </div>
      </div>
    </button>
  );
}

// ─── Expanded stop body (stats, charger swap, while-here, alts, actions) ─────
const NEARBY_TABS: { id: 'all' | NearbyPlace['category']; label: string }[] = [
  { id: 'all', label: 'All' },
  { id: 'food', label: 'Food' },
  { id: 'do', label: 'Do' },
  { id: 'see', label: 'See' },
];

function NearbyGrid({ items }: { items: NearbyPlace[] }) {
  return (
    <div className="grid grid-cols-2 gap-1.5">
      {items.map((n, i) => (
        <div
          key={i}
          className="p-2 rounded-lg flex items-center gap-2"
          style={{ background: 'var(--gd-bg)', border: '1px solid var(--gd-border)' }}
        >
          <div
            className="w-6 h-6 rounded grid place-items-center flex-shrink-0 text-[13px] leading-none"
            style={{ background: 'var(--gd-panel-2)' }}
          >
            {AMENITY_ICONS[n.icon] || '•'}
          </div>
          <div className="min-w-0 flex-1">
            <div className="text-[11.5px] font-medium truncate">{n.name}</div>
            <div className="text-[10px] truncate" style={{ color: 'var(--gd-text-3)' }}>{n.detail}</div>
          </div>
        </div>
      ))}
    </div>
  );
}

function ChargerSwapBlock({ options, onPick }: { options: ChargerOption[]; onPick: (c: ChargerOption) => void }) {
  const [open, setOpen] = React.useState(false);
  if (options.length === 0) return null;
  const current = options.find(o => o.isCurrent) || options[0];
  const others = options.filter(o => o.id !== current.id);
  return (
    <div>
      <button
        onClick={(e) => { e.stopPropagation(); setOpen(o => !o); }}
        className="w-full p-2.5 rounded-lg flex items-center gap-2.5 text-left transition"
        style={{ background: 'var(--gd-red-soft)', border: '1px solid var(--gd-red-line)' }}
      >
        <div
          className="w-7 h-7 rounded-md grid place-items-center flex-shrink-0"
          style={{ background: 'rgba(74,222,128,0.18)' }}
        >
          <Zap className="w-3.5 h-3.5" style={{ color: 'var(--gd-green)' }} />
        </div>
        <div className="flex-1 min-w-0">
          <div className="text-[12px] font-medium truncate">{current.name}</div>
          <div className="text-[10.5px] num" style={{ color: 'var(--gd-text-3)' }}>
            {current.stalls} stalls · {current.kw} kW · €{current.pricePerKwh.toFixed(2)}/kWh
            {current.network && current.network !== 'Tesla' ? ` · ${current.network}` : ''}
          </div>
        </div>
        <div className="text-[11px] font-medium flex items-center gap-1 flex-shrink-0" style={{ color: 'var(--gd-red)' }}>
          {others.length > 0 && `${others.length} alts`}
          {open ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
        </div>
      </button>
      {open && others.length > 0 && (
        <div className="mt-1.5 p-1.5 rounded-lg space-y-0.5" style={{ background: 'var(--gd-bg)', border: '1px solid var(--gd-border)' }}>
          {others.map(o => (
            <div
              key={o.id}
              onClick={(e) => e.stopPropagation()}
              className="p-2.5 rounded-md flex items-center gap-2.5 transition hover:bg-white/[0.04]"
            >
              <div
                className="w-6 h-6 rounded grid place-items-center flex-shrink-0"
                style={{ background: 'rgba(255,255,255,0.04)' }}
              >
                <Zap className="w-3 h-3" style={{ color: 'var(--gd-text-2)' }} />
              </div>
              <div className="flex-1 min-w-0">
                <div className="text-[12px] font-medium flex items-center gap-1.5">
                  <span className="truncate">{o.name}</span>
                  {o.badge && (
                    <span
                      className="text-[9px] px-1.5 py-px rounded font-semibold whitespace-nowrap"
                      style={{ color: 'var(--gd-amber)', border: '1px solid rgba(251,191,36,0.4)' }}
                    >
                      {o.badge}
                    </span>
                  )}
                </div>
                <div className="text-[10.5px] num" style={{ color: 'var(--gd-text-3)' }}>
                  {o.stalls} stalls · {o.kw} kW · €{o.pricePerKwh.toFixed(2)}/kWh
                  {o.detourMin > 0 ? ` · +${o.detourMin} min` : ''}
                  {o.network && o.network !== 'Tesla' ? ` · ${o.network}` : ''}
                </div>
              </div>
              <button
                onClick={(e) => { e.stopPropagation(); onPick(o); }}
                className="h-6 px-2.5 rounded-md text-[10.5px] flex-shrink-0"
                style={{ background: 'var(--gd-panel-2)', border: '1px solid var(--gd-border)' }}
              >
                Use
              </button>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function StopExpansion({ stop, onSwap, onRemove, onCustomise, onPickCrossing, onSendToNav, canSendToNav }: {
  stop: Stop;
  onSwap: (alt: AlternativeStop) => void;
  onRemove: () => void;
  onCustomise: () => void;
  onPickCrossing?: (c: CrossingOption) => void;
  onSendToNav?: () => void;
  canSendToNav?: boolean;
}) {
  const isCharge = stop.type === 'supercharger' || stop.type === 'destination-charger';
  const isCrossing = stop.type === 'crossing' || stop.type === 'tunnel' || stop.type === 'ferry';
  const arrive = typeof stop.estArrivalBattery === 'number' ? stop.estArrivalBattery : null;
  const charge = typeof stop.chargeMinutes === 'number' && stop.chargeMinutes > 0 ? stop.chargeMinutes : null;
  const leave = arrive != null && charge != null ? Math.min(100, arrive + Math.round(charge * 1.1)) : null;
  const cost = arrive != null && charge != null ? (Math.max(20, 80 - arrive) * 0.35).toFixed(2) : null;
  const amenities = (stop.amenities || []).slice(0, 8);
  const alts = stop.alternatives || [];
  const chargers = stop.chargerOptions || [];
  const crossings = stop.crossingOptions || [];
  const nearby = stop.nearby || [];

  const [nearbyTab, setNearbyTab] = React.useState<'all' | NearbyPlace['category']>('all');
  const filteredNearby = nearbyTab === 'all' ? nearby : nearby.filter(n => n.category === nearbyTab);

  return (
    <div className="mt-3 pt-3" style={{ borderTop: '1px solid var(--gd-border)' }}>
      {stop.description && (
        <div className="text-[12px] leading-[1.55] mb-3" style={{ color: 'var(--gd-text-2)' }}>{stop.description}</div>
      )}

      {isCharge && (arrive != null || charge != null) && (
        <div className="grid grid-cols-4 gap-1.5 mb-3.5">
          <ExpStat label="Charge" value={charge != null ? `${charge}m` : '–'} tone="green" />
          <ExpStat label="Arrive" value={arrive != null ? `${arrive}%` : '–'} tone={arrive != null && arrive < 30 ? 'amber' : 'text'} />
          <ExpStat label="Leave" value={leave != null ? `${leave}%` : '–'} tone="green" />
          <ExpStat label="Cost" value={cost != null ? `€${cost}` : '–'} />
        </div>
      )}

      {isCharge && chargers.length > 0 && (
        <div className="mb-3.5">
          <SectionLabel>Charger</SectionLabel>
          <ChargerSwapBlock
            options={chargers}
            onPick={(c) => toast.success(`Picked ${c.name}`, { description: `${c.kw} kW · €${c.pricePerKwh.toFixed(2)}/kWh` })}
          />
        </div>
      )}

      {isCrossing && crossings.length > 0 && (
        <div className="mb-3.5">
          <SectionLabel>Crossing</SectionLabel>
          <CrossingSwapBlock
            options={crossings}
            onPick={(c) => onPickCrossing?.(c)}
          />
        </div>
      )}

      {nearby.length > 0 && (
        <div className="mb-3">
          <div className="flex items-center justify-between mb-2">
            <SectionLabel>While here</SectionLabel>
            <div className="flex gap-1">
              {NEARBY_TABS.map(t => {
                const isSel = nearbyTab === t.id;
                const exists = t.id === 'all' || nearby.some(n => n.category === t.id);
                if (!exists) return null;
                return (
                  <button
                    key={t.id}
                    onClick={(e) => { e.stopPropagation(); setNearbyTab(t.id); }}
                    className="h-5.5 px-2 rounded-md text-[10.5px] transition"
                    style={{
                      background: isSel ? 'var(--gd-red-soft)' : 'transparent',
                      color: isSel ? 'var(--gd-red)' : 'var(--gd-text-3)',
                      fontWeight: isSel ? 600 : 400,
                    }}
                  >
                    {t.label}
                  </button>
                );
              })}
            </div>
          </div>
          {filteredNearby.length > 0 ? (
            <NearbyGrid items={filteredNearby.slice(0, 6)} />
          ) : (
            <div className="text-[11px] italic px-2 py-2" style={{ color: 'var(--gd-text-3)' }}>
              Nothing in this category nearby.
            </div>
          )}
        </div>
      )}

      {amenities.length > 0 && (
        <div className="mb-3">
          <SectionLabel>Amenities</SectionLabel>
          <div className="flex flex-wrap gap-1.5">
            {amenities.map(a => (
              <div
                key={a}
                className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-[11px]"
                style={{ background: 'var(--gd-bg)', border: '1px solid var(--gd-border)', color: 'var(--gd-text-2)' }}
              >
                <span className="text-[13px] leading-none">{AMENITY_ICONS[a] || '•'}</span>
                <span className="capitalize">{a.replace(/-/g, ' ')}</span>
              </div>
            ))}
          </div>
        </div>
      )}

      {alts.length > 0 && (
        <div className="mb-3">
          <SectionLabel>{alts.length} location alternative{alts.length === 1 ? '' : 's'}</SectionLabel>
          <div className="space-y-1.5">
            {alts.map(alt => (
              <AlternativeRow key={alt.id} alt={alt} onSwap={() => onSwap(alt)} />
            ))}
          </div>
        </div>
      )}

      {stop.notes && (
        <div
          className="text-[11px] italic mt-2 mb-3 px-3 py-2 rounded"
          style={{ background: 'var(--gd-bg)', color: 'var(--gd-text-3)' }}
        >
          {stop.notes}
        </div>
      )}

      <div className="flex gap-1.5 mt-3">
        {canSendToNav && (
          <button
            onClick={(e) => { e.stopPropagation(); onSendToNav?.(); }}
            className="flex-1 h-8 inline-flex items-center justify-center gap-1.5 rounded-lg text-[11.5px] transition"
            style={{ background: 'var(--gd-red)', color: '#fff', border: '1px solid var(--gd-red)' }}
          >
            <Navigation className="w-3 h-3" /> Send to Tesla nav
          </button>
        )}
        <button
          onClick={(e) => { e.stopPropagation(); onCustomise(); }}
          className="flex-1 h-8 inline-flex items-center justify-center gap-1.5 rounded-lg text-[11.5px] border transition"
          style={{ background: 'var(--gd-panel-2)', borderColor: 'var(--gd-border)', color: 'var(--gd-text)' }}
        >
          <Settings2 className="w-3 h-3" /> Customise
        </button>
        <button
          onClick={(e) => { e.stopPropagation(); onRemove(); }}
          className="h-8 px-3 inline-flex items-center gap-1.5 rounded-lg text-[11.5px] transition"
          style={{ background: 'transparent', border: '1px solid rgba(227,25,55,0.3)', color: 'var(--gd-red)' }}
        >
          <Trash2 className="w-3 h-3" /> Remove
        </button>
      </div>
    </div>
  );
}

function SectionLabel({ children }: { children: React.ReactNode }) {
  return (
    <div className="text-[10px] font-semibold uppercase tracking-[0.07em] mb-2" style={{ color: 'var(--gd-text-3)' }}>
      {children}
    </div>
  );
}

function ExpStat({ label, value, tone = 'text' }: { label: string; value: string; tone?: 'green' | 'amber' | 'text' }) {
  const color = tone === 'green' ? 'var(--gd-green)' : tone === 'amber' ? 'var(--gd-amber)' : 'var(--gd-text)';
  return (
    <div className="px-2.5 py-2 rounded-lg" style={{ background: 'var(--gd-bg)' }}>
      <div className="text-[9.5px] uppercase tracking-wider" style={{ color: 'var(--gd-text-3)' }}>{label}</div>
      <div className="text-[14px] font-medium num mt-px" style={{ color }}>{value}</div>
    </div>
  );
}

// ─── Night transition block (between days) ───────────────────────────────────
function NightBlock({ lastStop, onOpenHotelOptions }: { lastStop: Stop; onOpenHotelOptions: () => void }) {
  const isHotel = lastStop.type === 'hotel';
  const label = isHotel ? lastStop.name : 'Sleep in car at services';
  const detail = isHotel
    ? `Overnight · ${lastStop.cuisine || 'destination charging'}`
    : 'Safe overnight at last Supercharger · 24h facilities';
  return (
    <div
      className="my-3.5 p-3.5 rounded-[12px] flex items-center gap-3"
      style={{
        background: 'rgba(96,165,250,0.07)',
        border: '1px dashed rgba(96,165,250,0.4)',
      }}
    >
      <div
        className="w-[38px] h-[38px] rounded-[9px] grid place-items-center flex-shrink-0"
        style={{ background: 'rgba(96,165,250,0.16)' }}
      >
        <Bed className="w-4 h-4" style={{ color: 'var(--gd-blue)' }} />
      </div>
      <div className="flex-1 min-w-0">
        <div className="text-[13px] font-medium truncate">{label}</div>
        <div className="text-[11.5px]" style={{ color: 'var(--gd-text-3)' }}>{detail}</div>
      </div>
      <button
        onClick={onOpenHotelOptions}
        className="h-7 px-2.5 inline-flex items-center gap-1.5 rounded-lg text-[11.5px] whitespace-nowrap"
        style={{ background: 'var(--gd-panel-2)', border: '1px solid var(--gd-border)', color: 'var(--gd-text)' }}
      >
        <ArrowLeftRight className="w-3 h-3" /> Hotel options
      </button>
    </div>
  );
}

// ─── Stop card (icon-led) ────────────────────────────────────────────────────
function StopCard({
  stop, active, hover, dragging, onSelect, onHover, onSwap, onRemove, onCustomise, onPickCrossing,
  onMoveUp, onMoveDown, canMoveUp, canMoveDown, onSendToNav, canSendToNav,
  onDragStart, onDragOver, onDrop, onDragEnd,
}: {
  stop: Stop;
  active: boolean;
  hover: boolean;
  dragging: boolean;
  onSelect: () => void;
  onHover: (h: boolean) => void;
  onSwap: (alt: AlternativeStop) => void;
  onRemove: () => void;
  onCustomise: () => void;
  onPickCrossing?: (c: CrossingOption) => void;
  onMoveUp: () => void;
  onMoveDown: () => void;
  canMoveUp: boolean;
  canMoveDown: boolean;
  onSendToNav?: () => void;
  canSendToNav?: boolean;
  onDragStart: (e: React.DragEvent) => void;
  onDragOver: (e: React.DragEvent) => void;
  onDrop: (e: React.DragEvent) => void;
  onDragEnd: () => void;
}) {
  const meta = stopMeta(stop.type);
  const Icon = meta.icon;
  const isCharge = stop.type === 'supercharger' || stop.type === 'destination-charger';
  const isSleep = stop.type === 'hotel';
  const isCrossingStop = stop.type === 'tunnel' || stop.type === 'ferry' || stop.type === 'crossing';
  const currentCrossing = isCrossingStop ? (stop.crossingOptions?.find(o => o.isCurrent) || stop.crossingOptions?.[0]) : undefined;

  return (
    <div
      onClick={onSelect}
      onMouseEnter={() => onHover(true)}
      onMouseLeave={() => onHover(false)}
      draggable
      onDragStart={onDragStart}
      onDragOver={onDragOver}
      onDrop={onDrop}
      onDragEnd={onDragEnd}
      className="rounded-[12px] p-3.5 mb-2 cursor-pointer transition-all"
      style={{
        background: active ? 'var(--gd-panel-2)' : hover ? 'rgba(255,255,255,0.025)' : 'var(--gd-panel)',
        border: `1px solid ${active ? 'var(--gd-red-line)' : hover ? 'var(--gd-border-2)' : 'var(--gd-border)'}`,
        boxShadow: active ? '0 0 0 1px var(--gd-red-soft), 0 6px 20px rgba(0,0,0,0.25)' : 'none',
        opacity: dragging ? 0.5 : 1,
      }}
    >
      <div className="flex items-start gap-3">
        <div className="flex flex-col items-center gap-1 flex-shrink-0">
          <div
            className="w-10 h-10 rounded-[10px] grid place-items-center"
            style={{ background: `${meta.color}22` }}
          >
            <Icon size={18} className="" />
          </div>
          <div className="flex flex-col gap-0.5">
            <button
              onClick={(e) => { e.stopPropagation(); onMoveUp(); }}
              disabled={!canMoveUp}
              title="Move up"
              className="w-10 h-7 rounded-md grid place-items-center disabled:opacity-25"
              style={{ background: 'var(--gd-panel-2)', border: '1px solid var(--gd-border)' }}
            >
              <ArrowUp className="w-3.5 h-3.5" style={{ color: 'var(--gd-text-2)' }} />
            </button>
            <button
              onClick={(e) => { e.stopPropagation(); onMoveDown(); }}
              disabled={!canMoveDown}
              title="Move down"
              className="w-10 h-7 rounded-md grid place-items-center disabled:opacity-25"
              style={{ background: 'var(--gd-panel-2)', border: '1px solid var(--gd-border)' }}
            >
              <ArrowDown className="w-3.5 h-3.5" style={{ color: 'var(--gd-text-2)' }} />
            </button>
          </div>
        </div>
        <div className="flex-1 min-w-0">
          <div className="flex items-center gap-2">
            <div className="font-medium text-[14px] truncate" style={{ letterSpacing: '-0.005em' }}>{stop.name}</div>
            {isCharge && typeof stop.chargeMinutes === 'number' && stop.chargeMinutes > 0 && (
              <div className="text-[11px] num flex-shrink-0 whitespace-nowrap">
                <span style={{ color: 'var(--gd-green)', fontWeight: 600 }}>{stop.chargeMinutes}m</span>
                {typeof stop.estArrivalBattery === 'number' && (
                  <>
                    <span className="mx-1" style={{ color: 'var(--gd-text-3)' }}>·</span>
                    <span style={{ color: 'var(--gd-text-2)' }}>arrive {stop.estArrivalBattery}%</span>
                  </>
                )}
              </div>
            )}
            {isSleep && (
              <div className="text-[11px] num font-medium flex-shrink-0 whitespace-nowrap" style={{ color: 'var(--gd-blue)' }}>overnight</div>
            )}
            {isCrossingStop && currentCrossing && (
              <div className="text-[11px] num flex-shrink-0 whitespace-nowrap" style={{ color: 'var(--gd-text-3)' }}>
                €{Math.round(currentCrossing.priceEur)} · {formatDuration(currentCrossing.durationMin)}
              </div>
            )}
          </div>
          {stop.combo && (
            <div
              className="inline-block mt-1 px-2 py-0.5 rounded-full text-[10px] font-semibold uppercase tracking-wider"
              style={{ background: 'var(--gd-red-soft)', color: 'var(--gd-red)' }}
            >
              {stop.combo}
            </div>
          )}
          <div className="text-[11.5px] mt-1" style={{ color: 'var(--gd-text-3)' }}>
            {stop.cuisine || (stop.type === 'supercharger' ? 'Supercharger' : stop.type === 'destination-charger' ? 'Destination charger' : stop.type === 'hotel' ? 'Hotel' : stop.type === 'attraction' ? 'Attraction' : stop.type === 'restaurant' ? 'Restaurant' : stop.type === 'cafe' ? 'Cafe' : stop.type === 'viewpoint' ? 'Viewpoint' : stop.type === 'tunnel' ? 'Eurotunnel · drive on/off' : stop.type === 'ferry' ? 'Ferry crossing' : stop.type === 'crossing' ? 'Sea crossing' : 'Stop')}
          </div>

          {!active && stop.description && (
            <div className="text-[12px] mt-2 leading-snug line-clamp-2" style={{ color: 'var(--gd-text-2)' }}>
              {stop.description}
            </div>
          )}

          {active && <StopExpansion stop={stop} onSwap={onSwap} onRemove={onRemove} onCustomise={onCustomise} onPickCrossing={onPickCrossing} onSendToNav={onSendToNav} canSendToNav={canSendToNav} />}
        </div>
      </div>
    </div>
  );
}

// ─── Map controller: fit bounds when stops change ────────────────────────────
function MapAutoFit({ stops }: { stops: Stop[] }) {
  const map = useMap();
  React.useEffect(() => {
    const valid = stops.filter(s => typeof s.lat === 'number' && typeof s.lng === 'number');
    if (valid.length < 2) return;
    const bounds = L.latLngBounds(valid.map(s => [s.lat, s.lng] as [number, number]));
    map.fitBounds(bounds, { padding: [60, 60], maxZoom: 8 });
  }, [stops, map]);
  return null;
}

function MapFlyTo({ stop }: { stop: Stop | null }) {
  const map = useMap();
  React.useEffect(() => {
    if (!stop || typeof stop.lat !== 'number') return;
    map.flyTo([stop.lat, stop.lng], Math.max(map.getZoom(), 7), { duration: 0.6 });
  }, [stop, map]);
  return null;
}

// ─── Pin marker (svg-style coloured dot via Leaflet divIcon) ─────────────────
function makePinIcon(color: string, active: boolean, hover: boolean): L.DivIcon {
  const size = active ? 28 : hover ? 22 : 18;
  const dot = active ? 10 : hover ? 8 : 7;
  return L.divIcon({
    className: 'gd-pin',
    iconSize: [size, size],
    iconAnchor: [size / 2, size / 2],
    html: `
      <div style="width:${size}px;height:${size}px;display:grid;place-items:center;">
        ${active ? `<div style="position:absolute;width:${size}px;height:${size}px;border-radius:50%;background:${color}33;"></div>` : ''}
        <div style="position:relative;width:${dot * 2}px;height:${dot * 2}px;border-radius:50%;background:#0a0a0c;border:2px solid ${color};box-shadow:0 1px 4px rgba(0,0,0,0.5);"></div>
      </div>`,
  });
}

// ─── Top bar ─────────────────────────────────────────────────────────────────
function TopBar({
  origin, destination, onOriginChange, onDestinationChange, onODCommit,
  chatInput, setChatInput, onChatSubmit, chips, onRemoveChip,
  vehicle, onOpenVehiclePanel, grokStatus, onOpenGpx,
  travelDates, onOpenDates, onUseMyLocation,
  teslaStatus, teslaState, onConnectTesla, onDisconnectTesla, inCar,
}: {
  origin: string; destination: string;
  onOriginChange: (v: string) => void;
  onDestinationChange: (v: string) => void;
  onODCommit: () => void;
  chatInput: string; setChatInput: (v: string) => void;
  onChatSubmit: () => void;
  chips: string[]; onRemoveChip: (i: number) => void;
  vehicle: Vehicle; onOpenVehiclePanel: (rect: DOMRect) => void;
  grokStatus: { label?: string };
  onOpenGpx: () => void;
  travelDates: TravelDates;
  onOpenDates: (rect: DOMRect) => void;
  onUseMyLocation: () => void;
  teslaStatus: ReturnType<typeof useTesla>['status'];
  teslaState: ReturnType<typeof useTesla>['state'];
  onConnectTesla: () => void;
  onDisconnectTesla: () => void;
  inCar: boolean;
}) {
  // Tesla connected → we know the car (and where it is), so the vehicle picker
  // and the manual From input are both dead weight. The OD strip collapses to
  // a destination-focused planner (see ConnectedTripStrip).
  const hideVehicleChip = !!teslaStatus?.connected;
  const hideGpxChip = inCar;
  const [locating, setLocating] = React.useState(false);
  const handleLocate = async () => {
    setLocating(true);
    try { await onUseMyLocation(); } finally { setLocating(false); }
  };
  const datesLabel = travelDates.outbound
    ? formatDateRange(travelDates.outbound, travelDates.return)
    : 'Add dates';
  const datesEmpty = !travelDates.outbound;
  return (
    <div
      className="flex items-center gap-4 px-5 flex-shrink-0"
      style={{ height: 62, background: 'var(--gd-bg)', borderBottom: '1px solid var(--gd-border)' }}
    >
      {/* Brand */}
      <div className="flex items-center gap-2.5">
        <div className="w-[30px] h-[30px] rounded-[9px] grid place-items-center" style={{ background: 'var(--gd-red)' }}>
          <Zap className="w-4 h-4 text-white" />
        </div>
        <div className="flex items-center gap-1.5 whitespace-nowrap">
          <div className="font-semibold text-[16px]" style={{ letterSpacing: '-0.02em' }}>Grok Drive</div>
          <span
            className="text-[9.5px] font-semibold tracking-wider px-1.5 py-0.5 rounded"
            style={{ background: 'var(--gd-panel-2)', color: 'var(--gd-text-2)' }}
          >
            BETA
          </span>
        </div>
      </div>

      {/* Origin → Destination — collapsed to a single destination-focused strip
          when Tesla is connected, because the origin is just "where the car is". */}
      {teslaStatus?.connected ? (
        <ConnectedTripStrip
          activeRoute={teslaState?.activeRoute ?? null}
          destination={destination}
          onDestinationChange={onDestinationChange}
          onCommit={onODCommit}
        />
      ) : (
        <div
          className="flex items-center rounded-[10px] overflow-hidden"
          style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)', height: 38, minWidth: 380 }}
        >
          <div className="px-3.5 flex items-center gap-2 h-full flex-1" style={{ borderRight: '1px solid var(--gd-border)' }}>
            <div className="w-1.5 h-1.5 rounded-full" style={{ background: 'var(--gd-text-2)' }} />
            <input
              value={origin}
              onChange={(e) => onOriginChange(e.target.value)}
              onKeyDown={(e) => { if (e.key === 'Enter') { e.currentTarget.blur(); onODCommit(); } }}
              onBlur={onODCommit}
              placeholder="From"
              className="bg-transparent border-none outline-none text-[13px] w-full"
              style={{ color: 'var(--gd-text)' }}
            />
            <button
              onClick={handleLocate}
              disabled={locating}
              title="Use my current location"
              className="grid place-items-center rounded-md flex-shrink-0 disabled:opacity-40"
              style={{
                width: 26, height: 26,
                background: 'var(--gd-panel-2)',
                border: '1px solid var(--gd-border)',
                color: 'var(--gd-text-2)',
              }}
            >
              <Crosshair className={`w-3.5 h-3.5 ${locating ? 'animate-pulse' : ''}`} />
            </button>
          </div>
          <div className="px-3.5 flex items-center gap-2 h-full flex-1">
            <div className="w-1.5 h-1.5 rounded-full" style={{ background: 'var(--gd-red)' }} />
            <input
              value={destination}
              onChange={(e) => onDestinationChange(e.target.value)}
              onKeyDown={(e) => { if (e.key === 'Enter') { e.currentTarget.blur(); onODCommit(); } }}
              onBlur={onODCommit}
              placeholder="To"
              className="bg-transparent border-none outline-none text-[13px] w-full"
              style={{ color: 'var(--gd-text)' }}
            />
          </div>
        </div>
      )}

      <div className="flex-1" />

      {/* Chat composer with chips */}
      <div
        className="flex items-center pl-3 rounded-[10px]"
        style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)', height: 38, minWidth: 360 }}
      >
        <Sparkles className="w-3.5 h-3.5" style={{ color: 'var(--gd-red)' }} />
        {chips.length > 0 && (
          <div className="flex gap-1 ml-2">
            {chips.map((c, i) => (
              <div
                key={i}
                className="text-[11px] pl-2 pr-1 py-0.5 rounded-full inline-flex items-center gap-1 max-w-[140px]"
                style={{ background: 'var(--gd-red-soft)', color: 'var(--gd-red)' }}
              >
                <span className="truncate">{c}</span>
                <button
                  onClick={() => onRemoveChip(i)}
                  className="w-3.5 h-3.5 grid place-items-center rounded-full"
                  style={{ color: 'var(--gd-red)' }}
                >
                  <X className="w-2.5 h-2.5" />
                </button>
              </div>
            ))}
          </div>
        )}
        <input
          value={chatInput}
          onChange={(e) => setChatInput(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && onChatSubmit()}
          placeholder={chips.length ? 'add another...' : 'Refine — "avoid tolls", "lunch in Lyon"'}
          className="flex-1 px-3 bg-transparent border-none outline-none text-[13px] min-w-[60px]"
          style={{ color: 'var(--gd-text)' }}
        />
        <button
          onClick={onChatSubmit}
          className="w-[30px] h-[30px] m-1 rounded-[7px] grid place-items-center"
          style={{ background: 'var(--gd-red)' }}
        >
          <Send className="w-3 h-3 text-white" />
        </button>
      </div>

      {/* Vehicle chip — opens trim panel. Hidden when in-car with Tesla connected (we already know the car). */}
      {!hideVehicleChip && (
      <button
        onClick={(e) => onOpenVehiclePanel(e.currentTarget.getBoundingClientRect())}
        className="h-[38px] px-3 inline-flex items-center gap-2 rounded-[10px] cursor-pointer"
        style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)' }}
      >
        <Gauge className="w-3.5 h-3.5" style={{ color: 'var(--gd-text-2)' }} />
        <div className="flex flex-col items-start leading-[1.15]">
          <div className="text-[12px] font-medium" style={{ letterSpacing: '-0.005em' }}>
            {vehicle.name}{' '}
            <span style={{ color: 'var(--gd-text-3)', fontWeight: 400 }}>{abbrevTrim(vehicle.trim)}</span>
          </div>
          <div className="text-[10px] num" style={{ color: 'var(--gd-text-3)' }}>{vehicle.rangeKm} km</div>
        </div>
        <ChevronDown className="w-3 h-3" style={{ color: 'var(--gd-text-3)' }} />
      </button>
      )}

      <button
        onClick={(e) => onOpenDates(e.currentTarget.getBoundingClientRect())}
        className="h-[38px] px-3 inline-flex items-center gap-2 rounded-[10px] cursor-pointer"
        style={{
          background: datesEmpty ? 'var(--gd-red-soft)' : 'var(--gd-panel)',
          border: `1px solid ${datesEmpty ? 'var(--gd-red-line)' : 'var(--gd-border)'}`,
          color: datesEmpty ? 'var(--gd-red)' : 'var(--gd-text)',
        }}
      >
        <CalendarDays className="w-3.5 h-3.5" />
        <div className="text-[12px] font-medium">{datesLabel}</div>
        <ChevronDown className="w-3 h-3" style={{ color: datesEmpty ? 'var(--gd-red)' : 'var(--gd-text-3)' }} />
      </button>

      {teslaStatus?.available && (
        teslaStatus.connected ? (
          <button
            onClick={onDisconnectTesla}
            title="Disconnect Tesla"
            className="h-[38px] px-3 inline-flex items-center gap-2 rounded-[10px] cursor-pointer"
            style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)' }}
          >
            <Car className="w-3.5 h-3.5" style={{ color: 'var(--gd-red)' }} />
            <div className="flex flex-col items-start leading-[1.15]">
              <div className="text-[11.5px] font-medium">
                {teslaState?.battery != null ? `${teslaState.battery}%` : 'Connected'}
              </div>
              <div className="text-[9.5px] num" style={{ color: 'var(--gd-text-3)' }}>
                {teslaState?.rangeKm != null ? `${teslaState.rangeKm} km` : (teslaState?.asleep ? 'asleep' : '—')}
              </div>
            </div>
          </button>
        ) : (
          <button
            onClick={onConnectTesla}
            className="h-[38px] px-3 inline-flex items-center gap-2 rounded-[10px] cursor-pointer"
            style={{ background: 'var(--gd-red-soft)', border: '1px solid var(--gd-red-line)', color: 'var(--gd-red)' }}
          >
            <Car className="w-3.5 h-3.5" />
            <div className="text-[12px] font-medium">Connect Tesla</div>
          </button>
        )
      )}

      {/* Live charging widget — only when plugged in. */}
      {teslaState?.chargingState === 'Charging' && (
        <ChargingWidget
          kw={teslaState.chargerPowerKw}
          minutesToTarget={teslaState.timeToFullCharge != null ? Math.round(teslaState.timeToFullCharge * 60) : null}
          battery={teslaState.battery}
        />
      )}

      {!hideGpxChip && (
        <ChipButton onClick={() => onOpenGpx()}>
          <Download className="w-3.5 h-3.5" style={{ color: 'var(--gd-text-2)' }} />
          Export
        </ChipButton>
      )}
      {!inCar && (
        <ChipButton onClick={() => toast('Shareable link copied')}>
          <Share2 className="w-3.5 h-3.5" style={{ color: 'var(--gd-text-2)' }} />
          Share
        </ChipButton>
      )}
      {grokStatus.label && (
        <div className="text-[10px] px-2 py-1 rounded" style={{ background: 'var(--gd-panel-2)', color: 'var(--gd-text-3)' }}>
          {grokStatus.label}
        </div>
      )}
      <MockTeslaIndicator />
    </div>
  );
}

// Compact destination strip used when Tesla is connected. Origin is implicit
// (the car's GPS), so we only need to know where the user is going.
function ConnectedTripStrip({
  activeRoute, destination, onDestinationChange, onCommit,
}: {
  activeRoute: TeslaActiveRoute | null;
  destination: string;
  onDestinationChange: (v: string) => void;
  onCommit: () => void;
}) {
  // If Tesla nav already has a destination AND it doesn't match what the
  // planner's currently using, surface it as a one-tap apply suggestion.
  const navDest = activeRoute?.destination;
  const navMatches = !!navDest && destination.trim().toLowerCase() === navDest.toLowerCase();
  const showSuggestion = !!navDest && !navMatches;

  return (
    <div
      className="flex items-center rounded-[10px] overflow-hidden"
      style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)', height: 38, minWidth: 380 }}
    >
      <div className="px-3 flex items-center gap-2 h-full" style={{ borderRight: '1px solid var(--gd-border)' }}>
        <Car className="w-3.5 h-3.5" style={{ color: 'var(--gd-red)' }} />
        <div className="text-[11px] num" style={{ color: 'var(--gd-text-3)' }}>From car</div>
      </div>
      <div className="px-3 flex items-center gap-2 h-full flex-1">
        <div className="w-1.5 h-1.5 rounded-full" style={{ background: 'var(--gd-red)' }} />
        <input
          value={destination}
          onChange={(e) => onDestinationChange(e.target.value)}
          onKeyDown={(e) => { if (e.key === 'Enter') { e.currentTarget.blur(); onCommit(); } }}
          onBlur={onCommit}
          placeholder={navDest ? `Going to ${navDest} (or type)` : 'Where to?'}
          className="bg-transparent border-none outline-none text-[13px] w-full"
          style={{ color: 'var(--gd-text)' }}
        />
        {showSuggestion && (
          <button
            onClick={() => { onDestinationChange(navDest); onCommit(); }}
            title="Use the destination set in your Tesla nav"
            className="h-7 px-2 text-[10.5px] rounded-md inline-flex items-center gap-1 whitespace-nowrap"
            style={{ background: 'var(--gd-red-soft)', color: 'var(--gd-red)', border: '1px solid var(--gd-red-line)' }}
          >
            <Navigation className="w-2.5 h-2.5" /> Use Tesla nav
          </button>
        )}
      </div>
    </div>
  );
}

function ChargingWidget({ kw, minutesToTarget, battery }: {
  kw: number | null;
  minutesToTarget: number | null;
  battery: number | null;
}) {
  return (
    <div
      className="h-[38px] px-3 inline-flex items-center gap-2 rounded-[10px]"
      style={{
        background: 'rgba(74,222,128,0.10)',
        border: '1px solid rgba(74,222,128,0.4)',
        color: 'var(--gd-green)',
      }}
      title="Live charging from your Tesla"
    >
      <Zap className="w-3.5 h-3.5 animate-pulse" />
      <div className="flex flex-col items-start leading-[1.15]">
        <div className="text-[11.5px] font-semibold num">
          {kw != null ? `${Math.round(kw)} kW` : 'Charging'}
        </div>
        <div className="text-[9.5px] num" style={{ color: 'var(--gd-text-3)' }}>
          {battery != null && minutesToTarget != null
            ? `${battery}% · ${minutesToTarget}m left`
            : minutesToTarget != null
              ? `${minutesToTarget}m left`
              : battery != null
                ? `${battery}%`
                : '—'}
        </div>
      </div>
    </div>
  );
}

function MockTeslaIndicator() {
  const [scenario, setScenarioState] = React.useState(getMockScenario());
  if (!scenario) return null;
  const cycle = () => {
    const next = scenario === 'parked' ? 'driving'
      : scenario === 'driving' ? 'charging'
      : scenario === 'charging' ? 'asleep'
      : scenario === 'asleep' ? 'parked'
      : 'parked';
    setMockScenario(next);
    resetMockDrive();
    setScenarioState(next);
    // Hard reload so all hooks pick up the new scenario cleanly.
    window.location.reload();
  };
  const disable = () => {
    setMockScenario(null);
    window.location.reload();
  };
  return (
    <div
      className="text-[10px] px-2 py-1 rounded inline-flex items-center gap-1.5"
      style={{ background: 'rgba(251,191,36,0.15)', color: 'var(--gd-amber)', border: '1px solid rgba(251,191,36,0.45)' }}
    >
      <span className="font-semibold tracking-wider uppercase">MOCK</span>
      <button onClick={cycle} className="underline" title="Cycle scenario">{scenario}</button>
      <button onClick={disable} className="opacity-70 hover:opacity-100" title="Disable mock">×</button>
    </div>
  );
}

// ─── Main planner ────────────────────────────────────────────────────────────
export default function TeslaTripPlanner() {
  const tesla = useTesla();
  const inCar = React.useMemo(() => detectInCar().isInCar, []);
  const teslaConnected = !!tesla.status?.connected;
  const teslaInCar = inCar && teslaConnected;

  // When Tesla is connected, auto-detect the vehicle model + trim from the
  // Fleet API response so the planner uses the real range / kW instead of
  // the default.
  React.useEffect(() => {
    if (!tesla.state) return;
    const carType = tesla.state.carType || tesla.status?.carType;
    const trimBadging = tesla.state.trimBadging || tesla.status?.trimBadging;
    if (!carType) return;
    const modelId = carType === 'modely' ? 'model-y'
      : carType === 'model3' ? 'model-3'
      : carType === 'models' ? 'model-s'
      : carType === 'modelx' ? 'model-x'
      : carType === 'cybertruck' ? 'cybertruck'
      : null;
    if (!modelId) return;
    const model = TESLA_MODELS.find(m => m.id === modelId);
    if (!model) return;
    // Match trim badging (e.g. "lrawd" → Long Range AWD)
    const trim = model.trims.find(t => {
      const n = (trimBadging || '').toLowerCase();
      const id = t.id.toLowerCase().replace(/-/g, '');
      return n === id || n.includes(id) || id.includes(n);
    }) || model.trims[0];
    if (!trim) return;
    setVehicle(prev => prev.modelId === model.id && prev.trimId === trim.id ? prev : {
      modelId: model.id, trimId: trim.id, name: model.name, trim: trim.name,
      rangeKm: trim.rangeKm, kw: trim.kw, sec0to60: trim.sec0to60, topKmh: trim.topKmh, badge: trim.badge,
    });
  }, [tesla.state?.carType, tesla.state?.trimBadging, tesla.status?.carType, tesla.status?.trimBadging]);

  // When in-car AND Tesla connected: auto-fill the origin from the car's GPS
  // once on mount, so the user doesn't have to type their starting point.
  const autoOriginRef = React.useRef(false);
  React.useEffect(() => {
    if (!teslaInCar || autoOriginRef.current) return;
    if (!tesla.state?.lat || !tesla.state?.lng) return;
    autoOriginRef.current = true;
    reverseGeocode(tesla.state.lat, tesla.state.lng).then(name => {
      if (name) setOrigin(name);
    });
  }, [teslaInCar, tesla.state?.lat, tesla.state?.lng]);

  // When Tesla is connected and has an active in-car nav destination, and the
  // user hasn't typed their own destination yet, adopt the Tesla nav target.
  const autoDestRef = React.useRef(false);
  React.useEffect(() => {
    if (!teslaConnected || autoDestRef.current) return;
    const nav = tesla.state?.activeRoute?.destination;
    if (!nav) return;
    if (destination.trim()) return;       // user already chose one
    autoDestRef.current = true;
    setDestination(nav);
  }, [teslaConnected, tesla.state?.activeRoute?.destination]);

  // Surface a toast once after the OAuth round trip lands us back at /?tesla_connected=1
  React.useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    if (params.get('tesla_connected') === '1') {
      toast.success('Tesla connected', { description: 'Live battery + GPS + send-to-nav enabled.' });
      params.delete('tesla_connected');
      const q = params.toString();
      window.history.replaceState({}, '', window.location.pathname + (q ? `?${q}` : ''));
    } else if (params.get('tesla_error')) {
      toast.error('Tesla connect failed', { description: params.get('tesla_error') || undefined });
      params.delete('tesla_error');
      const q = params.toString();
      window.history.replaceState({}, '', window.location.pathname + (q ? `?${q}` : ''));
    }
  }, []);

  const [messages, setMessages] = useState<{ id: number; role: 'user' | 'assistant'; content: string }[]>([
    { id: 1, role: 'assistant', content: "Hello! I'm Grok Drive. Tell me where you want to go." },
  ]);
  const [chatInput, setChatInput] = useState('');
  const [chips, setChips] = useState<string[]>([]);
  const [thinking, setThinking] = useState(false);
  const [thinkingMessage, setThinkingMessage] = useState<string>('');
  const [itinerary, setItinerary] = useState<Itinerary>(EMPTY_ITINERARY);
  const [vehicle, setVehicle] = useState<Vehicle>(DEFAULT_VEHICLE);
  const [vehiclePanelOpen, setVehiclePanelOpen] = useState(false);
  const [vehicleAnchor, setVehicleAnchor] = useState<DOMRect | null>(null);
  const [grokStatus, setGrokStatus] = useState<{ label?: string }>({ label: 'Local Heavy' });
  const [legs, setLegs] = useState<Leg[]>([]);
  const [activeStopId, setActiveStopId] = useState<string | null>(null);
  const [hoverStopId, setHoverStopId] = useState<string | null>(null);
  const [origin, setOrigin] = useState('');
  const [destination, setDestination] = useState('');
  const [travelDates, setTravelDates] = useState<TravelDates>({ outbound: null, return: null, travellers: 2 });
  const [datePickerOpen, setDatePickerOpen] = useState(false);
  const [dateAnchor, setDateAnchor] = useState<DOMRect | null>(null);
  const [ownerLoginOpen, setOwnerLoginOpen] = useState(false);
  // After successful owner login, fire this pending action.
  const ownerLoginThenRef = React.useRef<null | (() => void)>(null);
  const lastODSent = React.useRef<{ from: string; to: string } | null>(null);
  const [variants, setVariants] = useState<RouteVariant[]>([]);
  const [selectedVariant, setSelectedVariant] = useState<RouteVariant['id']>('fast');
  const [variantSwitching, setVariantSwitching] = useState(false);
  const [variantCache, setVariantCache] = useState<Record<string, { itinerary: Itinerary; legs: Leg[] }>>({});
  const [showCompare, setShowCompare] = useState(false);
  const [draggingId, setDraggingId] = useState<string | null>(null);
  const [modal, setModal] = useState<
    | { kind: 'customise'; stopId: string }
    | { kind: 'detour'; afterStopId?: string }
    | { kind: 'gpx' }
    | null
  >(null);

  React.useEffect(() => {
    fetch('/api/grok/status').then(r => r.json()).then(setGrokStatus).catch(() => {});
  }, []);

  const allStops: Stop[] = itinerary.days
    .flatMap(d => d.stops)
    .filter((s): s is Stop => s != null && typeof s.lat === 'number');

  // Auto-populate origin/destination from first/last stop
  React.useEffect(() => {
    if (allStops.length > 0 && !origin) setOrigin(allStops[0].name);
    if (allStops.length > 0 && !destination) setDestination(allStops[allStops.length - 1].name);
  }, [allStops.length]);

  const legByFromId = React.useMemo(() => {
    const map = new Map<string, Leg>();
    for (const l of legs) map.set(l.fromId, l);
    return map;
  }, [legs]);

  React.useEffect(() => {
    let cancelled = false;
    const fetchRoutes = async () => {
      if (allStops.length < 2) { setLegs([]); return; }
      const fetched: Leg[] = [];
      for (let i = 0; i < allStops.length - 1; i++) {
        const leg = await getRoadLeg(allStops[i], allStops[i + 1]);
        if (cancelled) return;
        fetched.push(leg);
      }
      if (cancelled) return;
      setLegs(fetched);
      // Refresh cache with the up-to-date legs for the current variant
      setVariantCache(prev => ({ ...prev, [selectedVariant]: { itinerary, legs: fetched } }));
    };
    fetchRoutes();
    return () => { cancelled = true; };
  }, [itinerary]); // eslint-disable-line react-hooks/exhaustive-deps

  // ─── Prefetch other variants in the background ─────────────────────────────
  const prefetchedRef = React.useRef<Set<string>>(new Set());
  const prefetchKey = React.useMemo(() => {
    if (allStops.length < 2) return null;
    return `${allStops[0]?.name}__${allStops[allStops.length - 1]?.name}`;
  }, [allStops]);

  // Drop cached variants whose journey doesn't match the current origin/destination.
  const lastPrefetchKey = React.useRef<string | null>(null);
  React.useEffect(() => {
    if (!prefetchKey) return;
    if (lastPrefetchKey.current && lastPrefetchKey.current !== prefetchKey) {
      setVariantCache(prev => {
        const next: typeof prev = {};
        if (prev[selectedVariant]) next[selectedVariant] = prev[selectedVariant];
        return next;
      });
    }
    lastPrefetchKey.current = prefetchKey;
  }, [prefetchKey, selectedVariant]);

  // Drop variant cache + prefetch ledger when travel dates change — pricing is stale.
  const lastDatesKey = React.useRef<string>('');
  React.useEffect(() => {
    const key = `${travelDates.outbound || ''}|${travelDates.return || ''}|${travelDates.travellers || ''}`;
    if (lastDatesKey.current && lastDatesKey.current !== key) {
      setVariantCache(prev => {
        const next: typeof prev = {};
        if (prev[selectedVariant]) next[selectedVariant] = prev[selectedVariant];
        return next;
      });
      prefetchedRef.current.clear();
    }
    lastDatesKey.current = key;
  }, [travelDates.outbound, travelDates.return, travelDates.travellers, selectedVariant]);

  React.useEffect(() => {
    if (!prefetchKey) return;
    if (thinking || variantSwitching) return;
    if (variants.length < 2) return;
    const targets: RouteVariant['id'][] = ['fast', 'scenic', 'cheap'];
    const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
    if (!lastUserMsg) return;

    let cancelled = false;
    (async () => {
      for (const id of targets) {
        if (cancelled) return;
        if (id === selectedVariant) continue;
        if (variantCache[id]) continue;
        const key = `${prefetchKey}::${id}`;
        if (prefetchedRef.current.has(key)) continue;
        prefetchedRef.current.add(key);
        try {
          await sendMessage(`Replan the trip as the ${id} variant`, { variant: id, silent: true, prefetch: true });
        } catch (err) {
          console.warn('[TeslaTrip] prefetch failed for', id, err);
          prefetchedRef.current.delete(key);
        }
      }
    })();
    return () => { cancelled = true; };
  }, [prefetchKey, variants.length, thinking, variantSwitching, selectedVariant]); // eslint-disable-line react-hooks/exhaustive-deps

  const computedTotals = React.useMemo(() => {
    if (legs.length === 0) return null;
    const km = legs.reduce((s, l) => s + (l.distanceKm ?? 0), 0);
    const min = legs.reduce((s, l) => s + (l.durationMin ?? 0), 0);
    return { totalKm: km, driveMinutes: min };
  }, [legs]);

  const sendMessage = async (text: string, opts: { variant?: RouteVariant['id']; silent?: boolean; prefetch?: boolean } = {}) => {
    const trimmed = text.trim();
    if (!trimmed) return;
    const variantToUse = opts.variant ?? selectedVariant;
    if (!opts.silent && !opts.prefetch) {
      setMessages(prev => [...prev, { id: Date.now(), role: 'user', content: trimmed }]);
      setChatInput('');
      setChips(prev => [...prev, trimmed].slice(-6));
    }
    if (opts.prefetch) {
      // Background prefetch: never touch the visible itinerary or any spinner state.
    } else if (opts.variant) {
      setVariantSwitching(true);
    } else {
      setThinking(true);
      setThinkingMessage('');
    }

    let lastPartialItinerary: any = null;
    let lastVariants: any[] | null = null;
    let lastSelectedVariant: RouteVariant['id'] | null = null;
    let finalReply = '';

    try {
      const response = await fetch('/api/chat/stream', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: trimmed,
          vehicle: { name: vehicle.name, rangeKm: vehicle.rangeKm },
          itinerary,
          history: messages.map(m => ({ role: m.role, content: m.content })),
          selectedVariant: variantToUse,
          origin: origin.trim() || undefined,
          destination: destination.trim() || undefined,
          travelDates: (travelDates.outbound || travelDates.return || travelDates.travellers)
            ? travelDates : undefined,
        }),
      });
      if (!response.ok || !response.body) throw new Error('Failed to get streaming response');

      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      let sseBuffer = '';

      while (true) {
        const { value, done } = await reader.read();
        if (done) break;
        sseBuffer += decoder.decode(value, { stream: true });
        let blankIdx: number;
        while ((blankIdx = sseBuffer.indexOf('\n\n')) !== -1) {
          const block = sseBuffer.slice(0, blankIdx);
          sseBuffer = sseBuffer.slice(blankIdx + 2);
          let evName = 'message';
          let evData = '';
          for (const rawLine of block.split('\n')) {
            const line = rawLine.trim();
            if (line.startsWith('event:')) evName = line.slice(6).trim();
            else if (line.startsWith('data:')) evData += line.slice(5).trim();
          }
          if (!evData) continue;
          let payload: any = null;
          try { payload = JSON.parse(evData); } catch { continue; }

          if (evName === 'thinking') {
            if (!opts.prefetch && typeof payload.message === 'string' && payload.message.trim()) {
              setThinkingMessage(payload.message.trim());
            }
          } else if (evName === 'partial') {
            lastPartialItinerary = payload.itinerary;
            if (Array.isArray(payload.variants)) lastVariants = payload.variants;
            if (!opts.prefetch) {
              // Use synchronous normalizer for partials — no geocoding, no blocking
              if (payload.itinerary) {
                setItinerary(normalizePartialItinerary(payload.itinerary));
              }
              if (Array.isArray(payload.variants)) {
                setVariants(normalizeVariants(payload.variants));
              }
            }
          } else if (evName === 'done') {
            finalReply = payload.reply || '';
            if (payload.itinerary) {
              const clean = await normalizeAndSanitizeItinerary(payload.itinerary);
              const variantJustRendered = typeof payload.selectedVariant === 'string'
                ? payload.selectedVariant as RouteVariant['id']
                : opts.variant ?? selectedVariant;
              setVariantCache(prev => ({ ...prev, [variantJustRendered]: { itinerary: clean, legs: [] } }));
              lastSelectedVariant = variantJustRendered;
              if (!opts.prefetch) {
                setItinerary(clean);
              }
            }
            if (!opts.prefetch) {
              if (Array.isArray(payload.variants)) {
                setVariants(normalizeVariants(payload.variants));
              }
              if (typeof payload.selectedVariant === 'string') {
                setSelectedVariant(payload.selectedVariant as RouteVariant['id']);
              } else if (opts.variant) {
                setSelectedVariant(opts.variant);
              }
            }
          } else if (evName === 'error') {
            throw new Error(payload.error || 'Stream error');
          }
        }
      }

      if (!opts.silent && !opts.prefetch && finalReply) {
        setMessages(prev => [...prev, { id: Date.now() + 1, role: 'assistant', content: finalReply }]);
      }
      if (lastPartialItinerary && !opts.silent && !opts.prefetch) {
        toast.success('Grok finished your route');
      } else if (opts.variant && !opts.prefetch && lastSelectedVariant) {
        toast.success(`Switched to ${lastSelectedVariant} route`);
      }
    } catch (err: any) {
      console.error('[TeslaTrip] Grok stream failed:', err);
      if (!opts.silent && !opts.prefetch) {
        setMessages(prev => [...prev, { id: Date.now() + 1, role: 'assistant', content: "I'm having trouble reaching Grok right now. Check backend logs." }]);
      }
    } finally {
      if (!opts.prefetch) {
        setThinking(false);
        setVariantSwitching(false);
        setThinkingMessage('');
      }
    }

    // touch unused refs so eslint stays quiet (we keep them as breadcrumbs)
    void lastVariants;
  };

  const updateStop = (stopId: string, patch: Partial<Stop>) => {
    const next = structuredClone(itinerary);
    for (const d of next.days) {
      const idx = d.stops.findIndex(s => s.id === stopId);
      if (idx !== -1) {
        d.stops[idx] = { ...d.stops[idx], ...patch };
        break;
      }
    }
    setItinerary(next);
  };

  const insertDetour = (place: { name: string; lat: number; lng: number; type: StopType; description?: string }, afterStopId?: string) => {
    const next = structuredClone(itinerary);
    const newStop: Stop = {
      id: `detour-${Date.now()}`,
      name: place.name,
      type: place.type,
      lat: place.lat,
      lng: place.lng,
      day: 1,
      order: 1,
      description: place.description,
      combo: null,
    };
    // Find insertion point — after afterStopId or at the end of the first day
    if (afterStopId) {
      for (const d of next.days) {
        const idx = d.stops.findIndex(s => s.id === afterStopId);
        if (idx !== -1) {
          newStop.day = d.day;
          d.stops.splice(idx + 1, 0, newStop);
          break;
        }
      }
    } else if (next.days.length > 0) {
      const lastDay = next.days[next.days.length - 1];
      newStop.day = lastDay.day;
      lastDay.stops.push(newStop);
    } else {
      next.days = [{ day: 1, stops: [newStop] }];
    }
    for (const d of next.days) d.stops.forEach((s, i) => { s.order = i + 1; });
    setItinerary(next);
    toast.success(`Added ${place.name} to your trip`);
  };

  const moveStop = (stopId: string, direction: -1 | 1) => {
    // Move within the flat list across day boundaries — nudges by one slot.
    const flat = allStops;
    const idx = flat.findIndex(s => s.id === stopId);
    if (idx < 0) return;
    const targetIdx = idx + direction;
    if (targetIdx < 0 || targetIdx >= flat.length) return;
    reorderStops(stopId, flat[targetIdx].id);
  };

  const reorderStops = (dragId: string, targetId: string) => {
    if (dragId === targetId) return;
    const next = structuredClone(itinerary);
    // Find the dragged stop across all days and remove it
    let dragged: Stop | null = null;
    for (const d of next.days) {
      const idx = d.stops.findIndex(s => s.id === dragId);
      if (idx !== -1) {
        dragged = d.stops.splice(idx, 1)[0] as Stop;
        break;
      }
    }
    if (!dragged) return;
    // Insert before the target stop
    for (const d of next.days) {
      const idx = d.stops.findIndex(s => s.id === targetId);
      if (idx !== -1) {
        dragged.day = d.day;
        d.stops.splice(idx, 0, dragged);
        break;
      }
    }
    // Re-normalise order numbers within each day
    for (const d of next.days) {
      d.stops.forEach((s, i) => { s.order = i + 1; });
    }
    next.days = next.days.filter(d => d.stops.length > 0);
    setItinerary(next);
    toast.info('Stop reordered');
  };

  const useMyLocation = async () => {
    const t = toast.loading('Locating your car…');
    let lat: number | null = null;
    let lng: number | null = null;

    // Prefer Tesla Fleet API location when connected — it's the actual vehicle GPS.
    if (tesla.state?.lat != null && tesla.state?.lng != null) {
      lat = tesla.state.lat;
      lng = tesla.state.lng;
    } else {
      const coords = await getBrowserLocation();
      if (coords) { lat = coords.latitude; lng = coords.longitude; }
    }

    if (lat == null || lng == null) {
      toast.error('Could not get your location', {
        id: t,
        description: tesla.status?.connected
          ? 'Your Tesla may be asleep — try waking it or type a postcode.'
          : "Connect your Tesla account for vehicle GPS, or type a postcode.",
      });
      return;
    }
    const name = await reverseGeocode(lat, lng);
    if (!name) {
      const fallback = `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
      setOrigin(fallback);
      toast.success('Got your location', { id: t, description: fallback });
      return;
    }
    setOrigin(name);
    toast.success('Origin set to your location', { id: t, description: name });
  };

  const handleODCommit = () => {
    const from = origin.trim();
    const to = destination.trim();
    if (!from || !to) return;
    if (lastODSent.current && lastODSent.current.from === from && lastODSent.current.to === to) return;
    // First time: only fire if user has actually edited (we auto-populate from itinerary)
    const auto = allStops.length > 0 && from === allStops[0].name && to === allStops[allStops.length - 1].name;
    if (auto && !lastODSent.current) {
      lastODSent.current = { from, to };
      return;
    }
    lastODSent.current = { from, to };
    sendMessage(`Replan the trip from ${from} to ${to}`);
  };

  const switchVariant = (variantId: RouteVariant['id']) => {
    if (variantId === selectedVariant || variantSwitching || thinking) return;
    // Cache the current variant before switching
    if (itinerary.days.length > 0) {
      setVariantCache(prev => ({ ...prev, [selectedVariant]: { itinerary, legs } }));
    }
    // If target is already cached, swap instantly with no Grok call
    const cached = variantCache[variantId];
    if (cached) {
      setItinerary(cached.itinerary);
      setLegs(cached.legs);
      setSelectedVariant(variantId);
      toast.success(`Switched to ${variantId} (cached)`);
      return;
    }
    const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
    if (!lastUserMsg) {
      toast.info('Send a trip prompt first');
      return;
    }
    sendMessage(`Replan the trip as the ${variantId} variant`, { variant: variantId, silent: true });
  };

  const removeStop = (stopId: string) => {
    const next = structuredClone(itinerary);
    next.days.forEach(d => { d.stops = d.stops.filter(s => s.id !== stopId); });
    next.days = next.days.filter(d => d.stops.length > 0);
    setItinerary(next);
    if (activeStopId === stopId) setActiveStopId(null);
    toast.info('Stop removed');
  };

  const swapStop = (stopId: string, alt: AlternativeStop) => {
    const next = structuredClone(itinerary);
    for (const d of next.days) {
      const idx = d.stops.findIndex(s => s.id === stopId);
      if (idx === -1) continue;
      const original = d.stops[idx];
      const originalAsAlt: AlternativeStop = {
        id: original.id, name: original.name, type: original.type,
        lat: original.lat, lng: original.lng,
        description: original.description, combo: original.combo ?? null,
        amenities: original.amenities, cuisine: original.cuisine ?? null,
        priceLevel: original.priceLevel, chargeMinutes: original.chargeMinutes,
        durationMin: original.durationMin,
        deltaKm: typeof alt.deltaKm === 'number' ? -alt.deltaKm : undefined,
        deltaMin: typeof alt.deltaMin === 'number' ? -alt.deltaMin : undefined,
        reason: 'Original pick',
      };
      const otherAlts = (original.alternatives || []).filter(a => a.id !== alt.id);
      d.stops[idx] = {
        ...original,
        id: alt.id, name: alt.name, type: alt.type, lat: alt.lat, lng: alt.lng,
        description: alt.description, combo: alt.combo ?? null,
        amenities: alt.amenities, cuisine: alt.cuisine ?? null,
        priceLevel: alt.priceLevel, chargeMinutes: alt.chargeMinutes,
        durationMin: alt.durationMin, notes: undefined,
        alternatives: [originalAsAlt, ...otherAlts],
      };
      setActiveStopId(alt.id);
    }
    setItinerary(next);
    toast.success(`Swapped to ${alt.name}`);
  };

  const pickCrossing = (stopId: string, crossing: CrossingOption) => {
    const next = structuredClone(itinerary);
    for (const d of next.days) {
      const idx = d.stops.findIndex(s => s.id === stopId);
      if (idx === -1) continue;
      const stop = d.stops[idx];
      if (!stop.crossingOptions) continue;
      stop.crossingOptions = stop.crossingOptions.map(o => ({ ...o, isCurrent: o.id === crossing.id }));
      stop.name = `${crossing.operator} · ${crossing.fromPort} → ${crossing.toPort}`;
      stop.durationMin = crossing.durationMin;
      stop.type = crossing.mode === 'tunnel' ? 'tunnel' : 'ferry';
    }
    setItinerary(next);
    toast.success(`Switched crossing to ${crossing.operator}`, {
      description: `${crossing.fromPort} → ${crossing.toPort} · ${formatDuration(crossing.durationMin)}`,
    });
    // Ask Grok to re-plan the road portions either side of the new crossing
    sendMessage(`I picked the ${crossing.operator} crossing (${crossing.fromPort} → ${crossing.toPort}). Re-plan the stops on either side to match.`, { silent: true });
  };

  const activeStop = activeStopId ? allStops.find(s => s.id === activeStopId) || null : null;
  const dateLabels = ['Today', 'Tomorrow'];

  // Driving-mode trigger: car is moving (or in a non-Park gear). Also enabled
  // via ?drivingMode=1 for development.
  const forceDrivingMode = React.useMemo(() => {
    if (typeof window === 'undefined') return false;
    return new URLSearchParams(window.location.search).get('drivingMode') === '1';
  }, []);
  const isDriving = forceDrivingMode || (
    !!tesla.state?.shiftState && tesla.state.shiftState !== 'P'
  );
  // Closest planned stop to the car's current position, used by driving mode.
  const nextStop = React.useMemo(() => {
    if (!isDriving || !tesla.state?.lat || !tesla.state?.lng) return null;
    let best: { stop: Stop; km: number } | null = null;
    for (const s of allStops) {
      const km = haversineKm(
        { lat: tesla.state.lat, lng: tesla.state.lng },
        { lat: s.lat, lng: s.lng },
      );
      if (best === null || km < best.km) best = { stop: s, km };
    }
    return best;
  }, [isDriving, tesla.state?.lat, tesla.state?.lng, allStops]);

  return (
    <ErrorBoundary>
      {isDriving && (
        <DrivingMode
          tesla={tesla}
          nextStop={nextStop}
          vehicle={vehicle}
          onSendToNav={async () => {
            if (!nextStop) return;
            const ok = await sendToTeslaNav({ lat: nextStop.stop.lat, lng: nextStop.stop.lng, name: nextStop.stop.name });
            if (ok) toast.success(`Sent ${nextStop.stop.name} to Tesla nav`);
            else toast.error('Could not send');
          }}
          onExit={() => {
            // Force-exit driving mode (debug). Strips ?drivingMode=1 if present.
            const url = new URL(window.location.href);
            url.searchParams.delete('drivingMode');
            window.history.replaceState({}, '', url.toString());
            window.location.reload();
          }}
        />
      )}
      <div className="flex flex-col h-screen overflow-hidden" style={{ background: 'var(--gd-bg)', color: 'var(--gd-text)' }}>
        <TopBar
          origin={origin} destination={destination}
          onOriginChange={setOrigin} onDestinationChange={setDestination}
          onODCommit={handleODCommit}
          chatInput={chatInput} setChatInput={setChatInput}
          onChatSubmit={() => sendMessage(chatInput)}
          chips={chips} onRemoveChip={(i) => setChips(chips.filter((_, idx) => idx !== i))}
          vehicle={vehicle}
          onOpenVehiclePanel={(rect) => { setVehicleAnchor(rect); setVehiclePanelOpen(true); }}
          grokStatus={grokStatus}
          onOpenGpx={() => setModal({ kind: 'gpx' })}
          travelDates={travelDates}
          onOpenDates={(rect) => { setDateAnchor(rect); setDatePickerOpen(true); }}
          onUseMyLocation={useMyLocation}
          teslaStatus={tesla.status}
          teslaState={tesla.state}
          onConnectTesla={async () => {
            if (tesla.owner?.required && !tesla.owner.authenticated) {
              ownerLoginThenRef.current = async () => {
                try { await startTeslaConnect(); }
                catch { toast.error('Could not start Tesla OAuth'); }
              };
              setOwnerLoginOpen(true);
              return;
            }
            try { await startTeslaConnect(); }
            catch { toast.error('Could not start Tesla OAuth'); }
          }}
          onDisconnectTesla={async () => {
            await disconnectTesla();
            await tesla.refreshStatus();
            toast.success('Tesla disconnected');
          }}
          inCar={inCar}
        />

        {variants.length > 0 && (
          <VariantStrip
            variants={variants}
            selected={selectedVariant}
            onSelect={switchVariant}
            switching={variantSwitching}
            cachedIds={Object.keys(variantCache)}
            showCompare={showCompare}
            onToggleCompare={() => setShowCompare(s => !s)}
          />
        )}

        {/* Body: map left, rail right */}
        <div className="flex flex-1 overflow-hidden">
          {/* Map */}
          <div className="flex-1 relative" style={{ background: '#0a0f1a' }}>
            <MapContainer
              center={[51.5, 2.0]} zoom={5}
              style={{ height: '100%', width: '100%', background: '#0a0f1a' }}
              zoomControl={false}
            >
              <TileLayer
                attribution="&copy; OpenStreetMap contributors"
                url="https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png"
              />
              <MapAutoFit stops={allStops} />
              <MapFlyTo stop={activeStop} />
              {allStops.map(stop => {
                const meta = stopMeta(stop.type);
                const isActive = stop.id === activeStopId;
                const isHover = stop.id === hoverStopId;
                return (
                  <Marker
                    key={stop.id}
                    position={[stop.lat, stop.lng]}
                    icon={makePinIcon(meta.color, isActive, isHover)}
                    eventHandlers={{
                      click: () => setActiveStopId(stop.id),
                      mouseover: () => setHoverStopId(stop.id),
                      mouseout: () => setHoverStopId(null),
                    }}
                  >
                    <Popup>
                      <div>
                        <div className="font-semibold text-[13px] mb-0.5">{stop.name}</div>
                        {stop.combo && (
                          <div className="text-[10px] font-semibold uppercase tracking-wider mb-1" style={{ color: 'var(--gd-red)' }}>
                            {stop.combo}
                          </div>
                        )}
                        {stop.description && <div className="text-[11.5px] leading-snug">{stop.description}</div>}
                      </div>
                    </Popup>
                  </Marker>
                );
              })}
              {showCompare && Object.entries(variantCache)
                .filter(([id]) => id !== selectedVariant)
                .map(([id, cached]) => {
                  const variant = variants.find(v => v.id === id);
                  const color = variant?.tone === 'green' ? '#4ade80' : variant?.tone === 'blue' ? '#60a5fa' : '#e31937';
                  return cached.legs.map((leg, i) => (
                    <Polyline
                      key={`${id}-${i}`}
                      positions={leg.geometry}
                      pathOptions={{ color, weight: 2.4, opacity: 0.7, dashArray: '6 5' }}
                    />
                  ));
                })}
              {legs.map((leg, i) => (
                <React.Fragment key={i}>
                  <Polyline positions={leg.geometry} pathOptions={{ color: 'var(--gd-red)', weight: 6, opacity: 0.18 }} />
                  <Polyline positions={leg.geometry} pathOptions={{ color: 'var(--gd-red)', weight: 2.4, opacity: 1 }} />
                </React.Fragment>
              ))}
            </MapContainer>

            {/* Map legend (variants when comparing, else stop types) */}
            {showCompare ? (
              <div
                className="absolute top-4 left-4 px-3 py-2 rounded-[10px] flex flex-col gap-1.5 text-[11px]"
                style={{ background: 'rgba(20,20,24,0.85)', backdropFilter: 'blur(12px)', border: '1px solid var(--gd-border-2)', color: 'var(--gd-text-2)' }}
              >
                {variants.filter(v => v.id === selectedVariant || variantCache[v.id]).map(v => {
                  const isSel = v.id === selectedVariant;
                  const c = v.tone === 'green' ? '#4ade80' : v.tone === 'blue' ? '#60a5fa' : '#e31937';
                  return (
                    <div key={v.id} className="flex items-center gap-2">
                      <div
                        className="w-[18px] h-[3px] rounded"
                        style={{
                          background: isSel ? c : `repeating-linear-gradient(to right, ${c} 0 6px, transparent 6px 11px)`,
                        }}
                      />
                      <div className="text-[12px]" style={{ color: isSel ? 'var(--gd-text)' : 'var(--gd-text-2)', fontWeight: isSel ? 500 : 400 }}>
                        {v.label}
                      </div>
                    </div>
                  );
                })}
              </div>
            ) : (
            <div
              className="absolute top-4 left-4 px-3 py-2 rounded-[10px] flex items-center gap-3.5 text-[11px]"
              style={{ background: 'rgba(20,20,24,0.75)', backdropFilter: 'blur(12px)', border: '1px solid var(--gd-border)', color: 'var(--gd-text-2)' }}
            >
              <div className="flex items-center gap-1.5"><Zap className="w-3 h-3" style={{ color: 'var(--gd-green)' }} />Charge</div>
              <div className="flex items-center gap-1.5"><Bed className="w-3 h-3" style={{ color: 'var(--gd-blue)' }} />Sleep</div>
              <div className="flex items-center gap-1.5"><Camera className="w-3 h-3" style={{ color: 'var(--gd-purple)' }} />See</div>
            </div>
            )}

            {/* Refinements overlay */}
            {chips.length > 0 && (
              <div
                className="absolute left-4 bottom-4 px-3.5 py-2.5 rounded-[12px] max-w-[540px]"
                style={{ background: 'rgba(20,20,24,0.85)', backdropFilter: 'blur(12px)', border: '1px solid var(--gd-border-2)' }}
              >
                <div className="text-[10.5px] uppercase tracking-wider mb-1.5" style={{ color: 'var(--gd-text-3)' }}>
                  Refinements applied · {chips.length}
                </div>
                <div className="flex flex-wrap gap-1.5">
                  {chips.map((c, i) => (
                    <span
                      key={i}
                      className="text-[11.5px] px-2.5 py-1 rounded-full inline-flex items-center gap-1.5"
                      style={{ background: 'var(--gd-red-soft)', color: 'var(--gd-red)', border: '1px solid var(--gd-red-line)' }}
                    >
                      <Sparkles className="w-2.5 h-2.5" /> {c}
                    </span>
                  ))}
                </div>
              </div>
            )}

            {/* Empty state overlay */}
            {allStops.length === 0 && !thinking && (
              <div className="absolute inset-0 grid place-items-center pointer-events-none">
                <div className="text-center max-w-md">
                  <div className="text-[20px] font-medium mb-2" style={{ color: 'var(--gd-text-2)' }}>Where to?</div>
                  <div className="text-[13.5px]" style={{ color: 'var(--gd-text-3)' }}>
                    Use the chat above to describe your trip. Try one of the quick prompts in the stops rail.
                  </div>
                </div>
              </div>
            )}

            {/* Thinking overlay on the map (when Grok is planning but nothing rendered yet) */}
            {allStops.length === 0 && thinking && (
              <div className="absolute inset-0 grid place-items-center pointer-events-none">
                <div
                  className="text-center max-w-md px-6 py-5 rounded-2xl pointer-events-auto"
                  style={{
                    background: 'rgba(20,20,24,0.86)',
                    border: '1px solid var(--gd-border-2)',
                    backdropFilter: 'blur(14px)',
                    boxShadow: '0 12px 40px rgba(0,0,0,0.4)',
                  }}
                >
                  <div className="flex items-center justify-center gap-2 mb-3">
                    <div className="flex gap-1">
                      <div className="w-1.5 h-1.5 rounded-full animate-bounce" style={{ background: 'var(--gd-red)' }} />
                      <div className="w-1.5 h-1.5 rounded-full animate-bounce" style={{ background: 'var(--gd-red)', animationDelay: '120ms' }} />
                      <div className="w-1.5 h-1.5 rounded-full animate-bounce" style={{ background: 'var(--gd-red)', animationDelay: '240ms' }} />
                    </div>
                    <div className="text-[11px] tracking-[0.15em] uppercase" style={{ color: 'var(--gd-red)' }}>
                      Grok is planning your route
                    </div>
                  </div>
                  {thinkingMessage ? (
                    <div
                      key={thinkingMessage}
                      className="text-[13px] leading-snug min-h-[40px]"
                      style={{ color: 'var(--gd-text-2)', fontStyle: 'italic' }}
                    >
                      “{thinkingMessage}”
                    </div>
                  ) : (
                    <div className="space-y-2 min-h-[40px]">
                      <SkeletonRow widthPct={92} />
                      <SkeletonRow widthPct={78} />
                      <SkeletonRow widthPct={85} />
                    </div>
                  )}
                  <div className="text-[10.5px] mt-3 tracking-wider uppercase" style={{ color: 'var(--gd-text-3)' }}>
                    Stops will appear as soon as they're chosen
                  </div>
                </div>
              </div>
            )}
          </div>

          {/* Stops rail */}
          <aside
            className="flex flex-col overflow-hidden flex-shrink-0"
            style={{ width: 540, background: 'var(--gd-bg-2)', borderLeft: '1px solid var(--gd-border)' }}
          >
            {/* Trip header */}
            <div className="px-5 py-3.5 flex items-center gap-2.5" style={{ borderBottom: '1px solid var(--gd-border)' }}>
              <div>
                <div className="text-[13.5px] font-medium">
                  {allStops.length} stops · {itinerary.days.length || '–'} day{itinerary.days.length === 1 ? '' : 's'}
                </div>
                <div className="text-[11px] num" style={{ color: 'var(--gd-text-3)' }}>
                  {computedTotals ? formatKm(computedTotals.totalKm) : `${itinerary.summary.totalDistanceKm} km`}
                  {' · '}
                  {computedTotals ? formatDuration(computedTotals.driveMinutes) : `${itinerary.summary.estDriveHours}h`} drive
                  {' · '}
                  {formatDuration(itinerary.summary.estChargeHours * 60)} charging
                </div>
              </div>
              <div className="flex-1" />
              <button
                onClick={() => setModal({ kind: 'detour' })}
                className="h-7 px-2.5 inline-flex items-center gap-1.5 rounded-lg text-[11.5px] whitespace-nowrap"
                style={{ background: 'var(--gd-red-soft)', border: '1px solid var(--gd-red-line)', color: 'var(--gd-red)' }}
              >
                <Plus className="w-3 h-3" /> Add stop
              </button>
            </div>

            {/* Travel-dates nudge */}
            {itinerary.needsTravelDates && !travelDates.outbound && allStops.length > 0 && (
              <button
                onClick={(e) => { setDateAnchor(e.currentTarget.getBoundingClientRect()); setDatePickerOpen(true); }}
                className="mx-4 mt-3 px-3 py-2 rounded-lg text-left flex items-center gap-2.5 transition hover:bg-white/[0.02]"
                style={{ background: 'var(--gd-red-soft)', border: '1px solid var(--gd-red-line)' }}
              >
                <CalendarDays className="w-3.5 h-3.5 flex-shrink-0" style={{ color: 'var(--gd-red)' }} />
                <div className="flex-1 min-w-0">
                  <div className="text-[11.5px] font-medium" style={{ color: 'var(--gd-red)' }}>Add travel dates for accurate pricing</div>
                  <div className="text-[10.5px]" style={{ color: 'var(--gd-text-3)' }}>
                    Ferry, Eurotunnel and hotel prices vary by date — currently showing off-peak ballpark.
                  </div>
                </div>
                <ChevronDown className="w-3 h-3 rotate-[-90deg]" style={{ color: 'var(--gd-red)' }} />
              </button>
            )}

            {/* Stops list */}
            <div className="flex-1 overflow-y-auto px-4 pb-4">
              {itinerary.days.length === 0 ? (
                <div className="py-8 text-center">
                  <div className="text-[13px] mb-3" style={{ color: 'var(--gd-text-2)' }}>No trip planned yet.</div>
                  <div className="text-[11.5px] mb-4" style={{ color: 'var(--gd-text-3)' }}>Try one of these to get started:</div>
                  <div className="flex flex-col gap-1.5">
                    {QUICK_PROMPTS.map((p, i) => (
                      <button
                        key={i}
                        onClick={() => sendMessage(p)}
                        disabled={thinking}
                        className="text-left text-[12px] px-3 py-2 rounded-lg transition disabled:opacity-40"
                        style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)', color: 'var(--gd-text-2)' }}
                      >
                        {p}
                      </button>
                    ))}
                  </div>
                </div>
              ) : (
                itinerary.days.map((day, di) => {
                  const stops = (day.stops || []).filter(Boolean).sort((a, b) => a.order - b.order);
                  const dayLegs: Leg[] = [];
                  for (let i = 0; i < stops.length - 1; i++) {
                    const l = legByFromId.get(stops[i].id);
                    if (l && l.toId === stops[i + 1].id) dayLegs.push(l);
                  }
                  const dayKm = dayLegs.reduce((s, l) => s + (l.distanceKm ?? 0), 0);
                  const dayMin = dayLegs.reduce((s, l) => s + (l.durationMin ?? 0), 0);
                  const chargeMin = stops.reduce((s, st) => s + (st.chargeMinutes || 0), 0);
                  return (
                    <div key={di}>
                      <DayHeader
                        dayNumber={day.day}
                        title={day.title}
                        distanceKm={dayKm}
                        driveMin={dayMin}
                        chargeMin={chargeMin}
                        dateLabel={dateLabels[di]}
                      />
                      <div className="pt-3">
                        {stops.map((stop, si) => {
                          const isLast = si === stops.length - 1;
                          const leg = legByFromId.get(stop.id);
                          const hasNextDay = di < itinerary.days.length - 1;
                          const showNightBlock = isLast && hasNextDay && (stop.type === 'hotel' || stop.type === 'supercharger');
                          return (
                            <div key={stop.id}>
                              <StopCard
                                stop={stop}
                                active={stop.id === activeStopId}
                                hover={stop.id === hoverStopId}
                                dragging={stop.id === draggingId}
                                onSelect={() => setActiveStopId(stop.id === activeStopId ? null : stop.id)}
                                onHover={(h) => setHoverStopId(h ? stop.id : null)}
                                onSwap={(alt) => swapStop(stop.id, alt)}
                                onRemove={() => removeStop(stop.id)}
                                onCustomise={() => setModal({ kind: 'customise', stopId: stop.id })}
                                onPickCrossing={(c) => pickCrossing(stop.id, c)}
                                onMoveUp={() => moveStop(stop.id, -1)}
                                onMoveDown={() => moveStop(stop.id, 1)}
                                canMoveUp={allStops[0]?.id !== stop.id}
                                canMoveDown={allStops[allStops.length - 1]?.id !== stop.id}
                                onSendToNav={async () => {
                                  const ok = await sendToTeslaNav({ lat: stop.lat, lng: stop.lng, name: stop.name });
                                  if (ok) toast.success(`Sent to Tesla nav`, { description: stop.name });
                                  else toast.error('Could not send to nav', { description: 'Car may be asleep — try wake.' });
                                }}
                                canSendToNav={!!tesla.status?.connected}
                                onDragStart={(e) => {
                                  e.dataTransfer.setData('text/plain', stop.id);
                                  e.dataTransfer.effectAllowed = 'move';
                                  setDraggingId(stop.id);
                                }}
                                onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }}
                                onDrop={(e) => {
                                  e.preventDefault();
                                  const dragId = e.dataTransfer.getData('text/plain');
                                  if (dragId && dragId !== stop.id) reorderStops(dragId, stop.id);
                                  setDraggingId(null);
                                }}
                                onDragEnd={() => setDraggingId(null)}
                              />
                              {!isLast && <LegRow leg={leg} />}
                              {showNightBlock && (
                                <NightBlock
                                  lastStop={stop}
                                  onOpenHotelOptions={() => setActiveStopId(stop.id)}
                                />
                              )}
                            </div>
                          );
                        })}
                      </div>
                    </div>
                  );
                })
              )}

              {itinerary.days.length > 0 && (
                <button
                  onClick={() => setModal({ kind: 'detour' })}
                  className="w-full h-10 mt-3 rounded-[10px] text-[12px] inline-flex items-center justify-center gap-1.5 hover:bg-white/[0.02] transition"
                  style={{ border: '1px dashed var(--gd-border-2)', background: 'transparent', color: 'var(--gd-text-3)' }}
                >
                  <Plus className="w-3 h-3" /> Add a detour or stop
                </button>
              )}

              {thinking && (
                <div className="mt-4 px-3 py-2.5 rounded-lg" style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)' }}>
                  <div className="flex items-center gap-2 mb-1">
                    <div className="flex gap-1">
                      <div className="w-1 h-1 rounded-full animate-bounce" style={{ background: 'var(--gd-red)' }} />
                      <div className="w-1 h-1 rounded-full animate-bounce" style={{ background: 'var(--gd-red)', animationDelay: '120ms' }} />
                      <div className="w-1 h-1 rounded-full animate-bounce" style={{ background: 'var(--gd-red)', animationDelay: '240ms' }} />
                    </div>
                    <div className="text-[10px] tracking-wider uppercase" style={{ color: 'var(--gd-red)' }}>
                      {itinerary.days.length > 0 ? 'Refining your route' : 'Grok is planning your route'}
                    </div>
                  </div>
                  {thinkingMessage && (
                    <div
                      className="text-[12px] leading-snug mt-1.5 line-clamp-3"
                      style={{ color: 'var(--gd-text-2)', fontStyle: 'italic' }}
                      key={thinkingMessage}
                    >
                      {thinkingMessage}
                    </div>
                  )}
                  {!thinkingMessage && itinerary.days.length === 0 && (
                    <div className="mt-2 space-y-1.5">
                      <SkeletonRow widthPct={92} />
                      <SkeletonRow widthPct={78} />
                      <SkeletonRow widthPct={85} />
                    </div>
                  )}
                </div>
              )}

              {itinerary.summary.highlights && itinerary.summary.highlights.length > 0 && (
                <div className="mt-5 pt-4" style={{ borderTop: '1px solid var(--gd-border)' }}>
                  <div className="text-[10px] uppercase tracking-wider mb-2" style={{ color: 'var(--gd-red)' }}>Highlights</div>
                  <ul className="space-y-1.5 text-[12px]" style={{ color: 'var(--gd-text-2)' }}>
                    {itinerary.summary.highlights.map((h, i) => (
                      <li key={i} className="flex gap-2">
                        <span style={{ color: 'var(--gd-red)' }}>★</span>
                        <span>{h}</span>
                      </li>
                    ))}
                  </ul>
                </div>
              )}
            </div>
          </aside>
        </div>
      </div>

      {modal?.kind === 'customise' && (
        <CustomiseStopModal
          stop={allStops.find(s => s.id === modal.stopId) || itinerary.days.flatMap(d => d.stops).find(s => s.id === modal.stopId) || null}
          onClose={() => setModal(null)}
          onApply={(patch) => { updateStop(modal.stopId, patch); setModal(null); toast.success('Stop updated'); }}
        />
      )}
      {modal?.kind === 'detour' && (
        <AddDetourOverlay
          onClose={() => setModal(null)}
          onInsert={(place) => insertDetour(place, modal.afterStopId)}
        />
      )}
      {modal?.kind === 'gpx' && (
        <GpxExportModal itinerary={itinerary} onClose={() => setModal(null)} />
      )}

      <VehicleSelectorPanel
        open={vehiclePanelOpen}
        anchorRect={vehicleAnchor}
        selected={vehicle}
        onSelect={setVehicle}
        onClose={() => setVehiclePanelOpen(false)}
      />

      <TravelDatesPanel
        open={datePickerOpen}
        anchorRect={dateAnchor}
        value={travelDates}
        onChange={setTravelDates}
        onClose={() => setDatePickerOpen(false)}
        onApply={() => {
          setDatePickerOpen(false);
          if (allStops.length > 0) {
            sendMessage(`Update the itinerary for travel dates ${formatDateRange(travelDates.outbound, travelDates.return)}`, { silent: true });
          }
        }}
      />

      <OwnerLoginModal
        open={ownerLoginOpen}
        onClose={() => setOwnerLoginOpen(false)}
        onSuccess={async () => {
          setOwnerLoginOpen(false);
          await tesla.refreshStatus();
          const fn = ownerLoginThenRef.current;
          ownerLoginThenRef.current = null;
          if (fn) fn();
        }}
      />
    </ErrorBoundary>
  );
}

// ─── Driving mode ────────────────────────────────────────────────────────────
// Full-screen overlay when the car is moving. Huge typography, minimal chrome,
// only one possible action (Send next stop to nav).
function DrivingMode({ tesla, nextStop, vehicle, onSendToNav, onExit }: {
  tesla: ReturnType<typeof useTesla>;
  nextStop: { stop: Stop; km: number } | null;
  vehicle: Vehicle;
  onSendToNav: () => void;
  onExit: () => void;
}) {
  const state = tesla.state;
  const battery = state?.battery ?? null;
  const rangeKm = state?.rangeKm ?? null;
  const speed = state?.speedKmh ?? null;
  const distanceKm = nextStop?.km ?? null;
  const reqPctRough = distanceKm != null
    ? Math.max(5, Math.round((distanceKm / Math.max(50, vehicle.rangeKm)) * 100))
    : null;
  const enough = battery != null && reqPctRough != null ? battery >= reqPctRough + 8 : null;
  // ETA roughly distance ÷ assumed 95 km/h motorway, expressed as a clock time.
  const etaText = React.useMemo(() => {
    if (distanceKm == null) return null;
    const avgKmh = speed && speed > 30 ? Math.max(60, speed) : 95;
    const minutes = Math.round((distanceKm / avgKmh) * 60);
    const arrive = new Date(Date.now() + minutes * 60_000);
    return `${arrive.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })} · ${minutes}m`;
  }, [distanceKm, speed]);

  const meta = nextStop ? stopMeta(nextStop.stop.type) : null;
  const Icon = meta?.icon ?? MapPin;

  return (
    <div
      className="fixed inset-0 flex flex-col"
      style={{ zIndex: 9997, background: '#0a0a0c', color: 'var(--gd-text)' }}
    >
      {/* Top strip — battery + speed + exit */}
      <div className="px-8 py-5 flex items-center gap-6" style={{ borderBottom: '1px solid var(--gd-border)' }}>
        <div className="flex items-center gap-3">
          <div
            className="grid place-items-center rounded-2xl"
            style={{ width: 68, height: 44, background: 'var(--gd-panel-2)', border: '1px solid var(--gd-border-2)' }}
          >
            <div className="text-[22px] font-semibold num" style={{ color: battery != null && battery < 20 ? 'var(--gd-red)' : 'var(--gd-green)' }}>
              {battery != null ? `${battery}%` : '—'}
            </div>
          </div>
          <div className="leading-[1.15]">
            <div className="text-[13px]" style={{ color: 'var(--gd-text-3)' }}>Range</div>
            <div className="text-[20px] num font-medium">{rangeKm != null ? `${rangeKm} km` : '—'}</div>
          </div>
        </div>

        <div className="flex-1 text-center">
          <div className="text-[12px] uppercase tracking-[0.2em]" style={{ color: 'var(--gd-text-3)' }}>
            Driving · {state?.shiftState ?? 'D'}
          </div>
          <div className="text-[42px] num font-semibold leading-[1]">{speed ?? 0}</div>
          <div className="text-[12px]" style={{ color: 'var(--gd-text-3)' }}>km/h</div>
        </div>

        <button
          onClick={onExit}
          className="text-[12px] px-3 py-2 rounded-lg"
          style={{ background: 'var(--gd-panel-2)', border: '1px solid var(--gd-border)', color: 'var(--gd-text-2)' }}
          title="Exit driving mode"
        >
          Exit
        </button>
      </div>

      {/* Centre — next stop hero */}
      <div className="flex-1 flex flex-col items-center justify-center px-8">
        {nextStop ? (
          <>
            <div className="text-[14px] uppercase tracking-[0.25em] mb-5" style={{ color: 'var(--gd-red)' }}>
              Next stop
            </div>
            <div
              className="w-24 h-24 rounded-3xl grid place-items-center mb-6"
              style={{ background: `${meta?.color || '#9ca3af'}22`, border: `1px solid ${meta?.color || '#9ca3af'}55` }}
            >
              <Icon size={44} style={{ color: meta?.color }} />
            </div>
            <div className="text-[42px] font-semibold text-center leading-[1.1] mb-3 max-w-[80vw]">
              {nextStop.stop.name}
            </div>
            <div className="flex items-center gap-6 mb-10">
              <div className="text-center">
                <div className="text-[11px] uppercase tracking-wider" style={{ color: 'var(--gd-text-3)' }}>Distance</div>
                <div className="text-[34px] num font-semibold">{Math.round(distanceKm ?? 0)} km</div>
              </div>
              {etaText && (
                <div className="text-center">
                  <div className="text-[11px] uppercase tracking-wider" style={{ color: 'var(--gd-text-3)' }}>Arrive</div>
                  <div className="text-[34px] num font-semibold">{etaText}</div>
                </div>
              )}
            </div>
            {enough != null && (
              <div
                className="text-[14px] px-4 py-2 rounded-full mb-6"
                style={{
                  background: enough ? 'rgba(74,222,128,0.12)' : 'rgba(227,25,55,0.16)',
                  color: enough ? 'var(--gd-green)' : 'var(--gd-red)',
                  border: `1px solid ${enough ? 'rgba(74,222,128,0.35)' : 'rgba(227,25,55,0.45)'}`,
                }}
              >
                {enough ? `Enough battery — ~${reqPctRough}% needed` : `Low battery — need ~${reqPctRough}% to reach this stop`}
              </div>
            )}
            <button
              onClick={onSendToNav}
              className="h-16 px-10 rounded-2xl text-[20px] font-semibold inline-flex items-center gap-3"
              style={{ background: 'var(--gd-red)', color: '#fff', boxShadow: '0 8px 28px rgba(227,25,55,0.35)' }}
            >
              <Navigation className="w-6 h-6" /> Send to Tesla nav
            </button>
          </>
        ) : (
          <div className="text-center">
            <div className="text-[18px] mb-2" style={{ color: 'var(--gd-text-2)' }}>No trip loaded</div>
            <div className="text-[13px]" style={{ color: 'var(--gd-text-3)' }}>Plan a trip while parked and it'll pick up from here.</div>
          </div>
        )}
      </div>

      {/* Bottom — charging status if plugged in */}
      {state?.chargingState === 'Charging' && (
        <div className="px-8 py-4 flex items-center gap-6" style={{ borderTop: '1px solid var(--gd-border)', background: 'rgba(74,222,128,0.05)' }}>
          <Zap className="w-6 h-6" style={{ color: 'var(--gd-green)' }} />
          <div className="flex-1">
            <div className="text-[12px] uppercase tracking-wider" style={{ color: 'var(--gd-green)' }}>Charging</div>
            <div className="text-[20px] num">
              {state.chargerPowerKw ?? '—'} kW
              {state.timeToFullCharge != null && (
                <span className="text-[13px] ml-3" style={{ color: 'var(--gd-text-3)' }}>
                  · {Math.round(state.timeToFullCharge * 60)}m to target
                </span>
              )}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

function OwnerLoginModal({ open, onClose, onSuccess }: {
  open: boolean; onClose: () => void; onSuccess: () => void;
}) {
  const [secret, setSecret] = React.useState('');
  const [pending, setPending] = React.useState(false);
  React.useEffect(() => {
    if (!open) return;
    const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onClose]);
  if (!open) return null;
  const submit = async () => {
    if (!secret) return;
    setPending(true);
    try {
      const ok = await loginOwner(secret);
      if (ok) {
        toast.success('Logged in as owner');
        setSecret('');
        onSuccess();
      } else {
        toast.error('Invalid owner secret');
      }
    } finally {
      setPending(false);
    }
  };
  return (
    <div
      onClick={onClose}
      className="fixed inset-0 grid place-items-center p-6"
      style={{ zIndex: 9999, background: 'rgba(5,5,8,0.72)', backdropFilter: 'blur(8px)' }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        className="w-[440px] max-w-full overflow-hidden"
        style={{
          background: 'var(--gd-bg-2)',
          border: '1px solid var(--gd-border-2)',
          borderRadius: 16,
          boxShadow: '0 24px 60px rgba(0,0,0,0.55)',
        }}
      >
        <div className="px-5 py-4" style={{ borderBottom: '1px solid var(--gd-border)' }}>
          <div className="text-[14px] font-semibold">Owner login required</div>
          <div className="text-[11.5px] mt-0.5" style={{ color: 'var(--gd-text-3)' }}>
            The Tesla integration is restricted to the deploying user. Enter the OWNER_SECRET set in the deploy environment.
          </div>
        </div>
        <div className="px-5 py-4">
          <input
            type="password"
            autoFocus
            value={secret}
            onChange={(e) => setSecret(e.target.value)}
            onKeyDown={(e) => { if (e.key === 'Enter') submit(); }}
            placeholder="Owner secret"
            className="w-full text-[13.5px] px-3 py-2.5 rounded-lg outline-none"
            style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)', color: 'var(--gd-text)' }}
          />
        </div>
        <div className="px-5 py-4 flex items-center gap-2" style={{ borderTop: '1px solid var(--gd-border)' }}>
          <button
            onClick={onClose}
            className="flex-1 h-10 text-[12.5px] rounded-lg"
            style={{ background: 'transparent', border: '1px solid var(--gd-border)', color: 'var(--gd-text-2)' }}
          >
            Cancel
          </button>
          <button
            onClick={submit}
            disabled={!secret || pending}
            className="flex-[2] h-10 text-[12.5px] rounded-lg disabled:opacity-40"
            style={{ background: 'var(--gd-red)', color: '#fff' }}
          >
            {pending ? 'Logging in…' : 'Log in'}
          </button>
        </div>
      </div>
    </div>
  );
}

// ─── Modal shell ─────────────────────────────────────────────────────────────
function ModalShell({
  onClose, width = 720, title, subtitle, footer, children,
}: {
  onClose: () => void;
  width?: number;
  title?: string;
  subtitle?: string;
  footer?: React.ReactNode;
  children: React.ReactNode;
}) {
  React.useEffect(() => {
    const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  return (
    <div
      onClick={onClose}
      className="fixed inset-0 grid place-items-center p-6"
      style={{ zIndex: 9999, background: 'rgba(5,5,8,0.72)', backdropFilter: 'blur(8px)' }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        className="flex flex-col overflow-hidden"
        style={{
          background: 'var(--gd-bg-2)',
          border: '1px solid var(--gd-border-2)',
          borderRadius: 16,
          width,
          maxWidth: '100%',
          maxHeight: '90vh',
          boxShadow: '0 32px 80px rgba(0,0,0,0.5)',
        }}
      >
        {title && (
          <div className="px-5 py-4 flex items-center gap-3 flex-shrink-0" style={{ borderBottom: '1px solid var(--gd-border)' }}>
            <div className="flex-1 min-w-0">
              <div className="text-[16px] font-medium" style={{ letterSpacing: '-0.01em' }}>{title}</div>
              {subtitle && <div className="text-[12px] mt-0.5" style={{ color: 'var(--gd-text-3)' }}>{subtitle}</div>}
            </div>
            <button
              onClick={onClose}
              className="w-8 h-8 rounded-lg grid place-items-center hover:bg-white/5 transition"
              style={{ color: 'var(--gd-text-2)' }}
            >
              <X className="w-4 h-4" />
            </button>
          </div>
        )}
        <div className="flex-1 overflow-y-auto min-h-0">{children}</div>
        {footer && (
          <div className="px-5 py-3.5 flex items-center gap-2.5 flex-shrink-0" style={{ borderTop: '1px solid var(--gd-border)' }}>
            {footer}
          </div>
        )}
      </div>
    </div>
  );
}

// ─── Customise Stop modal (Charger / Overnight / Duration / Things / Detour) ─
type CustomiseTab = 'charger' | 'overnight' | 'duration' | 'things' | 'detour';

function CustomiseStopModal({
  stop, onClose, onApply,
}: {
  stop: Stop | null;
  onClose: () => void;
  onApply: (patch: Partial<Stop>) => void;
}) {
  if (!stop) return null;
  const isCharge = stop.type === 'supercharger' || stop.type === 'destination-charger';
  const isSleep = stop.type === 'hotel';
  const initialTab: CustomiseTab = isCharge ? 'charger' : isSleep ? 'overnight' : 'things';
  const [tab, setTab] = React.useState<CustomiseTab>(initialTab);
  const [chargeMinutes, setChargeMinutes] = React.useState(stop.chargeMinutes || 30);
  const [durationMin, setDurationMin] = React.useState(stop.durationMin || stop.chargeMinutes || 30);
  const [pickedNearby, setPickedNearby] = React.useState<Set<string>>(new Set());
  const [chosenChargerId, setChosenChargerId] = React.useState<string>(stop.chargerOptions?.find(c => c.isCurrent)?.id || stop.chargerOptions?.[0]?.id || '');

  const tabs: { id: CustomiseTab; label: string; icon: IconComponent; count?: number; show: boolean }[] = [
    { id: 'charger', label: 'Charger', icon: Zap, count: stop.chargerOptions?.length, show: isCharge },
    { id: 'overnight', label: 'Overnight', icon: Bed, count: stop.alternatives?.length, show: isSleep },
    { id: 'duration', label: 'Duration', icon: Clock, show: true },
    { id: 'things', label: 'Things to do', icon: Footprints, count: stop.nearby?.length, show: (stop.nearby?.length ?? 0) > 0 },
    { id: 'detour', label: 'Detour', icon: Route, show: true },
  ];

  return (
    <ModalShell
      onClose={onClose}
      width={780}
      title={`Customise · ${stop.name}`}
      subtitle={stop.description?.slice(0, 80)}
      footer={
        <>
          <div className="text-[12px] num" style={{ color: 'var(--gd-text-3)' }}>
            <span style={{ color: 'var(--gd-text)' }}>Total stop:</span> {durationMin + pickedNearby.size * 8} min
          </div>
          <div className="flex-1" />
          <button
            onClick={onClose}
            className="h-9 px-3.5 rounded-lg text-[13px] inline-flex items-center gap-1.5"
            style={{ background: 'var(--gd-panel-2)', border: '1px solid var(--gd-border)', color: 'var(--gd-text)' }}
          >
            Cancel
          </button>
          <button
            onClick={() => onApply({ chargeMinutes, durationMin })}
            className="h-9 px-4 rounded-lg text-[13px] font-medium inline-flex items-center gap-1.5"
            style={{ background: 'var(--gd-red)', color: '#fff' }}
          >
            <Sparkles className="w-3.5 h-3.5" /> Apply changes
          </button>
        </>
      }
    >
      <div className="flex" style={{ minHeight: 460 }}>
        {/* Tab rail */}
        <div className="w-[200px] py-3.5 px-3 flex flex-col gap-0.5 flex-shrink-0" style={{ borderRight: '1px solid var(--gd-border)' }}>
          {tabs.filter(t => t.show).map(t => {
            const TI = t.icon;
            const active = tab === t.id;
            return (
              <button
                key={t.id}
                onClick={() => setTab(t.id)}
                className="flex items-center gap-2.5 px-3 py-2.5 rounded-lg text-left text-[13px] transition"
                style={{
                  background: active ? 'var(--gd-panel-2)' : 'transparent',
                  color: active ? 'var(--gd-text)' : 'var(--gd-text-2)',
                  fontWeight: active ? 500 : 400,
                }}
              >
                <TI size={14} className="" />
                <span className="flex-1">{t.label}</span>
                {t.count != null && (
                  <span
                    className="text-[10.5px] num px-1.5 py-px rounded"
                    style={{ background: 'rgba(255,255,255,0.05)', color: 'var(--gd-text-3)' }}
                  >
                    {t.count}
                  </span>
                )}
              </button>
            );
          })}
        </div>

        {/* Body */}
        <div className="flex-1 p-5 overflow-y-auto">
          {tab === 'charger' && (
            <CustomiseChargerTab
              options={stop.chargerOptions || []}
              chosenId={chosenChargerId}
              onChoose={setChosenChargerId}
            />
          )}
          {tab === 'overnight' && (
            <CustomiseOvernightTab alternatives={stop.alternatives || []} currentName={stop.name} />
          )}
          {tab === 'duration' && (
            <CustomiseDurationTab
              value={durationMin}
              onChange={setDurationMin}
              chargeMinutes={chargeMinutes}
              onChargeChange={setChargeMinutes}
              isCharge={isCharge}
              arrivePct={stop.estArrivalBattery}
            />
          )}
          {tab === 'things' && (
            <CustomiseThingsTab
              nearby={stop.nearby || []}
              picked={pickedNearby}
              onTogglePick={(name) => {
                const next = new Set(pickedNearby);
                if (next.has(name)) next.delete(name); else next.add(name);
                setPickedNearby(next);
              }}
            />
          )}
          {tab === 'detour' && <CustomiseDetourTab />}
        </div>
      </div>
    </ModalShell>
  );
}

function CustomiseChargerTab({ options, chosenId, onChoose }: {
  options: ChargerOption[]; chosenId: string; onChoose: (id: string) => void;
}) {
  if (options.length === 0) {
    return <EmptyTab text="No alternative chargers near this stop." />;
  }
  return (
    <div>
      <SectionLabel>Choose a charger</SectionLabel>
      <div className="flex flex-col gap-2">
        {options.map(o => {
          const sel = o.id === chosenId;
          return (
            <div
              key={o.id}
              onClick={() => onChoose(o.id)}
              className="p-3.5 rounded-[12px] cursor-pointer transition"
              style={{
                background: sel ? 'var(--gd-red-soft)' : 'var(--gd-panel)',
                border: `1px solid ${sel ? 'var(--gd-red-line)' : 'var(--gd-border)'}`,
              }}
            >
              <div className="flex items-start gap-3">
                <div
                  className="w-[18px] h-[18px] rounded-full grid place-items-center flex-shrink-0 mt-0.5"
                  style={{
                    border: `2px solid ${sel ? 'var(--gd-red)' : 'var(--gd-border-2)'}`,
                    background: sel ? 'var(--gd-red)' : 'transparent',
                  }}
                >
                  {sel && <div className="w-1.5 h-1.5 rounded-full bg-white" />}
                </div>
                <div className="flex-1 min-w-0">
                  <div className="flex items-center gap-2 flex-wrap">
                    <div className="text-[14px] font-medium">{o.name}</div>
                    {o.badge && (
                      <span
                        className="text-[9.5px] px-1.5 py-px rounded whitespace-nowrap"
                        style={{ color: o.isCurrent ? 'var(--gd-red)' : 'var(--gd-amber)', border: `1px solid ${o.isCurrent ? 'var(--gd-red-line)' : 'rgba(251,191,36,0.4)'}` }}
                      >
                        {o.badge}
                      </span>
                    )}
                  </div>
                  <div className="text-[11.5px] mt-1 flex gap-3.5 flex-wrap num" style={{ color: 'var(--gd-text-3)' }}>
                    <span className="inline-flex items-center gap-1.5">
                      <Zap className="w-3 h-3" style={{ color: 'var(--gd-green)' }} /> {o.kw} kW · {o.stalls} stalls
                    </span>
                    <span className="inline-flex items-center gap-1.5">
                      <Euro className="w-3 h-3" /> €{o.pricePerKwh.toFixed(2)}/kWh
                    </span>
                    {o.detourMin > 0 ? (
                      <span style={{ color: 'var(--gd-amber)' }}>+{o.detourMin} min detour</span>
                    ) : (
                      <span style={{ color: 'var(--gd-green)' }}>On route</span>
                    )}
                    {o.network && <span>{o.network}</span>}
                  </div>
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function CustomiseOvernightTab({ alternatives, currentName }: { alternatives: AlternativeStop[]; currentName: string }) {
  if (alternatives.length === 0) {
    return <EmptyTab text="No alternative overnight options were suggested for this stop." />;
  }
  return (
    <div>
      <SectionLabel>Choose where to sleep</SectionLabel>
      <div className="flex flex-col gap-2.5">
        <div
          className="p-3.5 rounded-[12px]"
          style={{ background: 'rgba(96,165,250,0.06)', border: '1px solid rgba(96,165,250,0.4)' }}
        >
          <div className="flex items-center gap-3">
            <div className="w-9 h-9 rounded-[9px] grid place-items-center" style={{ background: 'rgba(96,165,250,0.2)' }}>
              <Bed className="w-4 h-4" style={{ color: 'var(--gd-blue)' }} />
            </div>
            <div className="flex-1">
              <div className="text-[14px] font-medium">{currentName}</div>
              <div className="text-[11.5px] mt-0.5" style={{ color: 'var(--gd-text-3)' }}>Currently selected</div>
            </div>
            <span className="text-[10.5px]" style={{ color: 'var(--gd-blue)' }}>Current</span>
          </div>
        </div>
        {alternatives.map(a => (
          <div
            key={a.id}
            className="p-3.5 rounded-[12px]"
            style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)' }}
          >
            <div className="flex items-center gap-3">
              <div className="w-9 h-9 rounded-[9px] grid place-items-center" style={{ background: 'var(--gd-panel-2)' }}>
                <Bed className="w-4 h-4" style={{ color: 'var(--gd-text-2)' }} />
              </div>
              <div className="flex-1 min-w-0">
                <div className="text-[14px] font-medium truncate">{a.name}</div>
                <div className="text-[11.5px] mt-0.5" style={{ color: 'var(--gd-text-3)' }}>{a.reason || a.description}</div>
              </div>
              <button
                className="h-7 px-2.5 rounded-lg text-[11px]"
                style={{ background: 'var(--gd-panel-2)', border: '1px solid var(--gd-border)' }}
              >
                Choose
              </button>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function CustomiseDurationTab({ value, onChange, chargeMinutes, onChargeChange, isCharge, arrivePct }: {
  value: number; onChange: (v: number) => void;
  chargeMinutes: number; onChargeChange: (v: number) => void;
  isCharge: boolean; arrivePct?: number;
}) {
  const leavePct = arrivePct != null ? Math.min(100, arrivePct + Math.round(chargeMinutes * 1.4)) : null;
  const sliderPct = ((value - 10) / 110) * 100;
  return (
    <div>
      <SectionLabel>How long here?</SectionLabel>
      <div className="flex items-baseline gap-2 mb-5">
        <div className="text-[48px] font-light num" style={{ letterSpacing: '-0.03em' }}>{value}</div>
        <div className="text-[16px]" style={{ color: 'var(--gd-text-2)' }}>minutes</div>
        <div className="flex-1" />
        {isCharge && arrivePct != null && (
          <div className="text-[12px] num" style={{ color: 'var(--gd-text-3)' }}>
            arrive {arrivePct}% · leave <span style={{ color: 'var(--gd-green)' }}>{leavePct}%</span>
          </div>
        )}
      </div>
      <div className="relative h-10 mb-6">
        <input
          type="range" min={10} max={120} step={5} value={value}
          onChange={(e) => onChange(parseInt(e.target.value))}
          className="absolute inset-0 w-full h-10 opacity-0 z-10 cursor-pointer"
        />
        <div className="absolute top-[18px] left-0 right-0 h-1 rounded" style={{ background: 'var(--gd-border)' }} />
        <div className="absolute top-[18px] left-0 h-1 rounded" style={{ width: `${sliderPct}%`, background: 'var(--gd-red)' }} />
        <div
          className="absolute top-3 w-4 h-4 rounded-full"
          style={{
            left: `calc(${sliderPct}% - 8px)`,
            background: '#fff',
            border: '3px solid var(--gd-red)',
            boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
          }}
        />
        {[15, 30, 45, 60, 90, 120].map(t => (
          <div
            key={t}
            className="absolute top-7 text-[10px] num w-7 text-center"
            style={{ left: `calc(${((t - 10) / 110) * 100}% - 14px)`, color: 'var(--gd-text-3)' }}
          >
            {t}m
          </div>
        ))}
      </div>
      <SectionLabel>Presets</SectionLabel>
      <div className="grid grid-cols-3 gap-2">
        {[
          { label: 'Quick top-up', mins: 15, sub: 'just enough to reach next' },
          { label: 'Coffee + restroom', mins: 30, sub: 'short walk, restrooms' },
          { label: 'Sit-down lunch', mins: 60, sub: 'café or restaurant nearby' },
          { label: 'Explore the town', mins: 90, sub: 'old town loop + lunch' },
          { label: 'Full charge', mins: 120, sub: 'to 100% if needed' },
          { label: 'Skip & risk it', mins: 10, sub: 'bypass entirely', warn: true },
        ].map(p => (
          <button
            key={p.label}
            onClick={() => { onChange(p.mins); if (isCharge) onChargeChange(p.mins); }}
            className="p-3 rounded-[9px] text-left"
            style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)' }}
          >
            <div className="text-[12px] font-medium" style={{ color: p.warn ? 'var(--gd-amber)' : 'var(--gd-text)' }}>{p.label}</div>
            <div className="text-[10.5px] mt-0.5" style={{ color: 'var(--gd-text-3)' }}>{p.sub}</div>
          </button>
        ))}
      </div>
    </div>
  );
}

function CustomiseThingsTab({ nearby, picked, onTogglePick }: {
  nearby: NearbyPlace[]; picked: Set<string>; onTogglePick: (name: string) => void;
}) {
  if (nearby.length === 0) {
    return <EmptyTab text="No nearby places suggested for this stop." />;
  }
  return (
    <div>
      <SectionLabel>Pick what you want to do here</SectionLabel>
      <div className="grid grid-cols-2 gap-2">
        {nearby.map((n, i) => {
          const sel = picked.has(n.name);
          return (
            <div
              key={i}
              onClick={() => onTogglePick(n.name)}
              className="p-3 rounded-[10px] flex items-center gap-2.5 cursor-pointer transition"
              style={{
                background: sel ? 'var(--gd-red-soft)' : 'var(--gd-panel)',
                border: `1px solid ${sel ? 'var(--gd-red-line)' : 'var(--gd-border)'}`,
              }}
            >
              <div
                className="w-9 h-9 rounded-[9px] grid place-items-center flex-shrink-0 text-[15px] leading-none"
                style={{ background: sel ? 'var(--gd-red-soft)' : 'var(--gd-panel-2)' }}
              >
                {AMENITY_ICONS[n.icon] || '📍'}
              </div>
              <div className="flex-1 min-w-0">
                <div className="text-[12.5px] font-medium truncate">{n.name}</div>
                <div className="text-[10.5px] mt-0.5 truncate" style={{ color: 'var(--gd-text-3)' }}>{n.detail}</div>
              </div>
              <div
                className="w-[18px] h-[18px] rounded grid place-items-center flex-shrink-0"
                style={{
                  border: `1.5px solid ${sel ? 'var(--gd-red)' : 'var(--gd-border-2)'}`,
                  background: sel ? 'var(--gd-red)' : 'transparent',
                }}
              >
                {sel && <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth={3}><path d="M5 12l5 5 9-11" /></svg>}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function CustomiseDetourTab() {
  return (
    <div>
      <SectionLabel>Add a detour from this stop</SectionLabel>
      <div className="p-4 rounded-[12px] flex items-center gap-2" style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)' }}>
        <MapPin className="w-3.5 h-3.5" style={{ color: 'var(--gd-text-3)' }} />
        <input
          placeholder="Search for a town, attraction, restaurant…"
          className="flex-1 bg-transparent outline-none border-none text-[13px]"
          style={{ color: 'var(--gd-text)' }}
        />
      </div>
      <div className="text-[11px] mt-3" style={{ color: 'var(--gd-text-3)' }}>
        Search is mocked — use the "Add stop" button in the rail header to insert a detour from the popular-places list.
      </div>
    </div>
  );
}

function EmptyTab({ text }: { text: string }) {
  return (
    <div className="h-full grid place-items-center min-h-[260px]">
      <div className="text-center max-w-xs">
        <div className="text-[13px]" style={{ color: 'var(--gd-text-2)' }}>{text}</div>
      </div>
    </div>
  );
}

// ─── Add Detour overlay ──────────────────────────────────────────────────────
const POPULAR_DETOURS: { name: string; lat: number; lng: number; type: StopType; description: string; detourMin: number }[] = [
  { name: 'York Minster', lat: 53.962, lng: -1.082, type: 'attraction', description: 'Gothic cathedral · 12th century', detourMin: 25 },
  { name: 'Lake District National Park', lat: 54.4609, lng: -3.0886, type: 'viewpoint', description: 'England\'s largest national park', detourMin: 45 },
  { name: 'Tebay Services Farm Shop', lat: 54.4331, lng: -2.6049, type: 'restaurant', description: 'Independent farm shop on the M6', detourMin: 5 },
  { name: 'Beaune town centre', lat: 47.0241, lng: 4.8398, type: 'attraction', description: 'Cobbled lanes, mustard shop, hospices', detourMin: 4 },
  { name: 'Hadrian\'s Wall', lat: 55.0114, lng: -2.2854, type: 'attraction', description: 'Roman wall · UNESCO World Heritage', detourMin: 30 },
  { name: 'Bamburgh Castle', lat: 55.6086, lng: -1.7102, type: 'attraction', description: 'Coastal castle · Northumbrian icon', detourMin: 35 },
];

function AddDetourOverlay({
  onClose, onInsert,
}: {
  onClose: () => void;
  onInsert: (place: typeof POPULAR_DETOURS[number]) => void;
}) {
  const [query, setQuery] = React.useState('');
  React.useEffect(() => {
    const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  const filtered = POPULAR_DETOURS.filter(p => !query || p.name.toLowerCase().includes(query.toLowerCase()));
  return (
    <div
      onClick={onClose}
      className="fixed inset-0 flex justify-center items-start px-6 pt-[90px]"
      style={{ zIndex: 9999, background: 'rgba(5,5,8,0.6)', backdropFilter: 'blur(6px)' }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        className="w-[640px] max-w-full overflow-hidden"
        style={{
          background: 'var(--gd-bg-2)',
          border: '1px solid var(--gd-border-2)',
          borderRadius: 14,
          boxShadow: '0 24px 60px rgba(0,0,0,0.5)',
        }}
      >
        <div className="px-4 py-3.5 flex items-center gap-3" style={{ borderBottom: '1px solid var(--gd-border)' }}>
          <MapPin className="w-4 h-4" style={{ color: 'var(--gd-red)' }} />
          <input
            autoFocus
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder="Add a stop — city, attraction, charger, restaurant…"
            className="flex-1 bg-transparent outline-none text-[15px]"
            style={{ color: 'var(--gd-text)' }}
          />
          <kbd className="mono text-[10px] px-1.5 py-0.5 rounded" style={{ background: 'rgba(255,255,255,0.05)', color: 'var(--gd-text-3)', border: '1px solid var(--gd-border)' }}>esc</kbd>
        </div>
        <div className="p-2 max-h-[380px] overflow-y-auto">
          {filtered.length === 0 ? (
            <div className="text-[12px] p-4 text-center" style={{ color: 'var(--gd-text-3)' }}>
              No matches in the popular-detours list. Real search coming soon.
            </div>
          ) : (
            filtered.map(r => (
              <div
                key={r.name}
                className="px-3 py-2.5 rounded-[9px] flex items-center gap-3 hover:bg-white/5 transition"
              >
                <div className="w-8 h-8 rounded-[7px] grid place-items-center flex-shrink-0" style={{ background: 'var(--gd-panel-2)' }}>
                  <MapPin className="w-3.5 h-3.5" style={{ color: 'var(--gd-text-2)' }} />
                </div>
                <div className="flex-1 min-w-0">
                  <div className="text-[13px] font-medium truncate">{r.name}</div>
                  <div className="text-[11px] truncate" style={{ color: 'var(--gd-text-3)' }}>{r.description} · +{r.detourMin} min</div>
                </div>
                <button
                  onClick={() => { onInsert(r); onClose(); }}
                  className="h-7 px-2.5 rounded-lg text-[11px] inline-flex items-center gap-1.5"
                  style={{ background: 'var(--gd-red-soft)', color: 'var(--gd-red)', border: '1px solid var(--gd-red-line)' }}
                >
                  <Plus className="w-3 h-3" /> Insert
                </button>
              </div>
            ))
          )}
        </div>
      </div>
    </div>
  );
}

// ─── GPX Export modal ────────────────────────────────────────────────────────
type ExportFormat = 'gpx' | 'kml' | 'csv';

function generateGpx(itinerary: Itinerary, includeNotes: boolean, includeNearby: boolean): string {
  const allStops = itinerary.days.flatMap(d => d.stops).filter(s => typeof s.lat === 'number');
  const header = `<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="Grok Drive" xmlns="http://www.topografix.com/GPX/1/1">
  <metadata>
    <name>${escapeXml(allStops[0]?.name || 'Trip')} → ${escapeXml(allStops[allStops.length - 1]?.name || 'Destination')}</name>
    <desc>${itinerary.summary.totalDistanceKm} km · ${itinerary.summary.estDriveHours}h drive · ${itinerary.summary.estChargeHours}h charging</desc>
    <time>${new Date().toISOString()}</time>
  </metadata>
  <trk>
    <name>Grok Drive route</name>
    <trkseg>`;
  const points = allStops.map(s => {
    const inner: string[] = [
      `      <name>${escapeXml(s.name)}</name>`,
      `      <type>${escapeXml(s.type)}</type>`,
    ];
    if (includeNotes && s.description) inner.push(`      <desc>${escapeXml(s.description)}</desc>`);
    if (includeNearby && s.nearby && s.nearby.length > 0) {
      inner.push(`      <cmt>Nearby: ${escapeXml(s.nearby.slice(0, 3).map(n => n.name).join(', '))}</cmt>`);
    }
    return `    <trkpt lat="${s.lat.toFixed(5)}" lon="${s.lng.toFixed(5)}">\n${inner.join('\n')}\n    </trkpt>`;
  }).join('\n');
  return `${header}\n${points}\n    </trkseg>\n  </trk>\n</gpx>`;
}

function generateKml(itinerary: Itinerary): string {
  const allStops = itinerary.days.flatMap(d => d.stops).filter(s => typeof s.lat === 'number');
  const placemarks = allStops.map(s => `    <Placemark>
      <name>${escapeXml(s.name)}</name>
      <description>${escapeXml(s.description || s.type)}</description>
      <Point><coordinates>${s.lng.toFixed(5)},${s.lat.toFixed(5)},0</coordinates></Point>
    </Placemark>`).join('\n');
  const lineCoords = allStops.map(s => `${s.lng.toFixed(5)},${s.lat.toFixed(5)},0`).join(' ');
  return `<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Document>
    <name>Grok Drive route</name>
${placemarks}
    <Placemark><name>Route</name><LineString><coordinates>${lineCoords}</coordinates></LineString></Placemark>
  </Document>
</kml>`;
}

function generateCsv(itinerary: Itinerary): string {
  const rows = [
    'day,order,name,type,lat,lng,charge_minutes,arrive_battery_pct,combo,description',
  ];
  for (const d of itinerary.days) {
    for (const s of d.stops) {
      rows.push([
        d.day, s.order, q(s.name), s.type, s.lat ?? '', s.lng ?? '',
        s.chargeMinutes ?? '', s.estArrivalBattery ?? '',
        q(s.combo || ''), q(s.description || ''),
      ].join(','));
    }
  }
  return rows.join('\n');
  function q(v: string) { return `"${v.replace(/"/g, '""')}"`; }
}

function escapeXml(s: string): string {
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}

function downloadFile(filename: string, content: string, mime: string) {
  const blob = new Blob([content], { type: mime });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

function GpxExportModal({ itinerary, onClose }: { itinerary: Itinerary; onClose: () => void }) {
  const [format, setFormat] = React.useState<ExportFormat>('gpx');
  const [includeNotes, setIncludeNotes] = React.useState(true);
  const [includeNearby, setIncludeNearby] = React.useState(false);
  const allStops = itinerary.days.flatMap(d => d.stops).filter(s => typeof s.lat === 'number');

  const content = format === 'gpx'
    ? generateGpx(itinerary, includeNotes, includeNearby)
    : format === 'kml'
      ? generateKml(itinerary)
      : generateCsv(itinerary);

  const baseName = `grok-drive-${(allStops[0]?.name || 'trip').toLowerCase().replace(/\s+/g, '-')}-${(allStops[allStops.length - 1]?.name || 'route').toLowerCase().replace(/\s+/g, '-')}`;
  const filename = `${baseName}.${format}`;

  const handleDownload = () => {
    const mime = format === 'gpx' ? 'application/gpx+xml' : format === 'kml' ? 'application/vnd.google-earth.kml+xml' : 'text/csv';
    downloadFile(filename, content, mime);
    toast.success(`Downloaded ${filename}`);
  };

  if (allStops.length === 0) {
    return (
      <ModalShell
        onClose={onClose}
        width={520}
        title="Export trip"
        footer={
          <>
            <div className="flex-1" />
            <button
              onClick={onClose}
              className="h-9 px-3.5 rounded-lg text-[13px]"
              style={{ background: 'var(--gd-panel-2)', border: '1px solid var(--gd-border)' }}
            >
              Close
            </button>
          </>
        }
      >
        <div className="p-6 text-center">
          <div className="text-[13px]" style={{ color: 'var(--gd-text-2)' }}>
            Plan a trip first — there's nothing to export yet.
          </div>
        </div>
      </ModalShell>
    );
  }

  return (
    <ModalShell
      onClose={onClose}
      width={820}
      title="Export trip"
      subtitle="Send this trip to your car, phone, or another planning app"
      footer={
        <>
          <div className="text-[12px] num" style={{ color: 'var(--gd-text-3)' }}>
            {content.split('\n').length} lines · ~{(content.length / 1024).toFixed(1)} KB
          </div>
          <div className="flex-1" />
          <button
            onClick={() => { navigator.clipboard.writeText(content); toast.success('Copied to clipboard'); }}
            className="h-9 px-3.5 rounded-lg text-[13px] inline-flex items-center gap-1.5"
            style={{ background: 'var(--gd-panel-2)', border: '1px solid var(--gd-border)' }}
          >
            <Share2 className="w-3.5 h-3.5" /> Copy
          </button>
          <button
            onClick={handleDownload}
            className="h-9 px-4 rounded-lg text-[13px] font-medium inline-flex items-center gap-1.5"
            style={{ background: 'var(--gd-red)', color: '#fff' }}
          >
            <Download className="w-3.5 h-3.5" /> Download .{format}
          </button>
        </>
      }
    >
      <div className="flex" style={{ minHeight: 460 }}>
        <div className="w-[260px] py-5 px-5 flex flex-col gap-4 flex-shrink-0" style={{ borderRight: '1px solid var(--gd-border)' }}>
          <div>
            <SectionLabel>Format</SectionLabel>
            {([
              { id: 'gpx', name: 'GPX', detail: 'Tesla, car nav, ABRP, most apps' },
              { id: 'kml', name: 'KML', detail: 'Google Earth / Google Maps' },
              { id: 'csv', name: 'CSV', detail: 'Spreadsheet of stops' },
            ] as { id: ExportFormat; name: string; detail: string }[]).map(f => {
              const sel = format === f.id;
              return (
                <div
                  key={f.id}
                  onClick={() => setFormat(f.id)}
                  className="px-3 py-2.5 rounded-lg mb-1 cursor-pointer"
                  style={{
                    background: sel ? 'var(--gd-red-soft)' : 'transparent',
                    border: `1px solid ${sel ? 'var(--gd-red-line)' : 'transparent'}`,
                  }}
                >
                  <div className="text-[12.5px] font-medium">{f.name}</div>
                  <div className="text-[10.5px]" style={{ color: 'var(--gd-text-3)' }}>{f.detail}</div>
                </div>
              );
            })}
          </div>
          {format === 'gpx' && (
            <div>
              <SectionLabel>Include</SectionLabel>
              <CheckRow label="Stop notes & descriptions" value={includeNotes} onChange={setIncludeNotes} />
              <CheckRow label="Nearby places" value={includeNearby} onChange={setIncludeNearby} />
            </div>
          )}
        </div>
        <div className="flex-1 relative overflow-hidden" style={{ background: 'var(--gd-bg)' }}>
          <div className="px-4 py-2.5 flex items-center gap-2.5 text-[11px]" style={{ borderBottom: '1px solid var(--gd-border)' }}>
            <span className="mono" style={{ color: 'var(--gd-text-3)' }}>{filename}</span>
            <div className="flex-1" />
            <span className="num" style={{ color: 'var(--gd-text-3)' }}>{allStops.length} waypoints</span>
          </div>
          <pre
            className="m-0 p-4 overflow-auto mono text-[11px] leading-[1.5]"
            style={{ color: 'var(--gd-text-2)', maxHeight: 420 }}
          >
            {content}
          </pre>
        </div>
      </div>
    </ModalShell>
  );
}

// ─── Travel dates panel ──────────────────────────────────────────────────────
function TravelDatesPanel({
  open, anchorRect, value, onChange, onClose, onApply,
}: {
  open: boolean;
  anchorRect: DOMRect | null;
  value: TravelDates;
  onChange: (v: TravelDates) => void;
  onClose: () => void;
  onApply: () => void;
}) {
  React.useEffect(() => {
    const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  if (!open || !anchorRect) return null;
  const panelWidth = 380;
  const left = Math.max(12, Math.min(window.innerWidth - panelWidth - 12, anchorRect.right - panelWidth));
  const top = anchorRect.bottom + 8;
  const nights = nightsBetween(value.outbound, value.return);
  const today = new Date().toISOString().slice(0, 10);

  return (
    <>
      <div className="fixed inset-0" style={{ zIndex: 9998 }} onClick={onClose} />
      <div
        className="fixed overflow-hidden"
        style={{
          zIndex: 9999,
          left, top, width: panelWidth,
          background: 'rgba(20,20,24,0.96)',
          backdropFilter: 'blur(18px)',
          border: '1px solid var(--gd-border-2)',
          borderRadius: 14,
          boxShadow: '0 24px 60px rgba(0,0,0,0.5)',
        }}
      >
        <div className="px-4 py-3 flex items-center justify-between" style={{ borderBottom: '1px solid var(--gd-border)' }}>
          <div>
            <div className="text-[13px] font-medium">Travel dates</div>
            <div className="text-[11px]" style={{ color: 'var(--gd-text-3)' }}>
              Sharpens crossing, ferry and hotel pricing
            </div>
          </div>
          {nights != null && (
            <div className="text-[11px] num px-2 py-0.5 rounded-full" style={{ background: 'var(--gd-red-soft)', color: 'var(--gd-red)' }}>
              {nights} night{nights === 1 ? '' : 's'}
            </div>
          )}
        </div>

        <div className="px-4 py-3 space-y-3">
          <div>
            <div className="text-[10.5px] uppercase tracking-wider mb-1.5" style={{ color: 'var(--gd-text-3)' }}>Outbound</div>
            <input
              type="date"
              min={today}
              value={value.outbound || ''}
              onChange={(e) => onChange({ ...value, outbound: e.target.value || null })}
              className="w-full text-[13px] px-3 py-2 rounded-lg outline-none"
              style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)', color: 'var(--gd-text)', colorScheme: 'dark' }}
            />
          </div>
          <div>
            <div className="text-[10.5px] uppercase tracking-wider mb-1.5 flex items-center justify-between" style={{ color: 'var(--gd-text-3)' }}>
              <span>Return</span>
              {value.return && (
                <button className="text-[10.5px] hover:underline" onClick={() => onChange({ ...value, return: null })}>
                  Make one-way
                </button>
              )}
            </div>
            <input
              type="date"
              min={value.outbound || today}
              value={value.return || ''}
              onChange={(e) => onChange({ ...value, return: e.target.value || null })}
              className="w-full text-[13px] px-3 py-2 rounded-lg outline-none"
              style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)', color: 'var(--gd-text)', colorScheme: 'dark' }}
            />
          </div>
          <div>
            <div className="text-[10.5px] uppercase tracking-wider mb-1.5 flex items-center justify-between" style={{ color: 'var(--gd-text-3)' }}>
              <span>Travellers</span>
              <Users className="w-3 h-3" />
            </div>
            <div className="flex items-center gap-2">
              {[1, 2, 3, 4, 5].map(n => {
                const selected = (value.travellers ?? 2) === n;
                return (
                  <button
                    key={n}
                    onClick={() => onChange({ ...value, travellers: n })}
                    className="flex-1 h-9 rounded-lg text-[12.5px] num transition"
                    style={{
                      background: selected ? 'var(--gd-red)' : 'var(--gd-panel)',
                      border: `1px solid ${selected ? 'var(--gd-red)' : 'var(--gd-border)'}`,
                      color: selected ? '#fff' : 'var(--gd-text-2)',
                    }}
                  >
                    {n}
                  </button>
                );
              })}
            </div>
          </div>
        </div>

        <div className="px-4 py-3 flex items-center gap-2" style={{ borderTop: '1px solid var(--gd-border)' }}>
          <button
            onClick={() => { onChange({ outbound: null, return: null, travellers: value.travellers }); }}
            className="flex-1 h-9 text-[12px] rounded-lg"
            style={{ background: 'transparent', border: '1px solid var(--gd-border)', color: 'var(--gd-text-2)' }}
          >
            Clear
          </button>
          <button
            onClick={onApply}
            disabled={!value.outbound}
            className="flex-[2] h-9 text-[12px] rounded-lg disabled:opacity-40"
            style={{ background: 'var(--gd-red)', color: '#fff' }}
          >
            {value.outbound ? 'Apply & re-quote' : 'Pick an outbound date'}
          </button>
        </div>
      </div>
    </>
  );
}

// ─── Crossing swap block ─────────────────────────────────────────────────────
function CrossingSwapBlock({ options, onPick }: { options: CrossingOption[]; onPick: (c: CrossingOption) => void }) {
  if (options.length === 0) return null;
  const current = options.find(o => o.isCurrent) || options[0];
  return (
    <div className="mt-2 rounded-xl overflow-hidden" style={{ background: 'var(--gd-panel)', border: '1px solid var(--gd-border)' }}>
      <div className="px-3 py-2 flex items-center gap-2" style={{ borderBottom: '1px solid var(--gd-border)' }}>
        <Ship className="w-3.5 h-3.5" style={{ color: 'var(--gd-blue)' }} />
        <div className="text-[10.5px] uppercase tracking-wider" style={{ color: 'var(--gd-text-3)' }}>
          Pick your crossing — {options.length} option{options.length === 1 ? '' : 's'}
        </div>
      </div>
      <div className="divide-y" style={{ borderColor: 'var(--gd-border)' }}>
        {options.map(opt => {
          const isPicked = opt.id === current.id;
          const deltaMin = formatDelta(opt.detourMin, 'min');
          const deltaKm = formatDelta(opt.detourKm, 'km');
          return (
            <button
              key={opt.id}
              onClick={() => !isPicked && onPick(opt)}
              disabled={isPicked}
              className="w-full text-left px-3 py-2.5 transition hover:bg-white/[0.03] disabled:cursor-default"
              style={{
                background: isPicked ? 'var(--gd-blue-soft, rgba(80,140,255,0.08))' : 'transparent',
                borderTop: 'none',
                borderBottom: '1px solid var(--gd-border)',
              }}
            >
              <div className="flex items-center gap-2 mb-1">
                <div className="text-[12.5px] font-medium flex-1" style={{ color: 'var(--gd-text)' }}>
                  {opt.operator}
                </div>
                {opt.badge && (
                  <span
                    className="text-[9.5px] font-semibold tracking-wide uppercase px-1.5 py-0.5 rounded"
                    style={{
                      background: isPicked ? 'var(--gd-blue, #3b82f6)' : 'var(--gd-panel-2)',
                      color: isPicked ? '#fff' : 'var(--gd-text-2)',
                    }}
                  >
                    {opt.badge}
                  </span>
                )}
                {isPicked && (
                  <span className="text-[9.5px] font-semibold uppercase px-1.5 py-0.5 rounded" style={{ background: 'var(--gd-blue, #3b82f6)', color: '#fff' }}>
                    Current
                  </span>
                )}
              </div>
              <div className="text-[11px] mb-1.5" style={{ color: 'var(--gd-text-2)' }}>
                {opt.fromPort} → {opt.toPort}
              </div>
              <div className="flex items-center gap-3 text-[11px] num" style={{ color: 'var(--gd-text-3)' }}>
                <span><Clock className="w-3 h-3 inline mr-0.5" />{formatDuration(opt.durationMin)}</span>
                <span><Euro className="w-3 h-3 inline mr-0.5" />€{Math.round(opt.priceEur)}</span>
                {opt.frequency && <span className="truncate">· {opt.frequency}</span>}
              </div>
              {(deltaMin || deltaKm) && !isPicked && (
                <div className="mt-1 text-[10.5px] num" style={{ color: 'var(--gd-text-3)' }}>
                  Trip impact: {deltaMin || '±0 min'} · {deltaKm || '±0 km'}
                </div>
              )}
              {opt.bookingUrl && (
                <a
                  href={opt.bookingUrl} target="_blank" rel="noreferrer"
                  onClick={(e) => e.stopPropagation()}
                  className="mt-1 inline-flex items-center gap-1 text-[10.5px] hover:underline"
                  style={{ color: 'var(--gd-blue, #5b8bff)' }}
                >
                  Book <ExternalLink className="w-2.5 h-2.5" />
                </a>
              )}
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ─── Vehicle selector panel ──────────────────────────────────────────────────
function VehicleSelectorPanel({
  open, anchorRect, selected, onSelect, onClose,
}: {
  open: boolean;
  anchorRect: DOMRect | null;
  selected: Vehicle;
  onSelect: (v: Vehicle) => void;
  onClose: () => void;
}) {
  const [chargePct, setChargePct] = React.useState(80);
  React.useEffect(() => {
    const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  if (!open || !anchorRect) return null;
  const panelWidth = 460;
  const left = Math.max(12, Math.min(window.innerWidth - panelWidth - 12, anchorRect.right - panelWidth));
  const top = anchorRect.bottom + 8;

  const pick = (m: VehicleModel, t: VehicleTrim) => {
    onSelect({
      modelId: m.id, trimId: t.id, name: m.name, trim: t.name,
      rangeKm: t.rangeKm, kw: t.kw, sec0to60: t.sec0to60, topKmh: t.topKmh, badge: t.badge,
    });
    onClose();
  };

  return (
    <>
      <div className="fixed inset-0" style={{ zIndex: 9998 }} onClick={onClose} />
      <div
        className="fixed overflow-hidden"
        style={{
          zIndex: 9999,
          left, top, width: panelWidth, maxHeight: '78vh',
          background: 'rgba(20,20,24,0.96)',
          backdropFilter: 'blur(18px)',
          border: '1px solid var(--gd-border-2)',
          borderRadius: 14,
          boxShadow: '0 24px 60px rgba(0,0,0,0.5)',
          display: 'flex', flexDirection: 'column',
        }}
      >
        {/* Header with starting-charge slider */}
        <div className="px-4 py-3.5 flex-shrink-0" style={{ borderBottom: '1px solid var(--gd-border)' }}>
          <div className="flex items-center justify-between mb-2.5">
            <div className="text-[11px] uppercase tracking-wider" style={{ color: 'var(--gd-text-3)' }}>
              Starting charge
            </div>
            <div className="text-[12px] num" style={{ color: 'var(--gd-text)' }}>
              {chargePct}% · <span className="num" style={{ color: 'var(--gd-green)' }}>
                {Math.round(selected.rangeKm * chargePct / 100)} km
              </span>
            </div>
          </div>
          <div className="relative h-4">
            <input
              type="range" min={10} max={100} step={5} value={chargePct}
              onChange={(e) => setChargePct(parseInt(e.target.value))}
              className="absolute inset-0 w-full opacity-0 cursor-pointer z-10"
            />
            <div className="absolute top-1.5 left-0 right-0 h-1 rounded" style={{ background: 'var(--gd-border)' }} />
            <div className="absolute top-1.5 left-0 h-1 rounded" style={{ width: `${chargePct}%`, background: 'var(--gd-green)' }} />
            <div
              className="absolute top-0 w-4 h-4 rounded-full"
              style={{ left: `calc(${chargePct}% - 8px)`, background: '#fff', border: '3px solid var(--gd-green)' }}
            />
          </div>
        </div>

        {/* Model + trim list */}
        <div className="overflow-y-auto px-2 py-2 flex-1 min-h-0">
          {TESLA_MODELS.map(m => (
            <ModelGroup
              key={m.id}
              model={m}
              selected={selected}
              chargePct={chargePct}
              onPick={(t) => pick(m, t)}
            />
          ))}
        </div>
      </div>
    </>
  );
}

function ModelGroup({ model, selected, chargePct, onPick }: {
  model: VehicleModel; selected: Vehicle; chargePct: number; onPick: (t: VehicleTrim) => void;
}) {
  const isExpanded = model.id === selected.modelId;
  const [open, setOpen] = React.useState(isExpanded);
  return (
    <div className="mb-1.5">
      <button
        onClick={() => setOpen(o => !o)}
        className="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/[0.04] transition"
      >
        <div className="text-[14px] font-medium flex-1 text-left">{model.name}</div>
        <div className="text-[11px]" style={{ color: 'var(--gd-text-3)' }}>{model.description}</div>
        {open ? <ChevronUp className="w-3.5 h-3.5" style={{ color: 'var(--gd-text-3)' }} /> : <ChevronDown className="w-3.5 h-3.5" style={{ color: 'var(--gd-text-3)' }} />}
      </button>
      {open && (
        <div className="pl-3 pr-1 pb-1 space-y-1">
          {model.trims.map(t => {
            const isSel = model.id === selected.modelId && t.id === selected.trimId;
            const kmNow = Math.round(t.rangeKm * chargePct / 100);
            return (
              <button
                key={t.id}
                onClick={() => onPick(t)}
                className="w-full text-left p-2.5 rounded-lg transition"
                style={{
                  background: isSel ? 'var(--gd-red-soft)' : 'transparent',
                  border: `1px solid ${isSel ? 'var(--gd-red-line)' : 'var(--gd-border)'}`,
                }}
              >
                <div className="flex items-center gap-2 flex-wrap">
                  <div className="text-[13px] font-medium">{t.name}</div>
                  {t.badge && (
                    <span
                      className="text-[9px] font-semibold tracking-wider px-1.5 py-0.5 rounded-full"
                      style={{
                        color: t.badge === 'Performance' ? 'var(--gd-amber)' : 'var(--gd-blue)',
                        border: `1px solid ${t.badge === 'Performance' ? 'rgba(251,191,36,0.4)' : 'rgba(96,165,250,0.4)'}`,
                      }}
                    >
                      {t.badge}
                    </span>
                  )}
                  <div className="flex-1" />
                  {isSel && (
                    <span className="text-[9.5px] font-semibold tracking-wider" style={{ color: 'var(--gd-red)' }}>SELECTED</span>
                  )}
                </div>
                <div className="flex gap-3.5 mt-1.5 text-[10.5px] num" style={{ color: 'var(--gd-text-3)' }}>
                  <span>{t.rangeKm} km full</span>
                  <span style={{ color: 'var(--gd-green)' }}>{kmNow} km now</span>
                  <span>0–100 in {t.sec0to60}s</span>
                  <span>{t.kw} kW peak</span>
                </div>
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

function CheckRow({ label, value, onChange }: { label: string; value: boolean; onChange: (v: boolean) => void }) {
  return (
    <label className="flex items-center gap-2 py-1.5 text-[12px] cursor-pointer" style={{ color: 'var(--gd-text-2)' }}>
      <div
        onClick={(e) => { e.preventDefault(); onChange(!value); }}
        className="w-4 h-4 rounded grid place-items-center"
        style={{
          border: `1.5px solid ${value ? 'var(--gd-text)' : 'var(--gd-border-2)'}`,
          background: value ? 'var(--gd-text)' : 'transparent',
        }}
      >
        {value && <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="#0a0a0c" strokeWidth={3.5}><path d="M5 12l5 5 9-11" /></svg>}
      </div>
      <span>{label}</span>
    </label>
  );
}
