/* StrataSnap — desktop council dashboard. Babel/JSX. Reuses window.SS + ui.jsx
   (Icon, StatusDot, Avatar, QRGraphic). Own store + components + accent picker. */
const { useState, useEffect } = React;

const ACCENTS = (window.SS && window.SS.APP_THEMES) || [
{ key: 'blue', acc: '#1E50C8', strong: '#1A45AE', text: '#1A45AE', tint: '#EAF0FC', border: '#C9D8F6' }];

function applyAccent(value) {
  const themes = (window.SS && window.SS.APP_THEMES) || ACCENTS;
  const a = themes.find((x) => x.key === value || x.acc === value) || themes[0];
  const r = document.documentElement.style;
  r.setProperty('--acc', a.acc);r.setProperty('--acc-strong', a.strong);r.setProperty('--acc-text', a.text);
  r.setProperty('--acc-tint', a.tint);r.setProperty('--acc-tint-border', a.border);
}

const PEOPLE = ['Dana Lee', 'Alex Morgan', 'Property manager', 'Preferred plumber'];
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "#1E50C8", "buildingSize": 86, "heroSpacing": 36, "showEyebrow": false } /*EDITMODE-END*/;

function DApp() {
  const SS = window.SS;
  const Store = window.SSStore;
  const Api = window.SSPilotApi;
  const params = new URLSearchParams(window.location.search || '');
  const apiEnabled = !!(Api && Api.enabled());
  const directOnboarding = params.get('onboarding') === '1' || params.get('demo') === 'onboarding' || params.get('view') === 'setup';
  const forceIntro = params.get('intro') === '1' || params.get('demo') === 'intro';
  const dashboardOverride = params.get('dashboard') === '1' || params.get('view') === 'dashboard';
  useEffect(() => {
    if (!apiEnabled && directOnboarding && !Store.getSession('council').authed) {
      Store.signIn({ role: 'council', email: 'dana.lee@harbourview.ca' });
    }
  }, []);
  const [authed, setAuthed] = useState(() => (apiEnabled ? Api.getSession('council').authed : Store.getSession('council').authed) || (!apiEnabled && directOnboarding));
  const [accent, setAccent] = useState('blue');
  useEffect(() => {applyAccent(accent);}, [accent]);
  const [tw, setTweak] = useTweaks(TWEAK_DEFAULTS);
  useEffect(() => {
    const r = document.documentElement;
    r.style.setProperty('--dl-illus-w', tw.buildingSize + '%');
    r.style.setProperty('--dl-hero-gap', tw.heroSpacing + 'px');
    r.setAttribute('data-eyebrow', tw.showEyebrow ? 'on' : 'off');
  }, [tw.buildingSize, tw.heroSpacing, tw.showEyebrow]);
  const [tickets, setTickets] = useState(() => Store.loadTickets(() => SS.seed()));
  const [setup, setSetup] = useState(() => Store.loadSetup(() => SS.setupSeed()));
  useEffect(() => {applyAccent(setup.appTheme || accent);}, [setup.appTheme, accent]);
  const [nav, setNav] = useState(directOnboarding ? 'setup' : 'queue'); // queue | activity | setup | usage | posters | settings
  const [view, setView] = useState('All');
  const [selId, setSelId] = useState(null);
  const [modal, setModal] = useState(null);
  const [toast, setToast] = useState(null);
  useEffect(() => {if (!toast) return;const t = setTimeout(() => setToast(null), 2600);return () => clearTimeout(t);}, [toast]);
  useEffect(() => {if (!apiEnabled) Store.saveTickets(tickets);}, [tickets]);
  useEffect(() => {if (!apiEnabled) Store.saveSetup(setup);}, [setup]);
  const refreshFromApi = () => Api.hydrate().then((data) => {setSetup(data.setup);setTickets(data.tickets);return data;});
  const firstRunSetup = authed && setup && !setup.activated && !dashboardOverride;
  useEffect(() => {
    if (firstRunSetup && nav !== 'setup') {
      setNav('setup');
      setSelId(null);
    }
  }, [firstRunSetup, nav]);
  useEffect(() => {
    if (!apiEnabled || !authed) return;
    let cancelled = false;
    refreshFromApi().then(() => {
      if (!cancelled) setToast('Connected to pilot API');
    }).catch((err) => setToast('Pilot API unavailable: ' + err.message));
    return () => {cancelled = true;};
  }, [apiEnabled, authed]);
  useEffect(() => {
    if (!apiEnabled || authed || !Api.completeMagicLinkToken) return;
    const loginToken = params.get('login_token');
    if (!loginToken) return;
    let cancelled = false;
    Api.completeMagicLinkToken(loginToken).then((session) => {
      if (cancelled || !session) return;
      setAuthed(true);
      setToast('Signed in with email link');
      if (window.history && window.history.replaceState) {
        const clean = new URL(window.location.href);
        clean.searchParams.delete('login_token');
        window.history.replaceState(null, document.title, clean.pathname + clean.search + clean.hash);
      }
    }).catch((err) => setToast('Email sign-in failed: ' + err.message));
    return () => {cancelled = true;};
  }, [apiEnabled, authed]);
  useEffect(() => {
    if (!apiEnabled || authed || !Api.completeSupabaseRedirect) return;
    let cancelled = false;
    Api.completeSupabaseRedirect().then((session) => {
      if (cancelled || !session) return;
      setAuthed(true);
      setToast('Signed in with Supabase');
    }).catch((err) => setToast('Supabase sign-in failed: ' + err.message));
    return () => {cancelled = true;};
  }, [apiEnabled, authed]);

  const patch = (id, fn) => setTickets((ts) => ts.map((t) => t.id === id ? fn(t) : t));
  const addEvents = (id, evs) => setTickets((ts) => ts.map((t) => t.id === id ? { ...t, timeline: [...t.timeline, ...evs] } : t));
  const patchSetup = (patch) => setSetup((s) => {
    const next = SS.normalizeSetup(typeof patch === 'function' ? patch(s) : { ...s, ...patch });
    if (apiEnabled) {
      Api.saveSetup(next).catch((err) => setToast('Setup saved locally; API sync failed: ' + err.message));
    }
    return next;
  });
  const nextId = () => {
    const nums = tickets.map((t) => parseInt(t.id.split('-')[1] || '0', 10)).filter((n) => !isNaN(n));
    return 'SS-' + ((nums.length ? Math.max(...nums) : 1042) + 1);
  };
  const cap = (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
  function createCouncilTicket(draft) {
    const id = nextId();
    const t = {
      id, token: Math.random().toString(36).slice(2, 8), locationId: draft.locationId, locationName: draft.locationName,
      category: draft.category, summary: cap(draft.summary.trim()), status: 'New', priority: 'Normal',
      responsibility: null, assignee: null, source: draft.source,
      reporterName: draft.name || 'Logged by council', reporterEmail: draft.email || '', reporterPhone: draft.phone || '',
      unit: draft.unit || '', photos: [], voiceNotes: [], createdAt: SS.NOW.toISOString(), nextStep: 'Needs triage',
      timeline: [{ id: SS.uid('e'), at: SS.NOW.toISOString(), kind: 'submitted', body: 'Logged by council (' + draft.source + ').', author: 'Dana Lee' }]
    };
    setTickets((ts) => [t, ...ts]);setModal(null);setSelId(id);setToast(id + ' added to the issue inbox');
    if (apiEnabled) {
      Api.createTicket({ ...draft, id: t.id, token: t.token, note: draft.summary, reporterName: draft.name, reporterEmail: draft.email }).then(() => refreshFromApi()).then((data) => {
        const newest = (data.tickets || [])[0];
        if (newest) setSelId(newest.id);
      }).catch((err) => setToast('Saved locally; API write failed: ' + err.message));
    }
  }

  const sel = tickets.find((t) => t.id === selId);
  return (
    <React.Fragment>
      {!authed ? <DLogin onAuthed={(email) => {
        if (apiEnabled) {
          return Api.signIn(email || 'dana.lee@harbourview.ca').then((result) => {
            if (result && result.pending) {
              setToast('Sign-in link sent');
              return 'Sign-in link sent. Check your inbox for the StrataSnap sign-in link.';
            }
            setAuthed(true);setToast('Signed in with pilot API');
            return '';
          }).catch((err) => {
            setToast('Pilot API sign-in failed: ' + err.message);
            throw err;
          });
        } else {
          Store.signIn({ role: 'council', email });setAuthed(true);
          return 'Signed in locally.';
        }
      }} /> :
      <div className={'d-app' + (firstRunSetup ? ' d-app-setup-focus' : '')}>
          {firstRunSetup ?
          <DesktopSetup setup={setup} patchSetup={patchSetup} setToast={setToast} startAtFirst forceIntro={forceIntro} /> :
          <React.Fragment>
            <SideNav nav={nav} setNav={(n) => {setNav(n);setSelId(null);}} tickets={tickets} setup={setup} accent={accent} setAccent={setAccent} apiEnabled={apiEnabled} onSignOut={() => {if (apiEnabled) Api.signOut(); else Store.signOut();setAuthed(false);}} />
            {nav === 'queue' ?
        <div className="d-main">
              <QueueList tickets={tickets} setup={setup} view={view} setView={setView} selId={selId} onSelect={setSelId} onNew={() => setModal({ type: 'new' })} />
              {sel && <DetailPanel ticket={sel} onClose={() => setSelId(null)} setModal={setModal} patch={patch} setToast={setToast} />}
            </div> :
        nav === 'activity' ?
        <div className="d-main">
              <UpdatesFeed tickets={tickets} onOpen={setSelId} />
              {sel && <DetailPanel ticket={sel} onClose={() => setSelId(null)} setModal={setModal} patch={patch} setToast={setToast} />}
            </div> :
        nav === 'setup' ?
        <DesktopSetup setup={setup} patchSetup={patchSetup} setToast={setToast} startAtFirst={directOnboarding} forceIntro={forceIntro} /> :
        nav === 'usage' ?
        <UsageReport tickets={tickets} setup={setup} setToast={setToast} /> :
        nav === 'settings' ?
        <SettingsScreen setup={setup} patchSetup={patchSetup} setToast={setToast} onOpenSetup={() => setNav('setup')} pilotApi={apiEnabled ? Api : null} refreshFromApi={refreshFromApi} /> :
        <PostersGrid tickets={tickets} setup={setup} setToast={setToast} />}
          </React.Fragment>}

          {modal && modal.type === 'new' && <NewTicketModal onClose={() => setModal(null)} onCreate={createCouncilTicket} />}
          {modal && modal.type !== 'new' && <Composers modal={modal} ticket={sel} onClose={() => setModal(null)} addEvents={addEvents} patch={patch} setToast={setToast} pilotApi={apiEnabled ? Api : null} refreshFromApi={refreshFromApi} />}
          {toast && <div className="d-toast"><Icon name="check" size={17} />{toast}</div>}
        </div>
      }
      <TweaksPanel>
        <TweakSection label="Brand" />
        <div style={{ fontSize: 13, color: '#9a948a', padding: '2px 2px 0', lineHeight: 1.5 }}>
          App accent presets are selected in setup and stay inside the StrataSnap design system.
        </div>
        <TweakSection label="Login hero" />
        <TweakSlider label="Building size" value={tw.buildingSize} min={50} max={110} step={2} unit="%" onChange={(v) => setTweak('buildingSize', v)} />
        <TweakSlider label="Vertical spacing" value={tw.heroSpacing} min={16} max={56} step={2} unit="px" onChange={(v) => setTweak('heroSpacing', v)} />
        <TweakToggle label="Show eyebrow tag" value={tw.showEyebrow} onChange={(v) => setTweak('showEyebrow', v)} />
      </TweaksPanel>
    </React.Fragment>);

}

function DLogin({ onAuthed }) {
  const [email, setEmail] = useState('dana.lee@harbourview.ca');
  const [pw, setPw] = useState('');
  const [show, setShow] = useState(false);
  const [notice, setNotice] = useState('');
  const submit = (mode) => {
    const trimmed = String(email || '').trim();
    if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
      setNotice('Enter a valid council email address.');
      return;
    }
    const result = onAuthed(trimmed, mode);
    if (result && typeof result.then === 'function') {
      result.then((message) => {
        if (message) setNotice(message);
      }).catch((err) => setNotice(err && err.message ? err.message : 'Sign-in failed. Try again.'));
    } else {
      setNotice(mode === 'email' ? 'Checking council access...' : 'Checking sign-in provider...');
    }
  };
  const providerNotice = (provider) => setNotice(provider + ' sign-in is not connected for this pilot yet. Use the email magic link.');
  return (
    <div className="d-login2">
      <div className="dl-left">
        <img className="dl-illus" src="art/condo-cluster.png" alt="" aria-hidden="true" onError={(e) => {e.target.style.display = 'none';}} />
        <div className="dl-left-in">
          <Logo tone="light" size={30} />
          <div className="dl-eyebrow">For strata communities</div>
          <h1>Building issues, clearly tracked.</h1>
          <p>QR reporting, council updates, and clean issue records for strata communities.</p>
          <div className="dl-trust"><span className="dl-trust-ic"><Icon name="lock" size={15} /></span> Private council dashboard · No resident app required.</div>
        </div>
        <div className="dl-deco" aria-hidden="true"></div>
      </div>
      <div className="dl-right">
        <div className="dl-card">
          <h2>Sign in to StrataSnap</h2>
          <p className="dl-cardsub">Access your building issue inbox and council records.</p>
          <label className="lbl-sm" style={{ marginTop: 20 }}>Email address</label>
          <div className="dl-field"><Icon name="message" size={18} /><input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@email.com" /></div>
          <button className="d-btn acc full" style={{ height: 50, marginTop: 6 }} onClick={() => submit('email')}>Email me a sign-in link <Icon name="send" size={16} /></button>
          {notice && <div className="dl-notice" role="status">{notice}</div>}
          <div className="dl-or"><span>OR</span></div>
          <button className="dl-google" onClick={() => providerNotice('Google')}><span className="g-mark">G</span> Continue with Google</button>
          <button className="dl-google" style={{ marginTop: 10 }} onClick={() => providerNotice('Microsoft')}><span className="ms-mark" aria-hidden="true"><svg width="15" height="15" viewBox="0 0 16 16"><rect width="7" height="7" fill="#F25022" /><rect x="9" width="7" height="7" fill="#7FBA00" /><rect y="9" width="7" height="7" fill="#00A4EF" /><rect x="9" y="9" width="7" height="7" fill="#FFB900" /></svg></span> Continue with Microsoft</button>
          <div className="dl-links"><button className="linkbtn" onClick={() => setNotice('Use the email magic link above to sign in for this pilot.')}>Forgot password?</button><span className="si-div" /><button className="linkbtn" onClick={() => setNotice('Request noted. For the pilot, use the council test email or ask an admin to invite you in Supabase.')}>Request access</button></div>
        </div>
        <button className="dl-resident" onClick={() => { window.location.href = 'StrataSnap.html'; }}>Trying to report an issue? <span>Track a report →</span></button>
        <div className="dl-foot">StrataSnap is a product by <b>StrataMatch</b>.</div>
      </div>
    </div>);

}

function SideNav({ nav, setNav, tickets, setup, accent, setAccent, onSignOut, apiEnabled }) {
  const SS = window.SS;
  const readiness = SS.setupProgress(setup);
  const open = (t) => SS.isOpenTicket(t);
  const openCount = tickets.filter(open).length;
  const counts = {
    New: tickets.filter((t) => t.status === 'New').length,
    Aging: tickets.filter((t) => open(t) && SS.ageDays(t) > 14).length,
    Waiting: tickets.filter((t) => t.status.startsWith('Waiting')).length,
    Resolved: tickets.filter((t) => t.status === 'Resolved').length
  };
  return (
    <div className="d-side">
      <div className="brand"><Logo tone="light" size={21} /></div>
      <div className="bldg">
        <b>{setup.buildingName}</b>
        <span>{setup.strataPlan} · {setup.lots} lots · {openCount} open</span>
        {apiEnabled && <em className="api-mode-badge">Pilot API</em>}
      </div>
      <div className="d-nav">
        <button className={'d-navitem' + (nav === 'queue' ? ' on' : '')} onClick={() => setNav('queue')}><span className="ss-nav-ic"><Icon name="ssInbox" size={18} /></span> Issue inbox <span className="ct">{openCount}</span></button>
        <button className={'d-navitem' + (nav === 'activity' ? ' on' : '')} onClick={() => setNav('activity')}><span className="ss-nav-ic"><Icon name="ssUpdate" size={18} /></span> Activity <span className="ct">{SS.feed(tickets, 99).length}</span></button>
        <button className={'d-navitem' + (nav === 'setup' ? ' on' : '')} onClick={() => setNav('setup')}><span className="ss-nav-ic"><Icon name="shieldCheck" size={18} /></span> Setup <span className="ct">{readiness.percent}%</span></button>
        <button className={'d-navitem' + (nav === 'usage' ? ' on' : '')} onClick={() => setNav('usage')}><span className="ss-nav-ic"><Icon name="files" size={18} /></span> Usage report</button>
        <button className={'d-navitem' + (nav === 'posters' ? ' on' : '')} onClick={() => setNav('posters')}><span className="ss-nav-ic"><Icon name="ssPoster" size={18} /></span> Poster network</button>
        <button className={'d-navitem' + (nav === 'settings' ? ' on' : '')} onClick={() => setNav('settings')}><Icon name="cog" size={18} /> Settings</button>
      </div>
      <div className="views-h">Status</div>
      <div className="d-nav">
        <div className="d-navitem" style={{ cursor: 'default' }}><span className="dot" style={{ width: 8, height: 8, borderRadius: 9, background: 'var(--st-blue)' }} /> New <span className="ct">{counts.New}</span></div>
        <div className="d-navitem" style={{ cursor: 'default' }}><span className="dot" style={{ width: 8, height: 8, borderRadius: 9, background: 'var(--st-amber)' }} /> Waiting <span className="ct">{counts.Waiting}</span></div>
        <div className="d-navitem" style={{ cursor: 'default' }}><span className="dot" style={{ width: 8, height: 8, borderRadius: 9, background: 'var(--st-urgent)' }} /> Overdue <span className="ct">{counts.Aging}</span></div>
      </div>
      <div className="spacer" />
      <div className="d-user">
        <span className="av">DL</span>
        <div><div className="nm">Dana Lee</div><div className="rl">Council chair</div></div>
        <button className="out" title="Sign out" onClick={onSignOut}><Icon name="refresh" size={16} /></button>
      </div>
    </div>);

}

