/* global React, ReactDOM */
const { useState, useEffect, useMemo, useRef } = React;

// =============================================================================
// DEMO MODE — append ?demo=1 to URL. Stubs all /api/* calls with seeded data
// so the admin can be previewed with no backend. Production: do not pass flag.
// =============================================================================
if (location.search.indexOf('demo=1') !== -1) {
  const demoUser = { id: 1, name: 'Anita (Demo)', email: 'demo@lacquerynails.in', role: 'admin', permissions: [] };
  const today = new Date().toISOString().slice(0, 10);
  const tomorrow = new Date(Date.now() + 86400000).toISOString().slice(0, 10);
  const minusDays = (n) => new Date(Date.now() - n * 86400000).toISOString().slice(0, 19).replace('T', ' ');

  const STUBS = {
    '/csrf': { csrf: 'demo-token', user: demoUser },

    '/dashboard': {
      stats: {
        today_appointments: 6,
        pending_orders: 3,
        refill_due: 4,
        low_stock: 2,
        total_clients: 142,
        completed_this_week: 23,
      },
      upcoming: [
        { id: 101, name: 'Ananya Reddy',  mobile: '+91 98765 43210', service: 'Gel Polish',     appt_date: today,    appt_time: '11:00 AM', status: 'confirmed' },
        { id: 102, name: 'Priya Sharma',  mobile: '+91 99876 54321', service: 'Gel Extensions', appt_date: today,    appt_time: '2:30 PM',  status: 'pending'   },
        { id: 103, name: 'Meera Iyer',    mobile: '+91 91234 56789', service: 'Nail Art',       appt_date: tomorrow, appt_time: '10:30 AM', status: 'confirmed' },
        { id: 104, name: 'Kavya Nair',    mobile: '+91 99887 76655', service: 'Refill',         appt_date: tomorrow, appt_time: '4:00 PM',  status: 'pending'   },
        { id: 105, name: 'Riya Menon',    mobile: '+91 98123 45670', service: 'Pedicure',       appt_date: tomorrow, appt_time: '6:30 PM',  status: 'confirmed' },
      ],
      recent_orders: [
        { id: 201, order_code: 'LQ-260501-A4F2', customer_name: 'Tara Kapoor',   product_name: 'Aurora Chrome',   total: 999,  status: 'pending',    created_at: minusDays(0) },
        { id: 202, order_code: 'LQ-260501-B81C', customer_name: 'Aisha Rao',     product_name: 'Vintage Rose',    total: 1199, status: 'processing', created_at: minusDays(1) },
        { id: 203, order_code: 'LQ-260430-C2E0', customer_name: 'Naina Patel',   product_name: 'Pearl Drop',      total: 1499, status: 'shipped',    created_at: minusDays(2) },
        { id: 204, order_code: 'LQ-260430-D915', customer_name: 'Sanya Joshi',   product_name: 'Cat Eye Galaxy',  total: 1099, status: 'delivered',  created_at: minusDays(3) },
      ],
    },

    '/appointments': [
      { id: 101, name: 'Ananya Reddy',  mobile: '+91 98765 43210', service: 'Gel Polish',     appt_date: today,    appt_time: '11:00 AM', status: 'confirmed', source: 'online'  },
      { id: 102, name: 'Priya Sharma',  mobile: '+91 99876 54321', service: 'Gel Extensions', appt_date: today,    appt_time: '2:30 PM',  status: 'pending',   source: 'whatsapp'},
      { id: 103, name: 'Meera Iyer',    mobile: '+91 91234 56789', service: 'Nail Art',       appt_date: tomorrow, appt_time: '10:30 AM', status: 'confirmed', source: 'online'  },
      { id: 104, name: 'Kavya Nair',    mobile: '+91 99887 76655', service: 'Refill',         appt_date: tomorrow, appt_time: '4:00 PM',  status: 'pending',   source: 'walk_in' },
      { id: 105, name: 'Riya Menon',    mobile: '+91 98123 45670', service: 'Pedicure',       appt_date: tomorrow, appt_time: '6:30 PM',  status: 'confirmed', source: 'phone'   },
      { id: 106, name: 'Tara Kapoor',   mobile: '+91 99988 77665', service: 'Manicure',       appt_date: today,    appt_time: '5:00 PM',  status: 'completed', source: 'online'  },
    ],

    '/clients': [
      { id: 1, name: 'Ananya Reddy', mobile: '+91 98765 43210', email: 'ananya@example.com',  total_visits: 8, total_spend: 9800,  no_show_count: 0, last_visit_at: '2026-04-22 14:30:00', last_service: 'Gel Polish',     refill_due_at: null,         tags: ['Bestseller'] },
      { id: 2, name: 'Priya Sharma', mobile: '+91 99876 54321', email: 'priya@example.com',   total_visits: 4, total_spend: 6800,  no_show_count: 1, last_visit_at: '2026-04-15 11:00:00', last_service: 'Gel Extensions', refill_due_at: today,        tags: [] },
      { id: 3, name: 'Meera Iyer',   mobile: '+91 91234 56789', email: null,                 total_visits: 12,total_spend: 15200, no_show_count: 0, last_visit_at: '2026-04-28 16:00:00', last_service: 'Nail Art',       refill_due_at: null,         tags: ['VIP'] },
      { id: 4, name: 'Kavya Nair',   mobile: '+91 99887 76655', email: 'kavya@example.com',   total_visits: 2, total_spend: 2998,  no_show_count: 0, last_visit_at: '2026-04-08 13:00:00', last_service: 'Acrylic Extensions', refill_due_at: minusDays(2).slice(0,10), tags: [] },
    ],

    '/products': [
      { id: 1, name: 'Aurora Chrome', category: 'Chrome',    shape: 'Almond',       price: 999,  purchase_price: 350, stock: 8, low_stock_alert: 3, tags: ['Bestseller'],     photo_path: null, is_active: 1 },
      { id: 2, name: 'Vintage Rose',  category: 'Florals',   shape: 'Coffin',       price: 1199, purchase_price: 420, stock: 4, low_stock_alert: 3, tags: ['Hand-painted'],   photo_path: null, is_active: 1 },
      { id: 3, name: 'Micro French',  category: 'French',    shape: 'Stiletto',     price: 899,  purchase_price: 300, stock: 2, low_stock_alert: 3, tags: [],                 photo_path: null, is_active: 1 },
      { id: 4, name: 'Pearl Drop',    category: '3D Charms', shape: 'Square',       price: 1499, purchase_price: 580, stock: 6, low_stock_alert: 3, tags: ['Bridal'],         photo_path: null, is_active: 1 },
      { id: 5, name: 'Cat Eye Galaxy',category: 'Glitter',   shape: 'Almond',       price: 1099, purchase_price: 380, stock: 9, low_stock_alert: 3, tags: ['New'],            photo_path: null, is_active: 1 },
      { id: 6, name: 'Butter Nude',   category: 'Minimal',   shape: 'Short Square', price: 799,  purchase_price: 280, stock: 1, low_stock_alert: 3, tags: [],                 photo_path: null, is_active: 1 },
    ],

    '/orders': [
      { id: 201, order_code: 'LQ-260501-A4F2', customer_name: 'Tara Kapoor',   customer_mobile: '+91 99988 77665', product_name: 'Aurora Chrome',  quantity: 1, total: 999,  status: 'pending',    label_printed: 0, created_at: minusDays(0) },
      { id: 202, order_code: 'LQ-260501-B81C', customer_name: 'Aisha Rao',     customer_mobile: '+91 99001 23456', product_name: 'Vintage Rose',   quantity: 1, total: 1199, status: 'processing', label_printed: 0, created_at: minusDays(1) },
      { id: 203, order_code: 'LQ-260430-C2E0', customer_name: 'Naina Patel',   customer_mobile: '+91 90909 90909', product_name: 'Pearl Drop',     quantity: 1, total: 1499, status: 'shipped',    label_printed: 1, created_at: minusDays(2) },
      { id: 204, order_code: 'LQ-260430-D915', customer_name: 'Sanya Joshi',   customer_mobile: '+91 98765 12340', product_name: 'Cat Eye Galaxy', quantity: 2, total: 2198, status: 'delivered',  label_printed: 1, created_at: minusDays(3) },
    ],

    '/photos': {},

    '/settings/site':           { whatsappNumber: '918317451779', phone: '+91 83174 51779', email: 'hello@lacquerynails.in', address: 'Near Shrirampura Metro, Bengaluru', hours: 'Mon to Sun · 10 AM to 9 PM' },
    '/settings/offers_banner':  { enabled: true, text: 'Festive offer · 20% off all press-on sets · code LACQUER20' },
    '/settings/shipping':       { provider: 'manual', shiprocket_email: '', shiprocket_password: '' },
    '/settings/payment':        { razorpay_key_id: '', razorpay_key_secret: '', webhook_secret: '', enable_booking_fee: false, enable_shop_payment: true },
    '/settings/refill_rules':   { days: 21, services: ['Gel Extensions', 'Acrylic Extensions', 'Permanent Extensions'], message: '' },

    '/blog': [
      { id: 1, title: 'Best Gel Polish Salon Near Shrirampura',         slug: 'best-gel-polish-shrirampura',     status: 'published', updated_at: minusDays(2) },
      { id: 2, title: 'Press-On Nails vs Gel Extensions: What to Pick', slug: 'press-on-vs-gel-extensions',      status: 'published', updated_at: minusDays(5) },
      { id: 3, title: 'Bridal Nail Trends 2026 — Bengaluru Edition',    slug: 'bridal-nail-trends-2026',         status: 'draft',     updated_at: minusDays(1) },
    ],

    '/users': [
      { id: 1, name: 'Anita (Demo)',    email: 'demo@lacquerynails.in',   role: 'admin', permissions: [], is_active: 1, last_login_at: minusDays(0), created_at: minusDays(45) },
      { id: 2, name: 'Sneha Technician', email: 'sneha@lacquerynails.in', role: 'staff', permissions: ['dashboard','appointments','clients','reminders','shipping'], is_active: 1, last_login_at: minusDays(1), created_at: minusDays(20) },
    ],

    '/audit': [
      { id: 1, user_id: 1, user_email: 'demo@lacquerynails.in', action: 'login',              entity_type: 'user',        entity_id: 1,    meta: null,                      ip: '127.0.0.1', created_at: minusDays(0) },
      { id: 2, user_id: 1, user_email: 'demo@lacquerynails.in', action: 'appointment_create', entity_type: 'appointment', entity_id: 106,  meta: { source: 'walk_in' },     ip: '127.0.0.1', created_at: minusDays(0) },
      { id: 3, user_id: 2, user_email: 'sneha@lacquerynails.in', action: 'order_update',      entity_type: 'order',       entity_id: 203,  meta: { fields: ['status'] },    ip: '127.0.0.1', created_at: minusDays(1) },
      { id: 4, user_id: 1, user_email: 'demo@lacquerynails.in', action: 'photo_upload',       entity_type: 'photo',       entity_id: null, meta: { slot_key: 'hero-photo' },ip: '127.0.0.1', created_at: minusDays(2) },
    ],

    '/reminders': {
      one_day: [
        { id: 103, name: 'Meera Iyer', mobile: '+91 91234 56789', service: 'Nail Art',     appt_time: '10:30 AM' },
        { id: 104, name: 'Kavya Nair', mobile: '+91 99887 76655', service: 'Refill',       appt_time: '4:00 PM'  },
      ],
      two_hour: [
        { id: 102, name: 'Priya Sharma', mobile: '+91 99876 54321', service: 'Gel Extensions', appt_time: '2:30 PM' },
      ],
      refill: [
        { id: 2, name: 'Priya Sharma', mobile: '+91 99876 54321', email: 'priya@example.com', last_service: 'Gel Extensions',     last_visit_at: '2026-04-15' },
        { id: 4, name: 'Kavya Nair',   mobile: '+91 99887 76655', email: 'kavya@example.com', last_service: 'Acrylic Extensions', last_visit_at: '2026-04-08' },
      ],
    },

    '/notifications': { max_appointment_id: 106, max_order_id: 204, new_appointments: [], new_orders: [] },
  };

  window.LQ = {
    API_BASE: '/api',
    request: function (path) {
      const cleanPath = path.split('?')[0];
      const data = STUBS[cleanPath];
      if (data !== undefined) return Promise.resolve(JSON.parse(JSON.stringify(data)));
      return Promise.resolve(null);
    },
    refreshSession: function () { return Promise.resolve(demoUser); },
    login:     function () { return Promise.resolve(demoUser); },
    logout:    function () { return Promise.resolve(); },
    requestReset:  function () { return Promise.resolve(); },
    confirmReset:  function () { return Promise.resolve(); },
    uploadPhoto:   function () { return Promise.resolve({ slot_key: 'demo', file_path: '' }); },
    downloadFile:  function () { alert('Demo mode: download disabled.'); },
    user: function () { return demoUser; },
    onAuthChange: function () { return function () {}; },
  };
}

