// ── Achievements: invisible until the first one lands ─────────
(function () {
  const KEY = 'achievements';
  const ALL = [
    { id: 'cat',           name: 'Cat lover',     how: 'Saw every picture of Pepsi' },
    { id: 'natural',       name: 'Natural',       how: 'Rolled a natural 20' },
    { id: 'investigator',  name: 'Investigator',  how: 'Clicked everything there is to click' },
    { id: 'adventurer',    name: 'Adventurer',    how: 'Completed the Quest for the Deleted Website' },
    { id: 'hacker',        name: 'Hacker',        how: 'Became root' },
    { id: 'completionist', name: 'Completionist', how: 'Earned every other achievement' },
  ];
  // Investigator's checklist: six named interactions plus every round
  // icon — same selector as js/icon-spin.js, which reports 'icon-<index>'
  const ICONS = document.querySelectorAll(
    '.skill-card-icon, .project-icon, .edu-icon, .nav-logo'
  ).length;
  const MARKS = ['anagram', 'slot', 'pronouns', 'avatar', 'd20', 'pepsi'];
  for (let i = 0; i < ICONS; i++) MARKS.push('icon-' + i);

  const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');

  // Medal art for earned achievements; ids without an entry render as
  // blank white shields until their design lands
  const MEDALS = {
    cat:
      '<svg class="m-cat" viewBox="0 0 80 95" aria-hidden="true">' +
      '<path d="M40,28 C38,27 35,26 33,26 L14,10 L17,37 C13,42 12,50 14,56 ' +
      'C18,67 28,73 40,73 C52,73 62,67 66,56 C68,50 67,42 63,37 L66,10 ' +
      'L47,26 C45,26 42,27 40,28 Z" fill="var(--ink)"/>' +
      '<ellipse cx="29" cy="48" rx="5" ry="6" fill="var(--yellow)"/>' +
      '<ellipse cx="51" cy="48" rx="5" ry="6" fill="var(--yellow)"/>' +
      '<ellipse cx="29" cy="48" rx="1.8" ry="5" fill="var(--ink)"/>' +
      '<ellipse cx="51" cy="48" rx="1.8" ry="5" fill="var(--ink)"/>' +
      '<path d="M36,58 h8 l-4,5 z" fill="var(--pink)" stroke="var(--pink)" ' +
      'stroke-width="2" stroke-linejoin="round"/>' +
      '<g fill="none" stroke="var(--white)" stroke-width="2" stroke-linecap="round">' +
      '<path d="M31,57 Q20,55 12,53"/><path d="M31,60 Q20,60 11,60"/>' +
      '<path d="M31,63 Q20,65 12,67"/><path d="M49,57 Q60,55 68,53"/>' +
      '<path d="M49,60 Q60,60 69,60"/><path d="M49,63 Q60,65 68,67"/>' +
      '</g></svg>',
    investigator:
      '<svg class="m-inv" viewBox="0 0 80 95" aria-hidden="true">' +
      '<defs><pattern id="invSeme" width="16" height="28" ' +
      'patternUnits="userSpaceOnUse" fill="var(--yellow)">' +
      '<rect x="-1.5" y="-4.5" width="3" height="9" rx="1.5" transform="translate(8 7) rotate(45)"/>' +
      '<rect x="-1.5" y="-4.5" width="3" height="9" rx="1.5" transform="translate(0 21) rotate(45)"/>' +
      '<rect x="-1.5" y="-4.5" width="3" height="9" rx="1.5" transform="translate(16 21) rotate(45)"/>' +
      '</pattern></defs>' +
      '<path d="M3 3 H77 V37.5 C77 68 55 84 40 92 C25 84 3 68 3 37.5 Z" fill="url(#invSeme)"/>' +
      '<circle cx="32" cy="38" r="15" fill="#177245" stroke="var(--white)" stroke-width="5"/>' +
      '<line x1="43" y1="49" x2="56" y2="64" stroke="var(--white)" stroke-width="7" stroke-linecap="round"/>' +
      '</svg>',
    natural:
      '<svg class="m-nat" viewBox="0 0 80 95" aria-hidden="true">' +
      '<defs><clipPath id="natFace">' +
      '<path d="M3 3 H77 V37.5 C77 68 55 84 40 92 C25 84 3 68 3 37.5 Z"/>' +
      '</clipPath></defs>' +
      '<g clip-path="url(#natFace)" fill="var(--yellow)">' +
      '<path d="M40 42 L26.5 -26.7 L53.5 -26.7 Z"/>' +
      '<path d="M40 42 L100.7 7 L114.2 30.4 Z"/>' +
      '<path d="M40 42 L114.2 53.6 L100.7 77 Z"/>' +
      '<path d="M40 42 L53.5 110.7 L26.5 110.7 Z"/>' +
      '<path d="M40 42 L-20.7 77 L-34.2 53.6 Z"/>' +
      '<path d="M40 42 L-34.2 30.4 L-20.7 7 Z"/>' +
      '</g>' +
      '<g stroke="var(--ink)" stroke-width="2" stroke-linejoin="round">' +
      '<polygon points="40,18 60.8,30 60.8,54 40,66 19.2,54 19.2,30" fill="var(--white)"/>' +
      '<g fill="none">' +
      '<polygon points="40,28 52.12,49 27.88,49"/>' +
      '<line x1="40" y1="28" x2="40" y2="18"/>' +
      '<line x1="27.88" y1="49" x2="19.2" y2="54"/>' +
      '<line x1="52.12" y1="49" x2="60.8" y2="54"/>' +
      '<line x1="40" y1="28" x2="60.8" y2="30"/>' +
      '<line x1="40" y1="28" x2="19.2" y2="30"/>' +
      '<line x1="27.88" y1="49" x2="19.2" y2="30"/>' +
      '<line x1="27.88" y1="49" x2="40" y2="66"/>' +
      '<line x1="52.12" y1="49" x2="40" y2="66"/>' +
      '<line x1="52.12" y1="49" x2="60.8" y2="30"/>' +
      '</g></g>' +
      '<text x="40" y="46" text-anchor="middle" ' +
      'font-family="\'Space Grotesk\', sans-serif" font-weight="700" ' +
      'font-size="9.5" fill="var(--ink)">20</text>' +
      '</svg>',
    adventurer:
      '<svg class="m-adv" viewBox="0 0 80 95" aria-hidden="true">' +
      '<path d="M40,9 L44.5,21 L43.5,58 L36.5,58 L35.5,21 Z" fill="var(--white)"/>' +
      '<rect x="24" y="58" width="32" height="6" rx="2" fill="var(--yellow)"/>' +
      '<rect x="36.5" y="64" width="7" height="14" fill="var(--ink)"/>' +
      '<circle cx="40" cy="82" r="5" fill="var(--yellow)"/>' +
      '</svg>',
    completionist:
      '<svg class="m-comp" viewBox="0 0 80 95" aria-hidden="true">' +
      '<g stroke="var(--ink)" stroke-width="2" stroke-linejoin="round" fill="var(--yellow)">' +
      '<path d="M19.2,54 L21.2,21 L30.9,44 L40,13 L49.1,44 L58.8,21 L60.8,54 Z"/>' +
      '<rect x="17.9" y="54" width="44.2" height="10.4" rx="2.6"/>' +
      '</g></svg>',
    hacker:
      '<svg class="m-hk" viewBox="0 0 80 95" aria-hidden="true">' +
      '<defs><filter id="hkGlow" x="-60%" y="-60%" width="220%" height="220%">' +
      '<feGaussianBlur stdDeviation="2.4" result="b"/>' +
      '<feMerge><feMergeNode in="b"/><feMergeNode in="b"/>' +
      '<feMergeNode in="SourceGraphic"/></feMerge>' +
      '</filter></defs>' +
      '<g filter="url(#hkGlow)">' +
      '<path d="M25,31 L40,42 L25,53" fill="none" stroke="var(--green)" ' +
      'stroke-width="5.5" stroke-linecap="square"/>' +
      '<rect class="m-hk-cursor" x="45" y="50" width="13" height="5.5" fill="var(--green)"/>' +
      '</g></svg>',
  };

  const state = { unlocked: [], announced: [], pepsi: [], marks: [] };
  try {
    const saved = JSON.parse(localStorage.getItem(KEY));
    if (saved && typeof saved === 'object') {
      for (const k of Object.keys(state)) {
        if (Array.isArray(saved[k])) state[k] = saved[k];
      }
    }
  } catch (e) {}
  const save = () => {
    try { localStorage.setItem(KEY, JSON.stringify(state)); } catch (e) {}
  };

  // ── Badge: absent from the DOM until at least one unlock ────
  let badge = null;
  let button = null;
  let panel = null;

  function buildBadge() {
    badge = document.createElement('div');
    badge.className = 'achv';
    button = document.createElement('button');
    button.type = 'button';
    button.className = 'achv-badge';
    button.setAttribute('aria-expanded', 'false');
    button.innerHTML =
      '<i class="fas fa-trophy" aria-hidden="true"></i><span class="achv-count"></span>';
    panel = document.createElement('ul');
    panel.className = 'achv-panel';
    panel.hidden = true;
    // Closing plays the slide-out first; hidden flips when it's done
    // (0.3s slide + 225ms reverse stagger)
    let closing = null;
    const openPanel = () => {
      if (closing) { clearTimeout(closing); closing = null; }
      panel.classList.remove('achv-panel--closing');
      panel.hidden = false;
      button.setAttribute('aria-expanded', 'true');
    };
    const closePanel = () => {
      if (panel.hidden || closing) return;
      button.setAttribute('aria-expanded', 'false');
      if (reduceMotion.matches) { panel.hidden = true; return; }
      panel.classList.add('achv-panel--closing');
      closing = setTimeout(() => {
        closing = null;
        panel.classList.remove('achv-panel--closing');
        panel.hidden = true;
      }, 560);
    };
    button.addEventListener('click', () => {
      if (panel.hidden || closing) openPanel(); else closePanel();
    });
    document.addEventListener('click', (e) => {
      if (badge.contains(e.target)) return;
      closePanel();
    });
    badge.appendChild(button);
    badge.appendChild(panel);
    // Body-level: keeps it a later sibling of #header, so the
    // `#header.scrolled ~ .achv` rule can slide it below the nav
    document.body.appendChild(badge);
  }

  function renderBadge() {
    if (!state.unlocked.length) return;
    if (!badge) buildBadge();
    if (!badge) return;
    const n = state.unlocked.length;
    button.querySelector('.achv-count').textContent = n + '/' + ALL.length;
    button.setAttribute(
      'aria-label',
      'Achievements: ' + n + ' of ' + ALL.length + ' unlocked'
    );
    panel.textContent = '';
    ALL.forEach((a) => {
      const li = document.createElement('li');
      const got = state.unlocked.includes(a.id);
      li.className = 'achv-medal' + (got ? ' achv-medal--got' : '');
      if (got && MEDALS[a.id]) {
        li.classList.add('achv-medal--' + a.id);
        li.innerHTML = MEDALS[a.id];
      }
      const tip = document.createElement('span');
      tip.className = 'achv-tip';
      if (got) {
        const title = document.createElement('b');
        title.textContent = a.name;
        tip.appendChild(title);
        tip.appendChild(document.createTextNode(a.how));
      } else {
        tip.textContent = '????';
      }
      li.appendChild(tip);
      const sr = document.createElement('span');
      sr.className = 'achv-sr';
      sr.textContent = got ? a.name + ' — ' + a.how : '????';
      li.appendChild(sr);
      panel.appendChild(li);
    });
  }

  // ── Toasts: queued, one at a time. Shown as manual popovers so
  //    they join the top layer and render above any open <dialog>
  //    (the terminal); without popover support they hold back until
  //    the dialog closes instead ──────────────────────────────────
  const queue = [];
  let showing = false;

  function next() {
    if (!queue.length) { showing = false; return; }
    if (!HTMLElement.prototype.showPopover &&
        document.querySelector('dialog[open]')) {
      setTimeout(next, 600);
      return;
    }
    const a = queue.shift();
    const el = document.createElement('div');
    el.className = 'achv-toast';
    el.setAttribute('role', 'status');
    el.setAttribute('popover', 'manual');
    // Tiny medal: a real shield scaled down, so the art stays shared
    const slot = document.createElement('span');
    slot.className = 'achv-toast-medal';
    const shield = document.createElement('span');
    shield.className = 'achv-medal achv-medal--got' +
      (MEDALS[a.id] ? ' achv-medal--' + a.id : '');
    if (MEDALS[a.id]) shield.innerHTML = MEDALS[a.id];
    slot.appendChild(shield);
    const text = document.createElement('span');
    text.className = 'achv-toast-text';
    const kicker = document.createElement('span');
    kicker.className = 'achv-toast-kicker';
    kicker.textContent = 'Achievement unlocked';
    const label = document.createElement('span');
    label.className = 'achv-toast-name';
    label.textContent = a.name;
    text.appendChild(kicker);
    text.appendChild(label);
    el.appendChild(slot);
    el.appendChild(text);
    document.body.appendChild(el);
    if (el.showPopover) el.showPopover();

    const remove = () => { el.remove(); next(); };
    const hide = () => {
      if (reduceMotion.matches) { remove(); return; }
      el.animate(
        [
          { transform: 'translate(-50%, 0)' },
          { transform: 'translate(-50%, -140%)' },
        ],
        { duration: 300, easing: 'cubic-bezier(0.4, 0, 0.7, 0.2)', fill: 'forwards' }
      ).finished.then(remove);
    };
    if (reduceMotion.matches) {
      el.style.transform = 'translate(-50%, 0)';
      setTimeout(hide, 3500);
      return;
    }
    el.animate(
      [
        { transform: 'translate(-50%, -140%)' },
        { transform: 'translate(-50%, 0)' },
      ],
      { duration: 350, easing: 'cubic-bezier(0.2, 0.9, 0.3, 1.1)', fill: 'forwards' }
    ).finished.then((anim) => {
      el.style.transform = 'translate(-50%, 0)';
      anim.cancel();
      setTimeout(hide, 3500);
    });
  }

  function toast(a) {
    queue.push(a);
    if (!showing) { showing = true; next(); }
  }

  function announce() {
    state.unlocked.forEach((id) => {
      if (state.announced.includes(id)) return;
      state.announced.push(id);
      const a = ALL.find((x) => x.id === id);
      if (a) toast(a);
    });
    save();
    renderBadge();
  }

  // ── The API the feature files call (always guarded:
  //    window.achv && window.achv.…) ─────────────────────────────
  function unlock(id) {
    if (state.unlocked.includes(id)) return;
    if (!ALL.some((a) => a.id === id)) return;
    state.unlocked.push(id);
    if (
      id !== 'completionist' &&
      ALL.every((a) => a.id === 'completionist' || state.unlocked.includes(a.id))
    ) {
      state.unlocked.push('completionist');
    }
    save();
    announce();
  }

  function mark(id) {
    if (!MARKS.includes(id) || state.marks.includes(id)) return;
    state.marks.push(id);
    save();
    if (MARKS.every((m) => state.marks.includes(m))) unlock('investigator');
  }

  function pepsi(n, total) {
    if (state.pepsi.includes(n)) return;
    state.pepsi.push(n);
    save();
    if (state.pepsi.length >= total) unlock('cat');
  }

  window.achv = { unlock, mark, pepsi };

  // Unlocks earned off-page (the quest) or before this system existed
  // (a rooted terminal) surface on the next index load
  setTimeout(() => {
    try {
      if (localStorage.getItem('questWon') === '1') unlock('adventurer');
      if (localStorage.getItem('termRoot') === '1') unlock('hacker');
    } catch (e) {}
    announce();
  }, 800);
})();
