// Main app — composes all views
const { useState, useEffect, useMemo } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#4F63D2",
  "density": "comfortable",
  "showCharts": true,
  "currency": "AUD",
  "highlightStuck": true
}/*EDITMODE-END*/;

function App() {
  const data = window.SALES_DATA;
  // Live (Supabase) mode: the server is the source of truth for every org-owned
  // collection. The kub_*_v1 localStorage cache is a DEMO-only working store —
  // reading it in live mode would show a stale/demo team list and silently
  // override the roster/roles the database returned (RLS already scopes reads
  // correctly per role). So in live mode we always seed state from data.* and
  // never let the cache win.
  const LIVE = !!window.STAGEVO_AUTHED;
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);

  const [deals, setDeals] = useState(() => {
    if (LIVE) return data.DEALS;
    try {
      const saved = localStorage.getItem('kub_deals_v1');
      if (saved) {
        const parsed = JSON.parse(saved);
        if (Array.isArray(parsed) && parsed.length > 0) {
          // Migration: dedupe any repeated deal ids from older caches
          const seen = {};
          let fixed = false;
          const out = parsed.map(d => {
            let id = d.id;
            if (seen[id] != null) { seen[id] += 1; id = `${d.id}-${seen[id]}`; fixed = true; }
            else { seen[id] = 0; }
            return id === d.id ? d : { ...d, id };
          });
          return out;
        }
      }
    } catch (e) {}
    return data.DEALS;
  });
  const [contacts, setContacts] = useState(() => {
    function isValidPhone(s) {
      if (!s) return false;
      return /^[0-9]{6,15}$/.test(s.toString().replace(/[\s\-()+]/g, ''));
    }
    function isValidEmail(s) {
      return s && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
    }
    function cleanContact(c) {
      let touched = false;
      if (c.phone && !isValidPhone(c.phone)) { c.phone = ''; touched = true; }
      if (c.email && !isValidEmail(c.email)) { c.email = ''; touched = true; }
      return touched;
    }
    if (LIVE) { const fresh = data.CONTACTS || []; fresh.forEach(cleanContact); return fresh; }
    try {
      const saved = localStorage.getItem('kub_contacts_v1');
      if (saved) {
        const parsed = JSON.parse(saved);
        if (Array.isArray(parsed) && parsed.length > 0) {
          // Migration: clean malformed phone/email fields once
          parsed.forEach(cleanContact);
          // Merge in any seeded inbound leads not already present (e.g. new marketing-lead demo)
          const seeded = (data.CONTACTS || []).filter(c => c.inbound);
          const haveIds = new Set(parsed.map(c => c.id));
          const toAdd = seeded.filter(c => !haveIds.has(c.id));
          if (toAdd.length) return [...toAdd, ...parsed];
          return parsed;
        }
      }
    } catch (e) {}
    const fresh = data.CONTACTS || [];
    fresh.forEach(cleanContact);
    return fresh;
  });
  const [users, setUsers] = useState(() => {
    if (LIVE) return data.USERS;
    try {
      const saved = localStorage.getItem('kub_users_v1');
      if (saved) {
        const parsed = JSON.parse(saved);
        if (Array.isArray(parsed) && parsed.length > 0) return parsed;
      }
    } catch (e) {}
    return data.USERS;
  });
  const [showroomsList, setShowroomsList] = useState(() => {
    if (LIVE) return data.SHOWROOMS;
    try {
      const saved = localStorage.getItem('kub_showrooms_v1');
      if (saved) {
        const parsed = JSON.parse(saved);
        if (Array.isArray(parsed) && parsed.length > 0) return parsed;
      }
    } catch (e) {}
    return data.SHOWROOMS;
  });
  useEffect(() => {
    if (LIVE) return;
    try { localStorage.setItem('kub_showrooms_v1', JSON.stringify(showroomsList)); } catch (e) {}
  }, [showroomsList]);
  const [targets, setTargets] = useState(() => {
    try {
      const saved = localStorage.getItem('kub_targets_v1');
      if (saved) return JSON.parse(saved);
    } catch (e) {}
    return {};
  });
  const [tasks, setTasks] = useState(() => {
    try {
      const saved = localStorage.getItem('kub_tasks_v1');
      if (saved) return JSON.parse(saved);
    } catch (e) {}
    return [];
  });
  useEffect(() => {
    try { localStorage.setItem('kub_tasks_v1', JSON.stringify(tasks)); } catch (e) {}
  }, [tasks]);
  useEffect(() => {
    try { localStorage.setItem('kub_targets_v1', JSON.stringify(targets)); } catch (e) {}
  }, [targets]);

  // ===== Company KPIs: definitions, per-designer monthly targets, month locks, audit =====
  const [kpiDefs, setKpiDefs] = useState(() => {
    let stored = null;
    try { const s = localStorage.getItem('pearler_kpis_v1'); if (s) { const p = JSON.parse(s); if (Array.isArray(p)) stored = p; } } catch (e) {}
    const DEF = window.DEFAULT_KPIS || [];
    if (!stored) return DEF;
    stored.forEach(k => { if (k.source == null) k.source = 'manual'; if (!k.targetPeriod) k.targetPeriod = 'month'; });
    const haveIds = new Set(stored.map(k => k.id));
    const toAdd = DEF.filter(k => k.source && k.source !== 'manual' && !haveIds.has(k.id));
    return toAdd.length ? [...stored, ...toAdd] : stored;
  });
  const [kpiTargets, setKpiTargets] = useState(() => { try { return JSON.parse(localStorage.getItem('pearler_kpi_targets_v1')) || {}; } catch (e) { return {}; } });
  const [kpiLocks, setKpiLocks] = useState(() => { try { return JSON.parse(localStorage.getItem('pearler_kpi_locks_v1')) || {}; } catch (e) { return {}; } });
  const [kpiAudit, setKpiAudit] = useState(() => { try { return JSON.parse(localStorage.getItem('pearler_kpi_audit_v1')) || []; } catch (e) { return []; } });
  useEffect(() => { try { localStorage.setItem('pearler_kpis_v1', JSON.stringify(kpiDefs)); } catch (e) {} }, [kpiDefs]);
  useEffect(() => { try { localStorage.setItem('pearler_kpi_targets_v1', JSON.stringify(kpiTargets)); } catch (e) {} }, [kpiTargets]);
  useEffect(() => { try { localStorage.setItem('pearler_kpi_locks_v1', JSON.stringify(kpiLocks)); } catch (e) {} }, [kpiLocks]);
  useEffect(() => { try { localStorage.setItem('pearler_kpi_audit_v1', JSON.stringify(kpiAudit)); } catch (e) {} }, [kpiAudit]);

  const _kpiAuditStamp = () => new Date().toLocaleString('en-AU', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' });
  const saveKpiDef = (k) => {
    setKpiDefs(prev => (k.id && prev.find(x => x.id === k.id)) ? prev.map(x => x.id === k.id ? k : x) : [...prev, { ...k, id: k.id || 'k' + Date.now() }]);
    showToast(k.id ? 'KPI updated' : 'KPI created');
  };
  const deleteKpiDef = (id) => { setKpiDefs(prev => prev.filter(x => x.id !== id)); showToast('KPI deleted'); };
  const setKpiTargetCell = (kpiId, repId, month, value) => {
    const def = kpiDefs.find(k => k.id === kpiId);
    const rep = reps.find(r => r.id === repId);
    const prev = ((kpiTargets[kpiId] || {})[repId] || {})[month];
    setKpiTargets(p => ({ ...p, [kpiId]: { ...(p[kpiId] || {}), [repId]: { ...((p[kpiId] || {})[repId] || {}), [month]: value } } }));
    setKpiAudit(a => [{ tsLabel: _kpiAuditStamp(), by: currentUser.name, byId: currentUser.id, kpiId, kpiName: def && def.name, repId, repName: rep && rep.name, month, action: 'target', prev: prev == null ? '' : prev, next: value }, ...a].slice(0, 800));
  };
  const toggleKpiLock = (kpiId, month) => {
    const def = kpiDefs.find(k => k.id === kpiId);
    const locked = !!((kpiLocks[kpiId] || {})[month]);
    setKpiLocks(p => { const m = { ...(p[kpiId] || {}) }; if (locked) delete m[month]; else m[month] = true; return { ...p, [kpiId]: m }; });
    setKpiAudit(a => [{ tsLabel: _kpiAuditStamp(), by: currentUser.name, byId: currentUser.id, kpiId, kpiName: def && def.name, month, action: locked ? 'unlock' : 'lock' }, ...a].slice(0, 800));
  };
  const [reps, setReps] = useState(() => {
    if (LIVE) return data.REPS;
    try {
      const saved = localStorage.getItem('kub_reps_v1');
      if (saved) {
        const parsed = JSON.parse(saved);
        if (Array.isArray(parsed) && parsed.length > 0) return parsed;
      }
    } catch (e) {}
    return data.REPS;
  });
  const [newDealOpen, setNewDealOpen] = useState(false);
  const [newContactOpen, setNewContactOpen] = useState(false);
  const [openContactId, setOpenContactId] = useState(null);
  const [view, setView] = useState('overview');
  const [termsVersion, setTermsVersion] = useState(0); // bump to re-render after terminology change
  const [currentUserId, setCurrentUserId] = useState(window.STAGEVO_CURRENT_USER_ID || 'u_jm'); // live: signed-in user; demo: Charith
  const [showroom, setShowroom] = useState('all'); // global filter
  const [openId, setOpenId] = useState(null);
  const [search, setSearch] = useState('');
  const [myDealsOnly, setMyDealsOnly] = useState(false);
  const [filters, setFilters] = useState({
    stage: 'all',
    rep: 'all',
    dateRange: 'all',
    agingOnly: false,
    pastForecast: false,
  });
  const [toast, setToast] = useState(null);
  // ROI Calculator settings — the org's plan + blended hourly rate (persisted)
  const [roiPlan, setRoiPlan] = useState(() => localStorage.getItem('stagevo_roi_plan') || 'growth');
  const [roiRate, setRoiRate] = useState(() => Number(localStorage.getItem('stagevo_roi_rate')) || 35);
  useEffect(() => { try { localStorage.setItem('stagevo_roi_plan', roiPlan); } catch (e) {} }, [roiPlan]);
  useEffect(() => { try { localStorage.setItem('stagevo_roi_rate', String(roiRate)); } catch (e) {} }, [roiRate]);

  const currentUser = users.find(u => u.id === currentUserId) || users[0] || {
    id: currentUserId, name: 'Admin', email: '', title: 'Admin',
    role: 'admin', status: 'active', superAdmin: true,
    home: null, manages: [], works: [], repId: currentUserId, quota: 0,
  };
  const role = currentUser.role || 'sales_designer';
  const isAdmin = role === 'admin' || currentUser.superAdmin;
  const isLeadership = isAdmin || role === 'leadership';
  const isManager = role === 'manager';
  const isSalesDesigner = role === 'sales_designer';

  // ===== Role-based section access =====
  const [accessMatrix, setAccessMatrix] = useState(() => window.loadAccessMatrix());
  useEffect(() => { try { localStorage.setItem('pearler_access_v1', JSON.stringify(accessMatrix)); } catch (e) {} }, [accessMatrix]);
  const canAccess = (v) => window.canAccessView(accessMatrix, role, v);
  const setRoleAccess = (roleId, viewId, allowed) => {
    if (roleId === 'admin') return; // admin is always full
    if ((window.ACCESS_ADMIN_LOCKED || []).includes(viewId)) return; // hard security floor
    setAccessMatrix(prev => ({ ...prev, [roleId]: { ...prev[roleId], [viewId]: allowed } }));
  };

  // ===== Workspace settings =====
  const [settings, setSettings] = useState(() => {
    const def = window.DEFAULT_SETTINGS || {};
    let stored = {};
    try { stored = JSON.parse(localStorage.getItem('pearler_settings_v1')) || {}; } catch (e) {}
    return {
      ...def, ...stored,
      org: { ...(def.org || {}), ...(stored.org || {}) },
      notifications: { ...(def.notifications || {}), ...(stored.notifications || {}) },
      security: { ...(def.security || {}), ...(stored.security || {}) },
      privacy: { ...(def.privacy || {}), ...(stored.privacy || {}) },
      integrations: { ...(def.integrations || {}), ...(stored.integrations || {}) },
      appearance: { ...(def.appearance || {}), ...(stored.appearance || {}) },
      webhooks: stored.webhooks || def.webhooks || [],
    };
  });
  useEffect(() => { try { localStorage.setItem('pearler_settings_v1', JSON.stringify(settings)); } catch (e) {} }, [settings]);
  const updateSetting = (section, key, value) => {
    setSettings(prev => key == null ? { ...prev, [section]: value } : { ...prev, [section]: { ...prev[section], [key]: value } });
  };

  // ===== Workflow automations =====
  const [automations, setAutomations] = useState(() => { try { return JSON.parse(localStorage.getItem('pearler_automations_v1')) || window.DEFAULT_AUTOMATIONS || []; } catch (e) { return window.DEFAULT_AUTOMATIONS || []; } });
  const [automationLog, setAutomationLog] = useState(() => { try { return JSON.parse(localStorage.getItem('pearler_automation_log_v1')) || []; } catch (e) { return []; } });
  useEffect(() => { try { localStorage.setItem('pearler_automations_v1', JSON.stringify(automations)); } catch (e) {} }, [automations]);
  useEffect(() => { try { localStorage.setItem('pearler_automation_log_v1', JSON.stringify(automationLog)); } catch (e) {} }, [automationLog]);
  const saveAutomation = (rule) => { setAutomations(prev => (rule.id && prev.find(r => r.id === rule.id)) ? prev.map(r => r.id === rule.id ? rule : r) : [...prev, { ...rule, id: rule.id || 'auto_' + Date.now(), runCount: 0, lastRun: null }]); showToast(rule.id ? 'Automation saved' : 'Automation created'); };
  const deleteAutomation = (id) => { setAutomations(prev => prev.filter(r => r.id !== id)); showToast('Automation deleted'); };
  const toggleAutomation = (id, enabled) => setAutomations(prev => prev.map(r => r.id === id ? { ...r, enabled } : r));

  // ===== Site-visit WHS checklists (attached to customer profiles) =====
  const [siteVisits, setSiteVisits] = useState(() => { try { return JSON.parse(localStorage.getItem('kub_sitevisits_v1')) || []; } catch (e) { return []; } });
  useEffect(() => { try { localStorage.setItem('kub_sitevisits_v1', JSON.stringify(siteVisits)); } catch (e) {} }, [siteVisits]);
  const saveSiteVisit = (visit) => {
    setSiteVisits(prev => prev.find(x => x.id === visit.id) ? prev.map(x => x.id === visit.id ? visit : x) : [visit, ...prev]);
    const msg = { sent: 'Checklist sent to customer for signature', signed: 'Customer signed the checklist', locked: 'Site-visit checklist finalised & locked' }[visit.status] || 'Site-visit checklist saved';
    showToast(msg);
  };

  const automationsRef = React.useRef(automations);
  useEffect(() => { automationsRef.current = automations; }, [automations]);
  const fireAutomations = (eventType, ctx) => {
    if (!window.runAutomations) return;
    const rules = automationsRef.current;
    const handlers = {
      repName: (id) => (reps.find(r => r.id === id) || {}).name,
      run: (a, c, rule) => {
        const deal = c.deal, contact = c.contact;
        if (a.type === 'create_task') {
          const dd = new Date(); dd.setDate(dd.getDate() + Number(a.days != null ? a.days : 2));
          const date = `${dd.getFullYear()}-${String(dd.getMonth() + 1).padStart(2, '0')}-${String(dd.getDate()).padStart(2, '0')}`;
          const repId = (deal && deal.rep) || (contact && (reps.find(r => r.name === contact.owner) || {}).id) || (reps[0] && reps[0].id);
          const who = deal ? deal.customer : (contact ? contact.name : '');
          setTasks(prev => [{ id: 'task_auto_' + Date.now() + Math.random().toString(36).slice(2, 5), title: (window.apptTypeLabel ? window.apptTypeLabel(a.taskType || 'followup_call') : 'Follow up') + ' · ' + who, type: a.taskType || 'followup_call', date, time: '09:00', repId, contactId: contact ? contact.id : null, notes: 'Auto-created by workflow: ' + rule.name, createdBy: 'Automation', leadMinutes: 15 }, ...prev]);
        } else if (a.type === 'log_note' && deal) {
          setDeals(prev => prev.map(x => x.id === deal.id ? { ...x, activities: [{ id: 'a_auto_' + Date.now(), type: 'note', label: '⚡ ' + (a.text || 'Workflow note'), date: data.TODAY, daysAgo: 0 }, ...(x.activities || [])] } : x));
        } else if (a.type === 'notify') {
          showToast('⚡ ' + (a.text || 'Automation triggered'));
        } else if (a.type === 'reassign' && deal && a.rep) {
          bulkReassign([deal.id], a.rep, 'Auto-reassigned by workflow: ' + rule.name);
        }
      },
      onFired: (ids, c) => {
        const stamp = new Date().toLocaleString('en-AU', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' });
        const subject = c.deal ? c.deal.customer : (c.contact ? c.contact.name : '');
        setAutomations(prev => prev.map(r => ids.includes(r.id) ? { ...r, runCount: (r.runCount || 0) + 1, lastRun: stamp } : r));
        setAutomationLog(prev => [...ids.map(id => ({ ts: stamp, ruleId: id, ruleName: (automationsRef.current.find(r => r.id === id) || {}).name, subject })), ...prev].slice(0, 100));
      },
    };
    window.runAutomations(eventType, ctx, rules, handlers);
  };
  // Combined: anyone above sales-designer can see broader views, but only admin can change settings
  const canViewAllShowrooms = isLeadership; // admin + leadership see all
  const canEditSettings = isAdmin; // ONLY admin can change settings
  const allowedShowrooms = canViewAllShowrooms
    ? showroomsList.map(s => s.id)
    : isManager
      ? (currentUser.manages || [])
      : (currentUser.works || []);

  // When a salesperson logs in, ensure the showroom filter is locked to their own
  useEffect(() => {
    if (!canViewAllShowrooms) {
      // Lock the showroom filter to the user's accessible set
      if (showroom === 'all' || !allowedShowrooms.includes(showroom)) {
        setShowroom(allowedShowrooms[0] || (currentUser.home || showroomsList[0].id));
      }
    }
  }, [currentUserId]);

  // Enforce section access — bounce to Overview if the current role can't access the view
  useEffect(() => {
    if (!canAccess(view)) setView('overview');
  }, [view, currentUserId, accessMatrix]);

  // Persist deals to localStorage on every change (DEMO only — in live mode the
  // database is the source of truth and caching would contaminate later sessions).
  useEffect(() => {
    if (LIVE) return;
    try { localStorage.setItem('kub_deals_v1', JSON.stringify(deals)); } catch (e) {}
  }, [deals]);
  useEffect(() => {
    if (LIVE) return;
    try { localStorage.setItem('kub_contacts_v1', JSON.stringify(contacts)); } catch (e) {}
  }, [contacts]);
  useEffect(() => {
    if (LIVE) return;
    try { localStorage.setItem('kub_users_v1', JSON.stringify(users)); } catch (e) {}
  }, [users]);
  useEffect(() => {
    if (LIVE) return;
    try { localStorage.setItem('kub_reps_v1', JSON.stringify(reps)); } catch (e) {}
  }, [reps]);

  // Apply theme + tweaks to DOM. We set the theme's tokens INLINE on <html> so
  // a runtime switch recomputes reliably. A synchronous display toggle forces a
  // full style recalc so descendants pick up the changed inherited custom
  // properties (works around a var() invalidation quirk in some engines).
  const theme = (settings.appearance && settings.appearance.theme) || 'pine';
  const [userThemeOverride, setUserThemeOverride] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('stagevo_user_prefs_' + currentUserId) || '{}').theme || null; } catch { return null; }
  });
  React.useEffect(() => {
    try { setUserThemeOverride(JSON.parse(localStorage.getItem('stagevo_user_prefs_' + currentUserId) || '{}').theme || null); } catch {}
  }, [currentUserId]);
  const effectiveTheme = userThemeOverride || theme;
  React.useLayoutEffect(() => {
    const el = document.documentElement;
    const tokens = (window.THEME_TOKENS || {})[effectiveTheme] || (window.THEME_TOKENS || {}).pine || {};
    Object.keys(tokens).forEach((k) => el.style.setProperty(k, tokens[k]));
    el.dataset.theme = effectiveTheme;
    document.body.dataset.density = tweaks.density;
    const prev = el.style.display;
    el.style.display = 'none';
    void el.offsetHeight;
    el.style.display = prev;
  }, [effectiveTheme, tweaks]);

  // ===== Negative numbers → red. Brackets come from fmt.*; here we flag any
  // value-cell whose text is a pure negative number so CSS can paint it red.
  // Scoped to numeric display elements and matched with a strict regex so plain
  // text (e.g. labels like "(>21d)") is never affected.
  useEffect(() => {
    const root = document.getElementById('root');
    if (!root || typeof MutationObserver === 'undefined') return;
    const NEG = /^(\(\$?\d[\d,]*(\.\d+)?[%kKmMdb]?\)|[-−]\$?\d[\d,]*(\.\d+)?[%kKmMdb]?)$/;
    const SEL = '.num, .kpi-value, .delta, .fc-value, .roi-mrow-val, .fc-cell .pct';
    let raf = null;
    const paint = () => {
      raf = null;
      root.querySelectorAll(SEL).forEach((el) => {
        el.classList.toggle('neg', NEG.test((el.textContent || '').trim()));
      });
    };
    const schedule = () => { if (raf == null) raf = requestAnimationFrame(paint); };
    paint();
    const mo = new MutationObserver(schedule);
    mo.observe(root, { childList: true, subtree: true, characterData: true });
    return () => { mo.disconnect(); if (raf) cancelAnimationFrame(raf); };
  }, []);

  // ===== Admin: user CRUD =====
  const updateUser = (id, patch) => {
    setUsers(prev => prev.map(u => u.id === id ? { ...u, ...patch } : u));
    // Sync to reps record if salesperson
    const u = users.find(x => x.id === id);
    if (u && u.repId) {
      setReps(prev => prev.map(r => r.id === u.repId
        ? { ...r, home: patch.home ?? r.home, works: patch.works ?? r.works, name: patch.name ?? r.name }
        : r
      ));
    }
    showToast(`Updated ${patch.name || (u && u.name) || 'user'}`);
  };
  const inviteUser = (newUser) => {
    const id = 'u_new_' + Math.random().toString(36).slice(2, 8);
    const repId = newUser.role === 'sales_designer' ? 'r_new_' + Math.random().toString(36).slice(2, 6) : undefined;
    const userRec = { id, repId, invitedBy: currentUserId, ...newUser };
    setUsers(prev => [...prev, userRec]);
    if (repId) {
      const repRec = { id: repId, name: newUser.name, home: newUser.home, works: newUser.works, quota: 200000 };
      setReps(prev => [...prev, repRec]);
    }
    showToast(`Invite sent to ${newUser.email}`);
  };
  const deleteUser = (id) => {
    const u = users.find(x => x.id === id);
    setUsers(prev => prev.filter(x => x.id !== id));
    if (u && u.repId) setReps(prev => prev.filter(r => r.id !== u.repId));
    showToast(`Removed ${u ? u.name : 'user'}`);
  };

  const resendInvite = (user) => {
    if (!user || !user.email) return;
    // Update the user's last invite timestamp (resets status to 'invited' if they were disabled)
    setUsers(prev => prev.map(u => u.id === user.id
      ? { ...u, lastInviteSentAt: data.TODAY, status: u.status === 'disabled' ? u.status : (u.status === 'active' ? u.status : 'invited') }
      : u
    ));
    const action = user.status === 'invited' ? 'Invite link' : user.status === 'disabled' ? 'Re-activation link' : 'Password reset link';
    showToast(`${action} sent to ${user.email}`);
  };

  const openDeal = (d) => setOpenId(d.id);
  const closeDeal = () => setOpenId(null);
  const openDealObj = openId ? deals.find(d => d.id === openId) : null;

  const editDeal = (id, patch) => {
    // Intercept a move to "Lost" — require a loss reason first (unless already supplied)
    if (patch.stage === 'lost' && !patch.lossReason) {
      const d = deals.find(x => x.id === id);
      if (!d || d.stage !== 'lost') {
        setLostModal({ id, patch });
        return;
      }
    }
    setDeals(prev => prev.map(d => d.id === id ? { ...d, ...patch, lastActivity: data.TODAY, lastActivityDaysAgo: 0 } : d));
    if (patch.stage) {
      const stage = data.STAGES.find(s => s.id === patch.stage);
      showToast(`Moved to ${stage.label}`);
      const movedDeal = { ...(deals.find(x => x.id === id) || {}), ...patch };
      fireAutomations('deal_stage', { deal: movedDeal });
      if (patch.stage === 'sold') fireAutomations('deal_won', { deal: movedDeal });
      if (patch.stage === 'lost') fireAutomations('deal_lost', { deal: movedDeal });
      // ── Audit: stage change ──
      if (LIVE && window.StagevoData && window.StagevoData.writeAudit) {
        const before = deals.find(x => x.id === id);
        window.StagevoData.writeAudit('stage_change', 'deals', id,
          { stage: before ? before.stage : null },
          { stage: patch.stage, label: stage ? stage.label : patch.stage }
        ).catch(() => {});
      }
    } else if (LIVE && window.StagevoData && window.StagevoData.writeAudit) {
      // ── Audit: field update ──
      const before = deals.find(x => x.id === id);
      const changedKeys = Object.keys(patch).filter(k => before && before[k] !== patch[k]);
      if (changedKeys.length) {
        const beforeSnap = {}; const afterSnap = {};
        changedKeys.forEach(k => { beforeSnap[k] = before[k]; afterSnap[k] = patch[k]; });
        window.StagevoData.writeAudit('update', 'deals', id, beforeSnap, afterSnap).catch(() => {});
      }
    }
  };
  const [lostModal, setLostModal] = useState(null);
  const confirmLost = (reason, note) => {
    if (!lostModal) return;
    const { id, patch } = lostModal;
    const label = `Marked Lost — ${reason}${note ? ': ' + note : ''}`;
    setDeals(prev => prev.map(d => d.id === id ? {
      ...d, ...patch, stage: 'lost',
      lossReason: reason, lossNote: note || '',
      activities: [{ id: 'a_lost_' + Date.now(), type: 'note', label, date: data.TODAY, daysAgo: 0 }, ...(d.activities || [])],
      lastActivity: data.TODAY, lastActivityDaysAgo: 0,
    } : d));
    setLostModal(null);
    showToast('Deal marked Lost · reason recorded');
    const lostDeal = { ...(deals.find(x => x.id === id) || {}), ...patch, stage: 'lost', lossReason: reason };
    fireAutomations('deal_lost', { deal: lostDeal });
    // ── Audit: lost deal ──
    if (LIVE && window.StagevoData && window.StagevoData.writeAudit) {
      const before = deals.find(x => x.id === id);
      window.StagevoData.writeAudit('stage_change', 'deals', id,
        { stage: before ? before.stage : null },
        { stage: 'lost', lossReason: reason, lossNote: note || '' }
      ).catch(() => {});
    }
  };

  const bulkReassign = (dealIds, newRepId, note) => {
    const newRep = reps.find(r => r.id === newRepId);
    const today = data.TODAY;
    setDeals(prev => prev.map(d => {
      if (!dealIds.includes(d.id)) return d;
      const fromName = d.repName;
      const handoverLabel = `Reassigned from ${fromName} to ${newRep.name} by ${currentUser.name}${note ? ` — ${note}` : ''}`;
      const activity = {
        id: 'a' + Math.floor(Math.random() * 1e9),
        type: 'note',
        label: handoverLabel,
        date: today,
        daysAgo: 0,
      };
      const logEntry = {
        from: fromName,
        fromRepId: d.rep,
        to: newRep.name,
        toRepId: newRepId,
        changedBy: currentUser.name,
        changedByEmail: currentUser.email,
        date: today,
        reason: note || '',
      };
      return {
        ...d,
        rep: newRepId,
        repName: newRep.name,
        activities: [activity, ...(d.activities || [])],
        transferLog: [...(d.transferLog || []), logEntry],
        lastActivity: today,
        lastActivityDaysAgo: 0,
      };
    }));
    showToast(`Reassigned ${dealIds.length} deal${dealIds.length > 1 ? 's' : ''} to ${newRep.name}`);
    // ── Audit: reassignment ──
    if (LIVE && window.StagevoData && window.StagevoData.writeAudit) {
      dealIds.forEach(id => {
        const d = deals.find(x => x.id === id);
        window.StagevoData.writeAudit('reassign', 'deals', id,
          { rep: d ? d.repName : null },
          { rep: newRep.name, repId: newRepId, note: note || '' }
        ).catch(() => {});
      });
    }
  };

  const createDeal = (draft) => {
    // Quote-number generator: per-showroom sequence + customer last-name prefix
    // Format: BAY 51476 EDM
    const showroomCode = {
      bay: 'BAY', pak: 'PAK', che: 'CHE', cap: 'CAP',
    }[draft.showroom] || draft.showroom.toUpperCase().slice(0, 3);
    // Find highest quote number for THIS showroom
    let maxQuote = { bay: 56500, pak: 50000, che: 52000, cap: 54000 }[draft.showroom] || 50000;
    for (const d of deals) {
      if (d.showroom !== draft.showroom) continue;
      const m = String(d.quoteNumber || '').match(/(\d{4,6})/);
      if (m) {
        const n = parseInt(m[1], 10);
        if (n > maxQuote) maxQuote = n;
      }
    }
    const nextNum = maxQuote + 1;
    // Customer last-name prefix: take first 3 letters of last word in name (skip &)
    const cleanName = draft.customer.replace(/&/g, ' ').replace(/\s+/g, ' ').trim();
    const parts = cleanName.split(' ').filter(p => p.length > 0);
    const lastName = parts[parts.length - 1] || 'NEW';
    const namePrefix = lastName.toUpperCase().replace(/[^A-Z]/g, '').slice(0, 3).padEnd(3, 'X');
    const quoteNumber = `${showroomCode} ${nextNum} ${namePrefix}`;
    const id = quoteNumber; // ID matches the human-readable quote number
    const today = data.TODAY;
    const created = draft.designApptDate || today;
    const todayD = new Date(today);
    const createdD = new Date(created);
    const createdDaysAgo = Math.max(0, Math.floor((todayD - createdD) / 86400000));
    const newDeal = {
      id, quoteNumber: quoteNumber,
      showroom: draft.showroom,
      customer: draft.customer,
      phone: draft.phone || '',
      email: draft.email || null,
      suburb: draft.suburb || null,
      address: draft.address || null,
      product: draft.product,
      stage: draft.stage,
      statusRaw: draft.stage === 'possible' ? 'Possible Sales' : 'In Progress',
      value: draft.value || 0,
      rep: draft.rep, repName: draft.repName,
      onsiteVisitDone: draft.onsiteVisitDone || false,
      designApptDate: draft.designApptDate || null,
      quotePresentationDate: draft.quotePresentationDate || null,
      forecastDepositMonth: draft.forecastDepositMonth || null,
      budgetRange: draft.budgetRange || '',
      created, createdDaysAgo,
      lastActivity: today, lastActivityDaysAgo: 0,
      stageEnteredDaysAgo: 0,
      expectedClose: draft.forecastDepositMonth || created,
      notes: draft.notes || '',
      activities: [{
        id: 'a_new_' + Math.floor(Math.random() * 1e9),
        type: 'note',
        label: `Lead created by ${currentUser.name}${draft.notes ? ' — ' + draft.notes.slice(0, 80) : ''}`,
        date: today, daysAgo: 0,
      }],
      email: draft.email || null, suburb: draft.suburb || null, source: null, suppliers: [], install: false,
    };
    setDeals(prev => [newDeal, ...prev]);
    // If created from a contact, link the deal to that contact
    if (newDealContact) {
      setContacts(prev => prev.map(c => c.id === newDealContact.id
        ? { ...c, dealIds: [id, ...(c.dealIds || [])], dealCount: (c.dealCount || 0) + 1, lifecycle: 'in_pipeline', lastContact: today, daysSinceContact: 0 }
        : c
      ));
      setNewDealContact(null);
    }
    setOpenId(id);
    setNewDealOpen(false);
    showToast(`Created ${id} · ${draft.customer}`);
    fireAutomations('deal_created', { deal: newDeal });
  };

  const resetDemoData = () => {
    if (!confirm('Reset all deals back to the imported spreadsheet data? Any changes will be lost.')) return;
    try {
      localStorage.removeItem('kub_deals_v1');
      localStorage.removeItem('kub_contacts_v1');
      localStorage.removeItem('kub_users_v1');
      localStorage.removeItem('kub_reps_v1');
    } catch (e) {}
    setDeals(data.DEALS);
    setContacts(data.CONTACTS);
    setUsers(data.USERS);
    setReps(data.REPS);
    showToast('Reset to imported sheet data');
  };

  const openContact = (c) => setOpenContactId(c.id);
  const closeContact = () => setOpenContactId(null);
  const openContactObj = openContactId ? contacts.find(c => c.id === openContactId) : null;

  const createContact = (draft) => {
    const newId = 'C-' + String(Date.now()).slice(-5);
    const today = data.TODAY;
    const newContact = {
      id: newId,
      name: draft.name.trim(),
      phone: draft.phone || '',
      email: draft.email || '',
      suburb: draft.suburb || '',
      address: draft.address || '',
      homeShowroom: draft.homeShowroom,
      owner: draft.owner,
      firstContact: today,
      lastContact: today,
      lifecycle: draft.outcome === 'qualifies' ? 'qualified' : draft.outcome === 'no_sale' ? 'no_sale' : 'new_lead',
      daysSinceContact: 0,
      ltv: 0,
      totalQuoted: 0,
      dealIds: [],
      entries: [{
        date: today,
        means: draft.means,
        source: draft.source,
        outcome: draft.outcome,
        salesPerson: draft.owner,
        comments: draft.comments,
      }],
      primaryMeans: draft.means,
      primarySource: draft.source,
      contactCount: 1,
      dealCount: 0,
      reminder: null,
    };
    setContacts(prev => [newContact, ...prev]);
    setNewContactOpen(false);
    setOpenContactId(newId);
    showToast(`Added ${draft.name}`);
    fireAutomations('contact_created', { contact: newContact });
  };

  const editContact = (id, patch) => {
    setContacts(prev => prev.map(c => {
      if (c.id !== id) return c;
      const changed = Object.keys(patch).filter(k => c[k] !== patch[k] && k !== 'audit');
      const auditEntry = changed.length ? [{ date: data.TODAY, label: 'Updated ' + changed.join(', '), by: currentUser.name }] : [];
      return { ...c, ...patch, audit: [...auditEntry, ...(c.audit || [])] };
    }));
  };

  // Verified identity update (phone/email/name/address) — writes a detailed, immutable audit entry per field.
  const updateContactVerified = (id, patch, meta) => {
    const FIELD_LABEL = { name: 'Name', phone: 'Phone number', email: 'Email address', address: 'Address', suburb: 'Suburb' };
    setContacts(prev => prev.map(c => {
      if (c.id !== id) return c;
      const storeName = showroomsList.find(s => s.id === c.homeShowroom)?.name || c.homeShowroom;
      const stamp = new Date();
      const tsLabel = stamp.toLocaleString('en-AU', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' });
      const adds = [];
      ['name', 'phone', 'email', 'address', 'suburb'].forEach(f => {
        if (patch[f] !== undefined && (patch[f] || '') !== (c[f] || '')) {
          adds.push({
            date: stamp.toISOString().slice(0, 10),
            ts: tsLabel,
            field: FIELD_LABEL[f] || f,
            prev: c[f] || '—',
            next: patch[f] || '—',
            store: storeName,
            by: currentUser.name,
            byId: currentUser.id,
            method: meta && meta.methodLabel ? meta.methodLabel : 'Staff update',
            label: `${FIELD_LABEL[f] || f} changed from “${c[f] || '—'}” to “${patch[f] || '—'}”`,
          });
        }
      });
      return { ...c, ...patch, audit: [...adds, ...(c.audit || [])] };
    }));
    showToast('Contact updated · change verified & logged');
  };

  // Append an entry to a contact's audit trail — used everywhere the contact is touched
  const logContactAudit = (id, label, by) => {
    setContacts(prev => prev.map(c => c.id === id ? {
      ...c,
      audit: [{ date: data.TODAY, label, by: by || currentUser.name }, ...(c.audit || [])],
    } : c));
  };

  const mergeContacts = (sourceId, targetId) => {
    let merged = null;
    setContacts(prev => {
      const src = prev.find(c => c.id === sourceId);
      const tgt = prev.find(c => c.id === targetId);
      if (!src || !tgt) return prev;
      merged = {
        ...tgt,
        phone: tgt.phone || src.phone,
        email: tgt.email || src.email,
        suburb: tgt.suburb || src.suburb,
        owner: tgt.owner || src.owner,
        firstContact: src.firstContact && (!tgt.firstContact || src.firstContact < tgt.firstContact) ? src.firstContact : tgt.firstContact,
        lastContact: src.lastContact && (!tgt.lastContact || src.lastContact > tgt.lastContact) ? src.lastContact : tgt.lastContact,
        ltv: (tgt.ltv || 0) + (src.ltv || 0),
        totalQuoted: (tgt.totalQuoted || 0) + (src.totalQuoted || 0),
        dealIds: [...new Set([...(tgt.dealIds || []), ...(src.dealIds || [])])],
        entries: [...(tgt.entries || []), ...(src.entries || [])].sort((a, b) => (b.date || '').localeCompare(a.date || '')),
        dealCount: ((tgt.dealCount || 0) + (src.dealCount || 0)),
        mergeLog: [...(tgt.mergeLog || []), {
          mergedFromId: src.id,
          mergedFromName: src.name,
          date: data.TODAY,
          mergedBy: currentUser.name,
        }],
      };
      return prev.filter(c => c.id !== sourceId).map(c => c.id === targetId ? merged : c);
    });
    showToast(`Merged into ${merged?.name || 'contact'}`);
  };

  const reassignContact = (id, newOwnerName, reason) => {
    setContacts(prev => prev.map(c => {
      if (c.id !== id) return c;
      const fromName = c.owner || null;
      const logEntry = {
        from: fromName,
        to: newOwnerName,
        changedBy: currentUser.name,
        changedByEmail: currentUser.email,
        date: data.TODAY,
        reason: reason || '',
      };
      return {
        ...c,
        owner: newOwnerName,
        transferLog: [...(c.transferLog || []), logEntry],
        audit: [{ date: data.TODAY, label: `Owner changed ${fromName || 'unassigned'} → ${newOwnerName}${reason ? ' (' + reason + ')' : ''}`, by: currentUser.name }, ...(c.audit || [])],
      };
    }));
    showToast(`Reassigned to ${newOwnerName}`);
  };

  const setContactReminder = (id, reminderText, reminderDate, reminderType, reminderTime, leadMinutes) => {
    setContacts(prev => prev.map(c => {
      if (c.id !== id) return c;
      const label = reminderDate
        ? `${window.apptTypeLabel ? window.apptTypeLabel(reminderType) : 'Reminder'} set for ${reminderDate}${reminderTime ? ' at ' + reminderTime : ''}`
        : 'Reminder cleared';
      return {
        ...c,
        reminder: reminderDate ? { text: reminderText, date: reminderDate, type: reminderType || 'followup_call', time: reminderTime || '09:00', leadMinutes: leadMinutes != null ? leadMinutes : 15, setBy: currentUser.name, setAt: data.TODAY } : null,
        audit: [{ date: data.TODAY, label, by: currentUser.name }, ...(c.audit || [])],
      };
    }));
    if (reminderDate) {
      const label = window.apptTypeLabel ? window.apptTypeLabel(reminderType) : 'Reminder';
      showToast(`${label} scheduled for ${reminderDate}${reminderTime ? ' at ' + reminderTime : ''} — added to calendar`);
    } else {
      showToast('Reminder cleared');
    }
  };

  const [newDealContact, setNewDealContact] = useState(null);

  const showToast = (msg) => {
    setToast(msg);
    clearTimeout(window._toastTimer);
    window._toastTimer = setTimeout(() => setToast(null), 2200);
  };

  // Hard access lock — always restrict deals to allowed showrooms first
  const accessibleDeals = useMemo(() => {
    return deals.filter(d => allowedShowrooms.includes(d.showroom));
  }, [deals, allowedShowrooms]);

  // Then apply user-selected showroom filter
  const scopedDeals = useMemo(() => {
    let result = showroom === 'all' ? accessibleDeals : accessibleDeals.filter(d => d.showroom === showroom);
    // Sales designers: hard-restrict to their own deals (regardless of myDealsOnly)
    if (isSalesDesigner && currentUser.repId) {
      result = result.filter(d => d.rep === currentUser.repId);
    } else if (myDealsOnly && currentUser.repId) {
      result = result.filter(d => d.rep === currentUser.repId);
    }
    return result;
  }, [accessibleDeals, showroom, myDealsOnly, currentUser, isSalesDesigner]);

  // Apply FilterBar filters (rep, stage, dateRange, agingOnly) — used by Pipeline & Aging
  // (Deals applies these internally because of search)
  const filteredDeals = useMemo(() => {
    let result = scopedDeals;
    if (filters.rep !== 'all') result = result.filter(d => d.rep === filters.rep);
    if (filters.stage !== 'all') {
      if (filters.stage === 'active') result = result.filter(d => d.stage !== 'sold' && d.stage !== 'lost');
      else if (Array.isArray(filters.stage)) {
        if (filters.stage.length > 0) result = result.filter(d => filters.stage.includes(d.stage));
      }
      else result = result.filter(d => d.stage === filters.stage);
    }
    if (filters.dateRange !== 'all') {
      const max = parseInt(filters.dateRange, 10);
      result = result.filter(d => d.createdDaysAgo <= max);
    }
    if (filters.agingOnly) result = result.filter(d => d.lastActivityDaysAgo > 21 && d.stage !== 'sold' && d.stage !== 'lost');
    if (filters.pastForecast) {
      const today = '2026-05-28';
      result = result.filter(d => d.forecastDepositMonth && d.forecastDepositMonth < today && d.stage !== 'sold' && d.stage !== 'lost');
    }
    return result;
  }, [scopedDeals, filters]);

  // Contacts scoped the same way as deals (showroom + designer + date range) so the
  // ROI Calculator reacts to the global filters. Contacts carry homeShowroom + owner (rep name).
  const scopedContacts = useMemo(() => {
    let result = contacts.filter(c => allowedShowrooms.includes(c.homeShowroom));
    if (showroom !== 'all') result = result.filter(c => c.homeShowroom === showroom);
    if (isSalesDesigner && currentUser.repId) {
      const myName = (reps.find(r => r.id === currentUser.repId) || {}).name;
      result = result.filter(c => c.owner === myName);
    }
    if (filters.rep !== 'all') {
      const repName = (reps.find(r => r.id === filters.rep) || {}).name;
      result = result.filter(c => c.owner === repName);
    }
    if (filters.dateRange !== 'all') {
      const max = parseInt(filters.dateRange, 10);
      result = result.filter(c => (c.daysSinceContact != null ? c.daysSinceContact : 9999) <= max);
    }
    return result;
  }, [contacts, allowedShowrooms, showroom, isSalesDesigner, currentUser, reps, filters.rep, filters.dateRange]);

  // Reps in the current selection — drives seat count + admin-time in the ROI model.
  const scopedReps = useMemo(() => {
    if (isSalesDesigner && currentUser.repId) return reps.filter(r => r.id === currentUser.repId);
    if (filters.rep !== 'all') return reps.filter(r => r.id === filters.rep);
    if (showroom !== 'all') return reps.filter(r => (r.works || []).includes(showroom));
    return reps.filter(r => (r.works || []).some(w => allowedShowrooms.includes(w)));
  }, [reps, filters.rep, showroom, allowedShowrooms, isSalesDesigner, currentUser]);

  const counts = useMemo(() => ({
    total: scopedDeals.length,
    active: scopedDeals.filter(d => d.stage !== 'sold' && d.stage !== 'lost').length,
    stuck: scopedDeals.filter(d => d.stage !== 'sold' && d.stage !== 'lost' && d.lastActivityDaysAgo > 21).length,
  }), [scopedDeals]);

  // ===== AI assistant data — scoped strictly to the signed-in user's authority.
  // Independent of the transient showroom-switcher selection, but never exceeding
  // what this role is permitted to see (a designer only ever sees their own deals).
  const assistantDeals = useMemo(() => {
    if (isSalesDesigner && currentUser.repId) return accessibleDeals.filter(d => d.rep === currentUser.repId);
    return accessibleDeals;
  }, [accessibleDeals, isSalesDesigner, currentUser]);

  const assistantReps = useMemo(() => {
    if (isSalesDesigner && currentUser.repId) return reps.filter(r => r.id === currentUser.repId);
    if (isManager) return reps.filter(r => (r.works || []).some(w => allowedShowrooms.includes(w)));
    return reps; // leadership / admin
  }, [reps, isSalesDesigner, isManager, currentUser, allowedShowrooms]);

  const assistantShowrooms = useMemo(
    () => showroomsList.filter(s => allowedShowrooms.includes(s.id)),
    [showroomsList, allowedShowrooms]
  );

  const assistantAccess = useMemo(() => {
    const names = assistantShowrooms.map(s => s.name);
    const roleLabel = isAdmin ? 'Admin' : role === 'leadership' ? 'Leadership' : isManager ? 'Showroom Manager' : 'Sales Designer';
    let scopeText;
    if (isAdmin || role === 'leadership') {
      scopeText = 'all showrooms, every sales designer, and all company-wide data';
    } else if (isManager) {
      scopeText = `only the showroom(s) they manage (${names.join(', ')}) and the designers working there — not other showrooms or company-wide figures`;
    } else {
      scopeText = `only their own deals and personal performance at ${names.join(', ') || 'their showroom'} — not other designers' deals, quotas, or showroom/company totals`;
    }
    return { roleLabel, scopeText, canSeeAll: isAdmin || role === 'leadership', isManager, isSalesDesigner };
  }, [assistantShowrooms, isAdmin, role, isManager, isSalesDesigner]);

  const currentShowroom = showroomsList.find(s => s.id === showroom);
  const T = window.T || ((k) => k);
  const pageMeta = {
    overview: { title: 'Pipeline overview', sub: 'Health, trends, and what needs attention' },
    pipeline: { title: 'Pipeline board', sub: 'Drag deals between stages · click for detail' },
    deals: { title: 'All deals', sub: 'Sortable, filterable register · inline edits supported' },
    aging: { title: 'Aging & stuck deals', sub: 'Surface deals that need a nudge' },
    reps: { title: 'Sales ' + T('reps'), sub: 'Leaderboard, win rate, and quota progress' },
    showrooms: { title: T('locations'), sub: 'Compare performance across all ' + T('locs_lc') },
    forecast: { title: 'Forecast', sub: 'Weighted pipeline and 3-month projection' },
    roi: { title: 'ROI Calculator', sub: 'What the CRM is worth in dollars · reacts to your filters' },
    calendar: { title: 'Calendar', sub: 'Schedule appointments, site visits, and follow-ups' },
    contacts: { title: 'Contacts', sub: 'Every person who has ever reached out to KUB' },
    reports: { title: 'Reports', sub: 'Marketing performance · sales analytics · activity insights' },
    admin: { title: 'Team & access', sub: 'Manage logins, roles, and showroom assignments' },
    targets: { title: 'Targets', sub: 'Set monthly sales targets for each designer' },
    manage_showrooms: { title: 'Manage ' + (window.T ? window.T('locations').toLowerCase() : 'showrooms'), sub: 'Add and edit ' + (window.T ? window.T('locs_lc') : 'showroom') + ' locations' },
    workspace: { title: 'Workspace & industry', sub: 'Tailor Pearler\u2019s terminology to your business' },
    company_kpis: { title: 'Company KPIs', sub: 'Track custom metrics specific to your business' },
    automations: { title: 'Automations', sub: 'Workflow rules that run automatically on pipeline events' },
    settings: { title: 'Settings', sub: 'Organisation, access, security, notifications, integrations & API' },
    profile: { title: 'My Settings', sub: 'Your personal preferences · theme, security & notifications' },
    kpi_admin: { title: 'KPIs & Targets', sub: 'Define KPIs and set per-designer monthly targets · with locking & audit' },
    import: { title: 'Data import', sub: 'Upload spreadsheet files · super-admin only' },
    help: { title: 'Help & Guide', sub: 'A five-minute tour of how Pearler works' },
    ai_hub: { title: 'AI Intelligence', sub: 'Deal scoring, win probability and next best actions — powered by AI' },
    comms: { title: 'Comms Hub', sub: 'All conversations — email, SMS and calls — linked to every deal' },
    quotes: { title: 'Quote Builder', sub: 'Build, preview and send proposals directly from any deal' },
    analytics_plus: { title: 'Analytics+', sub: 'Source attribution, cohort analysis, deal velocity and win/loss breakdown' },
    customer_portal: { title: 'Customer Portal', sub: 'Your customers\u2019 live view of their project — stages, documents and sign-off' },
    mobile_app: { title: 'Mobile Field App', sub: 'GPS check-in, site visits and quick deal updates from anywhere' },
    super_admin: { title: '⚡ Platform Admin', sub: 'Super admin — all organisations, billing, audit log and platform health' },
    audit_log: { title: 'Audit Log', sub: 'Immutable record of every sensitive action in your organisation' },
    compliance: { title: 'Compliance', sub: 'Data security, erasure requests, retention policies and data residency' },
  }[view] || { title: view, sub: '' };
  const scopedSub = showroom === 'all' || view === 'showrooms' || view === 'help' || view === 'profile' || view === 'super_admin'
    ? pageMeta.sub
    : `${pageMeta.sub} · ${currentShowroom.name} only`;

  const [navOpen, setNavOpen] = useState(false);

  return (
    <div className={"app" + (navOpen ? " nav-open" : "")}>
      <div className="nav-scrim" onClick={() => setNavOpen(false)}></div>
      <Sidebar
        view={view}
        setView={(v) => { setView(v); setNavOpen(false); }}
        counts={counts}
        currentUser={currentUser}
        users={users}
        setCurrentUserId={setCurrentUserId}
        isLeadership={isLeadership}
        isAdmin={isAdmin}
        role={role}
        accessMatrix={accessMatrix}
        onReset={resetDemoData}
      />
      <div className="main">
        <header className="topbar">
          <button className="nav-toggle" aria-label="Open menu" onClick={() => setNavOpen(o => !o)}>
            <Icon name="menu" size={18}/>
          </button>
          <div>
            <div className="page-title">{pageMeta.title}</div>
            <div className="page-sub">{scopedSub}</div>
          </div>
          {!isLeadership && (
            <div className="access-banner">
              <Icon name="alert" size={12}/>
              <span>Restricted view · you can only see deals from <strong>{currentUser.works.map(id => showroomsList.find(s => s.id === id).name).join(' & ')}</strong></span>
            </div>
          )}
          <div className="topbar-search">
            <span className="search-icon"><Icon name="search" size={13}/></span>
            <input
              placeholder="Search deals, customers, suburbs, reps…"
              value={search}
              onChange={(e) => {
                setSearch(e.target.value);
                if (e.target.value && view !== 'deals' && view !== 'contacts') setView('deals');
              }}
            />
          </div>
          <div className="topbar-actions">
            {isLeadership && (
              <button
                className="btn"
                onClick={() => downloadCSV('full-export-' + new Date().toISOString().slice(0, 10) + '.csv', scopedDeals, [
                  { key: 'id', label: 'Deal' },
                  { key: 'customer', label: 'Customer' },
                  { key: 'stage', label: 'Stage', format: (v) => data.STAGES.find(s => s.id === v)?.label || v },
                  { key: 'value', label: 'Value AUD' },
                  { key: 'showroom', label: 'Showroom', format: (v) => showroomsList.find(s => s.id === v)?.name || v },
                  { key: 'repName', label: 'Designer' },
                  { key: 'created', label: 'Created' },
                  { key: 'lastActivity', label: 'Last activity' },
                ])}
              >
                <Icon name="download" size={12}/> Export CSV
              </button>
            )}
            <button className="btn" onClick={() => setNewContactOpen(true)}><Icon name="plus" size={12}/> New contact</button>
            <button className="btn primary" onClick={() => setNewDealOpen(true)}><Icon name="plus" size={12}/> New lead</button>
          </div>
        </header>

        {view !== 'showrooms' && view !== 'help' && view !== 'super_admin' && (
          <ShowroomSwitcher
            showrooms={showroomsList}
            value={showroom}
            onChange={setShowroom}
            deals={accessibleDeals}
            allowedShowrooms={allowedShowrooms}
            allowAllOption={isLeadership || allowedShowrooms.length > 1}
            isLeadership={isLeadership}
          />
        )}

        {(view === 'deals' || view === 'pipeline' || view === 'aging' || view === 'forecast' || view === 'roi' || (view === 'overview' && isLeadership)) && (
          <FiltersBar
            filters={filters}
            setFilters={setFilters}
            view={view}
            stages={data.STAGES}
            reps={reps}
            showroom={showroom}
            currentUser={currentUser}
            myDealsOnly={myDealsOnly}
            setMyDealsOnly={setMyDealsOnly}
          />
        )}

        <div className={`canvas ${view === 'pipeline' ? 'no-pad' : ''}`}>
          {view === 'overview' && (
            <Overview
              deals={filteredDeals}
              stages={data.STAGES}
              reps={reps}
              months={data.MONTHS}
              openDeal={openDeal}
              filters={filters}
              setFilters={setFilters}
              showroomScope={currentShowroom}
            />
          )}
          {view === 'pipeline' && (
            <div>
              <PipelineAlerts deals={filteredDeals} currentUser={currentUser} openDeal={openDeal}/>
              <Pipeline
                deals={filteredDeals}
                stages={data.STAGES}
                openDeal={openDeal}
                onMoveStage={(id, stage) => editDeal(id, { stage })}
                onBulkReassign={bulkReassign}
                onEditDeal={editDeal}
                repFilter={filters.rep}
                setRepFilter={(rep) => setFilters({ ...filters, rep })}
                reps={reps}
                showrooms={showroomsList}
                canReassign={isLeadership || isManager}
              />
            </div>
          )}
          {view === 'deals' && (
            <Deals
              deals={filteredDeals}
              stages={data.STAGES}
              reps={reps}
              showrooms={showroomsList}
              openDeal={openDeal}
              onEditDeal={editDeal}
              onBulkReassign={bulkReassign}
              onNewDeal={() => setNewDealOpen(true)}
              filters={filters}
              setFilters={setFilters}
              search={search}
              showroomScope={showroom}
              isLeadership={isLeadership}
            />
          )}
          {view === 'aging' && (
            <Aging
              deals={filteredDeals}
              stages={data.STAGES}
              reps={reps}
              openDeal={openDeal}
              filters={filters}
            />
          )}
          {view === 'reps' && (
            <Reps
              deals={scopedDeals}
              allDeals={accessibleDeals}
              reps={reps}
              stages={data.STAGES}
              showrooms={showroomsList}
              showroomScope={showroom}
              targets={targets}
              openDeal={openDeal}
            />
          )}
          {view === 'showrooms' && canAccess('showrooms') && (
            <Showrooms
              deals={deals}
              showrooms={showroomsList}
              reps={reps}
              stages={data.STAGES}
              openDeal={openDeal}
              setShowroom={(id) => { setShowroom(id); setView('overview'); }}
            />
          )}
          {view === 'admin' && canAccess('admin') && (
            <Admin
              users={users}
              showrooms={showroomsList}
              accessMatrix={accessMatrix}
              onSetAccess={setRoleAccess}
              currentUser={currentUser}
              onUpdateUser={updateUser}
              onInviteUser={inviteUser}
              onDeleteUser={deleteUser}
              onResendInvite={resendInvite}
            />
          )}
          {view === 'import' && currentUser.superAdmin && canAccess('import') && (
            <DataImport
              currentUser={currentUser}
              users={users}
              showrooms={showroomsList}
              onTransferAdmin={(newId) => {
                setUsers(prev => prev.map(u => {
                  if (u.id === currentUser.id) return { ...u, superAdmin: false };
                  if (u.id === newId) return { ...u, superAdmin: true };
                  return u;
                }));
                showToast(`Super-admin transferred to ${users.find(u => u.id === newId)?.name}`);
              }}
              onImport={(summary) => {
                showToast(`Imported ${summary.length} file${summary.length > 1 ? 's' : ''}`);
              }}
            />
          )}
          {view === 'forecast' && (
            <Forecast
              deals={filteredDeals}
              allDeals={accessibleDeals}
              stages={data.STAGES}
              reps={reps}
              showrooms={showroomsList}
              months={data.MONTHS}
              targets={targets}
              currentUser={currentUser}
              showroomScope={showroom}
              filters={filters}
            />
          )}
          {view === 'roi' && canAccess('roi') && (
            <ROI
              deals={filteredDeals}
              contacts={scopedContacts}
              reps={scopedReps}
              stages={data.STAGES}
              targets={targets}
              plan={roiPlan}
              onChangePlan={setRoiPlan}
              hourlyRate={roiRate}
              onChangeHourlyRate={setRoiRate}
              scopeLabel={showroom === 'all' ? null : (currentShowroom && currentShowroom.name)}
            />
          )}
          {view === 'calendar' && (
            <Calendar
              tasks={tasks}
              deals={deals}
              contacts={contacts}
              reps={reps}
              currentUser={currentUser}
              showroomScope={showroom}
              allowedShowrooms={allowedShowrooms}
              onCreateTask={(t) => {
                setTasks(prev => [{ ...t, id: 'task_' + Date.now(), createdBy: currentUser.name }, ...prev]);
                if (t.contactId) {
                  const label = (window.apptTypeLabel ? window.apptTypeLabel(t.type) : 'Appointment') + ' booked for ' + t.date + (t.time ? ' at ' + t.time : '');
                  logContactAudit(t.contactId, label, currentUser.name);
                }
              }}
              onUpdateTask={(id, t) => {
                setTasks(prev => prev.map(x => x.id === id ? { ...x, ...t } : x));
                if (t.contactId) logContactAudit(t.contactId, 'Appointment updated: ' + (t.title || ''), currentUser.name);
              }}
              onDeleteTask={(id) => setTasks(prev => prev.filter(x => x.id !== id))}
              onOpenDeal={(d) => openDeal(d)}
              onOpenContact={(c) => setOpenContactId(c.id)}
            />
          )}
          {view === 'workspace' && canAccess('workspace') && (
            <WorkspaceSettings
              onApply={() => { setTermsVersion(v => v + 1); showToast('Terminology updated across workspace'); }}
            />
          )}
          {view === 'settings' && canAccess('settings') && (
            <Settings
              settings={settings}
              onChange={updateSetting}
              accessMatrix={accessMatrix}
              onSetAccess={setRoleAccess}
              currentUser={currentUser}
              showToast={showToast}
            />
          )}
          {view === 'automations' && canAccess('automations') && (
            <Automations
              automations={automations}
              log={automationLog}
              reps={reps}
              showrooms={showroomsList}
              currentUser={currentUser}
              onSave={saveAutomation}
              onDelete={deleteAutomation}
              onToggle={toggleAutomation}
            />
          )}
          {view === 'company_kpis' && (
            <CustomKPIs currentUser={currentUser} deals={accessibleDeals} contacts={contacts} stages={data.STAGES} showrooms={showroomsList} reps={reps} targets={targets} kpis={kpiDefs} kpiTargets={kpiTargets} allowedShowrooms={allowedShowrooms} globalShowroom={showroom}/>
          )}
          {view === 'kpi_admin' && canAccess('kpi_admin') && (
            <KPIAdmin
              kpis={kpiDefs}
              kpiTargets={kpiTargets}
              kpiLocks={kpiLocks}
              kpiAudit={kpiAudit}
              reps={reps}
              showrooms={showroomsList}
              currentUser={currentUser}
              onSaveKpiDef={saveKpiDef}
              onDeleteKpiDef={deleteKpiDef}
              onSetTarget={setKpiTargetCell}
              onToggleLock={toggleKpiLock}
            />
          )}
          {view === 'manage_showrooms' && canAccess('manage_showrooms') && (
            <ShowroomsAdmin
              showrooms={showroomsList}
              reps={reps}
              deals={deals}
              users={users}
              onSave={(next) => { setShowroomsList(next); showToast('Showrooms updated'); }}
            />
          )}
          {view === 'targets' && canAccess('targets') && (
            <TargetsAdmin
              reps={reps}
              currentUser={currentUser}
              targets={targets}
              onSave={(newTargets) => { setTargets(newTargets); showToast('Targets saved'); }}
            />
          )}
          {view === 'reports' && (
            <Reports
              contacts={contacts}
              deals={deals}
              reps={reps}
              showrooms={showroomsList}
              currentUser={currentUser}
              showroomScope={showroom}
              openDeal={openDeal}
            />
          )}
          {view === 'help' && (
            <Help currentUser={currentUser} setView={setView}/>
          )}
          {view === 'audit_log' && canAccess('audit_log') && typeof AuditLog !== 'undefined' && (
            <AuditLog currentUser={currentUser} showToast={showToast} />
          )}
          {view === 'compliance' && canAccess('compliance') && typeof Compliance !== 'undefined' && (
            <Compliance currentUser={currentUser} contacts={contacts} deals={filteredDeals} showToast={showToast} />
          )}
          {view === 'ai_hub' && canAccess('ai_hub') && typeof AIHub !== 'undefined' && (
            <AIHub deals={scopedDeals} reps={reps} contacts={contacts} showToast={showToast} />
          )}
          {view === 'comms' && canAccess('comms') && typeof CommsHub !== 'undefined' && (
            <CommsHub contacts={contacts} deals={scopedDeals} showToast={showToast} />
          )}
          {view === 'quotes' && canAccess('quotes') && typeof QuoteBuilder !== 'undefined' && (
            <QuoteBuilder deals={scopedDeals} contacts={contacts} reps={reps} showToast={showToast} />
          )}
          {view === 'analytics_plus' && canAccess('analytics_plus') && typeof AnalyticsPlus !== 'undefined' && (
            <AnalyticsPlus deals={scopedDeals} reps={reps} contacts={contacts} showToast={showToast} />
          )}
          {view === 'customer_portal' && canAccess('customer_portal') && typeof CustomerPortal !== 'undefined' && (
            <CustomerPortal deals={scopedDeals} contacts={contacts} showToast={showToast} />
          )}
          {view === 'mobile_app' && canAccess('mobile_app') && typeof MobileApp !== 'undefined' && (
            <MobileApp deals={scopedDeals} contacts={contacts} reps={reps} showToast={showToast} />
          )}
          {view === 'super_admin' && typeof SuperAdmin !== 'undefined' && (
            <SuperAdmin currentUser={currentUser} deals={scopedDeals} contacts={contacts} reps={reps} showToast={showToast} />
          )}
          {view === 'profile' && typeof MyProfile !== 'undefined' && (
            <MyProfile
              currentUser={currentUser}
              onUpdateUser={updateUser}
              showToast={showToast}
              onThemeChange={(t) => setUserThemeOverride(t)}
            />
          )}
          {view === 'contacts' && (
            <Contacts
              contacts={contacts}
              deals={deals}
              reps={reps}
              showrooms={showroomsList}
              showroomScope={showroom}
              currentUser={currentUser}
              openContact={openContact}
              openDeal={openDeal}
              onNewContact={() => setNewContactOpen(true)}
              onClaimLead={(id) => {
                const me = reps.find(r => r.id === currentUser.repId)?.name || currentUser.name;
                setContacts(prev => prev.map(c => c.id === id ? {
                  ...c,
                  owner: me,
                  inbound: false,
                  lastContact: data.TODAY,
                  transferLog: [...(c.transferLog || []), { from: null, to: me, changedBy: currentUser.name, date: data.TODAY, reason: 'Claimed inbound lead' }],
                } : c));
                showToast('Lead claimed — it\'s yours now');
              }}
              externalSearch={search}
            />
          )}
        </div>
      </div>

      {openDealObj && (
        <DealDetail
          deal={openDealObj}
          stages={data.STAGES}
          reps={reps}
          showrooms={showroomsList}
          onClose={closeDeal}
          onEdit={editDeal}
          isLeadership={isLeadership}
        />
      )}

      {openContactObj && (
        <ContactDetail
          contact={openContactObj}
          contacts={contacts}
          deals={deals}
          reps={reps}
          tasks={tasks}
          showrooms={showroomsList}
          onClose={closeContact}
          openDeal={(d) => { closeContact(); openDeal(d); }}
          onNewDeal={(c) => { setNewDealContact(c); setNewDealOpen(true); }}
          onEditContact={editContact}
          onSetReminder={setContactReminder}
          onReassignContact={reassignContact}
          onMergeContacts={mergeContacts}
          onVerifiedUpdate={updateContactVerified}
          siteVisits={siteVisits.filter(sv => sv.contactId === openContactObj.id)}
          onSaveSiteVisit={saveSiteVisit}
          currentUser={currentUser}
          isLeadership={isLeadership}
        />
      )}

      {newContactOpen && (
        <NewContactModal
          reps={reps}
          showrooms={showroomsList}
          currentUser={currentUser}
          defaultShowroom={showroom}
          allContacts={contacts}
          allDeals={deals}
          onClose={() => setNewContactOpen(false)}
          onCreate={createContact}
          onOpenContact={(c) => setOpenContactId(c.id)}
        />
      )}

      {newDealOpen && (
        <NewDealModal
          stages={data.STAGES}
          reps={reps}
          showrooms={showroomsList}
          currentUser={currentUser}
          defaultShowroom={showroom}
          prefillContact={newDealContact}
          allContacts={contacts}
          allDeals={deals}
          onClose={() => { setNewDealOpen(false); setNewDealContact(null); }}
          onCreate={createDeal}
        />
      )}

      {lostModal && (
        <LostReasonModal
          deal={deals.find(d => d.id === lostModal.id)}
          onClose={() => setLostModal(null)}
          onConfirm={confirmLost}
        />
      )}

      {toast && <div className="toast">{toast}</div>}

      <ReminderCenter
        contacts={contacts}
        deals={deals}
        tasks={tasks}
        reps={reps}
        onOpenContact={(c) => setOpenContactId(c.id)}
        onOpenDeal={(d) => setOpenId(d.id)}
      />

      <Assistant
        deals={assistantDeals}
        showrooms={assistantShowrooms}
        reps={assistantReps}
        stages={data.STAGES}
        contacts={contacts}
        currentUser={currentUser}
        access={assistantAccess}
        view={view}
        counts={counts}
      />

      <TweaksUI tweaks={tweaks} setTweak={setTweak} theme={effectiveTheme} setTheme={(t) => updateSetting('appearance', 'theme', t)}/>
    </div>
  );
}

