/**
 * Copyright Aquera Inc 2026
 *
 * This source code is licensed under the BSD-3-Clause license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { html, CSSResultArray, TemplateResult, css } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import NileElement from '../internal/nile-element';
import type { NileContextMenuItem } from '../nile-context-menu-item';
import '../nile-floating-panel';
import type { NileFloatingPanel } from '../nile-floating-panel';

const ITEM_TAG = 'nile-context-menu-item';
const SUBMENU_TAG = 'nile-context-submenu';
const HOVER_OPEN_DELAY = 100;
const HOVER_CLOSE_DELAY = 300;

let submenuIdSeq = 0;

/**
 * Nested submenu inside a `nile-context-menu-item`.
 * @tag nile-context-submenu
 */
@customElement('nile-context-submenu')
export class NileContextSubmenu extends NileElement {
  public static get styles(): CSSResultArray {
    return [
      css`
        :host { display: contents; }
      `,
    ];
  }

  @property({ attribute: true, type: Number, reflect: true }) zIndex = 9999;

  @state() private _open = false;

  public static openStack: NileContextSubmenu[] = [];

  private _parentItem: NileContextMenuItem | null = null;
  private _proxyId = `nile-context-submenu-anchor-${++submenuIdSeq}`;
  private _proxyEl?: HTMLDivElement;
  private _floatingPanelEl?: NileFloatingPanel;
  private _menuContainerRef: HTMLDivElement | null = null;
  private _openTimer?: number;
  private _closeTimer?: number;
  private _setupDone = false;

  public override connectedCallback(): void {
    super.connectedCallback();
    let p: Element | null = this.parentElement;
    while (p && p.tagName.toLowerCase() !== ITEM_TAG) p = p.parentElement;
    this._parentItem = p as NileContextMenuItem | null;
    this._ensureProxy();
    if (this._parentItem) {
      this._parentItem.setSubmenuExpanded?.(false);
      this._parentItem.addEventListener('mouseenter', this._onParentEnter);
      this._parentItem.addEventListener('mouseleave', this._onParentLeave);
      this._parentItem.addEventListener('click', this._onParentClick, true);
    }
    this._ensureBodyPanel();
  }

  private _ensureProxy(): void {
    if (this._proxyEl) return;
    const el = document.createElement('div');
    el.id = this._proxyId;
    el.setAttribute('aria-hidden', 'true');
    el.style.cssText =
      'position: fixed; top: 0; left: 0; width: 0; height: 0; pointer-events: none;';
    document.body.appendChild(el);
    this._proxyEl = el;
  }

  private _syncProxyToParent(): void {
    if (!this._proxyEl || !this._parentItem) return;
    const rect = this._parentItem.getBoundingClientRect();
    this._proxyEl.style.left = `${rect.left}px`;
    this._proxyEl.style.top = `${rect.top}px`;
    this._proxyEl.style.width = `${rect.width}px`;
    this._proxyEl.style.height = `${rect.height}px`;
  }

  public override disconnectedCallback(): void {
    super.disconnectedCallback();
    if (this._parentItem) {
      this._parentItem.removeEventListener('mouseenter', this._onParentEnter);
      this._parentItem.removeEventListener('mouseleave', this._onParentLeave);
      this._parentItem.removeEventListener('click', this._onParentClick, true);
    }
    this._clearTimers();
    if (this._open) this.closeSubmenu();
    this._teardownBodyArtifacts();
    NileContextSubmenu.openStack = NileContextSubmenu.openStack.filter(s => s !== this);
  }

  private _teardownBodyArtifacts(): void {
    const menu = this._menuContainerRef;
    if (menu) {
      menu.removeEventListener('click', this._onMenuClick);
      menu.removeEventListener('mouseover', this._onMenuMouseOver);
      menu.removeEventListener('mouseenter', this._onPanelEnter);
      menu.removeEventListener('mouseleave', this._onPanelLeave);
      menu.removeEventListener('nile-change', this._onNestedSelect as EventListener);
      while (menu.firstChild) this.appendChild(menu.firstChild);
    }
    this._floatingPanelEl?.remove();
    this._floatingPanelEl = undefined;
    this._menuContainerRef = null;
    this._setupDone = false;
    this._proxyEl?.remove();
    this._proxyEl = undefined;
  }

  private _ensureBodyPanel(): void {
    if (this._setupDone) return;
    const fp = document.createElement('nile-floating-panel') as NileFloatingPanel;
    fp.setAttribute('for', this._proxyId);
    fp.setAttribute('trigger', 'manual');
    fp.setAttribute('placement', 'right-start');
    fp.panelClass = 'nile-context-menu-panel';
    fp.zIndex = this.zIndex;
    fp.interactive = true;
    fp.hideOnClick = false;
    fp.closeOnEscape = false;
    (fp as unknown as { arrow: 'none' }).arrow = 'none';
    fp.distance = 0;

    const menu = document.createElement('div');
    menu.className = 'nile-context-menu__menu';
    menu.setAttribute('role', 'menu');
    fp.appendChild(menu);

    document.body.appendChild(fp);
    this._floatingPanelEl = fp;
    this._menuContainerRef = menu;

    menu.addEventListener('click', this._onMenuClick);
    menu.addEventListener('mouseover', this._onMenuMouseOver);
    menu.addEventListener('mouseenter', this._onPanelEnter);
    menu.addEventListener('mouseleave', this._onPanelLeave);
    menu.addEventListener('nile-change', this._onNestedSelect as EventListener);

    this._relocateLightChildren();
    this._setupDone = true;
  }

  protected override firstUpdated(): void {
    this._relocateLightChildren();
  }

