// ============================================================================
// 1. Mobile navigation toggle
//    - Handles opening/closing the mobile nav panel
//    - Updates ARIA attributes for accessibility
//    - Closes panel when a link inside it is clicked
// ============================================================================

(function () {
  const btn = document.getElementById('nav-toggle');
  const panel = document.getElementById('mobile-nav');
  if (!btn || !panel) return; // No mobile nav in this layout, abort

  btn.addEventListener('click', () => {
    // Toggle the "hidden" class on the panel.
    // classList.toggle returns true if the class is present AFTER the call.
    const isHidden = panel.classList.toggle('hidden');
    const expanded = !isHidden; // aria-expanded = true when the panel is visible

    btn.setAttribute('aria-expanded', String(expanded));
    btn.setAttribute('aria-label', expanded ? 'Close menu' : 'Open menu');
  });

  // Close panel when clicking any link inside the mobile nav
  panel.addEventListener('click', (e) => {
    const a = e.target.closest('a');
    if (!a) return;

    panel.classList.add('hidden');
    btn.setAttribute('aria-expanded', 'false');
    btn.setAttribute('aria-label', 'Open menu');
  });
})();


// ============================================================================
// 2. Image gallery
//    - Supports multiple galleries via [data-gallery-root]
//    - Thumbnail navigation, prev/next arrows, keyboard arrows, touch swipe
//    - Runs on initial load and after SxEngine swaps
// ============================================================================

(() => {
  /**
   * Initialize any galleries found within a given DOM subtree.
   * @param {ParentNode} root - Root element to search in (defaults to document).
   */
  function initGallery(root) {
    if (!root) return;

    // Find all nested gallery roots
    const galleries = root.querySelectorAll('[data-gallery-root]');

    // If root itself is a gallery and no nested galleries exist,
    // initialize just the root.
    if (!galleries.length && root.matches?.('[data-gallery-root]')) {
      initOneGallery(root);
      return;
    }

    galleries.forEach(initOneGallery);
  }

  /**
   * Initialize a single gallery instance.
   * This attaches handlers only once, even if SxEngine re-inserts the fragment.
   * @param {Element} root - Element with [data-gallery-root].
   */
  function initOneGallery(root) {
    // Prevent double-initialization (SxEngine may re-insert the same fragment)
    if (root.dataset.galleryInitialized === 'true') return;
    root.dataset.galleryInitialized = 'true';

    let index = 0;

    // Collect all image URLs from [data-image-src] attributes
    const imgs = Array.from(root.querySelectorAll('[data-image-src]'))
      .map(el => el.getAttribute('data-image-src') || el.dataset.imageSrc)
      .filter(Boolean);

    const main    = root.querySelector('[data-main-img]');
    const prevBtn = root.querySelector('[data-prev]');
    const nextBtn = root.querySelector('[data-next]');
    const thumbs  = Array.from(root.querySelectorAll('[data-thumb]'));
    const titleEl = root.querySelector('[data-title]');
    const total   = imgs.length;

    // Without a main image or any sources, the gallery is not usable
    if (!main || !total) return;

    /**
     * Render the gallery to reflect the current `index`:
     * - Update main image src/alt
     * - Update active thumbnail highlight
     * - Keep prev/next button ARIA labels consistent
     */
    function render() {
      main.setAttribute('src', imgs[index]);

      // Highlight active thumbnail
      thumbs.forEach((t, i) => {
        if (i === index) t.classList.add('ring-2', 'ring-stone-900');
        else t.classList.remove('ring-2', 'ring-stone-900');
      });

      // Basic ARIA labels for navigation buttons
      if (prevBtn && nextBtn) {
        prevBtn.setAttribute('aria-label', 'Previous image');
        nextBtn.setAttribute('aria-label', 'Next image');
      }

      // Alt text uses base title + position (e.g. "Product image (1/4)")
      const baseTitle = (titleEl?.textContent || 'Product image').trim();
      main.setAttribute('alt', `${baseTitle} (${index + 1}/${total})`);
    }

    /**
     * Move to a specific index, wrapping around at bounds.
     * @param {number} n - Desired index (can be out-of-bounds; we mod it).
     */
    function go(n) {
      index = (n + imgs.length) % imgs.length;
      render();
    }

    // --- Button handlers ----------------------------------------------------

    prevBtn?.addEventListener('click', (e) => {
      e.preventDefault();
      go(index - 1);
    });

    nextBtn?.addEventListener('click', (e) => {
      e.preventDefault();
      go(index + 1);
    });

    // --- Thumbnail handlers -------------------------------------------------

    thumbs.forEach((t, i) => {
      t.addEventListener('click', (e) => {
        e.preventDefault();
        go(i);
      });
    });

    // --- Keyboard navigation (left/right arrows) ---------------------------
    // Note: we only act if `root` is still attached to the DOM.
    const keyHandler = (e) => {
      if (!root.isConnected) return;
      if (e.key === 'ArrowLeft') go(index - 1);
      if (e.key === 'ArrowRight') go(index + 1);
    };
    document.addEventListener('keydown', keyHandler);

    // --- Touch swipe on main image (horizontal only) -----------------------

    let touchStartX = null;
    let touchStartY = null;
    const SWIPE_MIN = 30; // px

    main.addEventListener('touchstart', (e) => {
      const t = e.changedTouches[0];
      touchStartX = t.clientX;
      touchStartY = t.clientY;
    }, { passive: true });

    main.addEventListener('touchend', (e) => {
      if (touchStartX === null) return;

      const t = e.changedTouches[0];
      const dx = t.clientX - touchStartX;
      const dy = t.clientY - touchStartY;

      // Horizontal swipe: dx large, dy relatively small
      if (Math.abs(dx) > SWIPE_MIN && Math.abs(dy) < 0.6 * Math.abs(dx)) {
        if (dx < 0) go(index + 1);
        else go(index - 1);
      }

      touchStartX = touchStartY = null;
    }, { passive: true });

    // Initial UI state
    render();
  }

  // Initialize all galleries on initial page load
  document.addEventListener('DOMContentLoaded', () => {
    initGallery(document);
  });

  // Re-initialize galleries after SxEngine swaps
  document.addEventListener('sx:afterSettle', (evt) => {
    initGallery(evt.detail?.target || document);
  });
})();


