/*
 * ToolBajar - global dropdown hover behaviour.
 *
 * Loaded by every page that ships a header dropdown. Handles all header
 * dropdown variants used across the site:
 *   .dropdown-parent + .dropdown               (PDF / image tool pages)
 *   .dropdown-parent + .more-tools-dropdown    (image / SEO / social / utility)
 *   .group         + .dropdown-menu            (index.html, parts/public-header.php)
 *
 * Guarantees:
 *   - a panel stays open while the pointer is over the trigger OR the panel
 *   - minimum 1000 ms open time before a close may happen
 *   - 400 ms close delay, cancelled as soon as the pointer is back inside
 *   - the visual gap between trigger and panel is made hoverable
 *   - legacy inline `mouseleave` handlers that closed panels instantly are
 *     neutralised, so they can no longer fight the timers
 *   - touch/keyboard behaviour is untouched: everything is scoped to
 *     `(hover: hover) and (pointer: fine)` and to panels that are actually
 *     displayed, so click-driven mobile menus keep working unchanged.
 */
(() => {
  'use strict';

  const MIN_OPEN_MS = 1000;
  const CLOSE_DELAY_MS = 400;
  const STYLE_ID = 'tb-dropdown-hover-fix';

  const PANEL_SELECTOR = '.dropdown, .more-tools-dropdown, .dropdown-menu';
  const ROOT_SELECTOR = '.dropdown-parent, .nav-item';
  // Mobile drawers / generated menus / footers are click driven, not hover driven.
  const SKIP_SCOPE = '#mobile-menu, #mobileMenu, .mobile-menu, .tb-mobile-generated-menu, footer';

  const hoverCapable = window.matchMedia('(hover: hover) and (pointer: fine)');

  // root element -> state
  const states = new Map();

  function closestRoot(node) {
    if (!(node instanceof Element)) return null;
    return node.closest('[data-tb-hover-root]');
  }

  function triggerOf(root, panel) {
    const selector = 'button, [aria-haspopup="true"], a[href], [role="button"]';
    const children = root.children;
    for (let i = 0; i < children.length; i += 1) {
      const child = children[i];
      if (child === panel) continue;
      if (child.matches(selector)) return child;
      const nested = child.querySelector(selector);
      if (nested) return nested;
    }
    const loose = root.querySelector(selector);
    return loose && !panel.contains(loose) ? loose : null;
  }

  // Hover handling only makes sense while the panel is actually rendered.
  // Below the mobile breakpoint these panels are `display: none` until the
  // trigger is tapped, which keeps the tap-to-open behaviour intact.
  function isHoverReady(state) {
    if (!hoverCapable.matches) return false;
    return getComputedStyle(state.panel).display !== 'none';
  }

  // Resolve the panel's laid-out top edge, ignoring the open/close transform
  // so the measurement is stable even while the 0.2s transition is running.
  function layoutTop(panel) {
    const rect = panel.getBoundingClientRect();
    const transform = getComputedStyle(panel).transform;
    if (!transform || transform === 'none') return rect.top;
    try {
      return rect.top - new DOMMatrixReadOnly(transform).m42;
    } catch (error) {
      return rect.top;
    }
  }

  // Publish the size of the dead zone between trigger and panel so the
  // injected `::before` bridge can cover it.
  function syncBridge(state) {
    const { panel, root } = state;
    const style = getComputedStyle(panel);
    if (style.display === 'none' || style.position === 'static') {
      panel.style.removeProperty('--tb-hover-gap');
      return;
    }
    const panelRect = panel.getBoundingClientRect();
    if (!panelRect.width && !panelRect.height) {
      panel.style.removeProperty('--tb-hover-gap');
      return;
    }
    const gap = Math.max(0, Math.round(layoutTop(panel) - root.getBoundingClientRect().bottom));
    panel.style.setProperty('--tb-hover-gap', gap + 'px');
  }

  function cancelClose(state) {
    if (state.timer) {
      window.clearTimeout(state.timer);
      state.timer = 0;
    }
  }

  function closeOthers(state) {
    states.forEach((other) => {
      if (other !== state && other.open) close(other);
    });
  }

  function open(state) {
    cancelClose(state);
    if (state.open) return;
    state.openedAt = Date.now();
    closeOthers(state);
    state.open = true;
    state.root.classList.add('open', 'tb-hover-open');
    if (state.trigger) state.trigger.setAttribute('aria-expanded', 'true');
    syncBridge(state);
  }

  function close(state) {
    cancelClose(state);
    if (!state.open) return;
    state.open = false;
    state.root.classList.remove('open', 'tb-hover-open');
    if (state.trigger) state.trigger.setAttribute('aria-expanded', 'false');
  }

  function scheduleClose(state) {
    if (!state.open) return;
    cancelClose(state);
    const remaining = MIN_OPEN_MS - (Date.now() - state.openedAt);
    state.timer = window.setTimeout(() => close(state), Math.max(CLOSE_DELAY_MS, remaining));
  }

  function closeAll() {
    states.forEach(close);
  }

  function register(root, panel) {
    root.setAttribute('data-tb-hover-root', 'true');
    panel.classList.add('tb-hover-panel');

    const trigger = triggerOf(root, panel);
    if (trigger && (trigger.tagName === 'BUTTON' || trigger.getAttribute('role') === 'button')) {
      if (!trigger.hasAttribute('aria-haspopup')) trigger.setAttribute('aria-haspopup', 'true');
      if (!trigger.hasAttribute('aria-expanded')) trigger.setAttribute('aria-expanded', 'false');
    }

    const state = { root, panel, trigger, timer: 0, openedAt: 0, open: false };
    states.set(root, state);
    syncBridge(state);
  }

  function scan() {
    document.querySelectorAll(PANEL_SELECTOR).forEach((panel) => {
      if (panel.closest(SKIP_SCOPE)) return;
      const root = panel.closest(ROOT_SELECTOR) || panel.parentElement;
      if (!root || states.has(root) || root.closest(SKIP_SCOPE)) return;
      register(root, panel);
    });
  }

  // --- injected stylesheet -------------------------------------------------
  // Appended last so it wins over the per-page inline <style> blocks.
  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;

    const style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = [
      '@media (hover: hover) and (pointer: fine) {',
      // Hover bridge: makes the gap between trigger and panel part of the
      // panel, so `:hover` survives while the cursor crosses it.
      '  [data-tb-hover-root] .tb-hover-panel::before {',
      '    content: "";',
      '    position: absolute;',
      '    left: 0;',
      '    right: 0;',
      '    top: calc(-1 * var(--tb-hover-gap, 0px));',
      '    height: var(--tb-hover-gap, 0px);',
      '    background: transparent;',
      '    pointer-events: auto;',
      '  }',
      '  [data-tb-hover-root] > .dropdown,',
      '  [data-tb-hover-root] > .more-tools-dropdown,',
      '  [data-tb-hover-root] > .dropdown-menu {',
      '    z-index: 100001;',
      '  }',
      // Keep the panel visible for the full timer window even if a legacy',
      '  inline handler strips `.open` early.',
      '  [data-tb-hover-root].tb-hover-open > .dropdown,',
      '  [data-tb-hover-root].tb-hover-open > .more-tools-dropdown {',
      '    opacity: 1 !important;',
      '    visibility: visible !important;',
      '    pointer-events: auto !important;',
      '    transform: translateX(-50%) translateY(0) !important;',
      '  }',
      '  [data-tb-hover-root].tb-hover-open > .dropdown-menu {',
      '    opacity: 1 !important;',
      '    visibility: visible !important;',
      '    pointer-events: auto !important;',
      '    transform: translateY(0) !important;',
      '  }',
      '  [data-tb-hover-root].tb-hover-open > .arrow,',
      '  [data-tb-hover-root].tb-hover-open > .dropdown-btn .arrow,',
      '  [data-tb-hover-root].tb-hover-open > a .arrow {',
      '    transform: rotate(180deg);',
      '  }',
      '}'
    ].join('\n');

    document.head.appendChild(style);
  }

  // --- delegated listeners -------------------------------------------------
  // A single capture-phase pair on `document` covers every dropdown, so no
  // per-element listeners are created and dynamic markup keeps working.
  document.addEventListener('mouseover', (event) => {
    const root = closestRoot(event.target);
    if (!root) return;
    const state = states.get(root);
    if (!state) return;
    if (state.open) {
      cancelClose(state);
      return;
    }
    if (isHoverReady(state)) open(state);
  }, true);

  document.addEventListener('mouseout', (event) => {
    const root = closestRoot(event.target);
    if (!root) return;
    const state = states.get(root);
    if (!state || !state.open) return;
    const next = event.relatedTarget;
    if (next instanceof Node && root.contains(next)) return;
    if (isHoverReady(state)) scheduleClose(state);
  }, true);

  // Page scripts attach `mouseleave` handlers to `.dropdown-parent` that remove
  // `.open` immediately, which is what makes panels vanish mid-travel. Those
  // listeners live on the element itself, so stopping propagation while the
  // event is still in the capture phase keeps them from ever running.
  document.addEventListener('mouseleave', (event) => {
    if (closestRoot(event.target)) event.stopPropagation();
  }, true);

  // Keyboard parity with the existing menus.
  document.addEventListener('focusin', (event) => {
    const root = closestRoot(event.target);
    if (!root) return;
    const state = states.get(root);
    if (state && !state.open && isHoverReady(state)) open(state);
  }, true);

  document.addEventListener('focusout', (event) => {
    const root = closestRoot(event.target);
    if (!root) return;
    const state = states.get(root);
    if (!state || !state.open) return;
    const next = event.relatedTarget;
    if (next instanceof Node && root.contains(next)) return;
    if (isHoverReady(state)) scheduleClose(state);
  }, true);

  document.addEventListener('click', (event) => {
    const inside = closestRoot(event.target);
    states.forEach((state) => {
      if (state.root !== inside) close(state);
    });
  }, true);

  document.addEventListener('keydown', (event) => {
    if (event.key !== 'Escape' && event.key !== 'Esc') return;
    closeAll();
  }, true);

  // Leaving hover-capable mode (tablet, touch) must not strand open panels.
  if (hoverCapable.addEventListener) {
    hoverCapable.addEventListener('change', () => {
      if (!hoverCapable.matches) closeAll();
    });
  } else if (hoverCapable.addListener) {
    hoverCapable.addListener(() => {
      if (!hoverCapable.matches) closeAll();
    });
  }

  let resizeTimer = 0;
  window.addEventListener('resize', () => {
    window.clearTimeout(resizeTimer);
    resizeTimer = window.setTimeout(() => {
      states.forEach((state) => {
        if (state.open) close(state);
        syncBridge(state);
      });
    }, 200);
  });

  function init() {
    injectStyles();
    scan();
  }

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

  window.addEventListener('load', scan, { once: true });
})();