function ShowroomSwitcher({ showrooms, value, onChange, deals, allowedShowrooms, allowAllOption, isLeadership }) {
  const T = window.T || ((k) => k);
  const totals = useMemo(() => {
    const all = deals.length;
    const byShow = {};
    showrooms.forEach(s => {
      byShow[s.id] = deals.filter(d => d.showroom === s.id).length;
    });
    return { all, byShow };
  }, [deals, showrooms]);

  const accessibleShowrooms = showrooms.filter(s => allowedShowrooms.includes(s.id));

  // When there are many locations, a tile strip gets unwieldy — switch to a compact dropdown
  if (accessibleShowrooms.length > 5) {
    return (
      <div className="showroom-switcher">
        <span className="ss-label">{T('location')}</span>
        <select className="sel" value={value} onChange={(e) => onChange(e.target.value)} style={{ minWidth: 220, fontWeight: 500 }}>
          {allowAllOption && <option value="all">{isLeadership ? `All ${T('locs_lc')}` : `All my ${T('locs_lc')}`} ({totals.all})</option>}
          {accessibleShowrooms.map(s => (
            <option key={s.id} value={s.id}>{s.name} · {s.state} ({totals.byShow[s.id]})</option>
          ))}
        </select>
        {!isLeadership && showrooms.length > accessibleShowrooms.length && (
          <span className="ss-locked"><Icon name="alert" size={11}/>{showrooms.length - accessibleShowrooms.length} hidden</span>
        )}
      </div>
    );
  }

  return (
    <div className="showroom-switcher">
      <span className="ss-label">{T('location')}</span>
      {allowAllOption && (
        <button
          className={`ss-tab ${value === 'all' ? 'on' : ''}`}
          onClick={() => onChange('all')}
        >
          <span className="ss-tab-dot all"></span>
          {isLeadership ? `All ${T('locs_lc')}` : `All my ${T('locs_lc')}`}
          <span className="ss-tab-count num">{totals.all}</span>
        </button>
      )}
      {accessibleShowrooms.map(s => (
        <button
          key={s.id}
          className={`ss-tab ${value === s.id ? 'on' : ''}`}
          onClick={() => onChange(s.id)}
        >
          <span className={`ss-tab-dot ${s.id}`}></span>
          {s.name}
          <span className="ss-tab-state">{s.state}</span>
          <span className="ss-tab-count num">{totals.byShow[s.id]}</span>
        </button>
      ))}
      {!isLeadership && showrooms.length > accessibleShowrooms.length && (
        <span className="ss-locked">
          <Icon name="alert" size={11}/>
          {showrooms.length - accessibleShowrooms.length} showrooms hidden
        </span>
      )}
    </div>
  );
}

