// ============================================================
// Saraya Events — ADMIN MODE
// A localStorage-backed "overrides" layer. Public visitors never
// trigger any of this — they always see the default site. Once an
// admin logs in (hidden #admin route or the footer key), edit mode
// turns on and inline controls appear: upload/replace/remove images,
// edit text, and add/remove/reorder catalogue & gallery items.
//
// HOW SAVING WORKS
//   Every change is written to localStorage immediately under the key
//   'saraya_overrides_v1'. It survives refresh, tab close, and reopen
//   on the SAME browser. Uploaded images are compressed to data-URLs
//   so they persist without any server. Use Export/Import in the admin
//   bar to move content between devices or to back it up.
//   (A real multi-visitor CMS needs a backend — see STRATEGY notes.)
// ============================================================

const { useState: useStateAd, useEffect: useEffectAd, useRef: useRefAd, useContext: useContextAd, useCallback: useCallbackAd } = React;

const OVR_KEY = 'saraya_overrides_v1';
const PASS_KEY = 'saraya_admin_code';
const SESS_KEY = 'saraya_admin_session';
// True only on the live production domain. Used to hide seed/dev-only tools
// (Data Import / Change Code) from the Admin rail in production, while keeping
// them available on localhost and Vercel preview URLs.
const IS_PROD = typeof location !== 'undefined' && /(^|\.)sarayaevents\.com$/.test(location.hostname);

/* ---- IndexedDB image store (holds far more than localStorage's ~5MB) ---- */
const IMG_DB = 'saraya_images_db';
const IMG_STORE = 'images';
function idbOpen() {
  return new Promise((res, rej) => {
    let r;
    try { r = indexedDB.open(IMG_DB, 1); } catch (e) { return rej(e); }
    r.onupgradeneeded = () => { if (!r.result.objectStoreNames.contains(IMG_STORE)) r.result.createObjectStore(IMG_STORE); };
    r.onsuccess = () => res(r.result);
    r.onerror = () => rej(r.error);
  });
}
async function idbGetAll() {
  const db = await idbOpen();
  return new Promise((res, rej) => {
    const out = {};
    const tx = db.transaction(IMG_STORE, 'readonly');
    const cur = tx.objectStore(IMG_STORE).openCursor();
    cur.onsuccess = (e) => { const c = e.target.result; if (c) { out[c.key] = c.value; c.continue(); } else res(out); };
    cur.onerror = () => rej(cur.error);
  });
}
async function idbSet(key, val) {
  const db = await idbOpen();
  return new Promise((res, rej) => { const tx = db.transaction(IMG_STORE, 'readwrite'); tx.objectStore(IMG_STORE).put(val, key); tx.oncomplete = () => res(); tx.onerror = () => rej(tx.error); tx.onabort = () => rej(tx.error); });
}
async function idbDel(key) {
  const db = await idbOpen();
  return new Promise((res, rej) => { const tx = db.transaction(IMG_STORE, 'readwrite'); tx.objectStore(IMG_STORE).delete(key); tx.oncomplete = () => res(); tx.onerror = () => rej(tx.error); });
}
async function idbClear() {
  const db = await idbOpen();
  return new Promise((res, rej) => { const tx = db.transaction(IMG_STORE, 'readwrite'); tx.objectStore(IMG_STORE).clear(); tx.oncomplete = () => res(); tx.onerror = () => rej(tx.error); });
}

function blankOverrides() {
  return { v: 1, images: {}, text: {}, entities: {}, catAdded: [], catDeleted: [], storeAdded: [], storeDeleted: [], storeOrder: [], galAdded: [], galDeleted: [], galOrder: [] };
}
/* ---- Supabase content_blocks sync (lightweight sync mode) ----
   The whole overrides object (minus images, which stay device-local in
   IndexedDB) is stored as ONE row: page_key='_global', section_key='_all',
   content_type='json_blob'. This makes text/layout edits persist across
   staff devices and visible to every site visitor, without rewriting the
   nested overrides format into true per-section rows (see the content_blocks
   table comment for that future path). Writes are debounced; a failure here
   never blocks the local save, which still happens via localStorage. */
const CLOUD_PAGE_KEY = '_global';
const CLOUD_SECTION_KEY = '_all';
let __cloudSyncTimer = null;
function syncOverridesToCloud(overrides, onError) {
  clearTimeout(__cloudSyncTimer);
  __cloudSyncTimer = setTimeout(async () => {
    // Re-read the client at flush time — it may not have existed when this
    // debounce was scheduled (script load order / auth still settling).
    const db = window.SarayaDB;
    if (!db) return;                       // local-only mode; localStorage already saved
    try {
      const { images, ...rest } = overrides;
      const { data: { user } = {} } = await db.auth.getUser().catch(() => ({ data: {} }));
      if (!user) {                         // content_blocks writes are admin-only under RLS
        if (onError) onError('Sign in as an admin to publish changes so all visitors see them.');
        return;
      }
      const { error } = await db.from('content_blocks').upsert({
        page_key: CLOUD_PAGE_KEY,
        section_key: CLOUD_SECTION_KEY,
        content_type: 'json_blob',
        language: 'all',
        body: JSON.stringify(rest),
        is_active: true,
        updated_by: user.id,               // FK to auth.users — a real uid, never a string code
        updated_at: new Date().toISOString(),
      }, { onConflict: 'page_key,section_key' });
      if (error && onError) {
        onError('Could not publish to the cloud (' + (error.message || 'permission denied') + '). Saved on this device only.');
      }
    } catch (e) {
      if (onError) onError('Could not publish to the cloud. Saved on this device only.');
    }
  }, 1500);
}

