// Shared mock data + icons for the clinic webapp prototype
// Lives on window so all script files can use it.

const Icon = ({ name, size = 16, ...rest }) => {
  const paths = {
    calendar: <><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></>,
    user: <><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></>,
    users: <><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></>,
    chart: <><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></>,
    home: <><path d="M3 12l9-9 9 9"/><path d="M5 10v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V10"/></>,
    bag: <><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 0 1-8 0"/></>,
    heart: <><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></>,
    camera: <><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></>,
    photo: <><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></>,
    plus: <><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></>,
    arrow: <><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></>,
    back: <><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></>,
    search: <><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></>,
    bell: <><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></>,
    settings: <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></>,
    file: <><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></>,
    edit: <><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4z"/></>,
    check: <><polyline points="20 6 9 17 4 12"/></>,
    close: <><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></>,
    chevR: <><polyline points="9 18 15 12 9 6"/></>,
    chevL: <><polyline points="15 18 9 12 15 6"/></>,
    pin: <><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 1 1 18 0z"/><circle cx="12" cy="10" r="3"/></>,
    clock: <><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></>,
    phone: <><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.37 1.9.72 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z"/></>,
    mail: <><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><path d="M22 6l-10 7L2 6"/></>,
    info: <><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></>,
    lock: <><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></>,
    sparkle: <><path d="M12 2l1.8 5.4L19 9l-5.2 1.6L12 16l-1.8-5.4L5 9l5.2-1.6z"/></>,
    upload: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></>,
    grid: <><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></>,
    list: <><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></>,
    pound: <><path d="M18 7c0-2.2-1.8-4-4-4S10 4.8 10 7v3H7v3h3v3c0 1.7-1.3 3-3 3v2h13v-3h-7c.6-.6 1-1.4 1-2.3V13h4v-3h-4V7z"/></>,
  };
  return <svg viewBox="0 0 24 24" width={size} height={size} stroke="currentColor" fill="none" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" {...rest}>{paths[name]}</svg>;
};

const CLINICIANS = [
  { id: 'emma', name: 'Emma Carter', role: 'Clinical Director', initials: 'EC', avatarTint: '#8FA88A' },
  { id: 'grace', name: 'Grace Hollis', role: 'Aesthetic Nurse', initials: 'GH', avatarTint: '#B5C7AE' },
  { id: 'daniel', name: 'Daniel Mensah', role: 'Aesthetic Doctor', initials: 'DM', avatarTint: '#6E7D6B' },
  { id: 'sofia', name: 'Sofia Marsh', role: 'Skin Therapist', initials: 'SM', avatarTint: '#9DB4A0' },
];

// Treatment menu — demo Demo Clinic offering (room-allocation email).
// `room` is the primary room; some treatments are flexible (Room 1 OR Room 3) —
// the booking flow uses ROOMS.allowedTreatments to enforce routing.
// Legacy IDs preserved (co2, hifu, cryo, hydra, fire, micro) so existing
// appointments + COMM_LOG references don't break.
const TREATMENTS = [
  // ── Room 1 (Laser Suite) — laser + pigmentation
  { id: 'ltr',       name: 'Laser Tattoo Removal',                    cat: 'Laser',      mins: 30, price: 95,  blurb: 'Q-switched laser for unwanted ink — courses tailored to ink type and skin tone.',          tone: 'ph-treat' },
  { id: 'ler',       name: 'Laser Eyebrow Removal',                   cat: 'Laser',      mins: 20, price: 75,  blurb: 'Remove semi-permanent eyebrow pigment with precision laser.',                                 tone: 'ph-treat' },
  { id: 'co2',       name: 'CO₂ Rejuvenating Resurfacing',            cat: 'Laser',      mins: 90, price: 650, blurb: 'Fractional CO₂ for texture, fine lines and overall skin rejuvenation.',                       tone: 'ph-skin' },
  { id: 'co2-scar',  name: 'CO₂ Advanced Scar Resurfacing',           cat: 'Laser',      mins: 90, price: 750, blurb: 'Deeper fractional CO₂ for acne scarring and textural concerns.',                              tone: 'ph-skin' },
  { id: 'ss',        name: 'Sunspot & Pigmentation Removal',          cat: 'Laser',      mins: 30, price: 150, blurb: 'Targeted laser for sun damage, age spots and surface pigmentation.',                          tone: 'ph-skin' },
  // ── Room 2 (LHR Suite) — laser hair removal only
  { id: 'lhr',       name: 'Laser Hair Removal',            cat: 'Laser',      mins: 30, price: 105, blurb: 'Fast, comfortable and effective across skin tones.',                     tone: 'ph-treat' },
  // ── Room 3 (Skin & Body Studio) — body + skin tech
  { id: 'lipo',      name: 'Fat Reduction & Body Contouring', cat: 'Body',       mins: 60, price: 295, blurb: 'Multi-technology body contouring for stubborn pockets and tone.',                             tone: 'ph-treat' },
  { id: 'cryo',      name: 'Cryolipolysis · Targeted Fat Freezing',   cat: 'Body',       mins: 60, price: 350, blurb: 'Fat freezing for stubborn pockets — clinical, controlled.',                                   tone: 'ph-treat' },
  { id: 'hydra',     name: 'HydraFacial',                             cat: 'Skin',       mins: 45, price: 145, blurb: 'Cleanse, exfoliate and hydrate — the reset between bigger treatments.',                       tone: 'ph-room' },
  { id: 'hifu',      name: 'HIFU · Non-surgical Facelift', cat: 'Skin',       mins: 75, price: 495, blurb: 'Non-surgical lift and tightening — visible contour without downtime.',                        tone: 'ph-portrait' },
  { id: 'rfmicro',   name: 'RF Microneedling',             cat: 'Skin',       mins: 60, price: 295, blurb: 'Collagen-stimulating radiofrequency microneedling for firmness and tone.',                    tone: 'ph-clinical' },
  { id: 'thread',    name: 'Thread Vein Removal',                     cat: 'Body',       mins: 30, price: 145, blurb: 'Targeted laser for facial and body thread veins.',                                            tone: 'ph-treat' },
  { id: 'mole',      name: 'Mole & Skin Tag Removal',                 cat: 'Body',       mins: 30, price: 195, blurb: 'Laser removal of benign moles and skin tags.',                                                tone: 'ph-treat' },
  // ── Flexible (Room 1 or Room 3)
  { id: 'prescription-facial', name: 'Prescription Resurfacing Facial', cat: 'Skin',     mins: 60, price: 125, blurb: 'Bespoke peel-based resurfacing facial, tailored to your skin.',                               tone: 'ph-skin' },
  { id: 'epn',       name: 'EPN Microneedling with Mesotherapy',      cat: 'Skin',       mins: 75, price: 195, blurb: 'Dermal microneedling with electroporation + mesotherapy serums.',                             tone: 'ph-clinical' },
  { id: 'byconcern', name: 'Targeted Concern Facial',                        cat: 'Skin',       mins: 60, price: 145, blurb: 'Concern-led facial — pigmentation, acne, ageing or sensitivity.',                              tone: 'ph-skin' },
  { id: 'bloom',     name: 'Signature Glow Facial',                        cat: 'Skin',       mins: 60, price: 135, blurb: 'Signature glow facial — refresh and brighten.',                                                tone: 'ph-skin' },
  { id: 'exosomes-facial', name: 'Exosomes / Stem Cell Microneedling Facial', cat: 'Skin', mins: 75, price: 295, blurb: 'Microneedling with exosomes for advanced regeneration.',                                     tone: 'ph-clinical' },
  { id: 'awi',       name: 'Anti-Wrinkle Injections',                 cat: 'Injectable', mins: 30, price: 250, blurb: 'Botulinum toxin for dynamic lines — forehead, frown, crows feet.',                            tone: 'ph-treat' },
  { id: 'profhilo',  name: 'Profhilo',                                cat: 'Injectable', mins: 30, price: 295, blurb: 'Bio-remodelling injectable for skin quality, hydration and firmness.',                         tone: 'ph-skin' },
  { id: 'polynuc',   name: 'Polynucleotides',                         cat: 'Injectable', mins: 45, price: 350, blurb: 'DNA-based regenerative injectable for face, eyes, neck, hands and scalp.',                    tone: 'ph-clinical' },
  { id: 'skin-booster', name: 'Skin Boosters',                        cat: 'Injectable', mins: 45, price: 295, blurb: 'Hyaluronic acid boosters for hydration and glow.',                                            tone: 'ph-room' },
  { id: 'rf-hair',   name: 'Radiofrequency Stem Cell Hair Growth',    cat: 'Hair',       mins: 60, price: 350, blurb: 'RF microneedling with exosomes for hair loss and density.',                                   tone: 'ph-clinical' },
  { id: 'electrolysis', name: 'Electrolysis Hair Removal',            cat: 'Hair',       mins: 30, price: 65,  blurb: 'Targeted permanent hair removal — visible hairs treated one at a time.',                      tone: 'ph-treat' },
  // ── Legacy IDs kept so existing appointments + comm log still resolve
  { id: 'fire',      name: 'Fire & Ice Facial',                       cat: 'Skin',       mins: 60, price: 125, blurb: 'A signature resurfacing facial with immediate, visible glow.',                                tone: 'ph-skin' },
  { id: 'micro',     name: 'Microneedling',                           cat: 'Skin',       mins: 60, price: 295, blurb: 'Classic microneedling for texture and tone.',                                                    tone: 'ph-clinical' },
];

// flag — one of: 'highCheckIn' (loyal/reliable), 'highCancel' (cancellation risk),
//                'inactive' (no contact in 12+ months — Q7), 'followUp' (action owed), or null
const CUSTOMERS = [
  { id: 'sophie',  name: 'Sophie Reynolds',    dob: '1986-03-12', joined: '2022-08', phone: '07712 553 904', email: 'sophie.r@email.co.uk', initials: 'SR', concerns: ['Pigmentation', 'Texture'], allergies: 'None',       meds: 'Vitamin D', flag: 'highCheckIn', deposit: 50,  account: 0,   alert: 'Vit C AM, retinoid PM',                              marketingConsent: true },
  { id: 'hannah',  name: 'Hannah Whitlock',    dob: '1991-11-04', joined: '2023-02', phone: '07811 220 198', email: 'h.whitlock@email.com',   initials: 'HW', concerns: ['Acne scarring'],            allergies: 'Penicillin', meds: 'None',      flag: 'highCheckIn', deposit: 100, account: 0,   alert: 'CO2 course · 2 of 3 done',                           marketingConsent: false },
  { id: 'mark',    name: 'Mark Daley',         dob: '1979-06-22', joined: '2024-01', phone: '07900 110 332', email: 'mdaley@email.com',       initials: 'MD', concerns: ['Lines', 'Volume loss'],     allergies: 'None',       meds: 'Statin',    flag: 'followUp',    deposit: 0,   account: -50, alert: 'Owes £50 from last visit',                           marketingConsent: false },
  { id: 'lucy',    name: 'Lucy Bennett',       dob: '1995-09-30', joined: '2024-09', phone: '07733 884 002', email: 'lucy.b@email.com',       initials: 'LB', concerns: ['Body contouring'],          allergies: 'None',       meds: 'OCP',       flag: 'highCancel',  deposit: 0,   account: 0,   alert: 'Cancelled 3 of last 5 · take full deposit',          marketingConsent: true },
  { id: 'jess',    name: 'Jess Okafor',        dob: '1988-01-17', joined: '2021-05', phone: '07845 220 117', email: 'jess.o@email.com',       initials: 'JO', concerns: ['Maintenance'],              allergies: 'None',       meds: 'None',      flag: 'highCheckIn', deposit: 50,  account: 0,   alert: '',                                                   marketingConsent: true },
  { id: 'amelia',  name: 'Amelia Carrington',  dob: '1990-04-21', joined: '2023-06', phone: '07900 411 552', email: 'amelia.c@email.com',     initials: 'AC', concerns: ['Lines'],                    allergies: 'None',       meds: 'None',      flag: 'inactive',    deposit: 0,   account: 0,   alert: 'Last seen Apr 2025 · 13 months ago',                 marketingConsent: true },
  { id: 'priya',   name: 'Priya Devi',         dob: '1993-08-04', joined: '2024-03', phone: '07700 220 991', email: 'priya.d@email.com',      initials: 'PD', concerns: ['Hyperpigmentation'],        allergies: 'Latex',      meds: 'None',      flag: 'followUp',    deposit: 50,  account: 0,   alert: 'Patch test pending · book tretinoin after',          marketingConsent: true },
  { id: 'tom',     name: 'Tom Pendry',         dob: '1982-12-15', joined: '2025-01', phone: '07811 552 117', email: 'tpendry@email.com',      initials: 'TP', concerns: ['Skin laxity'],              allergies: 'None',       meds: 'None',      flag: null,          deposit: 0,   account: 0,   alert: '',                                                   marketingConsent: true },
];

const localISO = (d) => d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
const todayISO = localISO(new Date());
const offsetDay = (offset) => { const d = new Date(); d.setDate(d.getDate() + offset); return localISO(d); };

