class GitLabDropdownFilter
  BLUR_KEYCODES = [27, 40]
  HAS_VALUE_CLASS = "has-value"

  constructor: (@input, @options) ->
    $inputContainer = @input.parent()
    $clearButton = $inputContainer.find('.js-dropdown-input-clear')

    # Clear click
    $clearButton.on 'click', (e) =>
      e.preventDefault()
      e.stopPropagation()
      @input
        .val('')
        .trigger('keyup')
        .focus()

    # Key events
    timeout = ""
    @input.on "keyup", (e) =>
      if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS
        $inputContainer.addClass HAS_VALUE_CLASS
      else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
        $inputContainer.removeClass HAS_VALUE_CLASS

      if e.keyCode is 13 and @input.val() isnt ""
        if @options.enterCallback
          @options.enterCallback()
        return

      clearTimeout timeout
      timeout = setTimeout =>
        blur_field = @shouldBlur e.keyCode
        search_text = @input.val()

        if blur_field
          @input.blur()

        if @options.remote
          @options.query search_text, (data) =>
            @options.callback(data)
        else
          @filter search_text
      , 250

  shouldBlur: (keyCode) ->
    return BLUR_KEYCODES.indexOf(keyCode) >= 0

  filter: (search_text) ->
    data = @options.data()
    results = data

    if search_text isnt ""
      results = fuzzaldrinPlus.filter(data, search_text,
        key: @options.keys
      )

    @options.callback results

class GitLabDropdownRemote
  constructor: (@dataEndpoint, @options) ->

  execute: ->
    if typeof @dataEndpoint is "string"
      @fetchData()
    else if typeof @dataEndpoint is "function"
      if @options.beforeSend
        @options.beforeSend()

      # Fetch the data by calling the data funcfion
      @dataEndpoint "", (data) =>
        if @options.success
          @options.success(data)

        if @options.beforeSend
          @options.beforeSend()

  # Fetch the data through ajax if the data is a string
  fetchData: ->
    $.ajax(
      url: @dataEndpoint,
      dataType: @options.dataType,
      beforeSend: =>
        if @options.beforeSend
          @options.beforeSend()
      success: (data) =>
        if @options.success
          @options.success(data)
    )

