// Base catalogue shape. All operator/venue/booking data is loaded from the
// Bookable API at boot (see bookable-api.jsx). These arrays start empty and are
// populated by the live loader.
//
// Categories are NOT hardcoded: they are derived at boot from the real
// `category` slugs Bookable sends on each product (see ppRebuildCategories).
// PRODUCT_TYPES is mutated in place so React closures keep their reference.

const PRODUCT_TYPES = [];

const OPERATORS = [];
const VENUES = [];
const SEED_BOOKINGS = [];
const CITIES = [];

// Curated look (accent + blurb) for the well-known categories, keyed by the
// canonical Bookable slug. Best-effort decoration only — any category missing
// here still renders with a generated accent and a titlecased name.
const CURATED = {
  afternoon_tea:          { blurb: 'Sweet & savoury tiers',          accent: '#e8ddc6' },
  bottomless_brunch:      { blurb: 'Free-flow weekend brunch',       accent: '#e9d3d8' },
  lunch:                  { blurb: 'Sit-down midday service',        accent: '#d9e0c8' },
  dinner:                 { blurb: 'Evening dining',                 accent: '#d6d2e3' },
  guestlist_entry_ticket: { blurb: 'Door entry & queue jump',         accent: '#cfd8e6' },
  cocktail_masterclass:   { blurb: 'Hands-on bar experience',        accent: '#ecd9c8' },
  entry_and_drinks:       { blurb: 'Reserved areas & drinks tables', accent: '#e4dcc9' },
  karaoke:                { blurb: 'Private rooms & mic time',        accent: '#e7cfdb' },
};

// Display order for known categories; unknown slugs are appended alphabetically.
const CATEGORY_ORDER = [
  'breakfast', 'lunch', 'dinner', 'afternoon_tea', 'cocktail_masterclass',
  'bottomless_brunch', 'entry_and_drinks', 'karaoke', 'guestlist_entry_ticket',
  'coworking', 'venue_hire', 'live_sports',
];

const ACCENT_PALETTE = [
  '#e8ddc6', '#e9d3d8', '#d9e0c8', '#d6d2e3', '#cfd8e6', '#ecd9c8',
  '#e4dcc9', '#e7cfdb', '#cfdcc8', '#c9dcdf', '#ddd2e8', '#e9d4d4',
];

// Slug → human label: "live_sports" → "Live Sports".
function titleCase(slug) {
  return String(slug || '').replace(/[-_]+/g, ' ').trim()
    .replace(/\b\w/g, c => c.toUpperCase()) || 'Other';
}

// Stable accent for any category — curated colour if known, else a deterministic
// pick from the palette (same slug always maps to the same colour).
function accentFor(id) {
  if (CURATED[id]) return CURATED[id].accent;
  let h = 2166136261;
  const s = String(id || '');
  for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); }
  return ACCENT_PALETTE[(h >>> 0) % ACCENT_PALETTE.length];
}

// Rebuild PRODUCT_TYPES from the set of category slugs actually present in the
// loaded data. Drops `uncategorised`, orders known categories first.
function ppRebuildCategories(slugs) {
  const set = new Set((slugs || []).filter(s => s && s !== 'uncategorised'));
  const ordered = [
    ...CATEGORY_ORDER.filter(s => set.has(s)),
    ...[...set].filter(s => !CATEGORY_ORDER.includes(s)).sort(),
  ];
  PRODUCT_TYPES.length = 0;
  ordered.forEach(id => PRODUCT_TYPES.push({
    id,
    name: titleCase(id),
    blurb: (CURATED[id] && CURATED[id].blurb) || '',
    accent: accentFor(id),
  }));
  return PRODUCT_TYPES;
}

// Look up a category by slug. Falls back to a synthesized entry so callers that
// read `.name` never crash on a slug not (yet) in PRODUCT_TYPES.
function productById(id) {
  if (!id) return undefined;
  return PRODUCT_TYPES.find(p => p.id === id)
    || { id, name: titleCase(id), blurb: '', accent: accentFor(id) };
}
function operatorById(id) { return OPERATORS.find(o => o.id === id); }
function venueById(id) { return VENUES.find(v => v.id === id); }

// Availability comes from /api/availability — this stub keeps any legacy
// callers that haven't been migrated from crashing.
function slotsFor() { return []; }

