/* Shared data + hooks for the Fire Station Pizzeria app. */

const LS = {
  get(key, fallback) {
    try { const v = localStorage.getItem(key); return v == null ? fallback : JSON.parse(v); }
    catch { return fallback; }
  },
  set(key, val) { try { localStorage.setItem(key, JSON.stringify(val)); } catch {} }
};

const DEFAULT_LINEUP = {
  "Meat": ["Nduja Balls", "Pepperoni", "Anchovies", "Guanciale", "Ham", "Venison"],
  "Vegetables": ["Mushrooms", "Courgettes", "Peppers", "Caramelised Onions", "Red Onions", "Squash Base", "Basil", "Olives", "Capers", "Potato", "Garlic", "Oregano"],
  "Dairy / Cheese": ["Truffle", "Bufala", "Parm Cheese", "Ricotta", "Cheese", "Stracciatella"],
  "Sauces & Condiments": ["Tomato Sauce", "Chilli Honey", "Garlic Butter", "Bread", "Olive Oil"]
};

const DEFAULT_CHECKLIST = [
  "Sweep Floor",
  "Pull Out Fridge and Sweep Behind",
  "Clean Mirror",
  "Clean Screen",
  "Clean Outside Pass",
  "Clear Both Work Stations",
  "Check Labels",
  "Check Expiry Date",
  "Move Ingredients into White Tubs",
  "Clean Inside Small Fridge",
  "Put Boxes Inside Organised",
  "Wash Outside Small Fridge",
  "Wash Outside Big Fridge",
  "Clean Sink",
  "Clean Front of Oven",
  "Put Cloths Front of Oven",
  "Sweep Floor Again",
  "Put Dough in Fridge or on Work Station",
  "Turn AC On",
  "Clean Till Screen",
  "Clean Till and Around",
  "Turn Off Both Oven Fans",
  "Mop the Floor",
  "Take Bins Out"
];

// Sunday-only extras (shown on the closing checklist when it's Sunday)
const DEFAULT_SUNDAY_CHECKLIST = [
  "Ensure all upstands are cleaned correctly",
  "Ensure areas around feet are cleaned",
  "Ensure mixer is clean",
  "Wipe heat detectors",
  "Clean oven",
  "Clean fans",
  "Clean peel rack",
  "Make sure outside bins are left correctly"
];

// Opening checklist (from uploaded spreadsheet)
const DEFAULT_OPENING_CHECKLIST = [
  "Turn On Lights",
  "Fill in Temp Book",
  "Take Delivery Inside",
  "Submit Invoices",
  "Turn On Dishwasher",
  "Start Mix for Following Day",
  "Ball Up Mix For Service",
  "Check Prep List and Start",
  "Turn On Oven 2 hours Before Service",
  "Open Line for Service",
  "Turn On Order Screen, Till and Deliveroo",
  "Stock Up On Pizza Boxes"
];

// ---- CARNEVALE supplier list ----
// Has product codes (must NOT be changed).
// mustHave is stored as free text (e.g. "3 case", "2") to preserve exact ordering units.
const DEFAULT_CARNEVALE = [
  { name: "Peperoni",                    code: "SLO11", inStock: "1",      mustHave: "" },
  { name: "Mozzarella",                  code: "CH021", inStock: "0",      mustHave: "3 case" },
  { name: "San Marzano Tomatoes",        code: "PT009", inStock: "1 case", mustHave: "2 case" },
  { name: "Guanciale Levoni",            code: "LV365", inStock: "1",      mustHave: "5" },
  { name: "Ricotta",                     code: "CH040", inStock: "1",      mustHave: "2" },
  { name: "Lievito Fresco (Yeast)",      code: "BP033", inStock: "1",      mustHave: "1 pack" },
  { name: "Salsa Tartufata (Truffle Paste)", code: "CV506", inStock: "1",  mustHave: "2" },
  { name: "Parmasan",                    code: "CH260", inStock: "2",      mustHave: "3" },
  { name: "Prosciutto",                  code: "NE371", inStock: "2 packs", mustHave: "5" },
  { name: "Caputo Rosso",                code: "BP523", inStock: "3",      mustHave: "" },
  { name: "Stracciatella Maldera",       code: "CH215", inStock: "4",      mustHave: "4" },
  { name: "Extra Virgin Oil",            code: "OI035", inStock: "0",      mustHave: "1" },
  { name: "Burrata",                     code: "CH525", inStock: "8",      mustHave: "15" },
  { name: "Nduja",                       code: "SC708", inStock: "0",      mustHave: "2" },
  { name: "Anchoives",                   code: "FH005", inStock: "1",      mustHave: "5" },
  { name: "Spaghetti (12 x 1)",          code: "DC512", inStock: "0",      mustHave: "2" },
  { name: "Olive Tagghiosche",           code: "CV488", inStock: "1",      mustHave: "2" },
  { name: "Capers",                      code: "OL093", inStock: "1",      mustHave: "1" },
  { name: "Semolina",                    code: "",      inStock: "0",      mustHave: "1" },
  { name: "Speck",                       code: "",      inStock: "1",      mustHave: "" },
  { name: "Salami Milano",               code: "",      inStock: "1",      mustHave: "" }
].map((it, i) => ({ id: `carn_${i}`, ...it }));