function FiltersBar({ filters, setFilters, view, stages, reps, showroom, currentUser, myDealsOnly, setMyDealsOnly }) {
  const stageActive = Array.isArray(filters.stage) ? filters.stage.length > 0 : filters.stage !== 'all';
  const active = stageActive || filters.rep !== 'all' || filters.dateRange !== 'all' || filters.agingOnly || filters.pastForecast || myDealsOnly;
  const repOptions = showroom === 'all' ? reps : reps.filter(r => r.works.includes(showroom));
  return (
    <div className="filters-bar">
      <Icon name="filter" size={13} style={{ color: 'var(--ink-3)' }}/>
      {currentUser.role === 'sales_designer' && (
        <button
          className={`filter-chip ${myDealsOnly ? 'active' : ''}`}
          onClick={() => setMyDealsOnly(!myDealsOnly)}
        >
          <Icon name="users" size={11}/>
          My deals only
          {myDealsOnly && <span className="x">×</span>}
        </button>
      )}
      <select className="sel" value={filters.rep} onChange={(e) => setFilters({ ...filters, rep: e.target.value })}>
        <option value="all">All reps{showroom !== 'all' ? ` at this showroom` : ''}</option>
        {repOptions.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
      </select>
      {view !== 'pipeline' && (
        <StageMultiFilter
          stages={stages}
          value={filters.stage}
          onChange={(v) => setFilters({ ...filters, stage: v })}
        />
      )}
      <select className="sel" value={filters.dateRange} onChange={(e) => setFilters({ ...filters, dateRange: e.target.value })}>
        <option value="all">Any age</option>
        <option value="7">Last 7 days</option>
        <option value="30">Last 30 days</option>
        <option value="90">Last 90 days</option>
      </select>
      <button
        className={`filter-chip ${filters.pastForecast ? 'active' : ''}`}
        onClick={() => setFilters({ ...filters, pastForecast: !filters.pastForecast })}
        style={filters.pastForecast ? null : { color: 'var(--lost)', borderColor: 'var(--lost-soft)' }}
      >
        <Icon name="alert" size={11}/>
        Past forecast
        {filters.pastForecast && <span className="x">×</span>}
      </button>
      <button
        className={`filter-chip ${filters.agingOnly ? 'active' : ''}`}
        onClick={() => setFilters({ ...filters, agingOnly: !filters.agingOnly })}
      >
        <Icon name="clock" size={11}/>
        Stuck (>21d)
        {filters.agingOnly && <span className="x">×</span>}
      </button>
      {active && (
        <button
          className="filter-chip"
          onClick={() => { setFilters({ stage: 'all', rep: 'all', dateRange: 'all', agingOnly: false, pastForecast: false }); setMyDealsOnly(false); }}
          style={{ marginLeft: 4 }}
        >
          Clear filters
        </button>
      )}
    </div>
  );
}

function TweaksUI({ tweaks, setTweak, theme, setTheme }) {
  const themes = window.THEMES || [];
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection title="Theme">
        <TweakSelect
          label="Colour theme"
          value={theme}
          onChange={(v) => setTheme(v)}
          options={themes.map((t) => ({ value: t.id, label: t.name }))}
        />
      </TweakSection>
      <TweakSection title="Display">
        <TweakRadio
          label="Density"
          value={tweaks.density}
          onChange={(v) => setTweak('density', v)}
          options={[{ value: 'comfortable', label: 'Comfortable' }, { value: 'compact', label: 'Compact' }]}
        />
        <TweakToggle
          label="Highlight stuck deals"
          value={tweaks.highlightStuck}
          onChange={(v) => setTweak('highlightStuck', v)}
        />
        <TweakToggle
          label="Show charts"
          value={tweaks.showCharts}
          onChange={(v) => setTweak('showCharts', v)}
        />
      </TweakSection>
      <TweakSection title="Locale">
        <TweakRadio
          label="Currency"
          value={tweaks.currency}
          onChange={(v) => setTweak('currency', v)}
          options={[{ value: 'AUD', label: 'AUD' }, { value: 'USD', label: 'USD' }, { value: 'NZD', label: 'NZD' }]}
        />
      </TweakSection>
    </TweaksPanel>
  );
}