// ============================================================================
// 3. "Peek" scroll viewport
//    - Adds a clipped/peek effect to scrollable containers
//    - Uses negative margins and optional CSS mask fade
//    - Automatically updates on resize and DOM mutations
// ============================================================================

(() => {
  /**
   * Safely parse a numeric value or fall back to a default.
   */
  function px(val, def) {
    const n = Number(val);
    return Number.isFinite(n) ? n : def;
  }

  /**
   * Apply the peek effect to a viewport and its inner content.
   * @param {HTMLElement} vp    - The viewport (with data-peek-viewport).
   * @param {HTMLElement} inner - Inner content wrapper.
   */
  function applyPeek(vp, inner) {
    const edge = (vp.dataset.peekEdge || 'bottom').toLowerCase();
    const useMask = vp.dataset.peekMask === 'true';

    // Compute peek size in pixels:
    // - data-peek-size-px: direct px value
    // - data-peek-size: "units" that are scaled by root font size * 0.25
    // - default: 24px
    const sizePx =
      px(vp.dataset.peekSizePx, NaN) ||
      px(vp.dataset.peekSize, NaN) *
        (parseFloat(getComputedStyle(document.documentElement).fontSize) || 16) *
        0.25 ||
      24;

    const overflowing = vp.scrollHeight > vp.clientHeight;

    // Reset any previous modifications
    inner.style.marginTop = '';
    inner.style.marginBottom = '';
    vp.style.webkitMaskImage = vp.style.maskImage = '';

    // Reset last child's margin in case we changed it previously
    const last = inner.lastElementChild;
    if (last) last.style.marginBottom = '';

    if (!overflowing) return;

    // NOTE: For clipping to look right, we want the viewport's own bottom padding
    // to be minimal. Consider also using pb-0 in CSS if needed.

    // Apply negative margins to "cut" off content at top/bottom, creating peek
    if (edge === 'bottom' || edge === 'both') inner.style.marginBottom = `-${sizePx}px`;
    if (edge === 'top'    || edge === 'both') inner.style.marginTop    = `-${sizePx}px`;

    // Prevent the very last child from cancelling the visual clip
    if (edge === 'bottom' || edge === 'both') {
      if (last) last.style.marginBottom = '0px';
    }

    // Optional fade in/out mask on top/bottom
    if (useMask) {
      const topStop    = (edge === 'top' || edge === 'both') ? `${sizePx}px` : '0px';
      const bottomStop = (edge === 'bottom' || edge === 'both') ? `${sizePx}px` : '0px';
      const mask = `linear-gradient(
        180deg,
        transparent 0,
        black ${topStop},
        black calc(100% - ${bottomStop}),
        transparent 100%
      )`;
      vp.style.webkitMaskImage = vp.style.maskImage = mask;
    }
  }

  /**
   * Set up one viewport with peek behavior.
   * @param {HTMLElement} vp - Element with [data-peek-viewport].
   */
  function setupViewport(vp) {
    const inner = vp.querySelector('[data-peek-inner]') || vp.firstElementChild;
    if (!inner) return;

    const update = () => applyPeek(vp, inner);

    // Observe size changes (viewport & inner)
    const ro = 'ResizeObserver' in window ? new ResizeObserver(update) : null;
    ro?.observe(vp);
    ro?.observe(inner);

    // Observe DOM changes inside the inner container
    const mo = new MutationObserver(update);
    mo.observe(inner, { childList: true, subtree: true });

    // Run once on window load and once immediately
    window.addEventListener('load', update, { once: true });
    update();
  }

  /**
   * Initialize peek behavior for all [data-peek-viewport] elements
   * inside the given root.
   */
  function initPeek(root = document) {
    root.querySelectorAll('[data-peek-viewport]').forEach(setupViewport);
  }

  // Run on initial DOM readiness
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => initPeek());
  } else {
    initPeek();
  }

  // Expose for dynamic inserts (e.g., from SxEngine or other JS)
  window.initPeekScroll = initPeek;
})();


