// v-wire.jsx — wiring-truth layer: data classification tags, session model,
// last-sync/refresh model, staff invites, media assets, audit rows, Mustard Seed registry.

// ─── Wiring classification ───────────────────────────────────────────
// Every surface is honestly labelled by what backs it in production:
// live    — reads/writes a validated Supabase table/RPC
// partial — live read or validated write exists, rest is local
// local   — sample/local data only, no DB contract yet
// pending — contract not built; control shown disabled
// soon    — future scope (Mustard Seed)
const WIRE_KIND = {
  live:    { tone:'green', dot:true,  label:'Live · DB',        title:'Backed by a validated Supabase table/RPC' },
  partial: { tone:'blue',  dot:true,  label:'Partial · live read', title:'Live read validated; remainder local until contract lands' },
  local:   { tone:'grey',  dot:false, label:'Local demo',       title:'Sample data — no database contract yet' },
  pending: { tone:'amber', dot:false, label:'Pending contract', title:'Disabled until a backend contract exists and is validated' },
  soon:    { tone:'amber', dot:false, label:'Coming soon',      title:'Future scope — not live yet' },
};
function Wire({ kind='local', style={} }) {
  const k = WIRE_KIND[kind] || WIRE_KIND.local;
  return (
    <span title={k.title} style={{ cursor:'help', display:'inline-flex', ...style }}>
      <Pill tone={k.tone} dot={k.dot} style={{ fontSize:9.5, padding:'2px 8px', letterSpacing:'0.05em', textTransform:'uppercase' }}>{k.label}</Pill>
    </span>
  );
}

