/**
 * Layout Drawer Resizer
 *
 * Handles resizing of left and right drawers with drag functionality.
 * Communicates with server via HTMX to persist width changes.
 */

/**
 * Initialize drawer resizer functionality for a specific layout instance
 *
 * @param {string} layoutId - The ID of the layout instance to initialize
 */
function initLayoutResizer(layoutId) {
  'use strict';

  const MIN_WIDTH = 150;
  const MAX_WIDTH = 600;

  let isResizing = false;
  let currentResizer = null;
  let currentDrawer = null;
  let startX = 0;
  let startWidth = 0;
  let side = null;

  const layoutElement = document.getElementById(layoutId);

  if (!layoutElement) {
    console.error(`Layout element with ID "${layoutId}" not found`);
    return;
  }

  /**
   * Initialize resizer functionality for this layout instance
   */
  function initResizers() {
    const resizers = layoutElement.querySelectorAll('.mf-layout-resizer');

    resizers.forEach(resizer => {
      // Remove existing listener if any to avoid duplicates
      resizer.removeEventListener('mousedown', handleMouseDown);
      resizer.addEventListener('mousedown', handleMouseDown);
    });
  }

  /**
   * Handle mouse down event on resizer
   */
  function handleMouseDown(e) {
    e.preventDefault();

    currentResizer = e.target;
    side = currentResizer.dataset.side;
    currentDrawer = currentResizer.closest('.mf-layout-drawer');

    if (!currentDrawer) {
      console.error('Could not find drawer element');
      return;
    }

    isResizing = true;
    startX = e.clientX;
    startWidth = currentDrawer.offsetWidth;

    // Add event listeners for mouse move and up
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);

    // Add resizing class for visual feedback
    document.body.classList.add('mf-layout-resizing');
    currentDrawer.classList.add('mf-layout-drawer-resizing');
  }

  /**
   * Handle mouse move event during resize
   */
  function handleMouseMove(e) {
    if (!isResizing) return;

    e.preventDefault();

    let newWidth;

    if (side === 'left') {
      // Left drawer: increase width when moving right
      newWidth = startWidth + (e.clientX - startX);
    } else if (side === 'right') {
      // Right drawer: increase width when moving left
      newWidth = startWidth - (e.clientX - startX);
    }

    // Constrain width between min and max
    newWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, newWidth));

    // Update drawer width visually
    currentDrawer.style.width = `${newWidth}px`;
  }

  /**
   * Handle mouse up event - end resize and save to server
   */
  function handleMouseUp(e) {
    if (!isResizing) return;

    isResizing = false;

    // Remove event listeners
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);

    // Remove resizing classes
    document.body.classList.remove('mf-layout-resizing');
    currentDrawer.classList.remove('mf-layout-drawer-resizing');

    // Get final width
    const finalWidth = currentDrawer.offsetWidth;
    const commandId = currentResizer.dataset.commandId;

    if (!commandId) {
      console.error('No command ID found on resizer');
      return;
    }

    // Send width update to server
    saveDrawerWidth(commandId, finalWidth);

    // Reset state
    currentResizer = null;
    currentDrawer = null;
    side = null;
  }

  /**
   * Save drawer width to server via HTMX
   */
  function saveDrawerWidth(commandId, width) {
    htmx.ajax('POST', '/myfasthtml/commands', {
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      swap: "outerHTML",
      target: `#${currentDrawer.id}`,
      values: {
        c_id: commandId,
        width: width
      }
    });
  }

  // Initialize resizers
  initResizers();

  // Re-initialize after HTMX swaps within this layout
  layoutElement.addEventListener('htmx:afterSwap', function (event) {
    initResizers();
  });
}

function initBoundaries(elementId, updateUrl) {
  function updateBoundaries() {
    const container = document.getElementById(elementId);
    if (!container) {
      console.warn("initBoundaries : element " + elementId + " is not found !");
      return;
    }

    const rect = container.getBoundingClientRect();
    const width = Math.floor(rect.width);
    const height = Math.floor(rect.height);
    console.log("boundaries: ", rect)

    // Send boundaries to server
    htmx.ajax('POST', updateUrl, {
      target: '#' + elementId,
      swap: 'outerHTML',
      values: {width: width, height: height}
    });
  }

  // Debounce function
  let resizeTimeout;

  function debouncedUpdate() {
    clearTimeout(resizeTimeout);
    resizeTimeout = setTimeout(updateBoundaries, 250);
  }

  // Update on load
  setTimeout(updateBoundaries, 100);

  // Update on window resize
  const container = document.getElementById(elementId);
  container.addEventListener('resize', debouncedUpdate);

  // Cleanup on element removal
  if (container) {
    const observer = new MutationObserver(function (mutations) {
      mutations.forEach(function (mutation) {
        mutation.removedNodes.forEach(function (node) {
          if (node.id === elementId) {
            window.removeEventListener('resize', debouncedUpdate);
          }
        });
      });
    });
    observer.observe(container.parentNode, {childList: true});
  }
}