// ============================================================================
// 4. Exclusive <details> behavior
//    - Only one <details> with the same [data-toggle-group] is open at a time
//    - Respects SxEngine swaps by re-attaching afterSettle
//    - Scrolls to top when opening a panel
// ============================================================================

/**
 * Attach behavior so that only one <details> in each data-toggle-group is open.
 * @param {ParentNode} root - Limit binding to within this node (defaults to document).
 */
function attachExclusiveDetailsBehavior(root = document) {
  const detailsList = root.querySelectorAll('details[data-toggle-group]');

  detailsList.forEach((el) => {
    // Prevent double-binding on the same element
    if (el.__exclusiveBound) return;
    el.__exclusiveBound = true;

    el.addEventListener('toggle', function () {
      // Only act when this <details> was just opened
      if (!el.open) return;

      const group = el.getAttribute('data-toggle-group');
      if (!group) return;

      // Close all other <details> with the same data-toggle-group
      document
        .querySelectorAll('details[data-toggle-group="' + group + '"]')
        .forEach((other) => {
          if (other === el) return;
          if (other.open) {
            other.open = false;
          }
        });

      // Scroll to top when a panel is opened
      window.scrollTo(0, 0);
    });
  });
}

// Initial binding on page load
attachExclusiveDetailsBehavior();

// Re-bind for new content after SxEngine swaps
document.body.addEventListener('sx:afterSettle', function (evt) {
  attachExclusiveDetailsBehavior(evt.detail?.target || document);
});


// ============================================================================
// 5. Close <details> panels before SxEngine requests
//    - When a link/button inside a <details[data-toggle-group]> triggers SxEngine,
//      we close that panel and scroll to top.
// ============================================================================

document.body.addEventListener('sx:beforeRequest', function (evt) {
  const triggerEl = evt.target;
  const panel = triggerEl.closest('details[data-toggle-group]');
  if (!panel) return;
  panel.open = false;
  window.scrollTo(0, 0);
});


// ============================================================================
// 6. Ghost / Koenig video card fix
//    - Ghost/Koenig editors may output <figure class="kg-video-card"><video>...</video></figure>
//    - This replaces the <figure> with just the <video>, and enforces some defaults.
//    - Works on initial load, SxEngine swaps, and general DOM inserts.
// ============================================================================

