/**
 * EdGEL top nav component.
 */

import { html } from 'lit'
import EventHandler from 'bootstrap/js/src/dom/event-handler.js'
import Dropdown from 'bootstrap/js/src/dropdown.js'
import SelectorEngine from 'bootstrap/js/src/dom/selector-engine.js'
import { ifDefined } from 'lit/directives/if-defined.js'
import { EdGELMenu } from './menu.js'

const DATA_KEY = 'edgel.topnav'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'

const TAB_KEY = 'Tab'
// MouseEvent.button value for the secondary button, usually the right button
const RIGHT_MOUSE_BUTTON = 2

const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`

const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)'
const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.show`

export class EdGELTopNav extends EdGELMenu {
  constructor() {
    super()

    this.controls = 'uoe-topnav'
    this.itemClass = 'nav-link'
    this.activeClass = 'active'
    this._dropdowns = []

    this.clearMenus = this.clearMenus.bind(this)
  }

  _validateMenu() {
    if (!this.menu.topnav) {
      throw new Error('No top nav links supplied in the menu object.')
    }

    if (!Array.isArray(this.menu.topnav)) {
      throw new TypeError('The topnav property must be an array of links.')
    }

    return super._validateMenu()
  }

  _processMenu() {}

  _renderMenu() {
    const rendered = []

    for (let i = 0; i < this.menu.topnav.length; i++) {
      const item = { _classes: [], ...this.menu.topnav[i] }
      const id = `${this.controls}-${i}`

      const inBreadcrumb = this.menu.breadcrumb.findIndex(crumb => crumb.url === item.url)
      if (inBreadcrumb !== -1) {
        if (this.menu.breadcrumb.length === 1) {
          item._active = true
        } else {
          item._classes.push('active')
        }
      }

      if (item.children && item.children.length) {
        const dropdown = this._nestedList(item.children, id)
        item.url = '#'
        item._classes.push('dropdown-toggle')
        item._attrs = {
          id,
          role: 'button',
          expanded: 'false',
          click: this._toggleDropdown,
          toggle: 'dropdown'
        }

        rendered.push(html`<li class="nav-item dropdown">${this._itemLink(item)}${dropdown}</li>`)
      } else {
        rendered.push(html`<li class="nav-item">${this._itemLink(item)}</li>`)
      }
    }

    return html`<ul class="navbar-nav">${rendered}</ul>`
  }

  _nestedList(items, parentId) {
    const rendered = []

    for (const { url, text, restricted, children } of items) {
      if (children && children.length) {
        throw new Error('Only one level of dropdown is allowed in the top nav')
      }

      const classes = ['dropdown-item']
      let current
      const inBreadcrumb = this.menu.breadcrumb.findIndex(crumb => crumb.url === url)
      if (inBreadcrumb !== -1) {
        classes.push('active')
        if (inBreadcrumb === this.menu.breadcrumb.length - 1) {
          current = 'page'
        }
      }

      const suffix = this._lockSuffix(restricted)

      rendered.push(html`
        <li>
          <a
            href=${url}
            class=${classes.join(' ')}
            aria-current=${ifDefined(current)}
          >
            ${text}
            ${suffix}
          </a>
        </li>
      `)
    }

    return html`<ul class="dropdown-menu" aria-labelledby=${parentId}>${rendered}</ul>`
  }

  // Supplement Dropdown.clearMenus by repeating the search for open menus within the shadowDom
  clearMenus(event) {
    if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {
      return
    }

    const openToggles = this.shadowRoot.querySelectorAll(SELECTOR_DATA_TOGGLE_SHOWN)

    for (const toggle of openToggles) {
      const context = Dropdown.getInstance(toggle)
      const composedPath = event.composedPath()
      if (composedPath.includes(context._element)) {
        continue
      }

      // Tab navigation through the dropdown menu shouldn't close the menu
      if (composedPath.includes(context._menu) && (event.type === 'keyup' && event.key === TAB_KEY)) {
        continue
      }

      const relatedTarget = { relatedTarget: context._element }

      if (event.type === 'click') {
        relatedTarget.clickEvent = event
      }

      context._completeHide(relatedTarget)
    }
  }

  willUpdate(changedProperties) {
    super.willUpdate(changedProperties)

    for (const dropdown of this._dropdowns) {
      EventHandler.off(dropdown, EVENT_KEYDOWN_DATA_API, Dropdown.dataApiKeydownHandler)
      const menu = SelectorEngine.next(dropdown._element, '.dropdown-menu')[0]
      EventHandler.off(menu, EVENT_KEYDOWN_DATA_API, Dropdown.dataApiKeydownHandler)
      dropdown.dispose()
    }
  }

  updated(changedProperties) {
    this.collapseDisabled = !this.menuHtml
    super.updated(changedProperties)

    this._dropdowns = []
    const toggles = this.shadowRoot.querySelectorAll('a[role=button]')
    for (const toggle of toggles) {
      this._dropdowns.push(Dropdown.getOrCreateInstance(toggle))
      EventHandler.on(toggle, EVENT_KEYDOWN_DATA_API, Dropdown.dataApiKeydownHandler)
      const menu = SelectorEngine.next(toggle, '.dropdown-menu')[0]
      EventHandler.on(menu, EVENT_KEYDOWN_DATA_API, Dropdown.dataApiKeydownHandler)
    }
  }

  connectedCallback() {
    super.connectedCallback()

    EventHandler.on(document, EVENT_CLICK_DATA_API, this.clearMenus)
    EventHandler.on(document, EVENT_KEYUP_DATA_API, this.clearMenus)
  }

  disconnectedCallback() {
    super.disconnectedCallback()

    for (const dropdown of this._dropdowns) {
      dropdown.dispose()
    }

    EventHandler.off(document, EVENT_CLICK_DATA_API, this.clearMenus)
    EventHandler.off(document, EVENT_KEYUP_DATA_API, this.clearMenus)
  }

  _toggleDropdown(event) {
    event.preventDefault()
    event.stopPropagation()
    this.clearMenus(event)
    Dropdown.getInstance(event.target).toggle()
  }

  render() {
    if (!this.menuHtml) {
      return
    }

    const hamburgerClasses = this.expanded ?
      'hamburger hamburger-expanded' :
      'hamburger'

    const buttonText = html`
      <span aria-hidden="true" class=${hamburgerClasses}></span>
      <span class="visually-hidden">Toggle section links</span>
    `
    const button = this.buttonHtml(buttonText, 'navbar-toggler')

    return html`
      <span class="edgel-js">
        <nav class="navbar navbar-expand-md navbar-dark bg-dark" aria-label="Section navigation">
          <div class="container-topnav">
            ${button}
            <div class="collapse navbar-collapse" id=${this.controls}>
              ${this.menuHtml}
            </div>
          </div>
        </nav>
      </span>
    `
  }
}