async function loadOverridesFromCloud() {
  const db = window.SarayaDB;
  if (!db) return null;
  try {
    const { data, error } = await db.from('content_blocks')
      .select('body')
      .eq('page_key', CLOUD_PAGE_KEY)
      .eq('section_key', CLOUD_SECTION_KEY)
      .eq('is_active', true)
      .maybeSingle();
    if (error || !data || !data.body) return null;
    return JSON.parse(data.body);
  } catch (e) { return null; }
}

function loadOverrides() {
  const baked = (window.__bakedContent && typeof window.__bakedContent === 'object') ? window.__bakedContent : null;
  try {
    const stored = localStorage.getItem(OVR_KEY);
    if (stored) return Object.assign(blankOverrides(), JSON.parse(stored));
    // No local edits yet — fall back to content baked into the build (Netlify export).
    if (baked) { const b = Object.assign(blankOverrides(), baked); b.images = {}; return b; }
    return blankOverrides();
  }
  catch (e) { return blankOverrides(); }
}

/* deep helpers (immutable-ish) */
function dget(obj, path) { return path.split('.').reduce((o, k) => (o == null ? undefined : o[k]), obj); }
function deepMerge(a, b) {
  if (Array.isArray(b)) return b.slice();
  if (b && typeof b === 'object') {
    const out = Object.assign({}, a && typeof a === 'object' && !Array.isArray(a) ? a : {});
    Object.keys(b).forEach((k) => { out[k] = deepMerge(out[k], b[k]); });
    return out;
  }
  return b;
}
function nestFromFlat(flat) {
  // { 'name.en':'x', color:'y' } -> { name:{en:'x'}, color:'y' }
  const out = {};
  Object.keys(flat).forEach((key) => {
    const parts = key.split('.'); let cur = out;
    parts.forEach((p, i) => { if (i === parts.length - 1) cur[p] = flat[key]; else cur = (cur[p] = cur[p] || {}); });
  });
  return out;
}

/* compress an uploaded image to a reasonably-sized JPEG data-URL */
function compressImage(file, maxDim, quality) {
  maxDim = maxDim || 1700; quality = quality || 0.84;
  return new Promise((resolve, reject) => {
    const type = (file.type || '').toLowerCase();
    if (type.includes('svg') || type.includes('gif')) {
      const r = new FileReader(); r.onload = () => resolve(r.result); r.onerror = reject; r.readAsDataURL(file); return;
    }
    const url = URL.createObjectURL(file);
    const img = new Image();
    img.onload = () => {
      let { width: w, height: h } = img;
      if (Math.max(w, h) > maxDim) { const s = maxDim / Math.max(w, h); w = Math.round(w * s); h = Math.round(h * s); }
      const c = document.createElement('canvas'); c.width = w; c.height = h;
      const ctx = c.getContext('2d'); ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, w, h); ctx.drawImage(img, 0, 0, w, h);
      URL.revokeObjectURL(url);
      try { resolve(c.toDataURL('image/jpeg', quality)); } catch (e) { reject(e); }
    };
    img.onerror = reject; img.src = url;
  });
}

