import { html, TemplateResult, nothing } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { styles } from './nile-detail.css';
import NileElement from '../internal/nile-element';
import { watch } from '../internal/watch';
import {
  animateShow,
  animateHide,
  showDetail,
  hideDetail,
  handleSummaryClick,
  handleSummaryKeyDown,
} from './nile-detail.utils';
import '../nile-checkbox/index';
import '../nile-input/index';
import '../nile-link/index';
import '../nile-icon/index';
import '../nile-lite-tooltip/index';
import { VirtualizerController } from '@tanstack/lit-virtual';
import type { CSSResultGroup } from 'lit';

export type NileDetailVariant = 'default' | 'selection' | 'light' | 'select-light';

export interface NileDetailSelectionItem {
  value: string;
  label?: string;
  description?: string;
  prefix?: string;
  suffix?: string;
  disabled?: boolean;
  className?: string;
}

export interface NileDetailRenderItemConfig {
  getDisplayText: (item: any) => string;
  getValue?: (item: any) => string;
  getSearchText?: (item: any) => string;
  getDescription?: (item: any) => string;
  getPrefix?: (item: any) => string;
  getSuffix?: (item: any) => string;
  getDisabled?: (item: any) => boolean;
  getClassName?: (item: any) => string;
}

type SelectionItem = string | NileDetailSelectionItem | Record<string, any>;

const IS_BROWSER = typeof window !== 'undefined';

export interface NileDetailSelectionConfig {
  // Identity
  variant?: NileDetailVariant;
  heading?: string;
  description?: string;
  itemsLabel?: string;

  // Items / selection
  items?: SelectionItem[];
  selected?: string[];
  renderItemConfig?: NileDetailRenderItemConfig;
  allowHtmlLabel?: boolean;

  // Layout
  layout?: {
    orientation?: 'horizontal' | 'vertical' | 'both';
    gridRows?: number;
    gridColumns?: number;
    matrixColumns?: number;
    minColumnWidth?: string;
    laneHeight?: number;
    maxHeight?: string;
    expandIconPlacement?: 'left' | 'right';
  };

  // Search
  search?: {
    placeholder?: string;
    disableLocal?: boolean;
  };

  // Virtualization
  virtualization?: {
    enabled?: boolean;
    threshold?: number;
    overscan?: number;
  };

  // Pagination / infinite scroll
  pagination?: {
    totalCount?: number;
    pageSize?: number;
    fetchPage?: (offset: number, limit: number) => Promise<SelectionItem[]>;
    placeholderLabel?: string;
  };

  // Action labels
  labels?: {
    restore?: string;
    clear?: string;
    selectedOnly?: string;
    showAll?: string;
  };

  showSelectedToggle?: boolean;

  // Inherited / passthrough
  open?: boolean;
  disabled?: boolean;
}

@customElement('nile-detail')
export class NileDetail extends NileElement {

  static styles: CSSResultGroup = styles;

  @query('details') detail: HTMLDetailsElement;
  @query('summary') header: HTMLElement;
  @query('.detail__body') body: HTMLElement;

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

  @property({ attribute: true, type: String, reflect: true }) heading = '';

  @property({ attribute: true, type: String, reflect: true }) description = '';

  @property({ attribute: true, reflect: true }) expandIconPlacement: 'left' | 'right' = 'right';

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

  @property({ attribute: true, type: String, reflect: true }) variant: NileDetailVariant = 'default';

  @property({ attribute: false, type: Array }) items: SelectionItem[] = [];

  @property({ attribute: false, type: Array }) selected: string[] = [];

  @property({ attribute: false }) renderItemConfig?: NileDetailRenderItemConfig;

  @property({ attribute: 'allow-html-label', type: Boolean }) allowHtmlLabel = false;

  @property({ attribute: 'disable-local-search', type: Boolean }) disableLocalSearch = false;

  @property({ attribute: 'total-count', type: Number }) totalCount?: number;

  @property({ attribute: 'page-size', type: Number }) pageSize = 200;

  @property({ attribute: false }) fetchPage?: (offset: number, limit: number) => Promise<SelectionItem[]>;

  @property({ attribute: 'placeholder-label', type: String }) placeholderLabel = 'Loading…';

  @property({ attribute: false }) config?: NileDetailSelectionConfig;

  @property({ attribute: 'search-placeholder', type: String }) searchPlaceholder = 'Search...';

  @property({ attribute: 'items-label', type: String }) itemsLabel = 'attributes';

  @property({ attribute: 'restore-label', type: String }) restoreLabel = 'Restore';

  @property({ attribute: 'clear-label', type: String }) clearLabel = 'Clear';

  @property({ attribute: 'grid-rows', type: Number }) gridRows = 6;

  @property({ attribute: 'grid-columns', type: Number }) gridColumns = 3;

