import { useState, useEffect, useRef, useCallback } from 'react'
import { RefreshCw } from 'lucide-react'
import {
  getBookings, getConfig, putTimeReq, putStaff, putPickup,
  putGeneralTasks, putLastReviewed,
} from '../api'
import type {
  BookingsData, TimeReqs, StaffMember, GeneralTask,
  PickupData, RequiredDay, DayData,
} 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(1) + '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[],
): 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)
    result[date] = { booked: 0, pickup: 0, general: genHrs, total: genHrs, 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 [tolerance, setTolerance]       = useState(30)
  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)

  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 ? 5000 : 2500)
  }

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

  const loadAll = useCallback(async (force = false) => {
    setLoading(true)
    setError('')
    const today = todayStr()
    const ws = weekStart || today
    const lv = lastViewed || savedLastReviewed || offsetDate(today, -1)
    try {
      const [b, cfg] = await Promise.all([getBookings(ws, lv, force), getConfig()])
      setBookings(b)
      setTimeReqs(cfg.time_requirements || {})
      setStaff(cfg.staff_data || [])
      setPickup(cfg.pickup_data || {})
      setGeneralTasks(cfg.general_tasks || [])
      setTolerance(cfg.tolerance_minutes ?? 30)
      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)
    }
  }, [weekStart, lastViewed, savedLastReviewed])

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

  // Beacon save on unload
  useEffect(() => {
    function onUnload() {
      if (staff.length) {
        navigator.sendBeacon('/hk-planner/api/config/staff', JSON.stringify({ staff_data: staff }))
      }
      if (Object.keys(pickup).length) {
        navigator.sendBeacon('/hk-planner/api/config/pickup', JSON.stringify({ pickup_data: pickup }))
      }
      if (generalTasks.length) {
        navigator.sendBeacon('/hk-planner/api/config/general-tasks', JSON.stringify({ general_tasks: generalTasks }))
      }
    }
    window.addEventListener('beforeunload', onUnload)
    return () => window.removeEventListener('beforeunload', onUnload)
  }, [staff, pickup, generalTasks])

  function handleWeekOffset(days: number) {
    const today = todayStr()
    const newStart = offsetDate(weekStart || today, days)
    setWeekStart(newStart)
    stampLastReviewed()
    setTimeout(() => loadAll(true), 0)
  }

  function handleWeekToday() {
    setWeekStart(null)
    stampLastReviewed()
    setTimeout(() => loadAll(true), 0)
  }

  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)))
  }

  // ── Time requirements ───────────────────────────────────────────────────────

  function handleTimeReqChange(cat: string, action: string, value: number) {
    const next: TimeReqs = {
      ...timeReqs,
      [cat]: { ...(timeReqs[cat] || { depart: 0, stay: 0, arrive: 0 }), [action]: value },
    }
    setTimeReqs(next)
    stampLastReviewed()
    debounce(`req-${cat}-${action}`, () =>
      putTimeReq(cat, action, value).then(() => flash('Time requirements saved')).catch(e => flash(e.message, true))
    )
  }

  // ── 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: {} }])
  }

  // ── General tasks ────────────────────────────────────────────────────────────

  function handleTasksChange(next: GeneralTask[]) {
    setGeneralTasks(next)
    stampLastReviewed()
    debounce('tasks', () => putGeneralTasks(next).then(() => flash('Tasks saved')).catch(e => flash(e.message, true)))
  }

  function addTaskRow() {
    handleTasksChange([...generalTasks, { name: '', hours: {} }])
  }

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

  const required = bookings ? calcRequired(bookings, timeReqs, pickup, generalTasks) : 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) { setWeekStart(e.target.value); stampLastReviewed(); setTimeout(() => loadAll(true), 0) } }}
            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) { setLastViewed(e.target.value); setTimeout(() => loadAll(true), 0) } }}
              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 — Staff rota */}
          <Section title="Staff Rota" action={<CtrlBtn onClick={addStaffRow}>+ Add staff</CtrlBtn>}>
            <div className="table-scroll">
              <StaffTable
                bookings={bookings}
                staff={staff}
                required={required}
                tolerance={tolerance}
                onChange={handleStaffChange}
              />
            </div>
          </Section>

          {/* 4 — General tasks */}
          <Section title="General HK Tasks" action={<CtrlBtn onClick={addTaskRow}>+ Add task</CtrlBtn>}>
            <div className="table-scroll">
              <GeneralTasksTable tasks={generalTasks} onChange={handleTasksChange} />
            </div>
          </Section>

          {/* 5 — Time requirements */}
          <Section title="Time Requirements (minutes per room)">
            <div className="table-scroll">
              <TimeTable categories={bookings.categories} timeReqs={timeReqs} onChange={handleTimeReqChange} />
            </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',
                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: 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)' } : {}),
            }}>
              <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.5rem 0.4rem 0.4rem', textAlign: 'right',
              ...(wknd ? { background: 'rgba(100,120,160,0.07)' } : {}),
            }}>
              {(dNew > 0 || dCanc > 0) && (
                <div style={{ marginBottom: '3px' }}>
                  {dNew  > 0 && <span className="delta-new">▲{dNew}</span>}
                  {dCanc > 0 && <span className="delta-canc"> ▼{dCanc}</span>}
                </div>
              )}
              <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)' } : {}),
            }}>
              <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.5rem 0.4rem 0.4rem', textAlign: 'right',
              ...(wknd ? { background: 'rgba(100,120,160,0.07)' } : {}),
            }}>
              {(dNew > 0 || dCanc > 0) && (
                <div style={{ marginBottom: '3px' }}>
                  {dNew  > 0 && <span className="delta-new">▲{dNew}</span>}
                  {dCanc > 0 && <span className="delta-canc"> ▼{dCanc}</span>}
                </div>
              )}
              <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' }}>General tasks</td>
          {dates.map(d => <td key={d}>{required[d].general > 0.001 ? fmtH(required[d].general) : '—'}</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 StaffTable({ bookings, staff, required, tolerance, onChange }: {
  bookings: BookingsData
  staff: StaffMember[]
  required: Record<string, RequiredDay>
  tolerance: number
  onChange: (next: StaffMember[]) => void
}) {
  const { dates } = bookings
  const tolHrs = tolerance / 60

  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))
  }

  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>
        {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"
                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>
        <tr>
          <td className="col-label" style={{ fontSize: '0.78rem', color: 'var(--text-mid)' }}>Total Available</td>
          {dates.map(date => {
            const avail = staff.reduce((s, m) => s + (m.hours[date] || 0), 0)
            return <td key={date}>{fmtH(avail)}</td>
          })}
          <td />
        </tr>
        <tr>
          <td className="col-label" style={{ fontSize: '0.72rem', color: 'var(--text-mid)' }}>vs Booked</td>
          {dates.map(date => {
            const avail  = staff.reduce((s, m) => s + (m.hours[date] || 0), 0)
            const reqHrs = required[date].booked + required[date].general
            return <td key={date}><DiffCell available={avail} required={reqHrs} tolerance={tolHrs} /></td>
          })}
          <td />
        </tr>
        <tr>
          <td className="col-label" style={{ fontSize: '0.72rem', color: 'var(--text-mid)' }}>vs with Pickup</td>
          {dates.map(date => {
            const avail  = staff.reduce((s, m) => s + (m.hours[date] || 0), 0)
            return <td key={date}><DiffCell available={avail} required={required[date].total} tolerance={tolHrs} /></td>
          })}
          <td />
        </tr>
      </tfoot>
    </table>
  )
}

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