// ---- VEG FACTOR supplier list ----
const DEFAULT_VEGFACTOR = [
  { name: "Basil",                 inStock: "4",   mustHave: "5" },
  { name: "Peppers",               inStock: "4kg", mustHave: "5kg" },
  { name: "Red Onion",             inStock: "3kg", mustHave: "3kg" },
  { name: "Pealed Garlic",         inStock: "0",   mustHave: "1" },
  { name: "Chestnut Mushroom",     inStock: "2kg", mustHave: "3kg" },
  { name: "Courgette",             inStock: "6",   mustHave: "6" },
  { name: "Rocket",                inStock: "1",   mustHave: "2" },
  { name: "Romaine Lettuce",       inStock: "1",   mustHave: "3" },
  { name: "Baby Potato",           inStock: "3kg", mustHave: "5kg" },
  { name: "Plum Tomato",           inStock: "1kg", mustHave: "2kg" },
  { name: "Red Cherry Tomato",     inStock: "0",   mustHave: "2kg" },
  { name: "Yellow Cherry Tomato",  inStock: "0",   mustHave: "0" },
  { name: "Onion Chutney",         inStock: "1",   mustHave: "1" },
  { name: "Rosemary",              inStock: "1",   mustHave: "1" },
  { name: "Chives",                inStock: "1",   mustHave: "1" },
  { name: "Parsley",               inStock: "1",   mustHave: "1" },
  { name: "Thymes",                inStock: "1",   mustHave: "1" },
  { name: "Mint",                  inStock: "2",   mustHave: "2" },
  { name: "Blackbomber",           inStock: "0",   mustHave: "3" },
  { name: "Bufala",                inStock: "0",   mustHave: "3kg" },
  { name: "Sourdough Bread",       inStock: "2",   mustHave: "3" },
  { name: "Mascarpone",            inStock: "4",   mustHave: "4x250g" },
  { name: "Lemon",                 inStock: "3",   mustHave: "3" },
  { name: "Lime",                  inStock: "6",   mustHave: "6" },
  { name: "Orange",                inStock: "2",   mustHave: "2" },
  { name: "Raspberry",             inStock: "1",   mustHave: "1" },
  { name: "Edible Flour",          inStock: "1",   mustHave: "1" },
  { name: "Grapefruit",            inStock: "1",   mustHave: "1" },
  { name: "Cucumber",              inStock: "1",   mustHave: "1" }
].map((it, i) => ({ id: `veg_${i}`, ...it }));

// ---- PILGRIM supplier list ----
const DEFAULT_PILGRIM = [];

// ---- COBBLE LANE CURED supplier list ----
const DEFAULT_COBBLE_LANE = [];

// ---- FOH SUPPLIERS ----