function QueueList({ tickets, setup, view, setView, selId, onSelect, onNew }) {
  const SS = window.SS;
  const [viewMode, setViewMode] = useState('inbox'); // inbox | list | map
  const [drill, setDrill] = useState(null); // { kind:'loc'|'floor', id, label }
  const open = (t) => SS.isOpenTicket(t);
  const counts = {
    New: tickets.filter((t) => t.status === 'New').length,
    Waiting: tickets.filter((t) => t.status.startsWith('Waiting')).length,
    Aging: tickets.filter((t) => open(t) && SS.ageDays(t) > 14).length,
    Resolved: tickets.filter((t) => t.status === 'Resolved').length
  };
  const match = (t) => view === 'All' ? true :
  view === 'New' ? t.status === 'New' :
  view === 'Aging' ? open(t) && SS.ageDays(t) > 14 :
  view === 'Waiting' ? t.status.startsWith('Waiting') :
  view === 'Resolved' ? t.status === 'Resolved' : true;
  const drillMatch = (t) => {
    if (!drill) return true;
    if (drill.kind === 'loc') return t.locationId === drill.id;
    const loc = SS.LOCATIONS.find((l) => l.id === t.locationId);
    return loc && loc.floor === drill.id;
  };
  const goList = (d) => {setDrill(d);setViewMode('list');};
  const list = tickets.filter((t) => match(t) && drillMatch(t)).slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
  const compact = !!selId;
  const SEGS = [['inbox', 'Inbox', 'ssInbox'], ['list', 'List view', 'layoutList'], ['map', 'Building map', 'ssMap']];
  return (
    <div className="d-list">
      <div className="d-topbar">
        <div><h2>Issue Inbox</h2><div className="sub">{setup.buildingName} · {setup.strataPlan} · {setup.lots} lots</div></div>
        <div className="d-search"><Icon name="search" size={16} /><input placeholder="Search issues…" /></div>
        <button className="d-btn acc" onClick={onNew}><Icon name="plus" size={16} /> Add issue</button>
      </div>
      <div className="d-strip">
        <div className="d-stat"><b>{counts.New}</b><span>New</span></div>
        <div className="d-stat"><b>{counts.Waiting}</b><span>Waiting</span></div>
        <div className={'d-stat' + (counts.Aging ? ' warn' : '')}><b>{counts.Aging}</b><span>Overdue</span></div>
        <div className="d-stat"><b>{counts.Resolved}</b><span>Resolved</span></div>
      </div>
      <div className="d-segwrap">
        <div className="d-seg" role="tablist">
          {SEGS.map(([m, label, ic]) =>
          <button key={m} className={'d-segbtn' + (viewMode === m ? ' on' : '')} onClick={() => {setViewMode(m);if (m !== 'list') setDrill(null);}}><Icon name={ic} size={15} /> {label}</button>
          )}
        </div>
      </div>
      {viewMode === 'list' &&
      <React.Fragment>
          <div className="d-tabs">
            {drill && <button className="d-tab drill" onClick={() => setDrill(null)}>{drill.label} <Icon name="x" size={13} /></button>}
            {[['All', 'All'], ['New', 'New'], ['Aging', 'Overdue'], ['Waiting', 'Waiting'], ['Resolved', 'Resolved']].map(([v, label]) =>
          <button key={v} className={'d-tab' + (view === v ? ' on' : '')} onClick={() => setView(v)}>{label}</button>
          )}
          </div>
          <div className="d-scroll">
            <table className="d-table">
              <thead><tr><th>Issue</th><th>Summary</th>{!compact && <th>Location</th>}<th>Age</th><th>Status</th>{!compact && <th>Assigned to</th>}{!compact && <th>Next step</th>}</tr></thead>
              <tbody>
                {list.map((t) =>
              <tr key={t.id} className={selId === t.id ? 'sel' : ''} onClick={() => onSelect(t.id)}>
                    <td><span className="id">{t.id}</span></td>
                    <td className="sm-sum"><span className="sum-cell"><span className="row-thumb ph"><Icon name={SS.catIcon(t.category)} size={15} /></span>{t.summary}</span></td>
                    {!compact && <td className="muted">{t.locationName.replace(' – Elevator Lobby', '')}</td>}
                    <td className="muted">{SS.ageLabel(t)}</td>
                    <td><StatusDot status={t.status} /></td>
                    {!compact && <td>{t.assignee ? <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7 }}><span className="av">{t.assignee.split(' ').map((w) => w[0]).join('')}</span> <span className="muted" style={{ color: 'var(--ink-700)' }}>{t.assignee.split(' ')[0]}</span></span> : <span className="muted">—</span>}</td>}
                    {!compact && <td className="muted" style={{ color: 'var(--ink-700)' }}>{t.nextStep}</td>}
                  </tr>
              )}
              </tbody>
            </table>
            {list.length === 0 && <div className="d-empty">Nothing in this view right now.</div>}
          </div>
        </React.Fragment>
      }
      {viewMode === 'inbox' && <GroupedInbox tickets={tickets} selId={selId} onSelect={onSelect} />}
      {viewMode === 'map' && <BuildingMap tickets={tickets} setup={setup} onSelect={onSelect} goList={goList} />}
    </div>);

}

function ByLocation({ tickets, onPick }) {
  const SS = window.SS;
  const rows = SS.locationStats(tickets).filter((s) => s.count > 0).sort((a, b) => b.count - a.count);
  const max = rows.length ? rows[0].count : 1;
  return (
    <div className="d-scroll d-rollup">
      {rows.map((s) =>
      <button key={s.loc.id} className="rollup-row" onClick={() => onPick(s.loc)}>
          <span className="rollup-name">{s.loc.name}</span>
          <span className="rollup-bar"><span className={'rollup-fill tone-' + s.tone} style={{ width: 12 + s.count / max * 88 + '%' }} /></span>
          <span className="rollup-count">{s.count}</span>
          <Icon name="chevronR" size={15} style={{ color: 'var(--ink-300)', flex: '0 0 auto' }} />
        </button>
      )}
      {rows.length === 0 && <div className="d-empty">No open issues right now.</div>}
      <div className="map-legend" style={{ margin: '16px 0 0' }}>
        <span className="ml-title">Oldest open issue</span>
        <span className="ml-item"><span className="ml-dot tone-green" /> Recent</span>
        <span className="ml-item"><span className="ml-dot tone-amber" /> 7 to 14 days</span>
        <span className="ml-item"><span className="ml-dot tone-red" /> Over 14 days</span>
      </div>
    </div>);

}

function ByFloor({ tickets, onPick }) {
  const SS = window.SS;
  const bands = SS.floorStats(tickets); // already Roof → Ground → P1 → P2
  const max = Math.max(1, ...bands.map((b) => b.count));
  const label = { Roof: 'Roof', Ground: 'Ground', P1: 'Parkade P1', P2: 'Parkade P2' };
  const code = { Roof: 'RF', Ground: 'GF', P1: 'P1', P2: 'P2' };
  return (
    <div className="d-scroll d-floors">
      <div className="floors-stack">
        {bands.map((b) =>
        <button key={b.floor} className="floor-band" disabled={b.count === 0} onClick={() => b.count && onPick(b.floor)}>
            <span className={'floor-fill ' + (b.count ? 'tone-' + b.tone : 'none')} style={{ width: b.count ? 14 + b.count / max * 86 + '%' : '0%' }} />
            <span className={'floor-code' + (b.count ? ' tone-' + b.tone : '')}>{code[b.floor]}</span>
            <span className="floor-name">{label[b.floor]}</span>
            <span className="floor-meta">
              {b.count ? <span className="floor-count">{b.count} open</span> : <span className="floor-zero">All clear</span>}
              {b.count > 0 && <Icon name="chevronR" size={16} style={{ color: 'var(--ink-400)', flex: '0 0 auto' }} />}
            </span>
          </button>
        )}
      </div>
      <div className="map-legend">
        <span className="ml-title">Oldest open issue</span>
        <span className="ml-item"><span className="ml-dot tone-green" /> Recent</span>
        <span className="ml-item"><span className="ml-dot tone-amber" /> 7 to 14 days</span>
        <span className="ml-item"><span className="ml-dot tone-red" /> Over 14 days</span>
      </div>
    </div>);

}

const FLOOR_Y = { Roof: 0.15, Ground: 0.60, P1: 0.77, P2: 0.90 };

// Grouped inbox — the default council view. Answers "what needs me, what are we
// waiting on, what's done" instead of a flat table.
function GroupedInbox({ tickets, selId, onSelect }) {
  const SS = window.SS;
  const open = (t) => SS.isOpenTicket(t);
  const isWaiting = (t) => t.status.startsWith('Waiting') || t.status === 'Scheduled';
  const needs = tickets.filter((t) => open(t) && !isWaiting(t)).
  sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
  const waiting = tickets.filter((t) => open(t) && isWaiting(t)).
  sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
  const resolved = tickets.filter((t) => t.status === 'Resolved').
  sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));

  const Card = (t) =>
  <button key={t.id} className={'gi-card' + (selId === t.id ? ' sel' : '')} onClick={() => onSelect(t.id)}>
      <span className="gi-thumb ph"><Icon name={SS.catIcon(t.category)} size={20} /></span>
      <div className="gi-main">
        <div className="gi-sum">{t.summary}</div>
        <div className="gi-meta">{t.locationName.replace(' – Elevator Lobby', '')} · {SS.ageLabel(t)}{SS.ageDays(t) > 14 && open(t) ? ' · overdue' : ''}</div>
      </div>
      <div className="gi-side">
        <StatusDot status={t.status} />
        {t.assignee ? <span className="gi-assignee">{t.assignee.split(' ')[0]}</span> : <span className="gi-assignee none">Unassigned</span>}
      </div>
    </button>;


  const Group = ({ title, hint, items, tone }) =>
  <section className="gi-group">
      <div className="gi-head">
        <span className={'gi-mark tone-' + tone} />
        <h3>{title}</h3>
        <span className="gi-count">{items.length}</span>
        <span className="gi-hint">{hint}</span>
      </div>
      {items.length ? items.map(Card) : <div className="gi-empty">Nothing here.</div>}
    </section>;


  return (
    <div className="d-scroll d-inbox">
      <Group title="Needs attention" hint="New and in review" items={needs} tone="amber" />
      <Group title="Waiting" hint="On a contractor or the reporter" items={waiting} tone="navy" />
      <Group title="Recently resolved" hint="Closed out" items={resolved} tone="green" />
      <section className="gi-group">
        <div className="gi-head"><span className="gi-mark" style={{ background: 'var(--ink-300)' }} /><h3>Recent activity</h3><span className="gi-hint">Across the building</span></div>
        <div className="d-feed" style={{ padding: 0, maxWidth: 'none' }}>
          {SS.feed(tickets, 6).map((it, i) => {
            const e = it.e;
            const tg = e.kind === 'update' ? { cls: 'pub', icon: 'eye', label: 'Update to reporter' } :
            e.kind === 'note' ? { cls: 'priv', icon: 'lock', label: 'Private note' } :
            e.kind === 'submitted' ? { cls: 'new', icon: 'plus', label: 'New report' } :
            { cls: 'status', icon: 'chevronR', label: 'Status change' };
            return (
              <button className="d-feed-item" key={i} onClick={() => onSelect(it.ticketId)}>
                <span className={'feed-mk ' + tg.cls}><Icon name={tg.icon} size={15} /></span>
                <div className="d-feed-body">
                  <div className="d-feed-top"><span className="feed-tag">{tg.label}</span><span className="feed-when">{SS.whenLabel(e.at)}</span></div>
                  <div className="d-feed-text">{e.body}</div>
                  <div className="d-feed-ref"><span className="id">{it.ticketId}</span> · {it.summary}</div>
                </div>
              </button>);

          })}
        </div>
      </section>
    </div>);

}

// Building map — secondary view holding the plan/section/location/floor sub-views.
function BuildingMap({ tickets, setup, onSelect, goList }) {
  const [sub, setSub] = useState('map'); // map | section | byloc | byfloor
  const SUBS = [['map', 'Plan'], ['section', 'Section'], ['byloc', 'By location'], ['byfloor', 'By floor']];
  return (
    <div className="d-scroll d-buildingmap">
      <div className="map-modes" style={{ paddingTop: 18 }}>
        <div className="d-seg sm">
          {SUBS.map(([m, label]) =>
          <button key={m} className={'d-segbtn' + (sub === m ? ' on' : '')} onClick={() => setSub(m)}>{label}</button>
          )}
        </div>
      </div>
      {sub === 'map' && <FloorMap tickets={tickets} setup={setup} onOpen={onSelect} />}
      {sub === 'section' && <BuildingSectionView tickets={tickets} />}
      {sub === 'byloc' && <ByLocation tickets={tickets} onPick={(loc) => goList({ kind: 'loc', id: loc.id, label: loc.name })} />}
      {sub === 'byfloor' && <ByFloor tickets={tickets} onPick={(f) => goList({ kind: 'floor', id: f, label: 'Floor ' + f })} />}
    </div>);

}

function BuildingSectionView({ tickets }) {
  const SS = window.SS;
  const bands = SS.floorStats(tickets);
  const activeFloors = bands.filter((b) => b.count > 0);
  return (
    <div className="d-mapscroll">
      <div className="floormap section-map">
        <SectionSchematic />
      </div>
      <div className="map-legend">
        <span className="ml-title">Open by level</span>
        {activeFloors.map((b) => (
          <span className="ml-item" key={b.floor}><span className={'ml-dot tone-' + b.tone} /> {b.floor}: {b.count}</span>
        ))}
        {activeFloors.length === 0 && <span className="ml-item"><span className="ml-dot tone-green" /> No open issues</span>}
      </div>
    </div>);

}

function FloorMap({ tickets, setup, onOpen }) {
  const SS = window.SS;
  const stats = SS.locationStats(tickets).filter((s) => s.count > 0);
  const [pop, setPop] = useState(null);
  const sel = stats.find((s) => s.loc.id === pop);
  const posOf = (s) => ({ x: s.loc.x, y: s.loc.y });
  const selPos = sel ? posOf(sel) : null;
  const mapUpload = (setup || {}).mapUpload || {};
  const uploadedMap = !!mapUpload.imageUrl;

  return (
    <div className="d-mapscroll">
      <div className={'floormap' + (uploadedMap ? ' uploaded' : '')} onClick={() => setPop(null)}>
        {uploadedMap ?
          <img className="floor-uploaded-img" src={mapUpload.imageUrl} alt={(setup && setup.buildingName ? setup.buildingName + ' uploaded building map' : 'Uploaded building map')} /> :
          <FloorSchematic />
        }
        <div className="map-source-pill">
          <Icon name={uploadedMap ? 'shieldCheck' : 'ssMap'} size={14} />
          {uploadedMap ? (mapUpload.status === 'Reviewed' ? 'Reviewed setup map' : 'Uploaded map preview') : 'Prototype floor plan'}
        </div>
        {stats.map((s) => {
          const p = posOf(s);
          return (
            <div key={s.loc.id} className="map-pin-wrap" style={{ left: p.x * 100 + '%', top: p.y * 100 + '%' }}>
              <button className={'map-pin tone-' + s.tone} onClick={(e) => {e.stopPropagation();setPop(pop === s.loc.id ? null : s.loc.id);}} aria-label={s.loc.name + ', ' + s.count + ' open'}>
                {s.count}
              </button>
            </div>);

        })}
        {sel &&
        <div className="map-pop" style={{ left: selPos.x * 100 + '%', top: selPos.y * 100 + '%' }} onClick={(e) => e.stopPropagation()}>
            <div className="map-pop-head">
              <div><div className="mp-name">{sel.loc.name}</div><div className="mp-floor">{sel.loc.floor} · {sel.count} open · oldest {sel.oldestDays}d</div></div>
              <button className="mp-x" onClick={() => setPop(null)}><Icon name="x" size={15} /></button>
            </div>
            <div className="map-pop-list">
              {sel.open.map((t) =>
            <button key={t.id} className="mp-row" onClick={() => onOpen(t.id)}>
                  <span className="mp-id">{t.id}</span>
                  <span className="mp-sum">{t.summary}</span>
                  <StatusDot status={t.status} short />
                </button>
            )}
            </div>
          </div>
        }
      </div>
      <div className="map-legend">
        <span className="ml-title">Oldest open issue</span>
        <span className="ml-item"><span className="ml-dot tone-green" /> Recent</span>
        <span className="ml-item"><span className="ml-dot tone-amber" /> 7 to 14 days</span>
        <span className="ml-item"><span className="ml-dot tone-red" /> Over 14 days</span>
      </div>
    </div>);

}

function SectionSchematic() {
  // Vertical building section — illustrative, for a multi-level structure.
  const floors = [
  { label: 'ROOF', y0: 8, y1: 15, note: 'Rooftop patio' },
  { label: 'LEVEL 2', y0: 15, y1: 26, note: 'PT 11–20' },
  { label: 'LEVEL 1', y0: 26, y1: 38, note: 'PT 1–10' },
  { label: 'GROUND', y0: 38, y1: 52, note: 'Lobby · entrance' },
  { label: 'PARKADE P1', y0: 52, y1: 63, note: '' },
  { label: 'PARKADE P2', y0: 63, y1: 72, note: '' }];

  return (
    <svg className="floor-svg" viewBox="0 0 100 75" preserveAspectRatio="xMidYMid meet" aria-hidden="true">
      <g className="fp-stroke">
        <rect x="14" y="8" width="72" height="64" />
        {floors.map((f) => <line key={f.label} x1="14" y1={f.y1} x2="86" y2={f.y1} />)}
        {/* central core (stairs + lift) running the full height */}
        <rect x="46" y="8" width="8" height="64" />
        {/* a few window ticks per residential floor */}
        {[18, 30].map((wy) => [20, 28, 60, 68, 76].map((wx) => <rect key={wy + '-' + wx} x={wx} y={wy} width="4" height="3" />))}
      </g>
      <g className="fp-label">
        {floors.map((f) => <text key={f.label} x="16.5" y={(f.y0 + f.y1) / 2 + 0.8} className="fp-cp">{f.label}</text>)}
        {floors.filter((f) => f.note).map((f) => <text key={f.label + 'n'} x="84" y={(f.y0 + f.y1) / 2 + 0.8} textAnchor="end" className="fp-area">{f.note}</text>)}
        <text x="50" y="40" textAnchor="middle" className="fp-cp" transform="rotate(-90 50 40)">CORE · STAIRS · LIFT</text>
      </g>
      <text x="50" y="72.5" textAnchor="middle" className="fp-title">BUILDING SECTION</text>
    </svg>);

}

