/* ============================================================
   EMM — App shell: state, nav, header, breadcrumb, slide-over
   ============================================================ */
(function () {
  "use strict";
  const { useState, useEffect, useCallback } = React;
  const E = window.EMM, S = window.EMM_I18N, L = window.emmL;

  // ---- localStorage persistence (settings survive reload) ----
  function loadLS(key, fallback) {
    try { const v = localStorage.getItem(key); return v == null ? fallback : JSON.parse(v); } catch (e) { return fallback; }
  }
  function saveLS(key, val) { try { localStorage.setItem(key, JSON.stringify(val)); } catch (e) {} }
  const SETTINGS_KEY = "emm.settings", VIEWS_KEY = "emm.savedViews";
  const SETTINGS_DEFAULTS = {
    velocityPct: 200, windowH: 2, nssDrop: 15, reachThresh: 500000,
    channels: {}, // channel id → false means muted; missing = monitored
    dprSource: true, integrations: { barc: true, wa: true, sso: false },
  };
  // a channel is monitored unless explicitly muted (false)
  window.emmChannelOn = (id) => {
    const s = window.EMM_SETTINGS; return !s || s.channels[id] !== false;
  };

  // accent palette — shared chroma/lightness, varied hue (oklch) + rgb for heat ramps
  const ACCENTS = [
    { id: "indigo", name: "Deep Indigo", css: "oklch(0.40 0.095 264)", rgb: "27 58 107" },
    { id: "blue",   name: "Signal Blue", css: "oklch(0.40 0.095 240)", rgb: "23 64 116" },
    { id: "teal",   name: "Teal",        css: "oklch(0.40 0.080 200)", rgb: "10 76 88" },
    { id: "violet", name: "Violet Ink",  css: "oklch(0.40 0.095 300)", rgb: "62 46 112" },
  ];
  window.EMM_ACCENTS = ACCENTS;

  const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
    "theme": "light",
    "density": "comfortable",
    "accent": "indigo",
    "role": "secretary",
    "drillFull": false,
    "showGrid": true,
    "fontScale": 100
  }/*EDITMODE-END*/;

  // ---- rail icons (simple shapes only) ----------------------
  const ICON = {
    mainFeed: <svg viewBox="0 0 16 16"><rect x="1" y="1" width="6" height="6" rx="1"/><rect x="9" y="1" width="6" height="4" rx="1"/><rect x="9" y="7" width="6" height="8" rx="1"/><rect x="1" y="9" width="6" height="6" rx="1"/></svg>,
    sentiment: <svg viewBox="0 0 16 16"><circle cx="8" cy="8" r="7" fill="none" stroke="currentColor" strokeWidth="2"/><path d="M8 1 a7 7 0 0 1 0 14 z"/></svg>,
    viewership: <svg viewBox="0 0 16 16"><rect x="1" y="9" width="3" height="6" rx="1"/><rect x="6.5" y="5" width="3" height="10" rx="1"/><rect x="12" y="1" width="3" height="14" rx="1"/></svg>,
    crisis: <svg viewBox="0 0 16 16"><path d="M8 1 L15 14 L1 14 Z" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round"/><rect x="7.2" y="6" width="1.6" height="4" rx="0.8"/><circle cx="8" cy="11.6" r="0.9"/></svg>,
    reports: <svg viewBox="0 0 16 16"><path d="M3 1 h7 l3 3 v11 h-10 z" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinejoin="round"/><line x1="5" y1="7" x2="11" y2="7" stroke="currentColor" strokeWidth="1.2"/><line x1="5" y1="10" x2="11" y2="10" stroke="currentColor" strokeWidth="1.2"/></svg>,
    settings: <svg viewBox="0 0 16 16"><circle cx="8" cy="8" r="2.4" fill="none" stroke="currentColor" strokeWidth="1.4"/><path d="M8 1 v2 M8 13 v2 M1 8 h2 M13 8 h2 M3 3 l1.4 1.4 M11.6 11.6 L13 13 M13 3 l-1.4 1.4 M4.4 11.6 L3 13" stroke="currentColor" strokeWidth="1.3"/></svg>,
    compare: <svg viewBox="0 0 16 16"><rect x="1" y="2" width="6" height="12" rx="1"/><rect x="9" y="2" width="6" height="12" rx="1" opacity="0.5"/></svg>,
    mobile: <svg viewBox="0 0 16 16"><rect x="4" y="1" width="8" height="14" rx="2"/><rect x="6.5" y="12.5" width="3" height="1" rx="0.5" fill="var(--rail-bg)"/></svg>,
    spokespersons: <svg viewBox="0 0 16 16"><circle cx="8" cy="5" r="2.7" fill="none" stroke="currentColor" strokeWidth="1.5"/><path d="M2.8 14 C2.8 10.6 5.2 9.2 8 9.2 C10.8 9.2 13.2 10.6 13.2 14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>,
    adspend: <svg viewBox="0 0 16 16"><circle cx="8" cy="8" r="6.4" fill="none" stroke="currentColor" strokeWidth="1.4"/><path d="M6 4.6 h4 M6 7 h4 M9.4 4.6 c0 2.4 -3.4 2.4 -3.4 2.4 l3 3" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/></svg>,
    fakenews: <svg viewBox="0 0 16 16"><path d="M8 1 L14 3.2 V8 C14 11.8 8 15 8 15 C8 15 2 11.8 2 8 V3.2 Z" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinejoin="round"/><rect x="7.2" y="5" width="1.6" height="4" rx="0.8"/><circle cx="8" cy="11" r="0.9"/></svg>,
    mentionlog: <svg viewBox="0 0 16 16"><rect x="2" y="3" width="12" height="2" rx="0.5"/><rect x="2" y="7" width="12" height="2" rx="0.5"/><rect x="2" y="11" width="8" height="2" rx="0.5"/></svg>,
    matrix: <svg viewBox="0 0 16 16"><rect x="1.5" y="1.5" width="5" height="5" rx="1"/><rect x="9.5" y="1.5" width="5" height="5" rx="1"/><rect x="1.5" y="9.5" width="5" height="5" rx="1"/><rect x="9.5" y="9.5" width="5" height="5" rx="1"/></svg>,
  };

  const NAV_GROUPS = [
    { key: "grpOps", items: [
      { id: "mainFeed", key: "navMainFeed" },
      { id: "sentiment", key: "navSentiment" },
      { id: "spokespersons", key: "navSpokespersons" },
      { id: "viewership", key: "navViewership" },
      { id: "adspend", key: "navAdspend" },
      { id: "fakenews", key: "navFakeNews" },
      { id: "crisis", key: "navCrisis", badge: 3 },
    ] },
    { key: "grpAnalyse", items: [
      { id: "matrix", key: "navMatrix" },
      { id: "mentionlog", key: "navMentionLog" },
    ] },
    { key: "grpOutput", items: [
      { id: "reports", key: "navReports" },
    ] },
    { key: "grpSystem", items: [ { id: "settings", key: "navSettings" } ] },
  ];

  function App({ auth, onLogout }) {
    const [t, setTweak] = useTweaks({ ...TWEAK_DEFAULTS, ...loadLS("emm.tweaks", {}) });
    const [lang, setLang] = useState(() => loadLS("emm.lang", "en"));
    const lens = "count"; // L1–L5 are capability levels (see Settings → Capability framework), not a view filter; screens default to the count view
    const [time, setTime] = useState("today");
    const [view, setView] = useState({ screen: "mainFeed" });
    const [filters, setFilters] = useState(() => loadLS("emm.defaults", {}));
    const [q, setQ] = useState("");
    const [slide, setSlide] = useState(null); // {story, full}
    const [report, setReport] = useState(null); // report type id for the preview overlay
    const [toast, setToast] = useState("");     // transient confirmation message
    const [settings, setSettings] = useState(() => {
      const s = { ...SETTINGS_DEFAULTS, ...loadLS(SETTINGS_KEY, {}) };
      s.integrations = { ...SETTINGS_DEFAULTS.integrations, ...(s.integrations || {}) };
      s.channels = s.channels || {};
      window.EMM_SETTINGS = s;
      return s;
    });
    const toastTimer = React.useRef(null);
    const showToast = useCallback((msg) => {
      setToast(msg);
      if (toastTimer.current) clearTimeout(toastTimer.current);
      toastTimer.current = setTimeout(() => setToast(""), 2600);
    }, []);
    // update one settings key (or merge an object), persist, expose globally
    const setSetting = useCallback((keyOrObj, val) => {
      setSettings((prev) => {
        const next = typeof keyOrObj === "object" ? { ...prev, ...keyOrObj } : { ...prev, [keyOrObj]: val };
        window.EMM_SETTINGS = next; saveLS(SETTINGS_KEY, next); return next;
      });
    }, []);
    const resetSettings = useCallback(() => {
      const fresh = JSON.parse(JSON.stringify(SETTINGS_DEFAULTS));
      window.EMM_SETTINGS = fresh; saveLS(SETTINGS_KEY, fresh); setSettings(fresh);
    }, []);

    const tr = useCallback((k) => {
      const e = S[k]; return e ? (e[lang] || e.en) : k;
    }, [lang]);
    const ctxVal = { lang, t: tr };

    // apply tweaks to root
    useEffect(() => {
      const acc = ACCENTS.find((a) => a.id === t.accent) || ACCENTS[0];
      const r = document.documentElement;
      r.dataset.theme = t.theme;
      r.dataset.density = t.density;
      r.dataset.grid = t.showGrid ? "on" : "off";
      r.style.setProperty("--accent", acc.css);
      r.style.setProperty("--accent-rgb", acc.rgb);
      r.style.setProperty("--font-scale", (t.fontScale / 100));
      saveLS("emm.tweaks", t);
    }, [t]);
    useEffect(() => { document.documentElement.lang = lang; saveLS("emm.lang", lang); }, [lang]);
    useEffect(() => { saveLS("emm.defaults", { region: filters.region, entity: filters.entity }); }, [filters.region, filters.entity]);

    // ---- navigation -----------------------------------------
    const openStory = useCallback((story) => setSlide({ story, full: t.drillFull }), [t.drillFull]);
    const nav = {
      goMainFeed: () => { setView({ screen: "mainFeed" }); setSlide(null); },
      goSentiment: () => { setView({ screen: "sentiment" }); setSlide(null); },
      goSpokespersons: () => { setView({ screen: "spokespersons" }); setSlide(null); },
      goViewership: () => { setView({ screen: "viewership" }); setSlide(null); },
      goAdspend: () => { setView({ screen: "adspend" }); setSlide(null); },
      goFakeNews: () => { setView({ screen: "fakenews" }); setSlide(null); },
      goCrisis: () => { setView({ screen: "crisis" }); setSlide(null); },
      goReports: () => { setView({ screen: "reports" }); setSlide(null); },
      goMatrix: () => { setView({ screen: "matrix" }); setSlide(null); },
      goMentionLog: (f = {}) => { setView({ screen: "mentionlog" }); if (f && Object.keys(f).length) setFilters((x) => ({ ...x, ...f })); setSlide(null); },
      goSettings: () => { setView({ screen: "settings" }); setSlide(null); },
      goChannel: (id, opts = {}) => { setView({ screen: "channel", channelId: id }); if (opts.topic) setFilters((f) => ({ ...f, topic: opts.topic })); setSlide(null); },
      goTopic: (id, opts = {}) => { setView({ screen: "topic", topicId: id }); if (opts.channel) setFilters((f) => ({ ...f, channel: opts.channel })); setSlide(null); },
      goJournalists: () => { setView({ screen: "journalists" }); setSlide(null); },
      goCompare: () => { setView({ screen: "compare" }); setSlide(null); },
      goChannelsIndex: () => { setView({ screen: "channelsIndex" }); setSlide(null); },
      goTopicsIndex: () => { setView({ screen: "topicsIndex" }); setSlide(null); },
      goStories: (f = {}) => { setView({ screen: "stories" }); setFilters((x) => ({ ...x, ...f })); setSlide(null); },
      goMobile: () => { setView({ screen: "mobile" }); setSlide(null); },
      openStory,
      openReport: (id) => setReport(id),
      recallView: (v) => {
        setFilters(v.filters || {}); setTime(v.time || "today"); setQ(v.q || "");
        setView({ screen: v.screen || "mainFeed", channelId: v.channelId, topicId: v.topicId });
        setSlide(null);
      },
      deleteView: (idx) => { const views = loadLS(VIEWS_KEY, []); views.splice(idx, 1); saveLS(VIEWS_KEY, views); },
    };
    const navTo = (id) => ({
      mainFeed: nav.goMainFeed, sentiment: nav.goSentiment, spokespersons: nav.goSpokespersons,
      viewership: nav.goViewership, adspend: nav.goAdspend, fakenews: nav.goFakeNews,
      crisis: nav.goCrisis, reports: nav.goReports, matrix: nav.goMatrix, mentionlog: () => nav.goMentionLog(), settings: nav.goSettings,
    }[id] || nav.goMainFeed)();

    function clearFilter(k) { setFilters((f) => { const n = { ...f }; delete n[k]; return n; }); if (k === "q") setQ(""); }
    function clearAll() { setFilters({}); setQ(""); }

    // universal search → resolve a typed term to a structured scope (topic/entity/channel) or keyword
    function runSearch() {
      const r = E.resolveQuery(q);
      if (!r) return;
      setFilters((f) => {
        const n = { ...f }; delete n.q;
        if (r.kind === "topic") n.topic = r.id;
        else if (r.kind === "entity") n.entity = r.id;
        else if (r.kind === "channel") n.channel = r.id;
        else n.q = r.id;
        return n;
      });
    }
    function setRegion(v) { setFilters((f) => { const n = { ...f }; if (v) n.region = v; else delete n.region; return n; }); }
    function setDept(v) { setFilters((f) => { const n = { ...f }; if (v) n.entity = v; else delete n.entity; return n; }); }

    // Save View → persist the current screen + filters + time for later recall (Settings → Saved views)
    function saveView() {
      const views = loadLS(VIEWS_KEY, []);
      const label = tr(({ mainFeed: "navMainFeed", sentiment: "navSentiment", viewership: "navViewership",
        adspend: "navAdspend", fakenews: "navFakeNews", crisis: "navCrisis", reports: "navReports",
        matrix: "navMatrix", mentionlog: "navMentionLog", settings: "navSettings", spokespersons: "navSpokespersons" }[view.screen] || "navMainFeed"));
      views.unshift({ screen: view.screen, channelId: view.channelId, topicId: view.topicId, filters, time, q, label });
      saveLS(VIEWS_KEY, views.slice(0, 12));
      showToast(tr("toastViewSaved"));
    }
    // Export → download the currently-filtered mention facts as CSV
    function exportCurrent() {
      const facts = E.mentionFacts(filters) || [];
      const header = ["ID", "Day", "Time", "Daypart", "Channel", "Topic", "Sentiment", "Reach", "Duration(s)", "Authenticity", "Headline"];
      const rows = facts.map((m) => [m.id, m.day, m.time, m.daypart, m.channel, m.topicName, m.sentiment, m.reach, m.dur, m.authenticity, m.head]);
      window.downloadCsv("emm-mentions-" + (view.screen) + ".csv", header, rows);
      showToast(tr("toastExported"));
    }

    // close slide-over on Esc
    useEffect(() => {
      const h = (e) => { if (e.key === "Escape") { if (slide && slide.full) setSlide(null); else if (slide) setSlide(null); } };
      window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h);
    }, [slide]);

    // ---- breadcrumb -----------------------------------------
    const crumbs = buildCrumbs(view, filters, lang, tr, nav);

    const screen = view.screen;
    const role = t.role;
    const isActive = (id) => screen === id;

    return (
      <EMMCtx.Provider value={ctxVal}>
        <div className={"emm-app" + (slide && !slide.full ? " slide-open" : "")}>
          {/* ---- LEFT RAIL ---- */}
          <aside className="emm-rail">
            <div className="emm-rail-brand">
              <div className="emm-rail-mark">EMM</div>
              <div className="emm-rail-brandsub mono">DPR · CG</div>
            </div>
            <nav className="emm-rail-nav">
              {NAV_GROUPS.map((grp) => (
                <div key={grp.key} className="emm-rail-group">
                  <div className="emm-rail-grouplab mono">{tr(grp.key)}</div>
                  {grp.items.map((it) => (
                    <button key={it.id} className={"emm-rail-item" + (isActive(it.id) ? " active" : "")}
                      onClick={() => navTo(it.id)} title={tr(it.key)}>
                      <span className="emm-rail-ico">{ICON[it.id]}</span>
                      <span className="emm-rail-lab">{tr(it.key)}</span>
                      {it.badge && <span className="emm-rail-badge mono">{it.badge}</span>}
                    </button>
                  ))}
                </div>
              ))}
            </nav>
            <div className="emm-rail-foot">
              <button className={"emm-rail-item slim" + (screen === "mobile" ? " active" : "")} onClick={() => nav.goMobile()} title="Mobile cockpit">
                <span className="emm-rail-ico">{ICON.mobile}</span>
                <span className="emm-rail-lab">Mobile cockpit</span>
              </button>
              <div className="emm-rail-user">
                <span className="emm-rail-avatar mono">{authInitials(auth && auth.email)}</span>
                <div className="emm-rail-userbody">
                  <div className="emm-rail-username">{auth ? auth.email.split("@")[0] : tr("userName")}</div>
                  <div className="emm-rail-userrole mono">Skillmine · DPR-CG</div>
                </div>
                <button className="emm-rail-logout mono" title={tr("signOut")} onClick={onLogout} aria-label={tr("signOut")}>⏻</button>
              </div>
            </div>
          </aside>

          {/* ---- MAIN ---- */}
          <div className="emm-main">
            {/* top bar */}
            <header className="emm-topbar">
              <div className="emm-topbar-l">
                <div className="emm-topbar-titles">
                  <div className="emm-topbar-app">{tr("appName")}</div>
                  <div className="emm-topbar-org mono">{tr("appOrg")}</div>
                </div>
              </div>
              <div className="emm-topbar-r">
                <TimeControl time={time} onTime={setTime} />
                <div className="emm-topbar-div" />
                <button className={"emm-tool-btn mono" + (screen === "compare" ? " active" : "")} onClick={() => nav.goCompare()}>{tr("compare")}</button>
                <button className="emm-tool-btn mono ghost" onClick={saveView}>{tr("saveView")}</button>
                <button className="emm-tool-btn mono accent" onClick={exportCurrent}>{tr("export")}</button>
                <div className="emm-topbar-div" />
                <div className="emm-lang">
                  <button className={"emm-lang-btn mono" + (lang === "en" ? " active" : "")} onClick={() => setLang("en")}>EN</button>
                  <button className={"emm-lang-btn dev" + (lang === "hi" ? " active" : "")} onClick={() => setLang("hi")}>हिं</button>
                </div>
              </div>
            </header>

            {/* universal controls: search + region + department */}
            <div className="emm-controlbar">
              <UniversalControls q={q} onQ={setQ} onSubmit={runSearch}
                region={filters.region} onRegion={setRegion}
                dept={filters.entity} onDept={setDept} lang={lang} />
            </div>

            {/* sub bar: breadcrumb (L1–L5 are capability levels — documented in Settings, not a top-bar filter) */}
            <div className="emm-subbar">
              <Breadcrumb crumbs={crumbs} />
            </div>

            {/* filter chips */}
            <FilterChips filters={filters} onClear={clearFilter} onClearAll={clearAll} lang={lang} />

            {/* content */}
            <main className="emm-content">
              {screen === "mainFeed" && <CockpitScreen lens={lens} time={time} filters={filters} role={role} lang={lang} nav={nav} />}
              {screen === "sentiment" && <SentimentScreen time={time} filters={filters} lang={lang} nav={nav} />}
              {screen === "spokespersons" && <SpokespersonsScreen time={time} filters={filters} lang={lang} nav={nav} />}
              {screen === "viewership" && <ViewershipScreen time={time} filters={filters} lang={lang} nav={nav} />}
              {screen === "adspend" && <AdSpendScreen time={time} filters={filters} lang={lang} nav={nav} />}
              {screen === "fakenews" && <FakeNewsScreen lang={lang} nav={nav} />}
              {screen === "crisis" && <CrisisScreen lang={lang} nav={nav} />}
              {screen === "reports" && <ReportsScreen lang={lang} nav={nav} />}
              {screen === "matrix" && <MatrixScreen filters={filters} lang={lang} nav={nav} />}
              {screen === "mentionlog" && <MentionLogScreen filters={filters} lang={lang} nav={nav} />}
              {screen === "settings" && <SettingsScreen lang={lang} tweaks={t} setTweak={setTweak} setLang={setLang}
                accents={ACCENTS} filters={filters} setRegion={setRegion} setDept={setDept}
                settings={settings} setSetting={setSetting} resetSettings={resetSettings} showToast={showToast} nav={nav} />}
              {screen === "channel" && <ChannelScreen channelId={view.channelId} lens={lens} time={time} filters={filters} lang={lang} nav={nav} />}
              {screen === "topic" && <TopicScreen topicId={view.topicId} lens={lens} time={time} filters={filters} lang={lang} nav={nav} />}
              {screen === "channelsIndex" && <IndexScreen kind="channel" lens={lens} time={time} lang={lang} nav={nav} />}
              {screen === "topicsIndex" && <IndexScreen kind="topic" lens={lens} time={time} lang={lang} nav={nav} />}
              {screen === "stories" && <StoriesScreen filters={filters} lang={lang} lens={lens} nav={nav} />}
              {screen === "journalists" && <JournalistsScreen lang={lang} nav={nav} />}
              {screen === "compare" && <CompareScreen lens={lens} time={time} lang={lang} nav={nav} />}
              {screen === "mobile" && <MobilePreview lang={lang} nav={nav} />}
            </main>
          </div>

          {/* ---- SLIDE-OVER / FULL STORY ---- */}
          {slide && !slide.full && (
            <>
              <div className="emm-scrim" onClick={() => setSlide(null)} />
              <aside className="emm-slideover">
                <StoryDetail story={slide.story} lang={lang} full={false}
                  onFull={() => setSlide({ ...slide, full: true })} onClose={() => setSlide(null)} nav={nav} />
              </aside>
            </>
          )}
          {slide && slide.full && (
            <div className="emm-fullstory">
              <StoryDetail story={slide.story} lang={lang} full={true} onClose={() => setSlide(null)} nav={nav} />
            </div>
          )}

          {/* ---- REPORT PREVIEW ---- */}
          {report && (
            <div className="emm-report-overlay emm-report-print">
              <ReportPreview reportId={report} lang={lang} time={time} filters={filters}
                onClose={() => setReport(null)} showToast={showToast} />
            </div>
          )}

          {/* ---- TOAST ---- */}
          <Toast msg={toast} />

          {/* ---- TWEAKS ---- */}
          <TweaksPanel>
            <TweakSection label="Theme" />
            <TweakRadio label="Mode" value={t.theme} options={["light", "dark"]} onChange={(v) => setTweak("theme", v)} />
            <TweakColor label="Accent" value={accentColorFor(t.accent)}
              options={ACCENTS.map((a) => a.css)}
              onChange={(v) => { const a = ACCENTS.find((x) => x.css === v); setTweak("accent", a ? a.id : "indigo"); }} />
            <TweakToggle label="Backdrop grid" value={t.showGrid} onChange={(v) => setTweak("showGrid", v)} />
            <TweakSection label="Layout" />
            <TweakRadio label="Density" value={t.density} options={["comfortable", "compact"]} onChange={(v) => setTweak("density", v)} />
            <TweakSlider label="Font scale" value={t.fontScale} min={90} max={115} step={5} unit="%" onChange={(v) => setTweak("fontScale", v)} />
            <TweakSection label="Behaviour" />
            <TweakRadio label="View as (cockpit)" value={t.role} options={["secretary", "analyst"]} onChange={(v) => setTweak("role", v)} />
            <TweakToggle label="Open stories full-page" value={t.drillFull} onChange={(v) => setTweak("drillFull", v)} />
          </TweaksPanel>
        </div>
      </EMMCtx.Provider>
    );
  }

  function accentColorFor(id) { const a = (window.EMM_ACCENTS || []).find((x) => x.id === id); return a ? a.css : "oklch(0.40 0.095 264)"; }

  // ---- breadcrumb builder -----------------------------------
  function buildCrumbs(view, filters, lang, tr, nav) {
    const crumbs = [{ label: tr("allMedia"), onClick: () => nav.goMainFeed() }];
    const s = view.screen;
    if (s === "channel") {
      const ch = E.channel(view.channelId);
      crumbs.push({ label: ch.medium === "TV" ? tr("tv") : ch.medium === "Digital" ? tr("digital") : tr("social"), onClick: () => nav.goChannelsIndex() });
      crumbs.push({ label: L(ch, lang) || ch.name, onClick: () => nav.goChannel(view.channelId) });
      if (filters.topic) crumbs.push({ label: "[" + (L(E.topic(filters.topic), lang)) + "]" });
    } else if (s === "topic") {
      crumbs.push({ label: tr("navTopics"), onClick: () => nav.goTopicsIndex() });
      crumbs.push({ label: L(E.topic(view.topicId), lang) });
      if (filters.channel) crumbs.push({ label: "[" + (E.channel(filters.channel).name) + "]" });
    } else if (s === "mainFeed") { crumbs.push({ label: tr("navMainFeed") }); }
    else if (s === "sentiment") { crumbs.push({ label: tr("navSentiment") }); }
    else if (s === "spokespersons") { crumbs.push({ label: tr("navSpokespersons") }); }
    else if (s === "viewership") { crumbs.push({ label: tr("navViewership") }); }
    else if (s === "adspend") { crumbs.push({ label: tr("navAdspend") }); }
    else if (s === "fakenews") { crumbs.push({ label: tr("navFakeNews") }); }
    else if (s === "crisis") { crumbs.push({ label: tr("navCrisis") }); }
    else if (s === "reports") { crumbs.push({ label: tr("navReports") }); }
    else if (s === "matrix") { crumbs.push({ label: tr("navMatrix") }); }
    else if (s === "mentionlog") { crumbs.push({ label: tr("navMentionLog") }); }
    else if (s === "settings") { crumbs.push({ label: tr("navSettings") }); }
    else if (s === "channelsIndex") { crumbs.push({ label: tr("navChannels") }); }
    else if (s === "topicsIndex") { crumbs.push({ label: tr("navTopics") }); }
    else if (s === "journalists") { crumbs.push({ label: tr("navJournalists") }); }
    else if (s === "compare") { crumbs.push({ label: tr("compare") }); }
    else if (s === "stories") { crumbs.push({ label: tr("navStories") }); }
    else if (s === "mobile") { crumbs.push({ label: "Mobile cockpit" }); }
    return crumbs;
  }

  // ---- index screens (channels / topics list) ---------------
  function IndexScreen({ kind, lens, time, lang, nav }) {
    const { t } = window.useEmmT();
    const days = E.daysFor(time);
    const board = kind === "channel" ? E.channelBoard(days) : E.topicBoard(days);
    return (
      <div className="emm-screen index">
        <div className="emm-screenhead">
          <div>
            <h1 className="emm-screenhead-title">{kind === "channel" ? t("navChannels") : t("navTopics")}</h1>
            <p className="emm-screenhead-sub mono">{board.length} {kind === "channel" ? "channels" : "topics"} · click any to drill</p>
          </div>
        </div>
        <Panel>
          <BarsH items={board.filter((x) => x.count > 0)} rank sentiment valueFn={(x) => E.metric(x, lens)}
            labelFn={(x) => kind === "channel"
              ? <span className="emm-board-label"><ChannelLogo ch={x} size={22} />{L(x, lang) || x.name}</span>
              : L(x, lang)}
            subFn={(x) => kind === "channel" ? `${x.tier} · ${C2(x.reach)} ${t("reach")}` : `${C2(x.reach)} ${t("reach")}`}
            onClick={(x) => kind === "channel" ? nav.goChannel(x.id) : nav.goTopic(x.id)} />
        </Panel>
      </div>
    );
  }
  const C2 = (v) => E.fmt.compact(v);

  // ---- stories screen ---------------------------------------
  function StoriesScreen({ filters, lang, lens, nav }) {
    const { t } = window.useEmmT();
    const rows = E.stories(filters);
    return (
      <div className="emm-screen stories">
        <div className="emm-screenhead">
          <div>
            <h1 className="emm-screenhead-title">{t("navStories")}</h1>
            <p className="emm-screenhead-sub mono">{rows.length} {t("items")}</p>
          </div>
        </div>
        <Panel pad={false}>
          <StoryList rows={rows} onRow={nav.openStory} lang={lang} lens={lens} />
        </Panel>
      </div>
    );
  }

  // ---- mobile preview (device frame) ------------------------
  function MobilePreview({ lang, nav }) {
    const { t } = window.useEmmT();
    return (
      <div className="emm-screen mobilewrap">
        <div className="emm-mobile-stage">
          <div className="emm-mobile-side">
            <h1 className="emm-screenhead-title">Mobile cockpit</h1>
            <p className="emm-screenhead-sub mono">Field / district officer — the day's key items & alerts on a phone. One key mobile screen per scope.</p>
            <ul className="emm-mobile-notes mono">
              <li>Glanceable KPI stack</li>
              <li>Sentiment meter with %</li>
              <li>Needs-attention alerts</li>
              <li>Channel leaderboard</li>
              <li>Bottom tab navigation</li>
            </ul>
          </div>
          <MobileCockpit lang={lang} nav={nav} />
        </div>
      </div>
    );
  }

  // ============================================================
  //  AUTH GATE  (client-side demo gate — not a server boundary)
  //  Anyone with an @skill-mine.com email + the shared access
  //  password may enter. NOTE: this is a static, no-backend app,
  //  so this gate keeps casual visitors out but is NOT real
  //  security — for true access control put Cloudflare Access in
  //  front of the Worker.
  // ============================================================
  const AUTH_KEY = "emm.auth";
  const ALLOWED_DOMAIN = "@skill-mine.com";
  const ACCESS_PW = "Skill@Win";
  function currentAuth() { const a = loadLS(AUTH_KEY, null); return a && a.email ? a : null; }
  function authInitials(email) {
    if (!email) return "··";
    const local = email.split("@")[0].replace(/[^a-z0-9]/gi, "");
    return (local.slice(0, 2) || "··").toUpperCase();
  }

  function LoginGate({ onAuth }) {
    const [email, setEmail] = useState("");
    const [pw, setPw] = useState("");
    const [err, setErr] = useState("");
    function submit(e) {
      e.preventDefault();
      const em = email.trim().toLowerCase();
      if (!em || em.indexOf("@") < 0) { setErr("Enter your work email."); return; }
      if (!em.endsWith(ALLOWED_DOMAIN)) { setErr("Access is limited to " + ALLOWED_DOMAIN + " accounts."); return; }
      if (pw !== ACCESS_PW) { setErr("Incorrect password."); return; }
      const auth = { email: em, ts: Date.now() };
      saveLS(AUTH_KEY, auth);
      onAuth(auth);
    }
    return (
      <div className="emm-login">
        <div className="emm-login-card">
          <div className="emm-login-brand">
            <div className="emm-login-mark">EMM</div>
            <div className="emm-login-titles">
              <div className="emm-login-org">Electronic Media Monitoring</div>
              <div className="emm-login-sub mono">Department of Public Relations · Chhattisgarh</div>
            </div>
          </div>
          <form className="emm-login-form" onSubmit={submit}>
            <label className="emm-login-field">
              <span className="emm-login-lab mono">Work email</span>
              <input type="email" autoFocus autoComplete="username" value={email}
                onChange={(e) => { setEmail(e.target.value); setErr(""); }}
                placeholder="name@skill-mine.com" className="emm-login-input mono" />
            </label>
            <label className="emm-login-field">
              <span className="emm-login-lab mono">Password</span>
              <input type="password" autoComplete="current-password" value={pw}
                onChange={(e) => { setPw(e.target.value); setErr(""); }}
                placeholder="••••••••" className="emm-login-input mono" />
            </label>
            {err && <div className="emm-login-err mono">{err}</div>}
            <button type="submit" className="emm-login-btn mono">Sign in →</button>
          </form>
          <div className="emm-login-note mono">Authorised Skillmine personnel only · {ALLOWED_DOMAIN}</div>
        </div>
        <div className="emm-login-foot mono">Skillmine Technology Consulting</div>
      </div>
    );
  }

  function Root() {
    const [auth, setAuth] = useState(currentAuth);
    if (!auth) return <LoginGate onAuth={setAuth} />;
    return <App auth={auth} onLogout={() => { try { localStorage.removeItem(AUTH_KEY); } catch (e) {} setAuth(null); }} />;
  }

  // mount
  const root = ReactDOM.createRoot(document.getElementById("root"));
  root.render(<Root />);
})();
