/**
 * EdGEL menu component.
 *
 * Style a menu object as EdGEL's left content nav.
 *
 * The menu object can optionally be passed as a JSON string as the innerText.  If not provided
 * window.EdGEL.menu will be used instead.
 */

import { html } from 'lit'
import { ifDefined } from 'lit/directives/if-defined.js'
import { classMap } from 'lit/directives/class-map.js'

import { EdGELCollapse } from '../collapse.js'
import '../../edgel-icon.js'

const DEFAULT_PROTECTED_MSG = 'protected resource'

export class EdGELMenu extends EdGELCollapse {
  static properties = {
    expanded: { type: Boolean, attribute: false }
  }

  constructor() {
    super()

    this.itemClass = 'uoe-nav-link'
    this.activeClass = 'uoe-nav-link-active'

    this.expanded = false
    this.controls = 'uoe-menu'
    this._breadcrumb = []
    this._leaves = []

    this._setMenuHtml = this._setMenuHtml.bind(this)
    this.observer = new MutationObserver(this._setMenuHtml)
  }

  _setMenuHtml() {
    globalThis.EdGEL ||= {}

    try {
      const element = this.shadowRoot ? this.shadowRoot.host : this
      this.menu = JSON.parse(element.textContent.trim())

      // Inform other elements of the new menu setting
      globalThis.EdGEL.menu = structuredClone(this.menu)
      const event = new CustomEvent('updated.edgel.menu', {
        detail: {
          parts: Object.keys(this.menu)
        },
        bubbles: true,
        composed: true
      })
      this.dispatchEvent(event)
    } catch {
      if (!globalThis.EdGEL.menu) {
        throw new Error(`No valid menu supplied in the window.EdGEL.menu object or passed in the <${this.tagName.toLowerCase()}> tag.`)
      }

      this.menu = structuredClone(globalThis.EdGEL.menu)
    }

    this.collapseDisabled = true
    this._validateMenu()
    this._processMenu()

    this.menuHtml = this._renderMenu()
    this.collapseDisabled = !this.menuHtml

    this.requestUpdate()
  }

  connectedCallback() {
    super.connectedCallback()

    this._setMenuHtml()
    this.observer.observe(this.shadowRoot.host, { characterData: true, subtree: true })
  }

  disconnectedCallback() {
    super.disconnectedCallback()

    this.observer.disconnect()
  }

  _validateMenu() {
    if (!this.menu || !this.menu.breadcrumb) {
      throw new Error('No breadcrumb supplied in the menu object.')
    }

    if (!Array.isArray(this.menu.breadcrumb) || this.menu.breadcrumb.length < 1) {
      throw new Error('Breadcumb must be an array with at least one item.')
    }

    return true
  }

  _processMenu() {
    this._leaves = []
    this._breadcrumb = structuredClone(this.menu.breadcrumb)
    this._breadcrumb.at(-1)._active = true
    if (this.menu.children && Array.isArray(this.menu.children) && this.menu.children.length) {
      this._leaves = structuredClone(this.menu.children)
    } else if (this.menu.siblings && Array.isArray(this.menu.siblings) && this.menu.siblings.length) {
      // You can't have siblings of the breadcrumb home item.
      if (this._breadcrumb.length > 1) {
        const current = this._breadcrumb.pop()
        this._leaves = structuredClone(this.menu.siblings)
        const item = this._leaves.find(i => i.url === current.url)
        if (item) {
          item._active = true
        } else {
          this._leaves.unshift(current)
        }
      }
    } else if (this._breadcrumb.length > 2) {
      // No children or siblings, so treat the last breadcrumb item as
      this._leaves = [this._breadcrumb.pop()]
    }
  }

  _nestedList(items, depth = 0) {
    const item = items.shift()
    // The last item in the list is an already rendered list of leaves, so just return that as is.
    if (!items.length) {
      return item
    }

    let id
    item._classes = []
    if (!depth) {
      item._classes.push('uoe-nav-link-root')
      id = this.controls
    } else if (depth && items.length === 1 && items[0]) {
      // If this is the last item before the leaves, it is the "joint" item between breadcrumb and leaf menu.
      item._classes.push('uoe-nav-link-joint')
    } else if (items.length > 1) {
      // We are not the root item, and we're not the last breadcrumb item, so we're an ancestor.
      item._classes.push('uoe-nav-link-ancestor')
    }

    // Immediate children of root that are not ancestors are children.
    if (depth === 1 && items.length === 1) {
      item._classes.push('uoe-nav-link-child')
    }

    return html`
      <ul id=${ifDefined(id)}><li>
        ${this._itemLink(item)}
        ${this._nestedList(items, ++depth)}
      </li></ul>
    `
  }

  _lockSuffix(restricted) {
    return restricted ?
      html`
        <edgel-icon name="lock-fill"></edgel-icon>
        <span class="visually-hidden"> (${typeof restricted === 'string' ? restricted : DEFAULT_PROTECTED_MSG})</span>
      ` :
      html``
  }

  _itemLink({ url, text, restricted, _active, _classes = [], _attrs = {} }) {
    _classes.unshift(this.itemClass)

    if (_active) {
      _classes.push(this.activeClass)
    }

    const suffix = this._lockSuffix(restricted)

    return html`
      <a
          href=${url}
          class=${_classes.join(' ')}
          aria-current=${ifDefined(_active ? 'page' : undefined)}
          id=${ifDefined(_attrs.id)}
          role=${ifDefined(_attrs.role)}
          aria-expanded=${ifDefined(_attrs.expanded)}
          @click=${ifDefined(_attrs.click)}
          data-bs-toggle=${ifDefined(_attrs.toggle)}
      >
        ${text}
        ${suffix}
      </a>
    `
  }

  _renderMenu() {
    // If there is no breadcrumb, we cannot render the menu.  There must always be a home which
    // cannot have siblings.
    if (!this._breadcrumb.length) {
      return html``
    }

    // Build the leaves list.
    let leaves = ''
    const leafClass = this._breadcrumb.length > 1 ? 'uoe-nav-link-leaf' : 'uoe-nav-link-child'

    if (this._leaves.length) {
      const leafTemplates = []
      for (const leaf of this._leaves) {
        leaf._classes = [leafClass]
        leafTemplates.push(html`<li>${this._itemLink(leaf)}</li>`)
      }

      leaves = html`<ul>${leafTemplates}</ul>`
    }

    const tree = structuredClone(this._breadcrumb)
    tree.push(leaves)

    return this._nestedList(tree)
  }

  render() {
    if (this.collapseDisabled) {
      return
    }

    const hamburgerClasses = {
      hamburger: true,
      'hamburger-expanded': this.expanded
    }
    const buttonText = html`
      <span class=${classMap(hamburgerClasses)} aria-hidden="true"></span>
      <span class="visually-hidden">Toggle Navigation</span> Menu
    `
    const button = this.buttonHtml(buttonText, 'btn-uoe-nav-toggler')

    return html`
      <span class="edgel-js">
        <nav aria-label="Content navigation" class="uoe-nav">
          ${button}
          ${this.menuHtml}
        </nav>
      </span>
    `
  }
}