// Manual shipping label — opens printable window. No backend required.
window.LQ_printManualLabel = function (order, shipping) {
  shipping = shipping || {};
  function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'})[c]); }
  const fromBlock = [
    esc(shipping.from_name    || 'Lacquery Nails'),
    esc(shipping.from_address || 'Near Shrirampura Metro'),
    [esc(shipping.from_city || 'Bengaluru'), esc(shipping.from_state || 'Karnataka'), esc(shipping.from_pincode || '')].filter(Boolean).join(' · '),
    'Phone: ' + esc(shipping.from_phone || '+91 83174 51779'),
    shipping.from_gstin ? 'GSTIN: ' + esc(shipping.from_gstin) : ''
  ].filter(Boolean).join('<br>');

  const toBlock = [
    '<strong>' + esc(order.customer_name) + '</strong>',
    esc(order.customer_address || '— address pending, ask on WhatsApp —'),
    'Phone: ' + esc(order.customer_mobile),
    order.customer_email ? esc(order.customer_email) : ''
  ].filter(Boolean).join('<br>');

  const html = `<!doctype html>
<html><head><meta charset="utf-8"><title>Shipping Label · ${esc(order.order_code)}</title>
<style>
  @page { size: 100mm 150mm; margin: 0; }
  * { box-sizing: border-box; }
  body { font-family: 'Inter', -apple-system, system-ui, sans-serif; margin: 0; padding: 24px; color: #020049; background: #fff; }
  .label { width: 100mm; min-height: 150mm; padding: 14px; border: 2px solid #020049; display: flex; flex-direction: column; }
  .header { display: flex; justify-content: space-between; align-items: flex-start; border-bottom: 1px dashed #020049; padding-bottom: 10px; margin-bottom: 10px; }
  .brand { font-family: 'Cormorant Garamond', Georgia, serif; font-size: 22px; font-weight: 500; }
  .order-code { font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 0.08em; color: #020049; background: rgba(2,0,73,0.06); padding: 4px 8px; border-radius: 2px; }
  .lbl { font-size: 9px; letter-spacing: 0.18em; text-transform: uppercase; color: #777; margin-bottom: 4px; font-weight: 600; }
  .block { font-size: 12px; line-height: 1.5; padding: 8px 0; }
  .block.to { font-size: 14px; line-height: 1.55; padding: 12px 0; }
  .from { border-bottom: 1px dashed #020049; padding-bottom: 10px; margin-bottom: 10px; }
  .product { border-top: 1px dashed #020049; padding-top: 10px; margin-top: auto; font-size: 11px; }
  .product-row { display: flex; justify-content: space-between; padding: 2px 0; }
  .footer { font-size: 9px; color: #777; text-align: center; letter-spacing: 0.14em; text-transform: uppercase; margin-top: 8px; padding-top: 8px; border-top: 1px solid rgba(2,0,73,0.15); }
  @media print { body { padding: 0; } .label { border: none; } .no-print { display: none; } }
  .actions { padding: 16px; text-align: center; }
  .actions button { font: 500 11px/1 'Inter', sans-serif; letter-spacing: 0.18em; text-transform: uppercase; padding: 12px 22px; background: #020049; color: #fff; border: 0; border-radius: 2px; cursor: pointer; margin: 0 6px; }
  .actions button.ghost { background: transparent; color: #020049; border: 1px solid #020049; }
</style></head>
<body>
  <div class="actions no-print">
    <button onclick="window.print()">Print Label</button>
    <button class="ghost" onclick="window.close()">Close</button>
  </div>
  <div class="label">
    <div class="header">
      <div class="brand">Lacquery Nails</div>
      <div class="order-code">${esc(order.order_code)}</div>
    </div>
    <div class="block from">
      <div class="lbl">From</div>
      ${fromBlock}
    </div>
    <div class="block to">
      <div class="lbl">Ship To</div>
      ${toBlock}
    </div>
    <div class="product">
      <div class="lbl">Contents</div>
      <div class="product-row"><span>${esc(order.product_name)} × ${esc(order.quantity || 1)}</span><span></span></div>
    </div>
    <div class="footer">Handle with care · Press-on nails</div>
  </div>
  <script>window.addEventListener('load', function(){ setTimeout(function(){ window.print(); }, 250); });</` + `script>
</body></html>`;

  const w = window.open('', '_blank', 'width=520,height=720');
  if (!w) { alert('Allow pop-ups to print labels.'); return; }
  w.document.open();
  w.document.write(html);
  w.document.close();
};

