/**
 * Copyright Aquera Inc 2025
 *
 * 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, nothing, PropertyValues } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { styles } from './nile-inline-sidebar.css';
import NileElement from '../internal/nile-element';
import { drag } from '../internal/drag';
import { clamp } from '../internal/math';
import NileInlineSidebarItem from '../nile-inline-sidebar-item/nile-inline-sidebar-item';

/**
 * Nile inline sidebar component.
 *
 * @tag nile-inline-sidebar
 */
@customElement('nile-inline-sidebar')
export class NileInlineSidebar extends NileElement {
  @property({ type: Boolean, reflect: true }) collapsed = false;

  @property({ type: Boolean, reflect: true, attribute: true }) fixed = false;

  @property({ type: Boolean, reflect: true }) showTooltip = false;

  /** The side on which the sidebar is placed. */
  @property({ type: String, reflect: true }) placement: 'left' | 'right' = 'left';

  /** Enables a draggable resize handle on the sidebar's trailing edge. */
  @property({ type: Boolean, reflect: true }) resizable = false;

  /** Current sidebar width in pixels when resizable is enabled. */
  @property({ type: Number, attribute: true }) sidebarWidth = 216;

  /** Minimum sidebar width in pixels. */
  @property({ type: Number, attribute: true }) minSidebarWidth = 120;

  @property({ type: Boolean, attribute: true }) showToggleBtn = true;

  /** Maximum sidebar width in pixels. */
  @property({ type: Number, attribute: true}) maxSidebarWidth = 400;

  @property({ type: String, reflect: true, attribute: true }) variant?: 'minimal' | 'rich' = 'minimal';

  @state() private activeIndex: number = -1;

  @query('slot') private defaultSlot!: HTMLSlotElement;

  private mutationObserver?: MutationObserver;

  public static get styles(): CSSResultArray {
    return [styles];
  }

  private get sidebarItems(): HTMLElement[] {
    if (!this.defaultSlot) return [];
    const assigned = this.defaultSlot.assignedElements({ flatten: true });
    const items = assigned.flatMap(el =>
      el.tagName.toLowerCase() === 'nile-inline-sidebar-item'
        ? [el]
        : Array.from(el.querySelectorAll('nile-inline-sidebar-item'))
    );
    return items as HTMLElement[];
  }

  connectedCallback() {
    super.connectedCallback();
    this.addEventListener('nile-click', this.handleItemSelect);
    this.addEventListener('keydown', this.handleKeyDown);
    this.mutationObserver = new MutationObserver(() =>
      this.syncActiveFromItems()
    );
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.mutationObserver?.disconnect();
    this.removeEventListener('nile-click', this.handleItemSelect);
    this.removeEventListener('keydown', this.handleKeyDown);
  }

  firstUpdated() {
    this.observeSidebarItems();
    this.syncActiveFromItems();
    this.syncChildSidebarItemVariants();

    if (this.fixed) {
      this.collapsed = false;
    }
  }

  updated(changedProperties: PropertyValues) {
    super.updated(changedProperties);
    if (changedProperties.has('variant')) {
      this.syncChildSidebarItemVariants();
    }
  }

  /** Pushes sidebar `variant` onto each item (rich vs minimal) so layout stays in sync with slotted content. */
  private syncChildSidebarItemVariants() {
    const items = this.querySelectorAll(
      'nile-inline-sidebar-item',
    ) as NodeListOf<NileInlineSidebarItem>;
    const v = this.variant ?? 'minimal';
    items.forEach(item => {
      item.variant = v;
    });
  }

  private observeSidebarItems() {
    if (!this.sidebarItems?.length || !this.mutationObserver) return;
    this.sidebarItems.forEach(item =>
      this.mutationObserver!.observe(item, {
        attributes: true,
        attributeFilter: ['active'],
      })
    );
  }

  private syncActiveFromItems() {
    if (!this.sidebarItems?.length) return;
    const activeIndex = this.sidebarItems.findIndex(item =>
      item.hasAttribute('active')
    );
    if (activeIndex !== -1 && activeIndex !== this.activeIndex) {
      this.activeIndex = activeIndex;
      this.requestUpdate();
    }
    this.updateTabIndices();
  }

  
  private get focusableItems(): HTMLElement[] {
    return this.sidebarItems.filter(
      item => !item.hasAttribute('disabled')
    );
  }