function FloorSchematic() {
  // CAD-style architectural floor plan — blueprint linework, dimension strings,
  // grid bubbles, wall poché, door swings, north arrow, title block.
  const top = [{ id: 'PT7', a: '872', x: 18 }, { id: 'PT8', a: '926', x: 34 }, { id: 'PT9', a: '915', x: 50 }, { id: 'PT10', a: '807', x: 66 }];
  const bot = [{ id: 'PT6', a: '883' }, { id: 'PT5', a: '538' }, { id: 'PT4', a: '549' }, { id: 'PT3', a: '549' }, { id: 'PT2', a: '538' }, { id: 'PT1', a: '915' }];
  const bw = 80 / 6;
  const cols = [6, 18, 34, 50, 66, 82, 94]; // vertical gridlines
  const gridTags = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
  // double-line wall (poché) helper: outer rect + inset rect drawn via two rects
  return (
    <svg className="floor-svg blueprint" viewBox="0 0 104 80" preserveAspectRatio="xMidYMid meet" aria-hidden="true">
      {/* drafting grid */}
      <g className="fp-grid">
        {Array.from({ length: 21 }).map((_, i) => <line key={'v' + i} x1={4 + i * 4.8} y1="4" x2={4 + i * 4.8} y2="74" />)}
        {Array.from({ length: 15 }).map((_, i) => <line key={'h' + i} x1="4" y1={4 + i * 4.8} x2="100" y2={4 + i * 4.8} />)}
      </g>

      {/* grid column bubbles + dimension string (top) */}
      <g className="fp-grid-ref">
        {cols.map((x, i) =>
        <g key={'gb' + i}>
            <line x1={x} y1="3.4" x2={x} y2="8" />
            <circle cx={x} cy="2" r="1.9" />
            <text x={x} y="2.7" textAnchor="middle">{gridTags[i]}</text>
          </g>
        )}
        {/* dimension run between bubbles */}
        <line className="dim" x1="6" y1="6" x2="94" y2="6" />
        {cols.slice(0, -1).map((x, i) =>
        <text key={'dm' + i} x={(x + cols[i + 1]) / 2} y="5.2" textAnchor="middle" className="fp-dim">{[3.6, 4.8, 4.8, 4.8, 4.8, 3.6][i]}m</text>
        )}
        {[6, 18, 34, 50, 66, 82, 94].map((x, i) => <line key={'tk' + i} className="dim-tick" x1={x} y1="4.6" x2={x} y2="7.4" />)}
      </g>

      {/* left dimension string */}
      <g className="fp-grid-ref">
        <line className="dim" x1="2.4" y1="10" x2="2.4" y2="68" />
        <text x="1.7" y="20" textAnchor="middle" transform="rotate(-90 1.7 20)" className="fp-dim">12.0m</text>
        <text x="1.7" y="56" textAnchor="middle" transform="rotate(-90 1.7 56)" className="fp-dim">10.4m</text>
        <line className="dim-tick" x1="1.4" y1="10" x2="3.4" y2="10" />
        <line className="dim-tick" x1="1.4" y1="44" x2="3.4" y2="44" />
        <line className="dim-tick" x1="1.4" y1="68" x2="3.4" y2="68" />
      </g>

      {/* wall poché — outer envelope drawn as a thick double line */}
      <rect className="fp-wall-o" x="6" y="10" width="88" height="58" />
      <rect className="fp-wall-i" x="7.3" y="11.3" width="85.4" height="55.4" />

      <g className="fp-stroke">
        {/* corridor spine */}
        <line x1="18" y1="44" x2="82" y2="44" />
        {/* top units */}
        {top.map((u) => <rect key={u.id} x={u.x} y="18" width="16" height="20" />)}
        {/* balconies (top) */}
        {top.map((u) => <rect key={u.id + 'b'} className="fp-balcony" x={u.x + 3} y="11.3" width="10" height="6.7" />)}
        {/* corner stairs */}
        <rect x="7.3" y="18" width="10.7" height="16" />
        <rect x="82" y="18" width="10.7" height="16" />
        {/* lift core */}
        <rect className="fp-core" x="44" y="38" width="16" height="12" />
        {/* bottom units */}
        {bot.map((u, i) => <rect key={u.id} x={10 + i * bw} y="44" width={bw} height="22.7" />)}
        {bot.map((u, i) => <rect key={u.id + 'b'} className="fp-balcony" x={10 + i * bw + 2.5} y="60" width={bw - 5} height="6.7" />)}
      </g>

      {/* door swings (quarter-circle arcs) on a few units */}
      <g className="fp-door">
        {top.map((u) => <path key={u.id + 'd'} d={`M ${u.x + 2} 38 A 4 4 0 0 1 ${u.x + 6} 42`} />)}
        {bot.map((u, i) => <path key={u.id + 'd'} d={`M ${10 + i * bw + 2} 44 A 4 4 0 0 0 ${10 + i * bw + 6} 48`} />)}
      </g>

      {/* stair tread hatching */}
      <g className="fp-hatch">
        {[20, 23, 26, 29, 32].map((y) => <line key={'sl' + y} x1="8" y1={y} x2="17" y2={y} />)}
        {[20, 23, 26, 29, 32].map((y) => <line key={'sr' + y} x1="83" y1={y} x2="92" y2={y} />)}
      </g>

      <g className="fp-label">
        {top.map((u) => <text key={u.id} x={u.x + 8} y="27" textAnchor="middle">{u.id}</text>)}
        {top.map((u) => <text key={u.id + 'a'} x={u.x + 8} y="31" textAnchor="middle" className="fp-area">{u.a} ft²</text>)}
        {bot.map((u, i) => <text key={u.id} x={10 + i * bw + bw / 2} y="53" textAnchor="middle">{u.id}</text>)}
        {bot.map((u, i) => <text key={u.id + 'a'} x={10 + i * bw + bw / 2} y="57" textAnchor="middle" className="fp-area">{u.a} ft²</text>)}
        <text x="12.6" y="40.5" textAnchor="middle" className="fp-cp">STAIR A</text>
        <text x="87.4" y="40.5" textAnchor="middle" className="fp-cp">STAIR B</text>
        <text x="52" y="44.8" textAnchor="middle" className="fp-cp">LIFT · LOBBY</text>
        <text x="10" y="16" className="fp-cp">CP</text>
        <text x="90" y="16" className="fp-cp">CP</text>
      </g>

      {/* north arrow */}
      <g className="fp-north" transform="translate(98 12)">
        <circle r="3.4" />
        <path d="M0 -2.4 L1.3 1.6 L0 0.6 L-1.3 1.6 Z" className="fp-north-fill" />
        <text y="-3.9" textAnchor="middle">N</text>
      </g>

      {/* title block */}
      <g className="fp-titleblock">
        <rect x="64" y="70.5" width="36" height="8" />
        <line x1="64" y1="74.5" x2="100" y2="74.5" />
        <line x1="82" y1="70.5" x2="82" y2="78.5" />
        <text x="65.4" y="73.4" className="tb-k">PROJECT</text>
        <text x="65.4" y="77.2" className="tb-v">HARBOUR VIEW</text>
        <text x="83.4" y="73.4" className="tb-k">SHEET</text>
        <text x="83.4" y="77.2" className="tb-v">A‑201</text>
      </g>
      <text x="6" y="78" className="fp-scaletag">SCALE 1:100 · EPS‑0000 · LEVEL 1</text>
    </svg>);

}

function safeRecordText(value) {
  return String(value == null || value === '' ? '—' : value).replace(/\r?\n/g, ' ').trim();
}

function exportRecordPack(ticket, setToast) {
  const SS = window.SS;
  const status = (SS.STATUS[ticket.status] || {}).short || ticket.status;
  const poster = ticket.source === 'qr' ? SS.posterCode(ticket.locationId) : 'No poster';
  const media = [
    `${(ticket.photos || []).length} photo${(ticket.photos || []).length === 1 ? '' : 's'}`,
    `${(ticket.voiceNotes || []).length} voice note${(ticket.voiceNotes || []).length === 1 ? '' : 's'}`,
  ].join(', ');
  const timeline = (ticket.timeline || []).map((event) => {
    const visibility = event.visibility === 'private' || event.kind === 'note' ? 'private council record' : event.visibility === 'reporter-visible' || event.kind === 'update' ? 'reporter-visible' : 'record';
    return [
      `- ${safeRecordText(event.at)} · ${safeRecordText(event.kind)} · ${visibility}`,
      `  - ${safeRecordText(event.body)}`,
      event.author ? `  - Author: ${safeRecordText(event.author)}` : '',
    ].filter(Boolean).join('\n');
  }).join('\n');
  const content = [
    `# StrataSnap Case Record ${safeRecordText(ticket.id)}`,
    '',
    '## Issue',
    `- Summary: ${safeRecordText(ticket.summary)}`,
    `- Status: ${safeRecordText(status)}`,
    `- Priority: ${safeRecordText(ticket.priority)}`,
    `- Location: ${safeRecordText(ticket.locationName)}`,
    `- Category: ${safeRecordText(SS.catLabel(ticket.category))}`,
    `- Source: ${safeRecordText(ticket.source)}`,
    `- Poster: ${safeRecordText(poster)}`,
    `- Created: ${safeRecordText(ticket.createdAt)}`,
    `- Next step: ${safeRecordText(ticket.nextStep)}`,
    `- Responsibility: ${safeRecordText(ticket.responsibility)}`,
    `- Assigned to: ${safeRecordText(ticket.assignee)}`,
    '',
    '## Reporter Contact',
    `- Name: ${safeRecordText(ticket.reporterName)}`,
    `- Email: ${safeRecordText(ticket.reporterEmail)}`,
    `- Phone: ${safeRecordText(ticket.reporterPhone)}`,
    `- Unit: ${safeRecordText(ticket.unit)}`,
    '',
    '## Media',
    `- Attachments: ${media}`,
    '',
    '## Timeline',
    timeline || '- No timeline events recorded.',
    '',
    'Generated by StrataSnap local pilot export.',
  ].join('\n');
  const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `${safeRecordText(ticket.id).replace(/[^a-z0-9-]+/gi, '-').toLowerCase()}-case-record.md`;
  document.body.appendChild(a);
  a.click();
  a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 250);
  setToast && setToast('Case record pack exported');
}

function buildUsageReport(setup, tickets) {
  const SS = window.SS;
  const activeLocations = SS.setupLocations(setup, { activeOnly: true });
  const testedLocations = activeLocations.filter((loc) => loc.tested);
  const openTickets = tickets.filter((t) => SS.isOpenTicket(t));
  const closedTickets = tickets.filter((t) => !SS.isOpenTicket(t));
  const qrTickets = tickets.filter((t) => t.source === 'qr');
  const publicUpdateCount = tickets.reduce((sum, t) => sum + (t.timeline || []).filter((e) => e.kind === 'update' && e.visibility === 'reporter-visible').length, 0);
  const privateNoteCount = tickets.reduce((sum, t) => sum + (t.timeline || []).filter((e) => e.kind === 'note' || e.visibility === 'private').length, 0);
  const residentContacts = new Set(tickets.flatMap((t) => [t.reporterEmail, t.reporterPhone]).filter(Boolean).map((v) => String(v).trim().toLowerCase()));
  const needsOwnership = tickets.filter((t) => SS.isOpenTicket(t) && (!t.responsibility || t.responsibility === 'Responsibility unclear'));
  const notCommonProperty = tickets.filter((t) => t.responsibility === 'Not common property' || t.status === 'Closed - not strata issue');
  const duplicateClosed = tickets.filter((t) => t.status === 'Closed - duplicate');
  const needsClarification = tickets.filter((t) => t.status === 'Needs clarification' || t.status === 'Waiting for reporter');
  const locationRows = activeLocations.map((loc) => {
    const locationTickets = tickets.filter((t) => t.locationId === loc.id);
    const open = locationTickets.filter((t) => SS.isOpenTicket(t));
    return {
      loc,
      reports: locationTickets.length,
      open: open.length,
      lastReport: locationTickets.slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))[0],
    };
  }).sort((a, b) => b.reports - a.reports || a.loc.name.localeCompare(b.loc.name));
  const categories = SS.CATEGORIES.map((cat) => ({ cat, count: tickets.filter((t) => t.category === cat.id).length }))
    .filter((row) => row.count)
    .sort((a, b) => b.count - a.count);
  const statusRows = Object.keys(SS.STATUS).map((status) => ({ status, count: tickets.filter((t) => t.status === status).length }))
    .filter((row) => row.count);
  return {
    activeLocations,
    testedLocations,
    openTickets,
    closedTickets,
    qrTickets,
    publicUpdateCount,
    privateNoteCount,
    residentContacts,
    needsOwnership,
    notCommonProperty,
    duplicateClosed,
    needsClarification,
    locationRows,
    categories,
    statusRows,
    readiness: SS.setupProgress(setup),
    pilot: SS.pilotReadiness(setup),
  };
}

function exportUsageReport(setup, tickets, setToast) {
  const SS = window.SS;
  const report = buildUsageReport(setup, tickets);
  const auth = setup.authProvider || {};
  const database = setup.database || {};
  const billing = setup.billing || {};
  const locationLines = report.locationRows.map((row) =>
    `- ${safeRecordText(row.loc.name)}: ${row.reports} report${row.reports === 1 ? '' : 's'}, ${row.open} open, QR ${safeRecordText(row.loc.qrId || SS.posterCode(row.loc.id))}, ${row.loc.tested ? 'scan tested' : 'not scan tested'}`
  ).join('\n');
  const statusLines = report.statusRows.map((row) => `- ${safeRecordText(row.status)}: ${row.count}`).join('\n');
  const categoryLines = report.categories.map((row) => `- ${safeRecordText(row.cat.short || row.cat.label)}: ${row.count}`).join('\n');
  const content = [
    `# StrataSnap Pilot Usage Report - ${safeRecordText(setup.buildingName)}`,
    '',
    `Generated: ${safeRecordText(new Date().toISOString())}`,
    '',
    '## Intake',
    `- Total reports: ${tickets.length}`,
    `- QR reports: ${report.qrTickets.length}`,
    `- Open reports: ${report.openTickets.length}`,
    `- Resolved or closed: ${report.closedTickets.length}`,
    `- Resident contacts captured: ${report.residentContacts.size}`,
    '',
    '## Council Workflow',
    `- Reporter-visible updates: ${report.publicUpdateCount}`,
    `- Private council notes: ${report.privateNoteCount}`,
    `- Needs ownership decision: ${report.needsOwnership.length}`,
    `- Needs clarification: ${report.needsClarification.length}`,
    `- Not common property / outside scope: ${report.notCommonProperty.length}`,
    `- Closed duplicate: ${report.duplicateClosed.length}`,
    '',
    '## Poster Performance',
    `- Active QR locations: ${report.activeLocations.length}`,
    `- Scan-tested QR locations: ${report.testedLocations.length}`,
    locationLines || '- No active QR locations.',
    '',
    '## Status Mix',
    statusLines || '- No statuses recorded.',
    '',
    '## Category Mix',
    categoryLines || '- No categories recorded.',
    '',
    '## Launch Readiness',
    `- Onboarding readiness: ${report.readiness.percent}%`,
    `- Pilot stack readiness: ${report.pilot.percent}%`,
    `- Auth mode: ${safeRecordText(auth.mode)} (${safeRecordText(auth.pilotProvider)})`,
    `- Database mode: ${safeRecordText(database.mode)} (${safeRecordText(database.pilotProvider)})`,
    `- Billing: ${safeRecordText(billing.provider || 'Stripe')} ${safeRecordText(billing.mode || 'test')} / ${billing.checkoutConfigured ? 'checkout configured' : 'checkout not configured'}`,
    '',
    'Generated by StrataSnap local pilot export.',
  ].join('\n');
  const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'stratasnap-usage-report.md';
  document.body.appendChild(a);
  a.click();
  a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 250);
  setToast && setToast('Usage report exported');
}

function DetailPanel({ ticket, onClose, setModal, patch, setToast }) {
  const SS = window.SS;
  const [showContact, setShowContact] = useState(false);
  const open = SS.isOpenTicket(ticket);
  const sourceLabel = ticket.source === 'qr' ? 'QR poster' : ticket.source === 'email' ? 'Email' : ticket.source === 'phone' ? 'Phone' : 'Manual entry';
  const poster = ticket.source === 'qr' ? SS.posterCode(ticket.locationId) : 'No poster';
  return (
    <div className="d-detail">
      <div className="dh">
        <div className="top">
          <span className="id">{ticket.id}</span>
          <StatusDot status={ticket.status} />
          <button className="x" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="sum">{ticket.summary}</div>
        <div className="facts">
          <span className="chip"><Icon name="pin" size={13} />{ticket.locationName}</span>
          <span className="chip"><Icon name="qr" size={13} />{sourceLabel} · {poster}</span>
          <span className="chip"><Icon name={SS.catIcon(ticket.category)} size={13} />{SS.catLabel(ticket.category)}</span>
          <span className="chip"><Icon name="clock" size={13} />{SS.dateLabel(ticket.createdAt)}</span>
          <span className="chip">{ticket.priority} priority</span>
        </div>
      </div>
      <div className="dbody">
        {ticket.photos && ticket.photos.length > 0 &&
        <div className="d-photos">
            {ticket.photos.map((p, i) => <IssuePhoto key={i} seed={p} i={i} alt={'Resident photo ' + (i + 1)} />)}
          </div>
        }
        {ticket.voiceNotes && ticket.voiceNotes.length > 0 &&
        <div className="d-voice-list">
            {ticket.voiceNotes.map((v, i) =>
          <div className="d-voice-item" key={v.id || i}>
                <Icon name="mic" size={16} />
                <span>Voice note {i + 1} · {Math.round((v.durationMs || 0) / 1000)}s</span>
                <audio src={v.url} controls />
              </div>
          )}
          </div>
        }
        <div className="case-summary">
          <span className="case-summary-ic"><Icon name="ssCase" size={20} /></span>
          <div>
            <div className="case-summary-k">Case file</div>
            <div className="case-summary-v">{open ? ticket.nextStep : 'Ready for council records'} · {sourceLabel} {ticket.source === 'qr' ? poster : ''} · {ticket.timeline.length} recorded events</div>
          </div>
        </div>

        <div className="d-secl">Actions</div>
        <div className="lane-row">
          <button className="lane-btn pub" onClick={() => setModal({ type: 'update', id: ticket.id })}>
            <span className="tt"><Icon name="ssUpdate" size={15} /> Send reporter update</span>
            <span className="ds">Visible on status page · preview first</span>
          </button>
          <button className="lane-btn priv" onClick={() => setModal({ type: 'note', id: ticket.id })}>
            <span className="tt"><Icon name="ssPrivate" size={15} /> Private council note</span>
            <span className="ds">Records only · never shown to reporter</span>
          </button>
        </div>

        <div className="d-secl">Next steps</div>
        <button className="d-prow" onClick={() => setModal({ type: 'status', id: ticket.id })}>
          <span className="k">Status</span><span className="v"><StatusDot status={ticket.status} /></span><Icon name="chevronR" size={15} className="chev" />
        </button>
        <button className="d-prow" onClick={() => setModal({ type: 'assignee', id: ticket.id })}>
          <span className="k">Assigned to</span><span className="v">{ticket.assignee || <span style={{ color: 'var(--ink-400)', fontWeight: 400 }}>Unassigned</span>}</span><Icon name="chevronR" size={15} className="chev" />
        </button>
        <button className="d-prow" onClick={() => setModal({ type: 'responsibility', id: ticket.id })}>
          <span className="k">Who owns this?</span><span className="v" style={{ fontWeight: 400, fontSize: 13.5 }}>{ticket.responsibility || <span style={{ color: 'var(--ink-400)' }}>Unclear</span>}</span><Icon name="chevronR" size={15} className="chev" />
        </button>
        <button className="d-prow" onClick={() => setModal({ type: 'vendor', id: ticket.id })}>
          <span className="k">Vendor dispatch</span><span className="v" style={{ fontWeight: 400, fontSize: 13.5 }}>{ticket.vendorEmail ? ticket.vendorEmail : <span style={{ color: 'var(--ink-400)' }}>Not sent</span>}</span><Icon name="chevronR" size={15} className="chev" />
        </button>

        <div className="d-secl">Reporter <span style={{ color: 'var(--ink-300)', fontWeight: 500 }}>· council only</span></div>
        {showContact ?
        <React.Fragment>
            <div className="kv"><span className="k">Name</span><span className="v">{ticket.reporterName}</span></div>
            <div className="kv"><span className="k">Email</span><span className="v">{ticket.reporterEmail || '—'}</span></div>
            <div className="kv"><span className="k">Phone</span><span className="v">{ticket.reporterPhone || '—'}</span></div>
            <div className="kv"><span className="k">Unit</span><span className="v">{ticket.unit || '—'}</span></div>
            <button className="d-prow" onClick={() => setShowContact(false)} style={{ marginTop: 10 }}>
              <span className="k">Contact</span><span className="v" style={{ color: 'var(--ink-500)', fontWeight: 400 }}>Hide details</span><Icon name="lock" size={14} style={{ color: 'var(--ink-300)' }} />
            </button>
          </React.Fragment> :

        <button className="d-prow" onClick={() => setShowContact(true)}>
            <span className="v" style={{ display: 'flex', alignItems: 'center', gap: 8 }}><Icon name="eye" size={15} style={{ color: 'var(--acc)' }} /> Show reporter contact</span>
            <span className="k" style={{ width: 'auto', color: 'var(--ink-400)' }}>{ticket.unit ? 'Unit ' + ticket.unit : 'private'}</span>
            <Icon name="chevronR" size={15} className="chev" />
          </button>
        }

        <div className="d-secl">Record</div>
        <div className="tl">
          {ticket.timeline.slice().reverse().map((e) => {
            const cls = { submitted: 'submitted', 'status-change': 'status', update: 'update', note: 'note', 'vendor-dispatch': 'note' }[e.kind] || '';
            return (
              <div className={'te ' + cls} key={e.id}>
                <span className="marker" />
                {e.kind === 'update' && <span className="lane-tag pub"><Icon name="eye" size={10} /> Reporter saw this</span>}
                {(e.kind === 'note' || e.visibility === 'private') && <span className="lane-tag priv"><Icon name="lock" size={10} /> Private</span>}
                <div className="when">{SS.whenLabel(e.at)}</div>
                <div className="tbody" style={e.kind === 'status-change' ? { fontWeight: 600, color: 'var(--ink-700)' } : null}>{e.body}</div>
                {e.author && e.kind !== 'status-change' && <div className="who">{e.author}</div>}
              </div>);

          })}
        </div>
        <button className="d-btn out full" style={{ marginTop: 16 }} onClick={() => exportRecordPack(ticket, setToast)}><Icon name="files" size={16} /> Export record pack</button>
      </div>
    </div>);

}