const DEFAULT_KATER4 = [
  // Softs
  { name: "Karma Kola",                    inStock: "", mustHave: "" },
  { name: "Karma Diet Kola",               inStock: "", mustHave: "" },
  { name: "Karma Orangeade",               inStock: "", mustHave: "" },
  { name: "Karma Lemonade",                inStock: "", mustHave: "" },
  { name: "Karma Raspberry Lemonade",      inStock: "", mustHave: "" },
  { name: "Karma Ginger Ale",              inStock: "", mustHave: "" },
  { name: "Fevertree Indian Tonic",        inStock: "", mustHave: "" },
  { name: "Fevertree Slimline Tonic",      inStock: "", mustHave: "" },
  { name: "Fevertree Elderflower Tonic",   inStock: "", mustHave: "" },
  { name: "Fevertree Med Tonic",           inStock: "", mustHave: "" },
  { name: "Eager Orange Juice",            inStock: "", mustHave: "" },
  { name: "Eager Cranberry Juice",         inStock: "", mustHave: "" },
  { name: "Eager Apple Juice",             inStock: "", mustHave: "" },
  { name: "Schweppes 2L Lemonade",         inStock: "", mustHave: "" },
  { name: "Acqua Panna 750ml",             inStock: "", mustHave: "" },
  { name: "San Pellegrino 750ml",          inStock: "", mustHave: "" },
  { name: "Elderflower Cordial",           inStock: "", mustHave: "" },
  { name: "Blackcurrant Cordial",          inStock: "", mustHave: "" },
  { name: "Orange Cordial",               inStock: "", mustHave: "" },
  { name: "Lime Cordial",                  inStock: "", mustHave: "" },
  // Spirits
  { name: "Aperol",                        inStock: "", mustHave: "" },
  { name: "Sarti",                         inStock: "", mustHave: "" },
  { name: "Campari",                       inStock: "", mustHave: "" },
  { name: "Limoncello",                    inStock: "", mustHave: "" },
  { name: "Elderflower Liqueur",           inStock: "", mustHave: "" },
  { name: "Jim and Tonic Vodka",           inStock: "", mustHave: "" },
  { name: "Absolut Vodka",                 inStock: "", mustHave: "" },
  { name: "Absolut Vanilla Vodka",         inStock: "", mustHave: "" },
  { name: "Jim and Tonic London Dry Gin",  inStock: "", mustHave: "" },
  { name: "Jim and Tonic Rhubarb Gin",     inStock: "", mustHave: "" },
  { name: "Malfy Rossa",                   inStock: "", mustHave: "" },
  { name: "Malfy Orange",                  inStock: "", mustHave: "" },
  { name: "Malfy Lemon",                   inStock: "", mustHave: "" },
  { name: "Malfy Original",                inStock: "", mustHave: "" },
  { name: "Hendricks",                     inStock: "", mustHave: "" },
  { name: "Tanqueray 10",                  inStock: "", mustHave: "" },
  { name: "Tanqueray 0%",                  inStock: "", mustHave: "" },
  { name: "Jim and Tonic Spiced Rum",      inStock: "", mustHave: "" },
  { name: "Jim and Tonic White Rum",       inStock: "", mustHave: "" },
  { name: "Lazy Dog Spiced",               inStock: "", mustHave: "" },
  { name: "Mount Gay",                     inStock: "", mustHave: "" },
  { name: "Captain Morgans Dark Rum",      inStock: "", mustHave: "" },
  { name: "Buffalo Trace",                 inStock: "", mustHave: "" },
  { name: "Monkey Shoulder",               inStock: "", mustHave: "" },
  { name: "Glenfiddich 12yr",              inStock: "", mustHave: "" },
  { name: "Jose Tequila",                  inStock: "", mustHave: "" },
  // Beers / Bottles / Ciders
  { name: "Moretti Keg",                   inStock: "", mustHave: "" },
  { name: "Brixton Keg",                   inStock: "", mustHave: "" },
  { name: "Peroni Bottles",                inStock: "", mustHave: "" },
  { name: "Peroni 0% Bottles",             inStock: "", mustHave: "" },
  { name: "Peroni Gluten Free Bottles",    inStock: "", mustHave: "" },
  { name: "Guinness 0% Cans",              inStock: "", mustHave: "" },
  { name: "Aspalls Bottles",               inStock: "", mustHave: "" },
  { name: "Charnwood Salvation Bottles",   inStock: "", mustHave: "" },
  { name: "Old Mout Berries & Cherries",   inStock: "", mustHave: "" },
  { name: "Birra Ichnusa",                 inStock: "", mustHave: "" },
  // Other
  { name: "Tall Candles",                  inStock: "", mustHave: "" },
  { name: "Tealights",                     inStock: "", mustHave: "" },
  { name: "Black Paper Straws",            inStock: "", mustHave: "" },
  { name: "Airlaid Napkins Case",          inStock: "", mustHave: "" },
  { name: "Black Bev Napkins",             inStock: "", mustHave: "" },
  { name: "Large Toilet Rolls",            inStock: "", mustHave: "" },
  { name: "Check Pads (x10)",              inStock: "", mustHave: "" },
  { name: "Brasso Bottle",                 inStock: "", mustHave: "" },
  { name: "Gas 30/70",                     inStock: "", mustHave: "" },
  { name: "Gas 60/40",                     inStock: "", mustHave: "" },
  { name: "Grenadine",                     inStock: "", mustHave: "" },
  { name: "Sugar Syrup",                   inStock: "", mustHave: "" },
  { name: "Lime ODK",                      inStock: "", mustHave: "" },
  { name: "Lemon ODK",                     inStock: "", mustHave: "" },
  { name: "Till Rolls",                    inStock: "", mustHave: "" },
  { name: "PDQ Till Rolls",                inStock: "", mustHave: "" }
].map((it, i) => ({ id: `k4_${i}`, ...it }));