class GitLabDropdown
  LOADING_CLASS = "is-loading"
  PAGE_TWO_CLASS = "is-page-two"
  ACTIVE_CLASS = "is-active"

  constructor: (@el, @options) ->
    self = @
    @dropdown = $(@el).parent()
    search_fields = if @options.search then @options.search.fields else [];

    if @options.data
      # Remote data
      @remote = new GitLabDropdownRemote @options.data, {
        dataType: @options.dataType,
        beforeSend: @toggleLoading.bind(@)
        success: (data) =>
          @fullData = data

          @parseData @fullData
      }

    # Init filiterable
    if @options.filterable
      @input = @dropdown.find('.dropdown-input .dropdown-input-field')

      @filter = new GitLabDropdownFilter @input,
        remote: @options.filterRemote
        query: @options.data
        keys: @options.search.fields
        data: =>
          return @fullData
        callback: (data) =>
          @parseData data
          @highlightRow 1
        enterCallback: =>
          @selectFirstRow()

    # Event listeners

    @dropdown.on "shown.bs.dropdown", @opened
    @dropdown.on "hidden.bs.dropdown", @hidden
    @dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate

    if @dropdown.find(".dropdown-toggle-page").length
      @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
        e.preventDefault()
        e.stopPropagation()

        @togglePage()

    if @options.selectable
      selector = ".dropdown-content a"

      if @dropdown.find(".dropdown-toggle-page").length
        selector = ".dropdown-page-one .dropdown-content a"

      @dropdown.on "click", selector, (e) ->
        selected = self.rowClicked $(@)

        if self.options.clicked
          self.options.clicked(selected)

  toggleLoading: ->
    $('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS

  togglePage: ->
    menu = $('.dropdown-menu', @dropdown)

    if menu.hasClass(PAGE_TWO_CLASS)
      if @remote
        @remote.execute()

    menu.toggleClass PAGE_TWO_CLASS

  parseData: (data) ->
    @renderedData = data

    # Render each row
    html = $.map data, (obj) =>
      return @renderItem(obj)

    if @options.filterable and data.length is 0
      # render no matching results
      html = [@noResults()]

    # Render the full menu
    full_html = @renderMenu(html.join(""))

    @appendMenu(full_html)

  shouldPropagate: (e) =>
    if @options.multiSelect
      $target = $(e.target)
      if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon')
        e.stopPropagation()
        return false
      else
        return true

  opened: =>
    contentHtml = $('.dropdown-content', @dropdown).html()
    if @remote && contentHtml is ""
      @remote.execute()

    if @options.filterable
      @dropdown.find(".dropdown-input-field").focus()

    @dropdown.trigger('shown.gl.dropdown')

  hidden: (e) =>
    if @options.filterable
      @dropdown
        .find(".dropdown-input-field")
        .blur()
        .val("")
        .trigger("keyup")

    if @dropdown.find(".dropdown-toggle-page").length
      $('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS

    if @options.hidden
      @options.hidden.call(@,e)

    @dropdown.trigger('hidden.gl.dropdown')


  # Render the full menu
  renderMenu: (html) ->
    menu_html = ""

    if @options.renderMenu
      menu_html = @options.renderMenu(html)
    else
      menu_html = "<ul>#{html}</ul>"

    return menu_html

  # Append the menu into the dropdown
  appendMenu: (html) ->
    selector = '.dropdown-content'
    if @dropdown.find(".dropdown-toggle-page").length
      selector = ".dropdown-page-one .dropdown-content"

    $(selector, @dropdown).html html

  # Render the row
  renderItem: (data) ->
    html = ""

    return "<li class='divider'></li>" if data is "divider"

    if @options.renderRow
      # Call the render function
      html = @options.renderRow(data)
    else
      selected = if @options.isSelected then @options.isSelected(data) else false
      if not selected
        value = if @options.id then @options.id(data) else data.id
        fieldName = @options.fieldName
        field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
        if field.length
          selected = true

      url = if @options.url then @options.url(data) else "#"
      text = if @options.text then @options.text(data) else ""
      cssClass = "";

      if selected
        cssClass = "is-active"

      html = "<li>"
      html += "<a href='#{url}' class='#{cssClass}'>"
      html += text
      html += "</a>"
      html += "</li>"

    return html

  noResults: ->
    html = "<li>"
    html += "<a href='#' class='dropdown-menu-empty-link is-focused'>"
    html += "No matching results."
    html += "</a>"
    html += "</li>"

  highlightRow: (index) ->
    if @input.val() isnt ""
      selector = '.dropdown-content li:first-child a'
      if @dropdown.find(".dropdown-toggle-page").length
        selector = ".dropdown-page-one .dropdown-content li:first-child a"

      $(selector).addClass 'is-focused'

  rowClicked: (el) ->
    fieldName = @options.fieldName
    selectedIndex = el.parent().index()
    if @renderedData
      selectedObject = @renderedData[selectedIndex]
    value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
    field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
  
    if el.hasClass(ACTIVE_CLASS)
      el.removeClass(ACTIVE_CLASS)
      field.remove()

      # Toggle the dropdown label
      if @options.toggleLabel
        $(@el).find(".dropdown-toggle-text").text @options.toggleLabel
    else
      if !value?
        field.remove()

      if not @options.multiSelect
        @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
        @dropdown.parent().find("input[name='#{fieldName}']").remove()

      # Toggle active class for the tick mark
      el.addClass ACTIVE_CLASS

      # Toggle the dropdown label
      if @options.toggleLabel
        $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
      if value?
        if !field.length
          # Create hidden input for form
          input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
          if @options.inputId?
            input = $(input)
                      .attr('id', @options.inputId)
          @dropdown.before input

      return selectedObject

  selectFirstRow: ->
    selector = '.dropdown-content li:first-child a'
    if @dropdown.find(".dropdown-toggle-page").length
      selector = ".dropdown-page-one .dropdown-content li:first-child a"

    # simulate a click on the first link
    $(selector).trigger "click"

$.fn.glDropdown = (opts) ->
  return @.each ->
    new GitLabDropdown @, opts