function readDesktopLogoFile(file, onDone, onError) {
  if (!file) return;
  if (!/^image\/(png|jpeg)$/.test(file.type)) {
    onError && onError('Use a PNG or JPG logo for now.');
    return;
  }
  if (file.size > 5000000) {
    onError && onError('Logo file is too large. Use a file under 5 MB.');
    return;
  }
  const reader = new FileReader();
  reader.onload = () => {
    const url = String(reader.result || '');
    const img = new Image();
    img.onload = () => {
      if ((img.width * img.height) > 16000000) {
        onError && onError('Logo image dimensions are too large. Use a smaller image.');
        return;
      }
      const max = 480;
      const scale = Math.min(1, max / Math.max(img.width, img.height));
      const canvas = document.createElement('canvas');
      canvas.width = Math.max(1, Math.round(img.width * scale));
      canvas.height = Math.max(1, Math.round(img.height * scale));
      const ctx = canvas.getContext('2d');
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      const output = canvas.toDataURL(file.type === 'image/jpeg' ? 'image/jpeg' : 'image/png', 0.86);
      if (output.length > 650000) {
        onError && onError('Logo preview is still too large. Try a simpler PNG or JPG.');
        return;
      }
      try {
        localStorage.setItem('__stratasnap_logo_probe', output);
        localStorage.removeItem('__stratasnap_logo_probe');
      } catch (e) {
        onError && onError('Browser storage is full. Remove other prototype data before uploading a logo.');
        return;
      }
      onDone(output);
    };
    img.onerror = () => onError && onError('Could not process that logo image.');
    img.src = url;
  };
  reader.onerror = () => onError && onError('Could not read that logo file.');
  reader.readAsDataURL(file);
}

function readDesktopMapFile(file, onDone, onError) {
  if (!file) return;
  if (!/^image\/(png|jpeg)$/.test(file.type)) {
    onError && onError('Use a PNG or JPG floor plan for now.');
    return;
  }
  if (file.size > 7 * 1024 * 1024) {
    onError && onError('Map image is too large. Use an image under 7 MB.');
    return;
  }
  const reader = new FileReader();
  reader.onload = () => {
    const url = String(reader.result || '');
    const img = new Image();
    img.onload = () => {
      const max = 900;
      const scale = Math.min(1, max / Math.max(img.width, img.height));
      const canvas = document.createElement('canvas');
      canvas.width = Math.max(1, Math.round(img.width * scale));
      canvas.height = Math.max(1, Math.round(img.height * scale));
      const ctx = canvas.getContext('2d');
      ctx.fillStyle = '#fff';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      const output = canvas.toDataURL('image/jpeg', 0.72);
      if (output.length > 650000) {
        onError && onError('Map preview is still too large. Try a smaller floor plan image.');
        return;
      }
      onDone({
        phase: 'phase-1',
        fileName: file.name,
        imageUrl: output,
        standardizedImage: {
          width: canvas.width,
          height: canvas.height,
          mimeType: 'image/jpeg',
          maxDimension: max,
          sourceName: file.name,
          sourceBytes: file.size,
        },
        status: 'Source image ready',
        manualPins: [],
        floorsDetected: 0,
        locationsMatched: 0,
        reviewProvider: '',
        review: null,
        approvedReview: null,
        renderedMap: null,
        notes: 'Original plan uploaded. Add or edit pins first; AI suggestions are optional draft data for council review.',
        reviewedAt: new Date().toISOString(),
      });
    };
    img.onerror = () => onError && onError('Could not process that map image.');
    img.src = url;
  };
  reader.onerror = () => onError && onError('Could not read that map file.');
  reader.readAsDataURL(file);
}

function stableSetupLocationId(name) {
  return String(name || 'location')
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
    .slice(0, 40) || 'location';
}

function setupFloorRank(floor) {
  const text = String(floor || '').toLowerCase();
  if (text === 'roof') return 100;
  if (text === 'ground' || text === 'main' || text === 'lobby') return 0;
  const parkade = text.match(/^p(\d+)$/);
  if (parkade) return -Number(parkade[1]);
  const level = text.match(/(?:l|level)\s*(\d+)/);
  if (level) return Number(level[1]);
  return 10;
}

function clampSetupMapCoordinate(value, fallback) {
  const number = Number(value);
  const safe = Number.isFinite(number) ? number : fallback;
  return Math.max(0, Math.min(1, Number(safe).toFixed(2) * 1));
}

function setupBuildingFloors(floors, locations) {
  const floorMap = new Map();
  const putFloor = (floor, index = floorMap.size) => {
    const name = String((floor && floor.name) || floor || 'Ground').trim() || 'Ground';
    if (!floorMap.has(name)) {
      floorMap.set(name, {
        id: (floor && floor.id) || stableSetupLocationId(name),
        name,
        type: (floor && floor.type) || (/^p\d/i.test(name) ? 'parkade' : /roof/i.test(name) ? 'roof' : 'building'),
        displayOrder: Number.isFinite(Number(floor && floor.displayOrder)) ? Number(floor.displayOrder) : index + 1,
        source: (floor && floor.source) || 'standardized-review',
        confidence: Number.isFinite(Number(floor && floor.confidence)) ? Number(floor.confidence) : 0.6,
        locationCount: 0,
        locations: [],
        notes: (floor && floor.notes) || 'Confirm resident-facing floor name and orientation before publishing.',
      });
    }
    return floorMap.get(name);
  };
  (Array.isArray(floors) ? floors : []).forEach((floor, index) => putFloor(floor, index));
  (Array.isArray(locations) ? locations : []).forEach((loc, index) => {
    const floor = putFloor(loc.floor || 'Ground');
    floor.locations.push({
      locationId: loc.id || stableSetupLocationId(loc.name || `location-${index + 1}`),
      name: loc.name || `Location ${index + 1}`,
      area: loc.area || 'Common',
      type: loc.type || 'common',
      mapX: clampSetupMapCoordinate(loc.mapX, 0.5),
      mapY: clampSetupMapCoordinate(loc.mapY, 0.5),
      councilAction: loc.councilAction || 'confirm',
    });
  });
  return [...floorMap.values()]
    .sort((a, b) => setupFloorRank(a.name) - setupFloorRank(b.name) || a.displayOrder - b.displayOrder)
    .map((floor, index) => ({ ...floor, displayOrder: index + 1, locationCount: floor.locations.length }));
}

function localSetupMapReview({ setup, activeLocations, mapUpload }) {
  const locations = (activeLocations.length ? activeLocations : setup.locations).map((loc, i) => ({
    id: loc.id || stableSetupLocationId(loc.name),
    name: loc.name || loc.id || `Location ${i + 1}`,
    floor: loc.floor || 'Ground',
    area: loc.area || 'Common',
    type: /parkade|p\d/i.test((loc.name || '') + ' ' + (loc.floor || '')) ? 'parkade' : /garbage|bike|storage|mechanical/i.test(loc.name || '') ? 'service' : /roof|patio|amenity/i.test((loc.name || '') + ' ' + (loc.floor || '')) ? 'amenity' : /exterior|entry/i.test(loc.name || '') ? 'exterior' : 'common',
    confidence: 0.64,
    mapX: clampSetupMapCoordinate(loc.mapX ?? loc.x, 0.18 + (i % 4) * 0.2),
    mapY: clampSetupMapCoordinate(loc.mapY ?? loc.y, 0.24 + (Math.floor(i / 4) % 3) * 0.2),
    source: 'current-location-list',
    councilAction: 'confirm',
  }));
  const floorNames = [...new Set(locations.map((loc) => loc.floor || 'Ground'))];
  const floors = floorNames.map((name) => ({
    id: stableSetupLocationId(name),
    name,
    type: /^p\d/i.test(name) ? 'parkade' : /roof/i.test(name) ? 'roof' : 'building',
    confidence: 0.68,
    notes: 'Inferred from current setup locations. Confirm against the uploaded map.',
  }));
  return {
    status: 'review_ready',
    provider: 'local-rule-review',
    promptVersion: 'stratasnap-map-v1',
    standardizedImage: mapUpload.standardizedImage || null,
    buildingName: setup.buildingName,
    floors,
    buildingFloors: setupBuildingFloors(floors, locations),
    locations,
    posterSuggestions: locations.slice(0, 12).map((loc) => ({
      locationId: loc.id,
      label: loc.name,
      floor: loc.floor,
      mapX: loc.mapX,
      mapY: loc.mapY,
      rationale: 'Active reporting location; confirm exact mounting point before printing.',
      confidence: loc.confidence,
    })),
    uncertainties: ['Local fallback cannot read fine labels from the image. Connect OPENROUTER_API_KEY for vision-based extraction.'],
    councilQuestions: ['Are these floor labels resident-friendly?', 'Which service or restricted rooms should be hidden from resident reporting?', 'Where can each QR poster be mounted and scanned reliably?'],
    notes: 'Local fallback standardized current setup locations. OpenRouter review can extract additional labels from the image.',
  };
}

function defaultSetupMapPins({ setup, activeLocations }) {
  const SS = window.SS || {};
  const seedById = new Map(((SS.LOCATIONS || [])).map((loc) => [loc.id, loc]));
  const source = activeLocations.length ? activeLocations : (setup.locations || []);
  return source.map((loc, index) => {
    const seeded = seedById.get(loc.id) || {};
    return {
      id: 'pin-' + (loc.id || stableSetupLocationId(loc.name || `location-${index + 1}`)),
      locationId: loc.id || '',
      name: loc.name || `Location ${index + 1}`,
      floor: loc.floor || 'Ground',
      area: loc.area || 'Common',
      mapX: clampSetupMapCoordinate(loc.mapX ?? loc.x ?? seeded.mapX ?? seeded.x, 0.18 + (index % 4) * 0.2),
      mapY: clampSetupMapCoordinate(loc.mapY ?? loc.y ?? seeded.mapY ?? seeded.y, 0.24 + (Math.floor(index / 4) % 3) * 0.2),
      source: 'manual',
    };
  });
}

function setupSchematicFloors(locations) {
  const active = (locations || []).filter((loc) => loc.active !== false);
  const floorNames = [...new Set(active.map((loc) => loc.floor || 'Ground'))];
  return floorNames.map((name, index) => ({
    id: stableSetupLocationId(name),
    name,
    displayOrder: index + 1,
    locations: active
      .filter((loc) => (loc.floor || 'Ground') === name)
      .map((loc, locIndex) => ({
        id: loc.id || stableSetupLocationId(loc.name || `location-${locIndex + 1}`),
        name: loc.name || `Location ${locIndex + 1}`,
        area: loc.area || 'Common',
        qrId: loc.qrId || '',
        mapX: clampSetupMapCoordinate(loc.mapX ?? loc.x, 0.18 + (locIndex % 4) * 0.2),
        mapY: clampSetupMapCoordinate(loc.mapY ?? loc.y, 0.3 + (Math.floor(locIndex / 4) % 2) * 0.28),
        zoneType: loc.area || 'Common',
      })),
  })).filter((floor) => floor.locations.length);
}

function createSetupRenderedMap({ setup, locations, source }) {
  const sourceLocations = (locations || setup.locations || []).filter((loc) => loc.active !== false);
  const floors = setupSchematicFloors(sourceLocations);
  return {
    version: 'stratasnap-schematic-v1',
    source: source || 'manual-locations',
    buildingName: setup.buildingName || '',
    generatedAt: new Date().toISOString(),
    floorCount: floors.length,
    zoneCount: floors.reduce((sum, floor) => sum + floor.locations.length, 0),
    floors,
  };
}

function mapPipelinePhaseItems(mapUpload) {
  const phase = mapUpload.phase || 'phase-1';
  return [
    { id: 'phase-1', label: 'Phase 1', title: 'Source image and manual pins', state: mapUpload.imageUrl ? 'Active' : 'Waiting' },
    { id: 'phase-2', label: 'Phase 2', title: 'AI floor and common-area suggestions', state: mapUpload.approvedReview ? 'Approved' : mapUpload.review ? 'Draft ready' : 'Optional' },
    { id: 'phase-3', label: 'Phase 3', title: 'Clean rendered schematic map', state: mapUpload.renderedMap ? 'Preview ready' : 'Later' },
    { id: 'phase-4', label: 'Phase 4', title: 'QR placement and duplicate detection', state: phase === 'phase-4' ? 'Later draft' : 'Later' },
  ];
}

function SetupSchematicPreview({ renderedMap, onZone }) {
  if (!renderedMap || !Array.isArray(renderedMap.floors) || !renderedMap.floors.length) return null;
  return (
    <div className="ds-schematic">
      <div className="ds-schematic-head">
        <span><Icon name="ssMap" size={16} /> Phase 3 schematic preview</span>
        <b>{renderedMap.floorCount || renderedMap.floors.length} floors · {renderedMap.zoneCount || 0} zones · {renderedMap.source || 'manual-locations'}</b>
      </div>
      <div className="ds-schematic-stage">
        {renderedMap.floors.map((floor) => (
          <section key={floor.id || floor.name} className="ds-schematic-floor">
            <div className="ds-schematic-floor-head"><b>{floor.name}</b><span>{floor.locations.length} zones</span></div>
            <div className="ds-schematic-zones">
              {floor.locations.map((zone, index) => (
                <button key={zone.id} className="ds-schematic-zone" onClick={() => onZone(zone, floor)}>
                  <span>{index + 1}</span>
                  <b>{zone.name}</b>
                  <em>{zone.area}</em>
                </button>
              ))}
            </div>
          </section>
        ))}
      </div>
    </div>
  );
}

function DesktopLogoPreview({ setup }) {
  return (
    <div className="ds-logo-preview">
      {setup.logoUrl ? <img src={setup.logoUrl} alt={setup.buildingName + ' logo'} /> : <Logo tone="dark" size={24} />}
    </div>
  );
}