// ── General Tasks Table ───────────────────────────────────────────────────────

const WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']

function GeneralTasksTable({ tasks, onChange }: { tasks: GeneralTask[]; onChange: (next: GeneralTask[]) => void }) {
  function setName(i: number, name: string) {
    onChange(tasks.map((t, idx) => idx === i ? { ...t, name } : t))
  }

  function setHours(i: number, day: string, val: string) {
    const mins = parseInt(val, 10)
    onChange(tasks.map((t, idx) => {
      if (idx !== i) return t
      return { ...t, hours: { ...t.hours, [day]: isNaN(mins) ? 0 : Math.max(0, mins) } }
    }))
  }

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

  return (
    <table className="hk-table">
      <thead>
        <tr>
          <th className="col-label">Task</th>
          {WEEKDAYS.map(d => <th key={d}>{d}</th>)}
          <th style={{ width: '32px' }}></th>
        </tr>
      </thead>
      <tbody>
        {tasks.length === 0 && (
          <tr><td colSpan={9} style={{ color: 'var(--text-mid)', textAlign: 'center', padding: '1rem' }}>No tasks yet — add one above</td></tr>
        )}
        {tasks.map((task, i) => (
          <tr key={i}>
            <td style={{ padding: '0.3rem 0.5rem' }}>
              <input className="hk-text-input" value={task.name} placeholder="Task name"
                onChange={e => setName(i, e.target.value)} />
            </td>
            {WEEKDAYS.map(day => (
              <td key={day} style={{ padding: '0.3rem 0.4rem' }}>
                <input type="number" className="hk-num-input" min={0} max={999}
                  value={task.hours[day] || ''}
                  placeholder="0"
                  onChange={e => setHours(i, day, 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>
    </table>
  )
}

// ── Time Requirements Table ───────────────────────────────────────────────────

function TimeTable({ categories, timeReqs, onChange }: {
  categories: BookingsData['categories']
  timeReqs: TimeReqs
  onChange: (cat: string, action: string, value: number) => void
}) {
  return (
    <table className="hk-table" style={{ maxWidth: '560px' }}>
      <thead>
        <tr>
          <th className="col-label">Category</th>
          <th>Depart (mins)</th>
          <th>Stay (mins)</th>
          <th>Arrive (mins)</th>
        </tr>
      </thead>
      <tbody>
        {categories.map(cat => {
          const req = timeReqs[cat.id] || { depart: 0, stay: 0, arrive: 0 }
          return (
            <tr key={cat.id}>
              <td className="col-label">{cat.name}</td>
              {(['depart', 'stay', 'arrive'] as const).map(action => (
                <td key={action} style={{ padding: '0.3rem 0.4rem' }}>
                  <input type="number" className="hk-num-input" min={0} max={999}
                    value={req[action] || ''}
                    placeholder="0"
                    onChange={e => onChange(cat.id, action, parseInt(e.target.value, 10) || 0)}
                    onBlur={e => onChange(cat.id, action, parseInt(e.target.value, 10) || 0)}
                  />
                </td>
              ))}
            </tr>
          )
        })}
      </tbody>
    </table>
  )
}
