import {
  AfterViewInit,
  ChangeDetectorRef,
  Component,
  EventEmitter,
  HostListener,
  Input,
  OnInit,
  Output,
  ViewChild,
  ViewEncapsulation
} from '@angular/core'

export type OnLoadingMoreEvent = {
  loadMoreFinished: (moreExists: boolean) => void
}

@Component({
  selector: 'app-autocomplete',
  templateUrl: './autocomplete.component.html',
  styleUrls: ['./autocomplete.component.scss'],
  encapsulation: ViewEncapsulation.None,
  standalone: false
})
export class AutocompleteComponent implements OnInit, AfterViewInit {
  @ViewChild('input') inputElement: any

  @Input() inputId: string = ''
  @Input() placeholder: string = ''
  @Input() value: string = ''
  @Input() disabled: boolean = false
  @Input() type: string = 'text'
  @Input() autocomplete: string = 'off'
  @Input() tabindex: string = ''
  @Input() emitOnlySelected: boolean = false
  @Input() enableLoadMore: boolean = false

  @Output() onInputKeydown: EventEmitter<any> = new EventEmitter()
  @Output() onInputChange: EventEmitter<any> = new EventEmitter()
  @Output() valueChange: EventEmitter<any> = new EventEmitter()
  @Output() onLoadingMore: EventEmitter<OnLoadingMoreEvent> = new EventEmitter()

  datalistOpen: boolean = false
  afterOpenLockDelay: number = 150
  afterOpenLockDelayTimeout: any
  afterOpenLock: boolean = false

  inputFocused: boolean = false
  inputClientRect: {
    width?: number
    bottom?: number
  } = {}
  innerHeight: number | undefined
  datalistCloseTimeout: any
  preventDatalistClose: boolean = false

  loadingMore: boolean = false

  constructor(private cdr: ChangeDetectorRef) {}

  ngOnInit(): void {}

  ngAfterViewInit(): void {
    setTimeout(() => {
      this.calculateInputPosition()
    }, 500)
  }

  onInputFired(event?: any, emitEvent?: boolean, initiator?: 'click') {
    this.openDatalist(initiator !== 'click')
    this.unselectAllOptions()
    this.filterOptionsList()

    if (!this.emitOnlySelected || emitEvent) {
      this.onInputChange.emit({ target: { value: this.value } })
      this.valueChange.emit(this.value)
    }
  }

  onInputClick(event: any) {
    this.openDatalist()
  }

  openDatalist(lockDelay: boolean = true) {
    this.datalistOpen = true

    if (lockDelay) {
      this.afterOpenLock = true

      this.afterOpenLockDelayTimeout = setTimeout(() => {
        this.afterOpenLock = false
      }, this.afterOpenLockDelay)
    }
  }

  closeDatalist() {
    clearTimeout(this.afterOpenLockDelayTimeout)
    this.afterOpenLock = false
    this.datalistOpen = false
  }

  resetDatalistFilter() {
    this.filterOptionsList(true)
  }

  onInputFocusin(event: any) {
    this.inputFocused = true
  }

  onInputFocusout(event: any) {
    this.inputFocused = false

    this.setToCloseDatalist()
  }

  onInputMouseenter(event: any) {
    // If width not set it means calculation failed on init (input was hidden)
    if (!this.inputClientRect.width) this.calculateInputPosition()
  }

  filterOptionsList(showAll?: boolean) {
    const options = document.querySelectorAll(
      `#datalist_${this.inputId} option`
    )

    for (let i = 0; i < options.length; i++) {
      const option = options[i]

      if (this.value.length === 0) {
        option.classList.remove('hidden')

        continue
      }

      if (showAll) {
        option.classList.remove('hidden')

        continue
      }

      const optionLabel = (option as any).innerText || ''
      const optionValue = (option as any).value || ''
      const combined = optionLabel + optionValue

      if (combined?.toLowerCase().includes(this.value.toLowerCase())) {
        option.classList.remove('hidden')
      } else {
        option.classList.add('hidden')
      }
    }
  }