  @property({ attribute: 'min-column-width', type: String }) minColumnWidth = '220px';

  @property({ attribute: 'lane-height', type: Number }) laneHeight = 32;

  @property({ attribute: true, type: String, reflect: true }) orientation: 'horizontal' | 'vertical' | 'both' = 'horizontal';

  @property({ attribute: 'max-height', type: String }) maxHeight = '320px';

  @property({ attribute: 'matrix-columns', type: Number }) matrixColumns = 20;

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

  @property({ attribute: 'virtualize-threshold', type: Number }) virtualizeThreshold = 60;

  @property({ attribute: 'overscan', type: Number }) overscan = 4;

  @state() private _detailOpen = false;

  @state() private _hasSlottedContent = false;

  @state() private _searchTerm = '';

  @state() private _selectedSet: Set<string> = new Set();

  @state() private _showSelectedOnly = false;

  @property({ attribute: 'show-selected-toggle', type: Boolean, reflect: true }) showSelectedToggle = false;

  @property({ attribute: 'selected-only-label', type: String }) selectedOnlyLabel = 'Selected';

  @property({ attribute: 'show-all-label', type: String }) showAllLabel = 'Show all';

  private _restoreDefaults: string[] | null = null;

  private _gridResizeObserver: ResizeObserver | null = null;

  private _virtCtrl: VirtualizerController<HTMLElement, HTMLElement> | null = null;

  private _rowVirtCtrl: VirtualizerController<HTMLElement, HTMLElement> | null = null;

  private _colVirtCtrl: VirtualizerController<HTMLElement, HTMLElement> | null = null;

  private _pageCache: any = new Map();

  private _pendingPages: any = new Set();

  /** True for any variant that renders the selection UI/behavior. */
  private get _isSelectionVariant(): boolean {
    return this.variant === 'selection' || this.variant === 'select-light';
  }

  /** True for any variant that applies the light styling. */
  private get _isLightVariant(): boolean {
    return this.variant === 'light' || this.variant === 'select-light';
  }

  private get _isInfiniteMode(): boolean {
    return (
      this._isSelectionVariant &&
      typeof this.fetchPage === 'function' &&
      typeof this.totalCount === 'number' &&
      this.orientation !== 'both'
    );
  }

  private _effectiveTotalCount(): number {
    if (this._isInfiniteMode) return this.totalCount ?? 0;
    return this._filteredItems.length;
  }

  private _getItemAt(index: number): SelectionItem | undefined {
    if (!this._isInfiniteMode) return this._filteredItems[index];
    const pageSize = Math.max(1, this.pageSize);
    const pageIndex = Math.floor(index / pageSize);
    const page = this._pageCache.get(pageIndex);
    if (!page) return undefined;
    return page[index - pageIndex * pageSize];
  }

  private _scheduleFetchForRange(start: number, end: number) {
    if (!this._isInfiniteMode || !this.fetchPage) return;
    const pageSize = Math.max(1, this.pageSize);
    const total = this.totalCount ?? 0;
    if (total <= 0) return;

    const firstPage = Math.floor(start / pageSize);
    const lastPage = Math.floor(Math.min(end, total - 1) / pageSize);
    for (let p = firstPage; p <= lastPage; p++) {
      if (this._pageCache.has(p) || this._pendingPages.has(p)) continue;
      this._loadPage(p);
    }
  }

  private async _loadPage(pageIndex: number) {
    if (!this.fetchPage) return;
    const pageSize = Math.max(1, this.pageSize);
    const offset = pageIndex * pageSize;
    const total = this.totalCount ?? 0;
    const limit = Math.min(pageSize, Math.max(0, total - offset));
    if (limit <= 0) return;

    this._pendingPages.add(pageIndex);
    try {
      const rows = await this.fetchPage(offset, limit);
      this._pageCache.set(pageIndex, Array.isArray(rows) ? rows : []);
      this.dispatchEvent(
        new CustomEvent('nile-page-load', {
          detail: { pageIndex, offset, limit, rows: this._pageCache.get(pageIndex) },
          bubbles: true,
          composed: true,
        })
      );
    } catch (err) {
      this.dispatchEvent(
        new CustomEvent('nile-page-error', {
          detail: { pageIndex, offset, limit, error: err },
          bubbles: true,
          composed: true,
        })
      );
    } finally {
      this._pendingPages.delete(pageIndex);
      this.requestUpdate();
    }
  }

  /** Public: clear the page cache (use after a query change to refetch). */
  resetPages() {
    this._pageCache = new Map();
    this._pendingPages = new Set();
    this.requestUpdate();
  }

  private get _isVirtualized(): boolean {
    if (!IS_BROWSER) return false;
    if (this._isInfiniteMode) return true;
    return this._isSelectionVariant
      && (this.virtualize || this.items.length > this.virtualizeThreshold);
  }