const DEFAULT_KILOWINES = [
  { name: "Villa Braida Prosecco",          inStock: "", mustHave: "" },
  { name: "Villa Braida Prosecco Rose",     inStock: "", mustHave: "" },
  { name: "Berico Pinot Grigio",            inStock: "", mustHave: "" },
  { name: "Este Vinho Verde",               inStock: "", mustHave: "" },
  { name: "Pretty Paddock NZ Sauv",         inStock: "", mustHave: "" },
  { name: "Beauvignac Picpoul de Pinet",    inStock: "", mustHave: "" },
  { name: "Magda Pedrini Gavi Di Gavi",     inStock: "", mustHave: "" },
  { name: "Berico Pinot Rose",              inStock: "", mustHave: "" },
  { name: "Casa Santiago Merlot",           inStock: "", mustHave: "" },
  { name: "Cima Blanca Malbec",             inStock: "", mustHave: "" },
  { name: "I Prandi Pinot Noir",            inStock: "", mustHave: "" },
  { name: "Origini Primitivo",              inStock: "", mustHave: "" },
  { name: "Principianco Barolo",            inStock: "", mustHave: "" }
].map((it, i) => ({ id: `kw_${i}`, ...it }));

const DEFAULT_MAJESTIC = [
  { name: "Chapel Down",                    inStock: "", mustHave: "" },
  { name: "Chapel Down Rose",               inStock: "", mustHave: "" },
  { name: "Veuve Clicquot",                 inStock: "", mustHave: "" },
  { name: "Whispering Angel Rose",          inStock: "", mustHave: "" },
  { name: "Whispering Angel Rose Magnum",   inStock: "", mustHave: "" }
].map((it, i) => ({ id: `mj_${i}`, ...it }));

const DEFAULT_ROTHLEY = [
  { name: "King Richard",                   inStock: "", mustHave: "" },
  { name: "High Hopes Sparkling",           inStock: "", mustHave: "" }
].map((it, i) => ({ id: `rw_${i}`, ...it }));

const DEFAULT_ROUNDCORNER = [
  { name: "Frisby Cans",                    inStock: "", mustHave: "" },
  { name: "Steeplechase Cans",              inStock: "", mustHave: "" },
  { name: "Drovers Cans",                   inStock: "", mustHave: "" },
  { name: "10 Hours in LA Cans",            inStock: "", mustHave: "" },
  { name: "Special Edition",                inStock: "", mustHave: "" },
  { name: "Reverend Hooker Keg",            inStock: "", mustHave: "" }
].map((it, i) => ({ id: `rc_${i}`, ...it }));