function DesktopSetup({ setup, patchSetup, setToast, startAtFirst, forceIntro }) {
  const SS = window.SS;
  const Api = window.SSPilotApi;
  const readiness = SS.setupProgress(setup);
  const stepIds = ['profile', 'brand', 'locations', 'map', 'qr', 'test'];
  const nextToStep = { profile: 'profile', routing: 'profile', emergency: 'profile', brand: 'brand', locations: 'locations', qr: 'qr', scan: 'test', notify: 'test' };
  const [panel, setPanel] = useState((startAtFirst || !setup.activated) ? 'profile' : (nextToStep[readiness.next ? readiness.next.id : 'profile'] || 'profile'));
  const [introOpen, setIntroOpen] = useState(!!forceIntro || ((startAtFirst || !setup.activated) && !setup.onboardingIntroSeen));
  const stepIndex = Math.max(0, stepIds.indexOf(panel));
  const activeLocations = setup.locations.filter((l) => l.active);
  const testedLocations = activeLocations.filter((l) => l.tested);
  const qrStyle = setup.qrStyle || {};
  const themes = SS.APP_THEMES || [];
  const posterFulfillment = setup.posterFulfillment || {};
  const mapUpload = setup.mapUpload || {};
  const mapReview = mapUpload.review || null;
  const [mapReviewing, setMapReviewing] = useState(false);
  const [selectedMapPinId, setSelectedMapPinId] = useState('');
  const validEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(setup.routingEmail || '').trim());
  const set = (patch) => patchSetup((s) => ({ ...s, ...patch }));
  const setAppTheme = (appTheme) => patchSetup((s) => ({ ...s, appTheme }));
  const setQrStyle = (patch) => patchSetup((s) => ({ ...s, qrStyle: { ...(s.qrStyle || {}), ...patch } }));
  const setPosterFulfillment = (patch) => patchSetup((s) => ({ ...s, posterFulfillment: { ...(s.posterFulfillment || {}), ...patch } }));
  const setMapUpload = (patch) => patchSetup((s) => ({ ...s, mapUpload: { ...(s.mapUpload || {}), ...patch } }));
  const setManualPins = (manualPins, extra = {}) => setMapUpload({
    phase: 'phase-1',
    manualPins,
    locationsMatched: manualPins.length,
    status: manualPins.length ? 'Manual pins in progress' : 'Source image ready',
    notes: manualPins.length
      ? 'Manual pins are the Phase 1 source of truth. Apply them to reporting locations before printing QR posters.'
      : 'Original plan uploaded. Add or edit pins first; AI suggestions are optional draft data for council review.',
    ...extra,
  });
  const renderSchematicMap = (locations, source) => {
    const renderedMap = createSetupRenderedMap({ setup, locations: locations || setup.locations, source });
    if (!renderedMap.zoneCount) {
      setToast('Apply at least one active location before rendering a schematic');
      return null;
    }
    setMapUpload({
      phase: 'phase-3',
      renderedMap,
      status: 'Schematic preview ready',
      locationsMatched: renderedMap.zoneCount,
      notes: 'Phase 3 preview generated from council-approved locations. Keep resident map reporting off until this is reviewed.',
      reviewedAt: renderedMap.generatedAt,
    });
    setToast('Schematic map preview generated');
    return renderedMap;
  };
  const updateLoc = (id, patch) => patchSetup((s) => ({
    ...s,
    locations: s.locations.map((l) => l.id === id ? { ...l, ...patch } : l),
  }));
  const isTaskDone = (id) => !!(readiness.tasks.find((t) => t.id === id) || {}).done;
  const manualPins = Array.isArray(mapUpload.manualPins) ? mapUpload.manualPins : [];
  const stepDone = {
    profile: isTaskDone('profile') && isTaskDone('routing') && isTaskDone('emergency'),
    brand: !!setup.appTheme || !!setup.logoUrl || !!qrStyle.foreground,
    locations: isTaskDone('locations'),
    map: !!mapUpload.imageUrl,
    qr: isTaskDone('qr'),
    test: isTaskDone('scan') && isTaskDone('notify'),
  };
  const steps = [
    { id: 'profile', label: 'Building details', hint: 'Confirm the record, report inbox, and emergency instructions.', required: true, decision: 'Council confirms the building record, responsible inbox, and urgent-report instructions before residents see the QR posters.', impact: 'Residents see a familiar building name, know what counts as urgent, and have every report routed to one accountable inbox.' },
    { id: 'brand', label: 'Brand and QR style', hint: 'App accent, logo, and QR colour controls.', required: false, decision: 'Council chooses the approved app accent, logo use, and QR contrast for resident-facing posters and status pages.', impact: 'Residents can recognize the reporting surface as belonging to their building and can scan the QR code reliably.' },
    { id: 'locations', label: 'Locations', hint: 'Choose where QR posters go.', required: true, decision: 'Council selects the first common-property areas where QR reporting should be available.', impact: 'Reports arrive with a known place attached, reducing vague location descriptions and speeding triage.' },
    { id: 'map', label: 'Building map', hint: 'Upload floor plan for review.', required: false, decision: 'Council decides whether to add a floor plan now and keeps AI/OCR map review as a confirmation step.', impact: 'Council can place issues and posters on a visual map without letting automation publish unreviewed building details.' },
    { id: 'qr', label: 'Posters and printing', hint: 'Review QR IDs and print options.', required: true, decision: 'Council reviews each public QR ID and chooses the print or sticker workflow for the first pilot locations.', impact: 'Residents scan the right poster in the right area, and council can replace or reprint materials from a known source.' },
    { id: 'test', label: 'Test and launch', hint: 'Run checks before printing.', required: true, decision: 'Council confirms scan, notification, and status-page checks before inviting residents to use the system.', impact: 'The first real report follows a tested path from poster scan to council inbox and resident follow-up.' },
  ];
  const currentStep = steps[stepIndex] || steps[0];
  const showIntro = introOpen && (!!forceIntro || !setup.activated);
  const beginSetup = (nextPanel = 'profile') => {
    setIntroOpen(false);
    setPanel(nextPanel);
    if (!setup.onboardingIntroSeen) patchSetup({ onboardingIntroSeen: true });
  };
  const goNext = () => setPanel(stepIds[Math.min(stepIds.length - 1, stepIndex + 1)]);
  const goBack = () => setPanel(stepIds[Math.max(0, stepIndex - 1)]);
  const runScan = () => {
    if (!activeLocations.length) {
      setToast('Turn on at least one reporting location first');
      setPanel('locations');
      return;
    }
    patchSetup((s) => ({ ...s, testScanPassed: true, locations: s.locations.map((l) => ({ ...l, tested: l.active ? true : l.tested })) }));
    setToast('Test scan completed across active locations');
  };
  const sendNotification = () => {
    if (!validEmail) {
      setToast('Add a valid routing email before sending a test');
      setPanel('profile');
      return;
    }
    set({ notificationTested: true });
    setToast('Notification test sent to ' + setup.routingEmail);
  };
  const runMapReview = async () => {
    if (!mapUpload.imageUrl) {
      setToast('Upload a floor plan before running map review');
      setPanel('map');
      return;
    }
    setMapReviewing(true);
    setMapUpload({ phase: 'phase-2', status: 'Reviewing AI suggestions', notes: 'Processing the standardized image into draft floors, locations, poster suggestions, and council questions.' });
    let review;
    try {
      if (Api && Api.enabled && Api.enabled()) {
        const result = await Api.reviewMap({
          imageUrl: mapUpload.imageUrl,
          standardizedImage: mapUpload.standardizedImage,
          buildingName: setup.buildingName,
          locations: setup.locations,
        });
        review = result.review;
      } else {
        review = localSetupMapReview({ setup, activeLocations, mapUpload });
      }
    } catch (err) {
      review = localSetupMapReview({ setup, activeLocations, mapUpload });
      review.notes = 'AI review failed or is unavailable, so a local standardized review was generated. Backend detail: ' + (err && err.message ? err.message : 'unknown error');
    } finally {
      setMapReviewing(false);
    }
    review = {
      ...review,
      buildingFloors: Array.isArray(review.buildingFloors) && review.buildingFloors.length
        ? review.buildingFloors
        : setupBuildingFloors(review.floors, review.locations),
    };
    setMapUpload({
      phase: 'phase-2',
      status: 'Review ready',
      floorsDetected: (review.buildingFloors || review.floors || []).length,
      locationsMatched: (review.locations || []).length,
      reviewProvider: review.provider,
      review,
      approvedReview: null,
      notes: review.notes || 'Map review ready: confirm floors, common-property rooms, and poster coordinates before publishing.',
      reviewedAt: new Date().toISOString(),
    });
    setToast((review.provider || '').startsWith('openrouter:') || (review.provider || '').startsWith('openai:') ? 'AI map review ready for council confirmation' : 'Local map review ready for council confirmation');
  };
  const seedManualPins = () => {
    const pins = defaultSetupMapPins({ setup, activeLocations });
    setSelectedMapPinId(pins[0] ? pins[0].id : '');
    setManualPins(pins);
    setToast('Manual pins loaded from active locations');
  };
  const updateManualPin = (id, patch) => {
    const pins = manualPins.map((pin) => pin.id === id ? { ...pin, ...patch } : pin);
    setManualPins(pins);
  };
  const removeManualPin = (id) => {
    const pins = manualPins.filter((pin) => pin.id !== id);
    setSelectedMapPinId((current) => current === id ? (pins[0] ? pins[0].id : '') : current);
    setManualPins(pins);
  };
  const addManualPinAt = (mapX, mapY) => {
    const nextNumber = manualPins.length + 1;
    const id = 'pin-manual-' + Date.now().toString(36);
    const pin = {
      id,
      locationId: '',
      name: `Manual pin ${nextNumber}`,
      floor: 'Ground',
      area: 'Common',
      mapX: clampSetupMapCoordinate(mapX, 0.5),
      mapY: clampSetupMapCoordinate(mapY, 0.5),
      source: 'manual',
    };
    setSelectedMapPinId(id);
    setManualPins([...manualPins, pin]);
    setToast('Manual map pin added');
  };
  const handleManualMapClick = (event) => {
    if (!mapUpload.imageUrl) return;
    const rect = event.currentTarget.getBoundingClientRect();
    const mapX = (event.clientX - rect.left) / Math.max(1, rect.width);
    const mapY = (event.clientY - rect.top) / Math.max(1, rect.height);
    if (selectedMapPinId && manualPins.some((pin) => pin.id === selectedMapPinId)) {
      updateManualPin(selectedMapPinId, {
        mapX: clampSetupMapCoordinate(mapX, 0.5),
        mapY: clampSetupMapCoordinate(mapY, 0.5),
      });
      setToast('Selected pin moved');
      return;
    }
    addManualPinAt(mapX, mapY);
  };
  const applyManualPins = () => {
    if (!manualPins.length) {
      setToast('Add at least one manual pin first');
      return;
    }
    patchSetup((s) => {
      const existing = new Map((s.locations || []).map((loc) => [loc.id, loc]));
      const used = new Set();
      const updated = (s.locations || []).map((loc) => {
        const pin = manualPins.find((item) => item.locationId === loc.id || item.id === 'pin-' + loc.id);
        if (!pin) return loc;
        used.add(pin.id);
        return {
          ...loc,
          name: pin.name || loc.name,
          floor: pin.floor || loc.floor || 'Ground',
          area: pin.area || loc.area || 'Common',
          active: true,
          mapX: clampSetupMapCoordinate(pin.mapX, loc.mapX ?? loc.x ?? 0.5),
          mapY: clampSetupMapCoordinate(pin.mapY, loc.mapY ?? loc.y ?? 0.5),
        };
      });
      const additions = manualPins.filter((pin) => !used.has(pin.id)).map((pin, index) => {
        const baseId = stableSetupLocationId(pin.name || `manual-pin-${index + 1}`);
        let id = pin.locationId || baseId;
        let suffix = 2;
        while (existing.has(id) || updated.some((loc) => loc.id === id)) {
          id = `${baseId}-${suffix++}`;
        }
        return {
          id,
          name: pin.name || `Manual pin ${index + 1}`,
          area: pin.area || 'Common',
          floor: pin.floor || 'Ground',
          active: true,
          tested: false,
          qrId: 'MP-' + id.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 8),
          mapX: clampSetupMapCoordinate(pin.mapX, 0.5),
          mapY: clampSetupMapCoordinate(pin.mapY, 0.5),
        };
      });
      return { ...s, locations: [...updated, ...additions] };
    });
    setMapUpload({
      phase: 'phase-1',
      status: 'Manual pins applied',
      locationsMatched: manualPins.length,
      notes: 'Phase 1 complete: manual pins are applied to reporting locations. AI suggestions remain optional draft review data.',
      reviewedAt: new Date().toISOString(),
    });
    setToast('Manual pins applied to locations');
  };
  const applyMapReview = () => {
    const hasFlatLocations = Array.isArray(mapReview && mapReview.locations) && mapReview.locations.length;
    const hasFloorLocations = Array.isArray(mapReview && mapReview.buildingFloors)
      && mapReview.buildingFloors.some((floor) => Array.isArray(floor.locations) && floor.locations.length);
    if (!mapReview || (!hasFlatLocations && !hasFloorLocations)) {
      setToast('Run map review before applying standardized locations');
      return;
    }
    const floorLocations = (Array.isArray(mapReview.buildingFloors) ? mapReview.buildingFloors : [])
      .flatMap((floor) => (floor.locations || []).map((loc) => ({
        ...loc,
        id: loc.locationId || loc.id,
        floor: floor.name,
        confidence: loc.confidence || floor.confidence,
      })));
    const reviewLocations = floorLocations.length ? floorLocations : mapReview.locations;
    const existing = new Map(setup.locations.map((loc) => [loc.id, loc]));
    const standardized = reviewLocations.slice(0, 24).map((loc, i) => {
      const id = loc.id || stableSetupLocationId(loc.name);
      const current = existing.get(id) || {};
      return {
        ...current,
        id,
        name: loc.name || current.name || `Location ${i + 1}`,
        floor: loc.floor || current.floor || 'Ground',
        area: loc.area || current.area || 'Common',
        active: loc.councilAction !== 'do-not-publish' && loc.councilAction !== 'hide',
        tested: !!current.tested,
        qrId: current.qrId || ('AI-' + String(id).toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 8)),
        mapX: Number(loc.mapX ?? current.mapX ?? current.x ?? 0),
        mapY: Number(loc.mapY ?? current.mapY ?? current.y ?? 0),
      };
    });
    const renderedMap = createSetupRenderedMap({ setup, locations: standardized, source: 'approved-ai-review' });
    patchSetup((s) => ({ ...s, locations: standardized }));
    setMapUpload({
      phase: 'phase-3',
      status: 'AI approved and schematic ready',
      locationsMatched: standardized.length,
      approvedReview: {
        approvedAt: new Date().toISOString(),
        provider: mapReview.provider || mapUpload.reviewProvider || 'unknown',
        floorCount: (mapReview.buildingFloors || mapReview.floors || []).length,
        locationCount: standardized.length,
      },
      renderedMap,
      notes: 'AI suggestions were approved by council and converted into a Phase 3 schematic preview. QR codes still need to be tested before launch.',
    });
    setToast('AI suggestions approved and schematic generated');
  };
  const createPrintOrder = () => {
    setPosterFulfillment({ method: 'send-to-print', status: 'Draft order' });
    setToast('Print order draft created');
  };
  const downloadPosterPack = () => {
    setPosterFulfillment({ method: 'download', status: 'Poster PDF ready' });
    setToast('Poster PDF pack prepared');
  };
  const markReady = () => {
    if (!readiness.launchReady) {
      setToast('Finish required setup before launch');
      return;
    }
    set({ activated: true });
    setToast('Building setup marked ready');
  };
  const requiredCopy = readiness.launchReady ? 'All required launch checks are complete.' : readiness.next ? 'Next required step: ' + readiness.next.label : 'Finish optional branding when ready.';
  const StepFooter = () => (
    <div className="ds-step-footer">
      <button className="d-btn out" disabled={stepIndex === 0} onClick={goBack}>Back</button>
      <div className="ds-step-footnote">{currentStep.required ? 'Required for launch' : 'Optional - can be skipped'}</div>
      {stepIndex < stepIds.length - 1 ?
        <button className="d-btn acc" onClick={goNext}>Next: {steps[stepIndex + 1].label}</button> :
        <button className="d-btn acc" disabled={!readiness.launchReady} onClick={markReady}><Icon name="check" size={17} /> {setup.activated ? 'Ready' : 'Mark ready to launch'}</button>}
    </div>
  );

  return (
    <div className="d-list ds-setup">
      <div className="d-topbar"><div><h2>Building setup</h2><div className="sub">{setup.buildingName} · QR intake launch checklist</div></div></div>
      <div className="d-scroll ds-setup-scroll">
        <aside className="ds-readiness">
          <div className="ds-readiness-card">
            <DesktopLogoPreview setup={setup} />
            <div className="ds-ready-num">{readiness.percent}%</div>
            <div className="ds-ready-label">Launch readiness</div>
            <div className="ds-ready-bar"><span style={{ width: readiness.percent + '%' }} /></div>
            <p>{requiredCopy}</p>
          </div>
          <div className="ds-mobile-stepper-head" aria-hidden="true"><span>Setup path</span><b>Step {stepIndex + 1} of {steps.length}: {currentStep.label}</b></div>
          <div className="ds-task-list">
            {steps.map((s, i) => {
              const isCurrent = panel === s.id;
              const done = !!stepDone[s.id];
              const accessibleLabel = [
                isCurrent ? 'Current step' : '',
                done ? 'Completed' : '',
                'Step ' + (i + 1) + ' of ' + steps.length + ': ' + s.label,
                s.required ? 'Required for launch' : 'Optional',
                s.hint,
              ].filter(Boolean).join('. ');
              return (
                <button key={s.id} data-step={i + 1} aria-current={isCurrent ? 'step' : undefined} aria-label={accessibleLabel} className={(done ? 'done ' : '') + (isCurrent ? 'on' : '')} onClick={() => showIntro ? beginSetup(s.id) : setPanel(s.id)}>
                  <span><Icon name={done ? 'check' : 'clock'} size={15} /></span>
                  <b>{i + 1}. {s.label}</b>
                  {!s.required && <small>Optional</small>}
                  <em>{s.hint}</em>
                </button>
              );
            })}
          </div>
        </aside>
        <main className="ds-work">
          {showIntro ? <section className="ds-card ds-intro-card">
            <div className="ds-intro-hero">
              <span><Icon name="qr" size={18} /> Council setup</span>
              <h3>Set up QR issue reporting for this building</h3>
              <p>StrataSnap gives residents a QR code for common-property issues and gives council one place to triage, assign responsibility, send resident-safe updates, and track what has been resolved.</p>
              <div className="ds-intro-actions">
                <button className="d-btn acc" onClick={() => beginSetup('profile')}>Start with building details</button>
                <button className="d-btn out" onClick={() => beginSetup(readiness.next ? (nextToStep[readiness.next.id] || 'profile') : 'profile')}>Skip intro</button>
              </div>
            </div>
            <div className="ds-intro-flow" aria-label="Setup steps">
              {steps.map((s, i) => (
                <button key={s.id} className="ds-intro-step" onClick={() => beginSetup(s.id)}>
                  <span>{i + 1}</span>
                  <b>{s.label}</b>
                  <em>{s.required ? 'Required' : 'Optional'}</em>
                  <small>{s.hint}</small>
                </button>
              ))}
            </div>
            <div className="ds-intro-note">
              <Icon name="shieldCheck" size={17} />
              <span>Best pilot path: confirm the building, pick 5-10 locations, generate posters, scan-test every QR code, then launch.</span>
            </div>
          </section> : <React.Fragment>
          <div className="ds-linear-head">
            <span>Step {stepIndex + 1} of {steps.length}</span>
            <h3>{currentStep.label}</h3>
            <p>{currentStep.hint}</p>
          </div>
          <div className="ds-step-guidance">
            <div><span>Council decision</span><b>{currentStep.decision}</b></div>
            <div><span>Resident impact</span><b>{currentStep.impact}</b></div>
          </div>

          {panel === 'profile' && <section className="ds-card">
            <div className="ds-card-head"><h3>Confirm the building details</h3><p>These values appear in the app, QR poster labels, status pages, and notification routing.</p></div>
            <div className="ds-council-sequence">
              <div className={isTaskDone('profile') ? 'done' : ''}><Icon name="building" size={17} /><span>1</span><b>Building record</b><small>Name, address, lot count</small></div>
              <div className={isTaskDone('routing') ? 'done' : ''}><Icon name="send" size={17} /><span>2</span><b>Report routing</b><small>One accountable inbox</small></div>
              <div className={isTaskDone('emergency') ? 'done' : ''}><Icon name="alert" size={17} /><span>3</span><b>Safety path</b><small>Emergency instructions</small></div>
            </div>
            <div className="ds-form-grid">
              <label><span>Building name</span><input className="d-inp" value={setup.buildingName} onChange={(e) => set({ buildingName: e.target.value })} /></label>
              <label><span>Strata plan</span><input className="d-inp" value={setup.strataPlan} onChange={(e) => set({ strataPlan: e.target.value })} /></label>
              <label className="wide"><span>Address</span><input className="d-inp" value={setup.address} onChange={(e) => set({ address: e.target.value })} /></label>
              <label><span>Lots</span><input className="d-inp" type="number" min="1" value={setup.lots} onChange={(e) => set({ lots: Number(e.target.value || 0) })} /></label>
              <label><span>Manager mode</span><select className="d-inp" value={setup.managerMode} onChange={(e) => set({ managerMode: e.target.value })}><option>Council only</option><option>Council + property manager</option><option>Property manager led</option></select></label>
              <label className="wide"><span>Routing email</span><input className="d-inp" type="email" aria-invalid={!validEmail} value={setup.routingEmail} onChange={(e) => set({ routingEmail: e.target.value })} />{!validEmail && <small className="ds-field-error">Use a valid email so notifications can be tested.</small>}</label>
              <label className="wide"><span>Emergency contact shown before reports</span><input className="d-inp" value={setup.emergencyContact} onChange={(e) => set({ emergencyContact: e.target.value })} /></label>
            </div>
            <StepFooter />
          </section>}

          {panel === 'brand' && <section className="ds-card">
            <div className="ds-card-head"><h3>Set app accent, poster branding, and QR colour</h3><p>Choose an approved accent and keep QR contrast high for reliable scanning.</p></div>
            <div className="ds-brand-panel">
              <div className="ds-logo-block">
                <DesktopLogoPreview setup={setup} />
                <div className="ds-brand-actions">
                  <label className="d-btn out ds-upload">Upload logo<input type="file" accept="image/png,image/jpeg" onChange={(e) => readDesktopLogoFile(e.target.files[0], (url) => { set({ logoUrl: url }); setToast('Logo uploaded'); }, setToast)} /></label>
                  {setup.logoUrl && <button className="linkbtn" onClick={() => set({ logoUrl: '' })}>Remove logo</button>}
                </div>
                <p>PNG or JPG only. A building can launch without a logo.</p>
              </div>
              <div className="ds-style-card">
                <div className="ds-qr-large"><QRGraphic value={(activeLocations[0] || {}).qrId || 'SS'} size={132} fg={qrStyle.foreground || '#0A1B3F'} bg={qrStyle.background || '#FFFFFF'} /></div>
                <div className="ds-style-controls">
                  <div className="ds-field-label">App accent</div>
                  <div className="ds-theme-grid" aria-label="App accent">
                    {themes.map((theme) => (
                      <button key={theme.key} className={'ds-theme-choice' + (setup.appTheme === theme.key ? ' on' : '')} aria-pressed={setup.appTheme === theme.key} onClick={() => setAppTheme(theme.key)}>
                        <span className="ds-theme-swatch" style={{ background: theme.acc }} />
                        <span>{theme.label}</span>
                        <Icon name={setup.appTheme === theme.key ? 'check' : 'chevronR'} size={15} />
                      </button>
                    ))}
                  </div>
                  <div className="ds-color-grid qr-only">
                    <label><span>QR dark colour</span><input className="ds-color-input" type="color" value={qrStyle.foreground || '#0A1B3F'} onChange={(e) => setQrStyle({ foreground: e.target.value })} /></label>
                    <label><span>QR background</span><input className="ds-color-input" type="color" value={qrStyle.background || '#FFFFFF'} onChange={(e) => setQrStyle({ background: e.target.value })} /></label>
                  </div>
                </div>
                <div className="ds-inline-status">Recommended: dark QR on white. Avoid low-contrast colours for waterproof stickers.</div>
              </div>
            </div>
            <StepFooter />
          </section>}

          {panel === 'locations' && <section className="ds-card">
            <div className="ds-card-head"><h3>Choose QR reporting locations</h3><p>Start with common-property areas where residents otherwise describe the location vaguely. Keep at least five active locations for the pilot.</p></div>
            <div className="ds-location-grid">
              {setup.locations.map((l) => (
                <button key={l.id} className={'ds-location' + (l.active ? ' active' : '')} aria-pressed={l.active} onClick={() => updateLoc(l.id, { active: !l.active })}>
                  <span className="ds-loc-ic"><Icon name="pin" size={17} /></span>
                  <span><b>{l.name}</b><small>{l.floor} · {l.area}</small></span>
                  <em>{l.active ? 'Active' : 'Off'}</em>
                </button>
              ))}
            </div>
            <div className="ds-inline-status">{activeLocations.length} active reporting locations. {activeLocations.length < 5 ? 'Add more before launch.' : 'Enough for the pilot.'}</div>
            <StepFooter />
          </section>}

          {panel === 'map' && <section className="ds-card">
            <div className="ds-card-head"><h3>Build the map in phases</h3><p>Start with the original strata plan and council-edited pins. AI suggestions stay optional until council approves them.</p></div>
            <div className="ds-map-phase-list">
              {mapPipelinePhaseItems(mapUpload).map((item) => (
                <div key={item.id} className={'ds-map-phase ' + item.id}>
                  <span>{item.label}</span>
                  <b>{item.title}</b>
                  <em>{item.state}</em>
                </div>
              ))}
            </div>
            <div className="ds-map-upload-grid">
              <div className="ds-map-drop" data-testid="manual-map-canvas" onClick={handleManualMapClick}>
                {mapUpload.imageUrl ? <React.Fragment>
                  <img src={mapUpload.imageUrl} alt="Uploaded building map preview" />
                  <div className="ds-map-pin-layer" aria-label="Manual map pins">
                    {manualPins.map((pin, index) => (
                      <button
                        key={pin.id}
                        className={'ds-map-pin' + (selectedMapPinId === pin.id ? ' on' : '')}
                        style={{ left: (pin.mapX || 0.5) * 100 + '%', top: (pin.mapY || 0.5) * 100 + '%' }}
                        aria-label={(pin.name || `Manual pin ${index + 1}`) + ' map pin'}
                        onClick={(e) => {e.stopPropagation();setSelectedMapPinId(pin.id);}}>
                        {index + 1}
                      </button>
                    ))}
                  </div>
                </React.Fragment> : <div><Icon name="ssMap" size={34} /><b>No map uploaded</b><span>PNG or JPG, under 7 MB</span></div>}
              </div>
              <div className="ds-map-controls">
                <label className="d-btn out ds-upload">Upload strata plan<input type="file" accept="image/png,image/jpeg" onChange={(e) => readDesktopMapFile(e.target.files[0], (review) => { setSelectedMapPinId(''); setMapUpload(review); setToast('Strata plan uploaded'); }, setToast)} /></label>
                <button className="d-btn out" disabled={!mapUpload.imageUrl} onClick={seedManualPins}><Icon name="pin" size={17} /> Load active locations as pins</button>
                <button className="d-btn acc" disabled={!mapUpload.imageUrl || !manualPins.length} onClick={applyManualPins}><Icon name="check" size={17} /> Apply manual pins</button>
                <button className="d-btn out" disabled={!mapUpload.imageUrl || mapReviewing} onClick={runMapReview}><Icon name="spark" size={17} /> {mapReviewing ? 'Getting suggestions...' : 'Get AI suggestions'}</button>
                {mapReview && <button className="d-btn acc" onClick={applyMapReview}><Icon name="shieldCheck" size={17} /> Approve AI suggestions</button>}
                <button className="d-btn out" disabled={!mapUpload.imageUrl || !activeLocations.length} onClick={() => renderSchematicMap(activeLocations, mapUpload.approvedReview ? 'approved-locations' : 'manual-locations')}><Icon name="ssMap" size={17} /> Render schematic preview</button>
                {mapUpload.imageUrl && <button className="linkbtn" onClick={() => {setSelectedMapPinId('');setMapUpload({ phase: 'phase-1', fileName: '', imageUrl: '', standardizedImage: null, status: 'Not uploaded', manualPins: [], floorsDetected: 0, locationsMatched: 0, reviewProvider: '', review: null, approvedReview: null, renderedMap: null, notes: '', reviewedAt: '' });}}>Remove map</button>}
                <div className="ds-review-stack">
                  <div><span>Status</span><b>{mapUpload.status || 'Not uploaded'}</b></div>
                  <div><span>Image standard</span><b>{mapUpload.standardizedImage ? `${mapUpload.standardizedImage.width} x ${mapUpload.standardizedImage.height} JPG` : 'Not processed'}</b></div>
                  <div><span>Manual pins</span><b>{manualPins.length}</b></div>
                  <div><span>Floors detected</span><b>{mapUpload.floorsDetected || 0}</b></div>
                  <div><span>Locations matched</span><b>{mapUpload.locationsMatched || 0}/{activeLocations.length}</b></div>
                  <div><span>Review source</span><b>{mapUpload.reviewProvider || 'None'}</b></div>
                  <div><span>Approval</span><b>{mapUpload.approvedReview ? 'Council approved' : 'Not approved'}</b></div>
                  <div><span>Schematic</span><b>{mapUpload.renderedMap ? `${mapUpload.renderedMap.zoneCount || 0} zones` : 'Not rendered'}</b></div>
                </div>
                <p className="ds-map-note">{mapUpload.notes || 'Upload a source image, add manual pins, then optionally request AI suggestions.'}</p>
              </div>
            </div>
            {!!manualPins.length && <div className="ds-map-pin-editor">
              <div className="ds-map-pin-editor-head"><h4>Manual pins</h4><span>{manualPins.length} council-edited locations</span></div>
              {manualPins.map((pin, index) => (
                <div key={pin.id} className={'ds-map-pin-row' + (selectedMapPinId === pin.id ? ' on' : '')}>
                  <button className="ds-map-pin-index" onClick={() => setSelectedMapPinId(pin.id)}>{index + 1}</button>
                  <label><span>Name</span><input className="d-inp" value={pin.name || ''} onChange={(e) => updateManualPin(pin.id, { name: e.target.value })} /></label>
                  <label><span>Floor</span><input className="d-inp" value={pin.floor || ''} onChange={(e) => updateManualPin(pin.id, { floor: e.target.value })} /></label>
                  <label><span>Area</span><input className="d-inp" value={pin.area || ''} onChange={(e) => updateManualPin(pin.id, { area: e.target.value })} /></label>
                  <button className="linkbtn" onClick={() => removeManualPin(pin.id)}>Remove</button>
                </div>
              ))}
            </div>}
            {mapReview && <div className="ds-map-review">
              <div className="ds-map-review-head">
                <span><Icon name="shieldCheck" size={16} /> Phase 2 draft suggestions</span>
                <b>{(mapReview.buildingFloors || mapReview.floors || []).length} floors · {(mapReview.locations || []).length} locations · {(mapReview.posterSuggestions || []).length} poster points</b>
              </div>
              <div className="ds-map-review-grid">
                <div>
                  <h4>Floors</h4>
                  <div className="ds-map-chip-list">
                    {(mapReview.floors || []).map((floor) => <span key={floor.id || floor.name}>{floor.name}<small>{Math.round((floor.confidence || 0) * 100)}%</small></span>)}
                  </div>
                </div>
                <div>
                  <h4>Building floors</h4>
                  <div className="ds-map-location-list">
                    {(mapReview.buildingFloors || []).slice(0, 5).map((floor) => (
                      <div key={floor.id || floor.name}><b>{floor.displayOrder}. {floor.name}</b><span>{floor.locationCount || 0} review locations · {floor.source || 'standardized-review'}</span></div>
                    ))}
                  </div>
                </div>
                <div>
                  <h4>Standardized locations</h4>
                  <div className="ds-map-location-list">
                    {(mapReview.locations || []).slice(0, 8).map((loc) => (
                      <div key={loc.id || loc.name}><b>{loc.name}</b><span>{loc.floor} · {loc.area} · {loc.councilAction || 'confirm'}</span></div>
                    ))}
                  </div>
                </div>
                <div>
                  <h4>Poster points</h4>
                  <div className="ds-map-location-list">
                    {(mapReview.posterSuggestions || []).slice(0, 6).map((poster) => (
                      <div key={poster.locationId || poster.label}><b>{poster.label}</b><span>{poster.floor} · {Math.round((poster.mapX || 0) * 100)}%, {Math.round((poster.mapY || 0) * 100)}%</span></div>
                    ))}
                  </div>
                </div>
                <div>
                  <h4>Questions for council</h4>
                  <ul className="ds-map-questions">{(mapReview.councilQuestions || []).slice(0, 4).map((q) => <li key={q}>{q}</li>)}</ul>
                </div>
              </div>
              {!!(mapReview.uncertainties || []).length && <div className="ds-map-uncertainties"><b>Uncertainties</b>{mapReview.uncertainties.slice(0, 3).map((item) => <span key={item}>{item}</span>)}</div>}
            </div>}
            <SetupSchematicPreview renderedMap={mapUpload.renderedMap} onZone={(zone, floor) => setToast(`${zone.name} selected on ${floor.name}`)} />
            <StepFooter />
          </section>}

          {panel === 'qr' && <section className="ds-card">
            <div className="ds-card-head"><h3>Prepare posters and printing</h3><p>Review public QR IDs, choose whether to print in-house, download a PDF pack, or send a draft order for waterproof stickers.</p></div>
            <div className="ds-print-options">
              <button className={'ds-print-option' + (posterFulfillment.method === 'download' ? ' on' : '')} onClick={() => setPosterFulfillment({ method: 'download' })}>
                <Icon name="download" size={18} /><b>Download poster PDFs</b><span>Best for office printing or Canva editing.</span>
              </button>
              <button className={'ds-print-option' + (posterFulfillment.method === 'in-house' ? ' on' : '')} onClick={() => setPosterFulfillment({ method: 'in-house' })}>
                <Icon name="printer" size={18} /><b>Print in-house</b><span>Use waterproof vinyl sheets and a colour printer.</span>
              </button>
              <button className={'ds-print-option' + (posterFulfillment.method === 'send-to-print' ? ' on' : '')} onClick={() => setPosterFulfillment({ method: 'send-to-print' })}>
                <Icon name="send" size={18} /><b>Send for printing</b><span>Create a vendor-ready sticker order.</span>
              </button>
            </div>
            <div className="ds-print-form">
              <label><span>Material</span><select className="d-inp" value={posterFulfillment.material || 'Waterproof vinyl'} onChange={(e) => setPosterFulfillment({ material: e.target.value })}><option>Waterproof vinyl</option><option>Laminated paper</option><option>Indoor adhesive label</option></select></label>
              <label><span>Size</span><select className="d-inp" value={posterFulfillment.size || '3 x 4 in'} onChange={(e) => setPosterFulfillment({ size: e.target.value })}><option>3 x 4 in</option><option>4 x 6 in</option><option>5 x 7 in</option></select></label>
              <label><span>Quantity</span><input className="d-inp" type="number" min="1" value={posterFulfillment.quantity || activeLocations.length} onChange={(e) => setPosterFulfillment({ quantity: Number(e.target.value || 0) })} /></label>
              <label><span>Contact / vendor email</span><input className="d-inp" value={posterFulfillment.shippingContact || ''} onChange={(e) => setPosterFulfillment({ shippingContact: e.target.value })} placeholder="printer@example.com" /></label>
            </div>
            <div className="ds-qr-table">
              {!activeLocations.length && <div className="ds-empty-step">No active locations yet. Go back and turn on at least one reporting location.</div>}
              {activeLocations.map((l) => {
                const scanUrl = SS.qrScanUrl(setup, l);
                return (
                  <div className="ds-qr-row" key={l.id}>
                    <span className="ds-mini-qr"><QRGraphic value={scanUrl} size={46} quiet={0} fg={qrStyle.foreground || '#0A1B3F'} bg={qrStyle.background || '#FFFFFF'} /></span>
                    <span><b>{l.name}</b><small>{scanUrl.replace(/^https?:\/\//, '')}</small></span>
                    <span className={l.tested ? 'tested' : ''}>{l.tested ? 'Tested' : 'Not tested'}</span>
                    <a className="d-btn out sm" href={scanUrl} target="_blank" rel="noreferrer">Open</a>
                    <button className="d-btn out sm" onClick={() => { updateLoc(l.id, { tested: true, lastScanTestedAt: new Date().toISOString() }); setToast(l.name + ' scan tested'); }}>Test</button>
                  </div>
                );
              })}
            </div>
            <div className="ds-print-actions">
              <button className="d-btn out" onClick={downloadPosterPack}><Icon name="download" size={17} /> Prepare PDF pack</button>
              <button className="d-btn acc" onClick={createPrintOrder}><Icon name="send" size={17} /> Create print order draft</button>
            </div>
            <StepFooter />
          </section>}

          {panel === 'test' && <section className="ds-card">
            <div className="ds-card-head"><h3>Test the flow before printing posters</h3><p>Confirm a scan creates a report, notification routes to the right email, and the status page can be tracked.</p></div>
            <div className="ds-test-actions">
              <button className="d-btn out" disabled={!activeLocations.length} onClick={runScan}><Icon name="qr" size={17} /> Run test scan</button>
              <button className="d-btn out" disabled={!validEmail} onClick={sendNotification}><Icon name="send" size={17} /> Send notification test</button>
              <button className="d-btn acc" disabled={!readiness.launchReady} onClick={markReady}><Icon name="check" size={17} /> {setup.activated ? 'Ready' : 'Mark ready to launch'}</button>
            </div>
            <div className="ds-test-summary">
              <span className={isTaskDone('scan') ? 'done' : ''}>{testedLocations.length}/{activeLocations.length} locations tested</span>
              <span className={isTaskDone('notify') ? 'done' : ''}>Notification test {isTaskDone('notify') ? 'sent' : 'not sent'}</span>
              <span className={readiness.launchReady ? 'done' : ''}>{readiness.launchReady ? 'Ready for posters' : 'Not ready to print posters'}</span>
            </div>
            <StepFooter />
          </section>}
          </React.Fragment>}
        </main>
        <aside className="ds-preview">
          <div className="ds-poster-preview">
            <div className="poster-mini-eyebrow">{setup.buildingName}</div>
            <div className="poster-mini-title">Report an issue</div>
            <div className="poster-mini-qr"><QRGraphic value={(setup.locations.find((l) => l.active) || {}).qrId || 'SS'} size={128} fg={qrStyle.foreground || '#0A1B3F'} bg={qrStyle.background || '#FFFFFF'} /></div>
            <div className="poster-mini-loc"><Icon name="pin" size={12} /> {(setup.locations.find((l) => l.active) || {}).name || 'First location'}</div>
          </div>
          <div className="ds-preview-note">
            <b>Activation rule</b>
            <p>Do not print posters until one test scan and one notification test have passed.</p>
          </div>
        </aside>
      </div>
    </div>
  );
}

function PostersGrid({ tickets, setup, setToast }) {
  const SS = window.SS;
  const setupById = new Map(((setup || {}).locations || []).map((l) => [l.id, l]));
  const posterLocations = SS.setupLocations(setup, { activeOnly: true });
  const stats = SS.locationStats(tickets || []);
  const statFor = (id) => stats.find((s) => s.loc.id === id) || { count: 0, oldestDays: 0, tone: 'green' };
  const printPosters = (label) => {
    window.print();
    setToast(label ? 'Print dialog opened for ' + label : 'Print dialog opened for poster sheet');
  };
  return (
    <div className="d-list">
      <div className="d-topbar"><div><h2>Poster network</h2><div className="sub">One QR code per location. Print, place, and monitor building intake.</div></div></div>
      <div className="poster-toolbar">
        <div className="poster-toolbar-card"><Icon name="qr" size={18} /><b>{posterLocations.length}</b><span>QR locations</span></div>
        <div className="poster-toolbar-card"><Icon name="printer" size={18} /><b>Vinyl</b><span>waterproof labels</span></div>
        <div className="poster-toolbar-card"><Icon name="shieldCheck" size={18} /><b>No app</b><span>resident intake</span></div>
      </div>
      <div className="d-scroll poster-network">
        <div className="poster-grid">
          {posterLocations.map((l) =>
          {
            const s = statFor(l.id);
            const sl = setupById.get(l.id) || {};
            const qrId = sl.qrId || ('SS-' + l.id.toUpperCase());
            const scanUrl = SS.qrScanUrl(setup, { ...l, qrId });
            return (
              <div key={l.id} className="poster-card">
                <div className="poster-card-top">
                  <span className="poster-card-ic"><Icon name="ssPoster" size={18} /></span>
                  <div><div className="poster-loc-name">{l.name}</div><div className="poster-loc-meta">{l.floor} · {l.area}</div></div>
                  <span className={'poster-open tone-' + s.tone}>{s.count} open</span>
                </div>
                <div className="poster-preview">
                  <div className="poster-mini-eyebrow">{setup.buildingName || 'Harbour View Strata'}</div>
                  <div className="poster-mini-title">Report an issue</div>
                  <div className="poster-mini-qr"><QRGraphic value={scanUrl} size={112} fg={(setup.qrStyle || {}).foreground || '#0A1B3F'} bg={(setup.qrStyle || {}).background || '#FFFFFF'} /></div>
                  <div className="poster-mini-loc"><Icon name="pin" size={12} /> {l.name}</div>
                </div>
                <div className="poster-meta">
                  <div><span>Sticker</span><b>3 x 4 in waterproof vinyl</b></div>
                  <div><span>Code</span><b>{qrId}</b></div>
                  <div><span>Reports</span><b>{s.count ? 'Oldest open ' + s.oldestDays + 'd' : 'No open issues'}</b></div>
                </div>
                <div className="poster-actions">
                  <a className="d-btn out sm" href={scanUrl} target="_blank" rel="noreferrer"><Icon name="qr" size={15} /> Test</a>
                  <button className="d-btn out sm" onClick={() => printPosters(l.name)}><Icon name="download" size={15} /> PDF</button>
                  <button className="d-btn acc sm" onClick={() => printPosters(l.name)}><Icon name="printer" size={15} /> Print</button>
                </div>
              </div>
            );
          }
          )}
        </div>
      </div>
    </div>);

}

function UsageReport({ tickets, setup, setToast }) {
  const SS = window.SS;
  const report = buildUsageReport(setup, tickets || []);
  const activeCount = report.activeLocations.length || 1;
  const scanPct = Math.round(report.testedLocations.length / activeCount * 100);
  const auth = setup.authProvider || {};
  const database = setup.database || {};
  const billing = setup.billing || {};
  const mapUpload = setup.mapUpload || {};
  const topLocations = report.locationRows.slice(0, 6);
  const topCategories = report.categories.slice(0, 5);
  return (
    <div className="d-list usage-page">
      <div className="d-topbar">
        <div><h2>Pilot usage report</h2><div className="sub">{setup.buildingName} · onboarding, QR intake, and council workflow</div></div>
        <button className="d-btn acc" onClick={() => exportUsageReport(setup, tickets, setToast)}><Icon name="download" size={17} /> Export usage report</button>
      </div>
      <div className="d-scroll usage-scroll">
        <section className="usage-hero">
          <div>
            <div className="d-secl" style={{ marginTop: 0 }}>Testing posture</div>
            <h3>{report.readiness.percent}% onboarding · {report.pilot.percent}% stack</h3>
            <p>{report.readiness.launchReady ? 'Council onboarding is ready for a live pilot walkthrough.' : 'Finish the remaining setup checks before inviting external testers.'}</p>
          </div>
          <div className="usage-hero-grid">
            <span><b>{tickets.length}</b><small>Total reports</small></span>
            <span><b>{report.qrTickets.length}</b><small>QR reports</small></span>
            <span><b>{report.openTickets.length}</b><small>Open issues</small></span>
            <span><b>{report.closedTickets.length}</b><small>Resolved / closed</small></span>
          </div>
        </section>

        <section className="usage-grid">
          <div className="usage-panel">
            <div className="usage-panel-head"><Icon name="qr" size={18} /><div><h3>Resident intake</h3><p>What residents created through QR and public report flows.</p></div></div>
            <div className="usage-metrics">
              <div><b>{report.qrTickets.length}</b><span>QR reports</span></div>
              <div><b>{report.residentContacts.size}</b><span>resident contacts</span></div>
              <div><b>{report.needsClarification.length}</b><span>needs clarification</span></div>
              <div><b>{report.duplicateClosed.length}</b><span>closed duplicate</span></div>
            </div>
            <div className="usage-list">
              {topCategories.map((row) => <div key={row.cat.id}><span>{row.cat.short || row.cat.label}</span><b>{row.count}</b></div>)}
              {!topCategories.length && <div><span>No resident reports yet</span><b>0</b></div>}
            </div>
          </div>

          <div className="usage-panel">
            <div className="usage-panel-head"><Icon name="shieldCheck" size={18} /><div><h3>Council workflow</h3><p>Triage states that protect council from vague or out-of-scope intake.</p></div></div>
            <div className="usage-metrics">
              <div><b>{report.publicUpdateCount}</b><span>resident updates</span></div>
              <div><b>{report.privateNoteCount}</b><span>private notes</span></div>
              <div><b>{report.needsOwnership.length}</b><span>ownership unclear</span></div>
              <div><b>{report.notCommonProperty.length}</b><span>not common property</span></div>
            </div>
            <div className="usage-list">
              {report.statusRows.map((row) => <div key={row.status}><span>{row.status}</span><b>{row.count}</b></div>)}
            </div>
          </div>
        </section>

        <section className="usage-panel usage-wide">
          <div className="usage-panel-head"><Icon name="ssPoster" size={18} /><div><h3>Poster performance</h3><p>Each QR code should be scan-tested before residents see it.</p></div><span className={'usage-badge' + (scanPct === 100 ? ' ready' : '')}>{scanPct}% tested</span></div>
          <div className="usage-location-table">
            <div className="usage-location-head"><span>Location</span><span>QR code</span><span>Reports</span><span>Open</span><span>Test</span></div>
            {topLocations.map((row) => (
              <div className="usage-location-row" key={row.loc.id}>
                <span><b>{row.loc.name}</b><small>{row.loc.floor} · {row.loc.area}</small></span>
                <span>{row.loc.qrId || SS.posterCode(row.loc.id)}</span>
                <span>{row.reports}</span>
                <span>{row.open}</span>
                <span className={row.loc.tested ? 'ready' : ''}>{row.loc.tested ? 'Passed' : 'Needed'}</span>
              </div>
            ))}
          </div>
        </section>

        <section className="usage-grid">
          <div className="usage-panel">
            <div className="usage-panel-head"><Icon name="ssMap" size={18} /><div><h3>Map pipeline</h3><p>Phase 2 and Phase 3 readiness for map-based reporting.</p></div></div>
            <div className="usage-list">
              <div><span>Original plan uploaded</span><b>{mapUpload.imageUrl ? 'Yes' : 'No'}</b></div>
              <div><span>AI suggestions reviewed</span><b>{mapUpload.approvedReview ? 'Approved' : mapUpload.review ? 'Drafted' : 'Not yet'}</b></div>
              <div><span>Detected floors</span><b>{mapUpload.floorsDetected || 0}</b></div>
              <div><span>Locations matched</span><b>{mapUpload.locationsMatched || 0}</b></div>
              <div><span>Schematic rendered</span><b>{mapUpload.renderedMap ? 'Ready' : 'Pending'}</b></div>
            </div>
          </div>

          <div className="usage-panel">
            <div className="usage-panel-head"><Icon name="lock" size={18} /><div><h3>Auth and launch readiness</h3><p>What must be true before multiple testers use the same data.</p></div></div>
            <div className="usage-list">
              <div><span>Auth mode</span><b>{auth.mode || 'mock'}</b></div>
              <div><span>Auth provider</span><b>{auth.pilotProvider || 'Not selected'}</b></div>
              <div><span>Database mode</span><b>{database.mode || 'localStorage'}</b></div>
              <div><span>Schema status</span><b>{database.schemaStatus || 'Not migrated'}</b></div>
              <div><span>Stripe checkout</span><b>{billing.checkoutConfigured ? 'Configured' : 'Needed'}</b></div>
            </div>
          </div>
        </section>
      </div>
    </div>);
}

// ── modals (two-lane composer + state machine) ─────────────────
function UpdatesFeed({ tickets, onOpen }) {
  const SS = window.SS;
  const items = SS.feed(tickets, 40);
  const tag = (e) => {
    if (e.kind === 'update') return { cls: 'pub', icon: 'eye', label: 'Update to reporter' };
    if (e.kind === 'note') return { cls: 'priv', icon: 'lock', label: 'Private note' };
    if (e.kind === 'submitted') return { cls: 'new', icon: 'plus', label: 'New report' };
    return { cls: 'status', icon: 'refresh', label: 'Status change' };
  };
  return (
    <div className="d-list">
      <div className="d-topbar"><div><h2>Updates</h2><div className="sub">Recent activity across the building</div></div></div>
      <div className="d-scroll">
        <div className="d-feed">
          {items.map((it, i) => {
            const tg = tag(it.e);
            return (
              <button className="d-feed-item" key={i} onClick={() => onOpen(it.ticketId)}>
                <span className={'feed-mk ' + tg.cls}><Icon name={tg.icon} size={15} /></span>
                <div className="d-feed-body">
                  <div className="d-feed-top"><span className="feed-tag">{tg.label}</span><span className="feed-when">{SS.whenLabel(it.e.at)}</span></div>
                  <div className="d-feed-text">{it.e.body}</div>
                  <div className="d-feed-ref"><span className="id">{it.ticketId}</span> · {it.summary} · {it.locationName.replace(' – Elevator Lobby', '')}</div>
                </div>
                <Icon name="chevronR" size={16} style={{ color: 'var(--ink-300)', alignSelf: 'center', flex: '0 0 auto' }} />
              </button>);

          })}
        </div>
      </div>
    </div>);

}

function SettingsScreen({ setup, patchSetup, setToast, onOpenSetup, pilotApi, refreshFromApi }) {
  const SS = window.SS;
  const Store = window.SSStore;
  const readiness = SS.setupProgress(setup);
  const pilot = SS.pilotReadiness(setup);
  const [serviceReadiness, setServiceReadiness] = useState(null);
  const [serviceError, setServiceError] = useState('');
  const notif = setup.notifications || {};
  const auth = setup.authProvider || {};
  const database = setup.database || {};
  const billing = setup.billing || {};
  const members = setup.councilMembers || [];
  useEffect(() => {
    if (!pilotApi || !pilotApi.readiness) {
      setServiceReadiness(null);
      setServiceError('');
      return;
    }
    let cancelled = false;
    pilotApi.readiness().then((data) => {
      if (!cancelled) {
        setServiceReadiness(data);
        setServiceError('');
      }
    }).catch((err) => {
      if (!cancelled) {
        setServiceReadiness(null);
        setServiceError(err.message);
      }
    });
    return () => {cancelled = true;};
  }, [pilotApi]);
  const set = (patch) => patchSetup(patch);
  const setNotif = (key, value) => set({ notifications: { ...notif, [key]: value } });
  const setAuth = (patch) => patchSetup((s) => ({ ...s, authProvider: { ...(s.authProvider || {}), ...patch } }));
  const setDatabase = (patch) => patchSetup((s) => ({ ...s, database: { ...(s.database || {}), ...patch } }));
  const setBilling = (patch) => patchSetup((s) => ({ ...s, billing: { ...(s.billing || {}), ...patch } }));
  const updateMember = (i, patch) => set({ councilMembers: members.map((m, idx) => idx === i ? { ...m, ...patch } : m) });
  const addMember = () => {
    const next = [...members, { name: 'New member', role: 'Council member', email: 'member@example.com' }];
    set({ councilMembers: next });
    setToast('Member added');
  };
  const resetSetup = () => {
    const seeded = Store.resetSetup(() => SS.setupSeed());
    patchSetup(seeded);
    setToast('Setup restored to demo defaults');
  };
  const createCheckout = () => {
    if (!pilotApi) {
      setToast('Start the app with ?api=1 before creating a test checkout');
      return;
    }
    pilotApi.checkout(setup).then(() => refreshFromApi && refreshFromApi()).then(() => {
      setToast('Stripe test checkout created');
      if (pilotApi.readiness) pilotApi.readiness().then(setServiceReadiness).catch(() => {});
    }).catch((err) => setToast('Stripe test checkout failed: ' + err.message));
  };

  return (
    <div className="d-list settings-page">
      <div className="d-topbar"><div><h2>Settings</h2><div className="sub">{setup.buildingName} · app defaults and launch controls</div></div></div>
      <div className="d-scroll settings-scroll">
        <section className="settings-readiness">
          <div>
            <div className="d-secl" style={{ marginTop: 0 }}>Launch readiness</div>
            <h3>{readiness.percent}% complete</h3>
            <p>{readiness.next ? 'Next required setup item: ' + readiness.next.label : 'Ready to launch. Keep these settings current after onboarding.'}</p>
          </div>
          <button className="d-btn acc" onClick={onOpenSetup}><Icon name="shieldCheck" size={17} /> Open setup checklist</button>
        </section>

        <section className="pilot-readiness">
          <div className="pilot-ready-head">
            <div>
              <div className="d-secl" style={{ marginTop: 0 }}>Pilot testing readiness</div>
              <h3>{pilot.percent}% ready for external testing</h3>
              <p>Shows what can be tested now and what still needs real provider credentials before council pilots.</p>
            </div>
            <span className={'pilot-ready-badge' + (pilot.readyForPilot ? ' ready' : '')}>{pilot.ready}/{pilot.total} ready</span>
          </div>
          <div className="pilot-check-grid">
            {pilot.checks.map((c) => (
              <div key={c.id} className={'pilot-check ' + c.status}>
                <span className="pilot-check-ic"><Icon name={c.status === 'ready' ? 'check' : 'clock'} size={15} /></span>
                <div><b>{c.label}</b><p>{c.detail}</p></div>
              </div>
            ))}
          </div>
          <div className="pilot-service">
            <div className="pilot-service-head">
              <div>
                <b>Live service checks</b>
                <p>{pilotApi ? 'Read from the connected pilot API environment.' : 'Start with ?api=1 to test service configuration.'}</p>
              </div>
              <span className={'pilot-ready-badge' + (serviceReadiness && serviceReadiness.readyForExternalPilot ? ' ready' : '')}>
                {serviceReadiness ? serviceReadiness.ready + '/' + serviceReadiness.total + ' ready' : 'API not checked'}
              </span>
            </div>
            {serviceError && <div className="pilot-service-error"><Icon name="warn" size={15} /> {serviceError}</div>}
            {serviceReadiness &&
            <div className="pilot-service-grid">
              {serviceReadiness.checks.map((c) => (
                <div key={c.id} className={'pilot-service-check' + (c.ready ? ' ready' : '')}>
                  <span><Icon name={c.ready ? 'check' : 'clock'} size={14} /></span>
                  <div><b>{c.label}</b><p>{c.detail}</p></div>
                </div>
              ))}
            </div>}
          </div>
        </section>

        <div className="settings-grid">
          <section>
            <div className="d-secl">Building profile</div>
            <div className="set-card set-form">
              <label><span>Building name</span><input className="d-inp" value={setup.buildingName} onChange={(e) => set({ buildingName: e.target.value })} /></label>
              <label><span>Strata plan</span><input className="d-inp" value={setup.strataPlan} onChange={(e) => set({ strataPlan: e.target.value })} /></label>
              <label><span>Lots</span><input className="d-inp" type="number" min="1" value={setup.lots} onChange={(e) => set({ lots: Number(e.target.value || 0) })} /></label>
              <label><span>City</span><input className="d-inp" value={setup.city} onChange={(e) => set({ city: e.target.value })} /></label>
              <label className="wide"><span>Address</span><input className="d-inp" value={setup.address} onChange={(e) => set({ address: e.target.value })} /></label>
              <label className="wide"><span>Management mode</span><select className="d-inp" value={setup.managerMode} onChange={(e) => set({ managerMode: e.target.value })}><option>Council only</option><option>Council + property manager</option><option>Property manager led</option></select></label>
            </div>
          </section>

          <section>
            <div className="d-secl">Routing and resident safety</div>
            <div className="set-card set-form single">
              <label><span>Routing email</span><input className="d-inp" type="email" value={setup.routingEmail} onChange={(e) => set({ routingEmail: e.target.value })} /></label>
              <label><span>Emergency contact</span><input className="d-inp" value={setup.emergencyContact} onChange={(e) => set({ emergencyContact: e.target.value })} /></label>
              <div className="set-note"><Icon name="lock" size={16} /> Residents should see emergency instructions before submitting normal maintenance requests.</div>
            </div>
          </section>
        </div>

        <div className="settings-grid">
          <section>
            <div className="d-secl">Council and manager access</div>
            <div className="set-card">
              {members.map((m, i) =>
              <div className="set-member editable" key={m.email + i}>
                <span className="av">{String(m.name || 'M').split(' ').map((w) => w[0]).join('').slice(0, 2)}</span>
                <div className="set-member-fields">
                  <input className="d-inp" value={m.name} aria-label="Member name" onChange={(e) => updateMember(i, { name: e.target.value })} />
                  <input className="d-inp" type="email" value={m.email} aria-label="Member email" onChange={(e) => updateMember(i, { email: e.target.value })} />
                </div>
                <select className="d-inp role" value={m.role} aria-label="Member role" onChange={(e) => updateMember(i, { role: e.target.value })}>
                  <option>Council chair</option>
                  <option>Council member</option>
                  <option>Property manager</option>
                  <option>Building operator</option>
                </select>
              </div>
              )}
              <button className="set-add" onClick={addMember}><Icon name="plus" size={15} /> Add member</button>
            </div>
          </section>

          <section>
            <div className="d-secl">Email notifications</div>
            <div className="set-card">
              <button className="set-toggle" aria-pressed={!!notif.newReport} onClick={() => setNotif('newReport', !notif.newReport)}>
                <div className="flex1"><div className="mnm">New reports</div><div className="mem">Email the routing contact when a resident submits an issue.</div></div>
                <span className={'sw' + (notif.newReport ? ' on' : '')}><span className="kn" /></span>
              </button>
              <button className="set-toggle" aria-pressed={!!notif.overdue} onClick={() => setNotif('overdue', !notif.overdue)}>
                <div className="flex1"><div className="mnm">Overdue reminders</div><div className="mem">Nudge when an open issue passes 14 days without resolution.</div></div>
                <span className={'sw' + (notif.overdue ? ' on' : '')}><span className="kn" /></span>
              </button>
              <button className="set-toggle" aria-pressed={!!notif.weekly} onClick={() => setNotif('weekly', !notif.weekly)}>
                <div className="flex1"><div className="mnm">Weekly summary</div><div className="mem">Send a Monday digest of open, waiting, and resolved issues.</div></div>
                <span className={'sw' + (notif.weekly ? ' on' : '')}><span className="kn" /></span>
              </button>
            </div>
          </section>
        </div>

        <div className="settings-grid">
          <section>
            <div className="d-secl">Auth and login testing</div>
            <div className="set-card set-form single">
              <label><span>Current auth mode</span><select className="d-inp" value={auth.mode || 'mock'} onChange={(e) => setAuth({ mode: e.target.value })}><option value="mock">Mock local session</option><option value="supabase">Supabase Auth</option><option value="clerk">Clerk</option><option value="custom">Custom backend</option></select></label>
              <label><span>Pilot auth provider</span><input className="d-inp" value={auth.pilotProvider || ''} onChange={(e) => setAuth({ pilotProvider: e.target.value })} placeholder="Supabase Auth" /></label>
              <label><span>Test council email</span><input className="d-inp" type="email" value={auth.testEmail || ''} onChange={(e) => setAuth({ testEmail: e.target.value })} /></label>
              <div className="set-token-row">
                <button className="set-toggle compact" aria-pressed={!!auth.magicLink} onClick={() => setAuth({ magicLink: !auth.magicLink })}><div className="flex1"><div className="mnm">Magic links</div><div className="mem">Council can test email sign-in.</div></div><span className={'sw' + (auth.magicLink ? ' on' : '')}><span className="kn" /></span></button>
                <button className="set-toggle compact" aria-pressed={!!auth.googleOAuth} onClick={() => setAuth({ googleOAuth: !auth.googleOAuth })}><div className="flex1"><div className="mnm">Google OAuth</div><div className="mem">Enable after OAuth app setup.</div></div><span className={'sw' + (auth.googleOAuth ? ' on' : '')}><span className="kn" /></span></button>
                <button className="set-toggle compact" aria-pressed={!!auth.microsoftOAuth} onClick={() => setAuth({ microsoftOAuth: !auth.microsoftOAuth })}><div className="flex1"><div className="mnm">Microsoft OAuth</div><div className="mem">Enable for property managers.</div></div><span className={'sw' + (auth.microsoftOAuth ? ' on' : '')}><span className="kn" /></span></button>
              </div>
            </div>
          </section>

          <section>
            <div className="d-secl">Database and media testing</div>
            <div className="set-card set-form single">
              <label><span>Current database mode</span><select className="d-inp" value={database.mode || 'localStorage'} onChange={(e) => setDatabase({ mode: e.target.value })}><option value="localStorage">Browser localStorage</option><option value="supabase">Supabase Postgres</option><option value="postgres">Postgres API</option><option value="firebase">Firebase</option></select></label>
              <label><span>Pilot database provider</span><input className="d-inp" value={database.pilotProvider || ''} onChange={(e) => setDatabase({ pilotProvider: e.target.value })} placeholder="Supabase Postgres" /></label>
              <label><span>Project URL</span><input className="d-inp" value={database.projectUrl || ''} onChange={(e) => setDatabase({ projectUrl: e.target.value })} placeholder="https://project.supabase.co" /></label>
              <label><span>Schema status</span><select className="d-inp" value={database.schemaStatus || 'Not migrated'} onChange={(e) => setDatabase({ schemaStatus: e.target.value })}><option>Not migrated</option><option>Schema drafted</option><option>Migrations applied</option><option>Seeded for pilot</option></select></label>
              <label><span>Media bucket</span><input className="d-inp" value={database.storageBucket || ''} onChange={(e) => setDatabase({ storageBucket: e.target.value })} /></label>
            </div>
          </section>
        </div>

        <section>
          <div className="d-secl">Stripe pilot billing</div>
          <div className="set-card billing-card">
            <div className="billing-summary">
              <span className="billing-ic"><Icon name="files" size={18} /></span>
              <div><b>{billing.planName || 'Founding pilot'}</b><span>{billing.trialDays || 60} day trial · Stripe {billing.mode || 'test'} mode</span>{billing.status && <span>Subscription {billing.status}{billing.currentPeriodEndsAt ? ' · period ends ' + new Date(billing.currentPeriodEndsAt).toLocaleDateString() : ''}{billing.cancelAtPeriodEnd ? ' · cancellation scheduled' : ''}</span>}</div>
              <span className={'pilot-ready-badge' + (billing.checkoutConfigured ? ' ready' : '')}>{billing.checkoutConfigured ? 'Checkout configured' : 'Needs checkout'}</span>
            </div>
            <div className="set-form billing-form">
              <label><span>Stripe mode</span><select className="d-inp" value={billing.mode || 'test'} onChange={(e) => setBilling({ mode: e.target.value })}><option value="test">Test</option><option value="live">Live</option></select></label>
              <label><span>Plan name</span><input className="d-inp" value={billing.planName || ''} onChange={(e) => setBilling({ planName: e.target.value })} /></label>
              <label><span>Trial days</span><input className="d-inp" type="number" min="0" value={billing.trialDays || 0} onChange={(e) => setBilling({ trialDays: Number(e.target.value || 0) })} /></label>
              <label><span>Publishable key</span><input className="d-inp" value={billing.publishableKey || ''} onChange={(e) => setBilling({ publishableKey: e.target.value })} placeholder="pk_test_..." /></label>
              <label><span>Price ID</span><input className="d-inp" value={billing.priceId || ''} onChange={(e) => setBilling({ priceId: e.target.value })} placeholder="price_..." /></label>
              <button className="set-toggle compact" aria-pressed={!!billing.webhookSecretSet} onClick={() => setBilling({ webhookSecretSet: !billing.webhookSecretSet })}><div className="flex1"><div className="mnm">Webhook secret stored</div><div className="mem">Required for subscription events.</div></div><span className={'sw' + (billing.webhookSecretSet ? ' on' : '')}><span className="kn" /></span></button>
              <button className="set-toggle compact" aria-pressed={!!billing.checkoutConfigured} onClick={() => setBilling({ checkoutConfigured: !billing.checkoutConfigured })}><div className="flex1"><div className="mnm">Checkout route configured</div><div className="mem">Server endpoint creates Stripe checkout sessions.</div></div><span className={'sw' + (billing.checkoutConfigured ? ' on' : '')}><span className="kn" /></span></button>
              <button className="d-btn acc full" type="button" onClick={createCheckout}><Icon name="send" size={16} /> Create Stripe test checkout</button>
            </div>
          </div>
        </section>

        <section>
          <div className="d-secl">Connected setup data</div>
          <div className="set-card set-connected">
            <div><b>{setup.locations.filter((l) => l.active).length}</b><span>active QR locations</span></div>
            <div><b>{setup.locations.filter((l) => l.active && l.tested).length}</b><span>tested QR locations</span></div>
            <div><b>{setup.activated ? 'Ready' : 'Not ready'}</b><span>launch status</span></div>
            <button className="d-btn out" onClick={resetSetup}><Icon name="refresh" size={16} /> Reset setup demo data</button>
          </div>
        </section>

        <div className="poweredby" style={{ textAlign: 'left', marginTop: 18, fontSize: 12, color: 'var(--ink-400)' }}>Powered by <b style={{ color: 'var(--ink-600)' }}>Strata Match</b> · StrataSnap prototype</div>
      </div>
    </div>);

}

// ── modals (two-lane composer + state machine) ─────────────────
function Composers({ modal, ticket, onClose, addEvents, patch, setToast, pilotApi, refreshFromApi }) {
  const SS = window.SS;
  if (!ticket) return null;
  const close = onClose;
  const syncApiEvent = (event, status) => {
    if (!pilotApi) return;
    pilotApi.addEvent(ticket.id, { ...event, status }).then(() => refreshFromApi && refreshFromApi()).catch((err) => setToast('Saved locally; API event failed: ' + err.message));
  };

  if (modal.type === 'update') return <UpdateModal ticket={ticket} onClose={close}
  onPost={(body) => {addEvents(ticket.id, [{ id: SS.uid('e'), at: SS.NOW.toISOString(), kind: 'update', visibility: 'reporter-visible', body, author: 'Dana Lee' }]);syncApiEvent({ kind: 'update', visibility: 'reporter-visible', body });close();setToast('Update posted · reporter notified (mock)');}} />;

  if (modal.type === 'note') return <NoteModal onClose={close}
  onSave={(body) => {addEvents(ticket.id, [{ id: SS.uid('e'), at: SS.NOW.toISOString(), kind: 'note', visibility: 'private', body, author: 'Dana Lee' }]);syncApiEvent({ kind: 'note', visibility: 'private', body });close();setToast('Private note saved · council only');}} />;

  if (modal.type === 'status') return <StatusModal ticket={ticket} onClose={close}
  onApply={(tr, body) => {
    const label = (s) => SS.statusActionLabel ? SS.statusActionLabel(s) : ((SS.STATUS[s] || {}).short || s);
    const evs = [];
    if (body && tr.lane === 'reporter-visible') evs.push({ id: SS.uid('e'), at: SS.NOW.toISOString(), kind: 'update', visibility: 'reporter-visible', body, author: 'Dana Lee' });else
    if (body) evs.push({ id: SS.uid('e'), at: SS.NOW.toISOString(), kind: 'note', visibility: 'private', body, author: 'Dana Lee' });
    evs.push({ id: SS.uid('e'), at: SS.NOW.toISOString(), kind: 'status-change', body: label(ticket.status) + ' → ' + label(tr.to) });
    syncApiEvent({ kind: body && tr.lane === 'reporter-visible' ? 'update' : 'note', visibility: tr.lane || 'private', body: body || label(ticket.status) + ' → ' + label(tr.to) }, tr.to);
    addEvents(ticket.id, evs);patch(ticket.id, (t) => ({ ...t, status: tr.to }));close();setToast('Status changed to ' + label(tr.to));
  }} />;

  if (modal.type === 'assignee') return <PickModal title="Assigned to" options={[...PEOPLE, 'Unassigned']} current={ticket.assignee || 'Unassigned'} onClose={close}
  onPick={(v) => {patch(ticket.id, (t) => ({ ...t, assignee: v === 'Unassigned' ? null : v }));close();setToast('Assigned to ' + v);}} />;

  if (modal.type === 'responsibility') return <PickModal title="Who owns this issue?" subtitle="Internal record only. Use Not common property when the report should not become strata repair work." options={SS.RESPONSIBILITY} current={ticket.responsibility} onClose={close}
  onPick={(v) => {patch(ticket.id, (t) => ({ ...t, responsibility: v }));close();setToast('Handling decision saved');}} />;

  if (modal.type === 'vendor') return <VendorDispatchModal ticket={ticket} onClose={close}
  onDispatch={(draft) => {
    const linkToken = 'vendor-' + ticket.id.toLowerCase() + '-' + SS.uid('v').slice(-5);
    const body = [
      'Vendor dispatch drafted for ' + draft.vendorName + ' <' + draft.vendorEmail + '>.',
      'Scope: ' + draft.scope,
      draft.accessNote ? 'Access note: ' + draft.accessNote : '',
      'Magic link: /vendor/' + linkToken,
    ].filter(Boolean).join('\n');
    addEvents(ticket.id, [{ id: SS.uid('e'), at: SS.NOW.toISOString(), kind: 'vendor-dispatch', visibility: 'private', body, author: 'Dana Lee' }]);
    patch(ticket.id, (t) => ({ ...t, assignee: draft.vendorName, vendorName: draft.vendorName, vendorEmail: draft.vendorEmail, vendorLinkToken: linkToken, nextStep: 'Vendor dispatch sent' }));
    syncApiEvent({ kind: 'vendor-dispatch', visibility: 'private', body, vendorName: draft.vendorName, vendorEmail: draft.vendorEmail, vendorLinkToken: linkToken });
    close();setToast('Vendor dispatch link drafted');
  }} />;
  return null;
}

function Modal({ title, onClose, children, footer }) {
  return (
    <div className="d-modal-bg" onClick={onClose}>
      <div className="d-modal" onClick={(e) => e.stopPropagation()}>
        <div className="mh"><h3>{title}</h3><button className="x" onClick={onClose}><Icon name="x" size={18} /></button></div>
        <div className="mb">{children}</div>
        {footer && <div className="mf">{footer}</div>}
      </div>
    </div>);

}

function UpdateModal({ ticket, onClose, onPost }) {
  const [body, setBody] = useState('');
  const [stage, setStage] = useState('compose');
  return (
    <Modal title={stage === 'compose' ? 'Send reporter update' : 'Preview reporter update'} onClose={onClose}
    footer={stage === 'compose' ?
    <button className="d-btn acc" disabled={!body.trim()} onClick={() => setStage('preview')}>Preview <Icon name="chevronR" size={15} /></button> :
    <React.Fragment><button className="d-btn out" onClick={() => setStage('compose')}>Edit</button><button className="d-btn acc" onClick={() => onPost(body.trim())}>Post to reporter</button></React.Fragment>}>
      <div className="d-banner pub"><span className="ic"><Icon name="eye" size={17} /></span><div><div className="tt">Visible to the reporter</div><div className="ds">Posts to their status page and sends an update notice.</div></div></div>
      {stage === 'compose' ?
      <textarea className="d-ta" autoFocus value={body} onChange={(e) => setBody(e.target.value)} placeholder="e.g. We've booked a plumber for Friday morning." /> :
      <div><div style={{ fontSize: 13, color: 'var(--ink-500)', marginBottom: 8 }}>This is exactly what the reporter sees:</div>
            <div className="d-preview"><div className="l"><Icon name="eye" size={12} /> Update from council</div><div style={{ fontSize: 15, lineHeight: 1.5, color: 'var(--ink-900)' }}>{body}</div><div style={{ fontSize: 12, color: 'var(--ink-400)', marginTop: 8 }}>just now · Dana Lee</div></div></div>}
    </Modal>);

}

function NoteModal({ onClose, onSave }) {
  const [body, setBody] = useState('');
  return (
    <Modal title="Private council note" onClose={onClose}
    footer={<button className="d-btn dark" disabled={!body.trim()} onClick={() => onSave(body.trim())}>Save note</button>}>
      <div className="d-banner priv"><span className="ic"><Icon name="lock" size={17} /></span><div><div className="tt">Council and manager only</div><div className="ds">Saved to the record, but not shown on the reporter status page.</div></div></div>
      <textarea className="d-ta" autoFocus value={body} onChange={(e) => setBody(e.target.value)} placeholder="e.g. Check if the unit above had a renovation that caused this." />
    </Modal>);

}

function StatusModal({ ticket, onClose, onApply }) {
  const SS = window.SS;
  const [tr, setTr] = useState(null);
  const [body, setBody] = useState('');
  const transitions = SS.TRANSITIONS[ticket.status] || [];
  const label = (s) => SS.statusActionLabel ? SS.statusActionLabel(s) : ((SS.STATUS[s] || {}).short || s);
  if (!tr) {
    return (
      <Modal title="Change status" onClose={onClose}>
        <div style={{ fontSize: 14, color: 'var(--ink-500)', marginBottom: 14 }}>Currently <StatusDot status={ticket.status} />. Choose what happens next.</div>
        {transitions.map((t) =>
        <button className="d-opt" key={t.to} onClick={() => t.requires ? setTr(t) : onApply(t, '')}>
            <span className="dot" style={{ width: 11, height: 11, borderRadius: 9, background: 'var(--ink-300)', flex: '0 0 auto' }} />
            <span className="flex1"><span className="nm">{label(t.to)}</span><span className="ds" style={{ display: 'block' }}>{t.requires || 'No extra details needed'}</span></span>
            <Icon name="chevronR" size={15} style={{ color: 'var(--ink-300)' }} />
          </button>
        )}
      </Modal>);

  }
  const reporterLane = tr.lane === 'reporter-visible';
  return (
    <Modal title={'Move to ' + label(tr.to)} onClose={onClose}
    footer={<React.Fragment><button className="d-btn out" onClick={() => setTr(null)}>Back</button><button className="d-btn acc" disabled={!body.trim()} onClick={() => onApply(tr, body.trim())}>Confirm · {label(tr.to)}</button></React.Fragment>}>
      <div className={'d-banner ' + (reporterLane ? 'pub' : 'priv')}><span className="ic"><Icon name={reporterLane ? 'eye' : 'lock'} size={17} /></span><div><div className="tt">{reporterLane ? 'Visible to the reporter' : 'Council and manager only'}</div><div className="ds">{reporterLane ? 'Added to their status page' : 'Saved internally, not sent to the reporter'}</div></div></div>
      <label className="lbl-sm" style={{ marginTop: 0 }}>{tr.requires}</label>
      <textarea className="d-ta" autoFocus value={body} onChange={(e) => setBody(e.target.value)} placeholder="Add the required detail…" />
    </Modal>);

}

function PickModal({ title, subtitle, options, current, onClose, onPick }) {
  return (
    <Modal title={title} onClose={onClose}>
      {subtitle && <div style={{ fontSize: 13, color: 'var(--ink-500)', marginBottom: 12 }}>{subtitle}</div>}
      {options.map((o) =>
      <button className="d-opt" key={o} onClick={() => onPick(o)} style={o === current ? { borderColor: 'var(--acc)', background: 'var(--acc-tint)' } : null}>
          <span className="flex1"><span className="nm">{o}</span></span>
          {o === current && <Icon name="check" size={17} style={{ color: 'var(--acc)' }} />}
        </button>
      )}
    </Modal>);

}

function VendorDispatchModal({ ticket, onClose, onDispatch }) {
  const [draft, setDraft] = useState({
    vendorName: ticket.vendorName || ticket.assignee || 'Preferred plumber',
    vendorEmail: ticket.vendorEmail || '',
    scope: 'Review ' + ticket.summary + ' at ' + ticket.locationName + ' and reply with quote, schedule, or completion note.',
    accessNote: '',
  });
  const set = (patch) => setDraft((d) => ({ ...d, ...patch }));
  const validEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(draft.vendorEmail || '').trim());
  const canDispatch = draft.vendorName.trim() && validEmail && draft.scope.trim();
  return (
    <Modal title="Dispatch vendor" onClose={onClose}
    footer={<React.Fragment><button className="d-btn out" onClick={onClose}>Cancel</button><button className="d-btn acc" disabled={!canDispatch} onClick={() => onDispatch({ ...draft, vendorName: draft.vendorName.trim(), vendorEmail: draft.vendorEmail.trim(), scope: draft.scope.trim(), accessNote: draft.accessNote.trim() })}>Create dispatch link</button></React.Fragment>}>
      <div className="d-banner priv"><span className="ic"><Icon name="send" size={17} /></span><div><div className="tt">Vendor sees assigned work only</div><div className="ds">Creates a mock email and magic link for this issue. Reporter contact and council notes stay private.</div></div></div>
      <div className="set-form" style={{ marginTop: 14 }}>
        <label><span>Vendor name</span><input className="d-inp" value={draft.vendorName} onChange={(e) => set({ vendorName: e.target.value })} /></label>
        <label><span>Vendor email</span><input className="d-inp" type="email" aria-invalid={!validEmail && !!draft.vendorEmail} value={draft.vendorEmail} onChange={(e) => set({ vendorEmail: e.target.value })} placeholder="vendor@example.com" /></label>
        <label className="wide"><span>Scope of work</span><textarea className="d-ta" value={draft.scope} onChange={(e) => set({ scope: e.target.value })} /></label>
        <label className="wide"><span>Access note · optional</span><textarea className="d-ta" value={draft.accessNote} onChange={(e) => set({ accessNote: e.target.value })} placeholder="e.g. Ask concierge for mechanical-room key." /></label>
      </div>
    </Modal>);

}