  private updateTabIndices() {
    const items = this.sidebarItems;
    if (!items.length) return;

    const focusTarget =
      this.activeIndex >= 0 ? items[this.activeIndex] : null;

    items.forEach(item => {
      if (item.hasAttribute('disabled')) {
        item.setAttribute('tabindex', '-1');
      } else if (item === focusTarget) {
        item.setAttribute('tabindex', '0');
      } else if (!focusTarget && item === this.focusableItems[0]) {
        item.setAttribute('tabindex', '0');
      } else {
        item.setAttribute('tabindex', '-1');
      }
    });
  }

  private focusItem(item: HTMLElement) {
    const items = this.sidebarItems;
    items.forEach(el => el.setAttribute('tabindex', '-1'));
    item.setAttribute('tabindex', '0');
    item.focus();
  }

  private handleKeyDown = (event: KeyboardEvent) => {
    if (this.collapsed) return;

    const focusable = this.focusableItems;
    if (!focusable.length) return;

    const currentFocus = this.sidebarItems.find(
      item =>
        item === document.activeElement ||
        item === this.shadowRoot?.activeElement ||
        item.matches(':focus-within')
    );

    const currentIndex = currentFocus
      ? focusable.indexOf(currentFocus)
      : -1;

    let nextIndex: number | null = null;

    switch (event.key) {
      case 'ArrowDown':
        nextIndex =
          currentIndex < focusable.length - 1 ? currentIndex + 1 : 0;
        break;

      case 'ArrowUp':
        nextIndex =
          currentIndex > 0 ? currentIndex - 1 : focusable.length - 1;
        break;

      case 'Home':
        nextIndex = 0;
        break;

      case 'End':
        nextIndex = focusable.length - 1;
        break;

      case 'Enter':
      case ' ':
        return;

      default:
        return;
    }

    event.preventDefault();

    if (nextIndex !== null && focusable[nextIndex]) {
      this.focusItem(focusable[nextIndex]);
    }
  };

  private handleItemSelect = (event: CustomEvent) => {
    const selectedItem = event.detail.item as HTMLElement;
    const index = this.sidebarItems.indexOf(selectedItem);

    this.sidebarItems.forEach(
      (item, i) => ((item as any).active = i === index)
    );
    this.activeIndex = index;
    this.updateTabIndices();

    this.dispatchEvent(
      new CustomEvent('nile-change', {
        detail: { selectedItem, index },
        bubbles: true,
        composed: true,
      })
    );

    this.requestUpdate();
  };

  private toggleCollapse() {
    if (this.fixed) return;
    this.collapsed = !this.collapsed;

    this.dispatchEvent(
      new CustomEvent('nile-toggle', {
        detail: { collapsed: this.collapsed },
        bubbles: true,
        composed: true,
      })
    );
  }

  private handleMenuItemClick(index: number) {
    const selectedItem = this.sidebarItems[index];
    if (!selectedItem || selectedItem.hasAttribute('disabled')) return;

    this.sidebarItems.forEach(
      (item, i) => ((item as any).active = i === index)
    );
    this.activeIndex = index;
    const text = selectedItem.textContent?.trim() || '';
    const href = (selectedItem as any).href;
    this.dispatchEvent(
      new CustomEvent('nile-click', {
        detail: { item: selectedItem, href, text },
        bubbles: true,
        composed: true,
      })
    );

    if (href) window.location.href = href;

    this.requestUpdate();
  }

  private handleSeparatorDrag(event: PointerEvent) {
    if (!this.resizable || this.collapsed) return;
    if (event.cancelable) event.preventDefault();

    const isRight = this.placement === 'right';

    this.style.setProperty('--sidebar-transition', 'none');

    drag(this, {
      onMove: (x: number) => {
        const hostRect = this.getBoundingClientRect();
        let newWidth: number;

        if (isRight) {
          newWidth = hostRect.width - x;
        } else {
          newWidth = x;
        }

        this.sidebarWidth = clamp(newWidth, this.minSidebarWidth, this.maxSidebarWidth);
        this.emit('nile-sidebar-resize', { width: this.sidebarWidth });
      },
      onStop: () => {
        this.style.removeProperty('--sidebar-transition');
      },
      initialEvent: event,
    });
  }

