/*! trentpower.fr · authored source */
/*
  trentpower.fr
  citation overlay

  Role:
  renders the page-record overlay opened by the footer cite button.
  peer of the homepage view project modal; same .modal-overlay /
  .modal css family, same focus-trap, escape and click-outside
  lifecycle exposed by app.js as window.TP_OVERLAY.

  Source:
  edited here as cite.template.js; compiled to cite.js by
  generate_site.py, which substitutes the edition literal so the
  on-screen citation text matches identity_canonical.json.

  Data:
  window.TP_VERIFICATION_MAP, built by generate_verification_map.py,
  is the single record source shared with /verify/. loaded as a
  sibling script on every active html so the overlay carries a full
  record on every page.

  Constraints:
  - no fetch; csp connect-src 'none' is preserved
  - no inline event handlers; csp script-src 'self' plus nonces holds
  - no tracking, no third-party scripts
*/

(function () {
  'use strict';

  var EDITION = '2026-05-02';

  function getLang() {
    var t = document.documentElement.lang || 'en';
    if (window.I18N && window.I18N[t]) return t;
    var nav = (navigator.languages && navigator.languages[0]) || navigator.language || 'en';
    return /^fr\b/i.test(nav) ? 'fr' : 'en';
  }
  function tt(key, fallback) {
    var lang = getLang();
    var ref = (window.I18N && window.I18N[lang]) || {};
    var parts = key.split('.');
    for (var i = 0; i < parts.length; i++) {
      if (ref == null || typeof ref !== 'object') return fallback || '';
      ref = ref[parts[i]];
    }
    return (typeof ref === 'string') ? ref : (fallback || '');
  }

  // ─── path normalisation: same shape as /verify/'s normalisepath ────────
  function normalisePath(raw) {
    if (!raw) return '/';
    raw = String(raw).trim();
    if (!raw) return '/';
    if (raw.charAt(0) !== '/') raw = '/' + raw;
    raw = raw.replace(/\/index\.html$/, '/');
    if (raw.indexOf('.') === -1 && raw.charAt(raw.length - 1) !== '/') raw += '/';
    return raw;
  }

  function currentRecord() {
    var map = (typeof window !== 'undefined' && window.TP_VERIFICATION_MAP) || {};
    var p = normalisePath(location.pathname || '/');
    return map[p] || null;
  }

  // ─── citation fallback (when route is not in the verification map) ─────
  function pageTitle() {
    var raw = document.title || '';
    if (raw.indexOf('|') !== -1) {
      return raw.replace(/^[^|]+\|\s*/, '').trim().replace(/\s*&\s*/g, ' and ');
    }
    return raw.replace(/\s*[—\-]\s*Trent Power$/, '').trim();
  }
  function canonicalUrl() {
    var link = document.querySelector('link[rel="canonical"]');
    return link ? link.href : location.href;
  }
  function fallbackCitation() {
    return 'Trent Power. "' + pageTitle() + '." ' +
           tt('cite.site_label', 'Personal Site') +
           '. Paris, France. ' +
           tt('cite.edition_label', 'Edition') + ' ' + EDITION + '. ' +
           canonicalUrl();
  }
  function verifyUrl() {
    var p = normalisePath(location.pathname || '/');
    return '/verify/?path=' + encodeURIComponent(p);
  }

  // ─── print label , page-aware. trust / utility / error pages print as
  // a "sheet" (one-page a4 layout). verify is a utility sheet too. the
  // homepage is the executive profile. anything else gets a generic
  // "print page" wording. one label per page; no responsive twin.
  // print action label · home prints the editorial profile sheet
  // ("print profile"); every other page prints the standard page
  // record ("print page"). data-print-label on <body> can override
  // the default when a page wants its own wording.
  function printLabel() {
    var explicit = document.body && document.body.dataset && document.body.dataset.printLabel;
    if (explicit) return explicit;
    var page = (document.body && document.body.dataset && document.body.dataset.page)
            || (document.documentElement.dataset && document.documentElement.dataset.page)
            || '';
    if (page === 'home') return tt('cite.overlay.action.print_home', 'Print profile');
    return tt('cite.overlay.action.print_page', 'Print page');
  }

  // ─── dom helpers ───────────────────────────────────────────────────────
  function el(tag, attrs, text) {
    var n = document.createElement(tag);
    if (attrs) for (var k in attrs) {
      if (Object.prototype.hasOwnProperty.call(attrs, k) && attrs[k] != null) {
        n.setAttribute(k, attrs[k]);
      }
    }
    if (text != null) n.textContent = text;
    return n;
  }

  function safeHref(href) {
    if (!href) return '#';
    if (href.charAt(0) === '/' || /^https?:\/\//.test(href)) return href;
    return '#';
  }

  // ─── build overlay dom lazily ──────────────────────────────────────────
  var overlay = null;
  var copyBtnAction = {};   // action name → button element

  // ─── overlay shape: a quiet record card ────────────────────────────────
  // four elements, top-down: close × · header (kicker + title + lede) ·
  // metadata <dl> (edition + signed release) · actions stack (copy
  // citation · verify · view source · print). nothing else. the visual
  // weight target is "printed colophon", not "control surface" — no
  // status chips, no hash row, no related-records nav, no technical-
  // details disclosure. /verify/ and /integrity/ already carry the
  // forensic surface; the modal is just the publication record.
  // resolve the editorial publication title for this page. priority:
  //   1. explicit `<body data-citation-title>` override
  //   2. i18n key `cite.overlay.page_title.<data-page>` (the canonical
  //      route — translates per language so the homepage reads as
  //      "client strategy & growth systems" / "stratégie client …")
  //   3. verification-map record title (already curated, but generic
  //      pages like "homepage" are intentionally retired upstream)
  //   4. document.title minus the "— trent power" suffix as a final
  //      fallback so the modal never blanks.
  function resolveTitle(rec) {
    var explicit = document.body && document.body.dataset && document.body.dataset.citationTitle;
    if (explicit) return explicit;
    var page = (document.body && document.body.dataset && document.body.dataset.page) || '';
    if (page) {
      var key = 'cite.overlay.page_title.' + page;
      var translated = tt(key, '');
      if (translated) return translated;
    }
    return (rec && rec.title) || pageTitle() || tt('cite.site_label', 'Personal Site');
  }

  function buildOverlay() {
    var rec = currentRecord();
    var citation = (rec && rec.citation) || fallbackCitation();
    var title    = resolveTitle(rec);

    overlay = el('div', {
      'class': 'modal-overlay cite-overlay',
      'id': 'cite-dialog',
      'role': 'dialog',
      'aria-modal': 'true',
      'aria-labelledby': 'cite-modal-title',
      'aria-hidden': 'true',
    });

    var card = el('div', { 'class': 'modal cite-modal' });

    // one close affordance: top-right ×. focus trap + escape +
    // click-outside are wired by window.TP_OVERLAY from app-enhance.js.
    var closeX = el('button', {
      type: 'button',
      'class': 'cite-modal-close-x',
      'data-cite-action': 'close',
      'aria-label': tt('cite.overlay.action.close', 'Close'),
    }, '\u00d7');
    card.appendChild(closeX);

    // ── header · eyebrow + headline + lede ──
    // kicker reads as a small-caps publication mark; the title
    // preserves the page's document.title verbatim (so the homepage
    // shows the editorial doc title with the author suffix); the
    // lede is the single-sentence descriptor.
    var header = el('header', { 'class': 'cite-modal-header' });
    header.appendChild(el('p', { 'class': 'cite-modal-kicker' },
      tt('cite.overlay.kicker', 'This page')));
    header.appendChild(el('h2', { 'class': 'cite-modal-title', 'id': 'cite-modal-title' },
      title));
    header.appendChild(el('p', { 'class': 'cite-modal-lede' },
      tt('cite.overlay.lede', 'Canonical publication record for this page.')));
    card.appendChild(header);

    // ── actions · two tiers ──
    // primary (verify · view source · view integrity) — the editorial
    // navigation pathway. each link opens a verification surface.
    // secondary (copy citation · print profile) — utilities. quieter.
    // the footer now carries persistent edition / signed-sha / last-
    // verified state, so the modal no longer repeats those metadata
    // rows here.
    var actions = el('div', { 'class': 'cite-modal-actions' });

    var primary = el('div', { 'class': 'cite-modal-actions-row cite-modal-actions-row--primary' });
    primary.appendChild(el('a', {
      'href': safeHref(verifyUrl()),
      'class': 'cite-modal-action cite-modal-action--primary',
      'data-cite-action': 'verify',
    }, tt('cite.overlay.action.verify', 'Verify this page')));
    // page-aware "view source" target. for routes carried by the
    // verification map (every active page on the site), rec.reader is
    // the per-page source viewer url. rec.source is the raw mirror as
    // a secondary. /source/ catalogue lands as a final fallback.
    var sourceHref = (rec && rec.reader) || (rec && rec.source) || '/source/';
    primary.appendChild(el('a', {
      'href': safeHref(sourceHref),
      'class': 'cite-modal-action cite-modal-action--primary',
      'data-cite-action': 'open-source',
    }, tt('cite.overlay.action.open_source', 'View source')));
    actions.appendChild(primary);

    var secondary = el('div', { 'class': 'cite-modal-actions-row cite-modal-actions-row--secondary' });
    var copyBtn = el('button', {
      type: 'button',
      'class': 'cite-modal-action cite-modal-action--secondary',
      'data-cite-action': 'copy-citation',
    }, tt('cite.overlay.action.copy_citation', 'Copy citation'));
    copyBtn.dataset.payload = citation;
    secondary.appendChild(copyBtn);
    copyBtnAction['copy-citation'] = copyBtn;

    var printBtn = el('button', {
      type: 'button',
      'class': 'cite-modal-action cite-modal-action--secondary',
      'data-cite-action': 'print',
    }, printLabel());
    secondary.appendChild(printBtn);
    copyBtnAction['print'] = printBtn;
    actions.appendChild(secondary);

    card.appendChild(actions);

    // ── quiet footer line ──
    // the persistent footer at the bottom of the page already carries
    // the per-page signed sha-256, edition, last-verified status. here
    // the modal closes with a short publication-colophon stamp: the
    // edition + a signed-SHA256 acknowledgement (label only, no hash).
    var modalFooter = el('p', { 'class': 'cite-modal-footer-line' });
    var footerStamp = tt('cite.overlay.footer_signed',
      'Edition ' + EDITION + ' · Signed SHA256')
      .replace('{edition}', EDITION);
    modalFooter.appendChild(document.createTextNode(footerStamp));
    card.appendChild(modalFooter);

    // ── aria-live region for copy feedback. polite priority so
    // screen readers announce 'citation copied' without interrupting.
    var liveStatus = el('output', {
      'class': 'visually-hidden',
      'aria-live': 'polite',
      'id': 'cite-copy-status',
    });
    card.appendChild(liveStatus);

    overlay.appendChild(card);
    document.body.appendChild(overlay);

    overlay.addEventListener('click', function (e) {
      if (e.target === overlay) {
        if (window.TP_OVERLAY && window.TP_OVERLAY.close) window.TP_OVERLAY.close();
      }
    });

    card.addEventListener('click', handleActionClick);
  }
  function handleActionClick(e) {
    var t = e.target && e.target.closest
      ? e.target.closest('[data-cite-action]')
      : null;
    if (!t) return;
    var action = t.dataset.citeAction;

    if (action === 'close') {
      if (window.TP_OVERLAY && window.TP_OVERLAY.close) window.TP_OVERLAY.close();
      return;
    }
    if (action === 'print') {
      // ios safari requires window.print() synchronously inside the
      // gesture handler — settimeout breaks the gesture context. the
      // print stylesheet hides .modal-overlay across every data-page,
      // so calling print() with the overlay still mounted is safe; the
      // close runs after to tidy on-screen state.
      window.print();
      if (window.TP_OVERLAY && window.TP_OVERLAY.close) window.TP_OVERLAY.close();
      return;
    }
    if (action === 'copy-citation') {
      e.preventDefault();
      var payload = t.dataset.payload || '';
      if (!payload) return;
      if (!navigator.clipboard) return;
      var prev = t.textContent;
      var toastKey = 'cite.overlay.toast.citation_copied';
      var toastFallback = 'citation copied';
      navigator.clipboard.writeText(payload).then(function () {
        var toastText = tt(toastKey, toastFallback);
        t.textContent = toastText;
        t.setAttribute('data-state', 'copied');
        // announce via aria-live so screen readers receive the
        // confirmation. re-set the same text after a tick if it was
        // already there to force the live-region change event.
        var status = document.getElementById('cite-copy-status');
        if (status) {
          status.textContent = '';
          // microtask defer so the empty-then-text transition reliably
          // fires the at live-region notification.
          setTimeout(function () { status.textContent = toastText; }, 0);
        }
        setTimeout(function () {
          t.textContent = prev;
          t.removeAttribute('data-state');
          if (status) status.textContent = '';
        }, 1400);
      }, function () { /* clipboard denied , silent */ });
      return;
    }
    // 'verify' and 'open-source' are plain <a> elements — let the
    // browser navigate, no interception needed.
  }

  // ─── wire the cite trigger ─────────────────────────────────────────────
  // single trigger contract: anything carrying `[data-cite-open]` opens
  // the citation overlay. `.cite-btn` survives as a styling hook only.
  // history-integrated open pushes #cite onto the url so the browser
  // back button closes the overlay naturally and direct links auto-open
  // it. if the overlay infra (window.TP_OVERLAY) is missing for any
  // reason, the handler falls back to navigating to /verify/ so the
  // user is never stranded.
  function openCite(e, opener) {
    if (!opener && e && e.currentTarget) opener = e.currentTarget;
    if (!window.TP_OVERLAY || !window.TP_OVERLAY.open) {
      if (e && typeof e.preventDefault === 'function') e.preventDefault();
      try { console.warn('cite overlay unavailable, falling back to /verify/'); }
      catch (_) {}
      window.location.href = '/verify/';
      return;
    }
    if (e && typeof e.preventDefault === 'function') e.preventDefault();
    if (!overlay) buildOverlay();
    window.TP_OVERLAY.open(overlay, opener || null, { hash: '#cite' });
  }

  function init() {
    var triggers = document.querySelectorAll('[data-cite-open]');
    if (!triggers.length) return;
    Array.prototype.forEach.call(triggers, function (trigger) {
      trigger.addEventListener('click', function (e) { openCite(e, trigger); });
    });

    // deep-link · if the page is loaded with #cite in the url (shared
    // link, bookmark, browser-history navigation), auto-open the
    // overlay after the trigger is wired. skipped if the hash matches
    // an in-page anchor that exists (e.g. #contact) — only #cite is
    // claimed by this overlay.
    if (window.location.hash === '#cite') {
      // defer to the next tick so any other domcontentloaded handlers
      // finish first (notably the smooth-scroll and language-switcher
      // handlers in app.js).
      setTimeout(function () { openCite(null, triggers[0]); }, 0);
    }

    // popstate forward navigation: if we're back at #cite without an
    // active overlay, open. mirrors the back-button case so forward
    // re-opens cleanly. tagged with frompopstate so the open doesn't
    // push another history entry.
    window.addEventListener('popstate', function () {
      var st = history.state;
      if (window.location.hash === '#cite' && st && st.tp_overlay === 'cite' &&
          !document.querySelector('.modal-overlay.active')) {
        if (!overlay) buildOverlay();
        if (window.TP_OVERLAY && window.TP_OVERLAY.open) {
          window.TP_OVERLAY.open(overlay, triggers[0], { hash: '#cite', fromPopstate: true });
        }
      }
    });

    // re-render overlay if the language changes after first open. cheap:
    // we just discard it; next click rebuilds.
    var observer = new MutationObserver(function (mutations) {
      for (var i = 0; i < mutations.length; i++) {
        if (mutations[i].attributeName === 'lang') {
          if (overlay) {
            overlay.parentNode && overlay.parentNode.removeChild(overlay);
            overlay = null;
            copyBtnAction = {};
          }
          break;
        }
      }
    });
    observer.observe(document.documentElement, { attributes: true });
  }

  // defer init off the critical render path. the cite-button click
  // happens long after first paint, so building the overlay shell at
  // domcontentloaded was wasted main-thread time (~50 ms long task on
  // mobile). requestidlecallback runs after the browser is idle;
  // settimeout 500 ms is the fallback for engines without it.
  function _scheduleInit() {
    if ('requestIdleCallback' in window) {
      window.requestIdleCallback(init, { timeout: 1500 });
    } else {
      window.setTimeout(init, 500);
    }
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', _scheduleInit);
  } else {
    _scheduleInit();
  }

  // fingerprint + verify-command copy buttons. defer the dom walk +
  // listener attachment to idle: clicks happen well after first paint,
  // and walking the document for these selectors before lcp costs
  // main-thread time for no user-visible benefit.
  function _wireCopyButtons() {
    var copyBtns = document.querySelectorAll('.copy-fingerprint[data-copy-target], .verify-command-copy[data-copy-target]');
    copyBtns.forEach(function (b) {
      var resting = b.textContent;
      var collapse = b.classList.contains('copy-fingerprint');
      b.addEventListener('click', function () {
        var target = document.getElementById(b.dataset.copyTarget);
        if (!target || !navigator.clipboard) return;
        var raw = target.textContent || '';
        var text = collapse ? raw.replace(/\s+/g, ' ').trim() : raw.replace(/\s+$/, '');
        navigator.clipboard.writeText(text).then(function () {
          b.textContent = tt('cite.copied', 'Copied');
          b.setAttribute('data-state', 'copied');
          setTimeout(function () {
            b.textContent = resting;
            b.removeAttribute('data-state');
          }, 1500);
        }, function () { /* silent */ });
      });
    });
  }
  if ('requestIdleCallback' in window) {
    window.requestIdleCallback(_wireCopyButtons, { timeout: 1500 });
  } else {
    window.setTimeout(_wireCopyButtons, 500);
  }

})();