  private get _columnWidthPx(): number {
    const parsed = parseInt(this.minColumnWidth, 10);
    return Number.isFinite(parsed) && parsed > 0 ? parsed : 220;
  }

  firstUpdated() {
    this._detailOpen = this.open;
    this.body.hidden = !this.open;
    this.body.style.height = this.open ? 'auto' : '0';
    this._syncSelectedFromProperty();
    if (this._restoreDefaults === null) {
      this._restoreDefaults = [...this._selectedSet];
    }
    this._setupGridResizeObserver();
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this._gridResizeObserver?.disconnect();
    this._gridResizeObserver = null;
  }

  updated(_changed: Map<string, unknown>) {
    if (this._isSelectionVariant) {
      this._setupGridResizeObserver();
      this._syncVirtualizer();
      this._scheduleTooltipOverflowCheck();
    }
  }

  private _syncVirtualizer() {
    if (!IS_BROWSER) return;
    if (!this._isVirtualized) {
      this._virtCtrl = null;
      this._rowVirtCtrl = null;
      this._colVirtCtrl = null;
      return;
    }
    const grid = this.shadowRoot?.querySelector('.detail__selection-grid') as HTMLElement | null;
    if (!grid) return;

    if (this.orientation === 'both') {
      this._virtCtrl = null;
      this._syncBothVirtualizers();
      return;
    }

    // Reset 2-axis controllers if we switched away from 'both'.
    this._rowVirtCtrl = null;
    this._colVirtCtrl = null;

    const count = this._effectiveTotalCount();
    const isVertical = this.orientation === 'vertical';
    const lanes = Math.max(1, isVertical ? this.gridColumns : this.gridRows);
    const itemSize = isVertical ? this.laneHeight : this._columnWidthPx;

    if (!this._virtCtrl) {
      this._virtCtrl = new VirtualizerController<HTMLElement, HTMLElement>(this, {
        count,
        getScrollElement: () =>
          this.shadowRoot?.querySelector('.detail__selection-grid') as HTMLElement | null,
        estimateSize: () => itemSize,
        horizontal: !isVertical,
        lanes,
        overscan: this.overscan,
      });
      return;
    }

    const v = this._virtCtrl.getVirtualizer();
    const prev = v.options;
    const orientationChanged = !!prev.horizontal !== !isVertical;
    if (
      prev.count !== count ||
      prev.lanes !== lanes ||
      prev.overscan !== this.overscan ||
      orientationChanged
    ) {
      v.setOptions({
        ...prev,
        count,
        lanes,
        overscan: this.overscan,
        horizontal: !isVertical,
        estimateSize: () => itemSize,
      });
      v.measure();
    }
  }

  private _syncBothVirtualizers() {
    const totalItems = this._filteredItems.length;
    const cols = Math.max(1, this.matrixColumns);
    const rows = Math.max(1, Math.ceil(totalItems / cols));
    const columnWidth = this._columnWidthPx;
    const rowHeight = this.laneHeight;
    const getScrollEl = () =>
      this.shadowRoot?.querySelector('.detail__selection-grid') as HTMLElement | null;

    if (!this._rowVirtCtrl) {
      this._rowVirtCtrl = new VirtualizerController<HTMLElement, HTMLElement>(this, {
        count: rows,
        getScrollElement: getScrollEl,
        estimateSize: () => rowHeight,
        horizontal: false,
        overscan: this.overscan,
      });
    } else {
      const rv = this._rowVirtCtrl.getVirtualizer();
      const prev = rv.options;
      if (prev.count !== rows || prev.overscan !== this.overscan) {
        rv.setOptions({
          ...prev,
          count: rows,
          overscan: this.overscan,
          estimateSize: () => rowHeight,
        });
        rv.measure();
      }
    }

    if (!this._colVirtCtrl) {
      this._colVirtCtrl = new VirtualizerController<HTMLElement, HTMLElement>(this, {
        count: cols,
        getScrollElement: getScrollEl,
        estimateSize: () => columnWidth,
        horizontal: true,
        overscan: this.overscan,
      });
    } else {
      const cv = this._colVirtCtrl.getVirtualizer();
      const prev = cv.options;
      if (prev.count !== cols || prev.overscan !== this.overscan) {
        cv.setOptions({
          ...prev,
          count: cols,
          overscan: this.overscan,
          estimateSize: () => columnWidth,
        });
        cv.measure();
      }
    }
  }

  private _setupGridResizeObserver() {
    if (!this._isSelectionVariant) return;
    if (typeof ResizeObserver === 'undefined') return;
    const grid = this.shadowRoot?.querySelector('.detail__selection-grid') as HTMLElement | null;
    if (!grid) return;
    if (this._gridResizeObserver) return;
    this._gridResizeObserver = new ResizeObserver(() => this._scheduleTooltipOverflowCheck());
    this._gridResizeObserver.observe(grid);
  }

