import {
  EventHandlerUtil,
  DataUtil,
  getBreakpoint,
  getAttributeValueByBreakpoint,
  stringSnakeToCamel,
  getObjectPropertyValueByKey,
  getViewPort,
  isVisibleElement,
  throttle,
} from '../_utils/index'

import {MenuComponent, defaultMenuOptions} from './MenuComponent'

export interface ISearchOptions {
  minLength: number // Miniam text lenght to query search
  keypress: boolean // Enable search on keypress
  enter: boolean // Enable search on enter key press
  layout: 'menu' | 'inline' // Use 'menu' or 'inline' layout options to display search results
  responsive?: number // Pass integer value or bootstrap compatible breakpoint key(sm,md,lg,xl,xxl) to enable reponsive form mode for device width below the breakpoint value
  showOnFocus: boolean // Always show menu on input focus
}

export interface ISearchQueries {
  componentName: string
  instanseQuery: string
  attrQuery: string
}

const defaultSearchOptions: ISearchOptions = {
  minLength: 2, // Miniam text lenght to query search
  keypress: true, // Enable search on keypress
  enter: true, // Enable search on enter key press
  layout: 'menu', // Use 'menu' or 'inline' layout options to display search results
  showOnFocus: true, // Always show menu on input focus
}

const defaultSearchQueires: ISearchQueries = {
  componentName: 'search',
  instanseQuery: '[data-kt-search]',
  attrQuery: 'data-kt-search-',
}

class SearchComponent {
  element: HTMLElement
  contentElement: HTMLElement
  formElement: HTMLFormElement
  inputElement: HTMLInputElement
  spinnerElement: HTMLElement
  clearElement: HTMLElement
  toggleElement: HTMLElement
  submitElement: HTMLElement
  toolbarElement: HTMLElement
  resultsElement: HTMLElement
  suggestionElement: HTMLElement
  emptyElement: HTMLElement
  layout: any

  options: ISearchOptions
  queries: ISearchQueries

  processing: boolean = false
  menuObject: MenuComponent | undefined

  constructor(_element: HTMLElement, _options: ISearchOptions, _queries: ISearchQueries) {
    // Variables
    this.options = Object.assign(defaultSearchOptions, _options)
    this.queries = _queries

    // Elements
    this.element = _element
    this.contentElement = this._getElement('content') as HTMLElement
    this.formElement = this._getElement('form') as HTMLFormElement
    this.inputElement = this._getElement('input') as HTMLInputElement
    this.spinnerElement = this._getElement('spinner') as HTMLElement
    this.clearElement = this._getElement('clear') as HTMLElement
    this.toggleElement = this._getElement('toggle') as HTMLElement
    this.submitElement = this._getElement('submit') as HTMLElement
    this.toolbarElement = this._getElement('toolbar') as HTMLElement

    this.resultsElement = this._getElement('results') as HTMLElement
    this.suggestionElement = this._getElement('suggestion') as HTMLElement
    this.emptyElement = this._getElement('empty') as HTMLElement

    // Layout
    this.layout = this.getOption('layout')
    if (this.layout === 'menu') {
      this.menuObject = new MenuComponent(this.contentElement, defaultMenuOptions)
    }

    // Update
    this.update()

    // Event Handlers
    this.handlers()

    DataUtil.set(this.element, this.queries.componentName, this)
  }

  private _getElement = (name: string) => {
    return this.element.querySelector('[data-kt-search-element="' + name + '"]')
  }

  // Get option
  private getOption = (name: string) => {
    const attr = this.element.getAttribute(`${this.queries.attrQuery}${name}`)
    if (attr) {
      let value = getAttributeValueByBreakpoint(attr)

      if (value !== null && String(value) === 'true') {
        return true
      } else if (value !== null && String(value) === 'false') {
        return false
      }

      return value
    } else {
      const optionName = stringSnakeToCamel(name)

      const option = getObjectPropertyValueByKey(this.options, optionName)
      if (option) {
        return getAttributeValueByBreakpoint(option)
      } else {
        return null
      }
    }
  }

