'use client'

import { useEffect, useRef, useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { toast } from 'sonner'
import { getAckedWarnings } from '@/lib/ack'
import { cityState, formatFullDate, formatInches, formatWeight, localToday } from '@/lib/format'
import { stateName } from '@/lib/domain/states'
import { getDemoCurrentState } from '@/lib/demo/current-state'
import { DEMO_ROUTE_TYPES } from '@/lib/demo/route-types'
import { DimensionsEditor } from '@/components/app/dimensions-editor'
import { StateNotesDialog } from '@/components/app/state-notes-dialog'
import { pilotAccessCoversState } from '@/lib/domain/pilot'
import { demoPilotContacts, demoPilotPaperwork, isDemoPilotState, type DemoPilotContact } from '@/lib/demo/pilot-cars'
import { PaperworkBlock, useViewerPaperworkAccess, type MyPilotEntry } from '@/components/app/pilot-tools/my-pilot-tab'
import { LockedProFeature } from '@/components/app/pilot-tools/locked-pro'
import { canUseBusinessTools, PAPERWORK_TRIAL_DAYS, PLAN_NAMES_FOR_PREVIEW, type PaperworkAccess, type PlanName } from '@/lib/domain/invoicing'
import { ExpenseTracker } from '@/components/app/pilot-tools/expense-tracker'
import { useViewerPlanPreview } from '@/lib/demo/invoicing-store'
import { buildPaperworkPdf, openPdf } from '@/lib/demo/invoice-pdf'
import { ChatPanel } from '@/components/app/chat-panel'
import { CompletionPrompt } from '@/components/app/trip-completion'
import { ActivateTripButton } from '@/components/app/trip-activation'
import {
  CartBar, RoutePurchaseControl, useRouteCart, type RouteCart, type RouteCredits,
} from '@/components/app/route-purchase'
import { RequestNewPermitButton } from '@/components/app/permit-reorder'
import {
  requesterText, resolveRequester, ROUTE_CHIP, RouteFormatChips,
} from '@/components/app/route-formats'
import {
  EMPTY_COMPLETION,
  isActiveStatus,
  resolveStatusForUser,
  shouldPromptCompletion,
  STATUS_LABELS,
  type TripCompletion,
} from '@/lib/domain/status'
import type {
  ChatMessage, Trip, TripParticipant, TripRole, TripStatus, TripUnit, TripWarning,
} from '@/types/db'
import type { PermitLite, RouteRequestLite } from '@/lib/data/dashboard-data'

/**
 * Driver mobile experience — a swipeable trip command screen (carousel effect,
 * per the meetings). Menu per the 2026-09-04 driver feedback: "the first one
 * should be my trips… Active trip… agent… the tab for My unit." The Active
 * Trip pane shows a section per state, in path order, with completed states
 * pushed to the bottom.
 *
 * The separate Routes tab was removed on 2026-09-07 once routes moved into the
 * Active Trip state sections — Nash: "the tab routes is not necessary, because
 * it will be inside the active trip."
 */

const PANES = ['My Trips', 'Active Trip', 'Agent', 'My Unit'] as const
const PANE_ICONS = ['🗂️', '🚛', '💬', '🚚'] as const
const MY_TRIPS_PANE = 0
const ACTIVE_TRIP_PANE = 1
const AGENT_PANE = 2

/**
 * Every trip link inside the driver interface carries `?view=driver` (Task 78).
 *
 * The trip page decides which surface to render from the `?view=` override
 * first, then the person's role on that trip. A broker account previewing the
 * driver dashboard (`/dashboard?view=driver`) that tapped a plain `/trips/[id]`
 * lost the override and landed in the three-column desktop workspace — Nash:
 * "it opens the trips view for the web browser, not for my driver… it should
 * open the trip workspace of the driver interface." For a real driver account
 * the parameter is harmless: it resolves to the same driver view.
 */
const driverTripHref = (tripNumber: string) => `/ct-trip-workspace/${tripNumber}`

const SUGGESTED = [
  'Can I move right now?',
  'Do I need escorts here?',
  'What are my curfews?',
  'Is my permit valid today?',
]

const ROUTE_STATUS_LABELS: Record<string, string> = {
  requested: 'Route requested',
  in_progress: 'Route in progress',
  fulfilled: '✓ Route ready',
}

export function DriverView({
  trips: allTrips,
  warnings: allWarnings,
  permits,
  userId,
  routeRequests,
  units,
  permitUrls,
  focusTrip,
  chat,
  participants,
  myRole,
  initialTab,
  initialPermitId,
  completions,
  shareChat,
  landingPane = 'active',
  routeCredits,
  chatLanguages,
  primaryLanguage,
  pilotAccess = {},
  accountCreatedAt,
  isInternal = false,
  myName,
}: {
  trips: Trip[]
  warnings: TripWarning[]
  permits: PermitLite[]
  userId: string
  routeRequests: RouteRequestLite[]
  units: TripUnit[]
  permitUrls: Record<string, string>
  /** Agent pane: the trip's conversation, rendered inline for the driver. */
  chat: ChatMessage[]
  participants: TripParticipant[]
  myRole: TripRole | null
  /** `?tab=chat` opens straight on the Agent pane. */
  initialTab?: string
  /** `?permit=<id>` scopes the agent chat to one permit. */
  initialPermitId?: string
  /** Per-trip personal completion state, keyed by trip id (2026-09-07). */
  completions: Record<string, TripCompletion>
  /** This user's "share my questions" choice on the focused trip. */
  shareChat?: boolean
  /**
   * Which pane opens first (2026-09-07, Task 72). Nash: "By default, the
   * driver dashboard should use the My Trips as the landing… if I click on any
   * of the trips from this list… it should switch me into the tab Active Trip
   * with the details of that trip that I selected." So /dashboard lands on
   * My Trips and /trips/[id] (a tapped trip) lands on Active Trip.
   */
  landingPane?: 'my-trips' | 'active'
  /** Prepaid Express Route credits — real, decrementing (Task 69). */
  routeCredits: RouteCredits
  /** Agent chat languages from the profile (Task 71). */
  chatLanguages: string[]
  primaryLanguage: string
  /**
   * Access each pilot participant was given, keyed by lower-cased email
   * (from the invite history). Drives the "Pilot car attached" mark and the
   * pilot contacts on each state card (Nash, 2026-09-12).
   */
  pilotAccess?: Record<string, string>
  /** Account sign-up date — drives the 90-day pilot-paperwork window (Nash, 2026-09-13). */
  accountCreatedAt?: string
  /** Internal admin — shows the plan preview switch on the expenses card. */
  isInternal?: boolean
  /** The driver's display name, recorded on each expense. */
  myName?: string
  /**
   * Render this exact trip instead of the driver's first active one — set when
   * the driver opens a trip from My Trips (2026-09-07). Nash: "from my trips,
   * when I click on the trip, send me to the active trip page… Open it for the
   * driver view." Works for completed trips too, so a driver can still open a
   * previous trip and ask the agent about it.
   */
  focusTrip?: Trip
}) {
  const router = useRouter()
  // Per-user acknowledged warnings stay hidden for this user (Task 10).
  const [acked, setAcked] = useState<Set<string>>(new Set())
  useEffect(() => setAcked(getAckedWarnings(userId)), [userId])
  const warnings = allWarnings.filter((w) => !acked.has(w.id))
  // An admin can open a trip they are not a participant of, so the focused
  // trip is not guaranteed to be in the visible list — make sure it is, or the
  // My Trips pane would not show the trip the driver is currently looking at.
  const trips =
    focusTrip && !allTrips.some((t) => t.id === focusTrip.id)
      ? [focusTrip, ...allTrips]
      : allTrips
  // Completion is PERSONAL (2026-09-07): a trip another participant finished
  // stays active for this driver until he marks it himself.
  const statusFor = (t: Trip) => resolveStatusForUser(t.status, completions[t.id])
  const activeTrips = trips.filter((t) => isActiveStatus(statusFor(t)))
  const previous = trips.filter((t) => !isActiveStatus(statusFor(t)))
  const trip = focusTrip ?? activeTrips[0] ?? null
  // Route shopping cart — per user, per trip (Task 69). Called before the
  // early return below so the hook order never changes.
  const cart = useRouteCart(trip?.id ?? 'none', userId)

  const trackRef = useRef<HTMLDivElement>(null)
  const landingIndex =
    initialTab === 'chat'
      ? AGENT_PANE
      : landingPane === 'my-trips'
        ? MY_TRIPS_PANE
        : ACTIVE_TRIP_PANE
  const [pane, setPane] = useState(landingIndex)
  // Permit-scoped agent chat, driven by the state sections' "Ask your agent".
  const [scopedPermitId, setScopedPermitId] = useState<string | null>(
    initialPermitId ?? null,
  )

  // Land on the Active Trip pane without a visible scroll animation. The
  // track has no CSS scroll-behavior (nav clicks pass an explicit `smooth`
  // option instead — see goTo), so a direct scrollLeft assignment here is
  // already instant; no style toggling needed. A single requestAnimationFrame
  // is not consistently enough — layout can still be settling (e.g. a font
  // swap) one frame after mount, landing scrollLeft between two panes. Wait
  // two frames, then read the target pane's OWN live offsetLeft rather than
  // a computed clientWidth*index — offsetLeft reflects whatever the layout
  // actually settled on.
  useEffect(() => {
    let raf2 = 0
    const raf1 = requestAnimationFrame(() => {
      raf2 = requestAnimationFrame(() => {
        const el = trackRef.current
        const pane = el?.children[landingIndex] as HTMLElement | undefined
        if (!el || !pane) return
        // Position of the pane INSIDE the track (Task 79). `offsetLeft` alone
        // is measured from the nearest positioned ancestor — on a desktop
        // browser the phone frame sits centred in a wide window, so it added
        // the frame's left offset and overshot to a later pane (Nash: "he
        // opens my unit tab"). On a real phone the offset is ~0, which is why
        // it looked right there.
        el.scrollLeft = pane.getBoundingClientRect().left - el.getBoundingClientRect().left + el.scrollLeft
      })
    })
    return () => {
      cancelAnimationFrame(raf1)
      cancelAnimationFrame(raf2)
    }
  }, [landingIndex])

  // The track is only as tall as the pane being shown (Task 74, second pass).
  // A flex row is always as tall as its TALLEST child whatever the alignment,
  // so My Trips, Agent and My Unit still carried the Active Trip pane's height
  // as dead space below their content. Nash: "there is a lot of dead space
  // under the chat… under the trailer info… My trips also. Only Active Trip
  // has the good view." Measured with a ResizeObserver so it follows content
  // changes (expanding a state, sending a message) without a re-render.
  const [trackHeight, setTrackHeight] = useState<number | undefined>(undefined)
  useEffect(() => {
    const el = trackRef.current
    if (!el || typeof ResizeObserver === 'undefined') return
    const measure = () => {
      const current = el.children[pane] as HTMLElement | undefined
      if (current) setTrackHeight(current.offsetHeight)
    }
    const observer = new ResizeObserver(measure)
    for (const child of Array.from(el.children)) observer.observe(child)
    return () => observer.disconnect()
  }, [pane])

  if (!trip) {
    return (
      <main className="mx-auto max-w-md px-4 py-10 text-center">
        <h1 className="text-xl font-bold tracking-tight">My trips</h1>
        <div className="mt-6 rounded-2xl border border-dashed p-10 text-sm text-neutral-500">
          No active trips. When a dispatcher invites you, the trip appears here.
        </div>
        {previous.length > 0 && <PreviousTrips trips={previous} />}
      </main>
    )
  }

  const tripPermits = permits.filter((p) => p.trip_id === trip.id)
  const scopedPermit = tripPermits.find((p) => p.id === scopedPermitId) ?? null
  const tw = warnings.filter((w) => w.trip_id === trip.id)
  const curfew = tw.filter((w) => w.kind === 'curfew')
  const escort = tw.filter((w) => w.kind === 'escort')

  const today = localToday()
  const isValidToday = (p: PermitLite) =>
    (!p.effective_date || p.effective_date <= today) &&
    (!p.expiration_date || p.expiration_date >= today)
  const validToday = tripPermits.filter(isValidToday)
  const states = [...new Set(tripPermits.map((p) => p.state_code).filter(Boolean))]

  // Demo current state (Nash: "We are in Wyoming"): states before it on the
  // path are completed; the current state and everything after are ahead.
  const currentState = getDemoCurrentState(trip.ref_code)
  const currentIdx = currentState ? states.indexOf(currentState) : -1
  const completedStates = currentIdx > 0 ? states.slice(0, currentIdx) : []
  const aheadStates = currentIdx >= 0 ? states.slice(currentIdx) : states
  // Path order, completed pushed to the bottom ("the ones that are already
  // completed, they will be pushed to the bottom as the completed ones").
  const orderedStates = [...aheadStates, ...completedStates]

  const tripRequests = routeRequests.filter((r) => r.trip_id === trip.id && r.status !== 'cancelled')
  const tripRoutes = tripRequests.filter((r) => r.type === 'route_request')
  // Permit re-orders for expired states (Task 75).
  const tripPermitRequests = tripRequests.filter((r) => r.type === 'permit_request')
  const unit = units.find((u) => u.trip_id === trip.id) ?? null

  // Alerts that no state section will render (no permit, or a permit without
  // a usable state) stay visible at the top — nothing is silently hidden.
  const sectionPermitIds = new Set(
    orderedStates.map((s) => tripPermits.find((p) => p.state_code === s)?.id).filter(Boolean),
  )
  const tripAlerts = tw.filter((w) => !w.permit_id || !sectionPermitIds.has(w.permit_id))

  function goTo(i: number) {
    const el = trackRef.current
    if (!el) return
    el.scrollTo({ left: i * el.clientWidth, behavior: 'smooth' })
  }

  function onScroll() {
    const el = trackRef.current
    if (!el) return
    const i = Math.round(el.scrollLeft / el.clientWidth)
    if (i !== pane) setPane(i)
  }


  const statBox = (k: string, v: string, tone: 'ok' | 'warn' | 'muted') => (
    <div key={k} className="rounded-lg bg-white/5 px-1 py-2 ring-1 ring-white/10">
      <p className="text-navy-100/70">{k}</p>
      <p
        className={`mt-0.5 text-xs font-bold ${
          tone === 'ok' ? 'text-green-300' : tone === 'warn' ? 'text-amber-brand' : 'text-navy-100'
        }`}
      >
        {v}
      </p>
    </div>
  )

  return (
    <main className="py-2 sm:py-6">
      <div className="mx-auto max-w-[420px] overflow-hidden bg-paper sm:rounded-[2rem] sm:shadow-2xl sm:ring-8 sm:ring-navy-900">
        {/* Trip header */}
        <header className="bg-navy-900 px-5 pb-5 pt-6 text-white">
          {/* The 📐 dimensions shortcut lived here until 2026-09-07. Nash:
              "I don't think it's useful, because you have the My Unit
              information right here on the bottom." Overall Dimensions are
              edited in the My Unit pane instead. */}
          <p className="text-[11px] font-bold uppercase tracking-[0.14em] text-amber-brand">
            {['completed', 'cancelled'].includes(trip.status) ? 'Previous Trip' : 'Current Trip'}
            {trip.unit_number ? ` · Unit ${trip.unit_number}` : ''}
          </p>
          <h1 className="mt-1 text-xl font-extrabold tracking-tight">
            {trip.origin || 'Origin TBD'} → {trip.destination || 'Destination TBD'}
          </h1>
          <p className="mt-0.5 text-xs text-navy-100">
            {trip.commodity || 'Commodity TBD'}
            {trip.carrier_name ? ` · ${trip.carrier_name}` : ''}
          </p>
          <div className="mt-4 grid grid-cols-4 gap-2 text-center text-[10px] font-semibold">
            {statBox(
              'Permits',
              tripPermits.length > 0 ? `${tripPermits.length} ✓` : 'None',
              tripPermits.length > 0 ? 'ok' : 'warn',
            )}
            {statBox(
              'Valid',
              tripPermits.length > 0 ? `${validToday.length} today` : '—',
              validToday.length === tripPermits.length && tripPermits.length > 0 ? 'ok' : 'warn',
            )}
            {statBox(
              'Curfew',
              curfew.length > 0 ? 'Warning' : tripPermits.length > 0 ? 'Clear' : '—',
              curfew.length > 0 ? 'warn' : tripPermits.length > 0 ? 'ok' : 'muted',
            )}
            {statBox(
              'Escort',
              escort.length > 0 ? 'Review' : tripPermits.length > 0 ? 'Clear' : '—',
              escort.length > 0 ? 'warn' : tripPermits.length > 0 ? 'ok' : 'muted',
            )}
          </div>
          <p className="mt-3 text-center text-[10px] text-navy-100/60">← Swipe between tabs →</p>
        </header>


        {/* Swipeable panes — the track's height follows the pane on screen
            (see trackHeight above), so no pane carries another pane's height
            as empty space below it. Taller neighbours are clipped only while
            a swipe is in progress. */}
        <div
          ref={trackRef}
          onScroll={onScroll}
          className="relative flex snap-x snap-mandatory items-start overflow-x-auto overflow-y-hidden transition-[height] duration-200"
          style={{ scrollbarWidth: 'none', height: trackHeight }}
        >
          {/* Pane 1: My Trips — all trips, most recent/active on top */}
          <section className="w-full shrink-0 snap-center space-y-3 px-4 py-5 pb-8">
            <p className="text-[10px] font-bold uppercase tracking-[0.14em] text-slate-body/70">
              My Trips ({trips.length})
            </p>
            {/* Somebody else finished this trip — ask, never force
                (2026-09-07). */}
            {activeTrips
              .filter((t) => shouldPromptCompletion(completions[t.id]))
              .map((t) => (
                <CompletionPrompt
                  key={`prompt-${t.id}`}
                  trip={t}
                  completion={completions[t.id] ?? EMPTY_COMPLETION}
                  onDone={() => router.refresh()}
                />
              ))}
            {activeTrips.map((t) => (
              <TripListCard
                key={t.id}
                trip={t}
                status={statusFor(t)}
                highlighted={t.id === trip.id}
                canMark={!!completions[t.id]?.myParticipantId}
                onDone={() => router.refresh()}
              />
            ))}
            {previous.length > 0 && (
              <>
                <p className="pt-2 text-[10px] font-bold uppercase tracking-[0.14em] text-slate-body/70">
                  Previous trips
                </p>
                {/* A completed trip can be swiped/held back to active too —
                    Nash: "if it's not active, I hold, is gonna give me Mark as
                    active… So I can choose which one I want to activate." */}
                {previous.map((t) => (
                  <TripListCard
                    key={t.id}
                    trip={t}
                    status={statusFor(t)}
                    highlighted={t.id === trip.id}
                    canMark={!!completions[t.id]?.myParticipantId}
                    onDone={() => router.refresh()}
                  />
                ))}
              </>
            )}
            <p className="text-center text-[10px] leading-relaxed text-slate-body/70">
              Open any trip — including previous ones — and ask the agent questions about it.
            </p>
          </section>

          {/* Pane 2: Active Trip — a section per state, in path order */}
          <section className="w-full shrink-0 snap-center space-y-4 px-4 py-5 pb-8">
            {/* Trip-wide activation while the trip is still waiting for
                permits (Task 67). Nash: "If the driver pushes that order into
                active, it should be activated for all users." Confirms first.
                Renders nothing once the trip is active. */}
            <ActivateTripButton
              trip={trip}
              myRole={myRole}
              onDone={() => router.refresh()}
              variant="driver"
            />
            {/* The active shopping cart (Task 69) — shows only with items. */}
            <CartBar tripId={trip.id} cart={cart} onPaid={() => router.refresh()} variant="driver" />
            <LocationCard
              states={states}
              currentState={currentState}
              completedStates={completedStates}
            />

            {/* Trip-wide alerts (not tied to a state section) stay visible */}
            {tripAlerts.map((w) => (
              <Card key={w.id} tone={w.severity === 'danger' ? 'danger' : 'warn'}>
                <p className="text-xs leading-relaxed text-slate-body">⚠ {w.message}</p>
              </Card>
            ))}

            {tripPermits.length === 0 ? (
              <p className="rounded-2xl border border-dashed border-line p-8 text-center text-xs text-slate-body">
                No permits uploaded yet. Ask your dispatcher, or upload them in the trip workspace.
              </p>
            ) : (
              <StateSections
                trip={trip}
                orderedStates={orderedStates}
                currentState={currentState}
                completedStates={completedStates}
                permits={tripPermits}
                warnings={tw}
                permitUrls={permitUrls}
                pilotAccess={pilotAccess}
                accountCreatedAt={accountCreatedAt}
                isValidToday={isValidToday}
                routeRequests={tripRoutes}
                permitRequests={tripPermitRequests}
                participants={participants}
                myRole={myRole}
                routeCredits={routeCredits}
                cart={cart}
                onSaved={() => router.refresh()}
                onAskAgent={(permitId) => {
                  setScopedPermitId(permitId)
                  goTo(AGENT_PANE)
                }}
              />
            )}

            {/* Trip expenses (Nash, 2026-09-13): "the truck drivers must also
                have the ability to track their expenses related to the trip."
                Pro feature — same tracker as the pilot tools, minus invoices. */}
            <TripExpensesCard trip={trip} actorName={myName ?? 'Driver'} isInternal={isInternal} />

            <p className="px-2 text-center text-[10px] leading-relaxed text-slate-body/70">
              The uploaded permit remains the official controlling document. The driver and carrier
              remain responsible for legal operation.
            </p>
          </section>

          {/* Pane 3: Agent — the trip chat, rendered INLINE for the driver
              (2026-09-07). Nash: "When I click on Ask Your Agent… I should
              have the chat view for the driver dashboard, how it looks on the
              driver." Previously this pane only linked out to the desktop
              workspace. */}
          <section className="w-full shrink-0 snap-center space-y-3 px-4 py-5 pb-8">
            <p className="text-[10px] font-bold uppercase tracking-[0.14em] text-slate-body/70">
              Ask HeavyHaul Agent
            </p>
            {/* The composer's own starter pills carry the four driver
                questions and fill the input in place. */}
            <ChatPanel
              trip={trip}
              chat={chat}
              permits={tripPermits}
              myRole={myRole}
              myUserId={userId}
              participants={participants}
              scopedPermit={scopedPermit}
              onExitScope={() => setScopedPermitId(null)}
              suggestions={SUGGESTED}
              shareChat={shareChat}
              languages={chatLanguages}
              primaryLanguage={primaryLanguage}
              compact
            />
          </section>

          {/* Pane 4: My Unit — unit profile for this trip */}
          <section className="w-full shrink-0 snap-center space-y-4 px-4 py-5 pb-8">
            <p className="text-[10px] font-bold uppercase tracking-[0.14em] text-slate-body/70">
              My Unit — this trip
            </p>
            {/* Nash, 2026-09-07: "We could also have here commodity dimensions…
                under my unit, above overall dimensions, axles, truck, trailer
                info." Same per-trip record — "This is my unit this trip… You
                manage your unit for the trip." */}
            <Card>
              <DimensionsEditor
                title="Commodity Dimensions"
                tripId={trip.id}
                prefix="load"
                values={{
                  length: trip.load_length_in,
                  width: trip.load_width_in,
                  height: trip.load_height_in,
                  weight: trip.load_weight_lbs,
                }}
                note="The commodity itself — not the truck and trailer. Overall dimensions are below."
                canEdit
                onSaved={() => router.refresh()}
              />
            </Card>
            <Card>
              <DimensionsEditor
                title="Overall Dimensions"
                tripId={trip.id}
                prefix="overall"
                values={{
                  length: trip.overall_length_in,
                  width: trip.overall_width_in,
                  height: trip.overall_height_in,
                  weight: trip.overall_weight_lbs,
                }}
                note="Truck + trailer combined — every permit is cross-checked against these. Changes are recorded in trip history and visible to everyone on this trip."
                canEdit
                onSaved={() => router.refresh()}
              />
            </Card>
            <UnitProfile trip={trip} unit={unit} onSaved={() => router.refresh()} />
          </section>
        </div>

        {/* Bottom nav — synced with the carousel */}
        <nav className="sticky bottom-0 grid grid-cols-4 border-t border-line bg-white py-2 text-center text-[10px] font-semibold text-slate-body">
          {PANES.map((label, i) => (
            <button
              key={label}
              onClick={() => goTo(i)}
              className={pane === i ? 'text-amber-deep' : 'hover:text-ink'}
            >
              <span className="block text-lg">{PANE_ICONS[i]}</span>
              {label}
            </button>
          ))}
        </nav>
      </div>
    </main>
  )
}

/* ---------------- My Trips: swipe / press-and-hold to mark ---------------- */

/** Nash: "Click and hold for like two seconds will generate the swipe effect." */
const HOLD_MS = 2000
/** How far a horizontal drag must travel before it counts as a swipe. */
const SWIPE_PX = 40

/**
 * One trip in the driver's My Trips list.
 *
 * Nash, 2026-09-07: "maybe I can swipe it, like, you know how you swipe text
 * messages… you swipe to the left, and it gives you, like [delete] — but
 * instead of delete, we can change the status to active, completed… Click and
 * hold for like two seconds will generate the swipe effect and it's going to
 * give me an option to mark as completed… Now the one below that's not active,
 * if it's not active, I hold, is gonna give me Mark as active."
 *
 * Both gestures reveal the SAME action, so this works with a mouse on desktop
 * as well as a finger on a phone. A plain tap still opens the trip.
 *
 * `touch-action: pan-y` is what keeps a horizontal swipe here from being eaten
 * by the surrounding pane carousel, which is itself a horizontal scroller.
 */
function TripListCard({
  trip,
  status,
  highlighted,
  canMark,
  onDone,
}: {
  trip: Trip
  status: TripStatus
  highlighted: boolean
  /** Only an actual participant has a row to mark. */
  canMark: boolean
  onDone: () => void
}) {
  const [revealed, setRevealed] = useState(false)
  const [saving, setSaving] = useState(false)
  const start = useRef<{ x: number; y: number } | null>(null)
  const holdTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
  /** Set when a gesture (not a tap) happened, so the tap must not navigate. */
  const gestured = useRef(false)

  const completed = status === 'completed'

  function clearHold() {
    if (holdTimer.current) {
      clearTimeout(holdTimer.current)
      holdTimer.current = null
    }
  }
  useEffect(() => clearHold, [])

  function onPointerDown(e: React.PointerEvent) {
    if (!canMark) return
    start.current = { x: e.clientX, y: e.clientY }
    gestured.current = false
    clearHold()
    holdTimer.current = setTimeout(() => {
      gestured.current = true
      setRevealed(true)
    }, HOLD_MS)
  }

  function onPointerMove(e: React.PointerEvent) {
    if (!start.current) return
    const dx = e.clientX - start.current.x
    const dy = e.clientY - start.current.y
    // Any real movement cancels the press-and-hold.
    if (Math.abs(dx) > 8 || Math.abs(dy) > 8) clearHold()
    // Only a clearly horizontal drag counts — a vertical scroll must not.
    if (Math.abs(dx) <= Math.abs(dy)) return
    if (dx < -SWIPE_PX) {
      gestured.current = true
      setRevealed(true)
      start.current = null
    } else if (dx > SWIPE_PX) {
      gestured.current = true
      setRevealed(false)
      start.current = null
    }
  }

  function endPointer() {
    clearHold()
    start.current = null
  }

  async function mark() {
    setSaving(true)
    try {
      const res = await fetch(`/api/trips/${trip.id}/status`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ scope: 'me', to: completed ? 'active' : 'completed' }),
      })
      const json = await res.json().catch(() => ({}))
      if (!res.ok) {
        toast.error(json.error ?? 'Could not change this trip')
        return
      }
      toast.success(
        completed
          ? 'Marked active again — only for you'
          : 'Marked completed for you. Everyone else keeps it active until they choose.',
      )
      setRevealed(false)
      onDone()
    } finally {
      setSaving(false)
    }
  }

  return (
    <div className="relative overflow-hidden rounded-2xl" style={{ touchAction: 'pan-y' }}>
      {/* Action revealed underneath the card */}
      <div className="absolute inset-y-0 right-0 flex items-center">
        <button
          onClick={mark}
          disabled={saving}
          className={`h-full w-36 px-2 text-[11px] font-bold text-white disabled:opacity-60 ${
            completed ? 'bg-ok' : 'bg-navy-900'
          }`}
        >
          {saving ? '…' : completed ? 'Mark as active' : 'Mark as completed'}
        </button>
      </div>

      <Link
        href={driverTripHref(trip.ref_code)}
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={endPointer}
        onPointerCancel={endPointer}
        onClick={(e) => {
          // A swipe or long-press must never also open the trip; and while the
          // action is showing, a tap just puts the card back.
          if (gestured.current || revealed) {
            e.preventDefault()
            gestured.current = false
            setRevealed(false)
          }
        }}
        className={`relative block rounded-2xl border p-4 shadow-sm transition-transform duration-200 ${
          revealed ? '-translate-x-36' : ''
        } ${
          highlighted
            ? 'border-amber-brand/60 bg-white ring-1 ring-amber-brand/40'
            : 'border-line bg-white'
        }`}
      >
        <div className="flex items-center justify-between gap-2">
          {/* "City, ST → City, ST" in the list (Nash, 2026-09-08): the full
              address does not fit a phone-width card; it stays on the trip
              header once the trip is opened. */}
          <p className="truncate text-sm font-extrabold" title={`${trip.origin} → ${trip.destination}`}>
            {cityState(trip.origin) || 'Origin TBD'} → {cityState(trip.destination) || 'Destination TBD'}
          </p>
          {/* Real per-user status — not "first in the list" (Nash: "This only
              says that it's active. How do I know if it's active or inactive?") */}
          <span
            className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-bold ${
              status === 'active'
                ? 'bg-amber-brand text-navy-900'
                : status === 'completed'
                  ? 'bg-paper text-slate-body ring-1 ring-line'
                  : status === 'cancelled'
                    ? 'bg-danger-bg text-danger'
                    : 'bg-paper text-slate-body ring-1 ring-line'
            }`}
          >
            {STATUS_LABELS[status]}
          </span>
        </div>
        <p className="mt-0.5 truncate text-[11px] text-slate-body">
          {trip.commodity || 'Commodity TBD'}
          {trip.carrier_name ? ` · ${trip.carrier_name}` : ''}
        </p>
        {canMark && (
          <p className="mt-1 text-[10px] text-slate-body/60">
            Swipe left or hold to mark {completed ? 'active' : 'completed'}
          </p>
        )}
      </Link>
    </div>
  )
}

/* ---------------- Active Trip: a section per state ---------------- */

/**
 * Per the driver feedback: "a section for each state… in the order that I
 * have them in my path. The ones that are already completed, they will be
 * pushed to the bottom as the completed ones." Each section shows the permit
 * file, the provisions, and only THAT state's alerts, and answers the major
 * questions (night travel, curfews, pilot cars, permit-vs-dims discrepancy).
 */
function StateSections({
  trip,
  orderedStates,
  currentState,
  completedStates,
  permits,
  warnings,
  permitUrls,
  isValidToday,
  routeRequests,
  permitRequests,
  participants,
  myRole,
  routeCredits,
  cart,
  onSaved,
  onAskAgent,
  pilotAccess = {},
  accountCreatedAt,
}: {
  trip: Trip
  orderedStates: string[]
  pilotAccess?: Record<string, string>
  /** Account sign-up date — drives the 90-day pilot-paperwork window (Nash, 2026-09-13). */
  accountCreatedAt?: string
  currentState: string | null
  completedStates: string[]
  permits: PermitLite[]
  warnings: TripWarning[]
  permitUrls: Record<string, string>
  isValidToday: (p: PermitLite) => boolean
  routeRequests: RouteRequestLite[]
  /** Permit re-orders, to show "New permit requested by …" on an expired state (Task 75). */
  permitRequests: RouteRequestLite[]
  /** To name who requested / purchased a route (Task 70). */
  participants: TripParticipant[]
  myRole: TripRole | null
  routeCredits: RouteCredits
  cart: RouteCart
  onSaved: () => void
  /** Scope the Agent pane to this permit and slide the carousel to it. */
  onAskAgent: (permitId: string) => void
}) {
  // "View state notes" popup — one dialog for the whole pane (2026-09-12).
  const [notesState, setNotesState] = useState<string | null>(null)
  const [open, setOpen] = useState<Set<string>>(
    () => new Set(currentState ? [currentState] : []),
  )
  const completed = new Set(completedStates)
  const firstCompletedIdx = orderedStates.findIndex((s) => completed.has(s))

  function toggle(s: string) {
    setOpen((prev) => {
      const next = new Set(prev)
      if (next.has(s)) next.delete(s)
      else next.add(s)
      return next
    })
  }

  return (
    <div className="space-y-2.5">
      {orderedStates.map((s, i) => {
        const permit = permits.find((p) => p.state_code === s)
        if (!permit) return null
        const isCurrent = s === currentState
        const isCompleted = completed.has(s)
        const isOpen = open.has(s)
        const pw = warnings.filter((w) => w.permit_id === permit.id)
        const isExpired = !!permit.expiration_date && permit.expiration_date < localToday()
        const valid = isValidToday(permit)
        // Pilot cars hired for THIS state (Nash, 2026-09-12: "I click on New
        // Mexico, and I can see who's my pilot car").
        const realPilots = participants.filter(
          (pt) => pt.role === 'pilot' && pt.status !== 'removed' && pilotAccessCoversState(pilotAccess[pt.email.toLowerCase()], s),
        )
        // Demo overlay (Nash, 2026-09-12): New Mexico and Arizona always have
        // a hired pilot car — John Cena (driver) and Mark Cuban (dispatch) —
        // unless a real pilot already covers the state.
        const statePilots: Array<TripParticipant & { kind?: string }> =
          realPilots.length === 0 && isDemoPilotState(s) ? demoPilotContacts(trip.id) : realPilots

        return (
          <div key={s}>
            {i === firstCompletedIdx && (
              <p className="mb-2 mt-4 text-[10px] font-bold uppercase tracking-[0.14em] text-slate-body/50">
                Completed
              </p>
            )}
            <div
              className={`rounded-2xl border shadow-sm ${
                isCurrent
                  ? 'border-amber-brand/70 bg-white ring-1 ring-amber-brand/40'
                  : isCompleted
                    ? 'border-line bg-paper opacity-60'
                    : 'border-line bg-white'
              }`}
            >
              <button onClick={() => toggle(s)} className="flex w-full items-center gap-2 p-3.5 text-left">
                <div className="min-w-0 flex-1">
                  <p className="truncate text-sm font-extrabold">
                    {isCurrent && <span className="mr-1">📍</span>}
                    {stateName(s)}
                    {isCurrent && (
                      <span className="ml-1.5 rounded-full bg-amber-brand px-2 py-0.5 text-[10px] font-bold text-navy-900">
                        You are here
                      </span>
                    )}
                    {isCompleted && (
                      <span className="ml-1.5 text-[10px] font-semibold text-slate-body">✓ Completed</span>
                    )}
                  </p>
                  <p className="mt-0.5 text-[11px] text-slate-body">
                    {permit.permit_number && (
                      <span className="mr-1.5 font-mono">{permit.permit_number}</span>
                    )}
                    {isExpired ? (
                      <span className="font-semibold text-danger">Expired</span>
                    ) : valid ? (
                      <span className="font-semibold text-ok">✓ Valid today</span>
                    ) : (
                      <span className="font-semibold text-warn">⚠ Not valid today</span>
                    )}
                    {statePilots.length > 0 && (
                      <span className="ml-1.5 font-semibold text-info">· ✓ Pilot car attached</span>
                    )}
                    {pw.length > 0 && (
                      <span className="ml-1.5 font-semibold text-warn">
                        · {pw.length} alert{pw.length === 1 ? '' : 's'}
                      </span>
                    )}
                  </p>
                </div>
                <span className={`shrink-0 text-slate-body/60 transition ${isOpen ? 'rotate-180' : ''}`}>⌄</span>
              </button>

              {isOpen && (
                <div className="space-y-3 border-t border-line/70 px-3.5 pb-3.5 pt-3">
                  {/* Alerts for THIS state only */}
                  <StateCheck
                    label="Curfews"
                    warnings={pw.filter((w) => w.kind === 'curfew')}
                    empty={`No curfew recorded for ${stateName(s)}.`}
                  />
                  <StateCheck
                    label="Pilot cars / escorts"
                    warnings={pw.filter((w) => w.kind === 'escort')}
                    empty="No pilot-car requirement recorded."
                  />
                  <StateCheck
                    label="Permit vs overall dimensions"
                    warnings={pw.filter((w) => w.kind === 'dimension_mismatch')}
                    empty="✓ No discrepancy with your overall dimensions."
                    emptyTone="ok"
                  />
                  <StateCheck
                    label="Travel restrictions & other alerts"
                    warnings={pw.filter(
                      (w) => !['curfew', 'escort', 'dimension_mismatch'].includes(w.kind),
                    )}
                    empty="Night-travel rules come from the permit provisions — ask the agent to check."
                  />

                  {/* Permit file · provisions · ask the agent about this permit */}
                  <div className="flex flex-wrap items-center gap-x-3 gap-y-1.5 border-t border-line/70 pt-2.5 text-[11px]">
                    {permit.document_id && permitUrls[permit.document_id] ? (
                      <a
                        href={permitUrls[permit.document_id]}
                        target="_blank"
                        rel="noreferrer"
                        className="font-bold text-info hover:underline"
                      >
                        View permit
                      </a>
                    ) : (
                      <button
                        onClick={() => toast.info('No permit file is linked to this state yet.')}
                        className="font-bold text-slate-body/60"
                      >
                        View permit
                      </button>
                    )}
                    <button
                      onClick={() =>
                        toast.info(
                          'Provisions come from Synchron Permits — available once the connection is live.',
                        )
                      }
                      className="font-bold text-slate-body/60 hover:text-slate-body"
                    >
                      View provisions
                    </button>
                    {/* Internal state notes shared with drivers (Nash, 2026-09-12). */}
                    <button
                      onClick={() => setNotesState(permit.state_code)}
                      className="font-bold text-info hover:underline"
                    >
                      View state notes
                    </button>
                    <button
                      onClick={() => onAskAgent(permit.id)}
                      className="ml-auto rounded-lg bg-navy-900 px-2.5 py-1.5 font-bold text-white hover:bg-navy-800"
                    >
                      💬 Ask your agent
                    </button>
                  </div>

                  {/* Expired → re-order from Synchron, any member (Task 75). */}
                  {isExpired && (
                    <div className="flex flex-wrap items-center gap-2 text-[11px]">
                      <RequestNewPermitButton
                        tripId={trip.id}
                        permit={permit}
                        myRole={myRole}
                        existing={
                          permitRequests.find((r) => r.replaces_permit_id === permit.id) ?? null
                        }
                        participants={participants}
                        onDone={onSaved}
                        variant="driver"
                      />
                    </div>
                  )}

                  {/* Second footer: the route, in all three delivered formats */}
                  <RouteDeliverables
                    trip={trip}
                    permit={permit}
                    routeRequest={routeRequests.find((r) => r.state_code === s)}
                    participants={participants}
                    routeCredits={routeCredits}
                    cart={cart}
                    onSaved={onSaved}
                  />

                  {/* Pilot cars hired for this state, under the route (Nash,
                      2026-09-12): tap a pilot to get name, phone and email —
                      call, email, or copy the address. */}
                  {statePilots.length > 0 && <PilotContacts pilots={statePilots} state={s} tripRef={trip.ref_code} accountCreatedAt={accountCreatedAt} />}
                </div>
              )}
            </div>
          </div>
        )
      })}
      <StateNotesDialog code={notesState} onClose={() => setNotesState(null)} />
    </div>
  )
}

/**
 * The pilot car(s) hired for one state — collapsed to names, tap to open the
 * contact card. Nash: "an easy way to get in touch with the pilot car or
 * with the pilot dispatch anytime… click on the phone number, and then we'll
 * dial him… create an email… copy the email address."
 *
 * Pilot participants carry one trip role today; the pilot-driver vs
 * pilot-dispatch split arrives with pilot accounts (Phase 2), so each entry
 * reads "Pilot car".
 */
function PilotContacts({ pilots, state, tripRef, accountCreatedAt }: { pilots: Array<TripParticipant & { kind?: string; demo?: boolean }>; state: string; tripRef: string; accountCreatedAt?: string }) {
  const [openId, setOpenId] = useState<string | null>(null)
  // Pilot paperwork for the carrier driver too (Nash, 2026-09-13: "carrier
  // dispatchers and the drivers… track the paperwork for each pilot").
  const { access, plan } = useViewerPaperworkAccess(accountCreatedAt)
  return (
    <div className="border-t border-line/70 pt-2.5">
      <p className="text-[10px] font-bold uppercase tracking-[0.14em] text-slate-body/70">
        Pilot car{pilots.length === 1 ? '' : 's'} for {stateName(state) || state}
      </p>
      <div className="mt-1.5 space-y-1.5">
        {pilots.map((p) => {
          const open = openId === p.id
          const phone = p.phone ? `${p.phone}${p.phone_ext ? ` x${p.phone_ext}` : ''}` : null
          return (
            <div key={p.id} className="rounded-xl border border-line bg-white">
              <button
                type="button"
                onClick={() => setOpenId(open ? null : p.id)}
                className="flex w-full items-center justify-between px-3 py-2 text-left text-xs"
              >
                <span>
                  <span className="font-semibold">{p.name || p.email}</span>
                  <span className="ml-1.5 rounded-full bg-info-bg px-1.5 py-0.5 text-[10px] font-bold text-info">{p.kind ?? 'Pilot car'}</span>
                </span>
                <span className="text-slate-body">{open ? '▾' : '▸'}</span>
              </button>
              {open && (
                <div className="space-y-1.5 border-t border-line/70 px-3 py-2 text-xs">
                  <p className="text-slate-body">{p.status === 'active' ? '● Joined the trip' : '○ Invited'}</p>
                  {phone ? (
                    <a href={`tel:${p.phone!.replace(/[^\d+]/g, '')}`} className="flex items-center justify-between rounded-lg bg-paper px-2.5 py-1.5 font-semibold">
                      <span>📞 {phone}</span><span className="text-[10px] text-slate-body">Tap to call</span>
                    </a>
                  ) : (
                    <p className="text-slate-body">No phone on file</p>
                  )}
                  <div className="flex items-center gap-1.5">
                    <a href={`mailto:${p.email}`} className="flex flex-1 items-center justify-between rounded-lg bg-paper px-2.5 py-1.5 font-semibold">
                      <span className="truncate">✉️ {p.email}</span><span className="ml-2 shrink-0 text-[10px] text-slate-body">Email</span>
                    </a>
                    <button
                      type="button"
                      onClick={() => {
                        navigator.clipboard?.writeText(p.email)
                        toast.success('Email address copied')
                      }}
                      className="rounded-lg border border-line px-2.5 py-1.5 text-[11px] font-semibold"
                    >
                      Copy
                    </button>
                  </div>
                  <PilotPaperwork pilot={p} access={access} plan={plan} tripRef={tripRef} />
                </div>
              )}
            </div>
          )
        })}
      </div>
    </div>
  )
}

/**
 * Expense tracking for the carrier truck driver on the active trip. Plan-gated
 * on the driver's own plan (Pro or Pro Plus); Free/Starter see the locked
 * card. Expenses are private to the driver and never go on an invoice.
 */
function TripExpensesCard({ trip, actorName, isInternal }: { trip: Trip; actorName: string; isInternal: boolean }) {
  const { plan, setPlan } = useViewerPlanPreview('Free')
  return (
    <Card>
      <CardHead label="Trip expenses" />
      <p className="mt-1 text-[11px] text-slate-body">Fuel, tolls, hotels and other costs for this trip — with receipts and a per-trip summary. Private to you.</p>
      {isInternal && (
        <p className="mt-2 rounded-lg border border-dashed border-amber-300 bg-amber-50/60 px-2.5 py-1.5 text-[11px] text-amber-900">
          <span className="font-semibold">Design preview — your plan:</span>
          {PLAN_NAMES_FOR_PREVIEW.map((p) => (
            <button key={p} onClick={() => setPlan(p)} className={`ml-1 rounded-full px-2 py-0.5 font-semibold ${plan === p ? 'bg-amber-900 text-white' : 'bg-white/70 hover:bg-white'}`}>{p}</button>
          ))}
        </p>
      )}
      <div className="mt-3">
        {canUseBusinessTools(plan) ? (
          <ExpenseTracker assignmentId={trip.id} tripRef={trip.ref_code} ownerType="carrier_driver" ownerName={actorName} actorName={actorName} />
        ) : (
          <LockedProFeature plan={plan} feature="expenses" />
        )}
      </div>
    </Card>
  )
}

/** The pilot's paperwork under the contact card — same block as the My Pilot tab. */
function PilotPaperwork({ pilot, access, plan, tripRef }: { pilot: TripParticipant & { kind?: string; demo?: boolean }; access: PaperworkAccess; plan: PlanName; tripRef: string }) {
  if (access.mode === 'locked') return <LockedProFeature plan={plan} feature="paperwork" />
  const entry: MyPilotEntry = {
    name: pilot.name || pilot.email,
    kind: pilot.kind === 'Pilot dispatch' ? 'Pilot dispatch' : 'Pilot driver',
    phone: pilot.phone ?? '—',
    email: pilot.email,
    access: '',
    states: [],
    status: pilot.status,
    documents: pilot.demo ? demoPilotPaperwork((pilot as DemoPilotContact).kind) : undefined,
  }
  return (
    <div>
      {access.mode === 'trial' && (
        <p className="text-[10px] font-semibold text-amber-800">Pilot paperwork free for your first {PAPERWORK_TRIAL_DAYS} days · {access.daysLeft} day{access.daysLeft === 1 ? '' : 's'} left</p>
      )}
      <PaperworkBlock
        entry={entry}
        onView={async (e, d) =>
          openPdf(
            await buildPaperworkPdf({ label: d.label, owner: e.name, ownerKind: e.kind, status: d.status, expirationDate: d.expirationDate, uploadedAt: d.uploadedAt, tripRef }),
            `${e.name.replace(/\s+/g, '-')}-${d.key}.pdf`,
            'view',
          )}
      />
    </div>
  )
}

function StateCheck({
  label,
  warnings,
  empty,
  emptyTone,
}: {
  label: string
  warnings: TripWarning[]
  empty: string
  emptyTone?: 'ok'
}) {
  return (
    <div>
      <p className="text-[10px] font-bold uppercase tracking-wide text-slate-body/60">{label}</p>
      {warnings.length > 0 ? (
        warnings.map((w) => (
          <p
            key={w.id}
            className={`mt-0.5 text-xs leading-relaxed ${
              w.severity === 'danger' ? 'font-semibold text-danger' : 'text-slate-body'
            }`}
          >
            ⚠ {w.message}
          </p>
        ))
      ) : (
        <p className={`mt-0.5 text-xs leading-relaxed ${emptyTone === 'ok' ? 'text-ok' : 'text-slate-body/80'}`}>
          {empty}
        </p>
      )}
    </div>
  )
}

/* ---------------- Route deliverables (second footer) ---------------- */

/**
 * The SECOND footer row of a state section (2026-09-07 meeting).
 *
 * Nash: "the second footer section, right, is gonna have, like, a GPX, a
 * Hummer GPS, maybe an icon for each one… or below, like, the Part 1, Part 2,
 * Google Map, Part 1, 2, 3, 4. And having that option is gonna give the user
 * ability to choose which one he wants to see more."
 *
 * A purchased route is delivered in THREE formats — a GPX file the driver
 * sends to the in-cab GPS over Bluetooth, a Hummer GPS app deep link, and
 * Google Maps in 1..N parts ("it could be like eight parts or 12 parts for
 * Texas"). When the driver does not have the route yet, this is also where he
 * buys it: "why don't we have in that footer the button to purchase the map?"
 */
function RouteDeliverables({
  trip,
  permit,
  routeRequest,
  participants,
  routeCredits,
  cart,
  onSaved,
}: {
  trip: Trip
  permit: PermitLite
  routeRequest?: RouteRequestLite
  participants: TripParticipant[]
  routeCredits: RouteCredits
  cart: RouteCart
  onSaved: () => void
}) {
  // Demo-only: express/extended per state is normally assigned by the backend.
  const routeType =
    trip.ref_code === 'HH-48843549' ? DEMO_ROUTE_TYPES[permit.state_code] : undefined

  const links = routeRequest?.route_links ?? []
  const ready = routeRequest?.status === 'fulfilled'
  // Routes come free with a Synchron-processed permit (Task 45) — never sell
  // the driver something he already has.
  const routeIncluded = trip.permit_policy === 'synchron_required'
  // Who ordered it — public to everyone on the trip (Task 70). Nash: "who
  // placed the request… which member of the group? It could be the broker,
  // it could be the driver."
  const requester = routeRequest ? resolveRequester(routeRequest, participants) : null

  return (
    <div className="border-t border-line/70 pt-2.5">
      <p className="text-[10px] font-bold uppercase tracking-wide text-slate-body/60">Route</p>

      {ready ? (
        <>
          <div className="mt-1.5 flex flex-wrap items-center gap-1.5">
            {routeRequest && <RouteFormatChips request={routeRequest} chipClassName={ROUTE_CHIP} />}
            {links.length === 0 && !routeRequest?.route_gpx_url && !routeRequest?.route_hummer_url && (
              <p className="text-[11px] text-slate-body">
                Route is marked ready — the map files are on their way.
              </p>
            )}
          </div>
          {requester && (
            <p className="mt-1 text-[10px] text-slate-body/70">
              Purchased by {requesterText(requester)}
            </p>
          )}
        </>
      ) : routeRequest ? (
        <p className="mt-1.5 text-[11px] font-semibold text-info">
          {ROUTE_STATUS_LABELS[routeRequest.status] ?? routeRequest.status}
          {requester && (
            <span className="font-normal text-slate-body"> — requested by {requesterText(requester)}</span>
          )}
        </p>
      ) : routeIncluded ? (
        <p
          className="mt-1.5 inline-block rounded-md bg-green-50 px-2 py-1 text-[10px] font-bold text-green-700 ring-1 ring-inset ring-green-200"
          title="Routes are included for permits processed by Synchron Permits — no separate purchase required."
        >
          ✓ Route included
        </p>
      ) : (
        <div className="mt-1.5">
          {/* Prepaid credit → use it from the button; money due → cart. Same
              flow and logic as the desktop workspace (Task 69). */}
          <RoutePurchaseControl
            tripId={trip.id}
            stateCode={permit.state_code}
            routeType={routeType}
            credits={routeCredits}
            cart={cart}
            onOrdered={onSaved}
            variant="driver"
          />
        </div>
      )}
    </div>
  )
}

/* ---------------- My Unit pane ---------------- */

type UnitForm = {
  axleCount: string
  spacings: Array<{ ft: string; in: string }>
  /** Kingpin → rear axle (KPTRA) — the last spacing, its own field (Task 76). */
  kptra: { ft: string; in: string }
  weights: string[]
  truck: { unit: string; vin: string; make: string; model: string; year: string }
  trailer: { unit: string; vin: string; make: string; model: string; year: string }
  trailerAxles: string
}

/** '7' → 7 (clamped 1..30); anything blank/invalid → 0. */
function parseAxleCount(s: string): number {
  const n = Math.round(Number(s))
  return Number.isFinite(n) && n >= 1 ? Math.min(n, 30) : 0
}

/**
 * Unit profile for the trip (driver feedback): number of axles ("5 Axel,
 * 6 Axel, 7 Axel. That's very important"), axle spacings and weights (pulled
 * from the permits when available), truck info and trailer info. The driver
 * can change anything — every save is logged to trip history.
 */
/**
 * Placeholder plates until the order feed supplies them (Nash, 2026-09-12):
 * Washington for trucks, Maine for trailers, generic numbers.
 */
const PLACEHOLDER_PLATES = {
  truck: { plate: 'C99 8351', state: 'WA · Washington' },
  trailer: { plate: '2847 TR', state: 'ME · Maine' },
} as const

function UnitProfile({
  trip,
  unit,
  onSaved,
}: {
  trip: Trip
  unit: TripUnit | null
  onSaved: () => void
}) {
  const [editing, setEditing] = useState(false)
  const [saving, setSaving] = useState(false)
  const toFt = (v: number | null | undefined) => (v == null ? '' : String(Math.floor(v / 12)))
  const toIn = (v: number | null | undefined) => (v == null ? '' : String(v % 12))

  function buildForm(): UnitForm {
    const count = unit?.axle_count ?? 0
    const spacings = Array.from({ length: Math.max(count - 1, 0) }, (_, i) => ({
      ft: toFt(unit?.axle_spacings_in?.[i]),
      in: toIn(unit?.axle_spacings_in?.[i]),
    }))
    const weights = Array.from({ length: count }, (_, i) =>
      unit?.axle_weights_lbs?.[i] == null ? '' : String(unit.axle_weights_lbs[i]),
    )
    return {
      axleCount: unit?.axle_count == null ? '' : String(unit.axle_count),
      spacings,
      kptra: { ft: toFt(unit?.kingpin_to_rear_axle_in), in: toIn(unit?.kingpin_to_rear_axle_in) },
      weights,
      truck: {
        unit: unit?.truck_unit_number ?? '',
        vin: unit?.truck_vin ?? '',
        make: unit?.truck_make ?? '',
        model: unit?.truck_model ?? '',
        year: unit?.truck_year == null ? '' : String(unit.truck_year),
      },
      trailer: {
        unit: unit?.trailer_unit_number ?? '',
        vin: unit?.trailer_vin ?? '',
        make: unit?.trailer_make ?? '',
        model: unit?.trailer_model ?? '',
        year: unit?.trailer_year == null ? '' : String(unit.trailer_year),
      },
      trailerAxles: unit?.trailer_axle_count == null ? '' : String(unit.trailer_axle_count),
    }
  }
  const [form, setForm] = useState<UnitForm>(buildForm)

  function startEdit() {
    setForm(buildForm())
    setEditing(true)
  }

  function setAxleCount(v: string) {
    const n = parseAxleCount(v)
    setForm((f) => {
      // Grow-only while typing: a transient clear-and-retype must never wipe
      // spacings/weights already entered. Extra rows are trimmed on save.
      if (n === 0) return { ...f, axleCount: v }
      return {
        ...f,
        axleCount: v,
        spacings: Array.from({ length: Math.max(n - 1, f.spacings.length) }, (_, i) => f.spacings[i] ?? { ft: '', in: '' }),
        weights: Array.from({ length: Math.max(n, f.weights.length) }, (_, i) => f.weights[i] ?? ''),
      }
    })
  }

  function inches(ft: string, inch: string): number | null {
    if (ft.trim() === '' && inch.trim() === '') return null
    const f = Number(ft || 0)
    const i = Number(inch || 0)
    if (!Number.isFinite(f) || !Number.isFinite(i) || f < 0 || i < 0) return null
    return Math.round(f * 12 + i)
  }

  async function save() {
    // Field-level validation BEFORE sending — the API rejects the whole save
    // otherwise, and a generic 400 would not tell the driver what to fix.
    const axleCount = form.axleCount.trim() === '' ? null : parseAxleCount(form.axleCount)
    if (axleCount === 0) {
      toast.error('Number of axles must be between 1 and 30.')
      return
    }
    const trailerAxles = form.trailerAxles.trim() === '' ? null : parseAxleCount(form.trailerAxles)
    if (trailerAxles === 0) {
      toast.error('Trailer axle count must be between 1 and 30.')
      return
    }
    const year = (label: string, s: string): number | null | false => {
      if (s.trim() === '') return null
      const n = Math.round(Number(s))
      if (!Number.isFinite(n) || n < 1900 || n > 2100) {
        toast.error(`${label} year must be a 4-digit year (e.g. 2025).`)
        return false
      }
      return n
    }
    const truckYear = year('Truck', form.truck.year)
    if (truckYear === false) return
    const trailerYear = year('Trailer', form.trailer.year)
    if (trailerYear === false) return

    setSaving(true)
    try {
      const num = (s: string) => {
        if (s.trim() === '') return null
        const n = Math.round(Number(s))
        return Number.isFinite(n) && n >= 0 ? n : null
      }
      const text = (s: string) => (s.trim() === '' ? null : s.trim())
      // The form keeps extra rows while typing (grow-only) — trim to the
      // final axle count here.
      const spacings = axleCount ? form.spacings.slice(0, Math.max(axleCount - 1, 0)) : []
      const weights = axleCount ? form.weights.slice(0, axleCount) : []
      const res = await fetch(`/api/trips/${trip.id}/unit`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          axle_count: axleCount,
          axle_spacings_in: spacings.length ? spacings.map((s) => inches(s.ft, s.in)) : null,
          kingpin_to_rear_axle_in: inches(form.kptra.ft, form.kptra.in),
          axle_weights_lbs: weights.length ? weights.map(num) : null,
          truck_unit_number: text(form.truck.unit),
          truck_vin: text(form.truck.vin),
          truck_make: text(form.truck.make),
          truck_model: text(form.truck.model),
          truck_year: truckYear,
          trailer_unit_number: text(form.trailer.unit),
          trailer_vin: text(form.trailer.vin),
          trailer_make: text(form.trailer.make),
          trailer_model: text(form.trailer.model),
          trailer_year: trailerYear,
          trailer_axle_count: trailerAxles,
        }),
      })
      const json = await res.json()
      if (!res.ok) {
        toast.error(json.error ?? 'Could not save the unit profile')
        return
      }
      if (json.note) toast.warning(json.note)
      toast.success('Unit profile saved — the change is visible to everyone on this trip')
      setEditing(false)
      onSaved()
    } finally {
      setSaving(false)
    }
  }

  const vehicleRows = (stored: {
    unit: string | null
    vin: string | null
    make: string | null
    model: string | null
    year: number | null
    axles?: number | null
    plate?: string | null
    plateState?: string | null
  }): Array<[string, string | null]> => [
    ['Unit #', stored.unit],
    ['VIN', stored.vin],
    ['Make', stored.make],
    ['Model', stored.model],
    ['Year', stored.year == null ? null : String(stored.year)],
    // Nash, 2026-09-12: "he should be able to see the plate number… the
    // registration state and plate number. For the truck and trailer."
    ['Plate #', stored.plate ?? null],
    ['Registration state', stored.plateState ?? null],
    ...(stored.axles !== undefined
      ? ([['Axles', stored.axles == null ? null : String(stored.axles)]] as Array<[string, string | null]>)
      : []),
  ]

  if (editing) {
    // Only the rows for the typed axle count are shown; the form keeps any
    // extra entries behind the scenes so a retype never loses data.
    const axleN = parseAxleCount(form.axleCount)
    const visibleSpacings = form.spacings.slice(0, Math.max(axleN - 1, 0))
    const visibleWeights = form.weights.slice(0, axleN)
    const vehicleInputs = (key: 'truck' | 'trailer') => (
      <div className="mt-2 grid grid-cols-2 gap-2">
        {(
          [
            ['unit', 'Unit #'],
            ['vin', 'VIN'],
            ['make', 'Make'],
            ['model', 'Model'],
            ['year', 'Year'],
          ] as const
        ).map(([k, label]) => (
          <label key={k} className="space-y-0.5">
            <span className="text-[10px] font-bold uppercase text-slate-body/60">{label}</span>
            <input
              value={form[key][k]}
              onChange={(e) => setForm((f) => ({ ...f, [key]: { ...f[key], [k]: e.target.value } }))}
              className="w-full rounded-lg border border-line px-2 py-1.5 text-xs"
            />
          </label>
        ))}
      </div>
    )
    return (
      <Card>
        <CardHead label="Unit Profile" />
        <div className="mt-2 space-y-3 text-xs">
          <label className="flex items-center gap-2">
            <span className="text-[10px] font-bold uppercase text-slate-body/60">Number of axles</span>
            <input
              type="number"
              min={1}
              max={30}
              value={form.axleCount}
              onChange={(e) => setAxleCount(e.target.value)}
              className="w-16 rounded-lg border border-line px-2 py-1.5"
            />
          </label>
          {visibleSpacings.length > 0 && (
            <div>
              <p className="text-[10px] font-bold uppercase text-slate-body/60">Axle spacings</p>
              <div className="mt-1 space-y-1">
                {visibleSpacings.map((s, i) => (
                  <div key={i} className="flex items-center gap-1.5">
                    <span className="w-14 text-[10px] text-slate-body/60">
                      {i + 1} → {i + 2}
                    </span>
                    <input
                      type="number"
                      min={0}
                      value={s.ft}
                      onChange={(e) =>
                        setForm((f) => ({
                          ...f,
                          spacings: f.spacings.map((sp, j) => (j === i ? { ...sp, ft: e.target.value } : sp)),
                        }))
                      }
                      className="w-14 rounded-lg border border-line px-2 py-1"
                    />
                    <span className="text-[10px] text-slate-body/60">ft</span>
                    <input
                      type="number"
                      min={0}
                      max={11}
                      value={s.in}
                      onChange={(e) =>
                        setForm((f) => ({
                          ...f,
                          spacings: f.spacings.map((sp, j) => (j === i ? { ...sp, in: e.target.value } : sp)),
                        }))
                      }
                      className="w-14 rounded-lg border border-line px-2 py-1"
                    />
                    <span className="text-[10px] text-slate-body/60">in</span>
                  </div>
                ))}
              </div>
            </div>
          )}
          {/* Kingpin to rear axle — "This is the last one… extremely
              important" (Nash, Task 76). Always shown: every tractor-trailer
              has one, whatever the axle count. */}
          <div>
            <p className="text-[10px] font-bold uppercase text-slate-body/60">
              Kingpin → rear axle (KPTRA)
            </p>
            <div className="mt-1 flex items-center gap-1.5">
              <span className="w-14 text-[10px] text-slate-body/60">KPTRA</span>
              <input
                type="number"
                min={0}
                value={form.kptra.ft}
                onChange={(e) => setForm((f) => ({ ...f, kptra: { ...f.kptra, ft: e.target.value } }))}
                className="w-14 rounded-lg border border-line px-2 py-1"
              />
              <span className="text-[10px] text-slate-body/60">ft</span>
              <input
                type="number"
                min={0}
                max={11}
                value={form.kptra.in}
                onChange={(e) => setForm((f) => ({ ...f, kptra: { ...f.kptra, in: e.target.value } }))}
                className="w-14 rounded-lg border border-line px-2 py-1"
              />
              <span className="text-[10px] text-slate-body/60">in</span>
            </div>
          </div>
          {visibleWeights.length > 0 && (
            <div>
              <p className="text-[10px] font-bold uppercase text-slate-body/60">Axle weights (lbs)</p>
              <div className="mt-1 grid grid-cols-4 gap-1.5">
                {visibleWeights.map((w, i) => (
                  <label key={i} className="space-y-0.5">
                    <span className="text-[10px] text-slate-body/60">#{i + 1}</span>
                    <input
                      type="number"
                      min={0}
                      value={w}
                      onChange={(e) =>
                        setForm((f) => ({
                          ...f,
                          weights: f.weights.map((wt, j) => (j === i ? e.target.value : wt)),
                        }))
                      }
                      className="w-full rounded-lg border border-line px-1.5 py-1"
                    />
                  </label>
                ))}
              </div>
            </div>
          )}
          <div>
            <p className="text-[10px] font-bold uppercase text-slate-body/60">Truck info</p>
            {vehicleInputs('truck')}
          </div>
          <div>
            <p className="text-[10px] font-bold uppercase text-slate-body/60">Trailer info</p>
            {vehicleInputs('trailer')}
            <label className="mt-2 flex items-center gap-2">
              <span className="text-[10px] font-bold uppercase text-slate-body/60">Trailer axles</span>
              <input
                type="number"
                min={1}
                max={30}
                value={form.trailerAxles}
                onChange={(e) => setForm((f) => ({ ...f, trailerAxles: e.target.value }))}
                className="w-16 rounded-lg border border-line px-2 py-1.5"
              />
            </label>
          </div>
          <div className="flex gap-2 pt-1">
            <button
              onClick={save}
              disabled={saving}
              className="rounded-lg bg-navy-900 px-3 py-2 text-[11px] font-bold text-white disabled:opacity-60"
            >
              {saving ? 'Saving…' : 'Save unit profile'}
            </button>
            <button
              onClick={() => setEditing(false)}
              disabled={saving}
              className="rounded-lg px-3 py-2 text-[11px] font-bold text-slate-body"
            >
              Cancel
            </button>
          </div>
        </div>
      </Card>
    )
  }

  return (
    <>
      <Card>
        <div className="flex items-center justify-between">
          <CardHead label="Axles" />
          <button
            onClick={startEdit}
            className="rounded-lg border border-line px-2.5 py-1 text-[11px] font-bold text-slate-body hover:text-ink"
          >
            Edit
          </button>
        </div>
        <p className="mt-2 text-sm font-extrabold">
          {unit?.axle_count != null ? `${unit.axle_count}-axle` : '—'}
        </p>
        <div className="mt-2 text-xs text-slate-body">
          <p className="text-[10px] font-bold uppercase text-slate-body/60">Axle spacings</p>
          <p className="mt-0.5 font-mono">
            {unit?.axle_spacings_in?.length
              ? unit.axle_spacings_in.map((v) => formatInches(v)).join(' · ')
              : '—'}
          </p>
          <p className="mt-2 text-[10px] font-bold uppercase text-slate-body/60">
            Kingpin → rear axle (KPTRA)
          </p>
          <p className="mt-0.5 font-mono">
            {unit?.kingpin_to_rear_axle_in != null ? formatInches(unit.kingpin_to_rear_axle_in) : '—'}
          </p>
          <p className="mt-2 text-[10px] font-bold uppercase text-slate-body/60">Axle weights</p>
          <p className="mt-0.5 font-mono">
            {unit?.axle_weights_lbs?.length
              ? unit.axle_weights_lbs.map((v) => formatWeight(v)).join(' · ')
              : '—'}
          </p>
        </div>
        <p className="mt-2 text-[10px] leading-relaxed text-slate-body/70">
          Pulled from the permits when available and pre-filled for you — correct anything that’s
          wrong. The spacing cross-check against your permits activates with permit extraction.
        </p>
      </Card>

      {(
        [
          // Plate + registration state are PLACEHOLDERS until they are pulled
          // from the orders (Nash, 2026-09-12: "we can pull those from the
          // orders… for now put state of Washington for the trucks, Maine for
          // the trailers"). `trip_units` has no plate columns yet — Phase 2.
          ['Truck info', {
            unit: unit?.truck_unit_number ?? null,
            vin: unit?.truck_vin ?? null,
            make: unit?.truck_make ?? null,
            model: unit?.truck_model ?? null,
            year: unit?.truck_year ?? null,
            plate: PLACEHOLDER_PLATES.truck.plate,
            plateState: PLACEHOLDER_PLATES.truck.state,
          }],
          ['Trailer info', {
            unit: unit?.trailer_unit_number ?? null,
            vin: unit?.trailer_vin ?? null,
            make: unit?.trailer_make ?? null,
            model: unit?.trailer_model ?? null,
            year: unit?.trailer_year ?? null,
            axles: unit?.trailer_axle_count ?? null,
            plate: PLACEHOLDER_PLATES.trailer.plate,
            plateState: PLACEHOLDER_PLATES.trailer.state,
          }],
        ] as const
      ).map(([label, v]) => (
        <Card key={label}>
          <CardHead label={label} />
          <dl className="mt-2 grid grid-cols-2 gap-x-3 gap-y-2 text-xs">
            {vehicleRows(v).map(([k, val]) => (
              <div key={k}>
                <dt className="text-[10px] font-bold uppercase text-slate-body/60">{k}</dt>
                <dd className={`mt-0.5 ${k === 'VIN' ? 'break-all font-mono text-[11px]' : 'font-medium'}`}>
                  {val ?? '—'}
                </dd>
              </div>
            ))}
          </dl>
        </Card>
      ))}
      <p className="text-center text-[10px] leading-relaxed text-slate-body/70">
        This is the unit profile for this specific trip. Any change is recorded in trip history —
        everyone on the trip sees it. Plate numbers and registration states are placeholders until
        they are pulled from the order.
      </p>
    </>
  )
}

/* ---------------- Shared pieces ---------------- */

/**
 * Current-location check. Real browser geolocation; the demo trip pins the
 * detected state (Nash: "We are in Wyoming") so completed states dim and the
 * current state highlights. Live state mapping activates with the backend.
 */
function LocationCard({
  states,
  currentState,
  completedStates,
}: {
  states: string[]
  currentState: string | null
  completedStates: string[]
}) {
  const [status, setStatus] = useState<'idle' | 'locating' | 'done' | 'denied'>('idle')
  const [coords, setCoords] = useState<{ lat: number; lon: number } | null>(null)
  const completed = new Set(completedStates)

  function locate() {
    if (!navigator.geolocation) {
      setStatus('denied')
      return
    }
    setStatus('locating')
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        setCoords({ lat: pos.coords.latitude, lon: pos.coords.longitude })
        setStatus('done')
      },
      () => setStatus('denied'),
      { timeout: 10_000 },
    )
  }

  return (
    <Card>
      <CardHead label="Current Location" />
      {currentState ? (
        <p className="mt-1.5 text-sm font-bold text-ink">
          📍 You are in {stateName(currentState)}
        </p>
      ) : status === 'done' && coords ? (
        <>
          <p className="mt-1.5 text-sm font-bold text-ok">✓ Location detected</p>
          <p className="mt-0.5 font-mono text-[11px] text-slate-body">
            {coords.lat.toFixed(4)}, {coords.lon.toFixed(4)}
          </p>
        </>
      ) : status === 'denied' ? (
        <p className="mt-1.5 text-xs text-slate-body">
          Location unavailable — allow location access to use this check.
        </p>
      ) : (
        <button
          onClick={locate}
          disabled={status === 'locating'}
          className="mt-2 w-full rounded-xl bg-navy-900 py-2.5 text-xs font-bold text-white disabled:opacity-60"
        >
          {status === 'locating' ? 'Locating…' : '📍 Check my location'}
        </button>
      )}
      {states.length > 0 && (
        <div className="mt-3 flex flex-wrap items-center gap-1.5">
          {states.map((s, i) => (
            <span key={s} className="flex items-center gap-1.5">
              <span
                className={`rounded-full border px-2.5 py-1 font-mono text-[10px] font-bold ${
                  s === currentState
                    ? 'border-amber-brand bg-amber-brand text-navy-900'
                    : completed.has(s)
                      ? 'border-line bg-paper text-slate-body/40'
                      : 'border-line bg-paper text-slate-body'
                }`}
              >
                {s === currentState ? `📍 ${s}` : s}
              </span>
              {i < states.length - 1 && <span className="text-[10px] text-slate-body/50">→</span>}
            </span>
          ))}
        </div>
      )}
      <p className="mt-2 text-[10px] leading-relaxed text-slate-body/70">
        {currentState
          ? 'Demo — the current state is pinned for this trip. Live state detection activates with the app backend.'
          : 'State detection and location-aware alerts (passed states turn off, current and next states highlight) activate with the app backend.'}
      </p>
    </Card>
  )
}

function PreviousTrips({ trips }: { trips: Trip[] }) {
  return (
    <section className="mt-2">
      <p className="mb-2 text-[10px] font-bold uppercase tracking-[0.14em] text-slate-body/70">
        History
      </p>
      <div className="space-y-1.5">
        {trips.map((t) => (
          <Link
            key={t.id}
            href={driverTripHref(t.ref_code)}
            className="flex items-center justify-between rounded-xl border border-line bg-white p-3 text-left text-xs"
          >
            <span className="truncate" title={`${t.origin} → ${t.destination}`}>
              {cityState(t.origin) || 'Origin TBD'} → {cityState(t.destination) || 'Destination TBD'}
            </span>
            <span className="ml-2 shrink-0 text-slate-body">
              {formatFullDate(t.completed_at ?? t.updated_at)}
            </span>
          </Link>
        ))}
      </div>
    </section>
  )
}

function Card({ children, tone }: { children: React.ReactNode; tone?: 'warn' | 'danger' }) {
  const ring =
    tone === 'warn'
      ? 'border-warn/30 bg-warn-bg'
      : tone === 'danger'
        ? 'border-danger/30 bg-danger-bg'
        : 'border-line bg-white'
  return <section className={`rounded-2xl border p-4 shadow-sm ${ring}`}>{children}</section>
}

function CardHead({ label, action }: { label: string; action?: { href: string; text: string } }) {
  return (
    <div className="flex items-center justify-between">
      <p className="text-[10px] font-bold uppercase tracking-[0.14em] text-slate-body/70">{label}</p>
      {action && (
        <Link href={action.href} className="text-[11px] font-bold text-info hover:underline">
          {action.text}
        </Link>
      )}
    </div>
  )
}