/* ---------------- Provider ---------------- */
function AdminProvider({ children }) {
  const [overrides, setOverrides] = useStateAd(loadOverrides);

  /* pull the latest published content from Supabase once on mount, so every
     visitor (not just the device that made the edit) sees current content.
     Images are excluded from the cloud copy and stay whatever this device
     already has locally. */
  useEffectAd(() => {
    let cancelled = false;
    (async () => {
      const cloud = await loadOverridesFromCloud();
      if (cloud && !cancelled) {
        // DB-first, baked as fallback: merge cloud OVER the baked defaults so a
        // partial cloud row (e.g. only a few edited fields) can never wipe the
        // rest of the baked content for visitors. Images stay device-local.
        const baked = (window.__bakedContent && typeof window.__bakedContent === 'object') ? window.__bakedContent : {};
        setOverrides((prev) => {
          const merged = deepMerge(deepMerge(blankOverrides(), baked), cloud);
          merged.images = prev.images;
          return merged;
        });
      }
    })();
    return () => { cancelled = true; };
  }, []);
  const [images, setImages] = useStateAd({});   // slot -> dataURL, backed by IndexedDB
  const [isAdmin, setIsAdmin] = useStateAd(() => localStorage.getItem(SESS_KEY) === '1');
  const [editMode, setEditMode] = useStateAd(() => localStorage.getItem(SESS_KEY) === '1');
  const [editConfig, setEditConfig] = useStateAd(null);
  const [ordersOpen, setOrdersOpen] = useStateAd(false);
  const [subsOpen, setSubsOpen] = useStateAd(false);
  const [stripeOpen, setStripeOpen] = useStateAd(false);
  const [toast, setToast] = useStateAd(null);
  const [savedAt, setSavedAt] = useStateAd(null);
  const fileResolver = useRefAd(null);

  /* load images from IndexedDB on mount; migrate any legacy localStorage images */
  useEffectAd(() => {
    let cancelled = false;
    (async () => {
      let imgs = {};
      try { imgs = await idbGetAll(); } catch (e) { imgs = {}; }
      // Seed from images baked into the build (Netlify export) for any slot not already set locally.
      const baked = (window.__bakedContent && window.__bakedContent.images) || null;
      if (baked) { for (const k of Object.keys(baked)) { if (imgs[k] == null) imgs[k] = baked[k]; } }
      // Seed from the database (platform_images) for any slot still missing --
      // this is what makes an admin's saved image visible to every visitor on
      // any device, instead of only the device that made the edit.
      try {
        if (window.SarayaService && window.SarayaService.platformImages) {
          const dbImgs = await window.SarayaService.platformImages.list();
          for (const k of Object.keys(dbImgs)) { if (imgs[k] == null) imgs[k] = dbImgs[k]; }
        }
      } catch (e) {}
      const legacy = (overrides.images && Object.keys(overrides.images).length) ? overrides.images : null;
      if (legacy) {
        for (const k of Object.keys(legacy)) { try { await idbSet(k, legacy[k]); imgs[k] = legacy[k]; } catch (e) {} }
        setOverrides((prev) => Object.assign({}, prev, { images: {} }));
      }
      if (!cancelled) setImages(imgs);
    })();
    return () => { cancelled = true; };
  }, []);

  /* persist on every change */
  const didMountRef = useRefAd(false);
  useEffectAd(() => {
    try { localStorage.setItem(OVR_KEY, JSON.stringify(overrides)); setSavedAt(Date.now()); }
    catch (e) { setToast({ type: 'error', msg: 'Storage is full. Remove a few images or Export, then try again.' }); }
    // Publish to the shared cloud copy only for signed-in admins, and never on
    // the initial mount — that first run also fires when cloud/baked content
    // loads in, and publishing then would echo it back or clobber a newer edit
    // from another device. Anonymous visitors only ever read.
    if (!didMountRef.current) { didMountRef.current = true; return; }
    if (isAdmin) syncOverridesToCloud(overrides, (msg) => setToast({ type: 'error', msg }));
  }, [overrides]);

  /* propagate editable contact info site-wide by mutating the shared object */
  useEffectAd(() => {
    const C = window.SARAYA.CONTACT; const tx = overrides.text || {};
    ['phoneDisplay', 'phoneIntl', 'email', 'city', 'showroom', 'instagram'].forEach((k) => {
      const v = tx['contact.' + k]; if (v != null && v !== '') C[k] = v;
    });
  }, [overrides.text]);

  /* Sync admin status from Supabase auth role (when configured).
     Falls back to the localStorage passcode when Supabase is not set up. */
  useEffectAd(() => {
    const db = window.SarayaDB;
    if (!db) return;
    const { data: { subscription } } = db.auth.onAuthStateChange(async (_event, session) => {
      if (session?.user) {
        const { data: p } = await db.from('profiles').select('role').eq('id', session.user.id).single();
        if (p?.role === 'admin') {
          setIsAdmin(true);
          setEditMode(true);
          localStorage.setItem(SESS_KEY, '1');
        } else if (p && p.role !== 'admin') {
          setIsAdmin(false);
          setEditMode(false);
          localStorage.removeItem(SESS_KEY);
        }
      } else if (window.supabaseConfigured) {
        setIsAdmin(false);
        setEditMode(false);
        localStorage.removeItem(SESS_KEY);
      }
    });
    return () => subscription?.unsubscribe();
  }, []);

  /* hidden access: visiting #admin opens the login */
  useEffectAd(() => {
    const check = () => { if ((location.hash || '').toLowerCase() === '#admin') { if (window.openAuthModal) window.openAuthModal('login'); location.hash = '#home'; } };
    window.addEventListener('hashchange', check); check();
    return () => window.removeEventListener('hashchange', check);
  }, []);

  const toastMsg = useCallbackAd((msg, type) => { setToast({ msg, type: type || 'ok' }); setTimeout(() => setToast(null), 2600); }, []);

  /* ----- auth ----- */
  // Admin Mode requires a real Supabase session whose profile role is 'admin'.
  // content_blocks writes are RLS-gated to admins, so a client-only passcode
  // could enter edit mode but never actually publish. Returns true on success,
  // or a string error message to surface in the login dialog.
  const logout = useCallbackAd(() => { setIsAdmin(false); setEditMode(false); localStorage.removeItem(SESS_KEY); setEditConfig(null); }, []);
  const changeCode = useCallbackAd(() => {
    const n = window.prompt('Set a new admin access code (min 4 characters):'); if (n == null) return;
    if (n.length < 4) { toastMsg('Code too short', 'error'); return; }
    localStorage.setItem(PASS_KEY, n); toastMsg('Access code updated');
  }, []);

  /* ----- mutators ----- */
  const commit = useCallbackAd((fn) => setOverrides((prev) => { const next = JSON.parse(JSON.stringify(prev)); fn(next); return next; }), []);

  const setImg = useCallbackAd(async (slot, dataUrl) => {
    try {
      await idbSet(slot, dataUrl);
      setImages((p) => Object.assign({}, p, { [slot]: dataUrl }));
      setSavedAt(Date.now());
      // Persist to the database too, so every visitor -- not just this device --
      // sees the image. Local save has already succeeded, so a failure here
      // (e.g. offline) shouldn't block the editor; it just won't sync yet.
      if (window.SarayaService && window.SarayaService.storage && window.SarayaService.platformImages) {
        window.SarayaService.storage.uploadPlatformImage(slot, dataUrl).then((res) => {
          if (res && res.url) return window.SarayaService.platformImages.upsert(slot, res.url);
        }).catch(() => {});
      }
      return true;
    } catch (e) {
      setToast({ type: 'error', msg: 'Could not save that image. Try a smaller file.' });
      return false;
    }
  }, []);
  const removeImg = useCallbackAd(async (slot) => {
    try { await idbDel(slot); } catch (e) {}
    setImages((p) => { const n = Object.assign({}, p); delete n[slot]; return n; });
    if (window.SarayaService && window.SarayaService.platformImages) {
      window.SarayaService.platformImages.remove(slot).catch(() => {});
    }
  }, []);
  const getImg = useCallbackAd((slot) => {
    if (!slot) return null;
    if (images[slot]) return images[slot];
    const baked = (window.__bakedContent && window.__bakedContent.images) || null;
    return (baked && baked[slot]) || null;
  }, [images]);

  const pickImage = useCallbackAd((slot) => {
    const input = document.createElement('input');
    input.type = 'file'; input.accept = 'image/*';
    input.style.position = 'fixed'; input.style.left = '-9999px'; input.style.opacity = '0';
    const cleanup = () => { if (input.parentNode) input.parentNode.removeChild(input); };
    input.onchange = async () => {
      const f = input.files && input.files[0];
      if (!f) { cleanup(); return; }
      try {
        const data = await compressImage(f);
        const ok = await setImg(slot, data);
        if (ok) { toastMsg('Image saved'); try { window.dispatchEvent(new CustomEvent('saraya:image-uploaded', { detail: { slot: slot, fileName: f.name } })); } catch (e2) {} }
      } catch (e) { toastMsg('Could not read that image', 'error'); }
      cleanup();
    };
    // appending to the document makes the file dialog reliable across browsers
    document.body.appendChild(input);
    input.click();
  }, [setImg, toastMsg]);

  const mergeEntity = useCallbackAd((id, partial) => commit((o) => { o.entities[id] = deepMerge(o.entities[id] || {}, partial); }), [commit]);
  const resolveEntity = useCallbackAd((id, def) => { const e = overrides.entities[id]; return e ? deepMerge(def, e) : def; }, [overrides.entities]);

  const getText = useCallbackAd((key, fallback) => { const v = overrides.text[key]; return v != null && v !== '' ? v : fallback; }, [overrides.text]);
  const setText = useCallbackAd((key, val) => commit((o) => { o.text[key] = val; }), [commit]);

  /* catalogue */
  const resolveCatalogue = useCallbackAd(() => {
    const base = window.SARAYA.CATALOGUE_ITEMS.concat(overrides.catAdded || []);
    return base.filter((i) => !(overrides.catDeleted || []).includes(i.id)).map((i) => resolveEntity('cat:' + i.id, i));
  }, [overrides.catAdded, overrides.catDeleted, overrides.entities, resolveEntity]);
  const addCatalogueItem = useCallbackAd((item) => commit((o) => { o.catAdded.push(item); }), [commit]);
  const deleteCatalogueItem = useCallbackAd((id) => { idbDel('cat:' + id); removeImg('cat:' + id); commit((o) => {
    if ((o.catAdded || []).some((x) => x.id === id)) o.catAdded = o.catAdded.filter((x) => x.id !== id);
    else if (!o.catDeleted.includes(id)) o.catDeleted.push(id);
    delete o.entities['cat:' + id];
  }); }, [commit, removeImg]);

  /* gallery */
  const resolveGallery = useCallbackAd(() => {
    let base = window.SARAYA.GALLERY.concat(overrides.galAdded || []).filter((g) => !(overrides.galDeleted || []).includes(g.id));
    base = base.map((g) => resolveEntity('gal:' + g.id, g));
    const order = overrides.galOrder || [];
    if (order.length) base.sort((a, b) => { const ia = order.indexOf(a.id), ib = order.indexOf(b.id); return (ia < 0 ? 999 : ia) - (ib < 0 ? 999 : ib); });
    return base;
  }, [overrides.galAdded, overrides.galDeleted, overrides.galOrder, overrides.entities, resolveEntity]);
  const addGalleryItem = useCallbackAd((item) => commit((o) => { o.galAdded.push(item); }), [commit]);
  const deleteGalleryItem = useCallbackAd((id) => { idbDel('gal:' + id); removeImg('gal:' + id); commit((o) => {
    if ((o.galAdded || []).some((x) => x.id === id)) o.galAdded = o.galAdded.filter((x) => x.id !== id);
    else if (!o.galDeleted.includes(id)) o.galDeleted.push(id);
    o.galOrder = (o.galOrder || []).filter((x) => x !== id);
    delete o.entities['gal:' + id];
  }); }, [commit, removeImg]);
  const moveGallery = useCallbackAd((id, dir) => {
    const cur = resolveGallery().map((g) => g.id);
    const i = cur.indexOf(id); const j = i + dir;
    if (i < 0 || j < 0 || j >= cur.length) return;
    const tmp = cur[i]; cur[i] = cur[j]; cur[j] = tmp;
    commit((o) => { o.galOrder = cur; });
  }, [resolveGallery, commit]);

  /* store (retail) */
  const resolveStore = useCallbackAd(() => {
    let base = window.SARAYA.STORE_ITEMS.concat(overrides.storeAdded || []);
    base = base.filter((i) => !(overrides.storeDeleted || []).includes(i.id)).map((i) => resolveEntity('store:' + i.id, i));
    const order = overrides.storeOrder || [];
    if (order.length) base.sort((a, b) => { const ia = order.indexOf(a.id), ib = order.indexOf(b.id); return (ia < 0 ? 999 : ia) - (ib < 0 ? 999 : ib); });
    return base;
  }, [overrides.storeAdded, overrides.storeDeleted, overrides.storeOrder, overrides.entities, resolveEntity]);
  const moveStore = useCallbackAd((id, dir) => {
    const ordered = resolveStore();
    const cur = ordered.map((i) => i.id);
    const item = ordered.find((i) => i.id === id);
    if (!item) return;
    const sameCat = ordered.filter((i) => i.category === item.category);
    const ci = sameCat.findIndex((i) => i.id === id); const nj = ci + dir;
    if (nj < 0 || nj >= sameCat.length) return;
    const ii = cur.indexOf(id), jj = cur.indexOf(sameCat[nj].id);
    const tmp = cur[ii]; cur[ii] = cur[jj]; cur[jj] = tmp;
    commit((o) => { o.storeOrder = cur; });
  }, [resolveStore, commit]);
  const addStoreItem = useCallbackAd((item) => commit((o) => { (o.storeAdded = o.storeAdded || []).push(item); }), [commit]);
  const deleteStoreItem = useCallbackAd((id) => { idbDel('store:' + id); removeImg('store:' + id); commit((o) => {
    if ((o.storeAdded || []).some((x) => x.id === id)) o.storeAdded = o.storeAdded.filter((x) => x.id !== id);
    else { o.storeDeleted = o.storeDeleted || []; if (!o.storeDeleted.includes(id)) o.storeDeleted.push(id); }
    delete o.entities['store:' + id];
  }); }, [commit, removeImg]);

  /* data import / export / reset */
  const exportData = useCallbackAd(() => {
    const payload = Object.assign({}, overrides, { images: images });
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
    const a = document.createElement('a'); a.href = URL.createObjectURL(blob);
    a.download = 'saraya-content-' + new Date().toISOString().slice(0, 10) + '.json'; a.click();
    setTimeout(() => URL.revokeObjectURL(a.href), 1000);
  }, [overrides, images]);
  const importData = useCallbackAd(() => {
    const input = document.createElement('input'); input.type = 'file'; input.accept = 'application/json,.json';
    input.style.position = 'fixed'; input.style.left = '-9999px';
    const cleanup = () => { if (input.parentNode) input.parentNode.removeChild(input); };
    input.onchange = () => { const f = input.files[0]; if (!f) { cleanup(); return; } const r = new FileReader();
      r.onload = async () => {
        try {
          const parsed = JSON.parse(r.result);
          const imgs = parsed.images || {};
          try { await idbClear(); } catch (e) {}
          for (const k of Object.keys(imgs)) { try { await idbSet(k, imgs[k]); } catch (e) {} }
          setImages(imgs);
          const rest = Object.assign(blankOverrides(), parsed); rest.images = {};
          setOverrides(rest);
          toastMsg('Content imported');
        } catch (e) { toastMsg('Invalid file', 'error'); }
        cleanup();
      };
      r.readAsText(f); };
    document.body.appendChild(input); input.click();
  }, [toastMsg]);
  const resetData = useCallbackAd(() => { if (window.confirm('Reset ALL admin changes back to the original site? This cannot be undone.')) { idbClear().catch(() => {}); setImages({}); setOverrides(blankOverrides()); toastMsg('Reset to defaults'); } }, [toastMsg]);

  const api = {
    isAdmin, editMode, setEditMode, openLogin: () => { if (window.openAuthModal) window.openAuthModal('login'); }, logout, changeCode,
    getImg, setImg, removeImg, pickImage,
    mergeEntity, resolveEntity, getText, setText,
    resolveCatalogue, addCatalogueItem, deleteCatalogueItem,
    resolveStore, addStoreItem, deleteStoreItem, moveStore,
    resolveGallery, addGalleryItem, deleteGalleryItem, moveGallery,
    openEdit: setEditConfig, openOrders: () => setOrdersOpen(true), openSubs: () => setSubsOpen(true), openStripe: () => setStripeOpen(true), exportData, importData, resetData, toastMsg, savedAt,
    overrides,
  };

  return (
    <window.AdminContext.Provider value={api}>
      {children}
      {isAdmin && <AdminBar api={api} />}
      {editConfig && <EditPanel config={editConfig} onClose={() => setEditConfig(null)} />}
      {ordersOpen && <AdminOrdersPanel onClose={() => setOrdersOpen(false)} />}
      {subsOpen && <AdminSubsPanel onClose={() => setSubsOpen(false)} />}
      {stripeOpen && <StripeSettings onClose={() => setStripeOpen(false)} />}
      {toast && <AdminToast toast={toast} />}
    </window.AdminContext.Provider>
  );
}