  // Check if responsive form mode is enabled
  private getResponsiveFormMode = () => {
    const responsive = this.getOption('responsive') as string
    const width = getViewPort().width

    if (!responsive) {
      return null
    }

    const breakpoint = getBreakpoint(responsive)
    let breakPointNum = -1
    if (!breakpoint) {
      breakPointNum = parseInt(responsive)
    } else {
      breakPointNum = +breakpoint
    }

    if (width < breakPointNum) {
      return 'on'
    } else {
      return 'off'
    }
  }

  // Focus
  private focus = () => {
    this.element.classList.add('focus')

    if (
      this.getOption('show-on-focus') === true ||
      this.inputElement.value.length >= this.options.minLength
    ) {
      this.show()
    }
  }

  // Blur
  private blur = () => {
    this.element.classList.remove('focus')
  }

  // Enter
  private enter = (e: KeyboardEvent) => {
    const key = e.charCode || e.keyCode || 0

    if (key === 13) {
      e.preventDefault()

      this.search()
    }
  }

  // Input
  private input = () => {
    if (this.getOption('min-length')) {
      const minLength = parseInt(this.getOption('min-length') as string)

      if (this.inputElement.value.length >= minLength) {
        this.search()
      } else if (this.inputElement.value.length === 0) {
        this.clear()
      }
    }
  }

  private handlers(): void {
    const context = this

    // Focus
    this.inputElement.addEventListener('focus', this.focus)

    // Blur
    this.inputElement.addEventListener('blur', this.blur)

    // Keypress
    if (this.getOption('keypress') === true) {
      this.inputElement.addEventListener('input', this.input)
    }

    // Submit
    if (this.submitElement) {
      this.submitElement.addEventListener('click', this.search)
    }

    // Enter
    if (this.getOption('enter') === true) {
      this.inputElement.addEventListener('keypress', this.enter)
    }

    // Clear
    if (this.clearElement) {
      this.clearElement.addEventListener('click', this.clear)
    }

    // Menu
    if (this.menuObject) {
      // Toggle menu
      if (this.toggleElement) {
        this.toggleElement.addEventListener('click', this.show)

        this.menuObject.on('kt.menu.dropdown.show', function () {
          // @ts-ignore
          if (isVisibleElement(context.toggleElement)) {
            // @ts-ignore
            context.toggleElement.classList.add('active')
            // @ts-ignore
            context.toggleElement.classList.add('show')
          }
        })

        this.menuObject.on('kt.menu.dropdown.hide', function () {
          // @ts-ignore
          if (isVisibleElement(context.toggleElement)) {
            // @ts-ignore
            context.toggleElement.classList.remove('active')
            // @ts-ignore
            context.toggleElement.classList.remove('show')
          }
        })
      }

      this.menuObject.on('kt.menu.dropdown.shown', function () {
        // @ts-ignore
        context.inputElement.focus()
      })
    }

    // Window resize handling
    window.addEventListener('resize', () => {
      let timer

      throttle(
        timer,
        () => {
          this.update()
        },
        200
      )
    })
  }

  ///////////////////////
  // ** Public API  ** //
  ///////////////////////
  // Update
  public update = () => {
    // Handle responsive form
    if (this.layout === 'menu') {
      let responsiveFormMode = this.getResponsiveFormMode()

      if (responsiveFormMode === 'on' && !this.contentElement.contains(this.formElement)) {
        this.contentElement.prepend(this.formElement)
        this.formElement.classList.remove('d-none')
      } else if (responsiveFormMode === 'off' && this.contentElement.contains(this.formElement)) {
        this.element.prepend(this.formElement)
        this.formElement.classList.add('d-none')
      }
    }
  }

  // Show menu
  public show = () => {
    if (this.menuObject) {
      this.update()

      this.menuObject.show(this.element)
    }
  }

  // Hide menu
  public hide = () => {
    if (this.menuObject) {
      this.update()

      this.menuObject.hide(this.element)
    }
  }