  private handleSeparatorKeyDown(event: KeyboardEvent) {
    if (!this.resizable || this.collapsed) return;

    const step = event.shiftKey ? 10 : 1;
    let newWidth = this.sidebarWidth;

    switch (event.key) {
      case 'ArrowLeft':
        newWidth -= step;
        break;
      case 'ArrowRight':
        newWidth += step;
        break;
      case 'Home':
        newWidth = this.minSidebarWidth;
        break;
      case 'End':
        newWidth = this.maxSidebarWidth;
        break;
      default:
        return;
    }

    event.preventDefault();
    this.sidebarWidth = clamp(newWidth, this.minSidebarWidth, this.maxSidebarWidth);
    this.emit('nile-sidebar-resize', { width: this.sidebarWidth });
  }

  private get separatorTemplate() {
    if (!this.resizable || this.collapsed) return nothing;

    return html`
      <div
        class="separator"
        part="separator"
        tabindex="0"
        role="separator"
        aria-valuenow=${this.sidebarWidth}
        aria-valuemin=${this.minSidebarWidth}
        aria-valuemax=${this.maxSidebarWidth}
        aria-label="Resize sidebar"
        @keydown=${this.handleSeparatorKeyDown}
        @mousedown=${this.handleSeparatorDrag}
        @touchstart=${this.handleSeparatorDrag}
      ></div>
    `;
  }

  private get menuItemsTemplate() {
    if (!this.sidebarItems?.length) return null;

    const tooltipPlacement = this.placement === 'right' ? 'left' : 'right';

    return this.sidebarItems.map((item, index) => {
      const shouldShowTooltip = (item as any).tooltip;
      const isTruncated =
        item.scrollWidth > item.clientWidth ||
        item.scrollHeight > item.clientHeight;
  
      const content = html`
        <nile-side-bar-action-menu-item
          ?disabled=${item.hasAttribute('disabled')}
          ?active=${this.activeIndex === index}
          @click=${() => this.handleMenuItemClick(index)}
        >
          ${item.textContent}
        </nile-side-bar-action-menu-item>
      `;
      return shouldShowTooltip || isTruncated
        ? html`
            <nile-tooltip
              content=${item.textContent?.trim() || ''}
              hoist
              placement=${tooltipPlacement}
            >
              ${content}
            </nile-tooltip>
          `
        : content;
    });
  }
  
  
  

  public render(): TemplateResult {
    const isRight = this.placement === 'right';
    const collapsedIcon = isRight ? 'menu_open' : 'menu_close';
    const expandedIcon = isRight ? 'menu_close' : 'menu_open';
    const actionPlacement = isRight ? 'bottom-end' : 'bottom-start';

    const sidebarStyle = !this.collapsed
      ? `width: ${this.sidebarWidth}px;`
      : '';

    return html`
      <div class="sidebar" part="base" style=${sidebarStyle}>
        <div class="sidebar-header" part="header">
          ${!this.fixed
            ? this.collapsed
              ? html`
                  <nile-side-bar-action
                    class="toggle-action"
                    placement=${actionPlacement}
                    hoist
                    open
                    triggerDropdown="hover"
                  >
                    ${this.showToggleBtn
                      ? html`
                          <nile-button
                            slot="trigger"
                            class="toggle-btn"
                            variant="tertiary"
                            @click=${this.toggleCollapse}
                          >
                            <nile-icon name=${collapsedIcon}></nile-icon>
                          </nile-button>
                        `
                      : nothing}
                    <nile-side-bar-action-menu>
                      ${this.menuItemsTemplate}
                    </nile-side-bar-action-menu>
                  </nile-side-bar-action>
                `
              : html`
                ${this.showToggleBtn
                  ? html` 
                    <nile-button
                      class="toggle-btn"
                      variant="tertiary"
                      @click=${this.toggleCollapse}
                    >
                      <nile-icon name=${expandedIcon}></nile-icon>
                    </nile-button>
                  `
                  : nothing}
                `
            : null}
        </div>

        <nav
          class=${classMap({ 'sidebar-nav': true, 'sidebar-nav--rich': this.variant === 'rich' })}
          role="navigation"
          aria-label="Sidebar"
          style=${this.collapsed ? 'display:none;' : ''}
          part="nav-items"
        >
          <slot
            @slotchange=${() => {
              this.requestUpdate();
              this.observeSidebarItems();
              this.syncActiveFromItems();
              this.syncChildSidebarItemVariants();
            }}
          ></slot>
        </nav>
      </div>
      ${this.separatorTemplate}
    `;
  }
}

export default NileInlineSidebar;

declare global {
  interface HTMLElementTagNameMap {
    'nile-inline-sidebar': NileInlineSidebar;
  }
}
