/**
 * AppShell - Layout composition for app chrome.
 */

import { getIconSvgInner, type IconSet } from '../icons/index'
import { resolveContainer } from '../primitives/utils'
import { setSafeIconHtml } from '../utils/sanitize'

export interface TopMenuBarBreadcrumb {
  label: string
  href?: string
}

export interface TopMenuBarAction {
  id: string
  label: string
  icon?: string
  href?: string
  target?: string
  badge?: boolean | string | number
  className?: string
  showLabel?: boolean
  attributes?: Record<string, string | number | boolean | null | undefined>
  onClick?: () => void
}

export type TopMenuBarActionInput = TopMenuBarAction | HTMLElement

export interface TopMenuBarUser {
  name: string
  detail?: string
  initials?: string
  avatarUrl?: string
  menuLabel?: string
  actions?: TopMenuBarAction[]
}

export interface TopMenuBarConfig {
  container?: HTMLElement | string
  product?: string
  current?: string
  breadcrumbs?: TopMenuBarBreadcrumb[]
  beforeActions?: HTMLElement | HTMLElement[]
  actions?: TopMenuBarActionInput[]
  afterActions?: HTMLElement | HTMLElement[]
  user?: TopMenuBarUser
  iconSet?: IconSet
  className?: string
}

export interface AppShellConfig {
  container?: HTMLElement | string
  sidebar?: HTMLElement
  header?: HTMLElement
  topbar?: HTMLElement | TopMenuBarConfig
  content?: HTMLElement
  metrics?: HTMLElement
  variant?: 'default' | 'product'
  sidebarCollapsed?: boolean
  className?: string
}

function createIconSvg(iconSet: IconSet, name: string): SVGElement {
  const svgNs = 'http://www.w3.org/2000/svg'
  const svg = document.createElementNS(svgNs, 'svg')
  svg.setAttribute('xmlns', svgNs)
  svg.setAttribute('width', '16')
  svg.setAttribute('height', '16')
  svg.setAttribute('viewBox', '0 0 24 24')
  svg.setAttribute('fill', 'none')
  svg.setAttribute('stroke', 'currentColor')
  svg.setAttribute('stroke-width', '2')
  svg.setAttribute('stroke-linecap', 'round')
  svg.setAttribute('stroke-linejoin', 'round')
  svg.setAttribute('aria-hidden', 'true')

  const inner = getIconSvgInner(iconSet, name)
  if (inner) {
    setSafeIconHtml(svg, inner)
  }

  return svg
}

function toInitials(name: string): string {
  const parts = name.trim().split(/\s+/).filter(Boolean)

  if (parts.length >= 2) {
    return `${parts[0]?.charAt(0) ?? ''}${parts[parts.length - 1]?.charAt(0) ?? ''}`.toUpperCase()
  }

  return name.slice(0, 2).toUpperCase()
}

function appendElements(
  parent: HTMLElement,
  elementOrElements: HTMLElement | HTMLElement[] | undefined
): void {
  if (elementOrElements === undefined) {
    return
  }

  const elements = Array.isArray(elementOrElements) ? elementOrElements : [elementOrElements]
  for (const element of elements) {
    parent.appendChild(element)
  }
}

function applyAttributes(
  element: HTMLElement,
  attributes: TopMenuBarAction['attributes'] | undefined
): void {
  if (attributes === undefined) {
    return
  }

  for (const [name, value] of Object.entries(attributes)) {
    if (value === undefined || value === null || value === false) {
      continue
    }

    element.setAttribute(name, value === true ? '' : String(value))
  }
}

function createActionElement(action: TopMenuBarActionInput, iconSet: IconSet): HTMLElement {
  if (action instanceof HTMLElement) {
    return action
  }

  const element =
    action.href !== undefined ? document.createElement('a') : document.createElement('button')

  element.classList.add('cmm-topbar__action')
  if (action.className !== undefined) {
    element.classList.add(...action.className.split(/\s+/).filter(Boolean))
  }
  element.setAttribute('aria-label', action.label)
  element.setAttribute('title', action.label)
  element.setAttribute('data-action-id', action.id)
  applyAttributes(element, action.attributes)

  if (element instanceof HTMLAnchorElement) {
    element.href = action.href ?? '#'
    if (action.target !== undefined) {
      element.target = action.target
    }
    if (action.target === '_blank') {
      element.rel = 'noreferrer noopener'
    }
  } else if (element instanceof HTMLButtonElement) {
    element.type = 'button'
  }

  if (action.onClick !== undefined) {
    element.addEventListener('click', action.onClick)
  }

  if (action.icon !== undefined) {
    element.appendChild(createIconSvg(iconSet, action.icon))
  }

  if (action.showLabel === true || action.icon === undefined) {
    const label = document.createElement('span')
    label.classList.add('cmm-topbar__action-label')
    label.textContent = action.label
    element.appendChild(label)
  }

  if (action.badge !== undefined && action.badge !== false) {
    const badge = document.createElement('span')
    badge.classList.add('cmm-topbar__action-badge')
    badge.textContent = action.badge === true ? '' : String(action.badge)
    element.appendChild(badge)
  }

  return element
}

