/**
 * holofoil-init.js
 * -----------------------------------------------------------------------------
 * Instruments .image-grow and .card-image-wrap with the holofoil effect system.
 *
 *  GRID (.image-grow)
 *    Effect is invisible at rest. On hover, pointer tracking drives the shine
 *    and glare layers. The card lift/scale comes from main.scss as before.
 *
 *  MODAL (.card-image-wrap)
 *    Effect sweeps autonomously once per minute via CSS animation.
 *    Pointer tracking takes over while the user hovers the image.
 *
 *  DEFAULT FALLBACK
 *    If data-default="true" is set (onerror in the Astro markup), no effect
 *    is applied -- even if the attribute appears after stamp() has run.
 * -----------------------------------------------------------------------------
 */

(function HolofoilSystem() {

  'use strict';

  // -- Constants --------------------------------------------------------------

  const SHIMMER_SEL = [
    '.image-grow[data-rarity]',
    '.image-grow[data-variant="Holofoil"]',
    '.image-grow[data-variant="1st Edition Holofoil"]',
    '.image-grow[data-variant="Unlimited Holofoil"]',
    '.image-grow[data-variant="Reverse Holofoil"]',
    '.card-image-wrap[data-rarity]',
    '.card-image-wrap[data-variant="Holofoil"]',
    '.card-image-wrap[data-variant="1st Edition Holofoil"]',
    '.card-image-wrap[data-variant="Unlimited Holofoil"]',
    '.card-image-wrap[data-variant="Reverse Holofoil"]',
  ].join(',');

  const ALL_WRAPPERS_SEL = '.image-grow, .card-image-wrap';

  // Foil variant visual randomisation
  const FOIL_ANGLE_MIN = -65,  FOIL_ANGLE_MAX = -25;
  const FOIL_BRITE_MIN = 0.18, FOIL_BRITE_MAX = 0.32;
  const FOIL_SAT_MIN   = 0.40, FOIL_SAT_MAX   = 0.75;

  const SKIP_RARITIES = new Set(['common', 'uncommon', '']);


  // -- Helpers ----------------------------------------------------------------

  const rand = (min, max) => parseFloat((Math.random() * (max - min) + min).toFixed(2));
  const clamp01 = n => Math.max(0, Math.min(1, n));

  function pointerVars(x, y, rect) {
    const fromLeft   = clamp01((x - rect.left) / rect.width);
    const fromTop    = clamp01((y - rect.top)  / rect.height);
    const fromCenter = clamp01(Math.sqrt((fromLeft - 0.5) ** 2 + (fromTop - 0.5) ** 2) * 2);
    return {
      px:         fromLeft * 100,
      py:         fromTop  * 100,
      fromLeft,
      fromTop,
      fromCenter,
      bgX:        50 + (fromLeft - 0.5) * 30,
      bgY:        50 + (fromTop  - 0.5) * 30,
    };
  }

  function applyPointerVars(el, v) {
    el.style.setProperty('--pointer-x',           v.px.toFixed(1) + '%');
    el.style.setProperty('--pointer-y',           v.py.toFixed(1) + '%');
    el.style.setProperty('--pointer-from-left',   v.fromLeft.toFixed(3));
    el.style.setProperty('--pointer-from-top',    v.fromTop.toFixed(3));
    el.style.setProperty('--pointer-from-center', v.fromCenter.toFixed(3));
    el.style.setProperty('--background-x',        v.bgX.toFixed(1) + '%');
    el.style.setProperty('--background-y',        v.bgY.toFixed(1) + '%');
  }

  const isHoloVariant  = v => ['Holofoil', 'Reverse Holofoil', '1st Edition Holofoil', 'Unlimited Holofoil'].includes(v);
  const isModalWrapper = el => el.classList.contains('card-image-wrap');
  const isDefault      = el => el.dataset.default === 'true';


  // -- Child injection --------------------------------------------------------

  function injectChildren(el) {
    if (el.querySelector('.holo-shine')) return;
    const shine = document.createElement('div');
    shine.className = 'holo-shine';
    const glare = document.createElement('div');
    glare.className = 'holo-glare';
    el.appendChild(shine);
    el.appendChild(glare);
  }


  // -- Default image guard ----------------------------------------------------

  /**
   * Watch for the onerror handler in the Astro markup setting data-default="true"
   * after stamp() has already run. Hide the effect children immediately when seen.
   */
  function watchForDefault(el) {
    if (isDefault(el)) return;

    var observer = new MutationObserver(function() {
      if (isDefault(el)) {
        var shine = el.querySelector('.holo-shine');
        var glare = el.querySelector('.holo-glare');
        if (shine) shine.style.display = 'none';
        if (glare) glare.style.display = 'none';
        observer.disconnect();
      }
    });

    observer.observe(el, { attributes: true, attributeFilter: ['data-default'] });
  }


  // -- Stamp ------------------------------------------------------------------

  function stamp(el) {
    if (el.dataset.holoInit) return;

    // Skip if already a default fallback image
    if (isDefault(el)) {
      el.dataset.holoInit = 'skip';
      return;
    }

    const rarity  = (el.dataset.rarity  || '').toLowerCase();
    const variant =  el.dataset.variant  || '';

    const hasHoloRarity  = rarity && !SKIP_RARITIES.has(rarity);
    const hasHoloVariant = isHoloVariant(variant);

    if (!hasHoloRarity && !hasHoloVariant) {
      el.dataset.holoInit = 'skip';
      return;
    }

    injectChildren(el);

    // Per-card foil visual randomisation (angle/brightness/saturation)
    if (hasHoloVariant) {
      el.style.setProperty('--foil-angle',      Math.round(rand(FOIL_ANGLE_MIN, FOIL_ANGLE_MAX)) + 'deg');
      el.style.setProperty('--foil-brightness', rand(FOIL_BRITE_MIN, FOIL_BRITE_MAX).toFixed(2));
      el.style.setProperty('--foil-saturation', rand(FOIL_SAT_MIN,   FOIL_SAT_MAX  ).toFixed(2));
    }

    // Modal-only: set a stable delay offset for the autonomous CSS animation
    if (isModalWrapper(el)) {
      el.classList.add('holo-modal-mode');
      el.style.setProperty('--shimmer-delay', rand(-8, 0) + 's');
    }

    watchForDefault(el);

    el.dataset.holoInit = '1';
  }

  function stampAll(root) {
    (root || document).querySelectorAll(ALL_WRAPPERS_SEL).forEach(stamp);
  }


  // -- Pointer tracking -------------------------------------------------------

  const pointerState = new WeakMap();

  function onPointerEnter(e) {
    const el = e.currentTarget;
    if (el.dataset.holoInit !== '1' || isDefault(el)) return;

    el.dataset.holoActive = '1';
    if (!pointerState.has(el)) pointerState.set(el, { rafId: null });
  }

  function onPointerMove(e) {
    const el = e.currentTarget;
    if (el.dataset.holoInit !== '1') return;

    const state = pointerState.get(el);
    if (!state) return;

    if (state.rafId) cancelAnimationFrame(state.rafId);
    state.rafId = requestAnimationFrame(function() {
      const rect = el.getBoundingClientRect();
      applyPointerVars(el, pointerVars(e.clientX, e.clientY, rect));
      state.rafId = null;
    });
  }

  function onPointerLeave(e) {
    const el = e.currentTarget;
    if (el.dataset.holoInit !== '1') return;

    const state = pointerState.get(el);
    if (state && state.rafId) { cancelAnimationFrame(state.rafId); state.rafId = null; }

    delete el.dataset.holoActive;

    if (isModalWrapper(el)) {
      // Let the CSS animation resume driving --card-opacity
      el.style.removeProperty('--card-opacity');
    }
  }

  function attachListeners(el) {
    if (el.dataset.holoListeners) return;
    el.addEventListener('pointerenter', onPointerEnter, { passive: true });
    el.addEventListener('pointermove',  onPointerMove,  { passive: true });
    el.addEventListener('pointerleave', onPointerLeave, { passive: true });
    el.dataset.holoListeners = '1';
  }

  function attachAllListeners(root) {
    (root || document).querySelectorAll(SHIMMER_SEL).forEach(function(el) {
      stamp(el);
      if (el.dataset.holoInit === '1') attachListeners(el);
    });
  }


  // -- MutationObserver: react to HTMX / infinite scroll ----------------------

  function observeGrid() {
    var grid = document.getElementById('cardGrid');
    if (!grid) return;

    new MutationObserver(function(mutations) {
      for (var i = 0; i < mutations.length; i++) {
        var nodes = mutations[i].addedNodes;
        for (var j = 0; j < nodes.length; j++) {
          var node = nodes[j];
          if (node.nodeType !== 1) continue;
          if (node.matches && node.matches(ALL_WRAPPERS_SEL)) {
            stamp(node);
            if (node.dataset.holoInit === '1') attachListeners(node);
          }
          if (node.querySelectorAll) {
            node.querySelectorAll(ALL_WRAPPERS_SEL).forEach(function(el) {
              stamp(el);
              if (el.dataset.holoInit === '1') attachListeners(el);
            });
          }
        }
      }
    }).observe(grid, { childList: true, subtree: true });
  }

  function observeModal() {
    var modal = document.getElementById('cardModal');
    if (!modal) return;

    new MutationObserver(function() {
      modal.querySelectorAll(ALL_WRAPPERS_SEL).forEach(function(el) {
        stamp(el);
        if (el.dataset.holoInit === '1') attachListeners(el);
      });
    }).observe(modal, { childList: true, subtree: true });
  }


  // -- Bootstrap --------------------------------------------------------------

  function init() {
    stampAll();
    attachAllListeners();
    observeGrid();
    observeModal();
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

})();