/* global customElements */
const LENGTH = 128

const ledger = []
const listeners = []

function getComponentModel (el) {
  return globalThis.ami.componentModels.get(el?.closest('[data-name]')?.dataset?.key)
}

function addListeners (...args) {
  listeners.push(...args)
}

function getTextContent (el) {
  let contentEl = el
  if (el.querySelector('style')) {
    const clonedEl = el.cloneNode(true)
    for (const s of clonedEl.querySelectorAll('style, [aria-hidden]')) { s.remove() }
    contentEl = clonedEl
  }
  return contentEl.textContent.trim()
}

function record (entry) {
  ledger.push(entry)
  const offset = ledger.length - LENGTH
  if (offset > 0) {
    ledger.splice(0, offset)
  }

  for (const listener of listeners) {
    listener(entry)
  }
}

let hasLinkListener = false
function trackLinks () {
  if (hasLinkListener) {
    return
  }

  // Capture link click to prevent navigation from detaching the element.
  // This ensures tracking happens before the navigation.
  hasLinkListener = true
  document.addEventListener('click', event => {
    // Get all the clickable elements on the path (works inside Shadow DOM).
    const els = event.composedPath().filter(e => e?.classList?.contains?.('clickable'))

    if (els.length === 0) {
      return
    }

    const tagName = event.target.tagName.toLowerCase()
    const customElementTagName = customElements.get(tagName) ? tagName : null

    for (const el of els) {
      trackLink(el, customElementTagName)
    }
  }, { capture: true })
}

function trackLink (el, customElementTagName) {
  // For web components, use a dummy model with the organism set to the element name.
  const componentModel = {
    organism: customElementTagName,
    ...getComponentModel(el)
  }

  const linkModel = { name: el.title ? el.title : getTextContent(el) }
  const entry = { eventName: 'Button click', componentModel, linkModel }

  // This event fires before the expand/collapse event.
  // The aria-expanded attribute will not yet be updated.
  if (el.getAttribute('aria-expanded') === 'false' || el.parentElement.matches('details:not([open])')) {
    entry.eventName = 'Toggle'
    entry.value = 'Expanded'
  } else if (el.getAttribute('aria-expanded') === 'true' || el.parentElement.matches('details[open]')) {
    entry.eventName = 'Toggle'
    entry.value = 'Collapsed'
  } else if (el.href) {
    entry.linkModel.value = el.href
    entry.eventName = 'Link click'
  }

  record(entry)
}

let hasPageViewListener = false

function pageViewListener () {
  record({
    eventName: 'Page view',
    componentModel: getComponentModel(document.querySelector('[data-name="Page"]'))
  })
}

function trackPageView () {
  if (hasPageViewListener) {
    return
  }

  hasPageViewListener = true
  pageViewListener() // Current page view.
  globalThis.addEventListener('route', () => {
    pageViewListener()
  }) // All future page views.
}

export function initLedger () {
  if (globalThis?.ami?.ledger) {
    return
  }

  // Expose ledger listeners.
  globalThis.ledgerListeners = globalThis.ledgerListeners || []
  globalThis.ledgerListeners.push = function (handler, ...args) {
    if (globalThis.ledgerListeners.includes(handler)) {
      throw new Error('Handler already added.')
    }

    // Call handler for all existing entries.
    for (const entry of ledger) {
      handler(entry)
    }

    addListeners(handler, ...args)
    return Array.prototype.push.apply(this, [handler, ...args])
  }

  // Add existing ledger listeners.
  addListeners(...globalThis.ledgerListeners)

  globalThis.ami ||= {}
  globalThis.ami.ledger = ledger
  globalThis.ami.ledger.record = record

  trackPageView()
  trackLinks()
}