// ── Text normalisation, shared by the listing's search ───────────────────────
// Coerce arrays / object-maps / strings / missing into a flat list, so a mixed
// payload shape (e.g. productNames keyed by productId) can't throw.
function ppValues(x) {
  if (Array.isArray(x)) return x;
  if (x && typeof x === 'object') return Object.values(x);
  return (x != null && x !== '') ? [x] : [];
}

// Lowercase, strip accents, drop punctuation, collapse whitespace — so "Fish &
// Chips" and "fish chips" match. (Lived in omni-suggest.jsx until BOO-644
// replaced that dropdown with the prototype's plain keyword input.)
function ppNorm(s) {
  return (s || '').toString().toLowerCase()
    .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
    .replace(/[^a-z0-9\s]/g, ' ')
    .replace(/\s+/g, ' ').trim();
}

// Bookable sends `area` pipe-delimited, broadest last — "Mayfair|Central London",
// "Soho|West End|Fitzrovia". The first segment is the one a partner recognises;
// the rest read as noise in a dense row.
function ppArea(area) {
  return String(area || '').split('|')[0].trim();
}

// ── Venue search haystack ────────────────────────────────────────────────────
// One normalised blob per venue: name, description, city, area, operator, the
// product display names, and every pre-order menu/package/dish name. On the real
// Stonegate catalogue that is ~930KB of text over 939 venues, so it is built on
// first use and cached on the venue — rebuilding it per render cost ~35ms and
// made every keystroke visibly janky.
//
// The cache key is whether the lazily-loaded pre-order catalogue has landed yet
// (ppLoadVenuePreorders patches venue.preorders in place after the booking drawer
// opens), so a venue whose menus arrive later gets exactly one rebuild.
function ppVenueHay(v) {
  if (!v) return '';
  const pre = v.preorders || {};
  const depth = (pre.menus ? pre.menus.length : 0) + (pre.packages ? pre.packages.length : 0);
  if (v.__ppHay !== undefined && v.__ppHayDepth === depth) return v.__ppHay;
  const op = operatorById(v.operator);
  const menuText = ppValues(pre.menus).flatMap(m => [
    m.name, m.description, ...ppValues(m.items).flatMap(it => [it.name, it.description]),
  ]);
  const pkgText = ppValues(pre.packages).flatMap(p => [p.name, p.description]);
  const hay = ppNorm([
    v.name, v.description, v.city, v.area, op && op.name,
    // The boot catalogue carries a compact string of every menu/package/dish name;
    // menuText/pkgText only exist for venues whose drawer has been opened.
    v.preorderSearchText,
    ...ppValues(v.productNames), ...menuText, ...pkgText,
  ].filter(Boolean).join(' '));
  try { v.__ppHay = hay; v.__ppHayDepth = depth; } catch (e) {}
  return hay;
}

// ── Device-local UI preferences ──────────────────────────────────────────────
// One place for "remember this on this device" flags (list/grid/map view,
// itinerary mode). The pp:pref event is what lets a write from the Settings
// screen reach a hook mounted somewhere else in the tree without a reload —
// the same shape as the pp:auth / pp:data-loaded events in bookable-api.jsx.
function ppGetPref(key, fallback) {
  try {
    const raw = localStorage.getItem(key);
    return raw === null ? fallback : JSON.parse(raw);
  } catch (e) { return fallback; }
}
function ppSetPref(key, value) {
  try { localStorage.setItem(key, JSON.stringify(value)); } catch (e) {}
  window.dispatchEvent(new CustomEvent('pp:pref', { detail: { key, value } }));
}
function ppUsePref(key, fallback) {
  const [value, setValue] = React.useState(() => ppGetPref(key, fallback));
  React.useEffect(() => {
    const onPref = (e) => { if (e.detail && e.detail.key === key) setValue(e.detail.value); };
    window.addEventListener('pp:pref', onPref);
    return () => window.removeEventListener('pp:pref', onPref);
  }, [key]);
  return [value, (next) => ppSetPref(key, next)];
}

Object.assign(window, {
  PRODUCT_TYPES, OPERATORS, VENUES, CITIES, SEED_BOOKINGS,
  slotsFor, productById, operatorById, venueById,
  titleCase, accentFor, ppRebuildCategories,
  ppGetPref, ppSetPref, ppUsePref, ppNorm, ppValues, ppArea, ppVenueHay,
});