  private _scheduleTooltipOverflowCheck() {
    if (typeof requestAnimationFrame === 'undefined') return;
    requestAnimationFrame(() => this._updateTooltipOverflowStates());
  }

  private async _updateTooltipOverflowStates() {
    const tooltips = this.shadowRoot?.querySelectorAll(
      '.detail__selection-tooltip'
    ) as NodeListOf<HTMLElement> | undefined;
    if (!tooltips) return;
    for (const tip of tooltips) {
      const cb = tip.querySelector('nile-checkbox') as HTMLElement & { updateComplete?: Promise<unknown> } | null;
      if (!cb) continue;
      if (cb.updateComplete) await cb.updateComplete;
      const labelEl = cb.shadowRoot?.querySelector('[part="label"]') as HTMLElement | null;
      const overflowing = !!labelEl && labelEl.scrollWidth > labelEl.clientWidth + 1;
      if (overflowing) tip.removeAttribute('disabled');
      else tip.setAttribute('disabled', '');
    }
  }

  @watch('selected', { waitUntilFirstUpdate: false })
  _onSelectedPropertyChange() {
    this._syncSelectedFromProperty();
  }

  @watch('config', { waitUntilFirstUpdate: false })
  _onConfigChange() {
    this._applyConfig();
  }

  private _applyConfig() {
    const c = this.config;
    if (!c) return;

    if (c.variant     !== undefined) this.variant     = c.variant;
    if (c.heading     !== undefined) this.heading     = c.heading;
    if (c.description !== undefined) this.description = c.description;
    if (c.itemsLabel  !== undefined) this.itemsLabel  = c.itemsLabel;
    if (c.open        !== undefined) this.open        = c.open;
    if (c.disabled    !== undefined) this.disabled    = c.disabled;

    if (c.items     !== undefined) this.items     = c.items;
    if (c.selected  !== undefined) this.selected  = c.selected;
    if (c.renderItemConfig !== undefined) this.renderItemConfig = c.renderItemConfig;
    if (c.allowHtmlLabel   !== undefined) this.allowHtmlLabel   = c.allowHtmlLabel;

    if (c.layout) {
      const l = c.layout;
      if (l.orientation         !== undefined) this.orientation         = l.orientation;
      if (l.gridRows            !== undefined) this.gridRows            = l.gridRows;
      if (l.gridColumns         !== undefined) this.gridColumns         = l.gridColumns;
      if (l.matrixColumns       !== undefined) this.matrixColumns       = l.matrixColumns;
      if (l.minColumnWidth      !== undefined) this.minColumnWidth      = l.minColumnWidth;
      if (l.laneHeight          !== undefined) this.laneHeight          = l.laneHeight;
      if (l.maxHeight           !== undefined) this.maxHeight           = l.maxHeight;
      if (l.expandIconPlacement !== undefined) this.expandIconPlacement = l.expandIconPlacement;
    }

    if (c.search) {
      if (c.search.placeholder   !== undefined) this.searchPlaceholder  = c.search.placeholder;
      if (c.search.disableLocal  !== undefined) this.disableLocalSearch = c.search.disableLocal;
    }

    if (c.virtualization) {
      if (c.virtualization.enabled   !== undefined) this.virtualize          = c.virtualization.enabled;
      if (c.virtualization.threshold !== undefined) this.virtualizeThreshold = c.virtualization.threshold;
      if (c.virtualization.overscan  !== undefined) this.overscan            = c.virtualization.overscan;
    }

    if (c.pagination) {
      if (c.pagination.totalCount       !== undefined) this.totalCount       = c.pagination.totalCount;
      if (c.pagination.pageSize         !== undefined) this.pageSize         = c.pagination.pageSize;
      if (c.pagination.fetchPage        !== undefined) this.fetchPage        = c.pagination.fetchPage;
      if (c.pagination.placeholderLabel !== undefined) this.placeholderLabel = c.pagination.placeholderLabel;
    }

    if (c.labels) {
      if (c.labels.restore      !== undefined) this.restoreLabel      = c.labels.restore;
      if (c.labels.clear        !== undefined) this.clearLabel        = c.labels.clear;
      if (c.labels.selectedOnly !== undefined) this.selectedOnlyLabel = c.labels.selectedOnly;
      if (c.labels.showAll      !== undefined) this.showAllLabel      = c.labels.showAll;
    }

    if (c.showSelectedToggle !== undefined) this.showSelectedToggle = c.showSelectedToggle;
  }

  private _syncSelectedFromProperty() {
    this._selectedSet = new Set(this.selected || []);
  }

