/**
 * Copyright Aquera Inc 2023
 *
 * 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 {
  LitElement,
  html,
  CSSResultArray,
  TemplateResult,
  PropertyValues,
} from 'lit';
import { customElement, query, state, property } from 'lit/decorators.js';
import { styles } from './nile-chip.css';
import { classMap } from 'lit/directives/class-map.js';
import { HasSlotController } from '../internal/slot';
import NileElement, { NileFormControl } from '../internal/nile-element';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { VisibilityManager } from '../utilities/visibility-manager.js';

interface CustomEventDetail {
  value: string;
}

@customElement('nile-chip')
export class NileChip extends NileElement {
  public static get styles(): CSSResultArray {
    return [styles];
  }

  private readonly hasSlotController = new HasSlotController(
    this,
    'help-text',
    'label'
  );

  @state() tags: string[] = [];

  @state() inputValue: string = '';

  @state() isDropdownOpen: boolean = false;
  @state() tooltips: (string | null)[] = [];
  @state() private chipFocusIndex: number | null = null;


  @query('nile-auto-complete') autoComplete!: any;

  /** Sets the input to a warning state, changing its visual appearance. */
  @property({ type: Boolean }) warning = false;

@property({ type: Boolean }) noAutoComplete = false;


  /** Sets the input to an error state, changing its visual appearance. */
  @property({ type: Boolean }) error = false;

  /** Sets the input to a success state, changing its visual appearance. */
  @property({ type: Boolean }) success = false;

  /** Disables the duplicate entries. */
  @property({ type: Boolean }) noDuplicates = false;

  /** The input's label. If you need to display HTML, use the `label` slot instead. */
  @property() label = '';

  @property({ type: String }) tagVariant: string = ''; // can be '', 'normal', 'success', etc.


  /** Adds a clear button when the input is not empty. */
  @property({ type: Boolean }) acceptUserInput = false;

  /** When true, adds a chip when the input loses focus or when clicking outside the component. Only works when acceptUserInput is true. */
  @property({ type: Boolean }) addOnBlur = false;

  /** Adds a clear button when the input is not empty. */
  @property({ type: Boolean }) clearable = false;

  /** Placeholder text to show as a hint when the input is empty. */
  @property() placeholder = 'type here...';

  /** Makes the input readonly. */
  @property({ type: Boolean, reflect: true }) readonly = false;

  /** Disables the input. */
  @property({ type: Boolean, reflect: true }) disabled = false;

  /** 
   * When true, the dropdown menu will be appended to the document body instead of the parent container.
   * This is useful when the parent has overflow: hidden, clip-path, or transform applied.
   */
  @property({ type: Boolean, reflect: true }) portal = false;

  // AUTO-COMPLETE-OPTIONS

  /** Virtual scroll in  dropdown options. */
  @property({ type: Boolean }) enableVirtualScroll = false;

  @property({ type: Array }) autoCompleteOptions: any[] = [];

  @property({ type: Array }) filteredAutoCompleteOptions: any[] = [];

  @property({ type: Array }) value: any[] = [];

  @property({ type: Boolean }) noWrap: boolean = false;

  @property({ type: Boolean }) loading: boolean = false;

  @property({ type: Array }) errorIndexes: number[] = [];

  @property({ attribute: 'help-text', reflect: true }) helpText = '';

  @property({ attribute: 'error-message', reflect: true }) errorMessage = '';

  @property({ attribute:false}) filterFunction: (item:string,searchedValue:string)=>boolean = (item:string,searchedValue:string)=>item.toLowerCase().includes(searchedValue.toLowerCase());

  @property({ attribute:false}) renderItemFunction: (item:any)=>string = (item:any)=>item;

  private visibilityManager?: VisibilityManager;

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

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

  @property({ type: Boolean }) showTooltip: boolean = false;

  @property({ type: Boolean }) enableTagDelete = false;

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

  @property({
    reflect: true,
    converter: {
      fromAttribute: value => value !== 'false',
      toAttribute: value => (value ? '' : 'false'),
    },
  }) openDropdownOnFocus = true;

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

  private isSelectingFromDropdown = false;
  private resetDropdownState() {
    this.isSelectingFromDropdown = false;
  }
  

  protected updated(changedProperties: PropertyValues): void {
    super.updated(changedProperties);
    if (changedProperties.has('autoCompleteOptions')) {
      let options = this.autoCompleteOptions;
      if (typeof options === 'string') {
        try {
          options = JSON.parse(options);
        } catch (e) {
          options = [];
        }
      }
      this.filteredAutoCompleteOptions = Array.isArray(options) ? [...options] : [];
      if (this.noDuplicates) {
        this.filteredAutoCompleteOptions =
          this.filteredAutoCompleteOptions.filter(
            option => !this.value.includes(option)
          );
      }
    }
    if (changedProperties.has('value')){
      this.tags = [...this.value];
      this.onTagsChanged();
    }
    if (changedProperties.has('tags')){
      this.onTagsChanged();
    }
  }
 
  protected async firstUpdated(_changed: PropertyValues) {
    await this.updateComplete;
  
    const inputTarget =
      this.noAutoComplete
        ? this.renderRoot.querySelector('nile-input')?.input
        : this.autoComplete?.inputElement?.input || this.autoComplete?.inputElement;
  
    this.visibilityManager = new VisibilityManager({
      host: this,
      target: inputTarget,
      enableVisibilityEffect: this.enableVisibilityEffect,
      enableTabClose: this.enableTabClose,
      isOpen: () => this.isDropdownOpen,
      onAnchorOutOfView: () => {
        this.isDropdownOpen = false;
        if (this.autoComplete) {
          this.autoComplete.isDropdownOpen = false;
        }        
        this.emit('nile-visibility-change', {
          visible: false,
          reason: 'anchor-out-of-view',
        });
      },
      onDocumentHidden: () => {
        this.isDropdownOpen = false;
        if (this.autoComplete) {
          this.autoComplete.isDropdownOpen = false;
        }
        this.emit('nile-visibility-change', {
          visible: false,
          reason: 'document-hidden',
        });
      },
      emit: (event, detail) => this.emit(`nile-${event}`, detail),
    });
  }

  
  private handleDocumentClick = (event: MouseEvent) => {
    const path = event.composedPath();
  
    if (this.isSelectingFromDropdown) {
      this.resetDropdownState();
      return;
    }
    if (!path.includes(this)) {
      if (this.addOnBlur && this.acceptUserInput && this.inputValue) {
        this.addChipFromInputValue();
      }
      this.isDropdownOpen = false;
    }
  };
  
  connectedCallback() {
    super.connectedCallback();
    document.addEventListener('click', this.handleDocumentClick);
    if (this.noDuplicates) {
      this.filteredAutoCompleteOptions =
        this.filteredAutoCompleteOptions.filter(
          option => !this.value.includes(option)
        );
    }
    this.emit('nile-init');
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.visibilityManager?.cleanup();
    document.removeEventListener('click', this.handleDocumentClick);
    this.emit('nile-destroy');
  }

  private markDropdownSelectStart(event: MouseEvent) {
    this.isSelectingFromDropdown = true;
  }
  

  render() {
    // Check if slots are present
    const hasLabelSlot = this.hasSlotController.test('label');
    const hasHelpTextSlot = this.hasSlotController.test('help-text');

    // Check if label and help text are present
    const hasLabel = this.label ? true : !!hasLabelSlot;

    const hasHelpText = this.helpText ? true : false;
    const hasErrorMessage = this.errorMessage ? true : false;

    return html`
      <div
        part="form-control"
        class=${classMap({
          'form-control': true,
          'form-control--medium': true,
          'form-control--has-label': hasLabel,
          'form-control--has-help-text': hasHelpText,
          'nile-chip--disabled': this.disabled,
        })}
      >
        <label
          part="form-control-label"
          class="form-control__label"
          for="input"
          aria-hidden=${hasLabel ? 'false' : 'true'}
        >
          <slot name="label">${this.label}</slot>
        </label>

        <div
          part='base' 
          class=${classMap({
            'nile-chip': true,
            'nile-chip--warning': this.warning,
            'nile-chip--error': this.error,
            'nile-chip--success': this.success,
            'nile-chip--no-wrap': this.noWrap,
            'nile-chip--open': this.isDropdownOpen, 
          })}
        >
          ${this.tags.map((tag, index) => {
            const tooltipContent = this.tooltips[index];
            const isFocused = this.enableTagDelete && this.chipFocusIndex === index;
            let tagText = "";
            if(tag || typeof tag === "number") {
                tagText = tag.toString();
            }
            const tagTemplate = html`
              <nile-tag
                class=${classMap({
                  'nile-chip__tags': true,
                  'nile-chip__tags--focused': isFocused,
                })}
                aria-selected=${isFocused ? 'true' : 'false'}
                .variant=${this.errorIndexes.includes(index)
                  ? 'error'
                  : 'normal'}
                @nile-remove=${() => this.handleRemove(tag)}
                removable
                ?pill=${this.tagVariant !== 'normal'}
                ?disabled=${this.disabled}
              >
                ${unsafeHTML(tagText)}
              </nile-tag>
            `;

            if (this.showTooltip && tooltipContent) {
              return html`
                <nile-lite-tooltip allowHTML .content=${tooltipContent}>
                  ${tagTemplate}
                </nile-lite-tooltip>
              `;
            }

            return tagTemplate;
          })}

          <div class="nile-chip__auto-complete">
            ${this.noAutoComplete
              ? html`
                  <nile-input
                    .value=${this.inputValue}
                    .placeholder=${this.placeholder}
                    .disabled=${this.disabled}
                    ?no-border=${true}
                    ?no-outline=${true}
                    ?no-padding=${true}
                    @nile-input=${this.handleInputChange}
                    @keydown=${this.handleInputKeydown}
                    @focus=${this.handleFocus}
                    @blur=${this.handleBlur}
                    exportparts="input"
                    ?required=${this.required}
                  ></nile-input>
                `
              : html`
                  <nile-auto-complete
                    .enableVirtualScroll=${this.enableVirtualScroll}
                    .allMenuItems=${this.filteredAutoCompleteOptions}
                    .filterFunction=${this.filterFunction}
                    .renderItemFunction=${this.renderItemFunction}
                    .showTooltips=${this.showTooltip}
                    .loading="${this.loading}"
                    .value=${this.inputValue}
                    ?isDropdownOpen=${this.isDropdownOpen}
                    .noBorder=${true}
                    .noOutline=${true}
                    .noPadding=${true}
                    .disabled=${this.disabled}
                    .readonly=${this.readonly}
                    .portal=${this.portal}
                    ?openOnFocus=${this.openDropdownOnFocus}
                    exportparts="options__wrapper, input, base"
                    .placeholder=${this.placeholder}
                    @nile-input=${this.handleInputChange}
                    @keydown=${this.handleInputKeydown}
                    @nile-focus=${this.handleFocus}
                    @nile-blur=${this.handleBlur}
                    @nile-complete=${this.handleSelect}
                    @mousedown=${this.markDropdownSelectStart}
                    .noDropdownClose=${this.noDropdownClose}
                    ?required=${this.required}
                  ></nile-auto-complete>
                `}
          </div>
        </div>
        ${hasHelpText
          ? html` <nile-form-help-text>${this.helpText}</nile-form-help-text> `
          : ``}
        ${hasErrorMessage
          ? html`
              <nile-form-error-message
                >${this.errorMessage}</nile-form-error-message
              >
            `
          : ``}
      </div>
    `;
  }

  private handleSelect(event: CustomEvent<CustomEventDetail>) {
    this.isSelectingFromDropdown = false;
    this.resetDropdownState();
    // Add the selected value to the tags array only if it doesn't already exist
    const selectedValue = event.detail.value;
    const selectedOption = this.autoCompleteOptions.find(
      (opt) => opt.name === selectedValue || opt.id === selectedValue
    );

    let tooltipContent: string | null = null;
  
    if (this.showTooltip) {
      if (selectedOption?.tooltip?.content) {
        const { content, for: showFor } = selectedOption.tooltip;
        if (!showFor || showFor === 'tag') {
          if (content instanceof Promise) {
            this.tooltips = [...this.tooltips, 'Loading...'];
  
            const currentIndex = this.tooltips.length - 1;
            content.then((resolved) => {
              this.tooltips[currentIndex] = resolved;
              this.requestUpdate();
            });
          } else {
            tooltipContent = content;
          }
        }
      } else {
        tooltipContent = selectedOption?.name || selectedValue;
      }
    }

    if (!this.noDuplicates || !this.tags.includes(selectedValue)) {
      this.tags = [...this.tags, selectedValue];
  

      if (!(selectedOption?.tooltip?.content instanceof Promise)) {
        this.tooltips = [...this.tooltips, tooltipContent];
      }
  
      this.emit('nile-chip-change', { value: this.tags });
      this.resetInput();
    }
  }

  private handleRemove(value: string) {
    // Remove the tag from the tags array
    this.tags = this.tags.filter(tag => tag !== value);

    if (this.noDuplicates && this.autoCompleteOptions.includes(value)) {
      this.filteredAutoCompleteOptions = [
        ...this.filteredAutoCompleteOptions,
        value,
      ];
    }
    this.emit('nile-chip-change', { value: this.tags });
  }

  private handleInputChange(event: CustomEvent<CustomEventDetail>) {
    // Update the input value
    this.inputValue = event.detail.value;
  }

  private addChipFromInputValue(): boolean {
    if (this.noDuplicates && this.tags.includes(this.inputValue)) {
      this.emit('nile-duplicates-blocked');
      return false;
    }

    this.tags = [...this.tags, this.inputValue];
    if (this.showTooltip) {
      this.tooltips = [...this.tooltips, this.inputValue];
    } else {
      this.tooltips = [...this.tooltips, null];
    }

    this.inputValue = '';
    this.visibilityManager?.cleanup();
    this.emit('nile-chip-change', { value: this.tags });
    return true;
  }

  private handleInputKeydown(event: KeyboardEvent) {
    if (
      this.enableTagDelete &&
      (event.key === 'Backspace' || event.key === 'Delete') &&
      !this.inputValue &&
      !this.readonly &&
      !this.disabled
    ) {
      if (this.tags.length === 0) {
        this.chipFocusIndex = null;
        return;
      }

      event.preventDefault();
      if (this.chipFocusIndex === null) {
        this.chipFocusIndex = this.tags.length - 1;
        this.requestUpdate();
        return;
      }
      const index = this.chipFocusIndex;
      const removedTag = this.tags[index];

      this.tags = this.tags.filter((_, i) => i !== index);
      this.tooltips = this.tooltips.filter((_, i) => i !== index);

      this.emit('nile-chip-change', { value: this.tags });
      if (this.tags.length > 0) {
        this.chipFocusIndex = Math.max(0, index - 1);
      } else {
        this.chipFocusIndex = null;
      }

      return;
    }

    if (this.readonly) {
      const allowedKeys = ['ArrowUp', 'ArrowDown', 'Enter', 'Tab'];
      if (!allowedKeys.includes(event.key)) {
        event.preventDefault();
        return;
      }
    }

    if (!this.acceptUserInput) {
      return;
    }

    if(event.key === 'Tab'){
      event.preventDefault()
    }
    
    if (
      (event.key === 'Enter' || event.key === 'Tab' ) 
      && this.inputValue 
      && (!this.noDuplicates || !this.tags.includes(this.inputValue))
    ) {
      event.preventDefault()
      this.tags = [...this.tags, this.inputValue];
      if (this.showTooltip) {
        this.tooltips = [...this.tooltips, this.inputValue];
      } else {
        this.tooltips = [...this.tooltips, null];
      }
    
      this.resetInput();
      this.emit('nile-chip-change', { value: this.tags });
    }

    if(
      (event.key === 'Enter'|| event.key === 'Tab' ) 
      && this.inputValue 
      && (this.noDuplicates || this.tags.includes(this.inputValue))
    ){
      this.emit('nile-duplicates-blocked');
    }
  }

  private handleFocus() {
    if (this.noAutoComplete) {
      return;
    }
    this.visibilityManager?.setup();
    this.isDropdownOpen = true;
  }

  private handleBlur() {
    if (this.isSelectingFromDropdown) return;
    if (this.addOnBlur && this.acceptUserInput && this.inputValue) {
      this.addChipFromInputValue();
    }
  }

  onTagsChanged() {
    if (this.noDuplicates)
      this.filteredAutoCompleteOptions = this.filteredAutoCompleteOptions.filter(option => !this.tags.includes(option));
  }


  private resetInput() {
    // Reset the input-related properties
    this.inputValue = '';
    this.isDropdownOpen = false;
    this.visibilityManager?.cleanup();
    if (!this.noAutoComplete && this.autoComplete) {
      this.autoComplete.value = '';
      this.autoComplete.handleFocus();
    }
  }  
}

export default NileChip;

declare global {
  interface HTMLElementTagNameMap {
    'nile-chip': NileChip;
  }
}