export class TopMenuBar {
  public readonly el: HTMLElement
  private readonly cleanupFns: (() => void)[] = []

  constructor(config: TopMenuBarConfig = {}) {
    const iconSet = config.iconSet ?? 'lucide'
    this.el = document.createElement('header')
    this.el.className = ['cmm-topbar', config.className].filter(Boolean).join(' ')

    const crumbWrap = document.createElement('div')
    crumbWrap.classList.add('cmm-topbar__breadcrumbs')

    const product = config.product ?? config.breadcrumbs?.[0]?.label
    if (product !== undefined) {
      const productEl = document.createElement('span')
      productEl.classList.add('cmm-topbar__product')
      productEl.textContent = product
      crumbWrap.appendChild(productEl)
    }

    const crumbs = config.breadcrumbs ?? []
    const crumbStart = config.product === undefined && product !== undefined ? 1 : 0
    for (const crumb of crumbs.slice(crumbStart)) {
      const separator = document.createElement('span')
      separator.classList.add('cmm-topbar__separator')
      separator.setAttribute('aria-hidden', 'true')
      separator.textContent = '•'
      crumbWrap.appendChild(separator)

      const crumbEl =
        crumb.href !== undefined ? document.createElement('a') : document.createElement('span')
      crumbEl.classList.add('cmm-topbar__breadcrumb')
      crumbEl.textContent = crumb.label
      if (crumbEl instanceof HTMLAnchorElement) {
        crumbEl.href = crumb.href ?? '#'
      }
      crumbWrap.appendChild(crumbEl)
    }

    if (config.current !== undefined) {
      const separator = document.createElement('span')
      separator.classList.add('cmm-topbar__separator')
      separator.setAttribute('aria-hidden', 'true')
      separator.textContent = '•'
      crumbWrap.appendChild(separator)

      const current = document.createElement('span')
      current.classList.add('cmm-topbar__current')
      current.textContent = config.current
      crumbWrap.appendChild(current)
    }

    const actions = document.createElement('div')
    actions.classList.add('cmm-topbar__actions')

    appendElements(actions, config.beforeActions)

    for (const action of config.actions ?? []) {
      actions.appendChild(createActionElement(action, iconSet))
    }

    if (config.user !== undefined) {
      actions.appendChild(this.createUserMenu(config.user, iconSet))
    }

    appendElements(actions, config.afterActions)

    this.el.appendChild(crumbWrap)
    this.el.appendChild(actions)

    if (config.container !== undefined) {
      resolveContainer(config.container).appendChild(this.el)
    }
  }

  destroy(): void {
    for (const cleanup of this.cleanupFns.splice(0)) {
      cleanup()
    }
    this.el.remove()
  }