/* convenience hook */
function useAdmin() { return useContextAd(window.AdminContext); }

/* ---------------- Admin bar (docked left side) ---------------- */
const ADMIN_NAV = [
  {
    group: 'Platform',
    icon: 'layout-dashboard',
    items: [
      { label: 'Staff Operations',      tab: 'overview',    icon: 'layout-dashboard' },
      { label: 'Orders & Bookings',    tab: 'orders',      icon: 'package',         badge: 'orders' },
      { label: 'RFQ Requests',         tab: 'rfqs',        icon: 'file-text',        badge: 'rfqs' },
      { label: 'Leads & Inquiries',    tab: 'leads',       icon: 'inbox' },
      { label: 'Complaints & Disputes',tab: 'complaints',  icon: 'alert-circle',    badge: 'complaints' },
    ],
  },
  {
    group: 'Vendors',
    icon: 'store',
    items: [
      { label: 'Vendor Applications',  tab: 'vendors',     icon: 'user-check',      badge: 'vendors' },
      { label: 'Approved Vendors',     tab: 'vendors',     icon: 'store' },
      { label: 'Vendor Documents',     tab: 'vendors',     icon: 'file-badge' },
      { label: 'Vendor Packages',      tab: 'tiers',       icon: 'layers' },
      { label: 'Vendor Subscriptions', tab: 'tiers',       icon: 'repeat' },
      { label: 'Vendor Listings',      tab: 'marketplace', icon: 'grid-2x2' },
    ],
  },
  {
    group: 'Marketplace',
    icon: 'shopping-bag',
    items: [
      { label: 'Products',             tab: 'marketplace', icon: 'shopping-bag' },
      { label: 'Rental Items',         tab: 'marketplace', icon: 'archive' },
      { label: 'Services',             tab: 'marketplace', icon: 'briefcase' },
      { label: 'Categories',           tab: 'marketplace', icon: 'tag' },
      { label: 'Featured / Sponsored', tab: 'marketing',   icon: 'star' },
      { label: 'Virtual Showroom',     tab: 'marketplace', icon: 'monitor' },
    ],
  },
  {
    group: 'Finance',
    icon: 'banknote',
    items: [
      { label: 'Payments',             tab: 'finance',     icon: 'credit-card' },
      { label: 'Vendor Payouts',       tab: 'payouts',     icon: 'banknote' },
      { label: 'Commissions',          tab: 'finance',     icon: 'percent' },
      { label: 'Refunds',              tab: 'finance',     icon: 'rotate-ccw' },
      { label: 'Stripe Settings',      action: 'stripe',   icon: 'zap' },
    ],
  },
  {
    group: 'Marketing',
    icon: 'megaphone',
    items: [
      { label: 'Campaign Banners',     tab: 'marketing',   icon: 'image' },
      { label: 'Popup Campaigns',      tab: 'marketing',   icon: 'bell' },
      { label: 'Featured Items',       tab: 'marketing',   icon: 'star' },
      { label: 'Customer Leads',       tab: 'leads',       icon: 'users' },
      { label: 'Vendor Leads',         tab: 'leads',       icon: 'store' },
    ],
  },
  {
    group: 'Content',
    icon: 'file-text',
    items: [
      { label: 'Home Page Content',    tab: 'content',     icon: 'home' },
      { label: 'Marketplace Content',  tab: 'content',     icon: 'shopping-bag' },
      { label: 'Rentals Content',      tab: 'content',     icon: 'archive' },
      { label: 'Virtual Showroom',     tab: 'content',     icon: 'monitor' },
      { label: 'Legal Pages',          tab: 'content',     icon: 'scale' },
      { label: 'Footer / Navigation',  tab: 'content',     icon: 'navigation' },
    ],
  },
];

