/**
 * 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, type TemplateResult } from 'lit';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { classMap } from 'lit/directives/class-map.js';
import { repeat } from 'lit/directives/repeat.js';
import type { VirtualItem } from '@tanstack/virtual-core';
import type { ComboboxRow, ComboboxHeaderRow } from './types';

export class ComboboxRenderer {

  static renderGroupHeader(row: ComboboxHeaderRow): TemplateResult {
    return html`
      <div
        part="group-header"
        class="combobox__group-header"
        role="presentation"
        data-group-id=${row.id}
        style=${`--group-depth:${row.depth}`}
      >
        ${row.prefix
          ? html`<nile-icon
              class="combobox__group-prefix"
              name=${row.prefix}
              size="14"
              method="fill"
            ></nile-icon>`
          : ''}
        <span class="combobox__group-label">${row.label}</span>
      </div>
    `;
  }

  static renderRowsPlain(
    rows: ComboboxRow[],
    value: string | string[],
    multiple: boolean,
    getDisplayText: (item: any) => string,
    getItemValue: (item: any) => string,
    showNoResults: boolean,
    noResultsMessage: string,
    isLoading: boolean,
    onScroll: (e: Event) => void,
    allowHtmlLabel: boolean,
    getItemDescription?: (item: any) => string,
    getItemPrefix?: (item: any) => string,
    getItemSuffix?: (item: any) => string,
    enableDescription?: boolean,
    noResultsSubtitle?: string,
  ): TemplateResult {
    if (showNoResults && !isLoading && rows.length === 0) {
      return ComboboxRenderer.renderNoResults(noResultsMessage, noResultsSubtitle);
    }

    return html`
      <div
        part="select-options"
        class="combobox__options ${isLoading ? 'loading' : ''}"
      >
        <div class="combobox__options-plain" @scroll=${onScroll}>
          ${rows.map((row) =>
            row.kind === 'header'
              ? ComboboxRenderer.renderGroupHeader(row)
              : ComboboxRenderer.renderItem(
                  row.item, value, multiple, getDisplayText, getItemValue,
                  allowHtmlLabel, getItemDescription, getItemPrefix,
                  getItemSuffix, enableDescription,
                ),
          )}
        </div>
      </div>
    `;
  }

  static renderRowsVirtualized(
    virtualItems: VirtualItem[],
    totalSize: number,
    rows: ComboboxRow[],
    value: string | string[],
    multiple: boolean,
    getDisplayText: (item: any) => string,
    getItemValue: (item: any) => string,
    isLoading: boolean,
    allowHtmlLabel: boolean,
    getItemDescription?: (item: any) => string,
    getItemPrefix?: (item: any) => string,
    getItemSuffix?: (item: any) => string,
    enableDescription?: boolean,
  ): TemplateResult {
    return html`
      <div
        part="select-options"
        class="combobox__options ${isLoading ? 'loading' : ''}"
      >
        <div style="position:relative;height:${totalSize}px;width:100%;">
          ${repeat(
            virtualItems,
            (vItem) => vItem.key,
            (vItem) => {
              const row = rows[vItem.index];
              if (!row) return html``;
              const posStyle =
                `position:absolute;top:0;left:0;right:0;` +
                `transform:translateY(${vItem.start}px);` +
                `height:${vItem.size}px;`;
              if (row.kind === 'header') {
                return html`
                  <div style=${posStyle} class="combobox__group-header-slot">
                    ${ComboboxRenderer.renderGroupHeader(row)}
                  </div>
                `;
              }
              return html`
                <div style=${posStyle}>
                  ${ComboboxRenderer.renderItem(
                    row.item, value, multiple, getDisplayText, getItemValue,
                    allowHtmlLabel, getItemDescription, getItemPrefix,
                    getItemSuffix, enableDescription,
                  )}
                </div>
              `;
            },
          )}
        </div>
      </div>
    `;
  }

  static renderVirtualizedOptions(
    virtualItems: VirtualItem[],
    totalSize: number,
    data: any[],
    value: string | string[],
    multiple: boolean,
    getDisplayText: (item: any) => string,
    getItemValue: (item: any) => string,
    isLoading: boolean,
    allowHtmlLabel: boolean,
    measureElement: (el: Element | null) => void,
    getItemDescription?: (item: any) => string,
    getItemPrefix?: (item: any) => string,
    getItemSuffix?: (item: any) => string,
    enableDescription?: boolean,
  ): TemplateResult {
    const offsetTop = virtualItems.length > 0 ? virtualItems[0].start : 0;

    return html`
      <div
        part="select-options"
        class="combobox__options ${isLoading ? 'loading' : ''}"
      >
        <div style="position:relative;height:${totalSize}px;width:100%;">
          <div style="position:absolute;top:0;left:0;width:100%;transform:translateY(${offsetTop}px);">
            ${repeat(
              virtualItems,
              (vItem) => vItem.key,
              (vItem) => {
                const item = data[vItem.index];
                return ComboboxRenderer.renderMeasuredItem(
                  item, vItem.index, value, multiple, getDisplayText, getItemValue,
                  allowHtmlLabel, getItemDescription, getItemPrefix,
                  getItemSuffix, enableDescription,
                );
              },
            )}
          </div>
        </div>
      </div>
    `;
  }

  static renderPlainOptions(
    data: any[],
    value: string | string[],
    multiple: boolean,
    getDisplayText: (item: any) => string,
    getItemValue: (item: any) => string,
    showNoResults: boolean,
    noResultsMessage: string,
    isLoading: boolean,
    onScroll: (e: Event) => void,
    allowHtmlLabel: boolean,
    getItemDescription?: (item: any) => string,
    getItemPrefix?: (item: any) => string,
    getItemSuffix?: (item: any) => string,
    enableDescription?: boolean,
    noResultsSubtitle?: string,
  ): TemplateResult {
    if (showNoResults && !isLoading && data.length === 0) {
      return ComboboxRenderer.renderNoResults(noResultsMessage, noResultsSubtitle);
    }

    return html`
      <div
        part="select-options"
        class="combobox__options ${isLoading ? 'loading' : ''}"
      >
        <div class="combobox__options-plain" @scroll=${onScroll}>
          ${data.map((item: any) =>
            ComboboxRenderer.renderItem(
              item, value, multiple, getDisplayText, getItemValue,
              allowHtmlLabel, getItemDescription, getItemPrefix,
              getItemSuffix, enableDescription,
            ),
          )}
        </div>
      </div>
    `;
  }

  static renderNoResults(noResultsMessage: string, noResultsSubtitle?: string): TemplateResult {
    return html`
      <div part="select-options" class="combobox__options">
        <div part="no-results" class="combobox__no-results">
          <div part="no-results-title" class="combobox__no-results-title">
            ${noResultsMessage || 'No result found'}
          </div>
          ${noResultsSubtitle
            ? html`<div part="no-results-subtitle" class="combobox__no-results-subtitle">${noResultsSubtitle}</div>`
            : ''}
        </div>
      </div>
    `;
  }

  static renderNoData(noDataMessage: string): TemplateResult {
    return html`
      <div part="select-options" class="combobox__options">
        <div part="no-data" class="combobox__no-results">
          <div part="no-data-title" class="combobox__no-results-title">
            ${noDataMessage || 'No data available'}
          </div>
        </div>
      </div>
    `;
  }

  private static renderMeasuredItem(
    item: any,
    index: number,
    value: string | string[],
    multiple: boolean,
    getDisplayText: (item: any) => string,
    getItemValue: (item: any) => string,
    allowHtmlLabel: boolean,
    getItemDescription?: (item: any) => string,
    getItemPrefix?: (item: any) => string,
    getItemSuffix?: (item: any) => string,
    enableDescription?: boolean,
  ): TemplateResult {
    if (!item) return html``;

    const optionValue = getItemValue(item);
    const displayText = getDisplayText(item);
    const isDisabled = item?.disabled || false;
    const className = item?.className;
    const description = getItemDescription ? getItemDescription(item) : (item?.description ?? '');
    const prefix = getItemPrefix ? getItemPrefix(item) : (item?.prefix ?? '');
    const suffix = getItemSuffix ? getItemSuffix(item) : (item?.suffix ?? '');

    let isSelected = false;
    if (multiple) {
      isSelected = Array.isArray(value) && value.some(v => String(v) === String(optionValue));
    } else {
      isSelected = String(Array.isArray(value) ? value[0] : value) === String(optionValue);
    }

    return html`
      <nile-option
        data-index=${index}
        value=${optionValue}
        .selected=${isSelected}
        .disabled=${isDisabled}
        .showCheckbox=${multiple}
        class=${classMap({
          option: enableDescription ?? false,
          [className ?? '']: !!className,
        })}
        .description=${description}
        .isDescriptionEnabled=${enableDescription}
      >
        ${unsafeHTML(prefix)}
        ${allowHtmlLabel ? unsafeHTML(displayText) : displayText}
        ${unsafeHTML(suffix)}
      </nile-option>
    `;
  }

  static renderItem(
    item: any,
    value: string | string[],
    multiple: boolean,
    getDisplayText: (item: any) => string,
    getItemValue: (item: any) => string,
    allowHtmlLabel: boolean,
    getItemDescription?: (item: any) => string,
    getItemPrefix?: (item: any) => string,
    getItemSuffix?: (item: any) => string,
    enableDescription?: boolean,
  ): TemplateResult {
    if (!item) return html``;

    const optionValue = getItemValue(item);
    const displayText = getDisplayText(item);
    const isDisabled = item?.disabled || false;
    const className = item?.className;
    const description = getItemDescription ? getItemDescription(item) : (item?.description ?? '');
    const prefix = getItemPrefix ? getItemPrefix(item) : (item?.prefix ?? '');
    const suffix = getItemSuffix ? getItemSuffix(item) : (item?.suffix ?? '');

    let isSelected = false;
    if (multiple) {
      isSelected = Array.isArray(value) && value.some(v => String(v) === String(optionValue));
    } else {
      isSelected = String(Array.isArray(value) ? value[0] : value) === String(optionValue);
    }

    return html`
      <nile-option
        value=${optionValue}
        .selected=${isSelected}
        .disabled=${isDisabled}
        .showCheckbox=${multiple}
        class=${classMap({
          option: enableDescription ?? false,
          [className ?? '']: !!className,
        })}
        .description=${description}
        .isDescriptionEnabled=${enableDescription}
      >
        ${unsafeHTML(prefix)}
        ${allowHtmlLabel ? unsafeHTML(displayText) : displayText}
        ${unsafeHTML(suffix)}
      </nile-option>
    `;
  }

  static renderVirtualizedGrid(
    virtualItems: VirtualItem[],
    totalSize: number,
    data: any[],
    value: string | string[],
    multiple: boolean,
    gridColumns: number,
    getDisplayText: (item: any) => string,
    getItemValue: (item: any) => string,
    isLoading: boolean,
    allowHtmlLabel: boolean,
    getItemDescription?: (item: any) => string,
    getItemPrefix?: (item: any) => string,
    getItemSuffix?: (item: any) => string,
    gridColumnWidth?: number,
  ): TemplateResult {
    const offsetTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
    const colTemplate = gridColumnWidth
      ? `repeat(${gridColumns}, ${gridColumnWidth}px)`
      : `repeat(${gridColumns}, 1fr)`;

    return html`
      <div
        part="select-options"
        class="combobox__options ${isLoading ? 'loading' : ''}"
      >
        <div style="position:relative;height:${totalSize}px;width:${gridColumnWidth ? 'max-content' : '100%'};min-width:100%;">
          <div style="position:absolute;top:0;left:0;width:${gridColumnWidth ? 'max-content' : '100%'};min-width:100%;transform:translateY(${offsetTop}px);">
            ${repeat(
              virtualItems,
              (vItem) => vItem.key,
              (vItem) => {
                const rowStart = vItem.index * gridColumns;
                const rowItems = data.slice(rowStart, rowStart + gridColumns);
                return html`
                  <div class="combobox__grid-row" style="display:grid;grid-template-columns:${colTemplate};gap:4px;">
                    ${rowItems.map((item: any) =>
                      ComboboxRenderer.renderItem(
                        item, value, multiple, getDisplayText, getItemValue,
                        allowHtmlLabel, getItemDescription, getItemPrefix,
                        getItemSuffix,
                      ),
                    )}
                  </div>
                `;
              },
            )}
          </div>
        </div>
      </div>
    `;
  }

  static renderHorizontalGrid(
    virtualItems: VirtualItem[],
    totalSize: number,
    data: any[],
    value: string | string[],
    multiple: boolean,
    gridRows: number,
    gridColumnWidth: number,
    getDisplayText: (item: any) => string,
    getItemValue: (item: any) => string,
    isLoading: boolean,
    allowHtmlLabel: boolean,
    getItemDescription?: (item: any) => string,
    getItemPrefix?: (item: any) => string,
    getItemSuffix?: (item: any) => string,
  ): TemplateResult {
    const offsetLeft = virtualItems.length > 0 ? virtualItems[0].start : 0;
    const rowHeight = 38;

    return html`
      <div
        part="select-options"
        class="combobox__options combobox__options--horizontal ${isLoading ? 'loading' : ''}"
      >
        <div style="position:relative;width:${totalSize}px;height:${rowHeight * gridRows}px;">
          <div style="position:absolute;top:0;left:0;height:100%;display:flex;transform:translateX(${offsetLeft}px);">
            ${repeat(
              virtualItems,
              (vCol) => vCol.key,
              (vCol) => {
                const colStart = vCol.index * gridRows;
                const colItems = data.slice(colStart, colStart + gridRows);
                return html`
                  <div class="combobox__grid-col" style="width:${gridColumnWidth}px;flex-shrink:0;display:flex;flex-direction:column;">
                    ${colItems.map((item: any) =>
                      ComboboxRenderer.renderItem(
                        item, value, multiple, getDisplayText, getItemValue,
                        allowHtmlLabel, getItemDescription, getItemPrefix,
                        getItemSuffix,
                      ),
                    )}
                  </div>
                `;
              },
            )}
          </div>
        </div>
      </div>
    `;
  }

  static renderAddCustomOption(
    searchValue: string,
    multiple: boolean,
  ): TemplateResult {
    return html`
      <nile-option
        value=${searchValue}
        class="combobox__add-option"
        .showCheckbox=${multiple}
      >
        + Add "${searchValue}"
      </nile-option>
    `;
  }

  static shouldUseVirtualizer(data: any[], gridColumns = 1): boolean {
    if (gridColumns > 1) return true;
    return data.length >= 5;
  }
}