  private createUserMenu(user: TopMenuBarUser, iconSet: IconSet): HTMLElement {
    const root = document.createElement('div')
    root.classList.add('cmm-topbar__user')

    const trigger = document.createElement('button')
    trigger.type = 'button'
    trigger.classList.add('cmm-topbar__user-trigger')
    trigger.setAttribute('aria-haspopup', 'menu')
    trigger.setAttribute('aria-expanded', 'false')
    trigger.setAttribute('aria-label', user.menuLabel ?? `User menu for ${user.name}`)

    const avatar = document.createElement('span')
    avatar.classList.add('cmm-topbar__avatar')
    if (user.avatarUrl !== undefined) {
      const image = document.createElement('img')
      image.src = user.avatarUrl
      image.alt = ''
      avatar.appendChild(image)
    } else {
      avatar.textContent = user.initials ?? toInitials(user.name)
    }
    trigger.appendChild(avatar)
    trigger.appendChild(createIconSvg(iconSet, 'chevron-down'))

    const menu = document.createElement('div')
    menu.classList.add('cmm-topbar__user-menu')
    menu.setAttribute('role', 'menu')
    menu.hidden = true

    const profile = document.createElement('div')
    profile.classList.add('cmm-topbar__user-profile')
    const name = document.createElement('span')
    name.classList.add('cmm-topbar__user-name')
    name.textContent = user.name
    profile.appendChild(name)
    if (user.detail !== undefined) {
      const detail = document.createElement('span')
      detail.classList.add('cmm-topbar__user-detail')
      detail.textContent = user.detail
      profile.appendChild(detail)
    }
    menu.appendChild(profile)

    for (const action of user.actions ?? []) {
      const item = createActionElement(action, iconSet)
      item.classList.remove('cmm-topbar__action')
      item.classList.add('cmm-topbar__menu-item')
      item.setAttribute('role', 'menuitem')
      if (item.querySelector('.cmm-topbar__action-label') === null) {
        const label = document.createElement('span')
        label.classList.add('cmm-topbar__action-label')
        label.textContent = action.label
        item.appendChild(label)
      }
      menu.appendChild(item)
    }

    const setOpen = (open: boolean): void => {
      menu.hidden = !open
      trigger.setAttribute('aria-expanded', String(open))
    }

    trigger.addEventListener('click', () => {
      setOpen(menu.hidden === true)
    })

    const closeOnOutsidePointer = (event: PointerEvent): void => {
      if (!root.contains(event.target as Node)) {
        setOpen(false)
      }
    }
    const closeOnEscape = (event: KeyboardEvent): void => {
      if (event.key === 'Escape') {
        setOpen(false)
      }
    }

    document.addEventListener('pointerdown', closeOnOutsidePointer)
    document.addEventListener('keydown', closeOnEscape)
    this.cleanupFns.push(() => {
      document.removeEventListener('pointerdown', closeOnOutsidePointer)
      document.removeEventListener('keydown', closeOnEscape)
    })

    root.appendChild(trigger)
    root.appendChild(menu)
    return root
  }
}

export class AppShell {
  public readonly el: HTMLElement
  public readonly mainEl: HTMLElement
  public readonly contentEl: HTMLElement

  constructor(config: AppShellConfig = {}) {
    this.el = document.createElement('div')
    this.el.className = [
      'cmm-app-shell',
      config.variant === 'product' && 'cmm-app-shell--product',
      config.className,
    ]
      .filter(Boolean)
      .join(' ')
    this.el.setAttribute('data-shell-layout', '')
    if (config.sidebarCollapsed !== undefined) {
      this.el.setAttribute('data-sidebar-collapsed', String(config.sidebarCollapsed))
    }

    if (config.sidebar !== undefined) {
      const sidebarSlot = document.createElement('aside')
      sidebarSlot.className = 'cmm-app-shell__sidebar'
      sidebarSlot.setAttribute('data-shell-sidebar', '')
      sidebarSlot.appendChild(config.sidebar)
      this.el.appendChild(sidebarSlot)
    }

    this.mainEl = document.createElement('div')
    this.mainEl.className = 'cmm-app-shell__main'

    if (config.topbar !== undefined) {
      const topbar =
        config.topbar instanceof HTMLElement ? config.topbar : new TopMenuBar(config.topbar).el
      this.mainEl.appendChild(topbar)
    } else if (config.header !== undefined) {
      this.mainEl.appendChild(config.header)
    }

    this.contentEl = document.createElement('main')
    this.contentEl.className = 'cmm-app-shell__content'

    if (config.metrics !== undefined) {
      const metrics = document.createElement('div')
      metrics.className = 'cmm-app-shell__metric-strip'
      metrics.appendChild(config.metrics)
      this.contentEl.appendChild(metrics)
    }

    if (config.content !== undefined) {
      this.contentEl.appendChild(config.content)
    }

    this.mainEl.appendChild(this.contentEl)
    this.el.appendChild(this.mainEl)

    if (config.container !== undefined) {
      resolveContainer(config.container).appendChild(this.el)
    }
  }
}

export function createAppShell(config: AppShellConfig = {}): HTMLElement {
  return new AppShell(config).el
}

export function createTopMenuBar(config: TopMenuBarConfig = {}): HTMLElement {
  return new TopMenuBar(config).el
}

export function createProductShell(config: AppShellConfig = {}): HTMLElement {
  return new AppShell({ ...config, variant: 'product' }).el
}