// Tiny chime via Web Audio — no asset upload needed.
window.LQ_chime = function () {
  try {
    var ACtx = window.AudioContext || window.webkitAudioContext;
    if (!ACtx) return;
    var ctx = window.LQ_audio_ctx || (window.LQ_audio_ctx = new ACtx());
    if (ctx.state === 'suspended') ctx.resume();
    var now = ctx.currentTime;
    [880, 1320].forEach(function (freq, i) {
      var o = ctx.createOscillator();
      var g = ctx.createGain();
      o.type = 'sine';
      o.frequency.setValueAtTime(freq, now + i * 0.12);
      g.gain.setValueAtTime(0.0001, now + i * 0.12);
      g.gain.exponentialRampToValueAtTime(0.18, now + i * 0.12 + 0.02);
      g.gain.exponentialRampToValueAtTime(0.0001, now + i * 0.12 + 0.45);
      o.connect(g); g.connect(ctx.destination);
      o.start(now + i * 0.12);
      o.stop(now + i * 0.12 + 0.5);
    });
  } catch (e) {}
};

// Tab definitions — the single source of truth for the sidebar + permission system.
// `adminOnly: true` means staff cannot be granted this tab (users, audit).
window.LQ_TABS = [
  { key: 'dashboard',    label: 'Dashboard',    icon: 'home',    section: 'core',   defaultStaff: true,  adminOnly: false },
  { key: 'appointments', label: 'Appointments', icon: 'cal',     section: 'core',   defaultStaff: true,  adminOnly: false },
  { key: 'clients',      label: 'Clients',      icon: 'people',  section: 'core',   defaultStaff: true,  adminOnly: false },
  { key: 'reminders',    label: 'Reminders',    icon: 'bell',    section: 'core',   defaultStaff: true,  adminOnly: false },
  { key: 'photos',       label: 'Photos',       icon: 'image',   section: 'shop',   defaultStaff: false, adminOnly: false },
  { key: 'shop',         label: 'Press-Ons',    icon: 'tag',     section: 'shop',   defaultStaff: false, adminOnly: false },
  { key: 'inventory',    label: 'Inventory',    icon: 'box',     section: 'shop',   defaultStaff: false, adminOnly: false },
  { key: 'shipping',     label: 'Shipping',     icon: 'truck',   section: 'shop',   defaultStaff: true,  adminOnly: false },
  { key: 'offers',       label: 'Offers',       icon: 'flag',    section: 'config', defaultStaff: false, adminOnly: false },
  { key: 'blog',         label: 'Blog',         icon: 'edit',    section: 'config', defaultStaff: false, adminOnly: false },
  { key: 'payment',      label: 'Payment',      icon: 'card',    section: 'config', defaultStaff: false, adminOnly: false },
  { key: 'settings',     label: 'Site Settings',icon: 'gear',    section: 'config', defaultStaff: false, adminOnly: false },
  { key: 'users',        label: 'Users',        icon: 'shield',  section: 'config', defaultStaff: false, adminOnly: true  },
  { key: 'audit',        label: 'Audit Log',    icon: 'log',     section: 'config', defaultStaff: false, adminOnly: true  },
];

