import { useState, useEffect, useRef, useCallback } from 'react'
import { RefreshCw, Clock } from 'lucide-react'
import {
  getBookings, getConfig, putStaff, putPickup,
  putLastReviewed, putAdjustments, syncWorkforce, syncTimesheets, getWorkforceShifts, getWorkforceStaff,
} from '../api'
import type {
  BookingsData, TimeReqs, StaffMember, GeneralTask,
  PickupData, RequiredDay, DayData, WfDayData, WfStaffMember, Adjustment,
} from '../types'

// ── Date helpers ──────────────────────────────────────────────────────────────

function todayStr() {
  const d = new Date()
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}

function offsetDate(s: string, days: number) {
  const [y, m, d] = s.split('-').map(Number)
  const dt = new Date(y, m - 1, d)
  dt.setDate(dt.getDate() + days)
  return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}-${String(dt.getDate()).padStart(2, '0')}`
}

function fmtDayHeader(date: string) {
  const d = new Date(date + 'T00:00:00')
  return ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][d.getDay()] + ' ' + d.getDate() + ' ' +
    ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][d.getMonth()]
}

function getDayName(date: string) {
  const d = new Date(date + 'T00:00:00')
  return ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'][(d.getDay() + 6) % 7]
}

function fullDayName(date: string) {
  const d = new Date(date + 'T00:00:00')
  return ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'][d.getDay()]
}

function isWeekend(date: string) {
  const day = new Date(date + 'T00:00:00').getDay()
  return day === 0 || day === 6
}

function priorLineFor(date: string, d: DayData): string {
  const hint = d.pickup_hint ?? 0
  const lead = d.pickup_lead ?? 0
  const late = hint > 0
    ? `${hint} room${hint === 1 ? '' : 's'} picked up within ${lead} day${lead === 1 ? '' : 's'} of arrival`
    : 'no late pickups'
  return `Last ${fullDayName(date)}: ${d.prior_occ} occ (${d.prior_vac} vac) — ${late}`
}

function fmtDate(date: string) {
  return new Date(date + 'T00:00:00').toLocaleDateString('en-GB', {
    weekday: 'short', day: 'numeric', month: 'short',
  })
}

function fmtH(n: number) { return n.toFixed(2) + 'h' }

// ── Pickup calculation ────────────────────────────────────────────────────────

function getDisplayPickup(
  pickup: PickupData, catId: string, date: string, occupiedNow: number, totalRooms: number,
): number {
  const vacant = Math.max(0, totalRooms - occupiedNow)
  const saved = pickup[catId]?.[date]
  if (!saved || !saved.count) return 0
  const savedBooked = saved.total - saved.count
  const count = occupiedNow > savedBooked
    ? Math.max(0, saved.total - occupiedNow)
    : saved.count
  return Math.min(count, vacant)
}

// ── Required hours calculation ────────────────────────────────────────────────

function calcRequired(
  bookings: BookingsData, timeReqs: TimeReqs, pickup: PickupData,
  generalTasks: GeneralTask[], adjustments: Adjustment[],
): Record<string, RequiredDay> {
  const result: Record<string, RequiredDay> = {}

  for (const date of bookings.dates) {
    const dayName = getDayName(date)
    const genHrs = generalTasks.reduce((s, t) => s + ((t.hours[dayName] || 0) / 60), 0)
    const adjHrs = adjustments.reduce((s, a) => s + (a.hours[date] || 0), 0)
    result[date] = { booked: 0, pickup: 0, general: genHrs, adjustments: adjHrs, total: genHrs + adjHrs, by_cat: {}, pickup_by_cat: {} }
  }

  for (const cat of bookings.categories) {
    const req = timeReqs[cat.id] || { depart: 0, stay: 0, arrive: 0 }
    bookings.dates.forEach((date, di) => {
      const d = cat.days[date]
      const occupied = d.stays + d.arrivals
      const pickupCount = getDisplayPickup(pickup, cat.id, date, occupied, cat.total_rooms)

      const bookedHrs      = (d.departs * (req.depart || 0) + d.stays * (req.stay || 0) + d.arrivals * (req.arrive || 0)) / 60
      const pickupArrHrs   = pickupCount * (req.arrive || 0) / 60

      result[date].by_cat[cat.id] = bookedHrs
      result[date].pickup_by_cat[cat.id] = (result[date].pickup_by_cat[cat.id] || 0) + pickupArrHrs
      result[date].booked += bookedHrs
      result[date].pickup += pickupArrHrs
      result[date].total  += bookedHrs + pickupArrHrs

      const nextDate = bookings.dates[di + 1]
      if (nextDate && pickupCount > 0) {
        const pickupDepHrs = pickupCount * (req.depart || 0) / 60
        result[nextDate].pickup_by_cat[cat.id] = (result[nextDate].pickup_by_cat[cat.id] || 0) + pickupDepHrs
        result[nextDate].pickup += pickupDepHrs
        result[nextDate].total  += pickupDepHrs
      }
    })
  }

  return result
}

// ── Main Planner component ────────────────────────────────────────────────────

export function Planner() {
  const [bookings, setBookings]         = useState<BookingsData | null>(null)
  const [timeReqs, setTimeReqs]         = useState<TimeReqs>({})
  const [staff, setStaff]               = useState<StaffMember[]>([])
  const [pickup, setPickup]             = useState<PickupData>({})
  const [generalTasks, setGeneralTasks] = useState<GeneralTask[]>([])
  const [adjustments, setAdjustments]     = useState<Adjustment[]>([])
  const [warnOverRed, setWarnOverRed]     = useState(4)
  const [warnOverAmber, setWarnOverAmber] = useState(1)
  const [warnUnderAmber, setWarnUnderAmber] = useState(1)
  const [warnUnderRed, setWarnUnderRed]   = useState(2)
  const [wfShifts, setWfShifts]         = useState<Record<string, WfDayData>>({})
  const [wfStaff, setWfStaff]           = useState<{ id: string; name: string }[]>([])
  const [syncing, setSyncing]                   = useState(false)
  const [timesheetSyncing, setTimesheetSyncing] = useState(false)
  const [weekStart, setWeekStart]       = useState<string | null>(null)
  const [lastViewed, setLastViewed]     = useState('')
  const [savedLastReviewed, setSavedLastReviewed] = useState('')
  const [loading, setLoading]           = useState(true)
  const [error, setError]               = useState('')
  const [saveMsg, setSaveMsg]           = useState<{ text: string; err: boolean } | null>(null)

  const timers = useRef<Record<string, ReturnType<typeof setTimeout>>>({})
  const saveMsgTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
  const wfStaffLoaded = useRef(false)
  const weekStartRef = useRef<string | null>(null)
  weekStartRef.current = weekStart // always current, updated every render

  function debounce(key: string, fn: () => void, delay = 400) {
    clearTimeout(timers.current[key])
    timers.current[key] = setTimeout(fn, delay)
  }

  function flash(text: string, err = false) {
    if (saveMsgTimer.current) clearTimeout(saveMsgTimer.current)
    setSaveMsg({ text, err })
    saveMsgTimer.current = setTimeout(() => setSaveMsg(null), err ? 15000 : 2500)
  }

  function stampLastReviewed() {
    const today = todayStr()
    if (today === savedLastReviewed) return
    setSavedLastReviewed(today)
    putLastReviewed(today).catch(() => {})
  }

  const loadAll = useCallback(async (force = false, lvArg?: string) => {
    setLoading(true)
    setError('')
    const today = todayStr()
    const ws    = weekStartRef.current || today
    const wsEnd = offsetDate(ws, 6)
    const lv = lvArg !== undefined ? (lvArg || offsetDate(today, -1)) : (lastViewed || savedLastReviewed || offsetDate(today, -1))
    try {
      const [b, cfg, shifts] = await Promise.all([
        getBookings(ws, lv, force),
        getConfig(),
        getWorkforceShifts(ws, wsEnd).catch(() => ({} as Record<string, WfDayData>)),
      ])
      setBookings(b)
      setTimeReqs(cfg.time_requirements || {})
      setStaff(cfg.staff_data || [])
      setPickup(cfg.pickup_data || {})
      setGeneralTasks(cfg.general_tasks || [])
      setAdjustments(cfg.adjustments || [])
      setWarnOverRed(cfg.warn_over_red_hrs ?? 4)
      setWarnOverAmber(cfg.warn_over_amber_hrs ?? 1)
      setWarnUnderAmber(cfg.warn_under_amber_hrs ?? 1)
      setWarnUnderRed(cfg.warn_under_red_hrs ?? 2)
      setWfShifts(shifts)
      if (cfg.last_reviewed) {
        setSavedLastReviewed(cfg.last_reviewed)
        if (!lastViewed) setLastViewed(cfg.last_reviewed)
      }
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Failed to load data')
    } finally {
      setLoading(false)
    }
  }, [lastViewed, savedLastReviewed])

  useEffect(() => { loadAll(false) }, []) // eslint-disable-line react-hooks/exhaustive-deps

  // Lazily load WF staff list for datalist once any shifts exist
  useEffect(() => {
    if (Object.keys(wfShifts).length > 0 && !wfStaffLoaded.current) {
      wfStaffLoaded.current = true
      getWorkforceStaff().then(setWfStaff).catch(() => {})
    }
  }, [wfShifts])

  // Keepalive save on unload (sendBeacon only supports POST but routes are PUT)
  useEffect(() => {
    function keepalivePut(path: string, body: unknown) {
      fetch(path, {
        method: 'PUT', keepalive: true, credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      })
    }
    function onUnload() {
      if (staff.length)              keepalivePut('/hk-planner/api/config/staff',       { staff_data: staff })
      if (Object.keys(pickup).length) keepalivePut('/hk-planner/api/config/pickup',      { pickup_data: pickup })
      if (adjustments.length)        keepalivePut('/hk-planner/api/config/adjustments',  { adjustments })
    }
    window.addEventListener('beforeunload', onUnload)
    return () => window.removeEventListener('beforeunload', onUnload)
  }, [staff, pickup, adjustments])

  function handleWeekOffset(days: number) {
    const today = todayStr()
    const newStart = offsetDate(weekStartRef.current || today, days)
    weekStartRef.current = newStart
    setWeekStart(newStart)
    stampLastReviewed()
    loadAll(true)
  }

  function handleWeekToday() {
    weekStartRef.current = null
    setWeekStart(null)
    stampLastReviewed()
    loadAll(true)
  }

  function handleRefresh() {
    stampLastReviewed()
    loadAll(true)
  }

  // ── Pickup ──────────────────────────────────────────────────────────────────

  function handlePickupChange(catId: string, date: string, delta: number, cat: { total_rooms: number; days: Record<string, DayData> }) {
    const d = cat.days[date]
    const occupied = d.stays + d.arrivals
    const vacant = Math.max(0, cat.total_rooms - occupied)
    const current = getDisplayPickup(pickup, catId, date, occupied, cat.total_rooms)
    const newVal = Math.max(0, Math.min(current + delta, vacant))

    const next: PickupData = {
      ...pickup,
      [catId]: { ...(pickup[catId] || {}), [date]: { count: newVal, total: occupied + newVal } },
    }
    setPickup(next)
    stampLastReviewed()
    debounce('pickup', () => putPickup(next).then(() => flash('Pickup saved')).catch(e => flash(e.message, true)))
  }

  // ── Adjustments ─────────────────────────────────────────────────────────────

  function handleAdjustmentsChange(next: Adjustment[]) {
    setAdjustments(next)
    debounce('adjustments', () =>
      putAdjustments(next).then(() => flash('Adjustments saved')).catch(e => flash(e.message, true))
    )
  }

  function addAdjustmentRow() {
    handleAdjustmentsChange([...adjustments, { label: '', hours: {} }])
  }

  // ── Staff ────────────────────────────────────────────────────────────────────

  function handleStaffChange(next: StaffMember[]) {
    setStaff(next)
    stampLastReviewed()
    debounce('staff', () => putStaff(next).then(() => flash('Staff hours saved')).catch(e => flash(e.message, true)))
  }

  function addStaffRow() {
    handleStaffChange([...staff, { name: '', hours: {} }])
  }

  // ── Workforce sync ───────────────────────────────────────────────────────────

  async function syncRota() {
    if (!bookings) return
    const start = bookings.dates[0]
    const end   = bookings.dates[bookings.dates.length - 1]
    setSyncing(true)
    try {
      await syncWorkforce(start, end)
      const shifts = await getWorkforceShifts(start, end)
      setWfShifts(shifts)
      flash('Rota synced from Workforce')
    } catch (e) {
      flash(e instanceof Error ? e.message : 'Sync failed', true)
    } finally {
      setSyncing(false)
    }
  }

  async function syncTimesheetData() {
    if (!bookings) return
    const start = bookings.dates[0]
    const today = todayStr()
    const end   = bookings.dates[bookings.dates.length - 1] > today ? today : bookings.dates[bookings.dates.length - 1]
    setTimesheetSyncing(true)
    try {
      await syncTimesheets(start, end)
      const shifts = await getWorkforceShifts(start, end)
      setWfShifts(shifts)
      flash('Timesheets pulled from Workforce')
    } catch (e) {
      flash(e instanceof Error ? e.message : 'Timesheet sync failed', true)
    } finally {
      setTimesheetSyncing(false)
    }
  }

  function wfSyncLabel(): string {
    if (!bookings) return ''
    const viewDates = bookings.dates.filter(d => wfShifts[d])
    if (!viewDates.length) return 'Not synced'
    const oldest = viewDates.reduce((min, d) =>
      new Date(wfShifts[d].synced_at) < new Date(wfShifts[min].synced_at) ? d : min
    )
    const dt = new Date(wfShifts[oldest].synced_at)
    const day  = dt.toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' })
    const time = dt.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })
    const missing = bookings.dates.filter(d => !wfShifts[d]).length
    return missing ? `Partial sync — from ${day} ${time}` : `From ${day} ${time}`
  }

  // ── Required hours (memoised on state changes) ────────────────────────────

  const required = bookings ? calcRequired(bookings, timeReqs, pickup, generalTasks, adjustments) : null

  // ── Render ──────────────────────────────────────────────────────────────────

  const today = todayStr()
  const displayWeekStart = weekStart || today

  return (
    <div style={{ padding: '1.5rem', maxWidth: '1600px' }}>

      {/* Controls */}
      <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: '0.75rem', marginBottom: '1.25rem' }}>
        <div>
          <h1 style={{ fontSize: '1.15rem', fontWeight: 700, color: 'var(--text-dark)' }}>
            Housekeeping Planner
          </h1>
          {bookings && (
            <p style={{ fontSize: '0.8rem', color: 'var(--text-mid)', marginTop: '0.1rem' }}>
              {fmtDate(bookings.dates[0])} – {fmtDate(bookings.dates[bookings.dates.length - 1])}
            </p>
          )}
        </div>

        <div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', marginLeft: 'auto', flexWrap: 'wrap' }}>
          {/* Week navigation */}
          <CtrlBtn onClick={() => handleWeekOffset(-7)}>‹ Prev</CtrlBtn>
          <CtrlBtn onClick={handleWeekToday} primary>Today</CtrlBtn>
          <CtrlBtn onClick={() => handleWeekOffset(7)}>Next ›</CtrlBtn>
          <input
            type="date"
            value={displayWeekStart}
            onChange={e => { if (e.target.value) { const v = e.target.value; weekStartRef.current = v; setWeekStart(v); stampLastReviewed(); debounce('weekStart', () => loadAll(true), 600) } }}
            style={{ border: '1px solid var(--card-border)', borderRadius: '6px', padding: '0.35rem 0.5rem', fontSize: '0.82rem', color: 'var(--text-dark)' }}
          />
          <div style={{ width: '1px', height: '24px', background: 'var(--card-border)' }} />
          {/* Last-reviewed */}
          <label style={{ fontSize: '0.78rem', color: 'var(--text-mid)', display: 'flex', alignItems: 'center', gap: '0.3rem' }}>
            <span>Since</span>
            <input
              type="date"
              value={lastViewed}
              onChange={e => { if (e.target.value) { const v = e.target.value; setLastViewed(v); debounce('lastViewed', () => loadAll(true, v), 600) } }}
              style={{ border: '1px solid var(--card-border)', borderRadius: '6px', padding: '0.3rem 0.5rem', fontSize: '0.78rem', color: 'var(--text-dark)' }}
            />
          </label>
          <CtrlBtn onClick={() => { stampLastReviewed(); loadAll(true) }}>Update to now</CtrlBtn>
          <div style={{ width: '1px', height: '24px', background: 'var(--card-border)' }} />
          <button onClick={handleRefresh} title="Refresh" style={{
            background: 'var(--card-bg)', border: '1px solid var(--card-border)', borderRadius: '6px',
            padding: '0.35rem 0.5rem', display: 'flex', alignItems: 'center', gap: '0.3rem',
            fontSize: '0.82rem', color: 'var(--text-dark)',
          }}>
            <RefreshCw size={13} strokeWidth={1.75} style={{ animation: loading ? 'spin 1s linear infinite' : 'none' }} />
          </button>
        </div>
      </div>

      {/* Status */}
      <style>{`@keyframes spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }`}</style>
      {saveMsg && (
        <div style={{
          display: 'inline-block', marginBottom: '0.75rem', padding: '0.35rem 0.75rem',
          borderRadius: '6px', fontSize: '0.8rem', fontWeight: 500,
          background: saveMsg.err ? '#fee2e2' : '#dcfce7',
          color: saveMsg.err ? 'var(--danger)' : 'var(--success)',
        }}>
          {saveMsg.text}
        </div>
      )}

      {error && (
        <div style={{
          marginBottom: '1rem', padding: '0.75rem 1rem', borderRadius: '8px',
          background: '#fee2e2', color: 'var(--danger)', fontSize: '0.875rem',
        }}>
          Error: {error}
        </div>
      )}

      {loading && !bookings && (
        <div style={{ color: 'var(--text-mid)', padding: '2rem 0', textAlign: 'center' }}>
          Loading bookings…
        </div>
      )}

      {bookings && required && (
        <>
          {/* 1 — Occupancy summary */}
          <Section title="7-Day Occupancy">
            <div className="table-scroll">
              <SummaryTable bookings={bookings} pickup={pickup} onPickupChange={handlePickupChange} />
            </div>
          </Section>

          {/* 2 — Required hours */}
          <Section title="Required Hours">
            <div className="table-scroll">
              <RequiredTable bookings={bookings} required={required} />
            </div>
          </Section>

          {/* 3 — Adjustments */}
          <Section title="Adjustments" action={<CtrlBtn onClick={addAdjustmentRow}>+ Add adjustment</CtrlBtn>}>
            <div className="table-scroll">
              <AdjustmentsTable
                bookings={bookings}
                adjustments={adjustments}
                onChange={handleAdjustmentsChange}
              />
            </div>
          </Section>

          {/* 4 — Staff rota */}
          <Section
            title="Staff Rota"
            action={
              <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>
                <button
                  onClick={syncRota}
                  disabled={syncing || timesheetSyncing}
                  style={{
                    display: 'flex', alignItems: 'center', gap: '0.3rem',
                    background: 'var(--card-bg)', color: 'var(--text-dark)',
                    border: '1px solid var(--card-border)', borderRadius: '6px',
                    padding: '0.3rem 0.65rem', fontSize: '0.78rem', fontWeight: 600,
                  }}
                >
                  <RefreshCw size={11} strokeWidth={1.75} style={{ animation: syncing ? 'spin 1s linear infinite' : 'none' }} />
                  {syncing ? 'Syncing…' : 'Sync rota'}
                </button>
                <button
                  onClick={syncTimesheetData}
                  disabled={syncing || timesheetSyncing}
                  style={{
                    display: 'flex', alignItems: 'center', gap: '0.3rem',
                    background: 'var(--card-bg)', color: 'var(--text-dark)',
                    border: '1px solid var(--card-border)', borderRadius: '6px',
                    padding: '0.3rem 0.65rem', fontSize: '0.78rem', fontWeight: 600,
                  }}
                >
                  <Clock size={11} strokeWidth={1.75} style={{ animation: timesheetSyncing ? 'spin 1s linear infinite' : 'none' }} />
                  {timesheetSyncing ? 'Pulling…' : 'Pull timesheets'}
                </button>
                <span style={{ fontSize: '0.72rem', color: 'var(--text-mid)' }}>{wfSyncLabel()}</span>
                <CtrlBtn onClick={addStaffRow}>+ Add staff</CtrlBtn>
              </div>
            }
          >
            <div className="table-scroll">
              <StaffTable
                bookings={bookings}
                staff={staff}
                required={required}
                warnOverRed={warnOverRed}
                warnOverAmber={warnOverAmber}
                warnUnderAmber={warnUnderAmber}
                warnUnderRed={warnUnderRed}
                onChange={handleStaffChange}
                wfShifts={wfShifts}
                wfStaff={wfStaff}
              />
            </div>
          </Section>

        </>
      )}
    </div>
  )
}

// ── Section wrapper ───────────────────────────────────────────────────────────

function Section({ title, children, action }: { title: string; children: React.ReactNode; action?: React.ReactNode }) {
  return (
    <div style={{ marginBottom: '1.5rem' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '0.5rem' }}>
        <h2 style={{ fontSize: '0.72rem', fontWeight: 700, color: 'var(--text-mid)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>
          {title}
        </h2>
        {action}
      </div>
      {children}
    </div>
  )
}

function CtrlBtn({ children, onClick, primary }: { children: React.ReactNode; onClick?: () => void; primary?: boolean }) {
  return (
    <button onClick={onClick} style={{
      background: primary ? 'var(--hk-green)' : 'var(--card-bg)',
      color: primary ? '#fff' : 'var(--text-dark)',
      border: `1px solid ${primary ? 'var(--hk-green)' : 'var(--card-border)'}`,
      borderRadius: '6px', padding: '0.35rem 0.75rem', fontSize: '0.82rem', fontWeight: 600,
    }}>
      {children}
    </button>
  )
}

// ── Summary Table ─────────────────────────────────────────────────────────────

interface SummaryProps {
  bookings: BookingsData
  pickup: PickupData
  onPickupChange: (catId: string, date: string, delta: number, cat: { total_rooms: number; days: Record<string, DayData> }) => void
}

function SummaryTable({ bookings, pickup, onPickupChange }: SummaryProps) {
  const { dates, categories: cats } = bookings

  return (
    <table className="hk-table">
      <thead>
        <tr>
          <th className="col-label" rowSpan={2}>Category</th>
          {dates.map(d => (
            <th key={d} colSpan={2} style={{
              borderLeft: '2px solid rgba(255,255,255,0.3)',
              ...(isWeekend(d) ? { background: '#253555' } : {}),
            }}>{fmtDayHeader(d)}</th>
          ))}
        </tr>
        <tr>
          {dates.map(d => (
            <>
              <th key={d + '-r'} style={{
                fontSize: '0.72rem', fontWeight: 400, minWidth: '72px',
                background: isWeekend(d) ? '#2a3d5e' : '#1e2d42',
                borderLeft: '2px solid rgba(255,255,255,0.3)',
              }}>Rooms</th>
              <th key={d + '-d'} style={{
                fontSize: '0.72rem', fontWeight: 400, minWidth: '56px', textAlign: 'center',
                background: isWeekend(d) ? '#2a3d5e' : '#1e2d42',
                borderLeft: '1px solid rgba(255,255,255,0.1)',
              }}>D / S / A</th>
            </>
          ))}
        </tr>
      </thead>
      <tbody>
        {cats.map((cat, ci) => {
          const isLast = ci === cats.length - 1
          return (
            <CatRows key={cat.id} cat={cat} dates={dates} pickup={pickup}
              onPickupChange={(date, delta) => onPickupChange(cat.id, date, delta, cat)}
              isLast={isLast}
            />
          )
        })}
      </tbody>
      <tfoot>
        <TotalRows dates={dates} cats={cats} pickup={pickup} />
      </tfoot>
    </table>
  )
}

function CatRows({ cat, dates, pickup, onPickupChange, isLast }: {
  cat: { id: string; name: string; total_rooms: number; days: Record<string, DayData> }
  dates: string[]
  pickup: PickupData
  onPickupChange: (date: string, delta: number) => void
  isLast: boolean
}) {
  const bbStyle = isLast ? '2px solid var(--card-border)' : undefined
  return (
    <tr>
      <td className="col-label" style={{ borderBottom: bbStyle }}>{cat.name}</td>
      {dates.map((date, di) => {
        const d        = cat.days[date]
        const occupied = d.stays + d.arrivals
        const vacant   = Math.max(0, cat.total_rooms - occupied)
        const pu       = getDisplayPickup(pickup, cat.id, date, occupied, cat.total_rooms)
        const prevDate = dates[di - 1]
        const prevOcc  = prevDate ? (cat.days[prevDate].stays + cat.days[prevDate].arrivals) : 0
        const prevPu   = prevDate ? getDisplayPickup(pickup, cat.id, prevDate, prevOcc, cat.total_rooms) : 0
        const dNew     = d.delta_new       || 0
        const dCanc    = d.delta_cancelled || 0
        const wknd     = isWeekend(date)
        const saved    = pickup[cat.id]?.[date]
        const priorLine = priorLineFor(date, d)
        let occTooltip = ''
        if (saved && saved.count) {
          const savedBooked = saved.total - saved.count
          const changeDesc  = occupied > savedBooked ? 'changed' : 'unchanged'
          const pickupDesc  = occupied > savedBooked ? 'decreased' : 'remains'
          occTooltip = `+${saved.count} pickup set when booked was ${savedBooked} (target ${saved.total}). Booked ${changeDesc}, so pickup ${pickupDesc}.`
        }
        occTooltip = (occTooltip ? occTooltip + ' | ' : '') + priorLine

        return (
          <>
            {/* Left cell: delta ticker + Rooms / vac / pickup controls */}
            <td key={date + '-r'} style={{
              textAlign: 'center', verticalAlign: 'middle', borderBottom: bbStyle,
              padding: '0.4rem 0.3rem',
              borderLeft: '2px solid var(--card-border)',
              ...(wknd ? { background: 'rgba(100,120,160,0.07)' } : {}),
            }}>
              {(dNew > 0 || dCanc > 0) && (
                <div style={{ marginBottom: '2px', fontSize: '0.72rem' }}>
                  {dNew  > 0 && <span className="delta-new">▲{dNew}</span>}
                  {dCanc > 0 && <span className="delta-canc"> ▼{dCanc}</span>}
                </div>
              )}
              <div style={{ fontWeight: 700, fontSize: '1.1rem' }} title={occTooltip}>
                {occupied}{pu > 0 && <span className="pickup-tag"> +{pu}</span>}
              </div>
              <div style={{ fontSize: '0.72rem', color: 'var(--text-mid)', marginBottom: '4px' }}>
                {vacant} vac{pu > 0 && <span className="pickup-tag"> ({vacant - pu})</span>}
              </div>
              <div className="pickup-ctrl">
                <button className="pickup-btn" disabled={pu <= 0} onClick={() => onPickupChange(date, -1)}>−</button>
                <span className="pickup-num">{pu}</span>
                <button className="pickup-btn" disabled={pu >= vacant}
                  title={priorLine}
                  onClick={() => onPickupChange(date, 1)}>+</button>
              </div>
            </td>
            {/* Right cell: D / S / A */}
            <td key={date + '-dsa'} style={{
              verticalAlign: 'middle', borderLeft: '1px solid var(--card-border)',
              borderBottom: bbStyle, padding: '0.4rem 0.3rem', textAlign: 'center',
              ...(wknd ? { background: 'rgba(100,120,160,0.07)' } : {}),
            }}>
              <div style={{ fontSize: '0.8rem', color: '#c2502e', fontWeight: 600 }}>
                {d.departs}d{prevPu > 0 && <span className="pickup-tag"> +{prevPu}</span>}
              </div>
              <div style={{ fontSize: '0.8rem', color: '#1d6fb8', fontWeight: 600 }}>
                {d.stays}s
              </div>
              <div style={{ fontSize: '0.8rem', color: '#1a7a4a', fontWeight: 600 }}>
                {d.arrivals}a{pu > 0 && <span className="pickup-tag"> +{pu}</span>}
              </div>
            </td>
          </>
        )
      })}
    </tr>
  )
}

function TotalRows({ dates, cats, pickup }: {
  dates: string[]
  cats: BookingsData['categories']
  pickup: PickupData
}) {
  return (
    <tr>
      <td className="col-label" style={{ fontStyle: 'italic' }}>Total</td>
      {dates.map((date, di) => {
        let occupied = 0, vacant = 0, pu = 0, prevPu = 0, departs = 0, stays = 0, arrivals = 0
        let dNew = 0, dCanc = 0
        for (const cat of cats) {
          const d     = cat.days[date]
          const occ   = d.stays + d.arrivals
          const vac   = Math.max(0, cat.total_rooms - occ)
          const p     = getDisplayPickup(pickup, cat.id, date, occ, cat.total_rooms)
          const prev  = dates[di - 1]
          const pOcc  = prev ? (cat.days[prev].stays + cat.days[prev].arrivals) : 0
          const pPu   = prev ? getDisplayPickup(pickup, cat.id, prev, pOcc, cat.total_rooms) : 0
          occupied += occ; vacant += vac; pu += p; prevPu += pPu
          departs += d.departs; stays += d.stays; arrivals += d.arrivals
          dNew  += d.delta_new       || 0
          dCanc += d.delta_cancelled || 0
        }
        const wknd = isWeekend(date)
        return (
          <>
            <td key={date + '-r'} style={{
              textAlign: 'center', verticalAlign: 'middle', padding: '0.4rem 0.3rem',
              borderLeft: '2px solid var(--card-border)',
              ...(wknd ? { background: 'rgba(100,120,160,0.07)' } : {}),
            }}>
              {(dNew > 0 || dCanc > 0) && (
                <div style={{ marginBottom: '2px', fontSize: '0.72rem' }}>
                  {dNew  > 0 && <span className="delta-new">▲{dNew}</span>}
                  {dCanc > 0 && <span className="delta-canc"> ▼{dCanc}</span>}
                </div>
              )}
              <div style={{ fontWeight: 700, fontSize: '1.1rem' }}>
                {occupied}{pu > 0 && <span className="pickup-tag"> +{pu}</span>}
              </div>
              <div style={{ fontSize: '0.72rem', color: 'var(--text-mid)' }}>
                {vacant} vac{pu > 0 && <span className="pickup-tag"> ({vacant - pu})</span>}
              </div>
            </td>
            <td key={date + '-dsa'} style={{
              verticalAlign: 'middle', borderLeft: '1px solid var(--card-border)',
              padding: '0.4rem 0.3rem', textAlign: 'center',
              ...(wknd ? { background: 'rgba(100,120,160,0.07)' } : {}),
            }}>
              <div style={{ fontSize: '0.8rem', color: '#c2502e', fontWeight: 600 }}>
                {departs}d{prevPu > 0 && <span className="pickup-tag"> +{prevPu}</span>}
              </div>
              <div style={{ fontSize: '0.8rem', color: '#1d6fb8', fontWeight: 600 }}>{stays}s</div>
              <div style={{ fontSize: '0.8rem', color: '#1a7a4a', fontWeight: 600 }}>
                {arrivals}a{pu > 0 && <span className="pickup-tag"> +{pu}</span>}
              </div>
            </td>
          </>
        )
      })}
    </tr>
  )
}

// ── Required Hours Table ──────────────────────────────────────────────────────

function RequiredTable({ bookings, required }: { bookings: BookingsData; required: Record<string, RequiredDay> }) {
  const { dates, categories: cats } = bookings
  return (
    <table className="hk-table">
      <thead>
        <tr>
          <th className="col-label">Category</th>
          {dates.map(d => <th key={d}>{fmtDayHeader(d)}</th>)}
        </tr>
      </thead>
      <tbody>
        {cats.map(cat => (
          <tr key={cat.id}>
            <td className="col-label">{cat.name}</td>
            {dates.map(date => {
              const booked = required[date].by_cat[cat.id] || 0
              const pu     = required[date].pickup_by_cat[cat.id] || 0
              return (
                <td key={date}>
                  {booked === 0 && pu === 0 ? '—' : fmtH(booked)}
                  {pu > 0.001 && <span className="pickup-tag"> (+{fmtH(pu)})</span>}
                </td>
              )
            })}
          </tr>
        ))}
      </tbody>
      <tfoot>
        <tr style={{ background: '#f1f5f9' }}>
          <td className="col-label" style={{ color: 'var(--text-mid)', fontSize: '0.78rem' }}>Booked hrs</td>
          {dates.map(d => <td key={d}>{fmtH(required[d].booked)}</td>)}
        </tr>
        <tr style={{ background: '#f1f5f9' }}>
          <td className="col-label" style={{ color: 'var(--text-mid)', fontSize: '0.78rem' }}>Pickup hrs</td>
          {dates.map(d => <td key={d}>{required[d].pickup > 0.001 ? fmtH(required[d].pickup) : '—'}</td>)}
        </tr>
        <tr style={{ background: '#f1f5f9' }}>
          <td className="col-label" style={{ color: 'var(--text-mid)', fontSize: '0.78rem' }}>Recurring tasks</td>
          {dates.map(d => <td key={d}>{required[d].general > 0.001 ? fmtH(required[d].general) : '—'}</td>)}
        </tr>
        {dates.some(d => required[d].adjustments !== 0) && (
          <tr style={{ background: '#f1f5f9' }}>
            <td className="col-label" style={{ color: 'var(--text-mid)', fontSize: '0.78rem' }}>Adjustments</td>
            {dates.map(d => {
              const adj = required[d].adjustments
              return (
                <td key={d} style={{ color: adj < 0 ? 'var(--danger)' : adj > 0 ? '#1a7a4a' : 'var(--text-mid)' }}>
                  {adj === 0 ? '—' : (adj > 0 ? '+' : '') + fmtH(adj)}
                </td>
              )
            })}
          </tr>
        )}
        <tr style={{ background: '#e2e8f0' }}>
          <td className="col-label">Total Required</td>
          {dates.map(d => (
            <td key={d} style={{ fontWeight: 700 }}>
              {fmtH(required[d].total)}
              {required[d].pickup > 0.001 && (
                <span className="pickup-tag" style={{ display: 'block', fontSize: '0.7rem' }}>
                  inc {fmtH(required[d].pickup)} pickup
                </span>
              )}
            </td>
          ))}
        </tr>
      </tfoot>
    </table>
  )
}

// ── Staff Table ───────────────────────────────────────────────────────────────

function pivotWfShifts(wfShifts: Record<string, WfDayData>, dates: string[]): WfStaffMember[] {
  const members: Record<string, WfStaffMember> = {}
  for (const date of dates) {
    const day = wfShifts[date]
    if (!day) continue
    for (const s of day.staff) {
      if (!members[s.id]) members[s.id] = { id: s.id, name: s.name, days: {} }
      members[s.id].days[date] = { hours: s.hours, times: s.times, status: s.status }
    }
  }
  return Object.values(members).sort((a, b) => a.name.localeCompare(b.name))
}

function StaffTable({ bookings, staff, required, warnOverRed, warnOverAmber, warnUnderAmber, warnUnderRed, onChange, wfShifts, wfStaff }: {
  bookings: BookingsData
  staff: StaffMember[]
  required: Record<string, RequiredDay>
  warnOverRed: number
  warnOverAmber: number
  warnUnderAmber: number
  warnUnderRed: number
  onChange: (next: StaffMember[]) => void
  wfShifts: Record<string, WfDayData>
  wfStaff: { id: string; name: string }[]
}) {
  const { dates } = bookings

  function setMemberName(i: number, name: string) {
    const next = staff.map((m, idx) => idx === i ? { ...m, name } : m)
    onChange(next)
  }

  function setMemberHours(i: number, date: string, val: string) {
    const hrs = parseFloat(val)
    const next = staff.map((m, idx) => {
      if (idx !== i) return m
      const hours = { ...m.hours }
      if (!isNaN(hrs) && hrs >= 0) hours[date] = hrs
      else delete hours[date]
      return { ...m, hours }
    })
    onChange(next)
  }

  function removeRow(i: number) {
    onChange(staff.filter((_, idx) => idx !== i))
  }

  const rotaMembers = pivotWfShifts(wfShifts, dates)

  return (
    <table className="hk-table">
      <thead>
        <tr>
          <th className="col-label">Staff Member</th>
          {dates.map(d => <th key={d}>{fmtDayHeader(d)}</th>)}
          <th style={{ width: '32px' }}></th>
        </tr>
      </thead>
      <tbody>
        {/* Workforce rota rows — read-only */}
        {rotaMembers.map(member => (
          <tr key={'wf-' + member.id} style={{ background: 'rgba(42,100,72,0.05)' }}>
            <td className="col-label">
              <span style={{
                display: 'inline-block', fontSize: '0.67rem', fontWeight: 700,
                background: 'rgba(42,100,72,0.18)', color: '#1a7a4a',
                borderRadius: '3px', padding: '0 4px', marginRight: '0.4rem', lineHeight: '1.5',
              }}>WF</span>
              {member.name}
            </td>
            {dates.map(date => {
              const shift       = member.days[date]
              const isTimesheet = wfShifts[date]?.source === 'timesheet'
              const isPending   = isTimesheet && shift?.status !== 'APPROVED'
              return (
                <td key={date} style={{ textAlign: 'center', padding: '0.3rem 0.4rem', verticalAlign: 'middle' }}>
                  {shift ? (
                    <>
                      <div style={{ fontSize: '0.68rem', color: 'var(--text-mid)', lineHeight: 1.25 }}>{shift.times}</div>
                      <div style={{ fontWeight: 600, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '0.2rem' }}>
                        {isTimesheet && (
                          <Clock size={10} strokeWidth={1.75} style={{ color: isPending ? '#d97706' : '#1a7a4a', flexShrink: 0 }} />
                        )}
                        {fmtH(shift.hours)}
                      </div>
                    </>
                  ) : <span style={{ color: 'var(--text-mid)' }}>—</span>}
                </td>
              )
            })}
            <td />
          </tr>
        ))}
        {/* Manual rows */}
        {wfStaff.length > 0 && (
          <datalist id="wf-staff-datalist">
            {wfStaff.map(s => <option key={s.id} value={s.name} />)}
          </datalist>
        )}
        {staff.map((member, i) => (
          <tr key={i}>
            <td className="col-label" style={{ padding: '0.3rem 0.5rem' }}>
              <input
                className="hk-text-input"
                value={member.name}
                placeholder="Staff name"
                list={wfStaff.length > 0 ? 'wf-staff-datalist' : undefined}
                onChange={e => setMemberName(i, e.target.value)}
              />
            </td>
            {dates.map(date => (
              <td key={date} style={{ padding: '0.3rem 0.4rem' }}>
                <input
                  type="number" className="hk-num-input"
                  min={0} max={24} step={0.5}
                  value={member.hours[date] ?? ''}
                  placeholder="0"
                  onChange={e => setMemberHours(i, date, e.target.value)}
                />
              </td>
            ))}
            <td>
              <button onClick={() => removeRow(i)} style={{
                background: 'none', border: 'none', color: 'var(--text-mid)',
                fontSize: '1rem', padding: '0.2rem 0.4rem',
              }}>×</button>
            </td>
          </tr>
        ))}
      </tbody>
      <tfoot>
        {(() => {
          const avail: Record<string, number> = {}
          for (const date of dates) {
            const rotaHrs   = rotaMembers.reduce((s, m) => s + (m.days[date]?.hours || 0), 0)
            const manualHrs = staff.reduce((s, m) => s + (m.hours[date] || 0), 0)
            avail[date] = rotaHrs + manualHrs
          }
          return (
            <>
              <tr>
                <td className="col-label" style={{ fontSize: '0.78rem', color: 'var(--text-mid)' }}>Total Available</td>
                {dates.map(date => <td key={date}>{fmtH(avail[date])}</td>)}
                <td />
              </tr>
              <tr>
                <td className="col-label" style={{ fontSize: '0.72rem', color: 'var(--text-mid)' }}>vs Booked</td>
                {dates.map(date => (
                  <td key={date}><PlainDiffCell available={avail[date]} required={required[date].booked} warnOverAmber={warnOverAmber} warnUnderAmber={warnUnderAmber} /></td>
                ))}
                <td />
              </tr>
              <tr>
                <td className="col-label" style={{ fontSize: '0.72rem', color: 'var(--text-mid)' }}>with Recurring Tasks</td>
                {dates.map(date => (
                  <td key={date}><PlainDiffCell available={avail[date]} required={required[date].booked + required[date].general} warnOverAmber={warnOverAmber} warnUnderAmber={warnUnderAmber} /></td>
                ))}
                <td />
              </tr>
              <tr>
                <td className="col-label" style={{ fontSize: '0.72rem', color: 'var(--text-mid)' }}>with Pickup</td>
                {dates.map(date => (
                  <td key={date}><PlainDiffCell available={avail[date]} required={required[date].booked + required[date].general + required[date].pickup} warnOverAmber={warnOverAmber} warnUnderAmber={warnUnderAmber} /></td>
                ))}
                <td />
              </tr>
              <tr style={{ background: '#e2e8f0' }}>
                <td className="col-label" style={{ fontWeight: 700 }}>with Adjustments</td>
                {dates.map(date => (
                  <td key={date}><DiffCell available={avail[date]} required={required[date].total} warnOverRed={warnOverRed} warnOverAmber={warnOverAmber} warnUnderAmber={warnUnderAmber} warnUnderRed={warnUnderRed} /></td>
                ))}
                <td />
              </tr>
            </>
          )
        })()}
      </tfoot>
    </table>
  )
}

function PlainDiffCell({ available, required, warnOverAmber, warnUnderAmber }: {
  available: number; required: number; warnOverAmber: number; warnUnderAmber: number
}) {
  if (available === 0 && required === 0) return <span style={{ color: 'var(--text-mid)' }}>—</span>
  const diff = available - required
  const icon = diff > warnOverAmber ? '⚠' : diff < -warnUnderAmber ? '✗' : '✓'
  return <span style={{ color: 'var(--text-mid)', fontSize: '0.82rem' }}>{icon} {diff >= 0 ? '+' : ''}{fmtH(diff)}</span>
}

function DiffCell({ available, required, warnOverRed, warnOverAmber, warnUnderAmber, warnUnderRed }: {
  available: number; required: number
  warnOverRed: number; warnOverAmber: number; warnUnderAmber: number; warnUnderRed: number
}) {
  if (available === 0 && required === 0) return <span style={{ color: 'var(--text-mid)' }}>—</span>
  const diff = available - required
  if (diff > warnOverRed)     return <span className="diff-over-red">⚠ {fmtH(diff)} spare</span>
  if (diff > warnOverAmber)   return <span className="diff-over">⚠ {fmtH(diff)} spare</span>
  if (diff < -warnUnderRed)   return <span className="diff-under">✗ {fmtH(Math.abs(diff))} short</span>
  if (diff < -warnUnderAmber) return <span className="diff-under-amber">✗ {fmtH(Math.abs(diff))} short</span>
  return <span className="diff-ok">✓ {diff >= 0 ? '+' : ''}{fmtH(diff)}</span>
}

// ── Adjustments Table ─────────────────────────────────────────────────────────

function AdjustmentsTable({ bookings, adjustments, onChange }: {
  bookings: BookingsData
  adjustments: Adjustment[]
  onChange: (next: Adjustment[]) => void
}) {
  const { dates } = bookings

  function setLabel(i: number, label: string) {
    onChange(adjustments.map((a, idx) => idx === i ? { ...a, label } : a))
  }

  function setHours(i: number, date: string, val: string) {
    const hrs = parseFloat(val)
    onChange(adjustments.map((a, idx) => {
      if (idx !== i) return a
      const hours = { ...a.hours }
      if (!isNaN(hrs)) hours[date] = hrs
      else delete hours[date]
      return { ...a, hours }
    }))
  }

  function removeRow(i: number) {
    onChange(adjustments.filter((_, idx) => idx !== i))
  }

  if (adjustments.length === 0) {
    return (
      <p style={{ color: 'var(--text-mid)', fontSize: '0.82rem', padding: '0.5rem 0' }}>
        No adjustments — use "+ Add adjustment" above to add a one-off hour offset for a specific date.
      </p>
    )
  }

  return (
    <table className="hk-table">
      <thead>
        <tr>
          <th className="col-label">Label</th>
          {dates.map(d => <th key={d}>{fmtDayHeader(d)}</th>)}
          <th style={{ width: '32px' }}></th>
        </tr>
      </thead>
      <tbody>
        {adjustments.map((adj, i) => (
          <tr key={i}>
            <td className="col-label" style={{ padding: '0.3rem 0.5rem' }}>
              <input
                className="hk-text-input"
                value={adj.label}
                placeholder="e.g. Rooms from Sunday"
                onChange={e => setLabel(i, e.target.value)}
              />
            </td>
            {dates.map(date => (
              <td key={date} style={{ padding: '0.3rem 0.4rem' }}>
                <input
                  type="number" className="hk-num-input"
                  step={0.25}
                  value={adj.hours[date] ?? ''}
                  placeholder="0"
                  onChange={e => setHours(i, date, e.target.value)}
                />
              </td>
            ))}
            <td>
              <button onClick={() => removeRow(i)} style={{
                background: 'none', border: 'none', color: 'var(--text-mid)',
                fontSize: '1rem', padding: '0.2rem 0.4rem',
              }}>×</button>
            </td>
          </tr>
        ))}
      </tbody>
      <tfoot>
        <tr>
          <td className="col-label" style={{ fontSize: '0.78rem', color: 'var(--text-mid)' }}>Total</td>
          {dates.map(date => {
            const total = adjustments.reduce((s, a) => s + (a.hours[date] || 0), 0)
            return (
              <td key={date} style={{ color: total < 0 ? 'var(--danger)' : total > 0 ? '#1a7a4a' : 'var(--text-mid)', fontWeight: total !== 0 ? 600 : undefined }}>
                {total === 0 ? '—' : (total > 0 ? '+' : '') + fmtH(total)}
              </td>
            )
          })}
          <td />
        </tr>
      </tfoot>
    </table>
  )
}