const DEFAULT_AMAZON = [
  { name: "Bayliss & Harding Soap Refills", inStock: "", mustHave: "" },
  { name: "Coffee Machine Water Filters",   inStock: "", mustHave: "" },
  { name: "CO2 Cartridges",                 inStock: "", mustHave: "" },
  { name: "Small Cocktail Paper Straws",    inStock: "", mustHave: "" },
  { name: "Mesh Pizza Screens",             inStock: "", mustHave: "" },
  { name: "Karma Diet Coke",                inStock: "", mustHave: "" },
  { name: "Karma Coke",                     inStock: "", mustHave: "" },
  { name: "Karma Raspberry Lemonade",       inStock: "", mustHave: "" },
  { name: "Karma Lemonade",                 inStock: "", mustHave: "" },
  { name: "Karma Orangeade",                inStock: "", mustHave: "" },
  { name: "Karma Ginger Ale",               inStock: "", mustHave: "" },
  { name: "Kraft Salad 750ml Boxes",         inStock: "", mustHave: "" },
  { name: "Dip Pots",                       inStock: "", mustHave: "" }
].map((it, i) => ({ id: `az_${i}`, ...it }));

// ---- FOH (Front of House) Checklists ----

const DEFAULT_FOH_OPENING = [
  "Turn on all lights",
  "Turn on music system — select Soho House playlist",
  "Turn on dining room lamps (x2)",
  "Turn on front room plug-in lamp",
  "Turn on disco ball — switch by toilets entrance",
  "Take out bottle bin",
  "Put out bar runners",
  "Check stock levels — fruit and milk",
  "Turn on coffee machine",
  "Empty ice machine into ice well",
  "Insert glasswasher filter and turn on",
  "Wash any glassware from previous night",
  "Turn on fridge lights",
  "Check POS machines are charged",
  "Fill wine bucket with by-the-glass wines, top with ice",
  "Wipe bar surfaces",
  "Turn on A/C or heating as needed",
  "Prepare fruit — lemons, limes, grapefruit, raspberries, mint",
  "Set tables per bookings",
  "Polish cutlery from previous shift",
  "Check toilets — toilet roll, hand soap, supplies",
  "Check with chef for shortages or out-of-stock items",
  "Ready for service"
];

const DEFAULT_FOH_OPENING_THURSDAY = [
  "Water plants",
  "Empty cigarette bin"
];

const DEFAULT_FOH_CLEANING = [
  "Clean window sill by table 4",
  "Clean window sill in bar area",
  "Clean banister area",
  "Polish brass around the bar",
  "Beer glasses — shelf to dishwasher",
  "Spritz glasses — shelf to dishwasher",
  "Wine glasses — shelf to dishwasher",
  "Tall glasses — shelf to dishwasher",
  "Deep clean bottle shelf and wipe bottles",
  "Deep clean ice machine",
  "Deep clean dishwasher",
  "Descale and deep clean coffee machine",
  "Deep clean bar area floor",
  "Empty and clean fridge 1 shelves",
  "Empty and clean fridge 2 shelves",
  "Brush and spot clean chairs",
  "Wipe down chair legs",
  "Deep clean highchairs",
  "Deep clean speed rails"
];

const DEFAULT_FOH_CLOSING = [
  "Clean, wipe, and relay tables",
  "Brush down chairs",
  "Restock napkins",
  "Polish all cutlery",
  "Turn off dining room lamps (x2)",
  "Turn off disco ball",
  "Replace candles in holders and glass jars",
  "Put card machines on charge",
  "Clean window by table 5",
  "Wipe down bar tops",
  "Wash all glassware",
  "Polish glasses",
  "Restock beverage napkins",
  "Restock straws",
  "Restock wine fridges",
  "Restock drinks fridge",
  "Restock beer fridge",
  "Clean drip trays",
  "Remove beer nozzles and soak in soda water",
  "Store fruit in fridges with cling film",
  "Turn off strip lighting",
  "Turn off Aperol sign",
  "Turn off small orange cable light",
  "Clean coffee machine",
  "Run measures, shakers, and bar equipment through glasswasher",
  "Empty ice machine into freezer",
  "Wipe down sinks",
  "Clean speed rails",
  "Run beer mats through glasswasher",
  "Empty bins and replace liners",
  "Move bottle bin to end of bar",
  "Wipe down all surfaces",
  "Run black meshing through dishwasher",
  "Replace bottle pourers on bottles",
  "Sanitise bar area with warm soapy water",
  "Clean table 21 with warm soapy water (if used)",
  "Relay tables per next day's bookings"
];