function oklchFromHex(hex, l = null, c = null) {
  const r = parseInt(hex.slice(1, 3), 16) / 255;
  const g = parseInt(hex.slice(3, 5), 16) / 255;
  const b = parseInt(hex.slice(5, 7), 16) / 255;
  if (l == null && c == null) return hex;
  if (l > 0.6) {
    const nr = Math.round((r + (1 - r) * 0.85) * 255);
    const ng = Math.round((g + (1 - g) * 0.85) * 255);
    const nb = Math.round((b + (1 - b) * 0.85) * 255);
    return `rgb(${nr}, ${ng}, ${nb})`;
  } else {
    return `rgb(${Math.round(r * 200)}, ${Math.round(g * 200)}, ${Math.round(b * 200)})`;
  }
}

const rootEl = document.getElementById('root');

class RootBoundary extends React.Component {
  constructor(p) { super(p); this.state = { err: null }; }
  static getDerivedStateFromError(e) { return { err: e }; }
  componentDidCatch(e, info) { console.error('App crashed:', e, info); }
  render() {
    if (this.state.err) {
      return (
        <div className="auth-wrap">
          <div className="auth-card" style={{ textAlign: 'center' }}>
            <h1 className="auth-title">Something went wrong</h1>
            <p className="auth-sub">This view hit an error while loading. Reloading usually fixes it.</p>
            <div style={{ fontSize: 11, color: 'var(--ink-4)', fontFamily: 'monospace', background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 8, padding: '8px 10px', margin: '0 0 14px', wordBreak: 'break-word', textAlign: 'left' }}>
              {String(this.state.err && (this.state.err.message || this.state.err))}
            </div>
            <div style={{ display: 'flex', gap: 8 }}>
              <button className="btn primary" style={{ flex: 1, height: 40, justifyContent: 'center' }} onClick={() => location.reload()}>Reload</button>
              <button className="btn" style={{ flex: 1, height: 40, justifyContent: 'center' }} onClick={() => (window.StagevoSignOut ? window.StagevoSignOut() : location.reload())}>Sign out</button>
            </div>
          </div>
        </div>
      );
    }
    return this.props.children;
  }
}