/**
 * Updates the tabs display by showing the active tab content and scrolling to make it visible.
 * This function is called when switching between tabs to update both the content visibility
 * and the tab button states.
 *
 * @param {string} controllerId - The ID of the tabs controller element (format: "{managerId}-controller")
 */
function updateTabs(controllerId) {
  const controller = document.getElementById(controllerId);
  if (!controller) {
    console.warn(`Controller ${controllerId} not found`);
    return;
  }

  const activeTabId = controller.dataset.activeTab;
  if (!activeTabId) {
    console.warn('No active tab ID found');
    return;
  }

  // Extract manager ID from controller ID (remove '-controller' suffix)
  const managerId = controllerId.replace('-controller', '');

  // Hide all tab contents for this manager
  const contentWrapper = document.getElementById(`${managerId}-content-wrapper`);
  if (contentWrapper) {
    contentWrapper.querySelectorAll('.mf-tab-content').forEach(content => {
      content.classList.add('hidden');
    });

    // Show the active tab content
    const activeContent = document.getElementById(`${managerId}-${activeTabId}-content`);
    if (activeContent) {
      activeContent.classList.remove('hidden');
    }
  }

  // Update active tab button styling
  const header = document.getElementById(`${managerId}-header`);
  if (header) {
    // Remove active class from all tabs
    header.querySelectorAll('.mf-tab-button').forEach(btn => {
      btn.classList.remove('mf-tab-active');
    });

    // Add active class to current tab
    const activeButton = header.querySelector(`[data-tab-id="${activeTabId}"]`);
    if (activeButton) {
      activeButton.classList.add('mf-tab-active');

      // Scroll to make active tab visible if needed
      activeButton.scrollIntoView({
        behavior: 'smooth',
        block: 'nearest',
        inline: 'nearest'
      });
    }
  }
}

/**
 * Create keyboard bindings
 */
