// CoverageMap — the v2 hero.
// Two layouts: 'rail' (book-rail scroll, default) and 'mosaic' (dense canvas).
// Pin color = max depth anywhere: empty / c (light accent) / b (deep accent).
// Seal, LUMO, Amos indicators layered on top. Clock icon for in-progress.

function CoverageMap({ onOpenPericope, onCommission, onOpenBiblica, onOpenVaultStudy, onOpenBook, onOpenCollection }) {
  const I = window.useI18n();
  const { BOOKS, PERICOPES } = window.PERICOPE_DATA;
  // touch/narrow screens default to the Book-rail (comfortable rows); the
  // dense mosaic stays one tap away and remains the desktop default
  const [layout, setLayout] = React.useState(() =>
    (typeof window !== 'undefined' && window.innerWidth <= 820) ? 'rail' : 'mosaic');
  const [showPretero, setShowPretero] = React.useState(true);
  const [pop, setPop] = React.useState(null);
  // Local target-language for the coverage lens — a CONTENT axis, fully
  // independent of the UI language (changing it must NOT change UI language).
  // audit rank 16: seed to the fullest-coverage view ('en') instead of the UI
  // language, so a Català/other-UI visitor doesn't land on a near-empty map and
  // conclude the library is empty. They can switch the lens to any language.
  const [coverLang, setCoverLang] = React.useState('en');
  const [langMenuOpen, setLangMenuOpen] = React.useState(false);

  const coverLangObj = I.LANGUAGES.find(l => l.code === coverLang) || I.LANGUAGES[0];

  // Close the lang menu on outside click
  const headRef = React.useRef(null);
  React.useEffect(() => {
    if (!langMenuOpen) return;
    const h = (e) => {
      if (headRef.current && !headRef.current.contains(e.target)) setLangMenuOpen(false);
    };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [langMenuOpen]);

  const books = BOOKS.filter(b => showPretero ? true : !b.pretero);

  // ── REAL HOLDINGS (review API) ────────────────────────────────
  // When amos-review-api is alive, the map stops showing the demo's procedurally
  // seeded coverage and renders the ACTUAL vault holdings: every real pericope
  // analysis becomes a Mode-B cell; books with no analyses show an all-empty
  // skeleton. Demo densities/seals are never mixed with real data.
  const [realByBook, setRealByBook] = React.useState(null);
  const [realTotal, setRealTotal] = React.useState(0);
  const [realCov, setRealCov] = React.useState(null); // verse-level coverage per book abbr
  React.useEffect(() => {
    if (!window.AmosAPI) return;
    let on = true;
    const load = () => window.AmosAPI.studies().then(list => {
      if (!on || !Array.isArray(list) || !list.length) return;
      const m = {};
      list.forEach(s => {
        const bid = String(s.abbr || '').toLowerCase();
        if (!bid) return;
        (m[bid] = m[bid] || []).push({
          id: s.id,
          book: bid,
          idx: (m[bid] || []).length + 1,
          ref: (s.abbr || '').toUpperCase() + ' ' + s.chapter + ':' + s.v1 +
               (s.v2 && s.v2 !== s.v1 ? '–' + s.v2 : ''),
          chap: s.chapter, v1: s.v1, v2: s.v2,
          title: s.pericope_title || '',
          state: 'b',            // what these are: Mode B analytical apparatus (ai-draft)
          inprog: false, seals: [], lumo: false, pub: false,
          real: true
        });
      });
      Object.keys(m).forEach(k => {
        m[k].sort((a, b) => (a.chap - b.chap) || (a.v1 - b.v1));
        m[k].forEach((p, i) => { p.idx = i + 1; });
      });
      setRealByBook(m); setRealTotal(list.length);
      if (window.AmosAPI.coverage) {
        window.AmosAPI.coverage().then(cov => { if (on) setRealCov(cov); }).catch(() => {});
      }
    }).catch(() => {});
    if (window.AmosAPI.isAlive()) load();
    else window.AmosAPI.onStatus(a => { if (a && on) load(); });
    return () => { on = false; };
  }, []);

  // ── TRANSLATION AVAILABLE (real vault text, no analysis yet) ──────────────
  // A book with no analytical studies still has a real vault translation when
  // either a source edition exists (masoretic/LXX/Vulgate/Peshitta/Targum) or
  // the library holds the work (pseudepigrapha). Those books render in the
  // "translation available" colour instead of empty gray.
  const [availBooks, setAvailBooks] = React.useState(null);
  React.useEffect(() => {
    if (!window.AmosAPI) return;
    let on = true;
    const norm = (t) => String(t || '').toLowerCase().split(/[·—]/)[0]
      .replace(/\([^)]*\)/g, ' ').replace(/[:.,'’"\-]/g, ' ')
      .replace(/\b(the book of|book of|the|trans|translation|text|ge ez|ethiopic|greek|syriac|english|r h charles|wlc|lxx|thgnt)\b/g, ' ')
      .replace(/\s+/g, ' ').trim();
    // Only OT + preterocanonical books get the "translation available" colour;
    // NT gaps stay gray (their empty cells open the Biblica courtesy preview,
    // and the Greek source is not a reader translation).
    const byName = {};
    BOOKS.forEach(b => { if (b.testament !== 'NT') byName[norm(b.name)] = b.id; });
    const load = async () => {
      const set = new Set();
      try {
        const books = await window.AmosAPI.books();     // source editions (real vault)
        (books || []).forEach(sb => {
          const m = /—\s*(.+?)\s*(\(|$)/.exec(sb.title || '');
          const id = byName[norm(m ? m[1] : sb.title)];
          if (id) set.add(id);
        });
      } catch (e) {}
      if (window.AmosAPI.libraryIndex) {
        try {
          const idx = await window.AmosAPI.libraryIndex();
          (idx || []).filter(x => x.kind === 'work').forEach(w => {
            const id = byName[norm(w.title)];
            if (id) set.add(id);
          });
        } catch (e) {}
      }
      if (on) setAvailBooks(set);
    };
    if (window.AmosAPI.isAlive()) load();
    else window.AmosAPI.onStatus(a => { if (a && on) load(); });
    return () => { on = false; };
  }, []);

  // Group pericopes by book — real holdings when live, demo skeleton otherwise.
  // In real mode, books without analyses keep the demo segmentation but forced
  // fully empty (honest gray), so the canon shape stays legible.
  const byBook = React.useMemo(() => {
    const demo = {};
    PERICOPES.forEach(p => { (demo[p.book] = demo[p.book] || []).push(p); });
    if (!realByBook) return demo;
    const m = {};
    Object.keys(demo).forEach(bid => {
      if (realByBook[bid] && realByBook[bid].length) {
        const cells = realByBook[bid].slice();
        // UNWRITTEN pericopes (verse-level gaps from the corpus) become honest
        // gray cells — so Acts reads as 5 teal + 26 gray, not a full 5/5 bar.
        const cov = realCov && realCov[bid];
        // an analysed OT/PT book (e.g. Isaiah) still has a vault translation
        // for its un-analysed verses → those gaps read "translation available"
        const gapState = (availBooks && availBooks.has(bid)) ? 'c' : 'empty';
        if (cov && cov.gaps && cov.gaps.length) {
          cov.gaps.forEach((g, gi) => {
            cells.push({
              id: bid + '-gap-' + gi, book: bid, idx: 1000 + gi,
              ref: bid.toUpperCase() + ' ' + g.label,
              chap: g.ch, v1: g.v1, v2: g.v2,
              state: gapState, gap: true, verses: g.verses,
              inprog: false, seals: [], lumo: false, pub: false
            });
          });
        }
        m[bid] = cells;
      } else {
        // No analysis: a real vault translation → "translation available" (c),
        // otherwise honest gray.
        const avail = availBooks && availBooks.has(bid);
        m[bid] = demo[bid].map(p => ({ ...p, state: avail ? 'c' : 'empty', seals: [], inprog: false }));
      }
    });
    return m;
  }, [realByBook, realCov, availBooks]);

  // Project a pericope's raw state through the coverLang lens.
  // English is the reference: most work happens there first, so EN sees
  // the full state. Other gateway languages step down by tier.
  const LANG_TIERS = {
    en:1.00, es:0.72, pt:0.58, fr:0.55, de:0.48, zh:0.38,
    ar:0.34, hi:0.32, ru:0.30, sw:0.22, bn:0.18, ca:0.14, am:0.10
  };
  const viewState = React.useCallback((p) => {
    if (coverLang === 'en') return p.state;
    // Real-holdings mode: the vault analyses are English-first, so a non-English
    // lens honestly shows them as not-yet-available — no procedural downgrade.
    if (realByBook) return 'empty';
    const tier = LANG_TIERS[coverLang] != null ? LANG_TIERS[coverLang] : 0.10;
    // deterministic hash per (pericope × lang) so toggling is stable
    let h = 2166136261;
    const key = p.id + '|' + coverLang;
    for (let i=0;i<key.length;i++){ h ^= key.charCodeAt(i); h = Math.imul(h, 16777619); }
    const r = (h >>> 0) / 0xffffffff;
    // Degradation ladder: sealed → b → c → empty
    if (p.state === 'empty') return 'empty';
    if (r > tier) return 'empty';
    if (p.state === 'sealed') return r < tier * 0.5 ? 'sealed' : (r < tier * 0.85 ? 'b' : 'c');
    if (p.state === 'b')      return r < tier * 0.65 ? 'b' : 'c';
    return 'c';
  }, [coverLang, realByBook]);

  // Proxy pin object with projected state
  const project = (p) => ({ ...p, state: viewState(p) });

  // Group books by OT/NT/PT for headers
  const sections = [
    { id:'ot', label:'Old Testament · Hebrew Scriptures' },
    { id:'nt', label:'New Testament · Greek Scriptures' },
    { id:'pt', label:'Preterocanonical overlay' },
  ];

  const showPop = (p, ev) => {
    const r = ev.currentTarget.getBoundingClientRect();
    const x = Math.min(Math.max(r.left + r.width/2 - 140, 12), window.innerWidth - 292);
    const y = r.bottom + 8 > window.innerHeight - 240 ? r.top - 180 : r.bottom + 8;
    setPop({ p, x, y });
  };
  const closePop = () => setPop(null);

  // Plain-language state label, reused for the hover title AND the
  // accessible name so screen-reader users get the state conveyed by colour.
  const stateLabel = (st) =>
    st === 'sealed' ? 'agency-reviewed'
    : st === 'b'    ? 'analytical apparatus'
    : st === 'c'    ? 'translation available'
    :                 'no work yet — opens a reference preview';

  const Pin = ({ p, featured }) => {
    const cls = [
      'peri',
      p.state === 'sealed' ? 'state-sealed' :
      p.state === 'b'      ? 'state-b'      :
      p.state === 'c'      ? 'state-c'      : '',
      featured ? 'featured' : '',
    ].filter(Boolean).join(' ');
    const label = p.ref + ' — ' + stateLabel(p.state);
    // audit rank 3: real <button> so the primary browse-to-study path is
    // keyboard-operable and announced (was a bare <div onClick>, invisible to AT).
    return React.createElement('button', {
      type: 'button',
      className: cls,
      title: label,
      'aria-label': label,
      // Direct open — skip the intermediate popup. Empty cells go to the
      // unified StudyView with the niv-only demo state; populated cells go
      // with their state-mapped demo. Both routes land in the same drawer.
      onClick: (e) => {
        e.stopPropagation();
        // REAL cells (live vault holdings) open the actual analysis directly —
        // the reviewer lands on the study itself, not a demo drawer.
        if (p.real && onOpenVaultStudy) { onOpenVaultStudy(p.id); return; }
        // Carry the heat-map's selected coverage language onto every click so
        // the drawer's empty-state copy reads "No translation in <Lang> yet"
        // and any future per-language behavior has the code at hand.
        const enriched = Object.assign({}, p, { lang: coverLang });
        if (p.state === 'empty') {
          onOpenBiblica && onOpenBiblica(enriched, coverLang);
        } else {
          onOpenPericope && onOpenPericope(enriched);
        }
      },
    });
  };

  // A book title's link destination, by testament:
  //   NT  → its own book hub (real per-book page; needs the API/hub data)
  //   PT  → the parabiblical collection (where these works are shelved + read)
  //   OT  → the Hebrew Bible collection
  // Returns null (plain, non-link title) when no real destination exists.
  const bookDest = (b) => {
    if (b.testament === 'NT' && onOpenBook && realCov)
      return { title: 'Open the ' + b.name + ' book page', go: () => onOpenBook(b.id, b.name) };
    if (onOpenCollection) {
      const key = b.pretero ? 'parabiblical' : (b.testament === 'OT' ? 'hebrew' : null);
      if (key) return { title: 'Open ' + b.name + ' in the library', go: () => onOpenCollection(key) };
    }
    return null;
  };
  const LINK_STYLE = { cursor: 'pointer', textDecoration: 'underline',
    textDecorationColor: 'rgba(0,0,0,.22)', textUnderlineOffset: '3px' };

  // Rail layout: one row per book with pericope cells
  const RailRow = ({ b }) => {
    const ps = (byBook[b.id] || []).map(project);
    const dest = bookDest(b);
    return React.createElement('div', { className: 'cov-rail-row' },
      React.createElement('div', { className: 'crr-book' },
        React.createElement('span', {
          style: dest ? LINK_STYLE : undefined,
          title: dest ? dest.title : undefined,
          onClick: dest ? dest.go : undefined
        }, b.name)
      ),
      React.createElement('div', { className: 'crr-cells' },
        ps.map(p => React.createElement(Pin, {
          key: p.id, p, featured: p.id === 'rom-8-28-30'
        }))
      )
    );
  };

  // Group books by section
  const otBooks = books.filter(b => b.testament === 'OT');
  const ntBooks = books.filter(b => b.testament === 'NT');
  const ptBooks = books.filter(b => b.testament === 'PT');

  return React.createElement('section', { className: 'amos-cov', onClick: closePop },
    React.createElement('div', { className: 'cov-head', ref: headRef },
      React.createElement('div', null,
        React.createElement('div', { className: 'ch-kicker' }, 'Library coverage'),
        React.createElement('h3', null,
          'What\u2019s already translated, analysed or yet to commission in ',
          React.createElement('button', {
            className: 'cov-lang-btn',
            onClick: (e) => { e.stopPropagation(); setLangMenuOpen(o => !o); },
            title: 'Change coverage language — does not change UI language'
          },
            React.createElement('span', { className: 'cl-name' }, coverLangObj.native),
            React.createElement('span', { className: 'cl-chev' }, window.I.chevronDown(14))
          )
        ),
        React.createElement('div', { className: 'ch-sub' },
          'Click any pericope to see its state. Empty cells open a ',
          React.createElement('em', null, 'Biblica courtesy preview'),
          ' so the passage still reads in ', coverLangObj.native,
          ' while you decide what to commission.'
        ),
        // (The old "Coverage lens" and "Live holdings" explainer pills were
        // production-facing notes — removed from the reader-facing header.
        // The lens↔UI-language independence remains documented in the language
        // menu's own hint below; live-vs-demo status shows in the sidebar dot.)
        langMenuOpen && React.createElement('div', { className: 'cov-lang-menu', onClick: (e) => e.stopPropagation() },
          React.createElement('div', { className: 'clm-hint' }, 'Coverage language'),
          React.createElement('div', { className: 'clm-grid' },
            I.GATEWAY.map(l => React.createElement('button', {
              key: l.code,
              className: 'clm-item ' + (coverLang === l.code ? 'on' : ''),
              onClick: () => { setCoverLang(l.code); setLangMenuOpen(false); }
            },
              React.createElement('span', { className: 'c' }, l.code),
              React.createElement('span', { className: 'n' }, l.native)
            ))
          )
        )
      ),
      React.createElement('div', { className: 'ch-ctrl' },
        React.createElement('div', { className: 'ch-seg' },
          React.createElement('button', {
            className: layout === 'rail' ? 'on' : '',
            onClick: () => setLayout('rail')
          }, window.I.list(13), 'Book-rail'),
          React.createElement('button', {
            className: layout === 'mosaic' ? 'on' : '',
            onClick: () => setLayout('mosaic')
          }, window.I.grid(13), 'Canon mosaic')
        ),
        React.createElement('button', {
          className: 'ch-check ' + (showPretero ? 'on' : ''),
          onClick: () => setShowPretero(!showPretero)
        },
          showPretero ? '✓' : '+',
          ' Preterocanonical'
        )
      )
    ),

    React.createElement('div', { className: 'cov-legend' },
      React.createElement('span', { className: 'lg' },
        React.createElement('span', { className: 'sw' }), 'No work yet'
      ),
      React.createElement('span', { className: 'lg c' },
        React.createElement('span', { className: 'sw' }), 'Translation'
      ),
      React.createElement('span', { className: 'lg b' },
        React.createElement('span', { className: 'sw' }), 'Analytical apparatus'
      ),
      React.createElement('span', { className: 'lg sealed' },
        React.createElement('span', { className: 'sw' }), 'Agency-reviewed'
      )
    ),

    layout === 'rail' && React.createElement('div', { className: 'cov-rail' },
      React.createElement('div', { className: 'cov-rail-group' }, 'Old Testament · Hebrew Scriptures'),
      otBooks.map(b => React.createElement(RailRow, { key: b.id, b })),
      React.createElement('div', { className: 'cov-rail-group' }, 'New Testament · Greek Scriptures'),
      ntBooks.map(b => React.createElement(RailRow, { key: b.id, b })),
      showPretero && React.createElement('div', { className: 'cov-rail-group' }, 'Preterocanonical overlay'),
      showPretero && ptBooks.map(b => React.createElement(RailRow, { key: b.id, b }))
    ),

    layout === 'mosaic' && (() => {
      const renderBook = (b, opts = {}) => {
        const ps = (byBook[b.id] || []).map(project);
        const done = ps.filter(p => p.state === 'sealed' || p.state === 'b').length;
        const missing = ps.filter(p => p.state === '-').length;
        const bookStatus =
          done === ps.length && ps.length > 0 ? 'has' :
          missing === ps.length              ? 'missing' : 'partial';
        const dest = bookDest(b);
        return React.createElement('div', { key: b.id, className: 'cov-mosaic-book status-' + bookStatus },
          React.createElement('div', { className: 'cmb-head' },
            React.createElement('span', {
              className: 'name',
              style: dest ? LINK_STYLE : undefined,
              title: dest ? dest.title : undefined,
              onClick: dest ? dest.go : undefined
            }, b.name),
            React.createElement('span', { className: 'count' },
              React.createElement('span', { className: 'book-dot ' + bookStatus, title: bookStatus }),
              React.createElement('b', null, done), ' / ', ps.length,
              // real mode: the honest verse-coverage share, so a partial book
              // (Acts 5 studies) can't masquerade as complete
              (realCov && realCov[b.id] && realCov[b.id].pct < 100) &&
                React.createElement('span', {
                  className: 'cov-pct',
                  title: realCov[b.id].missing + ' verses have no study yet'
                }, ' · ' + realCov[b.id].pct + '% of verses')
            )
          ),
          React.createElement('div', { className: 'cells' },
            ps.map(p => React.createElement(Pin, {
              key: p.id, p, featured: opts.showFeatured && p.id === 'rom-8-28-30'
            }))
          ),
          bookStatus === 'missing' && React.createElement('button', {
            className: 'cmb-commission-book',
            onClick: (e) => {
              e.stopPropagation();
              onCommission && onCommission({ id: b.id + '-all', ref: b.name + ' (whole book)', book: b.id, whole: true });
            },
            title: 'Commission the whole book'
          }, '+ Commission book')
        );
      };
      return React.createElement('div', { className: 'cov-mosaic' },
        React.createElement('div', { className: 'cov-mosaic-sec' }, 'Old Testament'),
        otBooks.map(b => renderBook(b)),
        React.createElement('div', { className: 'cov-mosaic-sec' }, 'New Testament'),
        ntBooks.map(b => renderBook(b, { showFeatured: true })),
        showPretero && React.createElement('div', { className: 'cov-mosaic-sec' }, 'Preterocanonical overlay'),
        showPretero && ptBooks.map(b => renderBook(b))
      );
    })(),

    // Hover / click popover
    pop && React.createElement('div', {
      className: 'peri-pop',
      style: { left: pop.x, top: pop.y },
      onClick: (e) => e.stopPropagation()
    },
      React.createElement('div', { className: 'pp-head' },
        React.createElement('div', { className: 'pp-ref' }, pop.p.ref),
        pop.p.lumo && React.createElement('span', {
          className: 'pp-media-chip',
          title: 'LUMO video segment available'
        },
          React.createElement('svg', { viewBox:'0 0 24 24', width:11, height:11, fill:'none', stroke:'currentColor', strokeWidth:2 },
            React.createElement('polygon', { points:'5 3 19 12 5 21', fill:'currentColor', stroke:'none' })
          ),
          'Video'
        )
      ),
      React.createElement('div', { className: 'pp-state ' + pop.p.state },
        pop.p.state === 'sealed' ? React.createElement('span', null, React.createElement('b', null, 'Agency-reviewed'), ' · analytical apparatus + seal') :
        pop.p.state === 'b'      ? React.createElement('span', null, React.createElement('b', null, 'Analytical apparatus'), ' · Mode B complete') :
        pop.p.state === 'c'      ? React.createElement('span', null, React.createElement('b', null, 'Translation'), ' · Mode C text available')
                                 : React.createElement('span', null, React.createElement('b', null, 'No work yet'), ' · nothing commissioned')
      ),
      pop.p.seals && pop.p.seals.length > 0 && React.createElement('div', { className: 'pp-seals' },
        pop.p.seals.map(s => React.createElement(window.SealBadge, { key: s, code: s, sm: true }))
      ),
      React.createElement('div', { className: 'pp-actions' },
        pop.p.state !== 'empty' && React.createElement('button', {
          className: 'btn btn-primary btn-sm',
          onClick: () => { closePop(); onOpenPericope && onOpenPericope(pop.p); }
        }, window.I.open(13), 'Open'),
        pop.p.state === 'empty' && React.createElement('button', {
          className: 'btn btn-primary btn-sm',
          onClick: () => { closePop(); onOpenBiblica && onOpenBiblica(pop.p, coverLang); }
        }, window.I.open(13), 'Read preview'),
        React.createElement('button', {
          className: 'btn btn-outline btn-sm',
          onClick: () => { closePop(); onCommission && onCommission(pop.p); }
        }, 'Commission')
      )
    )
  );
}
Object.assign(window, { CoverageMap });