function NewTicketModal({ onClose, onCreate }) {
  const SS = window.SS;
  const [d, setD] = useState({ source: 'phone', locationId: 'lobby', category: null, summary: '', name: '', email: '', phone: '', unit: '' });
  const set = (p) => setD((x) => ({ ...x, ...p }));
  const sources = ['email', 'phone', 'text', 'in person', 'council observation'];
  const loc = SS.LOCATIONS.find((l) => l.id === d.locationId);
  const canCreate = d.category && d.summary.trim();
  return (
    <Modal title="Add an issue" onClose={onClose}
    footer={<React.Fragment><button className="d-btn out" onClick={onClose}>Cancel</button><button className="d-btn acc" disabled={!canCreate} onClick={() => onCreate({ ...d, locationName: loc.name })}>Create issue</button></React.Fragment>}>
      <div style={{ fontSize: 13.5, color: 'var(--ink-500)', marginBottom: 16, lineHeight: 1.5 }}>For issues that came in by phone, email, or that you spotted yourself.</div>
      <label className="lbl-sm" style={{ marginTop: 0 }}>How did it come in?</label>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
        {sources.map((s) =>
        <button key={s} onClick={() => set({ source: s })} className="chip"
        style={d.source === s ? { background: 'var(--acc)', color: '#fff', borderColor: 'var(--acc)', textTransform: 'capitalize', cursor: 'pointer' } : { textTransform: 'capitalize', cursor: 'pointer' }}>{s}</button>
        )}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginTop: 4 }}>
        <div><label className="lbl-sm">Location</label>
          <select className="d-inp" value={d.locationId} onChange={(e) => set({ locationId: e.target.value })}>
            {SS.LOCATIONS.map((l) => <option key={l.id} value={l.id}>{l.name}</option>)}
          </select>
        </div>
        <div><label className="lbl-sm">Category</label>
          <select className="d-inp" value={d.category || ''} onChange={(e) => set({ category: e.target.value || null })}>
            <option value="">Choose…</option>
            {SS.CATEGORIES.map((c) => <option key={c.id} value={c.id}>{c.label}</option>)}
          </select>
        </div>
      </div>
      <label className="lbl-sm">Summary</label>
      <input className="d-inp" placeholder="Short description of the issue" value={d.summary} onChange={(e) => set({ summary: e.target.value })} />
      <div className="d-secl" style={{ margin: '20px 0 10px' }}>Reporter · optional</div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
        <input className="d-inp" placeholder="Name" value={d.name} onChange={(e) => set({ name: e.target.value })} />
        <input className="d-inp" placeholder="Unit" value={d.unit} onChange={(e) => set({ unit: e.target.value })} />
        <input className="d-inp" placeholder="Email" value={d.email} onChange={(e) => set({ email: e.target.value })} />
        <input className="d-inp" placeholder="Phone" value={d.phone} onChange={(e) => set({ phone: e.target.value })} />
      </div>
    </Modal>);

}

ReactDOM.createRoot(document.getElementById('root')).render(<DApp />);
