/**
 * Tree view keyboard navigation
 * Handles arrow key navigation, expand/collapse, selection, and checkbox toggling.
 */

const instances = new Map();

/**
 * Gets all visible tree items (role="treeitem") in DOM order within a container.
 * Only returns items that are actually visible (not inside collapsed groups).
 * @param {HTMLElement} container
 * @returns {HTMLElement[]}
 */
function getVisibleTreeItems(container) {
  if (!container) return [];
  const items = Array.from(container.querySelectorAll('[role="treeitem"]'));
  return items.filter(item => {
    // Check that all ancestor groups are visible (parent treeitem is expanded)
    let el = item.parentElement;
    while (el && el !== container) {
      if (el.getAttribute('role') === 'group') {
        const parentItem = el.parentElement;
        if (parentItem && parentItem.getAttribute('role') === 'treeitem') {
          if (parentItem.getAttribute('aria-expanded') !== 'true') {
            return false;
          }
        }
      }
      el = el.parentElement;
    }
    return true;
  });
}

/**
 * Gets the data-value attribute from a tree item element.
 * @param {HTMLElement} el
 * @returns {string|null}
 */
function getNodeValue(el) {
  return el ? el.getAttribute('data-value') : null;
}

/**
 * Checks if a tree item is disabled.
 * @param {HTMLElement} el
 * @returns {boolean}
 */
function isDisabled(el) {
  return el && el.getAttribute('aria-disabled') === 'true';
}

/**
 * Checks if the event target is an interactive element that should handle its own events.
 * @param {Event} e
 * @param {HTMLElement} treeItem - The closest treeitem ancestor
 * @returns {boolean}
 */
function isFromInteractiveElement(e, treeItem) {
  if (!(e.target instanceof Element)) return false;
  const interactiveSelectors = 'button:not([data-tree-toggle]):not([data-tree-checkbox]), a, input, textarea, select, [contenteditable="true"]';
  const interactive = e.target.closest(interactiveSelectors);
  // Only consider it interactive if it's inside the treeitem but not the treeitem itself
  return interactive != null && interactive !== treeItem && treeItem.contains(interactive);
}

/**
 * Finds the closest treeitem element from the event target.
 * @param {Event} e
 * @returns {HTMLElement|null}
 */
function getTreeItemFromEvent(e) {
  const target = e.target instanceof Element ? e.target : e.target.parentElement;
  return target ? target.closest('[role="treeitem"]') : null;
}

/**
 * Updates roving tabindex: sets tabindex="0" on the target item and
 * tabindex="-1" on all other visible items in the tree.
 * @param {HTMLElement} container
 * @param {HTMLElement} targetItem
 */
function updateRovingTabindex(container, targetItem) {
  const items = container.querySelectorAll('[role="treeitem"]');
  for (const item of items) {
    item.setAttribute('tabindex', item === targetItem ? '0' : '-1');
  }
}

/**
 * Initialize keyboard navigation for a tree view instance.
 * @param {HTMLElement} containerElement
 * @param {object} dotNetRef - DotNetObjectReference for C# callbacks
 * @param {string} instanceId - Unique instance ID
 */
