/**
 * TabPanel - Tab bar with switchable panels
 * @countermeasure/web-components/components/tabs
 */

import { type Tab, type TabPanelConfig } from './types'
import { escapeHtml } from '../../utils/sanitize'

export type { Tab, TabPanelConfig } from './types'

/** Resolve a container reference to an HTMLElement */
function resolveContainer(container: HTMLElement | string): HTMLElement {
  if (typeof container === 'string') {
    const el = document.querySelector<HTMLElement>(container)
    if (!el) {
      throw new Error(`TabPanel container not found: ${container}`)
    }
    return el
  }
  return container
}

/** Generate a unique ID */
let idCounter = 0
function uniqueId(prefix: string): string {
  return `${prefix}-${String(++idCounter)}`
}

export class TabPanel {
  private readonly container: HTMLElement
  private readonly config: TabPanelConfig
  private readonly instanceId: string
  private tabs: Tab[]
  private activeTabId: string
  private barEl: HTMLElement | null = null
  private panelsEl: HTMLElement | null = null
  private readonly panelMap = new Map<string, HTMLElement>()
  private readonly boundKeydownHandler: (e: KeyboardEvent) => void

  constructor(config: TabPanelConfig) {
    this.container = resolveContainer(config.container)
    this.config = config
    this.instanceId = uniqueId('cmm-tabs')
    this.tabs = [...config.tabs]

    // Determine initial active tab
    const firstTab = this.tabs[0]
    this.activeTabId = config.activeTab ?? (firstTab !== undefined ? firstTab.id : '')

    // Bind keyboard handler for proper cleanup
    this.boundKeydownHandler = this.handleKeydown.bind(this)

    this.render()
  }

  /** Switch to a specific tab by id */
  setActiveTab(tabId: string): void {
    const tab = this.tabs.find(t => t.id === tabId)
    if (!tab) {
      return
    }

    if (tab.disabled === true) {
      return
    }

    this.activeTabId = tabId
    this.updateTabBar()
    this.updatePanelVisibility()
    this.config.onTabChange?.(tabId)
  }

  /** Get the id of the currently active tab */
  getActiveTab(): string {
    return this.activeTabId
  }

  /** Add a new tab to the tab bar */
  addTab(tab: Tab): void {
    this.tabs.push(tab)
    this.render()
  }

  /** Remove a tab by id */
  removeTab(tabId: string): void {
    const index = this.tabs.findIndex(t => t.id === tabId)
    if (index < 0) {
      return
    }

    this.tabs.splice(index, 1)
    this.panelMap.delete(tabId)

    // If the removed tab was active, switch to the first available tab
    if (this.activeTabId === tabId) {
      const firstTab = this.tabs[0]
      this.activeTabId = firstTab !== undefined ? firstTab.id : ''
    }

    this.config.onTabClose?.(tabId)
    this.render()
  }

  /** Get the panel HTMLElement for a tab, creating it if it does not exist */
  getPanel(tabId: string): HTMLElement {
    const existing = this.panelMap.get(tabId)
    if (existing !== undefined) {
      return existing
    }

    const panel = document.createElement('div')
    panel.className = 'cmm-tabs__panel'
    panel.dataset.tabPanel = tabId

    if (tabId === this.activeTabId) {
      panel.classList.add('cmm-tabs__panel--active')
    }

    this.panelMap.set(tabId, panel)

    if (this.panelsEl) {
      this.panelsEl.appendChild(panel)
    }

    return panel
  }

  /** Remove all DOM elements and clean up */
  destroy(): void {
    if (this.barEl !== null) {
      this.barEl.removeEventListener('keydown', this.boundKeydownHandler)
    }
    this.container.innerHTML = ''
    this.barEl = null
    this.panelsEl = null
    this.panelMap.clear()
  }

  /** Handle keyboard navigation in tab bar */
  private handleKeydown(e: KeyboardEvent): void {
    const enabledTabs = this.tabs.filter(t => t.disabled !== true)
    if (enabledTabs.length === 0) return

    const currentIndex = enabledTabs.findIndex(t => t.id === this.activeTabId)
    let newIndex: number

    switch (e.key) {
      case 'ArrowLeft':
        newIndex = currentIndex > 0 ? currentIndex - 1 : enabledTabs.length - 1
        e.preventDefault()
        break
      case 'ArrowRight':
        newIndex = currentIndex < enabledTabs.length - 1 ? currentIndex + 1 : 0
        e.preventDefault()
        break
      case 'Home':
        newIndex = 0
        e.preventDefault()
        break
      case 'End':
        newIndex = enabledTabs.length - 1
        e.preventDefault()
        break
      default:
        return
    }

    const newTab = enabledTabs[newIndex]
    if (newTab !== undefined && newTab.id !== this.activeTabId) {
      this.setActiveTab(newTab.id)
      // Focus the new active tab button
      this.focusTab(newTab.id)
    }
  }

  /** Focus a specific tab button */
  private focusTab(tabId: string): void {
    if (this.barEl === null) return
    const btn = this.barEl.querySelector<HTMLElement>(`[data-tab-id="${tabId}"]`)
    btn?.focus()
  }