  private _getItemValue(item: SelectionItem): string {
    const rc = this.renderItemConfig;
    if (rc?.getValue) return String(rc.getValue(item));
    if (typeof item === 'string') return item;
    return String((item as NileDetailSelectionItem).value ?? '');
  }

  private _getItemLabel(item: SelectionItem): string {
    const rc = this.renderItemConfig;
    if (rc?.getDisplayText) return rc.getDisplayText(item);
    if (typeof item === 'string') return item;
    return (item as NileDetailSelectionItem).label ?? this._getItemValue(item);
  }

  private _getItemSearchText(item: SelectionItem): string {
    const rc = this.renderItemConfig;
    if (rc?.getSearchText) return rc.getSearchText(item);
    return this._getItemLabel(item);
  }

  private _getItemDescription(item: SelectionItem): string {
    const rc = this.renderItemConfig;
    if (rc?.getDescription) return rc.getDescription(item) ?? '';
    if (typeof item === 'string') return '';
    return (item as NileDetailSelectionItem).description ?? '';
  }

  private _getItemPrefix(item: SelectionItem): string {
    const rc = this.renderItemConfig;
    if (rc?.getPrefix) return rc.getPrefix(item) ?? '';
    if (typeof item === 'string') return '';
    return (item as NileDetailSelectionItem).prefix ?? '';
  }

  private _getItemSuffix(item: SelectionItem): string {
    const rc = this.renderItemConfig;
    if (rc?.getSuffix) return rc.getSuffix(item) ?? '';
    if (typeof item === 'string') return '';
    return (item as NileDetailSelectionItem).suffix ?? '';
  }

  private _getItemDisabled(item: SelectionItem): boolean {
    const rc = this.renderItemConfig;
    if (rc?.getDisabled) return !!rc.getDisabled(item);
    if (typeof item === 'string') return false;
    return !!(item as NileDetailSelectionItem).disabled;
  }

  private _getItemClassName(item: SelectionItem): string {
    const rc = this.renderItemConfig;
    if (rc?.getClassName) return rc.getClassName(item) ?? '';
    if (typeof item === 'string') return '';
    return (item as NileDetailSelectionItem).className ?? '';
  }

  private get _filteredItems(): SelectionItem[] {
    let source: SelectionItem[] = this.items;

    if (this._showSelectedOnly) {
      source = source.filter(item => this._selectedSet.has(this._getItemValue(item)));
    }

    if (this.disableLocalSearch) return source;
    const q = this._searchTerm.trim().toLowerCase();
    if (!q) return source;
    return source.filter(item =>
      this._getItemSearchText(item).toLowerCase().includes(q)
    );
  }

  private _onToggleSelectedOnly(event: Event) {
    event.preventDefault();
    const next = !this._showSelectedOnly;
    if (next && this._selectedSet.size === 0) return;
    this._showSelectedOnly = next;
  }

  private _selectableValues(): string[] {
    const source: SelectionItem[] = this._isInfiniteMode
      ? (Array.from(this._pageCache.values()).flat() as SelectionItem[])
      : this.items;
    return source
      .filter(i => !this._getItemDisabled(i))
      .map(i => this._getItemValue(i));
  }

  private get _allChecked(): boolean {
    const selectable = this._selectableValues();
    if (selectable.length === 0) return false;
    return selectable.every(v => this._selectedSet.has(v));
  }

  private get _indeterminate(): boolean {
    const selectable = this._selectableValues();
    const sel = selectable.filter(v => this._selectedSet.has(v)).length;
    return sel > 0 && sel < selectable.length;
  }

  private get _countLabel(): string {
    const total = this._isInfiniteMode ? (this.totalCount ?? 0) : this.items.length;
    return `${this._selectedSet.size} of ${total} ${this.itemsLabel} selected`;
  }

  private _emitSelectionChange() {
    this.selected = [...this._selectedSet];
    this.dispatchEvent(
      new CustomEvent('nile-change', {
        detail: { selected: [...this._selectedSet] },
        bubbles: true,
        composed: true,
      })
    );
  }

  private _onItemChange(value: string, disabled: boolean, event: Event) {
    if (disabled) {
      event.preventDefault();
      return;
    }
    const target = event.target as HTMLInputElement;
    const next = new Set(this._selectedSet);
    if (target.checked) next.add(value);
    else next.delete(value);
    this._selectedSet = next;
    this._emitSelectionChange();
  }

  private _onSelectAllChange(event: Event) {
    const target = event.target as HTMLInputElement;
    const selectable = this._selectableValues();
    const next = new Set(this._selectedSet);
    if (target.checked) selectable.forEach(v => next.add(v));
    else selectable.forEach(v => next.delete(v));
    this._selectedSet = next;
    this._emitSelectionChange();
  }