window.LQ_canAccess = function (user, tabKey) {
  if (!user) return false;
  if (user.role === 'admin') return true;
  return Array.isArray(user.permissions) && user.permissions.includes(tabKey);
};

window.Icon = function Icon({ name, size = 16 }) {
  const paths = {
    home:   <path d="M3 9.5L10 3l7 6.5V17a1 1 0 01-1 1h-3v-5H8v5H5a1 1 0 01-1-1V9.5z" />,
    cal:    <><rect x="3" y="4" width="14" height="13" rx="1" /><line x1="3" y1="8" x2="17" y2="8" /><line x1="7" y1="2" x2="7" y2="6" /><line x1="13" y1="2" x2="13" y2="6" /></>,
    people: <><circle cx="7" cy="7" r="3" /><circle cx="14" cy="8" r="2.5" /><path d="M2 17c0-2.5 2.5-4.5 5-4.5s5 2 5 4.5" /><path d="M11 16c.5-2 2-3 3.5-3 1.5 0 3 1 3.5 3" /></>,
    bell:   <><path d="M5 14h10l-1.5-2V8a3.5 3.5 0 00-7 0v4L5 14z" /><path d="M8 16a2 2 0 004 0" /></>,
    image:  <><rect x="2" y="3" width="16" height="14" rx="1" /><circle cx="7" cy="8" r="1.5" /><path d="M2 14l5-4 4 3 3-2 4 4" /></>,
    tag:    <><path d="M3 8V3h5l9 9-5 5-9-9z" /><circle cx="6" cy="6" r="1" /></>,
    box:    <><path d="M2 6l8-3 8 3v8l-8 3-8-3V6z" /><path d="M2 6l8 3 8-3" /><line x1="10" y1="9" x2="10" y2="17" /></>,
    truck:  <><rect x="1" y="5" width="11" height="9" /><path d="M12 8h4l3 3v3h-7z" /><circle cx="5" cy="16" r="1.5" /><circle cx="15" cy="16" r="1.5" /></>,
    flag:   <><path d="M4 2v16" /><path d="M4 3h11l-2 4 2 4H4" /></>,
    edit:   <><path d="M3 17l1-5L13 3l3 3-9 9-5 1z" /></>,
    card:   <><rect x="2" y="5" width="16" height="11" rx="1" /><line x1="2" y1="9" x2="18" y2="9" /></>,
    gear:   <><circle cx="10" cy="10" r="2.5" /><path d="M10 2v2M10 16v2M2 10h2M16 10h2M4 4l1.5 1.5M14.5 14.5L16 16M4 16l1.5-1.5M14.5 5.5L16 4" /></>,
    shield: <><path d="M10 2l7 3v5c0 4-3 7-7 8-4-1-7-4-7-8V5l7-3z" /></>,
    log:    <><rect x="3" y="3" width="14" height="14" rx="1" /><line x1="6" y1="7" x2="14" y2="7" /><line x1="6" y1="10" x2="14" y2="10" /><line x1="6" y1="13" x2="11" y2="13" /></>,
  };
  return (
    <svg width={size} height={size} viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
      {paths[name] || null}
    </svg>
  );
};