// ─── Session model (Supabase Auth + active vendor_staff membership) ──
// phase: restoring → signedout | signedin | expired
const SESSION_KEY = 'lasso_vendor_session_v2';
const VENDOR_TOKEN_KEY = 'lasso_vendor_access_token_v1';
const LASSO_RUNTIME_CONFIG = window.LASSO_RUNTIME_CONFIG || {};
const VENDOR_SUPABASE_URL = LASSO_RUNTIME_CONFIG.supabaseUrl || 'https://szuvqpusxcqixtykosfd.supabase.co';
const VENDOR_PUBLISHABLE_KEY = LASSO_RUNTIME_CONFIG.supabasePublishableKey || 'sb_publishable_yZbt2eESu-UFDHUCjPO1ZA_W4n8dECt';
function vendorAuthClient() {
  if (!window.supabase?.createClient) throw new Error('Secure sign-in is unavailable. Nothing has been submitted.');
  return window.supabase.createClient(VENDOR_SUPABASE_URL, VENDOR_PUBLISHABLE_KEY, {
    auth: { persistSession:false, autoRefreshToken:false, detectSessionInUrl:false },
  });
}
const ROLES = {
  owner:   { name:'Marco Bellini', init:'MB', label:'Owner',   email:'marco@fireanddough.co.uk', access:'Full access' },
  manager: { name:'Priya Shah',    init:'PS', label:'Manager', email:'priya@fireanddough.co.uk', access:'Menu · orders · campaigns · insights' },
  staff:   { name:'Tom Okafor',    init:'TO', label:'Staff',   email:'tom@fireanddough.co.uk',   access:'Orders only' },
};
// Which pages each role can open. Enforced in UI; production mirrors this with RLS.
const ROLE_PAGES = {
  owner:   null, // all
  manager: ['overview','map','menu','orders','insights','campaigns','rewards','trail','team','ops'],
  staff:   ['orders','menu'],
};
function roleCanSee(role, page) {
  const allow = ROLE_PAGES[role];
  return !allow || allow.includes(page);
}
function readSession() {
  try { return JSON.parse(localStorage.getItem(SESSION_KEY) || 'null'); } catch(e){ return null; }
}
function clearVendorSession() {
  localStorage.removeItem(VENDOR_TOKEN_KEY);
  localStorage.removeItem(SESSION_KEY);
  window.dispatchEvent(new Event('lasso:vendor-session-expired'));
}
function vendorTokenExpired(token) {
  try {
    const payload = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
    const exp = JSON.parse(atob(payload)).exp;
    return typeof exp === 'number' && exp * 1000 <= Date.now();
  } catch { return true; }
}
async function vendorRpc(name, body, accessToken) {
  const response = await fetch(`${VENDOR_SUPABASE_URL}/rest/v1/rpc/${name}`, {
    method:'POST',
    headers:{ apikey:VENDOR_PUBLISHABLE_KEY, Authorization:`Bearer ${accessToken}`, Accept:'application/json', 'Content-Type':'application/json' },
    body:JSON.stringify(body || {}),
  });
  const text = await response.text();
  const payload = text ? JSON.parse(text) : null;
  if (!response.ok) {
    if (response.status === 401 || /jwt expired|invalid jwt/i.test(payload?.message || payload?.msg || '')) clearVendorSession();
    throw new Error(payload?.message || payload?.msg || 'Vendor access was denied.');
  }
  return payload;
}
async function vendorRead(path, accessToken) {
  const response = await fetch(`${VENDOR_SUPABASE_URL}/rest/v1/${path}`, { headers:{ apikey:VENDOR_PUBLISHABLE_KEY, Authorization:`Bearer ${accessToken}`, Accept:'application/json' } });
  const payload = await response.json();
  if (!response.ok) {
    if (response.status === 401 || /jwt expired|invalid jwt/i.test(payload?.message || payload?.msg || '')) clearVendorSession();
    throw new Error(payload?.message || payload?.msg || 'Vendor data could not be loaded.');
  }
  return payload;
}
async function vendorWrite(path, body, method='POST') {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  const response = await fetch(`${VENDOR_SUPABASE_URL}/rest/v1/${path}`, {
    method,
    headers:{ apikey:VENDOR_PUBLISHABLE_KEY, Authorization:`Bearer ${token}`, Accept:'application/json', 'Content-Type':'application/json', Prefer:'return=representation' },
    body:JSON.stringify(body),
  });
  const text = await response.text();
  const payload = text ? JSON.parse(text) : null;
  if (!response.ok) {
    if (response.status === 401 || /jwt expired|invalid jwt/i.test(payload?.message || payload?.msg || '')) clearVendorSession();
    throw new Error(payload?.message || payload?.msg || 'Vendor data could not be saved.');
  }
  return payload;
}
function vendorAccessToken() {
  const token = localStorage.getItem(VENDOR_TOKEN_KEY);
  if (!token || vendorTokenExpired(token)) { if (token) clearVendorSession(); return null; }
  return token;
}
function vendorCallbackAccessToken() {
  try { return new URLSearchParams(window.location.hash.replace(/^#/, '')).get('access_token'); }
  catch { return null; }
}
function clearVendorCallback() {
  if (window.location.hash) window.history.replaceState({}, '', `${window.location.pathname}${window.location.search}`);
}
function vendorAuthUserId() {
  const token = vendorAccessToken();
  if (!token) return null;
  try {
    const payload = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
    return JSON.parse(atob(payload)).sub || null;
  } catch { return null; }
}
async function loadVendorStaffInvites() {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRead('lasso_vendor_staff_invites?select=id,email,role,status,created_at,expires_at&order=created_at.desc&limit=50', token);
}
async function createVendorStaffInvite({ email, role }) {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRpc('lasso_vendor_create_staff_invite', { p_email:email, p_role:role.toLowerCase() }, token);
}
async function revokeVendorStaffInvite(inviteId) {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRpc('lasso_vendor_revoke_staff_invite', { p_invite_id:inviteId }, token);
}
async function acceptVendorStaffInvite(inviteId, token) {
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRpc('lasso_vendor_accept_staff_invite', { p_invite_id:inviteId }, token);
}
async function loadVendorAuditRows() {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRead('lasso_audit_logs?select=id,action,target_type,target_id,created_at,metadata&order=created_at.desc&limit=20', token);
}
async function vendorStorage(path, options={}) {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  const response = await fetch(`${VENDOR_SUPABASE_URL}/storage/v1/${path}`, { ...options, headers:{ apikey:VENDOR_PUBLISHABLE_KEY, Authorization:`Bearer ${token}`, ...(options.headers || {}) } });
  const text = await response.text();
  let payload = null;
  try { payload = text ? JSON.parse(text) : null; } catch { payload = text; }
  if (!response.ok) throw new Error(payload?.message || payload?.error || 'Vendor media could not be updated.');
  return payload;
}
function vendorMediaPath(name) {
  const session = readSession();
  if (!session?.vendorId) throw new Error('Vendor sign-in is required.');
  return `${session.vendorId}/${name.replace(/[^a-zA-Z0-9._-]/g, '-')}`;
}
async function loadVendorMediaObjects() {
  const session = readSession();
  if (!session?.vendorId) throw new Error('Vendor sign-in is required.');
  const rows = await vendorStorage('object/list/lasso-media', { method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify({ prefix:session.vendorId, limit:100, offset:0, sortBy:{ column:'created_at', order:'desc' } }) });
  return (rows || []).filter((row)=>row.name && row.name !== '.emptyFolderPlaceholder').map((row)=>({ id:row.id || row.name, name:row.name.split('/').pop(), path:`${session.vendorId}/${row.name.replace(/^.*\//, '')}`, size:Math.round(Number(row.metadata?.size || 0)/1024)+' KB', status:'live', createdAt:row.created_at }));
}
async function uploadVendorMedia(file) {
  const path = vendorMediaPath(file.name);
  await vendorStorage(`object/lasso-media/${path}`, { method:'POST', headers:{ 'Content-Type':file.type || 'application/octet-stream', 'x-upsert':'true' }, body:file });
  return path;
}
async function vendorMediaSignedUrl(storagePath) {
  const payload = await vendorStorage(`object/sign/lasso-media/${storagePath}`, {
    method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify({ expiresIn:3600 }),
  });
  return payload?.signedURL ? `${VENDOR_SUPABASE_URL}/storage/v1${payload.signedURL}` : null;
}
async function loadVendorMediaAssets() {
  const token = vendorAccessToken();
  const session = readSession();
  if (!token || !session?.vendorId) throw new Error('Vendor sign-in is required.');
  const assets = await vendorRpc('lasso_vendor_list_media_assets', { p_vendor_id:session.vendorId }, token);
  return Promise.all((assets || []).map(async (asset)=>({
    ...asset,
    name:asset.file_name,
    size:asset.metadata?.size_bytes ? `${Math.round(asset.metadata.size_bytes / 1024)} KB` : '—',
    status:'live',
    url:asset.storage_path ? await vendorMediaSignedUrl(asset.storage_path) : null,
  })));
}
async function assignVendorMenuItemMedia({ menuItemId, storagePath, fileName, contentType, sizeBytes, altText='' }) {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRpc('lasso_vendor_assign_menu_item_media', {
    p_menu_item_id:menuItemId, p_storage_path:storagePath, p_file_name:fileName,
    p_content_type:contentType || null, p_size_bytes:sizeBytes || null, p_alt_text:altText || null,
  }, token);
}
async function assignVendorStorefrontMedia(file) {
  if (!file || !/^image\//.test(file.type || '')) throw new Error('Choose an image file for the storefront cover.');
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  const storagePath = await uploadVendorMedia(file);
  return vendorRpc('lasso_vendor_assign_storefront_media', {
    p_storage_path:storagePath, p_file_name:file.name, p_content_type:file.type || null,
    p_size_bytes:file.size || null, p_alt_text:'Storefront cover for Fire & Dough',
  }, token);
}
async function loadVendorCampaigns() {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRead('lasso_campaigns?select=id,name,code,status,campaign_type,budget_total,budget_spent,discount_type,discount_value,starts_at,ends_at,rules,metadata,created_at,updated_at&order=updated_at.desc&limit=100', token);
}
async function createVendorCampaign({ name, code, budget, discountType, discountValue, startsAt, endsAt, rules, metadata }) {
  const token = vendorAccessToken();
  const session = readSession();
  const createdBy = vendorAuthUserId();
  if (!token || !session?.vendorId || !createdBy) throw new Error('Vendor sign-in is required.');
  const response = await fetch(`${VENDOR_SUPABASE_URL}/rest/v1/lasso_campaigns`, {
    method:'POST',
    headers:{ apikey:VENDOR_PUBLISHABLE_KEY, Authorization:`Bearer ${token}`, Accept:'application/json', 'Content-Type':'application/json', Prefer:'return=representation' },
    body:JSON.stringify({ vendor_id:session.vendorId, name, code, status:'active', campaign_type:'promotion', budget_total:budget, budget_spent:0, discount_type:discountType, discount_value:discountValue, starts_at:startsAt, ends_at:endsAt, created_by_auth_user_id:createdBy, rules, metadata }),
  });
  const payload = await response.json();
  if (!response.ok) throw new Error(payload?.message || payload?.msg || 'Campaign could not be saved.');
  return Array.isArray(payload) ? payload[0] : payload;
}
async function loadVendorLiveOrders() {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  const [orders, items] = await Promise.all([
    vendorRead('lasso_orders?select=id,order_code,status,fulfillment_type,total,created_at,delivery_address&order=created_at.desc&limit=100', token),
    vendorRead('lasso_order_items?select=order_id,name,quantity&limit=500', token),
  ]);
  const itemsByOrder = (items || []).reduce((all, item) => { (all[item.order_id] ||= []).push(item); return all; }, {});
  return (orders || []).map((order) => ({ ...order, items:itemsByOrder[order.id] || [] }));
}
async function loadVendorMenuItems() {
  const token = vendorAccessToken();
  const session = readSession();
  if (!token || !session?.vendorId) throw new Error('Vendor sign-in is required.');
  const [items, assets] = await Promise.all([
    vendorRpc('lasso_vendor_list_menu_items', { p_vendor_id:session.vendorId }, token),
    loadVendorMediaAssets(),
  ]);
  const assetsById = new Map((assets || []).map((asset)=>[asset.id, asset]));
  return (items || []).map((item)=>({ ...item, image_url:assetsById.get(item.image_asset_id)?.url || null }));
}
async function setVendorMenuItemAvailability(menuItemId, isAvailable) {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRpc('lasso_vendor_set_menu_item_availability', { p_menu_item_id:menuItemId, p_is_available:isAvailable }, token);
}
async function saveVendorMenuItem({ menuItemId=null, sectionName, name, price, description='', isAvailable=true, isPopular=false, metadata={} }) {
  const token = vendorAccessToken();
  const session = readSession();
  if (!token || !session?.vendorId) throw new Error('Vendor sign-in is required.');
  return vendorRpc('lasso_vendor_save_menu_item', {
    p_vendor_id:session.vendorId, p_menu_item_id:menuItemId, p_section_name:sectionName,
    p_name:name, p_price:price, p_description:description, p_is_available:isAvailable,
    p_is_popular:isPopular, p_metadata:metadata,
  }, token);
}
async function saveVendorMenuItemAllergens(menuItemId, declarations) {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRpc('lasso_vendor_set_menu_item_allergens', { p_menu_item_id:menuItemId, p_declarations:declarations }, token);
}
async function loadVendorMenuItemAllergens(menuItemId) {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRpc('lasso_vendor_list_menu_item_allergens', { p_menu_item_id:menuItemId }, token);
}
async function loadVendorStorefront() {
  const token = vendorAccessToken();
  const session = readSession();
  if (!token || !session?.vendorId) throw new Error('Vendor sign-in is required.');
  const rows = await vendorRead('lasso_vendors?select=id,name,tagline,description,cuisine,status,status_label,eta_min,eta_max,eta_label,delivery_fee,min_order,tags,metadata&id=eq.' + encodeURIComponent(session.vendorId), token);
  return rows?.[0] || null;
}
async function saveVendorStorefront(input) {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRpc('lasso_vendor_save_storefront', {
    p_name: input.name, p_tagline: input.tagline, p_description: input.description,
    p_cuisine: input.cuisine, p_status: input.status, p_delivery_fee: input.deliveryFee,
    p_min_order: input.minOrder, p_eta_min: input.etaMin, p_eta_max: input.etaMax,
    p_tags: input.tags, p_metadata: input.metadata,
  }, token);
}
async function loadVendorChallenges() {
  const token = vendorAccessToken();
  const session = readSession();
  if (!token || !session?.vendorId) throw new Error('Vendor sign-in is required.');
  const [challenges, events] = await Promise.all([
    vendorRead('lasso_vendor_challenges?select=id,title,status,reward_type,reward_value,budget_total,budget_spent,rules,starts_at,ends_at,metadata,created_at&vendor_id=eq.' + encodeURIComponent(session.vendorId) + '&order=created_at.desc', token),
    vendorRead('lasso_vendor_challenge_events?select=challenge_id&limit=500', token),
  ]);
  const completions = (events || []).reduce((counts, event)=>{ counts[event.challenge_id] = (counts[event.challenge_id] || 0) + 1; return counts; }, {});
  return (challenges || []).map((challenge)=>({ ...challenge, completion_count:completions[challenge.id] || 0 }));
}
async function saveVendorChallenge(input) {
  const session = readSession();
  if (!session?.vendorId) throw new Error('Vendor sign-in is required.');
  const now = new Date();
  const end = new Date(now.getTime() + Number(input.days || 7) * 86400000);
  const body = {
    vendor_id:session.vendorId, title:input.title, status:input.status, reward_type:'xp', reward_value:Number(input.xp),
    budget_total:Number(input.budget), rules:{ type:input.type, value_mark:Number(input.mark), unit:input.unit, proof:input.proof, reward_xp:Number(input.xp), budget_cap:Number(input.budget), duration_days:Number(input.days), customer_copy:input.copy },
    starts_at:input.status === 'draft' ? null : now.toISOString(), ends_at:end.toISOString(),
    created_by_auth_user_id:vendorAuthUserId(), metadata:{ source:'vendor-os-trail-studio' },
  };
  if (input.id) {
    delete body.vendor_id; delete body.created_by_auth_user_id;
    const rows = await vendorWrite('lasso_vendor_challenges?id=eq.' + encodeURIComponent(input.id), body, 'PATCH');
    return rows?.[0] || null;
  }
  const rows = await vendorWrite('lasso_vendor_challenges', body);
  return rows?.[0] || null;
}
async function transitionVendorOrder(orderId, toStatus) {
  const token = vendorAccessToken();
  if (!token) throw new Error('Vendor sign-in is required.');
  return vendorRpc('lasso_transition_order_status', { p_order_id:orderId, p_to_status:toStatus, p_note:'Updated from Lasso Vendor OS', p_metadata:{ source:'vendor-os-live-orders' } }, token);
}
function vendorSessionFromPayload(payload) {
  const email = payload.email || '';
  const local = email.split('@')[0] || 'Vendor';
  return { role:payload.role, vendorId:payload.vendor_id, vendorName:payload.vendor_name, email, name:local.replace(/[._-]+/g, ' '), init:payload.initials || local.slice(0,2).toUpperCase(), signedInAt:Date.now() };
}
async function signInVendor({ email, password, captchaToken, inviteId=null }) {
  const result = await vendorAuthClient().auth.signInWithPassword({ email, password, options:{ captchaToken } });
  if (result.error || !result.data?.session?.access_token) throw new Error(result.error?.message || 'Sign-in failed.');
  const accessToken = result.data.session.access_token;
  if (inviteId) await acceptVendorStaffInvite(inviteId, accessToken);
  const session = vendorSessionFromPayload(await vendorRpc('lasso_vendor_authenticated_session', {}, accessToken));
  localStorage.setItem(VENDOR_TOKEN_KEY, accessToken);
  localStorage.setItem(SESSION_KEY, JSON.stringify(session));
  return session;
}
async function signUpVendor({ email, password, captchaToken, inviteId }) {
  if (!inviteId) throw new Error('A Vendor invitation link is required to create a kitchen account.');
  const redirectTo = new URL(window.location.href);
  redirectTo.searchParams.set('invite', inviteId);
  redirectTo.hash = '';
  const result = await vendorAuthClient().auth.signUp({ email, password, options:{ emailRedirectTo:redirectTo.toString(), captchaToken } });
  if (result.error) throw new Error(result.error.message || 'Account creation failed.');
  const accessToken = result.data?.session?.access_token;
  if (!accessToken) return { confirmationRequired:true };
  await acceptVendorStaffInvite(inviteId, accessToken);
  const session = vendorSessionFromPayload(await vendorRpc('lasso_vendor_authenticated_session', {}, accessToken));
  localStorage.setItem(VENDOR_TOKEN_KEY, accessToken);
  localStorage.setItem(SESSION_KEY, JSON.stringify(session));
  return { confirmationRequired:false, session };
}
async function restoreVendorSession() {
  const callbackToken = vendorCallbackAccessToken();
  const token = localStorage.getItem(VENDOR_TOKEN_KEY) || callbackToken;
  if (!token) return null;
  const inviteId = new URLSearchParams(window.location.search).get('invite');
  if (callbackToken && inviteId) await acceptVendorStaffInvite(inviteId, callbackToken);
  const session = vendorSessionFromPayload(await vendorRpc('lasso_vendor_authenticated_session', {}, token));
  localStorage.setItem(SESSION_KEY, JSON.stringify(session));
  localStorage.setItem(VENDOR_TOKEN_KEY, token);
  if (callbackToken) clearVendorCallback();
  return session;
}
function useSession() {
  // Never briefly render the application before the signed session is verified.
  // The server-side membership RPC remains the authority for every restored session.
  const [phase, setPhase] = React.useState('restoring');
  const [sess, setSess] = React.useState(null);
  React.useEffect(()=>{
    let mounted = true;
    const restore = async () => {
      try {
        const s = await restoreVendorSession();
        if (mounted && s && s.role && ROLES[s.role]) { setSess(s); setPhase('signedin'); }
        else if (mounted) setPhase('signedout');
      } catch { localStorage.removeItem(VENDOR_TOKEN_KEY); localStorage.removeItem(SESSION_KEY); if (mounted) setPhase('signedout'); }
    };
    const t = setTimeout(restore, 650);
    const onExpired = ()=>{ if (mounted) { setSess(null); setPhase('signedout'); } };
    window.addEventListener('lasso:vendor-session-expired', onExpired);
    return ()=>{ mounted=false; clearTimeout(t); window.removeEventListener('lasso:vendor-session-expired', onExpired); };
  }, []);
  const signIn = (session) => {
    setSess(session); setPhase('signedin');
  };
  const signOut = () => { localStorage.removeItem(VENDOR_TOKEN_KEY); localStorage.removeItem(SESSION_KEY); setSess(null); setPhase('signedout'); };
  const expire  = () => { setPhase('expired'); };
  const restore = () => { setPhase('signedin'); };
  return { phase, sess, signIn, signOut, expire, restore };
}

// ─── Last-sync / refresh model ───────────────────────────────────────
// Realtime is not implemented — the honest model is "last updated + refresh".
function LastSync({ onRefresh, label='Updated' }) {
  const [at, setAt] = React.useState(Date.now());
  const [busy, setBusy] = React.useState(false);
  const [, tick] = React.useState(0);
  React.useEffect(()=>{ const t=setInterval(()=>tick(x=>x+1), 5000); return ()=>clearInterval(t); }, []);
  const secs = Math.max(0, Math.round((Date.now()-at)/1000));
  const ago = secs<8 ? 'just now' : secs<60 ? `${secs}s ago` : `${Math.round(secs/60)}m ago`;
  const refresh = () => {
    if (busy) return;
    setBusy(true);
    setTimeout(()=>{ setAt(Date.now()); setBusy(false); onRefresh && onRefresh(); }, 700);
  };
  return (
    <span style={{ display:'inline-flex', alignItems:'center', gap:8 }}>
      <span style={{ fontFamily:'var(--font-mono)', fontSize:10.5, color:'var(--warm-stone)', whiteSpace:'nowrap' }}>{label} {ago}</span>
      <button onClick={refresh} title="Fetch latest from Supabase" style={{ display:'inline-flex', alignItems:'center', gap:5, padding:'4px 10px', borderRadius:9, border:'1px solid var(--soft-line)', background:'var(--bg-surface)', cursor:'pointer', fontFamily:'var(--font-display)', fontWeight:700, fontSize:11, color:'var(--fg-primary)' }}>
        <span style={{ display:'inline-flex', animation: busy?'v-spin 800ms linear infinite':'none' }}><I.refresh c="var(--brand-orange)" s={12}/></span>
        {busy?'Refreshing…':'Refresh'}
      </button>
    </span>
  );
}

// ─── Staff invites (public.lasso_vendor_staff_invites) ───────────────
// Invite creation is browser-validated. Email delivery + accept flow have no
// contract yet — shown as pending, never implied to work.
const STAFF_INVITES = [
  { id:'inv-9d42', email:'sofia.r@outlook.com',  role:'Manager', sent:'2 days ago', status:'pending' },
  { id:'inv-8c17', email:'jake.kitchen@gmail.com', role:'Staff', sent:'6 days ago', status:'pending' },
  { id:'inv-7b03', email:'old.hire@yahoo.co.uk', role:'Staff',  sent:'31 days ago', status:'expired' },
];

// ─── Media assets (private bucket lasso-media, vendor-scoped paths) ──
// Upload/update/delete policies are validated. Archive + moderation have no
// contract yet — controls render disabled with a pending tag.
const MEDIA_ASSETS = [
  { id:'md1', name:'pepperoni-storm-hero.jpg', kind:'Item photo',  size:'412 KB', path:'fd-2841/menu/pepperoni-storm-hero.jpg', status:'live',       linked:'Smoky Pepperoni Storm' },
  { id:'md2', name:'family-feast-bundle.jpg',  kind:'Item photo',  size:'388 KB', path:'fd-2841/menu/family-feast-bundle.jpg',  status:'live',       linked:'Family Feast Bundle' },
  { id:'md3', name:'storefront-night.jpg',     kind:'Storefront',  size:'1.2 MB', path:'fd-2841/brand/storefront-night.jpg',    status:'live',       linked:'Storefront profile' },
  { id:'md4', name:'loaded-fries-close.jpg',   kind:'Item photo',  size:'509 KB', path:'fd-2841/menu/loaded-fries-close.jpg',   status:'processing', linked:null },
];

// ─── Onboarding / application status (vendor account) ────────────────
const ONBOARD_STEPS = [
  { id:'app',   label:'Application submitted',       state:'done',    wire:'live',    note:'Stored in vendor application table' },
  { id:'biz',   label:'Business details confirmed',  state:'done',    wire:'live',    note:'Company no. · address · cuisine' },
  { id:'menu',  label:'Menu imported',               state:'done',    wire:'live',    note:'15 items across 5 categories' },
  { id:'bank',  label:'Payout account added',        state:'done',    wire:'live',    note:'Barclays Business ···4821' },
  { id:'docs',  label:'Document & payout verification', state:'pending', wire:'pending', note:'Admin review flow not built yet — status will update here' },
];

// ─── Audit activity (read-only; rows exist only for real mutations) ──
const AUDIT_ROWS = [
  { when:'14:32 today', actor:'Marco Bellini', action:'Order status → completed', target:'#4814 · Chris D.', kind:'orders' },
  { when:'13:05 today', actor:'Priya Shah',    action:'Campaign paused',           target:'Wolverton Win-Back', kind:'campaigns' },
  { when:'11:48 today', actor:'Marco Bellini', action:'Staff invite created',      target:'sofia.r@outlook.com · Manager', kind:'team' },
  { when:'Yesterday',   actor:'Marco Bellini', action:'Media uploaded',            target:'loaded-fries-close.jpg', kind:'media' },
  { when:'Yesterday',   actor:'Priya Shah',    action:'Menu item availability off', target:'Garden Veg Stonebake', kind:'menu' },
  { when:'2 days ago',  actor:'Marco Bellini', action:'Campaign created',          target:'Pepperoni First Bite · Bletchley', kind:'campaigns' },
];

// ─── Mustard Seed — future POS / inventory platform (vendor-only) ────
const MS_AREAS = [
  { id:'register', label:'Register',        icon:'grid' },
  { id:'pos',      label:'POS terminal',    icon:'zap' },
  { id:'inventory',label:'Inventory & stock', icon:'layers' },
  { id:'recipes',  label:'Recipes & COGS',  icon:'utensils' },
  { id:'suppliers',label:'Suppliers',       icon:'truck' },
  { id:'po',       label:'Purchase orders', icon:'receipt' },
  { id:'barcode',  label:'Barcode & labels',icon:'tag' },
  { id:'cash',     label:'Cash drawer',     icon:'pound' },
];

function MustardSeedModal({ area, onClose }) {
  return (
    <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:950, background:'rgba(12,8,6,0.55)', backdropFilter:'blur(6px)', display:'flex', alignItems:'center', justifyContent:'center', animation:'v-fade 180ms ease-out' }}>
      <div onClick={e=>e.stopPropagation()} style={{ width:480, maxWidth:'calc(100vw - 48px)', background:'var(--dark-chocolate)', color:'var(--dust-cream)', borderRadius:22, border:'1px solid rgba(255,255,255,0.14)', boxShadow:'0 40px 90px -20px rgba(0,0,0,0.8)', padding:'30px 30px 26px', animation:'v-pop 240ms var(--ease-out)', position:'relative' }}>
        <button onClick={onClose} style={{ position:'absolute', top:16, right:16, width:32, height:32, borderRadius:10, border:'1px solid rgba(255,255,255,0.14)', background:'rgba(255,255,255,0.06)', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center' }}><I.x c="#B6A595" s={15}/></button>
        <div style={{ display:'flex', alignItems:'center', gap:14, marginBottom:18 }}>
          <div style={{ width:52, height:52, borderRadius:15, background:'linear-gradient(135deg,#E0992A,#B8741A)', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0, boxShadow:'0 10px 24px -8px rgba(224,153,42,0.6)' }}>
            <I.droplet c="#fff" s={24} f/>
          </div>
          <div>
            <div style={{ fontFamily:'var(--font-display)', fontWeight:900, fontSize:20, letterSpacing:'-0.01em' }}>Mustard Seed is coming soon</div>
            <div style={{ fontFamily:'var(--font-body)', fontSize:12, color:'#9F8979', marginTop:3 }}>{area ? `${area} lives in Mustard Seed` : 'Back-of-house platform for Lasso vendors'}</div>
          </div>
        </div>
        <p style={{ margin:'0 0 14px', fontFamily:'var(--font-body)', fontSize:13.5, lineHeight:1.6, color:'#D8CCC0' }}>
          Mustard Seed is the register, POS and inventory platform we're building alongside Lasso. Register, stock, recipes, suppliers, purchase orders, cash drawer and terminal controls are <strong style={{ color:'var(--dust-cream)' }}>not live yet</strong> — nothing here shows real numbers until they are.
        </p>
        <div style={{ display:'flex', gap:10, alignItems:'flex-start', padding:'12px 14px', background:'rgba(46,139,78,0.12)', border:'1px solid rgba(46,139,78,0.3)', borderRadius:12, marginBottom:20 }}>
          <I.check c="#5B8F6E" s={16}/>
          <span style={{ fontFamily:'var(--font-body)', fontSize:12.5, lineHeight:1.5, color:'#D8CCC0' }}>Your Lasso delivery orders, menu visibility and this vendor dashboard keep working as normal today.</span>
        </div>
        <Btn kind="accent" full onClick={onClose}>Back to the dashboard</Btn>
      </div>
    </div>
  );
}

Object.assign(window, {
  WIRE_KIND, Wire, SESSION_KEY, ROLES, ROLE_PAGES, roleCanSee, readSession, useSession, signInVendor, signUpVendor, loadVendorLiveOrders, loadVendorMenuItems, setVendorMenuItemAvailability, saveVendorMenuItem, saveVendorMenuItemAllergens, loadVendorMenuItemAllergens, loadVendorStorefront, saveVendorStorefront, loadVendorChallenges, saveVendorChallenge, transitionVendorOrder, loadVendorStaffInvites, createVendorStaffInvite, revokeVendorStaffInvite, acceptVendorStaffInvite, loadVendorAuditRows, uploadVendorMedia, loadVendorMediaAssets, assignVendorMenuItemMedia, assignVendorStorefrontMedia, loadVendorCampaigns, createVendorCampaign,
  LastSync, STAFF_INVITES, MEDIA_ASSETS, ONBOARD_STEPS, AUDIT_ROWS, MS_AREAS, MustardSeedModal,
});
