import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useEffect, useRef, useCallback } from 'react';
import { RefreshCw } from 'lucide-react';
import { getBookings, getConfig, putStaff, putPickup, putLastReviewed, putAdjustments, syncWorkforce, getWorkforceShifts, getWorkforceStaff, } from '../api';
// ── 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, days) {
    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) {
    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) {
    const d = new Date(date + 'T00:00:00');
    return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][(d.getDay() + 6) % 7];
}
function fullDayName(date) {
    const d = new Date(date + 'T00:00:00');
    return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][d.getDay()];
}
function isWeekend(date) {
    const day = new Date(date + 'T00:00:00').getDay();
    return day === 0 || day === 6;
}
function priorLineFor(date, d) {
    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) {
    return new Date(date + 'T00:00:00').toLocaleDateString('en-GB', {
        weekday: 'short', day: 'numeric', month: 'short',
    });
}
function fmtH(n) { return n.toFixed(2) + 'h'; }
// ── Pickup calculation ────────────────────────────────────────────────────────
function getDisplayPickup(pickup, catId, date, occupiedNow, totalRooms) {
    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, timeReqs, pickup, generalTasks, adjustments) {
    const result = {};
    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(null);
    const [timeReqs, setTimeReqs] = useState({});
    const [staff, setStaff] = useState([]);
    const [pickup, setPickup] = useState({});
    const [generalTasks, setGeneralTasks] = useState([]);
    const [adjustments, setAdjustments] = useState([]);
    const [warnOverRed, setWarnOverRed] = useState(4);
    const [warnOverAmber, setWarnOverAmber] = useState(1);
    const [warnUnderAmber, setWarnUnderAmber] = useState(1);
    const [warnUnderRed, setWarnUnderRed] = useState(2);
    const [wfShifts, setWfShifts] = useState({});
    const [wfStaff, setWfStaff] = useState([]);
    const [syncing, setSyncing] = useState(false);
    const [weekStart, setWeekStart] = useState(null);
    const [lastViewed, setLastViewed] = useState('');
    const [savedLastReviewed, setSavedLastReviewed] = useState('');
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState('');
    const [saveMsg, setSaveMsg] = useState(null);
    const timers = useRef({});
    const saveMsgTimer = useRef(null);
    const wfStaffLoaded = useRef(false);
    const weekStartRef = useRef(null);
    weekStartRef.current = weekStart; // always current, updated every render
    function debounce(key, fn, delay = 400) {
        clearTimeout(timers.current[key]);
        timers.current[key] = setTimeout(fn, delay);
    }
    function flash(text, 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) => {
        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(() => ({})),
            ]);
            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, body) {
            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) {
        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, date, delta, cat) {
        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 = {
            ...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) {
        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) {
        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() {
        setSyncing(true);
        try {
            await syncWorkforce();
            if (bookings) {
                const shifts = await getWorkforceShifts(bookings.dates[0], bookings.dates[bookings.dates.length - 1]);
                setWfShifts(shifts);
            }
            flash('Rota synced from Workforce');
        }
        catch (e) {
            flash(e instanceof Error ? e.message : 'Sync failed', true);
        }
        finally {
            setSyncing(false);
        }
    }
    function wfSyncLabel() {
        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 (_jsxs("div", { style: { padding: '1.5rem', maxWidth: '1600px' }, children: [_jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: '0.75rem', marginBottom: '1.25rem' }, children: [_jsxs("div", { children: [_jsx("h1", { style: { fontSize: '1.15rem', fontWeight: 700, color: 'var(--text-dark)' }, children: "Housekeeping Planner" }), bookings && (_jsxs("p", { style: { fontSize: '0.8rem', color: 'var(--text-mid)', marginTop: '0.1rem' }, children: [fmtDate(bookings.dates[0]), " \u2013 ", fmtDate(bookings.dates[bookings.dates.length - 1])] }))] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '0.4rem', marginLeft: 'auto', flexWrap: 'wrap' }, children: [_jsx(CtrlBtn, { onClick: () => handleWeekOffset(-7), children: "\u2039 Prev" }), _jsx(CtrlBtn, { onClick: handleWeekToday, primary: true, children: "Today" }), _jsx(CtrlBtn, { onClick: () => handleWeekOffset(7), children: "Next \u203A" }), _jsx("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)' } }), _jsx("div", { style: { width: '1px', height: '24px', background: 'var(--card-border)' } }), _jsxs("label", { style: { fontSize: '0.78rem', color: 'var(--text-mid)', display: 'flex', alignItems: 'center', gap: '0.3rem' }, children: [_jsx("span", { children: "Since" }), _jsx("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)' } })] }), _jsx(CtrlBtn, { onClick: () => { stampLastReviewed(); loadAll(true); }, children: "Update to now" }), _jsx("div", { style: { width: '1px', height: '24px', background: 'var(--card-border)' } }), _jsx("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)',
                                }, children: _jsx(RefreshCw, { size: 13, strokeWidth: 1.75, style: { animation: loading ? 'spin 1s linear infinite' : 'none' } }) })] })] }), _jsx("style", { children: `@keyframes spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }` }), saveMsg && (_jsx("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)',
                }, children: saveMsg.text })), error && (_jsxs("div", { style: {
                    marginBottom: '1rem', padding: '0.75rem 1rem', borderRadius: '8px',
                    background: '#fee2e2', color: 'var(--danger)', fontSize: '0.875rem',
                }, children: ["Error: ", error] })), loading && !bookings && (_jsx("div", { style: { color: 'var(--text-mid)', padding: '2rem 0', textAlign: 'center' }, children: "Loading bookings\u2026" })), bookings && required && (_jsxs(_Fragment, { children: [_jsx(Section, { title: "7-Day Occupancy", children: _jsx("div", { className: "table-scroll", children: _jsx(SummaryTable, { bookings: bookings, pickup: pickup, onPickupChange: handlePickupChange }) }) }), _jsx(Section, { title: "Required Hours", children: _jsx("div", { className: "table-scroll", children: _jsx(RequiredTable, { bookings: bookings, required: required }) }) }), _jsx(Section, { title: "Adjustments", action: _jsx(CtrlBtn, { onClick: addAdjustmentRow, children: "+ Add adjustment" }), children: _jsx("div", { className: "table-scroll", children: _jsx(AdjustmentsTable, { bookings: bookings, adjustments: adjustments, onChange: handleAdjustmentsChange }) }) }), _jsx(Section, { title: "Staff Rota", action: _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }, children: [_jsxs("button", { onClick: syncRota, disabled: syncing, 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,
                                    }, children: [_jsx(RefreshCw, { size: 11, strokeWidth: 1.75, style: { animation: syncing ? 'spin 1s linear infinite' : 'none' } }), syncing ? 'Syncing…' : 'Sync from Workforce'] }), _jsx("span", { style: { fontSize: '0.72rem', color: 'var(--text-mid)' }, children: wfSyncLabel() }), _jsx(CtrlBtn, { onClick: addStaffRow, children: "+ Add staff" })] }), children: _jsx("div", { className: "table-scroll", children: _jsx(StaffTable, { bookings: bookings, staff: staff, required: required, warnOverRed: warnOverRed, warnOverAmber: warnOverAmber, warnUnderAmber: warnUnderAmber, warnUnderRed: warnUnderRed, onChange: handleStaffChange, wfShifts: wfShifts, wfStaff: wfStaff }) }) })] }))] }));
}
// ── Section wrapper ───────────────────────────────────────────────────────────
function Section({ title, children, action }) {
    return (_jsxs("div", { style: { marginBottom: '1.5rem' }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '0.5rem' }, children: [_jsx("h2", { style: { fontSize: '0.72rem', fontWeight: 700, color: 'var(--text-mid)', textTransform: 'uppercase', letterSpacing: '0.08em' }, children: title }), action] }), children] }));
}
function CtrlBtn({ children, onClick, primary }) {
    return (_jsx("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: children }));
}
function SummaryTable({ bookings, pickup, onPickupChange }) {
    const { dates, categories: cats } = bookings;
    return (_jsxs("table", { className: "hk-table", children: [_jsxs("thead", { children: [_jsxs("tr", { children: [_jsx("th", { className: "col-label", rowSpan: 2, children: "Category" }), dates.map(d => (_jsx("th", { colSpan: 2, style: {
                                    borderLeft: '2px solid rgba(255,255,255,0.3)',
                                    ...(isWeekend(d) ? { background: '#253555' } : {}),
                                }, children: fmtDayHeader(d) }, d)))] }), _jsx("tr", { children: dates.map(d => (_jsxs(_Fragment, { children: [_jsx("th", { style: {
                                        fontSize: '0.72rem', fontWeight: 400, minWidth: '72px',
                                        background: isWeekend(d) ? '#2a3d5e' : '#1e2d42',
                                        borderLeft: '2px solid rgba(255,255,255,0.3)',
                                    }, children: "Rooms" }, d + '-r'), _jsx("th", { style: {
                                        fontSize: '0.72rem', fontWeight: 400, minWidth: '56px', textAlign: 'center',
                                        background: isWeekend(d) ? '#2a3d5e' : '#1e2d42',
                                        borderLeft: '1px solid rgba(255,255,255,0.1)',
                                    }, children: "D / S / A" }, d + '-d')] }))) })] }), _jsx("tbody", { children: cats.map((cat, ci) => {
                    const isLast = ci === cats.length - 1;
                    return (_jsx(CatRows, { cat: cat, dates: dates, pickup: pickup, onPickupChange: (date, delta) => onPickupChange(cat.id, date, delta, cat), isLast: isLast }, cat.id));
                }) }), _jsx("tfoot", { children: _jsx(TotalRows, { dates: dates, cats: cats, pickup: pickup }) })] }));
}
function CatRows({ cat, dates, pickup, onPickupChange, isLast }) {
    const bbStyle = isLast ? '2px solid var(--card-border)' : undefined;
    return (_jsxs("tr", { children: [_jsx("td", { className: "col-label", style: { borderBottom: bbStyle }, children: cat.name }), 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 (_jsxs(_Fragment, { children: [_jsxs("td", { 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)' } : {}),
                            }, children: [(dNew > 0 || dCanc > 0) && (_jsxs("div", { style: { marginBottom: '2px', fontSize: '0.72rem' }, children: [dNew > 0 && _jsxs("span", { className: "delta-new", children: ["\u25B2", dNew] }), dCanc > 0 && _jsxs("span", { className: "delta-canc", children: [" \u25BC", dCanc] })] })), _jsxs("div", { style: { fontWeight: 700, fontSize: '1.1rem' }, title: occTooltip, children: [occupied, pu > 0 && _jsxs("span", { className: "pickup-tag", children: [" +", pu] })] }), _jsxs("div", { style: { fontSize: '0.72rem', color: 'var(--text-mid)', marginBottom: '4px' }, children: [vacant, " vac", pu > 0 && _jsxs("span", { className: "pickup-tag", children: [" (", vacant - pu, ")"] })] }), _jsxs("div", { className: "pickup-ctrl", children: [_jsx("button", { className: "pickup-btn", disabled: pu <= 0, onClick: () => onPickupChange(date, -1), children: "\u2212" }), _jsx("span", { className: "pickup-num", children: pu }), _jsx("button", { className: "pickup-btn", disabled: pu >= vacant, title: priorLine, onClick: () => onPickupChange(date, 1), children: "+" })] })] }, date + '-r'), _jsxs("td", { 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)' } : {}),
                            }, children: [_jsxs("div", { style: { fontSize: '0.8rem', color: '#c2502e', fontWeight: 600 }, children: [d.departs, "d", prevPu > 0 && _jsxs("span", { className: "pickup-tag", children: [" +", prevPu] })] }), _jsxs("div", { style: { fontSize: '0.8rem', color: '#1d6fb8', fontWeight: 600 }, children: [d.stays, "s"] }), _jsxs("div", { style: { fontSize: '0.8rem', color: '#1a7a4a', fontWeight: 600 }, children: [d.arrivals, "a", pu > 0 && _jsxs("span", { className: "pickup-tag", children: [" +", pu] })] })] }, date + '-dsa')] }));
            })] }));
}
function TotalRows({ dates, cats, pickup }) {
    return (_jsxs("tr", { children: [_jsx("td", { className: "col-label", style: { fontStyle: 'italic' }, children: "Total" }), 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 (_jsxs(_Fragment, { children: [_jsxs("td", { style: {
                                textAlign: 'center', verticalAlign: 'middle', padding: '0.4rem 0.3rem',
                                borderLeft: '2px solid var(--card-border)',
                                ...(wknd ? { background: 'rgba(100,120,160,0.07)' } : {}),
                            }, children: [(dNew > 0 || dCanc > 0) && (_jsxs("div", { style: { marginBottom: '2px', fontSize: '0.72rem' }, children: [dNew > 0 && _jsxs("span", { className: "delta-new", children: ["\u25B2", dNew] }), dCanc > 0 && _jsxs("span", { className: "delta-canc", children: [" \u25BC", dCanc] })] })), _jsxs("div", { style: { fontWeight: 700, fontSize: '1.1rem' }, children: [occupied, pu > 0 && _jsxs("span", { className: "pickup-tag", children: [" +", pu] })] }), _jsxs("div", { style: { fontSize: '0.72rem', color: 'var(--text-mid)' }, children: [vacant, " vac", pu > 0 && _jsxs("span", { className: "pickup-tag", children: [" (", vacant - pu, ")"] })] })] }, date + '-r'), _jsxs("td", { style: {
                                verticalAlign: 'middle', borderLeft: '1px solid var(--card-border)',
                                padding: '0.4rem 0.3rem', textAlign: 'center',
                                ...(wknd ? { background: 'rgba(100,120,160,0.07)' } : {}),
                            }, children: [_jsxs("div", { style: { fontSize: '0.8rem', color: '#c2502e', fontWeight: 600 }, children: [departs, "d", prevPu > 0 && _jsxs("span", { className: "pickup-tag", children: [" +", prevPu] })] }), _jsxs("div", { style: { fontSize: '0.8rem', color: '#1d6fb8', fontWeight: 600 }, children: [stays, "s"] }), _jsxs("div", { style: { fontSize: '0.8rem', color: '#1a7a4a', fontWeight: 600 }, children: [arrivals, "a", pu > 0 && _jsxs("span", { className: "pickup-tag", children: [" +", pu] })] })] }, date + '-dsa')] }));
            })] }));
}
// ── Required Hours Table ──────────────────────────────────────────────────────
function RequiredTable({ bookings, required }) {
    const { dates, categories: cats } = bookings;
    return (_jsxs("table", { className: "hk-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { className: "col-label", children: "Category" }), dates.map(d => _jsx("th", { children: fmtDayHeader(d) }, d))] }) }), _jsx("tbody", { children: cats.map(cat => (_jsxs("tr", { children: [_jsx("td", { className: "col-label", children: cat.name }), dates.map(date => {
                            const booked = required[date].by_cat[cat.id] || 0;
                            const pu = required[date].pickup_by_cat[cat.id] || 0;
                            return (_jsxs("td", { children: [booked === 0 && pu === 0 ? '—' : fmtH(booked), pu > 0.001 && _jsxs("span", { className: "pickup-tag", children: [" (+", fmtH(pu), ")"] })] }, date));
                        })] }, cat.id))) }), _jsxs("tfoot", { children: [_jsxs("tr", { style: { background: '#f1f5f9' }, children: [_jsx("td", { className: "col-label", style: { color: 'var(--text-mid)', fontSize: '0.78rem' }, children: "Booked hrs" }), dates.map(d => _jsx("td", { children: fmtH(required[d].booked) }, d))] }), _jsxs("tr", { style: { background: '#f1f5f9' }, children: [_jsx("td", { className: "col-label", style: { color: 'var(--text-mid)', fontSize: '0.78rem' }, children: "Pickup hrs" }), dates.map(d => _jsx("td", { children: required[d].pickup > 0.001 ? fmtH(required[d].pickup) : '—' }, d))] }), _jsxs("tr", { style: { background: '#f1f5f9' }, children: [_jsx("td", { className: "col-label", style: { color: 'var(--text-mid)', fontSize: '0.78rem' }, children: "Recurring tasks" }), dates.map(d => _jsx("td", { children: required[d].general > 0.001 ? fmtH(required[d].general) : '—' }, d))] }), dates.some(d => required[d].adjustments !== 0) && (_jsxs("tr", { style: { background: '#f1f5f9' }, children: [_jsx("td", { className: "col-label", style: { color: 'var(--text-mid)', fontSize: '0.78rem' }, children: "Adjustments" }), dates.map(d => {
                                const adj = required[d].adjustments;
                                return (_jsx("td", { style: { color: adj < 0 ? 'var(--danger)' : adj > 0 ? '#1a7a4a' : 'var(--text-mid)' }, children: adj === 0 ? '—' : (adj > 0 ? '+' : '') + fmtH(adj) }, d));
                            })] })), _jsxs("tr", { style: { background: '#e2e8f0' }, children: [_jsx("td", { className: "col-label", children: "Total Required" }), dates.map(d => (_jsxs("td", { style: { fontWeight: 700 }, children: [fmtH(required[d].total), required[d].pickup > 0.001 && (_jsxs("span", { className: "pickup-tag", style: { display: 'block', fontSize: '0.7rem' }, children: ["inc ", fmtH(required[d].pickup), " pickup"] }))] }, d)))] })] })] }));
}
// ── Staff Table ───────────────────────────────────────────────────────────────
function pivotWfShifts(wfShifts, dates) {
    const members = {};
    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 };
        }
    }
    return Object.values(members).sort((a, b) => a.name.localeCompare(b.name));
}
function StaffTable({ bookings, staff, required, warnOverRed, warnOverAmber, warnUnderAmber, warnUnderRed, onChange, wfShifts, wfStaff }) {
    const { dates } = bookings;
    function setMemberName(i, name) {
        const next = staff.map((m, idx) => idx === i ? { ...m, name } : m);
        onChange(next);
    }
    function setMemberHours(i, date, val) {
        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) {
        onChange(staff.filter((_, idx) => idx !== i));
    }
    const rotaMembers = pivotWfShifts(wfShifts, dates);
    return (_jsxs("table", { className: "hk-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { className: "col-label", children: "Staff Member" }), dates.map(d => _jsx("th", { children: fmtDayHeader(d) }, d)), _jsx("th", { style: { width: '32px' } })] }) }), _jsxs("tbody", { children: [rotaMembers.map(member => (_jsxs("tr", { style: { background: 'rgba(42,100,72,0.05)' }, children: [_jsxs("td", { className: "col-label", children: [_jsx("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',
                                        }, children: "WF" }), member.name] }), dates.map(date => {
                                const shift = member.days[date];
                                return (_jsx("td", { style: { textAlign: 'center', padding: '0.3rem 0.4rem', verticalAlign: 'middle' }, children: shift ? (_jsxs(_Fragment, { children: [_jsx("div", { style: { fontSize: '0.68rem', color: 'var(--text-mid)', lineHeight: 1.25 }, children: shift.times }), _jsx("div", { style: { fontWeight: 600 }, children: fmtH(shift.hours) })] })) : _jsx("span", { style: { color: 'var(--text-mid)' }, children: "\u2014" }) }, date));
                            }), _jsx("td", {})] }, 'wf-' + member.id))), wfStaff.length > 0 && (_jsx("datalist", { id: "wf-staff-datalist", children: wfStaff.map(s => _jsx("option", { value: s.name }, s.id)) })), staff.map((member, i) => (_jsxs("tr", { children: [_jsx("td", { className: "col-label", style: { padding: '0.3rem 0.5rem' }, children: _jsx("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) }) }), dates.map(date => (_jsx("td", { style: { padding: '0.3rem 0.4rem' }, children: _jsx("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) }) }, date))), _jsx("td", { children: _jsx("button", { onClick: () => removeRow(i), style: {
                                        background: 'none', border: 'none', color: 'var(--text-mid)',
                                        fontSize: '1rem', padding: '0.2rem 0.4rem',
                                    }, children: "\u00D7" }) })] }, i)))] }), _jsx("tfoot", { children: (() => {
                    const avail = {};
                    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 (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "col-label", style: { fontSize: '0.78rem', color: 'var(--text-mid)' }, children: "Total Available" }), dates.map(date => _jsx("td", { children: fmtH(avail[date]) }, date)), _jsx("td", {})] }), _jsxs("tr", { children: [_jsx("td", { className: "col-label", style: { fontSize: '0.72rem', color: 'var(--text-mid)' }, children: "vs Booked" }), dates.map(date => (_jsx("td", { children: _jsx(PlainDiffCell, { available: avail[date], required: required[date].booked, warnOverAmber: warnOverAmber, warnUnderAmber: warnUnderAmber }) }, date))), _jsx("td", {})] }), _jsxs("tr", { children: [_jsx("td", { className: "col-label", style: { fontSize: '0.72rem', color: 'var(--text-mid)' }, children: "with Recurring Tasks" }), dates.map(date => (_jsx("td", { children: _jsx(PlainDiffCell, { available: avail[date], required: required[date].booked + required[date].general, warnOverAmber: warnOverAmber, warnUnderAmber: warnUnderAmber }) }, date))), _jsx("td", {})] }), _jsxs("tr", { children: [_jsx("td", { className: "col-label", style: { fontSize: '0.72rem', color: 'var(--text-mid)' }, children: "with Pickup" }), dates.map(date => (_jsx("td", { children: _jsx(PlainDiffCell, { available: avail[date], required: required[date].booked + required[date].general + required[date].pickup, warnOverAmber: warnOverAmber, warnUnderAmber: warnUnderAmber }) }, date))), _jsx("td", {})] }), _jsxs("tr", { style: { background: '#e2e8f0' }, children: [_jsx("td", { className: "col-label", style: { fontWeight: 700 }, children: "with Adjustments" }), dates.map(date => (_jsx("td", { children: _jsx(DiffCell, { available: avail[date], required: required[date].total, warnOverRed: warnOverRed, warnOverAmber: warnOverAmber, warnUnderAmber: warnUnderAmber, warnUnderRed: warnUnderRed }) }, date))), _jsx("td", {})] })] }));
                })() })] }));
}
function PlainDiffCell({ available, required, warnOverAmber, warnUnderAmber }) {
    if (available === 0 && required === 0)
        return _jsx("span", { style: { color: 'var(--text-mid)' }, children: "\u2014" });
    const diff = available - required;
    const icon = diff > warnOverAmber ? '⚠' : diff < -warnUnderAmber ? '✗' : '✓';
    return _jsxs("span", { style: { color: 'var(--text-mid)', fontSize: '0.82rem' }, children: [icon, " ", diff >= 0 ? '+' : '', fmtH(diff)] });
}
function DiffCell({ available, required, warnOverRed, warnOverAmber, warnUnderAmber, warnUnderRed }) {
    if (available === 0 && required === 0)
        return _jsx("span", { style: { color: 'var(--text-mid)' }, children: "\u2014" });
    const diff = available - required;
    if (diff > warnOverRed)
        return _jsxs("span", { className: "diff-over-red", children: ["\u26A0 ", fmtH(diff), " spare"] });
    if (diff > warnOverAmber)
        return _jsxs("span", { className: "diff-over", children: ["\u26A0 ", fmtH(diff), " spare"] });
    if (diff < -warnUnderRed)
        return _jsxs("span", { className: "diff-under", children: ["\u2717 ", fmtH(Math.abs(diff)), " short"] });
    if (diff < -warnUnderAmber)
        return _jsxs("span", { className: "diff-under-amber", children: ["\u2717 ", fmtH(Math.abs(diff)), " short"] });
    return _jsxs("span", { className: "diff-ok", children: ["\u2713 ", diff >= 0 ? '+' : '', fmtH(diff)] });
}
// ── Adjustments Table ─────────────────────────────────────────────────────────
function AdjustmentsTable({ bookings, adjustments, onChange }) {
    const { dates } = bookings;
    function setLabel(i, label) {
        onChange(adjustments.map((a, idx) => idx === i ? { ...a, label } : a));
    }
    function setHours(i, date, val) {
        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) {
        onChange(adjustments.filter((_, idx) => idx !== i));
    }
    if (adjustments.length === 0) {
        return (_jsx("p", { style: { color: 'var(--text-mid)', fontSize: '0.82rem', padding: '0.5rem 0' }, children: "No adjustments \u2014 use \"+ Add adjustment\" above to add a one-off hour offset for a specific date." }));
    }
    return (_jsxs("table", { className: "hk-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { className: "col-label", children: "Label" }), dates.map(d => _jsx("th", { children: fmtDayHeader(d) }, d)), _jsx("th", { style: { width: '32px' } })] }) }), _jsx("tbody", { children: adjustments.map((adj, i) => (_jsxs("tr", { children: [_jsx("td", { className: "col-label", style: { padding: '0.3rem 0.5rem' }, children: _jsx("input", { className: "hk-text-input", value: adj.label, placeholder: "e.g. Rooms from Sunday", onChange: e => setLabel(i, e.target.value) }) }), dates.map(date => (_jsx("td", { style: { padding: '0.3rem 0.4rem' }, children: _jsx("input", { type: "number", className: "hk-num-input", step: 0.25, value: adj.hours[date] ?? '', placeholder: "0", onChange: e => setHours(i, date, e.target.value) }) }, date))), _jsx("td", { children: _jsx("button", { onClick: () => removeRow(i), style: {
                                    background: 'none', border: 'none', color: 'var(--text-mid)',
                                    fontSize: '1rem', padding: '0.2rem 0.4rem',
                                }, children: "\u00D7" }) })] }, i))) }), _jsx("tfoot", { children: _jsxs("tr", { children: [_jsx("td", { className: "col-label", style: { fontSize: '0.78rem', color: 'var(--text-mid)' }, children: "Total" }), dates.map(date => {
                            const total = adjustments.reduce((s, a) => s + (a.hours[date] || 0), 0);
                            return (_jsx("td", { style: { color: total < 0 ? 'var(--danger)' : total > 0 ? '#1a7a4a' : 'var(--text-mid)', fontWeight: total !== 0 ? 600 : undefined }, children: total === 0 ? '—' : (total > 0 ? '+' : '') + fmtH(total) }, date));
                        }), _jsx("td", {})] }) })] }));
}