  private _emitSearch(value: string) {
    this.dispatchEvent(
      new CustomEvent('nile-search', {
        detail: { value },
        bubbles: true,
        composed: true,
      })
    );
  }

  private _onSearchInput(event: Event) {
    const target = event.target as HTMLInputElement;
    const value = target.value ?? '';
    this._searchTerm = value;
    if (this.disableLocalSearch) this._emitSearch(value);
  }

  private _onSearchClear() {
    this._searchTerm = '';
    if (this.disableLocalSearch) this._emitSearch('');
  }

  private _onRestore(event: Event) {
    event.preventDefault();
    const source = this._restoreDefaults ?? this._selectableValues();
    this._selectedSet = new Set(source);
    this._emitSelectionChange();
  }

  private _onClear(event: Event) {
    event.preventDefault();
    this._selectedSet = new Set();
    this._showSelectedOnly = false;
    this._emitSelectionChange();
  }

  private _handleDefaultSlotChange(event: Event) {
    const slot = event.target as HTMLSlotElement;
    const nodes = slot.assignedNodes({ flatten: true });
    this._hasSlottedContent = nodes.some(node => {
      if (node.nodeType === Node.ELEMENT_NODE) return true;
      if (node.nodeType === Node.TEXT_NODE) return !!(node.textContent && node.textContent.trim());
      return false;
    });
  }

  private _stopHeaderToggle(event: Event) {
    event.stopPropagation();
  }

  private _handleSummaryClick(event: Event) {
    event.preventDefault();
    handleSummaryClick(this);
  }

  private _handleSummaryKeyDown(event: KeyboardEvent) {
    handleSummaryKeyDown(event, this);
  }

  @watch('open', { waitUntilFirstUpdate: true })
  async handleOpenChange() {
    if (this.open) {
      this._detailOpen = true;
      await animateShow(this);
    } else {
      await animateHide(this);
      this._detailOpen = false;
    }
  }

  async show() {
    return showDetail(this);
  }

  async hide() {
    return hideDetail(this);
  }

  private get _summaryLabel(): string {
    return [this.heading, this.description].filter(Boolean).join(', ');
  }

  private _renderSelectionLabel(): TemplateResult {
    return html`
      <div part="selection-label" class="detail__selection-label">
        <nile-checkbox
          part="select-all"
          class="detail__select-all"
          ?checked=${this._allChecked}
          ?indeterminate=${this._indeterminate}
          aria-label=${`Select all ${this.heading || this.itemsLabel}`}
          @click=${this._stopHeaderToggle}
          @nile-change=${this._onSelectAllChange}
        ></nile-checkbox>
        <div class="detail__selection-title">
          <span part="label-text" class="detail__heading-text">${this.heading}</span>
          <span part="selection-count" class="detail__selection-count">${this._countLabel}</span>
        </div>
      </div>
    `;
  }

  private _renderSelectionBody(): TemplateResult {
    const virtualized = this._isVirtualized;
    const isVertical = this.orientation === 'vertical';
    const isBoth = this.orientation === 'both';
    const laneHeightPx = this.laneHeight;
    const columnWidth = this._columnWidthPx;

    let gridStyle = '';
    if (virtualized) {
      if (isBoth) {
        gridStyle = `max-height: ${this.maxHeight}; height: ${this.maxHeight};`;
      } else if (isVertical) {
        gridStyle = `max-height: ${this.maxHeight}; height: ${this.maxHeight};`;
      } else {
        const lanes = Math.max(1, this.gridRows);
        gridStyle = `height: ${lanes * laneHeightPx}px;`;
      }
    } else if (isVertical) {
      const cols = Math.max(1, this.gridColumns);
      gridStyle = `grid-template-columns: repeat(${cols}, minmax(0, 1fr)); grid-auto-flow: row; max-height: ${this.maxHeight}; overflow-y: auto; overflow-x: hidden;`;
    } else {
      const lanes = Math.max(1, this.gridRows);
      gridStyle = `grid-template-rows: repeat(${lanes}, auto); grid-auto-columns: minmax(${this.minColumnWidth}, 1fr);`;
    }

    return html`
      <div part="selection-toolbar" class="detail__selection-toolbar">
      <slot name="tooblar-actions-prefix"></slot>
        <slot name="toolbar-search" class="detail__selection-search-slot">
          <nile-input
            part="search"
            class="detail__selection-search"
            placeholder=${this.searchPlaceholder}
            clearable
            .value=${this._searchTerm}
            @nile-input=${this._onSearchInput}
            @nile-clear=${this._onSearchClear}
          >
            <nile-icon slot="prefix" name="search" library="system"></nile-icon>
          </nile-input>
        </slot>
        <slot name="toolbar-actions" class="detail__selection-actions">
          ${this.showSelectedToggle
            ? html`
                <nile-link
                  href="#"
                  variant="subtle"
                  class="detail__selected-toggle"
                  @click=${this._onToggleSelectedOnly}
                >
                  <nile-icon slot="prefix" name="filter" library="system"></nile-icon>
                  ${this._showSelectedOnly ? this.showAllLabel : this.selectedOnlyLabel}
                </nile-link>
              `
            : nothing}
          <nile-link href="#" variant="subtle" @click=${this._onRestore}>${this.restoreLabel}</nile-link>
          <nile-link href="#" variant="subtle" @click=${this._onClear}>${this.clearLabel}</nile-link>
        </slot>
        <slot name="tooblar-actions-suffix"></slot>
      </div>
      <div
        part="selection-grid"
        class=${classMap({
          'detail__selection-grid': true,
          'detail__selection-grid--virtual': virtualized,
          'detail__selection-grid--vertical': isVertical,
          'detail__selection-grid--both': isBoth,
        })}
        style=${gridStyle}
      >
        ${virtualized
          ? (isBoth
              ? this._renderBothVirtualGrid(laneHeightPx, columnWidth)
              : this._renderVirtualGrid(laneHeightPx, columnWidth, isVertical))
          : this._renderStaticGrid()}
      </div>
    `;
  }