(function () {
  /**
   * Replace Ghost/Koenig video figures with their contained <video> element.
   * @param {ParentNode} root
   */
  function replaceKgFigures(root = document) {
    const figures = root.querySelectorAll('figure.kg-video-card');
    figures.forEach(fig => {
      const video = fig.querySelector('video');
      if (!video) return;

      // Ensure native UI & sensible defaults
      video.controls = true;
      video.preload = video.preload || 'metadata';
      video.playsInline = true; // iOS inline playback

      // Optional utility classes (Tailwind-esque)
      video.classList.add('max-w-full', 'h-auto', 'rounded-lg', 'shadow');

      // Replace the entire figure with the <video>
      const parent = fig.parentNode;
      if (!parent) return;
      parent.replaceChild(video, fig);
    });
  }

  // Initial run on DOM ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => replaceKgFigures());
  } else {
    replaceKgFigures();
  }

  // After SxEngine swaps, fix any new figures
  document.addEventListener('sx:afterSettle', e =>
    replaceKgFigures(e.detail?.target || document)
  );

  // Fallback: MutationObserver for other dynamic content inserts
  const mo = new MutationObserver(muts => {
    for (const m of muts) {
      for (const node of m.addedNodes) {
        if (node.nodeType !== 1) continue;

        if (node.matches?.('figure.kg-video-card')) {
          replaceKgFigures(node.parentNode || document);
        } else if (node.querySelector?.('figure.kg-video-card')) {
          replaceKgFigures(node);
        }
      }
    }
  });

  mo.observe(document.documentElement, { childList: true, subtree: true });
})();


// ============================================================================
// 7. Ghost / Koenig audio card fix
//    - Ghost may output <div class="kg-card kg-audio-card"><audio>...</audio></div>
//    - Replace wrapper with the raw <audio> tag and keep a class for styling.
// ============================================================================

document.addEventListener('DOMContentLoaded', () => {
  document
    .querySelectorAll('div.kg-card.kg-audio-card')
    .forEach(card => {
      const audio = card.querySelector('audio');
      if (!audio) return;

      // Ensure native audio controls are visible
      audio.setAttribute('controls', '');

      // Preserve a class for custom styling
      audio.classList.add('ghost-audio');

      // Replace the entire card element with the <audio>
      card.replaceWith(audio);
    });
});


// ============================================================================
// 8. Responsive YouTube iframes
//    - Targets iframes from youtube.com / youtube-nocookie.com
//    - Removes width/height attributes so CSS can take over
//    - Applies aspect-ratio if supported, else JS resize fallback
//    - Works on initial load + after SxEngine swaps
// ============================================================================

(function () {
  /**
   * Normalize and make YouTube iframes responsive.
   * @param {ParentNode} root
   */
  function fixYouTubeIframes(root = document) {
    const iframes = root.querySelectorAll(
      'iframe[src*="youtube.com"], iframe[src*="youtube-nocookie.com"]'
    );

    iframes.forEach(ifr => {
      const w = parseFloat(ifr.getAttribute('width'))  || 560;
      const h = parseFloat(ifr.getAttribute('height')) || 315;

      // Remove intrinsic sizes so CSS can control them
      ifr.removeAttribute('width');
      ifr.removeAttribute('height');

      // Make full-width by default
      ifr.style.display = 'block';
      ifr.style.width   = '100%';
      ifr.style.border  = '0';

      // Prefer modern CSS aspect-ratio if supported
      if (CSS.supports('aspect-ratio', '1 / 1')) {
        ifr.style.aspectRatio = `${w} / ${h}`;
        ifr.style.height = 'auto';
      } else {
        // Legacy fallback: compute height on window resize
        const setH = () => {
          ifr.style.height = (ifr.clientWidth * h / w) + 'px';
        };
        setH();
        window.addEventListener('resize', setH, { passive: true });
      }
    });
  }

  // Run on initial load
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => fixYouTubeIframes());
  } else {
    fixYouTubeIframes();
  }

  // Run after SxEngine swaps
  document.addEventListener('sx:afterSettle', e =>
    fixYouTubeIframes(e.detail?.target || document)
  );
})();


// ============================================================================
// 9. SxEngine global error handler (SweetAlert2)
//    - Listens for sx:responseError events
//    - Extracts error info from JSON or HTML responses
//    - Shows a SweetAlert error modal with details
// ============================================================================