  setToCloseDatalist() {
    this.datalistCloseTimeout = setTimeout(() => {
      if (this.preventDatalistClose || this.afterOpenLock) return

      this.closeDatalist()
      this.unselectAllOptions()
      this.resetDatalistFilter()
    }, 100)
  }

  calculateInputPosition() {
    const inputElement = this.inputElement.nativeElement
    const clientRect = inputElement?.getBoundingClientRect()

    if (clientRect.width > 0 && clientRect.height > 0) {
      this.inputClientRect = {
        width: clientRect.width,
        bottom: clientRect.bottom
      }
    }

    this.innerHeight = window.innerHeight
  }

  onDataListMouseEnter(event: any) {
    this.preventDatalistClose = true
  }

  onDataListMouseLeave(event: any) {
    this.preventDatalistClose = false
    this.inputElement.nativeElement.focus()
  }

  onDataListScroll(event: any) {
    this.setToCloseDatalist()
  }

  onDataListClick(event: any) {
    const clickedOption = event.target
    const itemType = clickedOption.dataset?.type

    if (itemType === 'load-more') {
      this.loadMore()
      return
    }

    if (clickedOption.tagName === 'OPTION') {
      this.setOptionValue(clickedOption.innerText, clickedOption.value)
      return
    }
  }

  loadMore() {
    this.onLoadingMore.emit({
      loadMoreFinished: (moreExists: boolean) => {
        this.loadingMore = false
        this.enableLoadMore = moreExists
      }
    })
    this.loadingMore = true
  }

  setOptionValue(label: string, value: string) {
    if (label || value) {
      if (!value) value = label
    }

    if (value) {
      this.preventDatalistClose = false
      this.value = value
      this.onInputFired(null, true, 'click')
      this.setToCloseDatalist()
    }
  }

  onMainKeydown(event: KeyboardEvent) {
    switch (event.key) {
      case 'ArrowUp': {
        this.getOptionsMoveFocus('up')
        break
      }
      case 'ArrowDown': {
        this.getOptionsMoveFocus('down')
        break
      }
      case 'Enter': {
        this.selectFocusedOption()
        break
      }
    }
  }

  onInputKeyup(event: any) {
    if (!this.datalistOpen) {
      return true
    }

    // We prevent propagation of Escape event
    // since it would close modal also if input is
    // inside of it (or trigger any other actions that are listening on escape)
    if (event.key === 'Escape') {
      event.stopPropagation()
      event.stopImmediatePropagation()

      this.setToCloseDatalist()

      return false
    }
  }

  unselectAllOptions() {
    const options = document.querySelectorAll(
      `#datalist_${this.inputId} option`
    )

    for (let i = 0; i < options.length; i++) {
      options[i].classList.remove('focused')
    }
  }

  selectFocusedOption() {
    const option = document.querySelector(
      `#datalist_${this.inputId} option.focused`
    )

    if (option) this.onDataListClick({ target: option })

    this.setToCloseDatalist()
  }

  getOptionsMoveFocus(direction: 'up' | 'down') {
    const options = document.querySelectorAll(
      `#datalist_${this.inputId} option:not(.hidden)`
    )
    let focusedIndex = 0

    for (let i = 0; i < options.length; i++) {
      const option = options[i]

      if (option.classList.contains('focused')) {
        focusedIndex = direction === 'down' ? i + 1 : i - 1
        if (focusedIndex < 0) focusedIndex = options.length - 1
        if (focusedIndex > options.length - 1) focusedIndex = 0
        option.classList.remove('focused')

        break
      }
    }

    options[focusedIndex].classList.add('focused')
  }

  @HostListener('window:resize', ['$event'])
  onResize(event: any) {
    this.calculateInputPosition()
  }
}