const APPOINTMENTS = [
  { id: 'a1', date: todayISO, time: '09:00', mins: 60, customerId: 'sophie', clinicianId: 'grace', treatmentId: 'hydra', status: 'confirmed', room: 'Room 3' },
  { id: 'a2', date: todayISO, time: '10:30', mins: 90, customerId: 'hannah', clinicianId: 'emma', treatmentId: 'co2', status: 'confirmed', room: 'Room 1', note: 'Session 3 of 3' },
  { id: 'a3', date: todayISO, time: '12:00', mins: 75, customerId: 'mark', clinicianId: 'daniel', treatmentId: 'hifu', status: 'confirmed', room: 'Room 3' },
  { id: 'a4', date: todayISO, time: '14:00', mins: 45, customerId: 'jess', clinicianId: 'sofia', treatmentId: 'fire', status: 'confirmed', room: 'Room 3' },
  { id: 'a5', date: todayISO, time: '15:00', mins: 60, customerId: 'lucy', clinicianId: 'grace', treatmentId: 'cryo', status: 'pending', room: 'Room 3', note: 'Consultation' },
  { id: 'a6', date: todayISO, time: '16:30', mins: 60, customerId: 'sophie', clinicianId: 'emma', treatmentId: 'micro', status: 'confirmed', room: 'Room 1' },
  { id: 'a7', date: offsetDay(1), time: '09:30', mins: 90, customerId: 'hannah', clinicianId: 'emma', treatmentId: 'co2', status: 'confirmed' },
  { id: 'a8', date: offsetDay(1), time: '11:00', mins: 60, customerId: 'jess', clinicianId: 'sofia', treatmentId: 'hydra', status: 'confirmed' },
  { id: 'a9', date: offsetDay(2), time: '13:00', mins: 75, customerId: 'mark', clinicianId: 'daniel', treatmentId: 'hifu', status: 'confirmed' },
  { id: 'a10', date: offsetDay(3), time: '10:00', mins: 60, customerId: 'lucy', clinicianId: 'grace', treatmentId: 'cryo', status: 'confirmed' },
];

const NOTES = {
  sophie: [
    { id: 'n1', when: '2025-09-15', author: 'Grace Hollis', kind: 'Treatment', title: 'HydraFacial · session 6', body: 'Skin tolerance excellent. Targeted forehead pigmentation w/ Britenol booster. Mild transient erythema at end of session, settled within 5 mins. Recommended SPF50 daily, reviewed home routine — added Obagi C-Clarifying serum.' },
    { id: 'n2', when: '2025-07-02', author: 'Emma Carter', kind: 'Consultation', title: 'Microneedling course — review', body: 'Course of 3 microneedling completed. Good response in cheek texture. Pigmentation persistent — discussed adding HydraFacial maintenance every 6 weeks. Patch test for tretinoin booked.' },
    { id: 'n3', when: '2025-04-18', author: 'Emma Carter', kind: 'Plan', title: 'Treatment plan signed off', body: 'Agreed 6-month plan: 3× microneedling at 4-wk intervals, then HydraFacial maintenance. Home: vitamin C AM, retinoid PM (graduated). Patient happy to proceed.' },
  ],
  hannah: [
    { id: 'n4', when: '2025-09-28', author: 'Emma Carter', kind: 'Treatment', title: 'CO2 Laser · session 2 of 3', body: 'Settings: 25mJ, density 5%, 1 pass cheeks + chin. Anaesthetic 45 min. Tolerated well. Aftercare reviewed: occlusive balm, no exercise 48hr, SPF50 strict.' },
    { id: 'n5', when: '2025-08-30', author: 'Emma Carter', kind: 'Treatment', title: 'CO2 Laser · session 1 of 3', body: 'First session — full-face fractional CO2. Conservative settings given baseline. Photos captured pre/post. Booked review at 2 weeks.' },
    { id: 'n6', when: '2025-08-15', author: 'Emma Carter', kind: 'Consultation', title: 'CO2 Laser consultation', body: 'Goals: reduce post-acne scarring along jawline. Reviewed risks (PIH given Fitz III). Penicillin allergy noted — non-relevant. Patch test booked.' },
  ],
  mark: [
    { id: 'n7', when: '2025-10-01', author: 'Dr Daniel Mensah', kind: 'Treatment', title: 'HIFU · full face', body: 'HIFU full-face protocol. 4.5mm + 3.0mm depths. Tolerance good with topical only. Follow-up at 12 weeks for collagen response.' },
  ],
  lucy: [
    { id: 'n8', when: '2025-09-22', author: 'Grace Hollis', kind: 'Consultation', title: 'Cryolipolysis consultation', body: 'Discussed flank treatment. BMI within range. Showed before/after gallery. Booked single applicator session — realistic expectations set (12-week response).' },
  ],
  jess: [
    { id: 'n9', when: '2025-09-25', author: 'Sofia Marsh', kind: 'Treatment', title: 'Fire & Ice · maintenance', body: 'Routine 6-weekly maintenance facial. Skin in excellent condition. No adjustments to home routine.' },
  ],
};

const PHOTOS = {
  sophie: [
    { id: 'p1', when: '2025-09-15', label: 'Front · post', tone: 'ph-skin' },
    { id: 'p2', when: '2025-09-15', label: 'Left · post', tone: 'ph-skin' },
    { id: 'p3', when: '2025-09-15', label: 'Right · post', tone: 'ph-skin' },
    { id: 'p4', when: '2025-07-02', label: 'Front · review', tone: 'ph-portrait' },
    { id: 'p5', when: '2025-04-18', label: 'Front · baseline', tone: 'ph-portrait' },
    { id: 'p6', when: '2025-04-18', label: 'Left · baseline', tone: 'ph-portrait' },
  ],
  hannah: [
    { id: 'p7', when: '2025-09-28', label: 'Jawline · pre', tone: 'ph-skin' },
    { id: 'p8', when: '2025-09-28', label: 'Jawline · post', tone: 'ph-skin' },
    { id: 'p9', when: '2025-08-30', label: 'Jawline · pre', tone: 'ph-portrait' },
    { id: 'p10', when: '2025-08-15', label: 'Baseline · front', tone: 'ph-portrait' },
  ],
  mark: [
    { id: 'p11', when: '2025-10-01', label: 'Front · pre', tone: 'ph-clinical' },
    { id: 'p12', when: '2025-10-01', label: 'Profile · pre', tone: 'ph-clinical' },
  ],
  lucy: [],
  jess: [
    { id: 'p13', when: '2025-09-25', label: 'Maintenance', tone: 'ph-skin' },
  ],
};

// Pending bookings — submitted online by customers, awaiting clinician approval
// (Section 2 of feedback: review treatment / length / clinician / forms before approving)
const PENDING_BOOKINGS = [
  { id: 'pb1', when: 'Just now',          customerId: 'tom',    treatmentId: 'hifu',  clinicianId: 'daniel',   wantedDate: offsetDay(2),  wantedTime: '14:30', formsComplete: false, formStatus: 'awaiting medical history' },
  { id: 'pb2', when: '17 min ago',        customerId: 'priya',  treatmentId: 'micro', clinicianId: 'emma',  wantedDate: offsetDay(3),  wantedTime: '10:00', formsComplete: true,  formStatus: 'all forms signed' },
  { id: 'pb3', when: '1 hr ago',          customerId: 'lucy',   treatmentId: 'cryo',  clinicianId: 'grace', wantedDate: offsetDay(5),  wantedTime: '11:30', formsComplete: false, formStatus: 'photo consent missing' },
  { id: 'pb4', when: 'Yesterday',         customerId: 'amelia', treatmentId: 'hydra', clinicianId: 'sofia',   wantedDate: offsetDay(7),  wantedTime: '15:00', formsComplete: true,  formStatus: 'all forms signed' },
];

// Waitlist — customers waiting for an earlier slot if one frees up
const WAITLIST = [
  { id: 'wl1', customerId: 'sophie',  treatmentId: 'co2',   clinicianId: 'emma',  noticeBefore: 'Anytime',         when: '2 days ago' },
  { id: 'wl2', customerId: 'mark',    treatmentId: 'hifu',  clinicianId: 'daniel',   noticeBefore: 'Eves & Sat only', when: '4 days ago' },
  { id: 'wl3', customerId: 'jess',    treatmentId: 'micro', clinicianId: 'grace', noticeBefore: 'Anytime',         when: 'Today' },
];

// Staff working hours / shifts for the week. Indexed by clinicianId.
// Each entry: array of length 7 (Mon..Sun) → "HH:MM-HH:MM" or null (off)
const STAFF_SHIFTS = {
  emma:  ['09:00-18:00','09:00-19:30','09:00-19:30','09:00-18:00','09:00-16:00', null,         null],
  grace: [null,         '09:00-18:00','10:00-19:30','10:00-19:30','09:00-18:00','09:00-14:00', null],
  daniel:   [null,         '12:00-18:00', null,        '12:00-19:30', null,        null,         null],
  sofia:   ['09:00-15:00','09:00-15:00', null,        '14:00-19:30','09:00-18:00','09:00-16:00', null],
};

// Treatment rooms — demo clinic layout (Q4, room allocation list).
//   Room 1 (Laser Suite) — laser tattoo / eyebrow, CO₂, sunspot/pigmentation
//   Room 2 (LHR Suite)               — Laser Hair Removal only
//   Room 3 (Skin & Body Studio)                  — body + skin tech (Lipo, Cryo, HydraFacial, HIFU, RF microneedling, thread, mole)
//   Flexible (Room 1 or Room 3)         — facials, injectables, polynucleotides, profhilo, electrolysis
// `allowedTreatments` enforces room ↔ treatment routing in the booking flow.
const ROOMS = [
  { id: 'room1', name: 'Room 1', alias: 'Laser Suite', type: 'treatment',
    allowedTreatments: [
      'ltr', 'ler', 'co2', 'co2-scar', 'ss',                               // exclusive to Room 1
      'prescription-facial', 'epn', 'byconcern', 'bloom', 'exosomes-facial', // flexible
      'awi', 'profhilo', 'polynuc', 'skin-booster', 'rf-hair', 'electrolysis', // flexible
      'fire', 'micro',                                                         // legacy IDs (flexible)
    ] },
  { id: 'room2', name: 'Room 2', alias: 'LHR Suite', type: 'treatment',
    allowedTreatments: ['lhr'] /* Laser Hair Removal only */ },
  { id: 'room3', name: 'Room 3', alias: 'Skin & Body Studio', type: 'treatment',
    allowedTreatments: [
      'lipo', 'cryo', 'hydra', 'hifu', 'rfmicro', 'thread', 'mole',         // exclusive to Room 3
      'prescription-facial', 'epn', 'byconcern', 'bloom', 'exosomes-facial', // flexible
      'awi', 'profhilo', 'polynuc', 'skin-booster', 'rf-hair', 'electrolysis', // flexible
      'fire', 'micro',                                                         // legacy IDs (flexible)
    ] },
];

// Approved leave — blocked off in calendar
const LEAVE = [
  { id: 'lv1', clinicianId: 'daniel',   from: offsetDay(8),  to: offsetDay(14), kind: 'Vacation',   note: 'Family holiday' },
  { id: 'lv2', clinicianId: 'grace', from: offsetDay(21), to: offsetDay(21), kind: 'CPD',        note: 'Galderma masterclass' },
];

// Medical-history form — answers + signature for the form viewer (§4)
const MEDICAL_FORMS = {
  sophie: {
    signedDate: '2025-04-18', signedBy: 'Sophie Reynolds', version: 'v3.2',
    answers: [
      { q: 'Are you currently pregnant or breastfeeding?',           a: 'No' },
      { q: 'Do you have any known allergies?',                       a: 'None' },
      { q: 'Are you taking any prescription medications?',           a: 'Vitamin D supplement only' },
      { q: 'Have you had any aesthetic treatment in the last 30 days?', a: 'HydraFacial · 26 Mar 2025' },
      { q: 'Skin type (Fitzpatrick)',                                a: 'III · medium, sometimes burns' },
      { q: 'Are you using retinoids or AHAs at home?',               a: 'Yes · retinoid PM, alternate nights' },
      { q: 'Any history of cold sores or HSV?',                      a: 'No' },
      { q: 'Heart conditions, pacemaker, or implants?',              a: 'No' },
      { q: 'Have you had any treatment with isotretinoin (Roaccutane) in the last 12 months?', a: 'No' },
      { q: 'Smoker?',                                                 a: 'No' },
    ],
  },
  hannah: {
    signedDate: '2025-08-15', signedBy: 'Hannah Whitlock', version: 'v3.2',
    answers: [
      { q: 'Are you currently pregnant or breastfeeding?',           a: 'No' },
      { q: 'Do you have any known allergies?',                       a: 'Penicillin (rash)' },
      { q: 'Are you taking any prescription medications?',           a: 'No' },
      { q: 'Skin type (Fitzpatrick)',                                a: 'III' },
      { q: 'Any history of cold sores or HSV?',                      a: 'No' },
      { q: 'Have you had any treatment with isotretinoin (Roaccutane) in the last 12 months?', a: 'No' },
      { q: 'Smoker?',                                                a: 'No' },
    ],
  },
};