ReactDOM.createRoot(rootEl).render(<RootBoundary><AuthGate/></RootBoundary>);

function StageMultiFilter({ stages, value, onChange }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, []);

  const selected = Array.isArray(value) ? value : (value === 'all' ? [] : value === 'active' ? stages.filter(s => s.id !== 'sold' && s.id !== 'lost').map(s => s.id) : [value]);

  const toggle = (id) => {
    const next = selected.includes(id) ? selected.filter(x => x !== id) : [...selected, id];
    onChange(next.length === 0 ? 'all' : next);
  };

  const label = !Array.isArray(value) && value === 'all'
    ? 'All stages'
    : !Array.isArray(value) && value === 'active'
      ? 'Active only'
      : selected.length === 0 ? 'All stages'
        : selected.length === 1 ? (stages.find(s => s.id === selected[0])?.label || selected[0])
          : `${selected.length} stages`;

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button
        className="sel"
        onClick={() => setOpen(o => !o)}
        style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 6, padding: '5px 26px 5px 10px', fontSize: 12, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 6 }}
      >
        {label}
        {selected.length > 1 && <span style={{ background: 'var(--ink)', color: 'white', borderRadius: 8, padding: '0 5px', fontSize: 10 }}>{selected.length}</span>}
      </button>
      {open && (
        <div style={{
          position: 'absolute', top: '100%', left: 0, marginTop: 4,
          background: 'var(--surface)', border: '1px solid var(--border)',
          borderRadius: 6, boxShadow: 'var(--shadow-md)', zIndex: 20,
          minWidth: 200, padding: 4,
        }}>
          <button
            style={{ display: 'block', width: '100%', textAlign: 'left', padding: '6px 10px', fontSize: 12, background: 'none', border: 'none', cursor: 'pointer', borderRadius: 4 }}
            onClick={() => { onChange('all'); setOpen(false); }}
          >All stages</button>
          <button
            style={{ display: 'block', width: '100%', textAlign: 'left', padding: '6px 10px', fontSize: 12, background: 'none', border: 'none', cursor: 'pointer', borderRadius: 4 }}
            onClick={() => { onChange('active'); setOpen(false); }}
          >Active only (excludes Sold + Lost)</button>
          <div style={{ borderTop: '1px solid var(--border)', margin: '4px 0' }}/>
          {stages.map(s => (
            <label
              key={s.id}
              style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px', fontSize: 12, cursor: 'pointer', borderRadius: 4 }}
              onMouseEnter={(e) => e.currentTarget.style.background = 'var(--surface-2)'}
              onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
            >
              <input
                type="checkbox"
                checked={selected.includes(s.id)}
                onChange={() => toggle(s.id)}
                style={{ margin: 0 }}
              />
              <span style={{ width: 8, height: 8, borderRadius: 4, background: `var(--s-${s.id})`, display: 'inline-block' }}></span>
              {s.label}
            </label>
          ))}
        </div>
      )}
    </div>
  );
}