(function() {
  /**
   * Global registry to store keyboard shortcuts for multiple elements
   */
  const KeyboardRegistry = {
    elements: new Map(), // elementId -> { trie, element }
    listenerAttached: false,
    currentKeys: new Set(),
    snapshotHistory: [],
    pendingTimeout: null,
    pendingMatches: [], // Array of matches waiting for timeout
    sequenceTimeout: 500 // 500ms timeout for sequences
  };

  /**
   * Normalize key names to lowercase for case-insensitive comparison
   * @param {string} key - The key to normalize
   * @returns {string} - Normalized key name
   */
  function normalizeKey(key) {
    const keyMap = {
      'control': 'ctrl',
      'escape': 'esc',
      'delete': 'del'
    };

    const normalized = key.toLowerCase();
    return keyMap[normalized] || normalized;
  }

  /**
   * Create a unique string key from a Set of keys for Map indexing
   * @param {Set} keySet - Set of normalized keys
   * @returns {string} - Sorted string representation
   */
  function setToKey(keySet) {
    return Array.from(keySet).sort().join('+');
  }

  /**
   * Parse a single element (can be a single key or a simultaneous combination)
   * @param {string} element - The element string (e.g., "a" or "Ctrl+C")
   * @returns {Set} - Set of normalized keys
   */
  function parseElement(element) {
    if (element.includes('+')) {
      // Simultaneous combination
      return new Set(element.split('+').map(k => normalizeKey(k.trim())));
    }
    // Single key
    return new Set([normalizeKey(element.trim())]);
  }

  /**
   * Parse a combination string into sequence elements
   * @param {string} combination - The combination string (e.g., "Ctrl+C C" or "A B C")
   * @returns {Array} - Array of Sets representing the sequence
   */
  function parseCombination(combination) {
    // Check if it's a sequence (contains space)
    if (combination.includes(' ')) {
      return combination.split(' ').map(el => parseElement(el.trim()));
    }

    // Single element (can be a key or simultaneous combination)
    return [parseElement(combination)];
  }

  /**
   * Create a new trie node
   * @returns {Object} - New trie node
   */
  function createTrieNode() {
    return {
      config: null,
      combinationStr: null,
      children: new Map()
    };
  }

  /**
   * Build a trie from combinations
   * @param {Object} combinations - Map of combination strings to HTMX config objects
   * @returns {Object} - Root trie node
   */
  function buildTrie(combinations) {
    const root = createTrieNode();

    for (const [combinationStr, config] of Object.entries(combinations)) {
      const sequence = parseCombination(combinationStr);
      let currentNode = root;

      for (const keySet of sequence) {
        const key = setToKey(keySet);

        if (!currentNode.children.has(key)) {
          currentNode.children.set(key, createTrieNode());
        }

        currentNode = currentNode.children.get(key);
      }

      // Mark as end of sequence and store config
      currentNode.config = config;
      currentNode.combinationStr = combinationStr;
    }

    return root;
  }

  /**
   * Traverse the trie with the current snapshot history
   * @param {Object} trieRoot - Root of the trie
   * @param {Array} snapshotHistory - Array of Sets representing pressed keys
   * @returns {Object|null} - Current node or null if no match
   */
  function traverseTrie(trieRoot, snapshotHistory) {
    let currentNode = trieRoot;

    for (const snapshot of snapshotHistory) {
      const key = setToKey(snapshot);

      if (!currentNode.children.has(key)) {
        return null;
      }

      currentNode = currentNode.children.get(key);
    }

    return currentNode;
  }

  /**
   * Check if we're inside an input element where typing should work normally
   * @returns {boolean} - True if inside an input-like element
   */
  function isInInputContext() {
    const activeElement = document.activeElement;
    if (!activeElement) return false;

    const tagName = activeElement.tagName.toLowerCase();

    // Check for input/textarea
    if (tagName === 'input' || tagName === 'textarea') {
      return true;
    }

    // Check for contenteditable
    if (activeElement.isContentEditable) {
      return true;
    }

    return false;
  }

  /**
   * Trigger an action for a matched combination
   * @param {string} elementId - ID of the element
   * @param {Object} config - HTMX configuration object
   * @param {string} combinationStr - The matched combination string
   */
  function triggerAction(elementId, config, combinationStr) {
    const element = document.getElementById(elementId);
    if (!element) return;

    const hasFocus = document.activeElement === element;

    // Extract HTTP method and URL from hx-* attributes
    let method = 'POST'; // default
    let url = null;

    const methodMap = {
      'hx-post': 'POST',
      'hx-get': 'GET',
      'hx-put': 'PUT',
      'hx-delete': 'DELETE',
      'hx-patch': 'PATCH'
    };

    for (const [attr, httpMethod] of Object.entries(methodMap)) {
      if (config[attr]) {
        method = httpMethod;
        url = config[attr];
        break;
      }
    }

    if (!url) {
      console.error('No HTTP method attribute found in config:', config);
      return;
    }

    // Build htmx.ajax options
    const htmxOptions = {};

    // Map hx-target to target
    if (config['hx-target']) {
      htmxOptions.target = config['hx-target'];
    }

    // Map hx-swap to swap
    if (config['hx-swap']) {
      htmxOptions.swap = config['hx-swap'];
    }

    // Map hx-vals to values and add combination and has_focus
    const values = {};
    if (config['hx-vals']) {
      Object.assign(values, config['hx-vals']);
    }
    values.combination = combinationStr;
    values.has_focus = hasFocus;
    htmxOptions.values = values;

    // Add any other hx-* attributes (like hx-headers, hx-select, etc.)
    for (const [key, value] of Object.entries(config)) {
      if (key.startsWith('hx-') && !['hx-post', 'hx-get', 'hx-put', 'hx-delete', 'hx-patch', 'hx-target', 'hx-swap', 'hx-vals'].includes(key)) {
        // Remove 'hx-' prefix and convert to camelCase
        const optionKey = key.substring(3).replace(/-([a-z])/g, (g) => g[1].toUpperCase());
        htmxOptions[optionKey] = value;
      }
    }

    // Make AJAX call with htmx
    htmx.ajax(method, url, htmxOptions);
  }

  /**
   * Handle keyboard events and trigger matching combinations
   * @param {KeyboardEvent} event - The keyboard event
   */
  function handleKeyboardEvent(event) {
    const key = normalizeKey(event.key);

    // Add key to current pressed keys
    KeyboardRegistry.currentKeys.add(key);

    // Create a snapshot of current keyboard state
    const snapshot = new Set(KeyboardRegistry.currentKeys);

    // Add snapshot to history
    KeyboardRegistry.snapshotHistory.push(snapshot);

    // Cancel any pending timeout
    if (KeyboardRegistry.pendingTimeout) {
      clearTimeout(KeyboardRegistry.pendingTimeout);
      KeyboardRegistry.pendingTimeout = null;
      KeyboardRegistry.pendingMatches = [];
    }

    // Collect match information for all elements
    const currentMatches = [];
    let anyHasLongerSequence = false;
    let foundAnyMatch = false;

    // Check all registered elements for matching combinations
    for (const [elementId, data] of KeyboardRegistry.elements) {
      const element = document.getElementById(elementId);
      if (!element) continue;

      const trieRoot = data.trie;

      // Traverse the trie with current snapshot history
      const currentNode = traverseTrie(trieRoot, KeyboardRegistry.snapshotHistory);

      if (!currentNode) {
        // No match in this trie, continue to next element
        continue;
      }

      // We found at least a partial match
      foundAnyMatch = true;

      // Check if we have a match (node has a URL)
      const hasMatch = currentNode.config !== null;

      // Check if there are longer sequences possible (node has children)
      const hasLongerSequences = currentNode.children.size > 0;

      // Track if ANY element has longer sequences possible
      if (hasLongerSequences) {
        anyHasLongerSequence = true;
      }

      // Collect matches
      if (hasMatch) {
        currentMatches.push({
          elementId: elementId,
          config: currentNode.config,
          combinationStr: currentNode.combinationStr
        });
      }
    }

    // Prevent default if we found any match and not in input context
    if (currentMatches.length > 0 && !isInInputContext()) {
      event.preventDefault();
    }

    // Decision logic based on matches and longer sequences
    if (currentMatches.length > 0 && !anyHasLongerSequence) {
      // We have matches and NO element has longer sequences possible
      // Trigger ALL matches immediately
      for (const match of currentMatches) {
        triggerAction(match.elementId, match.config, match.combinationStr);
      }

      // Clear history after triggering
      KeyboardRegistry.snapshotHistory = [];

    } else if (currentMatches.length > 0 && anyHasLongerSequence) {
      // We have matches but AT LEAST ONE element has longer sequences possible
      // Wait for timeout - ALL current matches will be triggered if timeout expires

      KeyboardRegistry.pendingMatches = currentMatches;

      KeyboardRegistry.pendingTimeout = setTimeout(() => {
        // Timeout expired, trigger ALL pending matches
        for (const match of KeyboardRegistry.pendingMatches) {
          triggerAction(match.elementId, match.config, match.combinationStr);
        }

        // Clear state
        KeyboardRegistry.snapshotHistory = [];
        KeyboardRegistry.pendingMatches = [];
        KeyboardRegistry.pendingTimeout = null;
      }, KeyboardRegistry.sequenceTimeout);

    } else if (currentMatches.length === 0 && anyHasLongerSequence) {
      // No matches yet but longer sequences are possible
      // Just wait, don't trigger anything

    } else {
      // No matches and no longer sequences possible
      // This is an invalid sequence - clear history
      KeyboardRegistry.snapshotHistory = [];
    }

    // If we found no match at all, clear the history
    // This handles invalid sequences like "A C" when only "A B" exists
    if (!foundAnyMatch) {
      KeyboardRegistry.snapshotHistory = [];
    }

    // Also clear history if it gets too long (prevent memory issues)
    if (KeyboardRegistry.snapshotHistory.length > 10) {
      KeyboardRegistry.snapshotHistory = [];
    }
  }

  /**
   * Handle keyup event to remove keys from current pressed keys
   * @param {KeyboardEvent} event - The keyboard event
   */
  function handleKeyUp(event) {
    const key = normalizeKey(event.key);
    KeyboardRegistry.currentKeys.delete(key);
  }

  /**
   * Attach the global keyboard event listener if not already attached
   */
  function attachGlobalListener() {
    if (!KeyboardRegistry.listenerAttached) {
      document.addEventListener('keydown', handleKeyboardEvent);
      document.addEventListener('keyup', handleKeyUp);
      KeyboardRegistry.listenerAttached = true;
    }
  }

  /**
   * Add keyboard support to an element
   * @param {string} elementId - The ID of the element
   * @param {string} combinationsJson - JSON string of combinations mapping
   */
  window.add_keyboard_support = function(elementId, combinationsJson) {
    // Parse the combinations JSON
    const combinations = JSON.parse(combinationsJson);

    // Build trie for this element
    const trie = buildTrie(combinations);

    // Get element reference
    const element = document.getElementById(elementId);

    // Add to registry
    KeyboardRegistry.elements.set(elementId, {
      trie: trie,
      element: element
    });

    // Attach global listener if not already attached
    attachGlobalListener();
  };
})();