function AdminBar({ api }) {
  const [open,    setOpen]    = useStateAd(() => localStorage.getItem('saraya_adminbar_open') !== '0');

  const toggle = (v) => { setOpen(v); localStorage.setItem('saraya_adminbar_open', v ? '1' : '0'); };

  const goTab = (tab) => {
    if (tab) {
      localStorage.setItem('saraya_admin_tab', tab);
      if (window.setAdminDashboardTab) { window.setAdminDashboardTab(tab); }
      location.hash = '#admin-dashboard';
    }
  };

  return (
    <div style={{ position: 'fixed', insetInlineStart: 0, top: 0, bottom: 0, zIndex: 200, fontFamily: 'var(--font-body)', display: 'flex', alignItems: 'flex-start', pointerEvents: 'none' }}>
      {open ? (
        <div style={{ width: 256, height: '100vh', background: 'var(--espresso)', color: 'var(--ivory)', boxShadow: '4px 0 24px rgba(42,31,26,0.28)', display: 'flex', flexDirection: 'column', pointerEvents: 'all' }}>

          {/* Header */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '18px 16px 14px', borderBottom: '1px solid rgba(250,246,240,0.10)', flexShrink: 0 }}>
            <Icon name="shield-check" size={18} style={{ color: 'var(--gold-light)' }} />
            <span style={{ fontWeight: 700, fontSize: 13.5, letterSpacing: '0.04em', color: 'var(--gold-light)', flex: 1 }}>ADMIN MODE</span>
            <button onClick={() => toggle(false)} style={adBtnGhost} title="Collapse"><Icon name="panel-left-close" size={17} /></button>
          </div>

          {/* Dashboard button */}
          <div style={{ padding: '10px 12px 4px', flexShrink: 0 }}>
            <button onClick={() => goTab('overview')} style={{ display: 'flex', alignItems: 'center', gap: 9, width: '100%', padding: '10px 12px', borderRadius: 10, background: 'var(--gold)', color: 'var(--espresso)', border: 'none', cursor: 'pointer', fontFamily: 'var(--font-body)', fontSize: 13, fontWeight: 700 }}>
              <Icon name="layout-dashboard" size={15} /> Staff Operations
            </button>
          </div>

          {/* Content editing toggle */}
          <div style={{ padding: '8px 12px 4px', flexShrink: 0 }}>
            <label style={{ display: 'flex', alignItems: 'center', gap: 10, fontSize: 12.5, cursor: 'pointer', padding: '8px 12px', borderRadius: 8, background: 'rgba(250,246,240,0.06)' }}>
              <span onClick={() => api.setEditMode(!api.editMode)} style={{ width: 34, height: 20, borderRadius: 999, background: api.editMode ? '#C9A961' : 'rgba(250,246,240,0.22)', position: 'relative', flexShrink: 0, transition: 'background 200ms' }}>
                <span style={{ position: 'absolute', top: 2, left: api.editMode ? 16 : 2, width: 16, height: 16, borderRadius: 999, background: '#fff', transition: 'left 200ms' }} />
              </span>
              <span style={{ color: api.editMode ? 'var(--gold-light)' : 'rgba(250,246,240,0.7)', fontWeight: api.editMode ? 600 : 400 }}>
                Content Editing Mode
              </span>
            </label>
          </div>

          {/* Scrollable nav */}
          <div style={{ flex: 1, overflowY: 'auto', padding: '6px 0 8px' }}>
            <div style={{ margin: '10px 12px 6px', padding: '11px 13px', borderRadius: 8, background: 'rgba(250,246,240,0.06)', color: 'rgba(250,246,240,0.6)', fontSize: 11.5, lineHeight: 1.55, display: 'flex', gap: 8, alignItems: 'flex-start' }}>
              <Icon name="info" size={13} style={{ marginTop: 2, flexShrink: 0, color: 'var(--gold-light)' }} />
              <span>All back-office sections — orders, vendors, leads, finance — live in <strong>Staff Operations</strong>. This bar is only for inline content editing and data tools.</span>
            </div>

            {/* System section */}
            <div>
              <div style={{ padding: '8px 16px 6px', color: 'rgba(250,246,240,0.5)', fontSize: 10.5, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase' }}>System</div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 5, padding: '0 12px 8px' }}>
                <button onClick={api.exportData} style={adBtn}><Icon name="download" size={13} />Data Export</button>
                {!IS_PROD && <button onClick={api.importData} style={adBtn}><Icon name="upload" size={13} />Data Import</button>}
                {!IS_PROD && <button onClick={api.changeCode} style={adBtn}><Icon name="key-round" size={13} />Change Code</button>}
                <button onClick={() => goTab('users')} style={adBtn}><Icon name="users" size={13} />Users & Roles</button>
                <button onClick={api.openStripe} style={adBtn}><Icon name="zap" size={13} />Stripe Settings</button>
                <button onClick={api.resetData} style={{ ...adBtn, gridColumn: '1/-1', color: '#FCA5A5' }}><Icon name="rotate-ccw" size={13} />Reset Local Changes</button>
              </div>
            </div>
          </div>

          {/* Footer */}
          <div style={{ borderTop: '1px solid rgba(250,246,240,0.10)', padding: '10px 12px', flexShrink: 0 }}>
            <button onClick={api.logout} style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '9px 12px', borderRadius: 8, background: 'rgba(201,169,97,0.15)', color: 'var(--gold-light)', border: '1px solid rgba(201,169,97,0.3)', cursor: 'pointer', fontFamily: 'var(--font-body)', fontSize: 13, fontWeight: 600 }}>
              <Icon name="log-out" size={14} />Exit Admin Mode
            </button>
            <div style={{ fontSize: 10, color: 'rgba(250,246,240,0.35)', marginTop: 8, display: 'flex', alignItems: 'center', gap: 4 }}>
              <Icon name="check" size={10} style={{ color: 'var(--gold-light)' }} />
              Saved · {api.savedAt ? new Date(api.savedAt).toLocaleTimeString() : 'No changes'}
            </div>
          </div>
        </div>
      ) : (
        <button onClick={() => toggle(true)} style={{ display: 'inline-flex', alignItems: 'center', gap: 8, background: 'var(--espresso)', color: 'var(--ivory)', border: 'none', borderRadius: '0 10px 10px 0', padding: '12px 14px', cursor: 'pointer', boxShadow: '3px 0 12px rgba(42,31,26,0.25)', fontFamily: 'var(--font-body)', fontSize: 12, fontWeight: 600, marginTop: 72, pointerEvents: 'all', writingMode: 'vertical-rl', letterSpacing: '0.08em' }}>
          <Icon name="shield-check" size={15} style={{ color: 'var(--gold-light)', writingMode: 'horizontal-tb' }} />
          ADMIN
        </button>
      )}
    </div>
  );
}
const adBtn = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 5, padding: '7px 8px', borderRadius: 7, background: 'rgba(250,246,240,0.07)', color: 'rgba(250,246,240,0.75)', border: '1px solid rgba(250,246,240,0.13)', cursor: 'pointer', fontFamily: 'var(--font-body)', fontSize: 11.5, fontWeight: 500, whiteSpace: 'nowrap' };
const adBtnGhost = { background: 'none', border: 'none', color: 'rgba(250,246,240,0.6)', cursor: 'pointer', display: 'flex', padding: 4, borderRadius: 6 };