const DEFAULT_FOH_CLOSING_THURSDAY = [
  "Refill salt and pepper grinders"
];

const DEFAULT_FOH_END_OF_NIGHT = [
  "Deposit cash and place printout in safe",
  "Turn off all lights",
  "Turn off heating systems",
  "Walk through — check nothing is out of place",
  "Tidy staff area and personal items",
  "Note any maintenance issues",
  "Check all windows and doors are locked",
  "Set alarm",
  "Lock door and double-check before leaving"
];

// ---- KITCHEN PORTER / POTWASH ----
const DEFAULT_POTWASH_OPENING = [
  "Check dishwasher detergent",
  "Clear all surfaces",
  "Make sure bins don't need emptying",
  "Make a tub of hot soapy water for cutlery",
  "Make sure cardboard is being recycled",
  "Make sure drying rack is clear"
];

const DEFAULT_POTWASH_CLOSING = [
  "Finish all washing",
  "Drain machine",
  "Take bins out",
  "Put in new bin bags",
  "Put plates in lift",
  "Clean bins if necessary",
  "Clean all sides with soapy water",
  "Close windows",
  "Turn extractor fan OFF",
  "Make sure all tubs are neatly organised",
  "Sweep floor",
  "Mop floor (and where the bins are kept)",
  "Make sure cloths are left to dry",
  "Make sure trays are stacked neatly",
  "Make sure kitchen door is closed on way out"
];

const { useState, useEffect, useMemo, useRef, useCallback } = React;

function useLocal(key, initial) {
  const [v, setV] = useState(() => LS.get(key, initial));
  useEffect(() => { LS.set(key, v); }, [key, v]);
  return [v, setV];
}

// ---- Whole-app checklist sync ----
// Checklist contents, ticks, prep quantities and initials sync between
// devices through the Worker (GET/POST /sync): one shared document, merged
// per key, newest write wins. localStorage stays as the on-device cache so
// everything still works offline; changes push when back online.
const SYNC_KEYS = [
  "fs.opening.v1", "fs.opening.checked.v1",
  "fs.lineup.v2", "fs.lineup.checked.v2",
  "fs.checklist.v2", "fs.checklist.sunday.v1", "fs.checklist.checked.v2",
  "fs.potwash.opening.v1", "fs.potwash.closing.v1", "fs.potwash.deep.v1", "fs.potwash.checked.v1",
  "fs.prep.open.v1", "fs.prep.cats.v1", "fs.prep.closing.v2", "fs.prep.deep.v1",
  "fs.prep.checked.v1", "fs.prep.qty.v1",
  "fs.initials.v1", "fs.daylog.lastSaved", "fs.daylog.activity",
  "foh.opening.v1", "foh.opening.thu.v1", "foh.opening.checked.v1",
  "foh.cleaning.v1", "foh.cleaning.checked.v1",
  "foh.closing.v1", "foh.closing.thu.v1", "foh.endofnight.v1", "foh.closing.checked.v1"
];
const SYNC_POLL_MS = 5000;
const SYNC_DEBOUNCE_MS = 700;