  /** Render the tab bar and panels container */
  private render(): void {
    this.container.innerHTML = ''
    this.panelMap.clear()

    const wrapper = document.createElement('div')
    wrapper.className = 'cmm-tabs'

    // Tab bar
    const bar = document.createElement('div')
    bar.className = 'cmm-tabs__bar'
    bar.setAttribute('role', 'tablist')
    bar.setAttribute('aria-label', 'Tabs')
    bar.addEventListener('keydown', this.boundKeydownHandler)
    this.barEl = bar

    for (const tab of this.tabs) {
      bar.appendChild(this.buildTabButton(tab))
    }

    wrapper.appendChild(bar)

    // Panels container
    const panelsContainer = document.createElement('div')
    panelsContainer.className = 'cmm-tabs__panels'
    this.panelsEl = panelsContainer

    // Create a panel for each tab
    for (const tab of this.tabs) {
      const panel = document.createElement('div')
      panel.className = 'cmm-tabs__panel'
      panel.id = `${this.instanceId}-panel-${tab.id}`
      panel.dataset.tabPanel = tab.id
      panel.setAttribute('role', 'tabpanel')
      panel.setAttribute('aria-labelledby', `${this.instanceId}-tab-${tab.id}`)
      panel.tabIndex = 0 // Allow panel to receive focus for accessibility

      if (tab.id === this.activeTabId) {
        panel.classList.add('cmm-tabs__panel--active')
      }

      this.panelMap.set(tab.id, panel)
      panelsContainer.appendChild(panel)
    }

    wrapper.appendChild(panelsContainer)
    this.container.appendChild(wrapper)
  }

  /** Build a single tab button element */
  private buildTabButton(tab: Tab): HTMLElement {
    const btn = document.createElement('button')
    btn.type = 'button'
    btn.className = 'cmm-tabs__tab'
    btn.setAttribute('role', 'tab')
    btn.setAttribute('aria-selected', tab.id === this.activeTabId ? 'true' : 'false')
    btn.setAttribute('aria-controls', `${this.instanceId}-panel-${tab.id}`)
    btn.id = `${this.instanceId}-tab-${tab.id}`
    btn.dataset.tabId = tab.id

    // Active tab gets tabindex=0, others get -1 for roving tabindex
    btn.tabIndex = tab.id === this.activeTabId ? 0 : -1

    if (tab.id === this.activeTabId) {
      btn.classList.add('cmm-tabs__tab--active')
    }

    if (tab.disabled === true) {
      btn.classList.add('cmm-tabs__tab--disabled')
      btn.disabled = true
      btn.tabIndex = -1
    }

    // Icon
    if (tab.icon !== undefined) {
      const iconSpan = document.createElement('span')
      iconSpan.className = 'cmm-tabs__tab-icon'
      iconSpan.textContent = tab.icon
      btn.appendChild(iconSpan)
    }

    // Label
    const labelSpan = document.createElement('span')
    labelSpan.className = 'cmm-tabs__tab-label'
    labelSpan.innerHTML = escapeHtml(tab.label)
    btn.appendChild(labelSpan)

    // Click handler
    btn.addEventListener('click', () => {
      this.setActiveTab(tab.id)
    })

    // Close button (use button element for accessibility)
    if (tab.closeable === true) {
      const closeBtn = document.createElement('button')
      closeBtn.type = 'button'
      closeBtn.className = 'cmm-tabs__tab-close'
      closeBtn.textContent = '\u00D7'
      closeBtn.setAttribute('aria-label', `Close ${tab.label}`)
      closeBtn.tabIndex = -1 // Don't include in tab order, accessible via parent
      closeBtn.addEventListener('click', (e: Event) => {
        e.stopPropagation()
        this.removeTab(tab.id)
      })
      btn.appendChild(closeBtn)
    }

    return btn
  }

  /** Update tab bar active states without full re-render */
  private updateTabBar(): void {
    if (!this.barEl) {
      return
    }

    const buttons = this.barEl.querySelectorAll<HTMLButtonElement>('.cmm-tabs__tab')
    buttons.forEach(btn => {
      const tabId = btn.dataset.tabId
      const tab = this.tabs.find(t => t.id === tabId)
      const isDisabled = tab?.disabled === true

      if (tabId === this.activeTabId) {
        btn.classList.add('cmm-tabs__tab--active')
        btn.setAttribute('aria-selected', 'true')
        btn.tabIndex = 0
        btn.disabled = false
      } else {
        btn.classList.remove('cmm-tabs__tab--active')
        btn.setAttribute('aria-selected', 'false')
        btn.tabIndex = -1 // Roving tabindex: only active tab gets tabIndex 0
        btn.disabled = isDisabled
      }
    })
  }

  /** Show the active panel and hide all others */
  private updatePanelVisibility(): void {
    for (const [tabId, panel] of this.panelMap) {
      if (tabId === this.activeTabId) {
        panel.classList.add('cmm-tabs__panel--active')
      } else {
        panel.classList.remove('cmm-tabs__panel--active')
      }
    }
  }
}