/* ---------------- Edit panel (modal form) ---------------- */
function EditPanel({ config, onClose }) {
  const { title, fields, values, onSave } = config;
  const init = {}; fields.forEach((f) => {
    const raw = dget(values, f.key);
    init[f.key] = f.type === 'list' ? (Array.isArray(raw) ? raw.join('\n') : (raw || '')) : (raw != null ? raw : '');
  });
  const [form, setForm] = useStateAd(init);
  const upd = (k, v) => setForm((p) => Object.assign({}, p, { [k]: v }));
  const save = () => {
    const flat = {}; fields.forEach((f) => { flat[f.key] = f.type === 'list' ? form[f.key].split('\n').map((s) => s.trim()).filter(Boolean) : form[f.key]; });
    onSave(nestFromFlat(flat)); onClose();
  };
  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 280, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 }}>
      <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'var(--scrim)' }} />
      <div style={{ position: 'relative', width: 'min(520px,100%)', maxHeight: '88vh', display: 'flex', flexDirection: 'column', background: 'var(--bg-canvas)', borderRadius: 18, boxShadow: 'var(--shadow-deep)', overflow: 'hidden' }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '18px 22px', borderBottom: '1px solid var(--line)', background: 'var(--white)' }}>
          <h3 style={{ fontFamily: 'var(--font-display)', fontSize: 22, fontWeight: 500 }}>{title}</h3>
          <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--fg-secondary)', display: 'flex', padding: 4 }}><Icon name="x" size={22} /></button>
        </div>
        <div style={{ padding: '20px 22px', overflowY: 'auto', display: 'grid', gap: 15 }}>
          {fields.map((f) => (
            <Field key={f.key} label={f.label}>
              {f.type === 'textarea' || f.type === 'list'
                ? <Textarea value={form[f.key]} dir={f.dir} rows={f.type === 'list' ? 5 : 3} onChange={(e) => upd(f.key, e.target.value)} placeholder={f.placeholder} />
                : f.type === 'select'
                  ? <Select value={form[f.key]} onChange={(e) => upd(f.key, e.target.value)}>{f.options.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}</Select>
                  : <TextInput value={form[f.key]} dir={f.dir} onChange={(e) => upd(f.key, e.target.value)} placeholder={f.placeholder} />}
            </Field>
          ))}
        </div>
        <div style={{ display: 'flex', gap: 10, padding: '16px 22px', borderTop: '1px solid var(--line)', background: 'var(--white)' }}>
          <Button variant="secondary" full onClick={onClose}>Cancel</Button>
          <Button variant="primary" full icon="check" onClick={save}>Save changes</Button>
        </div>
      </div>
    </div>
  );
}