const Sync = {
  applied: {},   // key -> timestamp of the value we hold
  dirty: {},     // key -> { v, t } waiting to be pushed
  listeners: {}, // key -> [setState]
  timer: null,
  started: false,

  subscribe(key, fn) {
    (this.listeners[key] = this.listeners[key] || []).push(fn);
    this.start();
    return () => { this.listeners[key] = (this.listeners[key] || []).filter(f => f !== fn); };
  },
  notify(key, v) { (this.listeners[key] || []).forEach(fn => fn(v)); },

  // Write a value locally and queue it for the server. Used by useShared and
  // by clearDayLocal so End the Day clears every device.
  set(key, v) {
    LS.set(key, v);
    const t = Date.now();
    this.applied[key] = t;
    this.dirty[key] = { v, t };
    this.notify(key, v);
    if (this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(() => this.push(), SYNC_DEBOUNCE_MS);
  },

  async push() {
    const base = getWorkerBase();
    const pw = getAuthPassword();
    if (!base || !pw) return;
    const changes = this.dirty;
    if (!Object.keys(changes).length) return;
    this.dirty = {};
    try {
      const res = await fetch(`${base}/sync`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ password: pw, changes })
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
    } catch (e) {
      // keep the changes so they retry on the next poll/change
      this.dirty = { ...changes, ...this.dirty };
    }
  },

  async pull() {
    const base = getWorkerBase();
    const pw = getAuthPassword();
    if (!base || !pw) return;
    try {
      const res = await fetch(`${base}/sync`, { headers: { "X-App-Password": pw } });
      if (!res.ok) return;
      const data = await res.json();
      const state = data.state || {};
      Object.keys(state).forEach(key => {
        if (SYNC_KEYS.indexOf(key) === -1) return;
        if (this.dirty[key]) return; // our newer change is still pending
        const entry = state[key];
        if (!entry || typeof entry.t !== "number") return;
        if ((this.applied[key] || 0) >= entry.t) return;
        this.applied[key] = entry.t;
        LS.set(key, entry.v);
        this.notify(key, entry.v);
      });
    } catch (e) {}
  },

  start() {
    if (this.started) return;
    this.started = true;
    this.pull();
    setInterval(() => { this.push(); this.pull(); }, SYNC_POLL_MS);
  }
};

// Drop-in replacement for useLocal on stores that must sync between devices.
function useShared(key, initial) {
  const [v, setV] = useState(() => LS.get(key, initial));
  useEffect(() => Sync.subscribe(key, setV), [key]);
  const set = useCallback((val) => { Sync.set(key, val); }, [key]);
  return [v, set];
}

// Parse "3 case" / "4kg" / "2 packs" -> {num, unit}
function parseQty(str) {
  if (str == null) return { num: 0, unit: "" };
  const s = String(str).trim();
  if (!s) return { num: 0, unit: "" };
  const m = s.match(/^(-?\d+(?:\.\d+)?)\s*(.*)$/);
  if (m) return { num: parseFloat(m[1]) || 0, unit: m[2].trim() };
  return { num: 0, unit: s };
}

// Compute shortfall: mustHave.num - inStock.num (clamped >=0).
// Returns a string with the original unit from mustHave (if any).
function stockShortfall(item) {
  const m = parseQty(item.mustHave);
  const h = parseQty(item.inStock);
  const diff = Math.max(0, m.num - h.num);
  if (!item.mustHave) return ""; // nothing set => no auto-fill
  if (diff === 0) return "0";
  const unit = m.unit || h.unit || "";
  return unit ? `${diff}${unit.startsWith('k') || unit.startsWith('g') ? '' : ' '}${unit}` : `${diff}`;
}

// Status for dot color — based on numeric comparison, fall back to text equal.
function stockStatus(item) {
  const m = parseQty(item.mustHave);
  const h = parseQty(item.inStock);
  if (!item.mustHave) return "ok";
  if (m.num === 0) return "ok";
  if (h.num <= 0) return "out";
  if (h.num < m.num * 0.5) return "low";
  if (h.num < m.num) return "partial";
  return "full";
}

function buildMailto(subject, lines, to) {
  const addr = to ? encodeURIComponent(to) : "";
  return `mailto:${addr}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(lines.join("\n"))}`;
}

const fmtDate = () => new Date().toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" });

// Next Thursday (today if it's already Thursday), formatted dd-mm-yy.
function nextThursday() {
  const d = new Date();
  const day = d.getDay(); // 0=Sun ... 4=Thu
  const add = (4 - day + 7) % 7; // 0 if today is Thu
  d.setDate(d.getDate() + add);
  const dd = String(d.getDate()).padStart(2, "0");
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const yy = String(d.getFullYear()).slice(-2);
  return `${dd}-${mm}-${yy}`;
}

