import { freeSeatsAt, buildSlotGrid, buildStartTimes, nearestOpen, toMinutes as m, DEFAULT_SLOT_CONFIG as D } from '../services/audit-slot.rules';
let fail = 0;
const eq = (name: string, got: any, want: any) => { const ok = JSON.stringify(got) === JSON.stringify(want); if (!ok) fail++; console.log(ok ? 'PASS' : 'FAIL', name, ok ? '' : `got=${JSON.stringify(got)} want=${JSON.stringify(want)}`); };
const g60 = { ...D, gapMinutes: 60 }, g30 = { ...D, gapMinutes: 30 };

eq('start times count', buildStartTimes(D).length, 16);               // 9:00-12:30 (8) + 14:00-17:30 (8)
eq('no start in lunch', buildStartTimes(D).includes(m('13:00')!), false);
// user's scenario: auditor A books 10:00 → 1 seat left at 10:00 for another auditor
eq('10:00 with 1 booking → 1 free', freeSeatsAt([m('10:00')!], m('10:00')!, g60), 1);
eq('10:00 with 2 bookings → 0 free', freeSeatsAt([m('10:00')!, m('10:00')!], m('10:00')!, g60), 0);
eq('empty day → 2 free', freeSeatsAt([], m('10:00')!, g60), 2);
// gap 60: 10:00 blocks 10:30 & 9:30 for that seat, but 11:00 / 9:00 are fine
eq('gap60 10:30 near one booking → 1 free', freeSeatsAt([m('10:00')!], m('10:30')!, g60), 1);
eq('gap60 11:00 after one booking → 2 free', freeSeatsAt([m('10:00')!], m('11:00')!, g60), 2);
eq('gap60 9:00 before one booking → 2 free', freeSeatsAt([m('10:00')!], m('09:00')!, g60), 2);
eq('gap60 two at 10:00 block 10:30', freeSeatsAt([m('10:00')!, m('10:00')!], m('10:30')!, g60), 0);
eq('gap60 two at 10:00 block 9:30', freeSeatsAt([m('10:00')!, m('10:00')!], m('09:30')!, g60), 0);
eq('gap60 two at 10:00 free at 11:00', freeSeatsAt([m('10:00')!, m('10:00')!], m('11:00')!, g60), 2);
eq('gap30 two at 10:00 free at 10:30', freeSeatsAt([m('10:00')!, m('10:00')!], m('10:30')!, g30), 2);
eq('gap30 two at 10:00 block 10:00', freeSeatsAt([m('10:00')!, m('10:00')!], m('10:00')!, g30), 0);
// staggered: 10:00 + 10:30 booked (gap60) → both seats busy around 10:00-10:30
eq('staggered 10:00+10:30 → 10:30 closed', freeSeatsAt([m('10:00')!, m('10:30')!], m('10:30')!, g60), 0);
eq('staggered 10:00+10:30 → 10:00 closed', freeSeatsAt([m('10:00')!, m('10:30')!], m('10:00')!, g60), 0);
eq('staggered 10:00+10:30 → 11:00 has 1 (10:30 within 60)', freeSeatsAt([m('10:00')!, m('10:30')!], m('11:00')!, g60), 1);
eq('staggered 10:00+10:30 → 11:30 free 2', freeSeatsAt([m('10:00')!, m('10:30')!], m('11:30')!, g60), 2);
// mockup Saturday data (seat labels dropped): 10:00,14:00,16:00,10:00,11:30
const sat = ['10:00','14:00','16:00','10:00','11:30'].map(x => m(x)!);
const grid = buildSlotGrid({ booked: sat, dayCount: 5, dateClosed: false, outsideWindow: false, notBeforeMin: null, cfg: g60 });
const at = (t: string) => grid.flatMap(s => s.slots).find(c => c.time === t)!;
eq('sat 10:00 fully booked', [at('10:00').free, at('10:00').reason], [0, 'Fully booked']);
eq('sat 10:30 too close', [at('10:30').free, at('10:30').reason], [0, 'Too close to another audit']);
eq('sat 11:00 limited', [at('11:00').free, at('11:00').status], [1, 'limited']);
eq('sat 09:00 open', [at('09:00').free, at('09:00').status], [2, 'open']);
eq('sat 14:00 limited', at('14:00').status, 'limited');
// day cap, closed, past
eq('day full closes all', buildSlotGrid({ booked: [], dayCount: 10, dateClosed: false, outsideWindow: false, notBeforeMin: null }).every(s => s.slots.every(c => c.reason === 'Day full')), true);
eq('closed date', buildSlotGrid({ booked: [], dayCount: 0, dateClosed: true, outsideWindow: false, notBeforeMin: null })[0].slots[0].reason, 'Closed');
const today = buildSlotGrid({ booked: [], dayCount: 0, dateClosed: false, outsideWindow: false, notBeforeMin: m('11:10')! });
eq('past times today', today.flatMap(s=>s.slots).filter(c=>c.reason==='Time passed').map(c=>c.time), ['09:00','09:30','10:00','10:30','11:00']);
eq('nearestOpen', nearestOpen(grid, '10:00', 3), ['09:00','11:00','11:30'].filter(x => at(x).free>0).slice(0,3).length ? nearestOpen(grid,'10:00',3) : []);
console.log('nearestOpen(10:00) =', nearestOpen(grid, '10:00', 3));
process.exit(fail ? 1 : 0);