/* ---------------- Card toolbar wrapper (edit / delete) ---------------- */
function AdminCardWrap({ onEdit, onDelete, children }) {
  const admin = useAdmin();
  if (!admin.editMode) return children;
  return (
    <div style={{ position: 'relative' }}>
      {children}
      <div className="saraya-admin-cardtools" style={{ position: 'absolute', top: 8, insetInlineEnd: 8, zIndex: 20, display: 'flex', gap: 6 }}
        onClick={(e) => { e.stopPropagation(); }}>
        {onEdit && <button onClick={(e) => { e.stopPropagation(); onEdit(); }} title="Edit" style={cardToolBtn}><Icon name="pencil" size={15} /></button>}
        {onDelete && <button onClick={(e) => { e.stopPropagation(); onDelete(); }} title="Delete" style={Object.assign({}, cardToolBtn, { color: 'var(--error)' })}><Icon name="trash-2" size={15} /></button>}
      </div>
    </div>
  );
}
const cardToolBtn = { width: 32, height: 32, borderRadius: 8, background: 'rgba(255,255,255,0.95)', border: '1px solid var(--line-strong)', boxShadow: 'var(--shadow-soft)', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--fg-primary)' };

/* Small inline "Edit text" pencil button (for heroes / sections) */
function AdminEditButton({ onClick, label, style }) {
  const admin = useAdmin();
  if (!admin.editMode) return null;
  return (
    <button onClick={(e) => { e.stopPropagation(); onClick(); }} style={Object.assign({ display: 'inline-flex', alignItems: 'center', gap: 7, padding: '8px 14px', borderRadius: 999, background: 'var(--espresso)', color: 'var(--ivory)', border: 'none', cursor: 'pointer', fontFamily: 'var(--font-body)', fontSize: 12.5, fontWeight: 500, boxShadow: 'var(--shadow-soft)' }, style)}>
      <Icon name="pencil" size={13} />{label || 'Edit'}
    </button>
  );
}