// ---- Shared stock sync ----
const POLL_MS = 5000;
const WRITE_DEBOUNCE_MS = 700;

function getWorkerBase() {
  const cfg = (window.FS_CONFIG && window.FS_CONFIG.WORKER_URL) || "";
  return cfg.replace(/\/+$/, "");
}
function getAuthPassword() {
  // In-memory only — the password is never written to localStorage.
  return (window.FS_SESSION && window.FS_SESSION.pw) || "";
}

function useSharedStock(supplier, fallback) {
  const cacheKey = `fs.stock.${supplier}.v1`;
  const [items, setItems] = useState(() => LS.get(cacheKey, fallback));
  const [syncState, setSyncState] = useState("loading");
  const [lastSync, setLastSync] = useState(0);

  const writeTimer = useRef(null);
  const dirtyRef = useRef(false);
  const lastServerRef = useRef(null);

  const push = useCallback(async (payload, { silent = false } = {}) => {
    const base = getWorkerBase();
    if (!base) { if (!silent) setSyncState("offline"); return; }
    if (!silent) setSyncState("saving");
    try {
      const res = await fetch(`${base}/stock`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ password: getAuthPassword(), supplier, items: payload })
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      lastServerRef.current = JSON.stringify(payload);
      dirtyRef.current = false;
      if (!silent) { setSyncState("idle"); setLastSync(Date.now()); }
    } catch (e) {
      if (!silent) setSyncState("offline");
    }
  }, [supplier]);

  const pull = useCallback(async () => {
    const base = getWorkerBase();
    if (!base) { setSyncState("offline"); return; }
    try {
      const res = await fetch(`${base}/stock`);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const data = await res.json();
      const remote = data[supplier];
      if (remote && Array.isArray(remote)) {
        const remoteStr = JSON.stringify(remote);
        if (remoteStr !== lastServerRef.current && !dirtyRef.current) {
          lastServerRef.current = remoteStr;
          setItems(remote);
          LS.set(cacheKey, remote);
        } else {
          lastServerRef.current = remoteStr;
        }
      } else if (remote === null) {
        const seed = LS.get(cacheKey, fallback);
        await push(seed, { silent: true });
      }
      setSyncState("idle");
      setLastSync(Date.now());
    } catch (e) {
      setSyncState("offline");
    }
  }, [supplier, cacheKey, push]);

  useEffect(() => {
    pull();
    const id = setInterval(pull, POLL_MS);
    return () => clearInterval(id);
  }, [pull]);

  const setItemsSynced = useCallback((updater) => {
    setItems(prev => {
      const next = typeof updater === "function" ? updater(prev) : updater;
      LS.set(cacheKey, next);
      dirtyRef.current = true;
      if (writeTimer.current) clearTimeout(writeTimer.current);
      writeTimer.current = setTimeout(() => push(next), WRITE_DEBOUNCE_MS);
      return next;
    });
  }, [cacheKey, push]);

  return [items, setItemsSynced, { syncState, lastSync }];
}

Object.assign(window, {
  LS, DEFAULT_LINEUP, DEFAULT_CHECKLIST, DEFAULT_SUNDAY_CHECKLIST, DEFAULT_OPENING_CHECKLIST,
  DEFAULT_CARNEVALE, DEFAULT_VEGFACTOR, DEFAULT_PILGRIM, DEFAULT_COBBLE_LANE,
  DEFAULT_KATER4, DEFAULT_KILOWINES, DEFAULT_MAJESTIC, DEFAULT_ROTHLEY, DEFAULT_ROUNDCORNER, DEFAULT_AMAZON,
  DEFAULT_FOH_OPENING, DEFAULT_FOH_OPENING_THURSDAY,
  DEFAULT_FOH_CLEANING,
  DEFAULT_FOH_CLOSING, DEFAULT_FOH_CLOSING_THURSDAY, DEFAULT_FOH_END_OF_NIGHT,
  DEFAULT_POTWASH_OPENING, DEFAULT_POTWASH_CLOSING,
  useLocal, useShared, Sync, useSharedStock,
  parseQty, stockShortfall, stockStatus, buildMailto, fmtDate, nextThursday
});