  private _relocateLightChildren(): void {
    if (!this._menuContainerRef) return;
    for (const kid of Array.from(this.children)) {
      this._menuContainerRef.appendChild(kid);
    }
  }

  private _clearTimers(): void {
    if (this._openTimer != null) { clearTimeout(this._openTimer); this._openTimer = undefined; }
    if (this._closeTimer != null) { clearTimeout(this._closeTimer); this._closeTimer = undefined; }
  }

  private _onParentEnter = (): void => {
    if (this._parentItem?.disabled) return;
    this._clearTimers();
    if (this._open) return;
    this._openTimer = window.setTimeout(() => this.openSubmenu(), HOVER_OPEN_DELAY);
  };

  private _onParentLeave = (): void => {
    if (this._openTimer != null) { clearTimeout(this._openTimer); this._openTimer = undefined; }
    if (this._open) {
      this._closeTimer = window.setTimeout(() => this.closeSubmenu(), HOVER_CLOSE_DELAY);
    }
  };

  private _onParentClick = (e: MouseEvent): void => {
    e.stopPropagation();
    if (this._parentItem?.disabled) return;
    this._clearTimers();
    if (this._open) this.closeSubmenu();
    else this.openSubmenu();
  };

  private _onPanelEnter = (): void => {
    this._clearTimers();
  };

  private _onPanelLeave = (): void => {
    if (this._open) {
      this._closeTimer = window.setTimeout(() => {
        const hasOpenDescendant = NileContextSubmenu.openStack.some(
          sub => sub !== this && this._menuContainerRef?.contains(sub),
        );
        if (hasOpenDescendant) return;
        this.closeSubmenu();
      }, HOVER_CLOSE_DELAY);
    }
  };

  public openSubmenu(): void {
    if (this._open) return;
    if (this._parentItem?.disabled) return;
    this._closeSiblingSubmenus();
    this._ensureProxy();
    this._ensureBodyPanel();
    this._syncProxyToParent();
    this._open = true;
    if (this._floatingPanelEl) this._floatingPanelEl.open = true;
    NileContextSubmenu.openStack.push(this);
    this._parentItem?.setSubmenuExpanded?.(true);
  }

  public closeSubmenu(): void {
    if (!this._open) return;
    for (const sub of [...NileContextSubmenu.openStack]) {
      if (sub !== this && this._menuContainerRef?.contains(sub)) {
        sub.closeSubmenu();
      }
    }
    this._open = false;
    if (this._floatingPanelEl) this._floatingPanelEl.open = false;
    NileContextSubmenu.openStack = NileContextSubmenu.openStack.filter(s => s !== this);
    this._clearTimers();
    this._parentItem?.setSubmenuExpanded?.(false);
  }

  public get isOpen(): boolean {
    return this._open;
  }

  public static findByContainer(container: Element): NileContextSubmenu | null {
    for (const sub of NileContextSubmenu.openStack) {
      if (sub._menuContainerRef === container) return sub;
    }
    return null;
  }

  public get parentItem(): NileContextMenuItem | null {
    return this._parentItem;
  }
  public focusFirstItem(): void {
    if (!this._menuContainerRef) return;
    const items = Array.from(
      this._menuContainerRef.querySelectorAll(ITEM_TAG)
    ) as NileContextMenuItem[];
    for (const item of items) {
      let cur: Element | null = item.parentElement;
      let inDeeperSub = false;
      while (cur && cur !== this._menuContainerRef) {
        if (cur.tagName.toLowerCase() === SUBMENU_TAG) { inDeeperSub = true; break; }
        cur = cur.parentElement;
      }
      if (inDeeperSub) continue;
      if (!item.disabled) { item.focus(); return; }
    }
  }

  private _closeSiblingSubmenus(): void {
    const myLevel = this._parentLevel();
    for (const sub of [...NileContextSubmenu.openStack]) {
      if (sub === this) continue;
      if (sub._parentLevel() === myLevel) sub.closeSubmenu();
    }
  }

  private _parentLevel(): Element | null {
    const item = this._parentItem;
    if (!item) return null;
    return item.parentElement?.closest('.nile-context-menu__menu') ?? null;
  }

  private _onMenuMouseOver = (e: MouseEvent): void => {
    const item = e.composedPath().find(
      n => n instanceof HTMLElement && n.tagName.toLowerCase() === ITEM_TAG
    ) as NileContextMenuItem | undefined;
    if (!item || item.disabled) return;
    item.focus();
  };

  private _onNestedSelect = (e: CustomEvent): void => {
    if (e.target === this) return;
    if (e.detail?.type !== 'click') return;
    this.emit('nile-change', e.detail);
  };

  private _onMenuClick = (e: MouseEvent): void => {
    const item = e.composedPath().find(
      n => n instanceof HTMLElement && n.tagName.toLowerCase() === ITEM_TAG
    ) as NileContextMenuItem | undefined;
    if (!item || item.disabled) return;
    if (item.querySelector(`:scope > ${SUBMENU_TAG}`)) return;
    const detail = {
      id: item.id,
      value: item.value,
      name: (item.textContent ?? '').trim(),
      target: null,
      originalEvent: e,
    };
    try {
      (item as unknown as { onSelect?: (d: typeof detail) => void }).onSelect?.(detail);
    } catch (err) {
      console.error('[nile-context-submenu] onSelect callback threw:', err);
    }
    this.emit('nile-change', { type: 'click', ...detail });
  };

  public render(): TemplateResult {
    return html``;
  }
}

export default NileContextSubmenu;

declare global {
  interface HTMLElementTagNameMap {
    'nile-context-submenu': NileContextSubmenu;
  }
}