// SxEngine global error handler
document.body.addEventListener("sx:responseError", function (event) {
  var resp = event.detail.response;
  if (!resp) return;
  // Don't show error modal when sx-retry will handle the failure
  var triggerEl = event.target;
  if (triggerEl && triggerEl.getAttribute("sx-retry")) return;
  var status = resp.status || 0;
  var form = triggerEl ? triggerEl.closest("form") : null;

  var title = "Something went wrong";
  if (status >= 500) title = "Server error";
  else if (status >= 400) title = "There was a problem with your request";

  resp.text().then(function (text) {
    var contentType = resp.headers.get("Content-Type") || "";
    var message = "";
    var html = "";
    var fieldErrors = null;

    if (contentType.includes("application/json")) {
      try {
        var data = JSON.parse(text || "{}");
        message = data.message || "";
        if (data.errors && typeof data.errors === "object" && !Array.isArray(data.errors)) {
          fieldErrors = data.errors;
          var allMessages = [];
          for (var field in data.errors) {
            var arr = Array.isArray(data.errors[field]) ? data.errors[field] : [data.errors[field]];
            allMessages.push.apply(allMessages, arr);
          }
          if (allMessages.length) {
            html = "<ul style='text-align:left;margin:0;padding-left:1.25rem;'>" +
              allMessages.map(function (e) { return "<li>" + e + "</li>"; }).join("") + "</ul>";
          }
        } else if (Array.isArray(data.errors)) {
          html = "<ul style='text-align:left;margin:0;padding-left:1.25rem;'>" +
            data.errors.map(function (e) { return "<li>" + e + "</li>"; }).join("") + "</ul>";
        } else if (data.error) {
          html = data.error;
        }
      } catch (e) { html = text; }
    } else {
      html = text;
    }

    if (form && fieldErrors) {
      form.querySelectorAll(".field-error").forEach(function (el) {
        el.classList.remove("field-error", "border-red-500", "ring-1", "ring-red-500");
        el.removeAttribute("aria-invalid");
      });
      var firstErrorInput = null;
      for (var fld in fieldErrors) {
        if (fld === "__all__" || fld === "_global") continue;
        if (fld === "days") {
          var group = form.querySelector("[data-days-group]");
          if (group) {
            group.classList.add("field-error", "border", "border-red-500", "rounded", "ring-1", "ring-red-500");
            if (!firstErrorInput) { var cb = group.querySelector('input[type="checkbox"]'); if (cb) firstErrorInput = cb; }
          }
          continue;
        }
        var input = form.querySelector('[name="' + fld + '"]');
        if (!input) continue;
        input.classList.add("field-error", "border-red-500", "ring-1", "ring-red-500");
        input.setAttribute("aria-invalid", "true");
        if (!firstErrorInput) firstErrorInput = input;
      }
      if (firstErrorInput) {
        firstErrorInput.scrollIntoView({ behavior: "smooth", block: "center" });
        firstErrorInput.focus({ preventScroll: true });
      }
    }

    if (typeof Swal !== "undefined") {
      Swal.fire({ icon: "error", title: message || title, html: html || "Please correct the highlighted fields and try again." });
    }
  }).catch(function () {});
});



document.addEventListener('toggle', function (event) {
  const details = event.target;
  // Only act on <details> elements that were just opened
  if (details.tagName !== 'DETAILS' || !details.open) return;

  // Close any child <details> that are open
  details.querySelectorAll('details[open]').forEach(function (child) {
    if (child !== details) {
      child.open = false;
    }
  });
}, true); //  <-- capture phase