  private _renderItemContent(item: SelectionItem): TemplateResult {
    const label = this._getItemLabel(item);
    const description = this._getItemDescription(item);
    const prefix = this._getItemPrefix(item);
    const suffix = this._getItemSuffix(item);
    const allowHtml = this.allowHtmlLabel;
    return html`
      ${prefix
        ? html`<span class="detail__selection-prefix" part="item-prefix">${allowHtml ? unsafeHTML(prefix) : prefix}</span>`
        : ''}
      <span class="detail__selection-text" part="item-text">
        <span class="detail__selection-item-label" part="item-label">${allowHtml ? unsafeHTML(label) : label}</span>
        ${description
          ? html`<span class="detail__selection-desc" part="item-description">${allowHtml ? unsafeHTML(description) : description}</span>`
          : ''}
      </span>
      ${suffix
        ? html`<span class="detail__selection-suffix" part="item-suffix">${allowHtml ? unsafeHTML(suffix) : suffix}</span>`
        : ''}
    `;
  }

  private _renderItemTooltip(item: SelectionItem, opts: { positionStyle?: string; extraClasses?: string } = {}): TemplateResult {
    const value = this._getItemValue(item);
    const label = this._getItemLabel(item);
    const description = this._getItemDescription(item);
    const isDisabled = this._getItemDisabled(item);
    const className = this._getItemClassName(item);
    const tooltipContent = description ? `${label} — ${description}` : label;

    const tooltipClass = `detail__selection-tooltip${opts.extraClasses ? ' ' + opts.extraClasses : ''}`;
    const checkboxClass = classMap({
      'detail__selection-checkbox': true,
      'detail__selection-checkbox--disabled': isDisabled,
      [className]: !!className,
    });

    return html`
      <nile-lite-tooltip
        class=${tooltipClass}
        content=${tooltipContent}
        size="small"
        disabled
        style=${opts.positionStyle ?? nothing as any}
      >
        <nile-checkbox
          class=${checkboxClass}
          ?checked=${this._selectedSet.has(value)}
          ?disabled=${isDisabled}
          @nile-change=${(e: Event) => this._onItemChange(value, isDisabled, e)}
        >${this._renderItemContent(item)}</nile-checkbox>
      </nile-lite-tooltip>
    `;
  }

  private _renderStaticGrid(): TemplateResult {
    return html`${this._filteredItems.map(item => this._renderItemTooltip(item))}`;
  }

  private _renderBothVirtualGrid(_laneHeightPx: number, _columnWidthPx: number): TemplateResult {
    const rowV = this._rowVirtCtrl?.getVirtualizer();
    const colV = this._colVirtCtrl?.getVirtualizer();
    const rowTotal = rowV?.getTotalSize() ?? 0;
    const colTotal = colV?.getTotalSize() ?? 0;
    const rowItems = rowV?.getVirtualItems() ?? [];
    const colItems = colV?.getVirtualItems() ?? [];
    const cols = Math.max(1, this.matrixColumns);
    const total = this._filteredItems.length;

    return html`
      <div
        class="detail__selection-track"
        style="width: ${colTotal}px; height: ${rowTotal}px; position: relative;"
      >
        ${rowItems.flatMap(rvi =>
          colItems.map(cvi => {
            const idx = rvi.index * cols + cvi.index;
            if (idx >= total) return null;
            const item = this._filteredItems[idx];
            if (item === undefined) return null;
            const positionStyle = `position: absolute; top: ${rvi.start}px; left: ${cvi.start}px; width: ${cvi.size}px; height: ${rvi.size}px;`;
            return this._renderItemTooltip(item, {
              positionStyle,
              extraClasses: 'detail__selection-tooltip--virtual',
            });
          })
        )}
      </div>
    `;
  }