  // Search
  public search = () => {
    if (!this.processing) {
      // Show search spinner
      if (this.spinnerElement) {
        this.spinnerElement.classList.remove('d-none')
      }

      // Hide search clear button
      if (this.clearElement) {
        this.clearElement.classList.add('d-none')
      }

      // Hide search toolbar
      if (this.toolbarElement) {
        this.toolbarElement.classList.add('d-none')
      }

      // Focus input
      this.inputElement.focus()

      this.processing = true
      EventHandlerUtil.trigger(this.element, 'kt.search.process', this)
    }
  }

  // Complete
  public complete = () => {
    if (this.spinnerElement) {
      this.spinnerElement.classList.add('d-none')
    }

    // Show search toolbar
    if (this.clearElement) {
      this.clearElement.classList.remove('d-none')
    }

    if (this.inputElement.value.length === 0) {
      this.clear()
    }

    // Focus input
    this.inputElement.focus()

    this.show()

    this.processing = false
  }

  // Clear
  public clear = () => {
    if (EventHandlerUtil.trigger(this.element, 'kt.search.clear') === false) {
      return
    }

    // Clear and focus input
    this.inputElement.value = ''
    this.inputElement.focus()

    // Hide clear icon
    if (this.clearElement) {
      this.clearElement.classList.add('d-none')
    }

    // Show search toolbar
    if (this.toolbarElement) {
      this.toolbarElement.classList.remove('d-none')
    }

    // Hide menu
    if (this.getOption('show-on-focus') === false) {
      this.hide()
    }

    EventHandlerUtil.trigger(this.element, 'kt.search.cleared')
  }

  public isProcessing = () => {
    return this.processing
  }

  public getQuery = () => {
    return this.inputElement.value
  }

  public getMenu = () => {
    return this.menuObject
  }

  public getFormElement = () => {
    return this.formElement
  }

  public getInputElement(): HTMLInputElement {
    return this.inputElement
  }

  public getContentElement(): HTMLElement {
    return this.contentElement
  }

  public getElement(): HTMLElement {
    return this.element
  }

  // Event API
  public on = (name: string, handler: Function) => {
    return EventHandlerUtil.on(this.element, name, handler)
  }

  public one = (name: string, handler: Function) => {
    return EventHandlerUtil.one(this.element, name, handler)
  }

  public off = (name: string, handlerId: string) => {
    return EventHandlerUtil.off(this.element, name, handlerId)
  }

  // Static methods
  public static getInstance = (
    el: HTMLElement,
    componentName: string = defaultSearchQueires.componentName
  ) => {
    const Search = DataUtil.get(el, componentName)
    if (Search) {
      return Search as SearchComponent
    }

    return null
  }

  public static createInstances = (
    selector: string = defaultSearchQueires.instanseQuery,
    options: ISearchOptions = defaultSearchOptions,
    queries: ISearchQueries = defaultSearchQueires
  ) => {
    const elements = document.body.querySelectorAll(selector)
    elements.forEach((el) => {
      const item = el as HTMLElement
      let Search = SearchComponent.getInstance(item)
      if (!Search) {
        Search = new SearchComponent(item, options, queries)
      }
    })
  }

  public static createInsance = (
    selector: string = defaultSearchQueires.instanseQuery,
    options: ISearchOptions = defaultSearchOptions,
    queries: ISearchQueries = defaultSearchQueires
  ): SearchComponent | undefined => {
    const element = document.body.querySelector(selector)
    if (!element) {
      return
    }
    const item = element as HTMLElement
    let Search = SearchComponent.getInstance(item)
    if (!Search) {
      Search = new SearchComponent(item, options, queries)
    }
    return Search
  }

  public static bootstrap = (selector: string = defaultSearchQueires.instanseQuery) => {
    SearchComponent.createInstances(selector)
  }

  public static reinitialization = (selector: string = defaultSearchQueires.instanseQuery) => {
    SearchComponent.createInstances(selector)
  }
}
export {SearchComponent, defaultSearchOptions, defaultSearchQueires}