document.body.addEventListener('click', function (e) {
  const btn = e.target.closest('[data-confirm]');
  if (!btn) return;

  const form = btn.closest('form');

  // If this is a submit button inside a form, run HTML5 validation *first*.
  // If invalid, let the browser show messages and DO NOT open SweetAlert.
  if (btn.type === 'submit' && form) {
    if (!form.checkValidity()) {
      // This shows the native validation bubbles and focuses the first invalid field
      form.reportValidity();
      return; // stop here – no preventDefault, no Swal
    }
  }

  // At this point the form is valid (or this isn't a submit button).
  // Now we show the confirmation dialog.
  e.preventDefault();

  const title =
    btn.getAttribute('data-confirm-title') || 'Are you sure?';
  const text =
    btn.getAttribute('data-confirm-text') || '';
  const icon =
    btn.getAttribute('data-confirm-icon') || 'warning';
  const confirmButtonText =
    btn.getAttribute('data-confirm-confirm-text') || 'Yes';
  const cancelButtonText =
    btn.getAttribute('data-confirm-cancel-text') || 'Cancel';

  Swal.fire({
    title,
    text,
    icon,
    showCancelButton: true,
    confirmButtonText,
    cancelButtonText,
  }).then((result) => {
    if (!result.isConfirmed) return;

    const eventName = btn.getAttribute('data-confirm-event');

    if (eventName) {
      // Fire a custom event (e.g. "confirmed") for sx-trigger
      btn.dispatchEvent(new CustomEvent(eventName, { bubbles: true }));
      return;
    }

    if (btn.type === 'submit') {
        const form = btn.closest('form');
        if (form) {
            if (typeof form.requestSubmit === 'function') {
                form.requestSubmit(btn);   // proper submit
            } else {
                const ev = new Event('submit', { bubbles: true, cancelable: true });
                form.dispatchEvent(ev);
            }
        }
        return;
    }

    // If it's not a submit button, allow original action
    btn.dispatchEvent(new Event('confirmed', { bubbles: true }));
  });
});


  

    document.addEventListener('change', function (event) {
    const target = event.target;

    // Only care about checkboxes
    if (target.type !== 'checkbox') return;

    // Find nearest days group
    const group = target.closest('[data-days-group]');
    if (!group) return;

    const allToggle = group.querySelector('[data-day-all]');
    const dayCheckboxes = group.querySelectorAll('input[type="checkbox"][data-day]');

    // If the "All" checkbox was toggled
    if (target === allToggle) {
      const checked = allToggle.checked;
      dayCheckboxes.forEach(cb => {
        cb.checked = checked;
      });
      return;
    }

    // Otherwise an individual day changed:
    // if *all* days are checked, tick "All", else untick it
    const allChecked = Array.from(dayCheckboxes).every(cb => cb.checked);
    if (allToggle) {
      allToggle.checked = allChecked;
    }
  });




// ============================================================================
// Client-side route nav selection
//    - Updates aria-selected on sub-nav links after client-side routing
//    - Scoped to <nav> elements (top-level section links are outside <nav>)
// ============================================================================

document.body.addEventListener('sx:clientRoute', function (e) {
  var pathname = e.detail && e.detail.pathname;
  if (!pathname) return;
  // Deselect all sub-nav links (inside <nav> elements)
  document.querySelectorAll('nav a[aria-selected]').forEach(function (a) {
    a.setAttribute('aria-selected', 'false');
  });
  // Select the matching link
  document.querySelectorAll('nav a[href="' + pathname + '"]').forEach(function (a) {
    a.setAttribute('aria-selected', 'true');
  });
});


// ============================================================================
// Scrolling menu arrow visibility (replaces hyperscript scroll/load handlers)
// Elements with data-scroll-arrows="arrow-class" show/hide arrows on overflow.
// ============================================================================

(function () {
  function updateArrows(container) {
    var arrowClass = container.getAttribute('data-scroll-arrows');
    if (!arrowClass) return;
    var arrows = document.querySelectorAll('.' + arrowClass);
    if (window.innerWidth >= 640 && container.scrollWidth > container.clientWidth) {
      arrows.forEach(function (a) { a.classList.remove('hidden'); a.classList.add('flex'); });
    } else {
      arrows.forEach(function (a) { a.classList.add('hidden'); a.classList.remove('flex'); });
    }
  }

  function initScrollArrows(root) {
    (root || document).querySelectorAll('[data-scroll-arrows]').forEach(function (el) {
      if (el._scrollArrowsBound) return;
      el._scrollArrowsBound = true;
      el.addEventListener('scroll', function () { updateArrows(el); }, { passive: true });
      updateArrows(el);
    });
  }

  document.addEventListener('DOMContentLoaded', function () { initScrollArrows(); });
  window.addEventListener('load', function () {
    document.querySelectorAll('[data-scroll-arrows]').forEach(updateArrows);
  });
  document.addEventListener('sx:afterSettle', function (e) { initScrollArrows(e.detail?.target); });
})();