  private _renderVirtualGrid(
    laneHeightPx: number,
    _columnWidthPx: number,
    isVertical: boolean
  ): TemplateResult {
    const v = this._virtCtrl?.getVirtualizer();
    const totalSize = v?.getTotalSize() ?? 0;
    const virtualItems = v?.getVirtualItems() ?? [];
    const lanes = Math.max(1, isVertical ? this.gridColumns : this.gridRows);

    if (this._isInfiniteMode && virtualItems.length > 0) {
      const first = virtualItems[0].index;
      const last = virtualItems[virtualItems.length - 1].index;
      this._scheduleFetchForRange(first, last);
    }

    const trackStyle = isVertical
      ? `width: 100%; height: ${totalSize}px; position: relative;`
      : `width: ${totalSize}px; height: 100%; position: relative;`;

    return html`
      <div class="detail__selection-track" style=${trackStyle}>
        ${virtualItems.map(vi => {
          const laneIdx = vi.lane ?? 0;
          const positionStyle = isVertical
            ? `position: absolute; top: ${vi.start}px; left: calc(${laneIdx} * (100% / ${lanes})); width: calc(100% / ${lanes}); height: ${vi.size}px;`
            : `position: absolute; top: ${laneIdx * laneHeightPx}px; left: ${vi.start}px; width: ${vi.size}px; height: ${laneHeightPx}px;`;
          const item = this._getItemAt(vi.index);
          if (item === undefined) {
            return this._renderPlaceholder(positionStyle);
          }
          return this._renderItemTooltip(item, {
            positionStyle,
            extraClasses: 'detail__selection-tooltip--virtual',
          });
        })}
      </div>
    `;
  }

  private _renderPlaceholder(positionStyle: string): TemplateResult {
    return html`
      <div class="detail__selection-placeholder" style=${positionStyle} part="item-placeholder">
        <div class="detail__selection-placeholder-bar"></div>
        <span class="detail__selection-placeholder-label">${this.placeholderLabel}</span>
      </div>
    `;
  }

  render(): TemplateResult {
    const isSelection = this._isSelectionVariant;
    return html`
      <details
        part="base"
        ?open=${this._detailOpen}
        class=${classMap({
          'detail': true,
          'detail--open': this.open,
          'detail--disabled': this.disabled,
          'detail--selection': isSelection,
          'detail--light': this._isLightVariant,
        })}
      >
        <summary
          part="header"
          id="header"
          class=${classMap({
            'detail__header': true,
            'detail__header--icon-left': this.expandIconPlacement === 'left',
          })}
          aria-label=${this._summaryLabel || nothing}
          aria-expanded=${this.open ? 'true' : 'false'}
          aria-disabled=${this.disabled ? 'true' : 'false'}
          tabindex=${this.disabled ? '-1' : '0'}
          @click=${this._handleSummaryClick}
          @keydown=${this._handleSummaryKeyDown}
        >
          <slot name="label" part="label" class="detail__label">
            ${isSelection
              ? this._renderSelectionLabel()
              : html`
                  <span part="label-text" class="detail__heading-text">${this.heading}</span>
                  ${this.description
                    ? html`<span part="description" class="detail__description">${this.description}</span>`
                    : ''}
                `}
          </slot>

          <slot name="header-actions" part="header-actions" class="detail__header-actions"></slot>

          <slot name="prefix" part="prefix" class="detail__prefix"></slot>

          <span part="summary-icon" class="detail__summary-icon" aria-hidden="true">
            <slot name="expand-icon">
              <nile-icon
                name="var(--nile-icon-arrow-right, var(--ng-icon-chevron-right))"
                method="var(--nile-svg-method-fill, var(--ng-svg-method-stroke))"
                library="system"
              ></nile-icon>
            </slot>
          </span>

          <slot name="suffix" part="suffix" class="detail__suffix"></slot>

        </summary>

        <div part="content" class="detail__body">
          ${isSelection
            ? html`<div part="selection-content" class="detail__selection-content">${this._renderSelectionBody()}</div>`
            : ''}
          <slot
            id="content"
            class=${classMap({
              'detail__content': true,
              'detail__content--empty': !this._hasSlottedContent,
            })}
            @slotchange=${this._handleDefaultSlotChange}
          ></slot>
        </div>
      </details>
    `;
  }
}

export default NileDetail;

declare global {
  interface HTMLElementTagNameMap {
    'nile-detail': NileDetail;
  }
}