function AdminApp() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [active, setActive] = useState('dashboard');
  const [sidebarOpen, setSidebarOpen] = useState(false);
  const [tick, setTick] = useState(0);
  const [toast, setToast] = useState(null);
  const lastIdsRef = useRef({ appt: 0, order: 0, primed: false });

  useEffect(() => {
    window.LQ.refreshSession().then((u) => { setUser(u); setLoading(false); });
    return window.LQ.onAuthChange((u) => setUser(u));
  }, []);

  // Real-time poll for new bookings + orders → chime + toast
  useEffect(() => {
    if (!user) return;
    let cancelled = false;

    async function poll() {
      try {
        const last = lastIdsRef.current;
        const qs = '?since_appt=' + (last.appt || 0) + '&since_order=' + (last.order || 0);
        const data = await window.LQ.request('/notifications' + qs);
        if (cancelled || !data) return;

        if (!last.primed) {
          lastIdsRef.current = { appt: data.max_appointment_id, order: data.max_order_id, primed: true };
          return;
        }

        const newAppts  = (data.new_appointments || []).filter(a => a.id > last.appt);
        const newOrders = (data.new_orders || []).filter(o => o.id > last.order);

        if (newAppts.length || newOrders.length) {
          window.LQ_chime();
          const parts = [];
          if (newAppts.length) {
            const a = newAppts[0];
            parts.push(newAppts.length === 1
              ? `New booking — ${a.name} · ${a.service}`
              : `${newAppts.length} new bookings`);
          }
          if (newOrders.length) {
            const o = newOrders[0];
            parts.push(newOrders.length === 1
              ? `New order — ${o.customer_name} · ${o.product_name}`
              : `${newOrders.length} new orders`);
          }
          setToast({ id: Date.now(), text: parts.join(' · ') });
          setTick(t => t + 1); // refresh dashboards
        }

        lastIdsRef.current = { appt: data.max_appointment_id, order: data.max_order_id, primed: true };
      } catch (e) { /* ignore */ }
    }

    poll();
    const id = setInterval(poll, 20000);
    return () => { cancelled = true; clearInterval(id); };
  }, [user]);

  useEffect(() => {
    if (!toast) return;
    const id = setTimeout(() => setToast(null), 6000);
    return () => clearTimeout(id);
  }, [toast]);

  // Reset to allowed tab if user loses permission
  useEffect(() => {
    if (user && !window.LQ_canAccess(user, active)) setActive('dashboard');
  }, [user, active]);

  // Close sidebar on resize to desktop
  useEffect(() => {
    const onResize = () => { if (window.innerWidth >= 900) setSidebarOpen(false); };
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  const allowed = useMemo(
    () => window.LQ_TABS.filter((t) => !user ? false : (user.role === 'admin' ? !t.adminOnly || t.adminOnly : true) && window.LQ_canAccess(user, t.key)),
    [user]
  );

  if (loading) {
    return (
      <div className="admin-auth-screen">
        <div className="admin-auth-card" style={{ textAlign: 'center', padding: '60px 40px' }}>
          <div className="admin-spinner" />
          <p style={{ marginTop: 20, color: '#555' }}>Loading…</p>
        </div>
      </div>
    );
  }

  if (!user) return <window.LoginScreen onAuthed={(u) => { setUser(u); setActive('dashboard'); }} />;

  const sectionProps = { user, refresh: () => setTick(tick + 1), tick };
  const Section = ({
    dashboard:    window.DashboardSection,
    appointments: window.AppointmentsSection,
    clients:      window.ClientsSection,
    reminders:    window.RemindersSection,
    photos:       window.PhotosSection,
    shop:         window.ShopSection,
    inventory:    window.InventorySection,
    shipping:     window.ShippingSection,
    offers:       window.OffersSection,
    blog:         window.BlogSection,
    payment:      window.PaymentSection,
    settings:     window.SettingsSection,
    users:        window.UsersSection,
    audit:        window.AuditSection,
  })[active] || (() => <div className="admin-empty">Coming soon.</div>);

  const navGroups = [
    { id: 'core',   label: 'Salon' },
    { id: 'shop',   label: 'Press-Ons' },
    { id: 'config', label: 'Config' },
  ];

  function logout() {
    if (confirm('Log out of the admin panel?')) {
      window.LQ.logout().then(() => setUser(null));
    }
  }

  return (
    <div className="admin-shell">
      <button
        className={`admin-sidebar-scrim ${sidebarOpen ? 'open' : ''}`}
        onClick={() => setSidebarOpen(false)}
        aria-label="Close menu"
      />
      <aside className={`admin-sidebar ${sidebarOpen ? 'open' : ''}`}>
        <div className="admin-sidebar-head">
          <img src="../assets/logo-wordmark-color.png" alt="Lacquery" className="admin-sidebar-logo" />
        </div>
        {navGroups.map((g) => {
          const tabs = allowed.filter((t) => t.section === g.id);
          if (!tabs.length) return null;
          return (
            <div className="admin-nav-group" key={g.id}>
              <div className="admin-nav-group-label">{g.label}</div>
              {tabs.map((t) => (
                <button
                  key={t.key}
                  className={`admin-nav-item ${active === t.key ? 'active' : ''}`}
                  onClick={() => { setActive(t.key); setSidebarOpen(false); }}
                >
                  <window.Icon name={t.icon} />
                  <span>{t.label}</span>
                </button>
              ))}
            </div>
          );
        })}
        <div className="admin-sidebar-foot">
          <div className="admin-user-card">
            <div className="admin-user-name">{user.name}</div>
            <div className="admin-user-role">{user.role}</div>
          </div>
          <button className="admin-btn admin-btn-ghost admin-btn-sm" onClick={logout}>Sign out</button>
        </div>
      </aside>

      <main className="admin-main">
        <header className="admin-topbar">
          <button className="admin-menu-btn" onClick={() => setSidebarOpen(true)} aria-label="Open menu">
            <svg width="20" height="14" viewBox="0 0 20 14" fill="none" stroke="currentColor" strokeWidth="1.6">
              <line x1="0" y1="2"  x2="20" y2="2"  /><line x1="0" y1="7"  x2="20" y2="7"  /><line x1="0" y1="12" x2="20" y2="12" />
            </svg>
          </button>
          <div className="admin-topbar-title">
            {(window.LQ_TABS.find((t) => t.key === active) || {}).label || 'Admin'}
          </div>
          <div className="admin-topbar-spacer" />
          <a className="admin-btn admin-btn-ghost admin-btn-sm" href="../index.html" target="_blank" rel="noopener">View site ↗</a>
        </header>

        <div className="admin-content">
          <Section {...sectionProps} />
        </div>

        {toast && (
          <div className="admin-toast" key={toast.id}>
            <span className="admin-toast-pulse" />
            <span>{toast.text}</span>
          </div>
        )}
      </main>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('admin-root')).render(<AdminApp />);