export function initialize(containerElement, dotNetRef, instanceId) {
  if (!containerElement || !dotNetRef) return;

  const state = { containerElement, dotNetRef, instanceId };

  const focusTreeItem = (item) => {
    if (!item) return;
    updateRovingTabindex(containerElement, item);
    item.focus({ preventScroll: false });
  };

  const handleKeyDown = (e) => {
    const currentItem = getTreeItemFromEvent(e);
    if (!currentItem) return;

    // Don't intercept events from interactive descendants (buttons, links, inputs, etc.)
    if (isFromInteractiveElement(e, currentItem)) return;

    const value = getNodeValue(currentItem);
    if (!value) return;

    const items = getVisibleTreeItems(containerElement);
    const currentIndex = items.indexOf(currentItem);

    switch (e.key) {
      case 'ArrowDown': {
        e.preventDefault();
        // Move focus to next visible node
        for (let i = currentIndex + 1; i < items.length; i++) {
          if (!isDisabled(items[i])) {
            focusTreeItem(items[i]);
            break;
          }
        }
        break;
      }

      case 'ArrowUp': {
        e.preventDefault();
        // Move focus to previous visible node
        for (let i = currentIndex - 1; i >= 0; i--) {
          if (!isDisabled(items[i])) {
            focusTreeItem(items[i]);
            break;
          }
        }
        break;
      }

      case 'ArrowRight': {
        e.preventDefault();
        const isExpanded = currentItem.getAttribute('aria-expanded') === 'true';
        const hasChildren = currentItem.getAttribute('data-has-children') === 'true';

        if (hasChildren && !isExpanded) {
          // Expand the node
          dotNetRef.invokeMethodAsync('JsOnNodeExpand', value).catch(() => {});
        } else if (hasChildren && isExpanded) {
          // Move to first child
          const group = currentItem.querySelector('[role="group"]');
          if (group) {
            const firstChild = group.querySelector('[role="treeitem"]');
            if (firstChild && !isDisabled(firstChild)) {
              focusTreeItem(firstChild);
            }
          }
        }
        break;
      }

      case 'ArrowLeft': {
        e.preventDefault();
        const isExpanded = currentItem.getAttribute('aria-expanded') === 'true';

        if (isExpanded) {
          // Collapse the node
          dotNetRef.invokeMethodAsync('JsOnNodeCollapse', value).catch(() => {});
        } else {
          // Move to parent
          const group = currentItem.parentElement;
          if (group && group.getAttribute('role') === 'group') {
            const parentItem = group.parentElement;
            if (parentItem && parentItem.getAttribute('role') === 'treeitem' && !isDisabled(parentItem)) {
              focusTreeItem(parentItem);
            }
          }
        }
        break;
      }

      case 'Enter': {
        e.preventDefault();
        if (!isDisabled(currentItem)) {
          const hasChildren = currentItem.getAttribute('data-has-children') === 'true';
          dotNetRef.invokeMethodAsync('JsOnNodeActivate', value, hasChildren).catch(() => {});
        }
        break;
      }

      case ' ': {
        e.preventDefault();
        if (!isDisabled(currentItem)) {
          // If checkable, toggle checkbox; otherwise, activate/select
          const isCheckable = currentItem.getAttribute('aria-checked') !== null;
          if (isCheckable) {
            dotNetRef.invokeMethodAsync('JsOnNodeCheck', value).catch(() => {});
          } else {
            const hasChildren = currentItem.getAttribute('data-has-children') === 'true';
            dotNetRef.invokeMethodAsync('JsOnNodeActivate', value, hasChildren).catch(() => {});
          }
        }
        break;
      }

      case 'Home': {
        e.preventDefault();
        // Focus first visible node
        for (let i = 0; i < items.length; i++) {
          if (!isDisabled(items[i])) {
            focusTreeItem(items[i]);
            break;
          }
        }
        break;
      }

      case 'End': {
        e.preventDefault();
        // Focus last visible node
        for (let i = items.length - 1; i >= 0; i--) {
          if (!isDisabled(items[i])) {
            focusTreeItem(items[i]);
            break;
          }
        }
        break;
      }

      case '*': {
        e.preventDefault();
        // Expand all siblings of the focused node
        dotNetRef.invokeMethodAsync('JsOnExpandSiblings', value).catch(() => {});
        break;
      }

      default:
        return; // Don't stop propagation for unhandled keys
    }
  };

  // Handle click to select/toggle
  const handleClick = (e) => {
    const treeItem = getTreeItemFromEvent(e);
    if (!treeItem || isDisabled(treeItem)) return;

    const value = getNodeValue(treeItem);
    if (!value) return;

    // Normalize target to Element for closest() calls
    const clickTarget = e.target instanceof Element ? e.target : (e.target.parentElement || null);

    // Check if the click was on the expand toggle area
    const toggle = clickTarget ? clickTarget.closest('[data-tree-toggle]') : null;
    if (toggle) {
      const isExpanded = treeItem.getAttribute('aria-expanded') === 'true';
      if (isExpanded) {
        dotNetRef.invokeMethodAsync('JsOnNodeCollapse', value).catch(() => {});
      } else {
        dotNetRef.invokeMethodAsync('JsOnNodeExpand', value).catch(() => {});
      }
      return;
    }

    // Check if click was on a checkbox
    const checkbox = clickTarget ? clickTarget.closest('[data-tree-checkbox]') : null;
    if (checkbox) {
      dotNetRef.invokeMethodAsync('JsOnNodeCheck', value).catch(() => {});
      return;
    }

    // Don't steal focus from interactive elements (buttons, links, inputs, etc.)
    if (isFromInteractiveElement(e, treeItem)) return;

    // Otherwise, select the node
    const hasChildren = treeItem.getAttribute('data-has-children') === 'true';
    dotNetRef.invokeMethodAsync('JsOnNodeActivate', value, hasChildren).catch(() => {});

    // Make sure the clicked item gets focus and update roving tabindex
    focusTreeItem(treeItem);
  };

  containerElement.addEventListener('keydown', handleKeyDown);
  containerElement.addEventListener('click', handleClick);

  state.handleKeyDown = handleKeyDown;
  state.handleClick = handleClick;
  instances.set(instanceId, state);
}

/**
 * Dispose keyboard navigation for a tree view instance.
 * @param {string} instanceId
 */
export function dispose(instanceId) {
  const state = instances.get(instanceId);
  if (!state) return;

  if (state.containerElement) {
    state.containerElement.removeEventListener('keydown', state.handleKeyDown);
    state.containerElement.removeEventListener('click', state.handleClick);
  }

  instances.delete(instanceId);
}