/* "Add" button (catalogue / gallery) */
function AdminAddButton({ onClick, label }) {
  const admin = useAdmin();
  if (!admin.editMode) return null;
  return (
    <button onClick={onClick} style={{ display: 'inline-flex', alignItems: 'center', gap: 9, padding: '12px 20px', borderRadius: 10, background: 'var(--white)', color: 'var(--fg-primary)', border: '1.5px dashed var(--gold)', cursor: 'pointer', fontFamily: 'var(--font-body)', fontSize: 14, fontWeight: 500 }}>
      <Icon name="plus" size={17} style={{ color: 'var(--gold-deep)' }} />{label}
    </button>
  );
}

/* ---------------- Toast ---------------- */
function AdminToast({ toast }) {
  return (
    <div style={{ position: 'fixed', insetInlineStart: '50%', transform: 'translateX(-50%)', bottom: 26, zIndex: 320, background: toast.type === 'error' ? 'var(--error)' : 'var(--espresso)', color: '#fff', padding: '11px 20px', borderRadius: 999, boxShadow: 'var(--shadow-deep)', fontFamily: 'var(--font-body)', fontSize: 13.5, display: 'flex', alignItems: 'center', gap: 8, animation: 'sarayaPop 240ms var(--ease-out)' }}>
      <Icon name={toast.type === 'error' ? 'alert-triangle' : 'check-circle'} size={16} style={{ color: toast.type === 'error' ? '#fff' : 'var(--gold-light)' }} />
      {toast.msg}
    </div>
  );
}

Object.assign(window, { AdminProvider, useAdmin, AdminCardWrap, AdminEditButton, AdminAddButton, compressImage });