// Email templates — generic demo wording written for the Demo Clinic demo.
// Every clinic gets their own wording; these show where it lives and how it fires.
//   `from`     — 'team' = "The Team at Demo Clinic", 'clinician' = "{{clinician}} at Demo Clinic"
//   `channel`  — 'email'
//   Variables — {{first}} {{treatment}} {{clinician}} {{date}} {{time}} {{slot}}
//               {{form}} {{forms}} {{precare}} {{postcare}} {{location}}
//
// Groups (in order below):
//   A. Booking journey (instant confirmation → form chase → 72h pre-care → 1h aftercare → 48h review)
//   B. Patch test follow-ups (24h after, booked / unbooked variants)
//   C. Per-treatment follow-up
//   D. Lead / assessment-form flow
//   E. Personalised advice no-reply sequence
//   F. Manual + utility templates
const COMM_TEMPLATES = [

  // ─── A. Standard booking journey ──────────────────────────────────────────
  { id: 'confirmation', name: 'Booking confirmation · instant', channel: 'email', from: 'team',
    subject: 'Your booking is confirmed · {{treatment}}',
    body:
      'Hi {{first}},\n\n' +
      'Location: {{location}}\n' +
      'Date: {{date}}\n' +
      'Time: {{time}}\n' +
      'Service: {{treatment}}\n' +
      'With: {{clinician}}\n\n' +
      'Thank you for booking with us — we look forward to seeing you.\n\n' +
      'BEFORE YOUR APPOINTMENT\n' +
      'Please complete your medical questionnaire within 24 hours of booking. Your appointment is not fully confirmed until it has been submitted.\n\n' +
      '[Complete medical form]\n\n' +
      'DEPOSITS & CANCELLATIONS\n' +
      '• A 50% deposit secures your booking.\n' +
      '• Deposits are non-refundable if you cancel within 72 hours of your appointment.\n' +
      '• If you decide not to proceed, your deposit is held as credit on your file.\n' +
      '• To reschedule or cancel, call us on 01234 567890 or reply to this email.\n\n' +
      'If you are running late, please let us know as soon as possible.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'form-reminder-24h', name: 'Medical form reminder · 24h after booking', channel: 'email', from: 'team',
    subject: 'Please complete your medical form',
    body:
      'Hi {{first}},\n\n' +
      'You have an upcoming appointment with us — before we can fully confirm it, we just need your medical history form. The link is below and it only takes a couple of minutes.\n\n' +
      '[Complete medical form]\n\n' +
      'Your appointment is held provisionally while we wait for this, so if you can complete it today we can secure your time.\n\n' +
      'Any trouble with the link, just reply to this email.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'pretreat-72h', name: 'Pre-care reminder · 72h before', channel: 'email', from: 'team',
    subject: '3 days to go · your {{treatment}} appointment',
    body:
      'Hi {{first}},\n\n' +
      'A reminder of your upcoming appointment:\n\n' +
      'Date: {{date}}\n' +
      'Time: {{time}}\n' +
      'Service: {{treatment}}\n' +
      'With: {{clinician}}\n\n' +
      'Please confirm you are attending using the button below.\n\n' +
      '[Confirm attending]\n\n' +
      'Pre-care for your treatment:\n\n' +
      '{{precare}}\n\n' +
      'If you need to change your appointment, please remember our 72-hour cancellation policy.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'aftercare-1h', name: 'Aftercare + review · 1h after', channel: 'email', from: 'clinician',
    subject: 'Thank you for visiting us today',
    body:
      'Hi {{first}},\n\n' +
      'Thank you for visiting us today — we hope you enjoyed your appointment.\n\n' +
      'Your aftercare for {{treatment}}:\n\n' +
      '{{postcare}}\n\n' +
      'If you have a moment, we would love a quick review — it makes a real difference to a small clinic:\n\n' +
      '[Google Review]   [Trustpilot]\n\n' +
      'Any questions at all, just reply to this email.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'review-nudge-48h', name: 'Review nudge · 48h after first appt (no review yet)', channel: 'email', from: 'team',
    subject: 'How was your first {{treatment}} with us?',
    body:
      'Hi {{first}},\n\n' +
      'Just checking in to see how you have been since your first {{treatment}} with us.\n\n' +
      'If you were happy with your visit, a short review would mean a lot:\n\n' +
      '[Google Review]   [Trustpilot]\n\n' +
      'And if anything was not right, please tell us first — we will always put it right.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'review-thanks-48h', name: 'Review thanks · 48h after first appt (review left)', channel: 'email', from: 'team',
    subject: 'Thank you so much, {{first}}',
    body:
      'Hi {{first}},\n\n' +
      'Thank you for taking the time to leave us a review — we really appreciate it.\n\n' +
      'Any questions about your treatment or aftercare, just reply to this email.\n\n' +
      'We look forward to seeing you again soon.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  // ─── B. Patch test 24h check-in ───────────────────────────────────────────
  { id: 'patch-checkin-unbooked', name: 'Patch test check-in · 24h after (not booked)', channel: 'email', from: 'team',
    subject: 'How was your patch test?',
    body:
      'Hi {{first}},\n\n' +
      'Just checking in after your patch test. Any reaction would show as itching, swelling, redness or irritation — if you have noticed anything, please let us know.\n\n' +
      'If all is well and you would like to book your first session, call us on 01234 567890 or reply with a good time to ring you.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'patch-checkin-booked', name: 'Patch test check-in · 24h after (already booked)', channel: 'email', from: 'team',
    subject: 'How was your patch test?',
    body:
      'Hi {{first}},\n\n' +
      'Just checking in after your patch test. Any reaction would show as itching, swelling, redness or irritation — if you have noticed anything, please let us know before your first session.\n\n' +
      'Otherwise, we look forward to seeing you at your appointment.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  // ─── C. Per-treatment follow-up if not rebooked ───────────────────────────
  { id: 'followup-lhr', name: 'Laser hair removal follow-up', channel: 'email', from: 'team',
    subject: 'How have you been since your laser hair removal?',
    body:
      'Hi {{first}},\n\n' +
      'Just checking in since your last laser hair removal session. Results build session by session, so now is a good time to book your next one if you have not already.\n\n' +
      'Call us on 01234 567890 or reply to this email and we will find you a time.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'followup-ltr', name: 'Tattoo removal follow-up', channel: 'email', from: 'team',
    subject: 'How have you been since your tattoo removal session?',
    body:
      'Hi {{first}},\n\n' +
      'Just checking in since your last tattoo removal session — the fade continues for several weeks after each visit.\n\n' +
      'When you are ready for your next session, call us on 01234 567890 or reply to this email.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'followup-pigmentation', name: 'Pigmentation follow-up', channel: 'email', from: 'team',
    subject: 'How is your skin looking?',
    body:
      'Hi {{first}},\n\n' +
      'Just checking in since your pigmentation treatment. If you would like a review of the area, or you are ready for your next session, we would love to see you.\n\n' +
      'Call us on 01234 567890 or reply to this email.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'followup-electrolysis', name: 'Electrolysis follow-up', channel: 'email', from: 'team',
    subject: 'How have you been since your electrolysis?',
    body:
      'Hi {{first}},\n\n' +
      'Just checking in since your last electrolysis session. Regular sessions get the best results, so now is a good time to book your next visit.\n\n' +
      'Call us on 01234 567890 or reply to this email.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'followup-facial', name: 'Facial follow-up · maintenance', channel: 'email', from: 'team',
    subject: 'How has your skin been since your facial?',
    body:
      'Hi {{first}},\n\n' +
      'We hope your skin has been feeling great since your last facial. For most of our facials we recommend a regular rhythm to keep the results building — so this is the ideal time to book your next one.\n\n' +
      'Call us on 01234 567890 or reply to this email and we will get you booked in.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'followup-fatfreeze', name: 'Fat reduction follow-up · 2.5 months', channel: 'email', from: 'team',
    subject: 'How are your results coming along?',
    body:
      'Hi {{first}},\n\n' +
      'It has been around two and a half months since your treatment, which is when results typically show. How is the area looking?\n\n' +
      'If you would like us to review your progress — including retaking your measurements — we can arrange a complimentary review appointment.\n\n' +
      'Call us on 01234 567890 or reply to this email.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  // ─── D. Lead / assessment-form flow (new enquiry) ─────────────────────────
  { id: 'lead-first', name: 'Lead · first auto-response', channel: 'email', from: 'team',
    subject: 'Thank you for getting in touch',
    body:
      'Hi {{first}},\n\n' +
      'Thank you for your message — we would love to help.\n\n' +
      'So the right specialist can give you accurate, personal advice, please complete our short assessment form. It lets you describe your concern and upload a few clear photos of the area:\n\n' +
      'https://clientloop.uk/assessment\n\n' +
      'Once submitted, it is reviewed confidentially by the most appropriate specialist, and we will come back to you within 24-48 hours with suitable options and a guide to pricing.\n\n' +
      'Any questions, call us on 01234 567890 or just reply to this email.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'lead-day2', name: 'Lead · day-2 follow-up (no reply)', channel: 'email', from: 'team',
    subject: 'Did our message reach you?',
    body:
      'Hi {{first}},\n\n' +
      'Just checking you received our earlier message with the assessment form link.\n\n' +
      'Completing it lets our specialists review your concern and reply with personal advice and pricing — all complimentary:\n\n' +
      'https://clientloop.uk/assessment\n\n' +
      'Any questions, call us on 01234 567890 or reply to this email.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'lead-day12', name: 'Lead · final follow-up (12 days later)', channel: 'email', from: 'team',
    subject: 'Just checking in',
    body:
      'Hi {{first}},\n\n' +
      'Just checking in about your treatment enquiry — if you have any questions at all, ask away.\n\n' +
      'You can complete the short assessment form at any time and we will come back with personal advice.\n\n' +
      'If you would rather not receive further messages, just let us know. You are always welcome back in future.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'lead-confirm-form', name: 'Lead · assessment form received', channel: 'email', from: 'team',
    subject: 'Thank you — we have your assessment form',
    body:
      'Hi {{first}},\n\n' +
      'Thank you for completing your assessment form — you are in good hands.\n\n' +
      'One of our specialists will review the information and photos you provided and reply within 24-48 hours with personal advice, suitable options and a guide to pricing.\n\n' +
      'In the meantime, any questions at all — call us on 01234 567890 or simply reply.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'lead-image-request', name: 'Lead · image-request booking fix (manual)', channel: 'email', from: 'team',
    subject: 'A quick clarification on your booking',
    body:
      'Hi {{first}},\n\n' +
      'Thank you for your booking. Just to clarify: what you selected is an online assessment request rather than an in-clinic appointment, so I have cancelled the diary slot for now — the assessment comes first.\n\n' +
      'Please complete the short form below with a little detail and a few clear photos of the area:\n\n' +
      'https://clientloop.uk/assessment\n\n' +
      'Our specialist will review it confidentially and reply within 24-48 hours with advice, options and pricing, plus which in-clinic appointment to book.\n\n' +
      'Any questions, just let me know.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  // ─── E. Personalised advice no-reply sequence ─────────────────────────────
  { id: 'advice-day2', name: 'Advice no-reply · day 2', channel: 'email', from: 'team',
    subject: 'Did your personalised advice reach you?',
    body:
      'Hi {{first}},\n\n' +
      'Just making sure you received the personalised advice we sent after your assessment.\n\n' +
      'There is no pressure to decide anything quickly — most people like time to read and think. If anything is unclear, or you would rather talk it through in a consultation, we are happy to help either way.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'advice-day10', name: 'Advice no-reply · day 10', channel: 'email', from: 'team',
    subject: 'Would it help to talk it through?',
    body:
      'Hi {{first}},\n\n' +
      'Following up in case it would help to talk through the advice we sent.\n\n' +
      'You do not need everything figured out first — many clients find a short consultation makes the options much clearer. If you would like one, just reply and we will arrange it.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'advice-day18', name: 'Advice no-reply · day 18', channel: 'email', from: 'team',
    subject: 'The first step is the hardest part',
    body:
      'Hi {{first}},\n\n' +
      'We know deciding on treatment can feel like a big step, and there is never any pressure from us to rush.\n\n' +
      'Your personalised advice is there whenever you are ready. Some people book straight away; others prefer to come in and talk first. Both are absolutely fine.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'advice-day35', name: 'Advice no-reply · day 35', channel: 'email', from: 'team',
    subject: 'How our team works',
    body:
      'Hi {{first}},\n\n' +
      'We thought it might help to share how we work: every concern is assessed individually by a specialist trained in that area — listen carefully, assess thoroughly, explain clearly, treat safely.\n\n' +
      'Whenever you feel ready, you are very welcome to arrange a consultation or appointment.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'advice-day55', name: 'Advice no-reply · day 55', channel: 'email', from: 'team',
    subject: 'Worth knowing before any treatment',
    body:
      'Hi {{first}},\n\n' +
      'One thing we hear a lot in consultations: "I did not realise how much there was to consider."\n\n' +
      'Some treatments can start straight away; others are best begun with a consultation. The goal is not to start quickly — it is to start correctly, and we will always guide you on which is right for you.\n\n' +
      'If you feel ready, we would be happy to arrange an appointment.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'advice-day80', name: 'Advice no-reply · day 80', channel: 'email', from: 'team',
    subject: 'Still deciding? That is fine',
    body:
      'Hi {{first}},\n\n' +
      'Choosing a clinic takes time, and plenty of our clients researched for a while before their first visit. What they tell us afterwards is that clear, honest guidance made the decision easier.\n\n' +
      'If treatment is still on your mind, the next step is simply a consultation — we would be happy to help whenever you are ready.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  { id: 'advice-day110', name: 'Advice no-reply · day 110 (final)', channel: 'email', from: 'team',
    subject: 'Just checking in',
    body:
      'Hi {{first}},\n\n' +
      'One last check-in following the advice we sent. If you have any questions, you are very welcome to ask.\n\n' +
      'If now is not the right time, that is absolutely fine — and if you would rather not hear from us again, just say and we will update your preferences.\n\n' +
      'You are always welcome back in the future.\n\n' +
      'Best wishes,\n' +
      'The Team at Demo Clinic'
  },

  // ─── F. Utility / extras ──────────────────────────────────────────────────
  { id: 'noshow', name: 'No-show follow-up', channel: 'email', from: 'team',
    subject: 'We missed you today · {{treatment}}',
    body:
      'Hi {{first}},\n\n' +
      'We were expecting you for {{treatment}} today at {{time}} and did not see you — we hope everything is OK.\n\n' +
      'Under our cancellation policy the deposit for a missed appointment is non-refundable, but if something came up, reply and let us know — we will do our best to help.\n\n' +
      'Would you like to rebook?\n\n' +
      '[Book again]\n\n' +
      'Best wishes,\nThe Team at Demo Clinic'
  },

  { id: 'birthday', name: 'Birthday wishes', channel: 'email', from: 'team',
    subject: 'Happy birthday, {{first}}',
    body:
      'Hi {{first}},\n\n' +
      'Happy birthday from all of us at the clinic!\n\n' +
      'If you fancy treating yourself this month, call us on 01234 567890 and we will get you booked in.\n\n' +
      'Best wishes,\nThe Team at Demo Clinic'
  },

  { id: 'course-complete', name: 'Course completion · congrats', channel: 'email', from: 'clinician',
    subject: 'You have completed your {{treatment}} course',
    body:
      'Hi {{first}},\n\n' +
      'That is your {{treatment}} course complete — well done for sticking with it!\n\n' +
      'The priority now is maintenance: keep up your home routine, SPF every day, and a 12-week review with comparison photos.\n\n' +
      'I have held a review slot with {{clinician}} on {{slot}}:\n\n' +
      '[Confirm review]\n\n' +
      'Any questions in the meantime, reply directly.\n\n' +
      '{{clinician}} at Demo Clinic'
  },

  { id: 'reengagement', name: 'Re-engagement · 12 months inactive', channel: 'email', from: 'team',
    subject: 'It has been a while — we would love to see you',
    body:
      'Hi {{first}},\n\n' +
      'It has been a while since your last visit, and we would love to see you again — we have added several new treatments since you were last in.\n\n' +
      'Call us on 01234 567890 and we will find you a time that works.\n\n' +
      'Best wishes,\nThe Team at Demo Clinic'
  },

  { id: 'rebook', name: 'Time to rebook · 6 weeks', channel: 'email', from: 'team',
    subject: 'Time for your next visit',
    body:
      'Hi {{first}},\n\n' +
      'It has been six weeks since your last {{treatment}} — the perfect time for a top-up. We have held a slot with {{clinician}} on {{slot}}:\n\n' +
      '[Book now]\n\n' +
      'Best wishes,\nThe Team at Demo Clinic'
  },

  { id: 'consent', name: 'Consent reminder', channel: 'email', from: 'team',
    subject: 'Please sign your {{form}}',
    body:
      'Hi {{first}},\n\n' +
      'A quick nudge — please sign your {{form}} before your appointment on {{date}}. It takes two minutes online.\n\n' +
      'Best wishes,\nThe Team at Demo Clinic'
  },

  { id: 'consent-email', name: 'Forms reminder · email', channel: 'email', from: 'team',
    subject: 'A quick reminder — please sign your forms before your visit',
    body:
      'Hi {{first}},\n\n' +
      'Before your visit on {{date}}, please sign the forms below — they take two minutes and let us treat you safely.\n\n' +
      'Forms still to sign:\n{{forms}}\n\n' +
      'Tap the link in this email to sign, or arrive 5 minutes early and we will set you up in the clinic.\n\n' +
      'Best wishes,\n{{clinician}} at Demo Clinic'
  },

  { id: 'reminder', name: 'Reminder · 24h before', channel: 'email', from: 'team',
    subject: 'Your {{treatment}} is tomorrow at {{time}}',
    body:
      'Hi {{first}},\n\n' +
      'A reminder of your {{treatment}} with {{clinician}} tomorrow at {{time}}. If you can no longer make it, please reply to this email or call us.\n\n' +
      'Best wishes,\nThe Team at Demo Clinic'
  },

  { id: 'aftercare', name: 'Aftercare check-in · day 2', channel: 'email', from: 'clinician',
    subject: 'Your aftercare from yesterday',
    body:
      'Hi {{first}},\n\n' +
      'It was lovely seeing you yesterday — you took the {{treatment}} brilliantly. A quick aftercare recap:\n\n' +
      '• SPF 50 daily, no exceptions\n• Skip retinoids and exfoliants for 5 days\n• Drink plenty of water\n• Any redness past 48 hours, message us\n\n' +
      'Best wishes,\n{{clinician}} at Demo Clinic'
  },

  { id: 'pretreat', name: 'Pre-treatment prep', channel: 'email', from: 'team',
    subject: 'Preparing for your {{treatment}}',
    body:
      'Hi {{first}},\n\n' +
      'Your {{treatment}} is on {{date}} at {{time}} — how to prepare:\n\n' +
      '• Stop retinoids 5 days before\n• No alcohol 24 hours before\n• Arrive with clean skin, no makeup\n• Eat something light beforehand\n\n' +
      'Questions? Reply to this email.\n\nThe Team at Demo Clinic'
  },
];


// Communication log — message history per customer (§7)
const COMM_LOG = {
  sophie: [
    { id: 'm1', when: '2026-04-28T09:14', direction: 'in',  channel: 'email', subject: 'Question about home routine', from: 'Sophie Reynolds',     to: 'Grace',         body: 'Hi Grace, my skin\'s a bit flaky after the retinoid — should I cut down to twice a week?', read: false },
    { id: 'm2', when: '2026-04-15T11:02', direction: 'out', channel: 'email', subject: 'Aftercare from yesterday',    from: 'Grace Hollis',    to: 'Sophie',          body: 'Hi Sophie,\n\nIt was lovely seeing you yesterday…', read: true,  templateId: 'aftercare', opened: true,  openedAt: '2026-04-15T15:21' },
    { id: 'm3', when: '2026-04-14T18:00', direction: 'out', channel: 'email', subject: 'Your HydraFacial is tomorrow at 09:00', from: 'Demo Clinic',                to: 'sophie.r@email.co.uk', body: 'Hi Sophie, reminder of your HydraFacial with Grace tomorrow at 09:00…', read: true, templateId: 'reminder', opened: true, openedAt: '2026-04-14T18:01' },
  ],
  hannah: [
    { id: 'm4', when: '2026-04-29T17:44', direction: 'in',  channel: 'email', subject: 'CO2 aftercare — day 4',       from: 'Hannah Whitlock',     to: 'Emma',          body: 'Just checking in, the redness is settling but I\'m a bit itchy on the cheeks — normal?', read: false },
    { id: 'm5', when: '2026-09-29T08:00', direction: 'out', channel: 'email', subject: 'Aftercare from yesterday',    from: 'Emma Carter',      to: 'Hannah',          body: 'Hi Hannah, the redness will settle…', read: true, templateId: 'aftercare', opened: true, openedAt: '2026-09-29T11:30' },
  ],
  amelia: [
    { id: 'm6', when: '2026-04-22T10:00', direction: 'out', channel: 'email', subject: 'We\'ve missed you at Demo Clinic',   from: 'Sofia Marsh',       to: 'Amelia',          body: 'Hi Amelia, we haven\'t seen you in a while…', read: true, templateId: 'reengagement', opened: false },
  ],
  mark: [], lucy: [], jess: [], priya: [], tom: [],
};

// Communication workflows (§7.5) — automation rules.
// Each rule fires a template at a trigger (relative to event).
// Demo automation rules.
// `group` lets the WorkflowsEditor cluster related rules visually.
const WORKFLOWS = [
  // ── A. Booking journey ───────────────────────────────────────────────────
  { id: 'wf-confirm',     group: 'Booking',  name: 'Booking confirmation · instant',           trigger: 'on_booking',    offsetHours: 0,     templateId: 'confirmation',         active: true,  fired: 248 },
  { id: 'wf-form-24h',    group: 'Booking',  name: 'Medical form reminder · 24h after booking',trigger: 'forms_pending', offsetHours: 24,    templateId: 'form-reminder-24h',    active: true,  fired: 41 },
  { id: 'wf-pre-72h',     group: 'Booking',  name: 'Pre-care reminder · 72h before',           trigger: 'before_appt',   offsetHours: -72,   templateId: 'pretreat-72h',         active: true,  fired: 142 },
  { id: 'wf-aftercare-1h',group: 'Booking',  name: 'Aftercare + review · 1h after',            trigger: 'after_appt',    offsetHours: 1,     templateId: 'aftercare-1h',         active: true,  fired: 96 },
  { id: 'wf-review-nudge',group: 'Booking',  name: 'Review nudge · 48h after first appt',      trigger: 'after_first',   offsetHours: 48,    templateId: 'review-nudge-48h',     active: true,  fired: 18, conditions: 'first appointment · no review left' },
  { id: 'wf-review-thanks',group: 'Booking', name: 'Review thanks · 48h after first appt',     trigger: 'after_first',   offsetHours: 48,    templateId: 'review-thanks-48h',    active: true,  fired: 11, conditions: 'first appointment · review left' },
  { id: 'wf-noshow',      group: 'Booking',  name: 'No-show follow-up',                        trigger: 'no_show',       offsetHours: 2,     templateId: 'noshow',               active: true,  fired: 7 },

  // ── B. Patch test follow-ups ─────────────────────────────────────────────
  { id: 'wf-patch-unbooked', group: 'Patch test', name: 'Patch test check-in · 24h (unbooked)',trigger: 'after_patch',   offsetHours: 24,    templateId: 'patch-checkin-unbooked', active: true, fired: 9,  conditions: 'no booking made within 24h of patch test' },
  { id: 'wf-patch-booked',   group: 'Patch test', name: 'Patch test check-in · 24h (booked)',  trigger: 'after_patch',   offsetHours: 24,    templateId: 'patch-checkin-booked',   active: true, fired: 14, conditions: 'booking made within 24h of patch test' },

  // ── C. Per-treatment follow-up (configurable interval per treatment) ─────
  { id: 'wf-followup-lhr',           group: 'Follow-up', name: 'LHR follow-up · 4/6/8/12 weeks',       trigger: 'treatment_followup', offsetHours: 1008, templateId: 'followup-lhr',           active: true, fired: 32, conditions: 'treatment = Laser Hair Removal · interval per client' },
  { id: 'wf-followup-ltr',           group: 'Follow-up', name: 'LTR follow-up · 6/8/12 weeks',         trigger: 'treatment_followup', offsetHours: 1344, templateId: 'followup-ltr',           active: true, fired: 8,  conditions: 'treatment = LTR or Laser Eyebrow · interval per client' },
  { id: 'wf-followup-pigmentation',  group: 'Follow-up', name: 'Pigmentation follow-up · 6/8/12 weeks',trigger: 'treatment_followup', offsetHours: 1344, templateId: 'followup-pigmentation',  active: true, fired: 5,  conditions: 'treatment = Sunspot / Pigmentation Removal · interval per client' },
  { id: 'wf-followup-electrolysis',  group: 'Follow-up', name: 'Electrolysis follow-up',               trigger: 'treatment_followup', offsetHours: 672,  templateId: 'followup-electrolysis',  active: true, fired: 2,  conditions: 'treatment = Electrolysis · interval per client' },
  { id: 'wf-followup-facial',        group: 'Follow-up', name: 'Facial follow-up · maintenance',       trigger: 'treatment_followup', offsetHours: 1008, templateId: 'followup-facial',        active: true, fired: 17, conditions: 'treatment in facials · interval per client' },
  { id: 'wf-followup-fatfreeze',     group: 'Follow-up', name: 'Fat reduction / cryo · 2.5 months',    trigger: 'after_appt',         offsetHours: 1800, templateId: 'followup-fatfreeze',     active: true, fired: 4,  conditions: 'treatment in Cryolipolysis, body contouring, Fat Reduction' },

  // ── D. Lead / assessment-form flow ───────────────────────────────────────
  { id: 'wf-lead-first',  group: 'Leads', name: 'Lead · first auto-response',            trigger: 'enquiry_received',    offsetHours: 0,     templateId: 'lead-first',          active: true, fired: 187 },
  { id: 'wf-lead-day2',   group: 'Leads', name: 'Lead · day-2 follow-up (no reply)',     trigger: 'enquiry_no_reply',    offsetHours: 48,    templateId: 'lead-day2',           active: true, fired: 62 },
  { id: 'wf-lead-task',   group: 'Leads', name: 'Lead · phone-call task (no reply)',     trigger: 'enquiry_no_reply',    offsetHours: 120,   templateId: '',                    active: true, fired: 28, conditions: 'create task in reception list · check spam first' },
  { id: 'wf-lead-day12',  group: 'Leads', name: 'Lead · final follow-up (12 days)',      trigger: 'enquiry_no_reply',    offsetHours: 288,   templateId: 'lead-day12',          active: true, fired: 19 },
  { id: 'wf-lead-confirm',group: 'Leads', name: 'Lead · assessment form received',       trigger: 'form_submitted',      offsetHours: 0,     templateId: 'lead-confirm-form',   active: true, fired: 114 },

  // ── E. Personalised advice no-reply sequence (8 touches / 110 days) ──────
  { id: 'wf-advice-d2',   group: 'Advice no-reply', name: 'Advice · day 2',     trigger: 'advice_sent', offsetHours: 48,    templateId: 'advice-day2',   active: true, fired: 46 },
  { id: 'wf-advice-task', group: 'Advice no-reply', name: 'Advice · phone-call task', trigger: 'advice_sent', offsetHours: 144, templateId: '',         active: true, fired: 22, conditions: 'task to reception · check spam · attempt phone' },
  { id: 'wf-advice-d10',  group: 'Advice no-reply', name: 'Advice · day 10',    trigger: 'advice_sent', offsetHours: 240,   templateId: 'advice-day10',  active: true, fired: 34 },
  { id: 'wf-advice-d18',  group: 'Advice no-reply', name: 'Advice · day 18',    trigger: 'advice_sent', offsetHours: 432,   templateId: 'advice-day18',  active: true, fired: 23 },
  { id: 'wf-advice-d35',  group: 'Advice no-reply', name: 'Advice · day 35',    trigger: 'advice_sent', offsetHours: 840,   templateId: 'advice-day35',  active: true, fired: 14 },
  { id: 'wf-advice-d55',  group: 'Advice no-reply', name: 'Advice · day 55',    trigger: 'advice_sent', offsetHours: 1320,  templateId: 'advice-day55',  active: true, fired: 9 },
  { id: 'wf-advice-d80',  group: 'Advice no-reply', name: 'Advice · day 80',    trigger: 'advice_sent', offsetHours: 1920,  templateId: 'advice-day80',  active: true, fired: 6 },
  { id: 'wf-advice-d110', group: 'Advice no-reply', name: 'Advice · day 110 (final)', trigger: 'advice_sent', offsetHours: 2640, templateId: 'advice-day110', active: true, fired: 4 },

  // ── F. Other ─────────────────────────────────────────────────────────────
  { id: 'wf-rebook',      group: 'Other', name: 'Rebook prompt · 6 weeks',        trigger: 'after_appt',     offsetHours: 1008, templateId: 'rebook',         active: true,  fired: 21 },
  { id: 'wf-reengage',    group: 'Other', name: 'Re-engage · 12 months inactive', trigger: 'inactive_days',  offsetHours: 8760, templateId: 'reengagement',   active: true,  fired: 4 },
  { id: 'wf-birthday',    group: 'Other', name: 'Birthday wishes',                trigger: 'birthday',       offsetHours: 0,    templateId: 'birthday',       active: false, fired: 0 },
  { id: 'wf-course',      group: 'Other', name: 'Course completion · congrats',   trigger: 'course_done',    offsetHours: 0,    templateId: 'course-complete',active: false, fired: 0 },
];

// ============================================================
// FORMS catalogue — demo 27 medical forms (Q9, May-15 zip).
// Every treatment has its own bespoke medical form (legally distinct
// "Please read important information" sections per treatment).
// Each entry links to one or more treatment IDs so the booking flow can
// auto-attach the correct form. `latestVersion` lets us prompt re-sign
// when a form is updated (Q10).
// ============================================================
const FORMS = [
  // ── Treatment-specific forms (one per treatment)
  { id: 'form-lhr',         title: 'Laser Hair Removal Medical History',                    treatmentIds: ['lhr'],                 latestVersion: 'v3.1', updated: '2026-02-12' },
  { id: 'form-ltr',         title: 'Laser Tattoo Removal Medical History',                  treatmentIds: ['ltr'],                 latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-ler',         title: 'Laser Eyebrow Removal Medical History',                 treatmentIds: ['ler'],                 latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-co2',         title: 'CO₂ Laser Treatment Medical History',                   treatmentIds: ['co2', 'co2-scar'],     latestVersion: 'v3.2', updated: '2026-03-04' },
  { id: 'form-ss',          title: 'Sunspot & Pigmentation Removal Medical History',       treatmentIds: ['ss'],                  latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-thread',      title: 'Thread Vein Removal Medical History',                   treatmentIds: ['thread'],              latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-mole',        title: 'Mole & Skin Tag Removal Medical History',               treatmentIds: ['mole'],                latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-electrolysis',title: 'Electrolysis Hair Removal Medical History',             treatmentIds: ['electrolysis'],        latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-cryo',        title: 'Cryolipolysis Targeted Fat Freezing Medical History',   treatmentIds: ['cryo'],                latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-lipo',        title: 'Fat Reduction Medical History',                          treatmentIds: ['lipo'],                latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-hifu',        title: 'HIFU Medical Form & Consent',              treatmentIds: ['hifu'],                latestVersion: 'v3.1', updated: '2026-02-18' },
  { id: 'form-rfmicro',     title: 'RF Microneedling Form & Consent', treatmentIds: ['rfmicro'],           latestVersion: 'v3.1', updated: '2026-02-18' },
  { id: 'form-hydra',       title: 'HydraFacial Medical Form & Consent',    treatmentIds: ['hydra'],               latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-epn',         title: 'Dermal Microneedling with Electroporation & Mesotherapy Form & Consent', treatmentIds: ['epn'], latestVersion: 'v3.1', updated: '2026-02-18' },
  { id: 'form-prescription',title: 'Prescription Resurfacing Facial Medical Form & Consent',treatmentIds: ['prescription-facial'], latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-byconcern',   title: 'Targeted Concern Facial Medical History',                       treatmentIds: ['byconcern'],           latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-bloom',       title: 'Signature Glow Facial Medical History',                       treatmentIds: ['bloom'],               latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-skin-therapy',title: 'Skin Therapy Medical History',                           treatmentIds: ['prescription-facial', 'fire'], latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-exosomes',    title: 'Exosomes & Stem Cell Therapy Form & Consent',           treatmentIds: ['exosomes-facial'],     latestVersion: 'v3.1', updated: '2026-02-18' },
  { id: 'form-skin-boosters',title:'Skin Boosters Medical History',                          treatmentIds: ['skin-booster'],        latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-dermal-filler',title:'Dermal Filler Medical History',                          treatmentIds: ['profhilo', 'polynuc'], latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-rf-facial',   title: 'RadioFrequency Facial Medical History',                  treatmentIds: ['rfmicro'],             latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-rf-hair',     title: 'Radiofrequency Microneedling with Exosomes for Hair Loss', treatmentIds: ['rf-hair'],         latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-afterglow',   title: 'Carbon Laser Facial Medical History',         treatmentIds: ['fire'],                latestVersion: 'v3.0', updated: '2026-01-20' },
  { id: 'form-facial-aesthetics', title: 'Facial Aesthetics Medical Form',                  treatmentIds: ['awi'],                 latestVersion: 'v3.0', updated: '2026-01-20' },

  // ── Universal forms (apply to every customer regardless of treatment)
  { id: 'form-medical-history', title: 'Medical History (general)',                          treatmentIds: ['*'], universal: true,  latestVersion: 'v3.2', updated: '2026-03-04' },
  { id: 'form-photo-consent',   title: 'Photography & Data Consent',                          treatmentIds: ['*'], universal: true,  latestVersion: 'v2.4', updated: '2025-11-09' },
];

// ============================================================
// PRE_POST_CARES — generic demo pre/post care text per treatment.
// Substituted into the 72h reminder ({{precare}}) and 1h aftercare
// ({{postcare}}) emails when the workflow fires. Each clinic loads
// their own wording here.
// ============================================================
const PRE_POST_CARES = {
  'lhr': {
    pre:
      'DO:\n' +
      '• Shave the area 24 hours before your appointment\n' +
      '• Arrive with clean, product-free skin (no make-up, deodorant or tan)\n' +
      '• Use SPF 30-50 daily between sessions (not on the day)\n' +
      '• Tell us about any new medication or health changes\n\n' +
      'DO NOT:\n' +
      '• Use retinoids, acids or exfoliants within 48 hours\n' +
      '• Have sun exposure or use sunbeds for 2 weeks before\n' +
      '• Wax, pluck or thread during your course — shaving only',
    post:
      'DO:\n' +
      '• Keep the area clean and hydrated\n' +
      '• Apply SPF 30-50 daily\n' +
      '• Shave again once the skin has settled (24-48 hours)\n\n' +
      'DO NOT:\n' +
      '• Expose the area to sun or sunbeds during your course\n' +
      '• Apply acids or strong actives for 48 hours\n' +
      '• Use saunas, hot baths or heavy exercise for 24-48 hours\n' +
      '• Wax, pluck or thread during your course — shaving only',
  },
  'ltr': {
    pre:
      'DO:\n' +
      '• Arrive with clean, product-free skin over the area\n' +
      '• Use SPF 30-50 daily in the weeks before (not on the day)\n' +
      '• Stay well hydrated and tell us about any medication changes\n\n' +
      'DO NOT:\n' +
      '• Apply products or self-tan to the area on the day\n' +
      '• Use retinoids or exfoliating acids within 48-72 hours\n' +
      '• Expose the area to sun or sunbeds for 2-4 weeks before',
    post:
      'DO:\n' +
      '• Keep the area clean and dry for 24-48 hours\n' +
      '• Use aloe vera or a cool compress if needed (cloth between compress and skin)\n' +
      '• Apply SPF 30-50 daily once healed\n\n' +
      'DO NOT:\n' +
      '• Pick, scratch or rub the treated area\n' +
      '• Apply perfumed products or active skincare until fully healed\n' +
      '• Expose the area to sun or sunbeds for 2-4 weeks\n' +
      '• Use saunas, hot baths or exercise for 24-48 hours\n\n' +
      'Healing typically takes 7-14 days per session. Mild redness, swelling and light scabbing are normal.',
  },
  'ler': '__ref__:ltr',
  'co2': {
    pre:
      'DO:\n' +
      '• Stay well hydrated and use SPF 30-50 daily in the lead-up\n' +
      '• Follow any prescribed pre-treatment skincare\n' +
      '• Allow 2 weeks since injectables, peels or exfoliating facials\n\n' +
      'DO NOT:\n' +
      '• Arrive with make-up, fake tan or recent sun exposure\n' +
      '• Use active ingredients (AHA, BHA, retinol) within 72 hours\n' +
      '• Drink alcohol or exercise heavily within 24 hours before',
    post:
      'The first 24 hours the skin may feel warm and tight, like sunburn — this is normal.\n\n' +
      'DO:\n' +
      '• Cleanse gently with lukewarm water and pat dry\n' +
      '• Apply your post-laser balm frequently\n' +
      '• Rest, hydrate, and keep your head slightly elevated if swollen\n\n' +
      'DO NOT:\n' +
      '• Pick or touch the skin, wear make-up, or use active skincare\n' +
      '• Exercise, use heat (saunas, hot baths) or expose the skin to sun\n\n' +
      'Days 4-7 the skin peels — let it shed naturally. Results develop over 3 months as collagen rebuilds.\n\n' +
      'Contact us immediately for severe pain, blistering, discharge or spreading redness.',
  },
  'co2-scar': '__ref__:co2',
  'ss': {
    pre:
      'DO:\n' +
      '• Arrive with clean, product-free skin\n' +
      '• Use SPF 30-50 daily through your course (not on the day)\n\n' +
      'DO NOT:\n' +
      '• Use acids, exfoliants or retinol for 48 hours before\n' +
      '• Use self-tan, sunbeds or have sun exposure for 2-4 weeks before',
    post:
      'The treated area will darken and dry before flaking away naturally over 7-10 days.\n\n' +
      'DO:\n' +
      '• Keep the area clean and dry for 24-48 hours\n' +
      '• Apply SPF 30-50 daily and let scabs heal naturally\n\n' +
      'DO NOT:\n' +
      '• Scratch, rub or exfoliate the area\n' +
      '• Apply make-up or actives until healed (7-14 days)\n' +
      '• Use saunas, steam or heavy exercise for 24-48 hours',
  },
  'electrolysis': '__ref__:lhr',
  'cryo': {
    pre:
      'DO:\n' +
      '• Drink plenty of water in the days before\n' +
      '• Arrive with clean, product-free skin and loose comfortable clothing\n' +
      '• Eat something light beforehand if you are prone to feeling light-headed\n\n' +
      'DO NOT:\n' +
      '• Drink alcohol within 24 hours before\n' +
      '• Apply lotions, self-tan or SPF to the area on the day\n' +
      '• Expose the area to sun or sunbeds for 48 hours before',
    post:
      'The skin may look pink and feel firm, numb or tender for a few days — this is normal.\n\n' +
      'DO:\n' +
      '• Drink plenty of water to support lymphatic drainage\n' +
      '• Gently massage the area daily\n' +
      '• Keep up a healthy diet and regular movement — it supports your results\n\n' +
      'DO NOT:\n' +
      '• Use saunas, hot baths or steam rooms for 48 hours\n' +
      '• Exercise vigorously for 24 hours\n\n' +
      'Contact the clinic about any unexpected skin changes or discomfort lasting beyond 7-10 days.',
  },
  'lipo': '__ref__:cryo',
  'hifu': {
    pre:
      'DO:\n' +
      '• Stay hydrated and use SPF 30-50 daily\n' +
      '• Allow 2 weeks since facials or peels, 4 weeks since injectables in the area\n' +
      '• Tell us about any medication or health changes\n\n' +
      'DO NOT:\n' +
      '• Arrive wearing make-up or fake tan\n' +
      '• Use active skincare (acids, retinol) for 7 days before\n' +
      '• Take anti-inflammatory pain relief for 24-48 hours before',
    post:
      'DO:\n' +
      '• Drink plenty of water\n' +
      '• Use a cool compress for any swelling (never ice directly on skin)\n' +
      '• Apply SPF 30-50 daily from day 3\n\n' +
      'DO NOT:\n' +
      '• Scratch, pick or rub the skin, or wear make-up for 24-48 hours\n' +
      '• Use actives for 7 days, or have other facial treatments for 2 weeks\n' +
      '• Use saunas, hot baths or pools for 72 hours\n\n' +
      'Results develop gradually over 3-6 months as new collagen forms.',
  },
  'rfmicro': {
    pre:
      'DO:\n' +
      '• Stay hydrated and use SPF 30-50 daily\n' +
      '• Allow 2 weeks since facials or peels, 4 weeks since injectables in the area\n' +
      '• You may take paracetamol an hour before for comfort\n\n' +
      'DO NOT:\n' +
      '• Arrive wearing make-up or fake tan\n' +
      '• Use active skincare (acids, retinol) for 7 days before\n' +
      '• Take anti-inflammatory pain relief for 24-48 hours before',
    post:
      'DO:\n' +
      '• Drink plenty of water and treat the skin gently for 3-5 days\n' +
      '• Use a cool compress for swelling\n' +
      '• Apply SPF 30-50 daily from 24-48 hours\n\n' +
      'DO NOT:\n' +
      '• Scratch, pick or rub the skin, or wear make-up for 24-48 hours\n' +
      '• Use acids, retinol or exfoliants for 5-7 days\n' +
      '• Use saunas, hot baths or pools for 24-48 hours\n\n' +
      'Results build over the following weeks as new collagen forms.',
  },
  'epn': '__ref__:rfmicro',
  'thread': '__ref__:ss',
  'mole': {
    pre:
      'DO:\n' +
      '• Arrive with clean, product-free skin\n' +
      '• Avoid sun exposure on the area for 5-7 days before\n\n' +
      'DO NOT:\n' +
      '• Apply anything to the area on the day\n' +
      '• Attend with an unhealed or irritated lesion',
    post:
      'A small crust usually forms within 24-48 hours and heals over 1-2 weeks.\n\n' +
      'DO:\n' +
      '• Keep the area clean and dry for 24-48 hours, then clean gently with saline\n' +
      '• Use the dressing provided for the first 5-7 days\n' +
      '• Apply SPF 30-50 daily once new skin has formed\n\n' +
      'DO NOT:\n' +
      '• Pick, scratch or rub the area\n' +
      '• Use acids, exfoliants or retinol for at least 7 days\n' +
      '• Expose the area to sun during healing',
  },
  'hydra': {
    pre:
      'DO:\n' +
      '• Stay hydrated and use SPF 30-50 daily\n' +
      '• Allow 2 weeks since other facial treatments or injectables\n\n' +
      'DO NOT:\n' +
      '• Arrive wearing make-up or fake tan\n' +
      '• Use active skincare (acids, retinol) for 72 hours before',
    post:
      'DO:\n' +
      '• Cleanse gently morning and evening, moisturise generously\n' +
      '• Apply SPF 30-50 daily\n\n' +
      'DO NOT:\n' +
      '• Apply make-up for 24-48 hours\n' +
      '• Use exfoliants or actives for 7 days\n' +
      '• Use saunas, steam or heavy exercise for 24-48 hours',
  },
  'prescription-facial': '__ref__:hydra',
  'byconcern':           '__ref__:hydra',
  'bloom':               '__ref__:hydra',
  'fire':                '__ref__:hydra',
  'exosomes-facial':     '__ref__:rfmicro',
  'awi': {
    pre:
      'DO:\n' +
      '• Stay hydrated and use SPF daily\n\n' +
      'DO NOT:\n' +
      '• Arrive wearing make-up or fake tan\n' +
      '• Use active skincare for 72 hours before\n' +
      '• Drink alcohol or take anti-inflammatory pain relief for 24-48 hours before',
    post:
      'Mild redness, swelling, small bumps or bruising at injection points is normal for a few days.\n\n' +
      'DO:\n' +
      '• Apply a cool compress for any swelling\n' +
      '• Keep pillowcases and towels clean for 48-72 hours\n\n' +
      'DO NOT:\n' +
      '• Touch, rub or massage the area\n' +
      '• Apply make-up for 12-48 hours\n' +
      '• Use saunas, hot baths or strenuous exercise for 48 hours\n' +
      '• Drink alcohol for 24-48 hours\n\n' +
      'URGENT: contact the clinic immediately if you notice blanching of the skin (pale, white or blue with pain or numbness) or signs of allergic reaction. For severe reactions call 999.',
  },
  'profhilo':     '__ref__:awi',
  'polynuc':      '__ref__:awi',
  'skin-booster': '__ref__:awi',
  'rf-hair':      '__ref__:rfmicro',
  'micro':        '__ref__:rfmicro', // legacy treatment id → routed to RF microneedling care
};


// Resolve __ref__:XXX indirections in PRE_POST_CARES at load time
Object.keys(PRE_POST_CARES).forEach(k => {
  const v = PRE_POST_CARES[k];
  if (typeof v === 'string' && v.startsWith('__ref__:')) {
    const target = v.slice(8);
    PRE_POST_CARES[k] = PRE_POST_CARES[target];
  }
});

// ============================================================
// ENQUIRIES — assessment-form submissions / lead inbox 
// the demo CRM's help-desk equivalent: enquiries arrive via the website form,
// reception sees them with images + answers, drafts a reply, and either
// enrols them in the advice-no-reply workflow or routes them to booking.
// Each enquiry tracks workflow stage so the 8-touch advice sequence
// (Day 2/10/18/35/55/80/110) is visible per-lead.
// ============================================================
const ENQUIRIES = [
  {
    id: 'enq1', name: 'Eleanor Hartwell', email: 'e.hartwell@email.co.uk', phone: '07900 442 118',
    submittedAt: '2026-05-14T11:24:00', source: 'Website assessment form',
    concern: 'Pigmentation on cheeks + faint melasma on forehead — worse since summer',
    treatmentArea: 'Face · pigmentation',
    images: ['front', 'left', 'right'],
    answers: [
      { q: 'How long has this been a concern?',       a: '2 years — worse the last summer' },
      { q: 'Have you had any previous treatments?',    a: 'Topical hydroquinone (GP), 6 months' },
      { q: 'Pregnant or breastfeeding?',               a: 'No' },
      { q: 'Currently taking any medication?',         a: 'Vitamin D, multivitamin' },
      { q: 'Known allergies?',                          a: 'None' },
      { q: 'Skin type (Fitzpatrick)?',                 a: 'III' },
      { q: 'Budget range / preference?',                a: 'Open · prefer non-invasive first' },
    ],
    stage: 'received', assignedTo: 'emma',
    workflow: 'lead-no-reply', // workflow trajectory once advice is sent
    history: [
      { when: '2026-05-14T11:24', kind: 'submitted', note: 'Assessment form completed via website' },
      { when: '2026-05-14T11:25', kind: 'auto-reply',note: 'Auto-sent "Thank you for completing your assessment form"' },
    ],
  },
  {
    id: 'enq2', name: 'James Patel', email: 'jpatel@email.com', phone: '07744 220 559',
    submittedAt: '2026-05-09T18:02:00', source: 'Website assessment form',
    concern: 'Sleeve tattoo removal — left forearm. Black ink, ~6 years old.',
    treatmentArea: 'Body · tattoo removal',
    images: ['close-up-1', 'close-up-2'],
    answers: [
      { q: 'How long has the tattoo been there?',       a: '~6 years' },
      { q: 'Previous removal attempts?',                a: 'No' },
      { q: 'Ink colour(s)?',                            a: 'Black only' },
      { q: 'Skin type (Fitzpatrick)?',                  a: 'V' },
      { q: 'Pregnant or breastfeeding?',                a: 'N/A' },
      { q: 'Currently taking any medication?',          a: 'None' },
      { q: 'Known allergies?',                          a: 'None' },
    ],
    stage: 'advice-sent', assignedTo: 'emma',
    workflow: 'lead-no-reply',
    adviceSentAt: '2026-05-11T09:15:00',
    advice:
      'Recommended Laser Tattoo Removal · 6-session course at minimum, possibly 8-10 given size + density. ' +
      'Patch test required first. Estimated total £570-£950 across the course. ' +
      'Pre-care: avoid sun + tan beds for 4 weeks before each session.',
    history: [
      { when: '2026-05-09T18:02', kind: 'submitted',    note: 'Assessment form completed' },
      { when: '2026-05-09T18:03', kind: 'auto-reply',   note: 'Auto-sent "Thank you for completing"' },
      { when: '2026-05-11T09:15', kind: 'advice-sent',  note: 'Personalised LTR plan emailed by Emma' },
    ],
    advicePings: [
      { day: 2,   sentAt: '2026-05-13T09:15', opened: true,  clicked: false },
      { day: 10,  sentAt: null, opened: null, clicked: null },
      { day: 18,  sentAt: null, opened: null, clicked: null },
      { day: 35,  sentAt: null, opened: null, clicked: null },
      { day: 55,  sentAt: null, opened: null, clicked: null },
      { day: 80,  sentAt: null, opened: null, clicked: null },
      { day: 110, sentAt: null, opened: null, clicked: null },
    ],
  },
  {
    id: 'enq3', name: 'Mia Robertson', email: 'mia.r@email.com', phone: '07811 005 442',
    submittedAt: '2026-04-26T13:48:00', source: 'Website assessment form',
    concern: 'Jawline tightening — interested in HIFU but unsure if suitable. 39, post-pregnancy.',
    treatmentArea: 'Face · jawline / HIFU',
    images: ['front', 'left-three-quarter', 'right-three-quarter'],
    answers: [
      { q: 'Main concern?',                              a: 'Jowls / jawline laxity' },
      { q: 'Previous treatments?',                       a: 'Profhilo 1 round (different clinic, Jan)' },
      { q: 'Pregnant or breastfeeding?',                 a: 'No (stopped breastfeeding 4 months ago)' },
      { q: 'Currently taking any medication?',           a: 'None' },
      { q: 'Known allergies?',                           a: 'Penicillin' },
      { q: 'Any injectables in last 4 weeks?',           a: 'No' },
      { q: 'Any PDO threads ever?',                      a: 'No' },
    ],
    stage: 'advice-day10', assignedTo: 'emma',
    workflow: 'lead-no-reply',
    adviceSentAt: '2026-04-28T11:00:00',
    advice:
      'HIFU is suitable — Lower Face + Submandibular combo recommended. Single session £495, results 6-12 weeks. ' +
      'Alternative: another round of Profhilo + skin boosters first if she\'d prefer gentler approach. ' +
      'Patch test not required for HIFU.',
    history: [
      { when: '2026-04-26T13:48', kind: 'submitted',    note: 'Assessment form completed via website' },
      { when: '2026-04-26T13:49', kind: 'auto-reply',   note: 'Auto-sent confirmation' },
      { when: '2026-04-28T11:00', kind: 'advice-sent',  note: 'Personalised HIFU advice emailed by Emma' },
      { when: '2026-04-30T11:00', kind: 'follow-up',    note: 'Day-2 check-in sent · opened' },
      { when: '2026-05-08T11:00', kind: 'follow-up',    note: 'Day-10 check-in sent · not yet opened' },
    ],
    advicePings: [
      { day: 2,   sentAt: '2026-04-30T11:00', opened: true,  clicked: false },
      { day: 10,  sentAt: '2026-05-08T11:00', opened: false, clicked: false },
      { day: 18,  sentAt: null, opened: null, clicked: null },
      { day: 35,  sentAt: null, opened: null, clicked: null },
      { day: 55,  sentAt: null, opened: null, clicked: null },
      { day: 80,  sentAt: null, opened: null, clicked: null },
      { day: 110, sentAt: null, opened: null, clicked: null },
    ],
  },
  {
    id: 'enq4', name: 'Oliver Khan', email: 'olikhan@email.com', phone: '07733 882 117',
    submittedAt: '2026-05-13T22:14:00', source: 'Website assessment form',
    concern: 'Persistent acne scarring on cheeks — looking at CO2 vs RF microneedling.',
    treatmentArea: 'Face · scarring',
    images: ['front', 'left-close', 'right-close'],
    answers: [
      { q: 'How long has this been a concern?',       a: '~8 years post-teen acne' },
      { q: 'Previous treatments?',                     a: 'Microneedling at home (dermaroller), Roaccutane completed 5 years ago' },
      { q: 'Pregnant or breastfeeding?',               a: 'N/A' },
      { q: 'Currently taking any medication?',         a: 'None' },
      { q: 'Known allergies?',                         a: 'None' },
      { q: 'Skin type (Fitzpatrick)?',                 a: 'IV' },
      { q: 'Recent sun exposure?',                     a: 'None — UK based, no holiday booked' },
    ],
    stage: 'replied', assignedTo: 'emma',
    workflow: 'manual',
    history: [
      { when: '2026-05-13T22:14', kind: 'submitted',    note: 'Assessment form completed' },
      { when: '2026-05-13T22:15', kind: 'auto-reply',   note: 'Auto-sent confirmation' },
      { when: '2026-05-14T14:00', kind: 'replied',     note: 'Emma replied with CO2-scar suitability advice. Awaiting customer reply.' },
    ],
  },
  {
    id: 'enq5', name: 'Chloe Bennett', email: 'chloe.bennett@email.com', phone: '07900 552 113',
    submittedAt: '2026-02-04T16:31:00', source: 'Website assessment form',
    concern: 'Body hair removal — full leg + bikini.',
    treatmentArea: 'Body · laser hair removal',
    images: ['full-leg', 'bikini'],
    answers: [
      { q: 'Areas to treat?',                          a: 'Full leg + Hollywood bikini' },
      { q: 'Hair colour?',                              a: 'Dark brown' },
      { q: 'Skin type (Fitzpatrick)?',                  a: 'II' },
      { q: 'Previous LHR?',                             a: 'Yes — different clinic, ~5 years ago, mixed results' },
      { q: 'Currently shaving / waxing?',               a: 'Waxing every 4 weeks' },
      { q: 'Pregnant or breastfeeding?',                a: 'No' },
      { q: 'Currently taking any medication?',          a: 'OCP' },
    ],
    stage: 'closed', assignedTo: 'sofia',
    workflow: 'lead-no-reply',
    adviceSentAt: '2026-02-06T09:30:00',
    closedAt: '2026-05-01T00:00:00',
    closedReason: 'No reply after 110-day advice sequence · moved to long-term nurture',
    history: [
      { when: '2026-02-04T16:31', kind: 'submitted',   note: 'Assessment form completed' },
      { when: '2026-02-04T16:32', kind: 'auto-reply',  note: 'Auto-sent confirmation' },
      { when: '2026-02-06T09:30', kind: 'advice-sent', note: 'LHR course plan emailed by Sofia · £105/session, 8 sessions, 6-week intervals' },
      { when: '2026-02-08T09:30', kind: 'follow-up',   note: 'Day-2 sent · opened, no reply' },
      { when: '2026-02-16T09:30', kind: 'follow-up',   note: 'Day-10 sent · opened, no reply' },
      { when: '2026-02-24T09:30', kind: 'follow-up',   note: 'Day-18 sent · not opened' },
      { when: '2026-03-13T09:30', kind: 'follow-up',   note: 'Day-35 sent · not opened' },
      { when: '2026-04-02T09:30', kind: 'follow-up',   note: 'Day-55 sent · not opened' },
      { when: '2026-04-27T09:30', kind: 'follow-up',   note: 'Day-80 sent · not opened' },
      { when: '2026-05-26T09:30', kind: 'scheduled',   note: 'Day-110 final follow-up scheduled' },
      { when: '2026-05-01T00:00', kind: 'closed',     note: 'Moved to long-term nurture list — no engagement' },
    ],
    advicePings: [
      { day: 2,   sentAt: '2026-02-08T09:30', opened: true,  clicked: false },
      { day: 10,  sentAt: '2026-02-16T09:30', opened: true,  clicked: false },
      { day: 18,  sentAt: '2026-02-24T09:30', opened: false, clicked: false },
      { day: 35,  sentAt: '2026-03-13T09:30', opened: false, clicked: false },
      { day: 55,  sentAt: '2026-04-02T09:30', opened: false, clicked: false },
      { day: 80,  sentAt: '2026-04-27T09:30', opened: false, clicked: false },
      { day: 110, sentAt: null, opened: null, clicked: null },
    ],
  },
];

// Stages used in ENQUIRIES — for visual badges + filtering in the inbox.
const ENQUIRY_STAGES = [
  { id: 'received',     label: 'New · awaiting reply',  tone: 'warn'  },
  { id: 'replied',      label: 'Replied · awaiting customer', tone: 'ok' },
  { id: 'advice-sent',  label: 'Advice sent',          tone: 'ok'    },
  { id: 'advice-day2',  label: 'Advice · day 2',        tone: ''      },
  { id: 'advice-day10', label: 'Advice · day 10',       tone: ''      },
  { id: 'advice-day18', label: 'Advice · day 18',       tone: ''      },
  { id: 'advice-day35', label: 'Advice · day 35',       tone: ''      },
  { id: 'advice-day55', label: 'Advice · day 55',       tone: ''      },
  { id: 'advice-day80', label: 'Advice · day 80',       tone: ''      },
  { id: 'advice-day110',label: 'Advice · day 110',      tone: ''      },
  { id: 'converted',    label: 'Converted to booking',  tone: 'ok'    },
  { id: 'closed',       label: 'Closed · long-term nurture', tone: 'ghost' },
];

// Notification feed (§11) — recent events for the bell icon
const NOTIFICATIONS = [
  { id: 'n1', when: '4 min ago',  kind: 'message', title: 'Sophie Reynolds replied',         body: 'Question about home routine',                        link: { customerId: 'sophie' } },
  { id: 'n2', when: '17 min ago', kind: 'booking', title: 'New online booking',              body: 'Tom Pendry · HIFU · Wed 14:30',           link: { pendingId: 'pb1' } },
  { id: 'n3', when: '1 hr ago',   kind: 'form',    title: 'Form signed',                     body: 'Priya Devi completed medical history',               link: { customerId: 'priya' } },
  { id: 'n4', when: '2 hr ago',   kind: 'payment', title: 'Payment received',                body: 'Hannah Whitlock · CO2 Laser · £650',                 link: null },
  { id: 'n5', when: 'Yesterday',  kind: 'cancel',  title: 'Booking cancelled',               body: 'Lucy Bennett cancelled tomorrow\'s Cryolipolysis',   link: { customerId: 'lucy' } },
  { id: 'n6', when: 'Yesterday',  kind: 'review',  title: '5★ review on Google',             body: 'Hannah Whitlock — "Emma is a magician"',           link: null },
  { id: 'n7', when: '2 days ago', kind: 'noshow',  title: 'Marked as no-show',               body: 'Mark Daley · HIFU 12:00',                            link: { customerId: 'mark' } },
];

// Tasks — daily running list (§14)
// owner can be 'clinic' (anyone) or a clinicianId (assigned)
const TASKS = [
  { id: 't1', title: 'Call Hannah W to schedule patch test',         owner: 'grace', due: todayISO,         customerId: 'hannah', kind: 'call',   priority: 'high',   done: false },
  { id: 't2', title: 'Order more Britenol boosters from supplier',   owner: 'emma',  due: todayISO,         customerId: null,     kind: 'admin',  priority: 'normal', done: false },
  { id: 't3', title: 'Send Sophie R 2-week post-CO2 photos request', owner: 'grace', due: todayISO,         customerId: 'sophie', kind: 'msg',    priority: 'normal', done: true },
  { id: 't4', title: 'Review medical form for Tom Pendry',           owner: 'daniel',   due: todayISO,         customerId: 'tom',    kind: 'review', priority: 'high',   done: false },
  { id: 't5', title: 'Confirm Stripe payout reconciliation',         owner: 'emma',  due: offsetDay(1),     customerId: null,     kind: 'finance', priority: 'normal', done: false },
  { id: 't6', title: 'Write Q2 Google Reviews response batch',       owner: 'clinic',  due: offsetDay(2),     customerId: null,     kind: 'admin',  priority: 'low',    done: false },
  { id: 't7', title: 'Patch test review — Sophie R (tretinoin)',     owner: 'grace', due: offsetDay(1),     customerId: 'sophie', kind: 'clinical', priority: 'high', done: false },
  { id: 't8', title: 'Re-engagement email batch (12 inactive)',      owner: 'sofia',   due: offsetDay(3),     customerId: null,     kind: 'msg',    priority: 'normal', done: false },
];


// ── shiftDay — move an ISO date by N days (noon-anchored to dodge DST) ──
function shiftDay(iso, delta) {
  const d = new Date(iso + 'T12:00:00');
  d.setDate(d.getDate() + delta);
  return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
}

// ============================================================
// Year-round demo data. Deterministic per date (hash-seeded PRNG),
// so revisiting a day always shows the same diary. Generates:
//   • ~120 extra clients so a year of bookings looks believable
//   • appointments for every working day, -6 months to +6 months,
//     following each team member's weekly shift pattern
// Hand-written rows for the demo's "today" window are kept and
// topped up around (never overlapped).
// ============================================================
(function generateYearRound() {
  const dateSeed = (str) => { let h = 7; for (let i = 0; i < str.length; i++) h = ((h * 31) + str.charCodeAt(i)) >>> 0; return h || 1; };
  const prng = (seed) => { let x = seed; return () => { x = (x * 1664525 + 1013904223) >>> 0; return x / 4294967296; }; };

  // ── extra clients ──
  const FIRSTS = ['Amber','Beth','Chloe','Daisy','Erin','Freya','Georgia','Holly','Isla','Katie','Leah','Megan','Nina','Olivia','Poppy','Rosie','Saskia','Tara','Verity','Willow','Adam','Ben','Callum','Dan','Ewan','Finn','Greg','Harvey','Ivan','Jack'];
  const LASTS  = ['Ashworth','Barker','Clarke','Dixon','Ellison','Foster','Grayson','Hale','Ingram','Jarvis','Kemp','Larkin','Nayler','Osborne','Pryce','Quinn','Rowe','Turnbull','Underhill','Vickers','Wakefield','Yardley','Aldous','Bretton','Caulfield','Draper','Eastwood','Fairbank','Goodall','Hepworth','Kirkwood','Lockyer','Mabbutt','Norcross','Ollerton'];
  const CONCERNS = [['Lines'], ['Pigmentation'], ['Acne scarring'], ['Skin laxity'], ['Body contouring'], ['Maintenance'], ['Texture'], ['Unwanted hair']];
  const r0 = prng(dateSeed('clientloop-clients'));
  for (let i = 0; i < 620; i++) {
    const first = FIRSTS[Math.floor(r0() * FIRSTS.length)];
    const last  = LASTS[Math.floor(r0() * LASTS.length)];
    const name = first + ' ' + last;
    if (CUSTOMERS.some(c => c.name === name)) continue; // skip dupes; pool shrinks slightly, fine
    const yy = 1968 + Math.floor(r0() * 38);
    CUSTOMERS.push({
      id: 'gc' + i, name,
      dob: yy + '-' + String(1 + Math.floor(r0() * 12)).padStart(2, '0') + '-' + String(1 + Math.floor(r0() * 28)).padStart(2, '0'),
      joined: (2021 + Math.floor(r0() * 5)) + '-' + String(1 + Math.floor(r0() * 12)).padStart(2, '0'),
      phone: '07' + String(100 + Math.floor(r0() * 900)) + ' ' + String(100 + Math.floor(r0() * 900)) + ' ' + String(100 + Math.floor(r0() * 900)),
      email: (first + '.' + last).toLowerCase() + '@email.com',
      initials: first[0] + last[0],
      concerns: CONCERNS[Math.floor(r0() * CONCERNS.length)],
      allergies: r0() < 0.12 ? 'Penicillin' : 'None', meds: 'None',
      flag: r0() < 0.07 ? 'inactive' : r0() < 0.12 ? 'highCheckIn' : null,
      deposit: 0, account: 0, alert: '', marketingConsent: r0() < 0.7,
    });
  }

  // ── appointments ──
  const treatPool = TREATMENTS.filter(t => t.id !== 'micro' && t.id !== 'fire');
  // existing hand-written bookings per date|clinician as busy intervals (hours)
  const busy = {};
  APPOINTMENTS.forEach(a => {
    const k = a.date + '|' + a.clinicianId;
    (busy[k] = busy[k] || []).push([parseInt(a.time.slice(0, 2), 10) + parseInt(a.time.slice(3, 5), 10) / 60, a.mins / 60]);
  });
  for (let off = -182; off <= 200; off++) {
    const date = shiftDay(todayISO, off);
    const dow = (new Date(date + 'T12:00:00').getDay() + 6) % 7; // Mon=0
    const rand = prng(dateSeed(date));
    let n = 0;
    CLINICIANS.forEach(c => {
      const shift = STAFF_SHIFTS[c.id] && STAFF_SHIFTS[c.id][dow];
      if (!shift) return;
      const startH = parseInt(shift.slice(0, 2), 10);
      const endH = parseInt(shift.slice(6, 8), 10);
      const key = date + '|' + c.id;
      let t = startH + (rand() < 0.5 ? 0 : 0.5);
      while (t < endH - 0.75) {
        if (rand() < 0.62) {
          const treat = treatPool[Math.floor(rand() * treatPool.length)];
          const durH = treat.mins / 60;
          const clash = (busy[key] || []).some(([bs, bd]) => t < bs + bd && bs < t + durH);
          if (!clash) {
            const cust = CUSTOMERS[Math.floor(rand() * CUSTOMERS.length)];
            const past = date < todayISO;
            const status = past
              ? (rand() < 0.04 ? 'cancelled' : 'completed')
              : (date === todayISO ? 'confirmed' : rand() < 0.1 ? 'pending' : 'confirmed');
            const rooms = ROOMS.filter(rm => rm.allowedTreatments && rm.allowedTreatments.includes(treat.id));
            APPOINTMENTS.push({
              id: 'g-' + date + '-' + (n++),
              date, time: String(Math.floor(t)).padStart(2, '0') + ':' + (t % 1 ? '30' : '00'),
              mins: treat.mins, customerId: cust.id, clinicianId: c.id, treatmentId: treat.id,
              status, room: rooms.length ? rooms[Math.floor(rand() * rooms.length)].name : undefined,
            });
            t += Math.ceil(durH * 2) / 2 + 0.5; // treatment + turnaround
          } else {
            t += 0.5;
          }
        } else {
          t += 0.5; // gap
        }
      }
    });
  }
})();

// Last completed visit per customer — computed once so the 400+ row client
// list doesn't re-scan the full year of appointments per row.
const LAST_COMPLETED_VISIT = {};
APPOINTMENTS.forEach(a => {
  if (a.date <= todayISO && a.status === 'completed') {
    const cur = LAST_COMPLETED_VISIT[a.customerId];
    if (!cur || a.date > cur.date) LAST_COMPLETED_VISIT[a.customerId] = a;
  }
});

function customerById(id) { return CUSTOMERS.find(c => c.id === id); }
function clinicianById(id) { return CLINICIANS.find(c => c.id === id); }
function treatmentById(id) { return TREATMENTS.find(t => t.id === id); }
function fmtDate(iso) {
  const d = new Date(iso);
  return d.toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' });
}
function fmtMoney(n) {
  if (n < 0) return '-£' + Math.abs(n).toLocaleString('en-GB');
  return '£' + n.toLocaleString('en-GB');
}

// Customer flag presentation
function flagInfo(flag) {
  if (flag === 'highCheckIn') return { label: 'High check-in',   tone: 'ok',    desc: 'Reliable customer' };
  if (flag === 'highCancel')  return { label: 'High cancellation', tone: 'warn', desc: '3+ cancellations in last 5' };
  if (flag === 'inactive')    return { label: 'Inactive',         tone: 'ghost', desc: 'No contact in 12+ months' };
  if (flag === 'followUp')    return { label: 'Follow-up',        tone: 'blush', desc: 'Action owed' };
  return null;
}

// ============================================================
// Finance + Marketing data (visual wireframe — Emma's KPI tracker)
// ============================================================
// 4 separate businesses Emma runs. Treated as fully independent —
// they share nothing with the Demo Clinic app (no shared customers/staff/rooms).
// The Finance tab rolls them up for dashboard purposes only.
const BUSINESSES = [
  { id: 'main',       name: 'Demo Clinic', short: 'Demo Clinic',  terminal: 'Terminal 1', vatRegistered: true,  tint: '#8FA88A' },
  { id: 'shop',   name: 'Shop & product sales',               short: 'Shop', terminal: 'Terminal 2', vatRegistered: false, tint: '#6E7D6B' },
];

// Helper to build a week-start ISO going back N weeks from today.
const weekStartISO = (weeksAgo) => {
  const d = new Date();
  d.setDate(d.getDate() - (weeksAgo * 7) - ((d.getDay() + 6) % 7));
  return d.toISOString().slice(0, 10);
};

// 12 weeks of mock weekly figures. Roughly realistic for a £350-450k/yr clinic.
// Per business: terminal (card), cash, transfer (bank), bills, wages, other.
const WEEKLY_FINANCE = (() => {
  const seed = [
    // [main-term, main-cash, main-tx, main-bills, main-wages, main-other, studio-term, studio-cash, studio-tx, studio-bills, studio-wages, studio-other, shop-term, shop-cash, shop-tx, shop-bills, shop-wages, shop-other, academy-term, academy-cash, academy-tx, academy-bills, academy-wages, academy-other]
    [4820,320,1200,1850,2100,180, 1450,80,0,220,540,0,    0,0,480,60,0,0,    0,0,150,0,0,40],
    [5210,180,950, 980, 2100,210, 1820,120,300,200,540,0, 0,0,520,60,0,0,    0,0,180,0,0,40],
    [4680,420,1450,2240,2100,90,  1340,60,0,200,540,0,    0,0,460,60,0,0,    0,0,140,0,0,40],
    [5840,260,1100,640, 2300,340, 1920,140,200,200,580,0, 0,0,510,60,0,0,    0,0,210,0,0,40],
    [5120,310,1280,1820,2100,180, 1610,110,0,200,540,0,   0,0,490,60,0,0,    0,0,160,0,0,40],
    [4480,180,820, 540, 2100,90,  1240,90,150,200,540,0,  0,0,440,60,0,0,    0,0,130,0,0,40],
    [5980,420,1640,2050,2200,260, 2080,170,400,220,580,0, 0,0,520,60,0,0,    0,0,210,0,0,40],
    [5410,290,1100,820, 2100,180, 1780,130,0,200,540,0,   0,0,480,60,0,0,    0,0,170,0,0,40],
    [4920,260,950, 1450,2100,140, 1520,100,250,200,540,0, 0,0,460,60,0,0,    0,0,150,0,0,40],
    [6210,380,1480,540, 2300,210, 1980,160,0,200,580,0,   0,0,520,60,0,0,    0,0,200,0,0,40],
    [5620,310,1240,2120,2100,180, 1720,120,180,200,540,0, 0,0,490,60,0,0,    0,0,180,0,0,40],
    [4280,140,720, 320, 2100,110, 1180,60,0,200,540,0,    0,0,420,60,0,0,    0,0,110,0,0,40],
  ];
  return seed.map((row, idx) => ({
    weekStart: weekStartISO(11 - idx),
    locked: idx < 11, // current week unlocked for editing
    perBusiness: {
      main:       { terminal: row[0],  cash: row[1],  transfer: row[2],  bills: row[3],  wages: row[4],  other: row[5] },
      shop:       { terminal: row[12], cash: row[13], transfer: row[14], bills: row[15], wages: row[16], other: row[17] },
    },
  }));
})();

// Per-staff pay rates. autoCalc=true means wages can be derived from rota hours
// × hourlyRate; autoCalc=false means Emma enters wages manually. Default off
// for everyone — Emma switches it on per-staff if she wants the calculation.
const PAY_RATES = [
  { staffId: 'emma',  name: 'Emma Carter',    role: 'Director',       type: 'salary', monthlyGross: 0,     hourlyRate: 0,    commissionPct: 0,  autoCalc: false },
  { staffId: 'grace', name: 'Grace Hollis',  role: 'Aesthetic Nurse', type: 'hourly', monthlyGross: 0,     hourlyRate: 28,   commissionPct: 10, autoCalc: false },
  { staffId: 'daniel',   name: 'Dr Daniel Mensah',     role: 'Aesthetic Doctor', type: 'session', monthlyGross: 0,   hourlyRate: 65,   commissionPct: 0,  autoCalc: false },
  { staffId: 'sofia',   name: 'Sofia Marsh',     role: 'Skin Therapist',  type: 'hourly', monthlyGross: 0,     hourlyRate: 18,   commissionPct: 5,  autoCalc: false },
  { staffId: 'reception', name: 'Reception (TBA)', role: 'Reception',       type: 'hourly', monthlyGross: 0,     hourlyRate: 13.5, commissionPct: 0,  autoCalc: false },
];

// Recurring monthly business expenses Emma wants tracked separately from
// weekly "bills paid" line. Drives the projection model.
const FIXED_COSTS = [
  { id: 'rent',      label: 'Rent',                       monthly: 2850, businessId: 'main',     category: 'Premises'  },
  { id: 'utilities', label: 'Utilities (gas/elec/water)', monthly: 480,  businessId: 'main',     category: 'Premises'  },
  { id: 'insurance', label: 'Insurance (premises + indemnity)', monthly: 220, businessId: 'main', category: 'Insurance' },
  { id: 'accountant',label: 'Accountant',                 monthly: 320,  businessId: 'main',     category: 'Professional' },
  { id: 'broadband', label: 'Broadband + phones',         monthly: 95,   businessId: 'main',     category: 'Comms'     },
  { id: 'cleaning',  label: 'Cleaning',                   monthly: 280,  businessId: 'main',     category: 'Premises'  },
  { id: 'software',  label: 'Software subscriptions',     monthly: 240,  businessId: 'main',     category: 'Software'  },
];

// Pricing calculator — materials catalogue (course template Emma has).
// Optional auto-pull: per-treatment material list + labour mins → suggested price.
const MATERIALS = [
  { id: 'mat-saline', name: 'Saline solution',         unit: 'ml',     costPerUnit: 0.012, supplier: 'Pharma UK' },
  { id: 'mat-numb',   name: 'Numbing cream',           unit: 'g',      costPerUnit: 0.18,  supplier: 'EMLA Direct' },
  { id: 'mat-needle', name: 'Microneedling cartridge', unit: 'unit',   costPerUnit: 4.50,  supplier: 'Dr Pen UK' },
  { id: 'mat-serum',  name: 'Mesotherapy serum',       unit: 'ml',     costPerUnit: 2.40,  supplier: 'Mesoestetic' },
  { id: 'mat-gel',    name: 'Ultrasound gel',          unit: 'ml',     costPerUnit: 0.008, supplier: 'Aquasonic' },
  { id: 'mat-mask',   name: 'Recovery sheet mask',     unit: 'unit',   costPerUnit: 3.20,  supplier: 'Esthetic Pro' },
  { id: 'mat-toxin',  name: 'Botulinum toxin (50u)',   unit: 'unit',   costPerUnit: 78.00, supplier: 'Allergan' },
  { id: 'mat-hyalu',  name: 'Profhilo (2ml)',          unit: 'unit',   costPerUnit: 165.00,supplier: 'IBSA' },
  { id: 'mat-towel',  name: 'Disposable towel',        unit: 'unit',   costPerUnit: 0.42,  supplier: 'Salonserve' },
  { id: 'mat-co2',    name: 'CO₂ tip (single-use)',    unit: 'unit',   costPerUnit: 38.00, supplier: 'DEKA' },
];

// Per-treatment cost build-ups for the pricing calculator. Same indirection
// pattern as PRE_POST_CARES — some treatments reuse another's build-up.
const TREATMENT_COSTS = {
  co2:       { materials: [{ id: 'mat-numb', qty: 3 }, { id: 'mat-saline', qty: 20 }, { id: 'mat-co2', qty: 1 }, { id: 'mat-mask', qty: 1 }, { id: 'mat-towel', qty: 4 }], labourMins: 90,  overheadPct: 0.22 },
  'co2-scar':{ materials: [{ id: 'mat-numb', qty: 4 }, { id: 'mat-saline', qty: 25 }, { id: 'mat-co2', qty: 1 }, { id: 'mat-mask', qty: 1 }, { id: 'mat-towel', qty: 4 }], labourMins: 90,  overheadPct: 0.22 },
  hydra:     { materials: [{ id: 'mat-serum', qty: 8 }, { id: 'mat-towel', qty: 3 }, { id: 'mat-saline', qty: 12 }], labourMins: 45, overheadPct: 0.20 },
  rfmicro:   { materials: [{ id: 'mat-needle', qty: 1 }, { id: 'mat-numb', qty: 2 }, { id: 'mat-serum', qty: 3 }, { id: 'mat-towel', qty: 3 }], labourMins: 60, overheadPct: 0.20 },
  hifu:      { materials: [{ id: 'mat-gel', qty: 25 }, { id: 'mat-towel', qty: 2 }], labourMins: 75, overheadPct: 0.18 },
  awi:       { materials: [{ id: 'mat-toxin', qty: 1 }, { id: 'mat-towel', qty: 1 }], labourMins: 30, overheadPct: 0.15 },
  profhilo:  { materials: [{ id: 'mat-hyalu', qty: 1 }, { id: 'mat-towel', qty: 1 }], labourMins: 30, overheadPct: 0.15 },
  lhr:       { materials: [{ id: 'mat-gel', qty: 15 }, { id: 'mat-towel', qty: 2 }], labourMins: 30, overheadPct: 0.18 },
};

// Finance & privacy settings. Owner-controlled. Values match Emma's KPI sheet.
const FINANCE_SETTINGS = {
  vatThreshold: 90000,
  vatRate: 0.20,
  vatRegisteredBusinessIds: ['main'],
  clinicHoursPerWeek: 41.25,
  pin: '4729',          // mock PIN — Emma would set her own
  pinRequired: true,    // master switch
  blurOnBlur: true,     // figures blur when window loses focus
  autoCalcWages: false, // build the option but default off (per Emma)
  autoPullCardTotals: true,  // Demo Clinic terminal pulls from cash-up flow
  defaultLabourCostPerMin: 0.75, // for pricing calculator
  targetMarginPct: 0.65, // for pricing calculator suggested-price
};

// Helpers ----------------------------------------------------------------
function businessById(id) { return BUSINESSES.find(b => b.id === id); }
function weekTotal(week, key) {
  return Object.values(week.perBusiness).reduce((sum, b) => sum + (b[key] || 0), 0);
}
// Defensive: filter undefined buckets in case callers pass a stray
// businessId (e.g. `weeks.map(weekRevenue)` would land the array index in
// businessId and look up `week.perBusiness[1]` which is undefined).
function weekRevenue(week, businessId = null) {
  const raw = businessId ? [week.perBusiness[businessId]] : Object.values(week.perBusiness);
  const buckets = raw.filter(Boolean);
  return buckets.reduce((sum, b) => sum + (b.terminal || 0) + (b.cash || 0) + (b.transfer || 0), 0);
}
function weekExpenses(week, businessId = null) {
  const raw = businessId ? [week.perBusiness[businessId]] : Object.values(week.perBusiness);
  const buckets = raw.filter(Boolean);
  return buckets.reduce((sum, b) => sum + (b.bills || 0) + (b.wages || 0) + (b.other || 0), 0);
}


// ============================================================
// Patch-test sequencing (demo) — treatments that need a patch test
// before a first session, and which clients already have one on file.
// The finished system enforces this ordering automatically.
// ============================================================
const PATCH_TEST_TREATMENTS = ['lhr', 'ltr', 'ler', 'ss', 'co2', 'co2-scar', 'electrolysis'];
const PATCH_TESTED_CUSTOMERS = ['hannah']; // mid-course, patch test passed

// ============================================================
// SCHEDULED_MESSAGES — the automation outbox (demo).
// What the workflows have queued, sent, or deliberately skipped.
// ============================================================
const SCHEDULED_MESSAGES = [
  // queued — sending next
  { id: 'q1', customerId: 'sophie', templateId: 'aftercare-1h',    channel: 'email', sendAt: todayISO + 'T11:00', status: 'queued',  context: 'HydraFacial · 1h after she leaves' },
  { id: 'q2', customerId: 'jess',   templateId: 'aftercare-1h',    channel: 'email', sendAt: todayISO + 'T15:45', status: 'queued',  context: 'Facial · 1h after she leaves' },
  { id: 'q3', customerId: 'lucy',   templateId: 'pretreat-72h',    channel: 'email', sendAt: todayISO + 'T16:00', status: 'queued',  context: 'Fat freezing ' + fmtDate(offsetDay(3)) + ' · pre-care, 3 days before' },
  { id: 'q4', customerId: 'tom',    templateId: 'form-reminder-24h', channel: 'email', sendAt: offsetDay(1) + 'T09:00', status: 'queued', context: 'Medical form still unsigned · 24h chase' },
  { id: 'q5', customerId: 'hannah', templateId: 'review-nudge-48h', channel: 'email', sendAt: offsetDay(2) + 'T10:00', status: 'queued', context: 'Course finished · review ask' },
  // sent earlier today
  { id: 's1', customerId: 'hannah', templateId: 'pretreat-72h',    channel: 'email', sendAt: todayISO + 'T08:00', status: 'sent', opened: true,  openedAt: todayISO + 'T08:34', context: 'CO2 resurfacing tomorrow' },
  { id: 's2', customerId: 'tom',    templateId: 'confirmation',    channel: 'email', sendAt: todayISO + 'T09:12', status: 'sent', opened: true,  openedAt: todayISO + 'T09:31', context: 'Online booking received' },
  { id: 's3', customerId: 'mark',   templateId: 'followup-fatfreeze', channel: 'email', sendAt: todayISO + 'T09:30', status: 'sent', opened: false, context: '2.5-month results check-in' },
  // skipped by the rules — the system checked first
  { id: 'k1', customerId: 'jess',   templateId: 'rebook',          channel: 'email', sendAt: todayISO + 'T09:00', status: 'skipped', reason: 'Already rebooked — next visit in the diary' },
  { id: 'k2', customerId: 'priya',  templateId: 'advice-day10',    channel: 'email', sendAt: todayISO + 'T09:00', status: 'skipped', reason: 'Client replied yesterday — sequence stopped' },
  { id: 'k3', customerId: 'lucy',   templateId: 'patch-checkin-unbooked', channel: 'email', sendAt: todayISO + 'T08:00', status: 'skipped', reason: 'Booked within 24h — sent the booked version instead' },
];

Object.assign(window, {
  Icon, CLINICIANS, TREATMENTS, CUSTOMERS, APPOINTMENTS, NOTES, PHOTOS,
  PENDING_BOOKINGS, WAITLIST, STAFF_SHIFTS, LEAVE, MEDICAL_FORMS,
  COMM_TEMPLATES, COMM_LOG, TASKS, WORKFLOWS, NOTIFICATIONS, ROOMS,
  BUSINESSES, WEEKLY_FINANCE, PAY_RATES, FIXED_COSTS, MATERIALS,
  TREATMENT_COSTS, FINANCE_SETTINGS,
  customerById, clinicianById, treatmentById, businessById,
  weekTotal, weekRevenue, weekExpenses,
  fmtDate, fmtMoney, flagInfo, todayISO, offsetDay, shiftDay,
  PATCH_TEST_TREATMENTS, PATCH_TESTED_CUSTOMERS, SCHEDULED_MESSAGES, LAST_COMPLETED_VISIT,
});
