// UAE CTL 2025 — Article Peek
// A slide-in panel that displays any article from the gazetted text on demand.
// Components: ArticlePeekProvider, useArticlePeek, ArticleRef, linkifyArticles,
// and the panel itself which renders once at the root.

(function () {
  const { createContext, useContext, useState, useEffect, useCallback, useMemo, useRef } = React;

  // ── format raw article body into structured items ────────────────────────
  // Each line in the source PDF wraps at ~75 chars. We coalesce wrapped lines
  // within the same numbered point into a single block of text.
  function formatArticle(text) {
    if (!text) return [];
    const lines = text.split('\n');
    const items = [];
    let cur = null;
    for (const raw of lines) {
      const t = raw.trim();
      if (!t) { cur = null; continue; }
      // numbered top-level item: "1." or "12."
      let m = t.match(/^(\d+)\.\s+(.*)$/);
      if (m) {
        cur = { level: 1, marker: m[1] + '.', text: m[2] };
        items.push(cur);
        continue;
      }
      // lettered sub-item: "a." or "b."
      m = t.match(/^([a-z])\.\s+(.*)$/);
      if (m) {
        cur = { level: 2, marker: m[1] + '.', text: m[2] };
        items.push(cur);
        continue;
      }
      // continuation of current block
      if (cur) {
        cur.text += ' ' + t;
      } else {
        cur = { level: 0, marker: '', text: t };
        items.push(cur);
      }
    }
    return items;
  }

  // ── corpus helpers ───────────────────────────────────────────────────────
  // Two corpora live on window: CTL_ARTICLES (2025) and CTL_ARTICLES_1985.
  function corpusMap(corpus) {
    return corpus === '1985' ? window.CTL_ARTICLES_1985 : window.CTL_ARTICLES;
  }
  function corpusMax(corpus) {
    const m = corpusMap(corpus);
    if (!m) return 0;
    let max = 0;
    for (const k in m) { const n = parseInt(k, 10); if (n > max) max = n; }
    return max;
  }
  function corpusLabel(corpus) {
    return corpus === '1985'
      ? { instrument: 'Federal Law No. (5) of 1985', name: 'Civil Transactions Law (repealed)' }
      : { instrument: 'Federal Decree-Law No. 25 of 2025', name: 'Civil Transactions Law' };
  }

  // ── pair-map ─────────────────────────────────────────────────────────────
  // Auto-built from data.js: each reform card lists a set of 2025 articles
  // (card.articles) and an explanatory `before:` paragraph that names the
  // 1985 articles being recast. Every 2025↔1985 pair that co-occurs in a
  // single card is recorded both ways. Used to seed the compare column with
  // the doctrinal counterpart instead of the same number.
  let _pairMap = null;
  function pairMap() {
    if (_pairMap) return _pairMap;
    const map = { '2025': {}, '1985': {} };
    if (!window.CTL_DATA || !window.CTL_DATA.clusters) { _pairMap = map; return map; }
    const re = /\bArt(?:icles?|\.)\s+(\d+)/g;
    for (const cluster of window.CTL_DATA.clusters) {
      for (const card of (cluster.cards || [])) {
        const arts2025 = (card.articles || [])
          .map(n => parseInt(n, 10))
          .filter(n => n && window.CTL_ARTICLES && (n in window.CTL_ARTICLES));
        const arts1985 = [];
        const beforeText = card.before || '';
        let m;
        while ((m = re.exec(beforeText)) !== null) {
          const n = parseInt(m[1], 10);
          if (window.CTL_ARTICLES_1985 && (n in window.CTL_ARTICLES_1985)) arts1985.push(n);
        }
        for (const a of arts2025) {
          for (const b of arts1985) {
            (map['2025'][a] = map['2025'][a] || []).includes(b) || map['2025'][a].push(b);
            (map['1985'][b] = map['1985'][b] || []).includes(a) || map['1985'][b].push(a);
          }
        }
      }
    }
    _pairMap = map;
    return map;
  }

  // For a given (num, corpus), return the best counterpart article number
  // in the OTHER corpus, or null if no curated pair exists.
  function counterpartOf(num, corpus) {
    const pm = pairMap();
    const side = corpus === '1985' ? '1985' : '2025';
    const arr = pm[side][num];
    return (arr && arr.length) ? arr[0] : null;
  }

  // ── context ──────────────────────────────────────────────────────────────
  const PeekCtx = createContext({ open: () => {} });
  window.useArticlePeek = () => useContext(PeekCtx);

  function ArticlePeekProvider({ children }) {
    const [active, setActive] = useState(null); // { num, corpus }
    const [history, setHistory] = useState([]); // breadcrumb back-stack of {num,corpus}
    // compare = { num, corpus } showing the OTHER corpus alongside `active`.
    // null = single-column mode. Both columns navigable independently.
    const [compare, setCompare] = useState(null);

    const open = useCallback((num, corpus) => {
      const n = parseInt(num, 10);
      const c = corpus === '1985' ? '1985' : '2025';
      const map = corpusMap(c);
      if (!map || !(n in map)) return;
      setActive(prev => {
        if (prev && (prev.num !== n || prev.corpus !== c)) {
          setHistory(h => [...h, prev]);
        }
        return { num: n, corpus: c };
      });
    }, []);

    const close = useCallback(() => {
      setActive(null);
      setCompare(null);
      setHistory([]);
    }, []);

    const goBack = useCallback(() => {
      setHistory(h => {
        if (h.length === 0) return h;
        const last = h[h.length - 1];
        setActive(last);
        return h.slice(0, -1);
      });
    }, []);

    const navigate = useCallback((num, corpus) => {
      const n = parseInt(num, 10);
      const c = corpus === '1985' ? '1985' : '2025';
      const map = corpusMap(c);
      if (!map || !(n in map)) return;
      setActive(prev => {
        if (prev) setHistory(h => [...h, prev]);
        return { num: n, corpus: c };
      });
    }, []);

    // Toggle the compare column. When enabling, pick the counterpart from
    // the auto-built pair-map; fall back to same-number-if-exists; else 1.
    const toggleCompare = useCallback(() => {
      setCompare(prev => {
        if (prev) return null;
        if (!active) return null;
        const otherCorpus = active.corpus === '1985' ? '2025' : '1985';
        const map = corpusMap(otherCorpus);
        if (!map) return null;
        const pair = counterpartOf(active.num, active.corpus);
        if (pair != null && pair in map) return { num: pair, corpus: otherCorpus };
        if (active.num in map) return { num: active.num, corpus: otherCorpus };
        return { num: 1, corpus: otherCorpus };
      });
    }, [active]);

    const navigateCompare = useCallback((num) => {
      setCompare(prev => {
        if (!prev) return prev;
        const n = parseInt(num, 10);
        const map = corpusMap(prev.corpus);
        if (!map || !(n in map)) return prev;
        return { ...prev, num: n };
      });
    }, []);

    // Auto-close compare if the user navigates the primary column to the
    // same corpus that compare was showing (that would be redundant).
    useEffect(() => {
      if (active && compare && active.corpus === compare.corpus) {
        setCompare(null);
      }
    }, [active, compare]);

    // keyboard: Esc to close, ←/→ to navigate within the active corpus
    useEffect(() => {
      if (!active) return;
      const max = corpusMax(active.corpus);
      const handler = (e) => {
        if (e.key === 'Escape') { close(); e.preventDefault(); }
        else if (e.key === 'ArrowLeft' && active.num > 1) { navigate(active.num - 1, active.corpus); e.preventDefault(); }
        else if (e.key === 'ArrowRight' && active.num < max) { navigate(active.num + 1, active.corpus); e.preventDefault(); }
      };
      window.addEventListener('keydown', handler);
      return () => window.removeEventListener('keydown', handler);
    }, [active, navigate, close]);

    const ctx = useMemo(() => ({
      open, close, navigate, navigateCompare, toggleCompare,
      active, activeNum: active && active.num, compare, history, goBack,
    }), [open, close, navigate, navigateCompare, toggleCompare, active, compare, history, goBack]);

    return (
      <PeekCtx.Provider value={ctx}>
        {children}
        <ArticlePeekPanel />
      </PeekCtx.Provider>
    );
  }
  window.ArticlePeekProvider = ArticlePeekProvider;

  // ── ArticleRef — clickable in-text mention ───────────────────────────────
  function ArticleRef({ number, corpus, children, className }) {
    const { open } = useContext(PeekCtx);
    const n = parseInt(number, 10);
    const c = corpus === '1985' ? '1985' : '2025';
    const map = corpusMap(c);
    const has = map && (n in map);
    if (!has) return <span className={className}>{children}</span>;
    const label = corpusLabel(c);
    return (
      <button
        type="button"
        className={"art-ref " + (c === '1985' ? "art-ref-1985 " : "") + (className || "")}
        onClick={(e) => { e.stopPropagation(); open(n, c); }}
        title={"View Article (" + n + ") — " + label.instrument}
      >
        {children}
      </button>
    );
  }
  window.ArticleRef = ArticleRef;

  // ── linkifyArticles — wrap inline article mentions in text strings ──────
  // Detects: "Article 121", "Articles 405–424", "Art. 340", "Article 340(2)"
  //
  // Resolution rules for which corpus a bare "Article N" mention links to:
  //   1. If the surrounding text explicitly tags it ("of the 1985 Code", "of
  //      that Code", "old Code", "repealed Code", "new Code", "2025 Code"),
  //      that wins.
  //   2. Otherwise it falls back to `defaultCorpus` — passed by the caller
  //      so that, e.g., the card `before:` field defaults to 1985 and the
  //      card `after:`/`relevance:` fields default to 2025.
  function linkifyArticles(text, defaultCorpus) {
    if (typeof text !== 'string' || !window.CTL_ARTICLES) return text;
    const fallback = defaultCorpus === '1985' ? '1985' : '2025';
    // Matches: "Article 121", "Articles 405", "Art. 340", "Art 340" (bare),
    // optionally followed by a parenthetical sub-paragraph "(2)", "(3)(a)" etc.
    const re = /\bArt(?:icles?|\.)?\s+(\d+)(?:\([^)]+\))?/g;
    const OLD_AFTER  = /^[^.]{0,80}?(1985|old Code|Old Code|repealed Code|that Code|thereof)/;
    const OLD_BEFORE = /(1985|old Code|Old Code|repealed Code)\b[^.]{0,80}$/;
    const NEW_AFTER  = /^[^.]{0,80}?(2025|new Code|New Code)/;
    const NEW_BEFORE = /(2025|new Code|New Code)\b[^.]{0,80}$/;
    const out = [];
    let last = 0;
    let idx = 0;
    let m;
    while ((m = re.exec(text)) !== null) {
      const num = parseInt(m[1], 10);
      const after = text.slice(m.index + m[0].length, m.index + m[0].length + 80);
      const before = text.slice(Math.max(0, m.index - 80), m.index);
      let corpus = fallback;
      if (OLD_AFTER.test(after) || OLD_BEFORE.test(before)) corpus = '1985';
      else if (NEW_AFTER.test(after) || NEW_BEFORE.test(before)) corpus = '2025';
      const map = corpusMap(corpus);
      if (!map || !(num in map)) continue;
      if (m.index > last) out.push(text.slice(last, m.index));
      out.push(<ArticleRef key={'a' + idx++} number={num} corpus={corpus}>{m[0]}</ArticleRef>);
      last = m.index + m[0].length;
    }
    if (last === 0) return text;
    if (last < text.length) out.push(text.slice(last));
    return <React.Fragment>{out}</React.Fragment>;
  }
  window.linkifyArticles = linkifyArticles;

  // ── ArticlePeekPanel ────────────────────────────────────────────────────
  function ArticlePeekPanel() {
    const { active, compare, close, navigate, navigateCompare, toggleCompare, history, goBack } = useContext(PeekCtx);
    const contentRef = useRef(null);
    const isOpen = active != null;
    const isCompare = isOpen && compare != null;

    // After the panel re-renders with a new article, scroll the primary
    // article into view at the top of the content area.
    useEffect(() => {
      if (!isOpen || !contentRef.current) return;
      const primaryEl = contentRef.current.querySelector('.peek-art.primary');
      if (primaryEl) {
        contentRef.current.scrollTop = primaryEl.offsetTop - 8;
      } else {
        contentRef.current.scrollTop = 0;
      }
    }, [active, isOpen]);

    return (
      <>
        <div
          className={"peek-scrim" + (isOpen ? " open" : "")}
          onClick={close}
          aria-hidden={!isOpen}
        ></div>
        <aside
          className={
            "peek-panel"
            + (isOpen ? " open" : "")
            + (isOpen && active.corpus === '1985' && !isCompare ? " peek-panel-1985" : "")
            + (isCompare ? " peek-panel-compare" : "")
          }
          role="dialog"
          aria-modal="true"
          aria-label={isOpen ? ("Article " + active.num) : ""}
        >
          {isOpen && (isCompare ? (
            <ArticleCompareBody
              left={active}
              right={compare}
              navigateLeft={navigate}
              navigateRight={navigateCompare}
              close={close}
              toggleCompare={toggleCompare}
              contentRef={contentRef}
            />
          ) : (
            <ArticlePeekBody
              num={active.num}
              corpus={active.corpus}
              navigate={navigate}
              close={close}
              history={history}
              goBack={goBack}
              toggleCompare={toggleCompare}
              contentRef={contentRef}
            />
          ))}
        </aside>
      </>
    );
  }

  // ── ArticleNumberPicker — typeable article jump ─────────────────────────
  // Renders an input that accepts an article number. Commits on Enter or
  // blur; ignores values outside the corpus. Used in both the single-view
  // header and each compare column header.
  function ArticleNumberPicker({ num, corpus, onPick, size }) {
    const [draft, setDraft] = useState(String(num));
    const max = corpusMax(corpus);
    useEffect(() => { setDraft(String(num)); }, [num, corpus]);
    function commit() {
      const n = parseInt(draft, 10);
      if (!n || n < 1 || n > max) { setDraft(String(num)); return; }
      const map = corpusMap(corpus);
      if (!map || !(n in map)) { setDraft(String(num)); return; }
      if (n !== num) onPick(n);
    }
    return (
      <span className={"art-picker" + (size ? " art-picker-" + size : "")}>
        <span className="art-picker-lbl">Art.</span>
        <input
          type="text"
          inputMode="numeric"
          pattern="[0-9]*"
          value={draft}
          onChange={(e) => setDraft(e.target.value.replace(/[^0-9]/g, ''))}
          onBlur={commit}
          onKeyDown={(e) => {
            if (e.key === 'Enter') { commit(); e.currentTarget.blur(); }
            else if (e.key === 'Escape') { setDraft(String(num)); e.currentTarget.blur(); }
          }}
          onFocus={(e) => e.currentTarget.select()}
          aria-label={"Jump to article in " + corpus + " Code"}
          title={"1 – " + max}
        />
      </span>
    );
  }

  function ArticlePeekBody({ num, corpus, navigate, close, history, goBack, toggleCompare, contentRef }) {
    const headings = (corpus === '2025' && window.CTL_HEADINGS_BEFORE && window.CTL_HEADINGS_BEFORE[num]) || [];
    const label = corpusLabel(corpus);
    const max = corpusMax(corpus);
    const otherLabel = corpus === '1985' ? '2025' : '1985';
    return (
      <>
        <header className="peek-head">
          <div className="peek-head-top">
            <div className="peek-eyebrow">
              <span>{label.instrument}</span>
              <span>{label.name}</span>
            </div>
            <div className="peek-head-actions">
              <button
                className="peek-compare-btn"
                onClick={toggleCompare}
                aria-label={"Compare with " + otherLabel + " Code"}
                title={"Show alongside " + otherLabel + " Code"}
              >
                <span className="arrow">↔</span>
                <span className="lbl">Compare with {otherLabel}</span>
              </button>
              <button className="peek-close" onClick={close} aria-label="Close article peek">×</button>
            </div>
          </div>
          <div className="peek-nav">
            <button
              className="peek-nav-btn"
              onClick={() => navigate(num - 1, corpus)}
              disabled={num <= 1}
              aria-label="Previous article"
            >
              <span className="arrow">←</span>
              <span className="lbl">Art. {num - 1}</span>
            </button>
            {history.length > 0 && (
              <button className="peek-nav-btn peek-back" onClick={goBack} aria-label="Back">
                <span className="arrow">↶</span>
                <span className="lbl">Back</span>
              </button>
            )}
            <ArticleNumberPicker
              num={num}
              corpus={corpus}
              onPick={(n) => navigate(n, corpus)}
            />
            <button
              className="peek-nav-btn"
              onClick={() => navigate(num + 1, corpus)}
              disabled={num >= max}
              aria-label="Next article"
            >
              <span className="lbl">Art. {num + 1}</span>
              <span className="arrow">→</span>
            </button>
          </div>
        </header>

        <div className="peek-content" ref={contentRef}>
          <ArticleBlock num={num - 1} corpus={corpus} muted onClick={() => navigate(num - 1, corpus)} />
          <ArticleBlock num={num} corpus={corpus} primary headings={headings} />
          <ArticleBlock num={num + 1} corpus={corpus} muted onClick={() => navigate(num + 1, corpus)} />
        </div>

        <footer className="peek-foot">
          <span>Use ← → to step · Esc to close</span>
        </footer>
      </>
    );
  }

  function ArticleCompareBody({ left, right, navigateLeft, navigateRight, close, toggleCompare, contentRef }) {
    return (
      <>
        <header className="peek-head peek-head-compare">
          <div className="peek-head-top">
            <div className="peek-eyebrow"><span>Side-by-side comparison</span></div>
            <div className="peek-head-actions">
              <button
                className="peek-compare-btn peek-compare-btn-off"
                onClick={toggleCompare}
                aria-label="Exit side-by-side"
                title="Exit side-by-side"
              >
                <span className="lbl">Single view</span>
              </button>
              <button className="peek-close" onClick={close} aria-label="Close article peek">×</button>
            </div>
          </div>
        </header>

        <div className="peek-content peek-content-compare" ref={contentRef}>
          <CompareColumn slot={left} navigate={navigateLeft} />
          <CompareColumn slot={right} navigate={(num) => navigateRight(num)} compareSide />
        </div>

        <footer className="peek-foot">
          <span>Each column navigates independently · Esc to close</span>
        </footer>
      </>
    );
  }

  function CompareColumn({ slot, navigate, compareSide }) {
    const { num, corpus } = slot;
    const label = corpusLabel(corpus);
    const max = corpusMax(corpus);
    return (
      <section className={"compare-col compare-col-" + corpus}>
        <header className="compare-col-head">
          <div className="compare-col-label">
            <span className="instrument">{label.instrument}</span>
            <span className="name">{label.name}</span>
          </div>
          <div className="compare-col-nav">
            <button
              className="peek-nav-btn"
              onClick={() => compareSide ? navigate(num - 1) : navigate(num - 1, corpus)}
              disabled={num <= 1}
              aria-label="Previous"
            ><span className="arrow">←</span></button>
            <ArticleNumberPicker
              num={num}
              corpus={corpus}
              onPick={(n) => compareSide ? navigate(n) : navigate(n, corpus)}
              size="sm"
            />
            <button
              className="peek-nav-btn"
              onClick={() => compareSide ? navigate(num + 1) : navigate(num + 1, corpus)}
              disabled={num >= max}
              aria-label="Next"
            ><span className="arrow">→</span></button>
          </div>
        </header>
        <div className="compare-col-body">
          <ArticleBlock num={num} corpus={corpus} primary />
        </div>
      </section>
    );
  }

  function ArticleBlock({ num, corpus, primary, muted, headings, onClick }) {
    const map = corpusMap(corpus);
    if (!map || !(num in map)) return null;
    const body = map[num];
    const items = formatArticle(body);
    return (
      <article
        className={"peek-art" + (primary ? " primary" : "") + (muted ? " muted" : "")}
        onClick={onClick}
      >
        {primary && headings && headings.length > 0 && (
          <div className="peek-art-lineage">
            {headings.map((h, i) => (
              <span key={i} className="lineage-item">{h}</span>
            ))}
          </div>
        )}
        <h3 className="peek-art-num">
          <span className="lbl">Article</span>
          <span className="num">({num})</span>
        </h3>
        <div className="peek-art-body">
          {items.map((it, i) => {
            if (it.level === 0) {
              return <p key={i}>{it.text}</p>;
            } else if (it.level === 1) {
              return (
                <p key={i} className="peek-li peek-li-1">
                  <span className="marker">{it.marker}</span>
                  <span className="text">{it.text}</span>
                </p>
              );
            } else {
              return (
                <p key={i} className="peek-li peek-li-2">
                  <span className="marker">{it.marker}</span>
                  <span className="text">{it.text}</span>
                </p>
              );
            }
          })}
        </div>
      </article>
    );
  }
})();
