/**
 * Timeline - Chronological event visualization with vertical/horizontal layout
 * @countermeasure/web-components/visualization/timeline
 */

import type { TimelineConfig, TimelineEvent } from './types'
import { resolveContainer, uniqueId } from '../../primitives/utils'
import { escapeHtml } from '../../utils/sanitize'

export type { TimelineConfig, TimelineEvent } from './types'

/** Default date formatter producing locale-aware short datetime */
function defaultDateFormat(date: Date): string {
  return date.toLocaleString(undefined, {
    month: 'short',
    day: 'numeric',
    year: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
  })
}

/** Default date-only formatter for group headers */
function defaultGroupDateFormat(date: Date): string {
  return date.toLocaleDateString(undefined, {
    weekday: 'long',
    month: 'long',
    day: 'numeric',
    year: 'numeric',
  })
}

/** Normalize a Date|string to a Date object */
function toDate(val: Date | string): Date {
  return val instanceof Date ? val : new Date(val)
}

/** Extract a YYYY-MM-DD key from a Date */
function dateKey(date: Date): string {
  const y = String(date.getFullYear())
  const m = String(date.getMonth() + 1).padStart(2, '0')
  const d = String(date.getDate()).padStart(2, '0')
  return `${y}-${m}-${d}`
}

export class Timeline {
  public readonly el: HTMLDivElement

  private readonly timelineId: string
  private readonly orientation: 'vertical' | 'horizontal'
  private readonly showTimestamps: boolean
  private readonly dateFormat: (date: Date) => string
  private readonly onEventClick: ((event: TimelineEvent) => void) | undefined
  private readonly groupByDate: boolean
  private events: TimelineEvent[]
  private eventHandlers: {
    element: HTMLElement
    type: string
    handler: EventListener
  }[] = []

  constructor(config: TimelineConfig) {
    this.timelineId = uniqueId('cmm-timeline')
    this.orientation = config.orientation ?? 'vertical'
    this.showTimestamps = config.showTimestamps !== false
    this.dateFormat = config.dateFormat ?? defaultDateFormat
    this.onEventClick = config.onEventClick
    this.groupByDate = config.groupByDate === true
    this.events = [...config.events]

    this.el = document.createElement('div')
    this.el.id = this.timelineId
    this.el.className = this.buildRootClassName(config.className)

    const parent = resolveContainer(config.container)
    parent.appendChild(this.el)

    this.render()
  }

  /** Replace all events and re-render */
  update(events: TimelineEvent[]): void {
    this.cleanupHandlers()
    this.events = [...events]
    this.render()
  }

  /** Append a single event and re-render */
  addEvent(event: TimelineEvent): void {
    this.cleanupHandlers()
    this.events.push(event)
    this.render()
  }

  /** Clean up DOM and event listeners */
  destroy(): void {
    this.cleanupHandlers()
    this.el.remove()
  }

  // ---------------------------------------------------------------------------
  // Rendering
  // ---------------------------------------------------------------------------

  private render(): void {
    this.el.innerHTML = ''
    this.eventHandlers = []

    // Sort events chronologically
    const sorted = [...this.events].sort(
      (a, b) => toDate(a.timestamp).getTime() - toDate(b.timestamp).getTime()
    )

    if (this.orientation === 'horizontal') {
      this.renderHorizontal(sorted)
    } else {
      this.renderVertical(sorted)
    }
  }

  private renderVertical(events: TimelineEvent[]): void {
    // Central line
    const line = document.createElement('div')
    line.className = 'cmm-timeline__line'
    line.setAttribute('aria-hidden', 'true')
    this.el.appendChild(line)

    if (this.groupByDate) {
      this.renderGroupedVertical(events)
    } else {
      this.renderFlatVertical(events)
    }
  }

  private renderFlatVertical(events: TimelineEvent[]): void {
    for (let i = 0; i < events.length; i++) {
      const evt = events[i]
      if (evt === undefined) continue
      const side = i % 2 === 0 ? 'left' : 'right'
      const el = this.createVerticalEvent(evt, side)
      this.el.appendChild(el)
    }
  }

  private renderGroupedVertical(events: TimelineEvent[]): void {
    const groups = new Map<string, TimelineEvent[]>()
    const groupOrder: string[] = []

    for (const evt of events) {
      const key = dateKey(toDate(evt.timestamp))
      const existing = groups.get(key)
      if (existing !== undefined) {
        existing.push(evt)
      } else {
        groups.set(key, [evt])
        groupOrder.push(key)
      }
    }

    let globalIndex = 0
    for (const key of groupOrder) {
      const groupEvents = groups.get(key)
      if (groupEvents === undefined) continue
      const firstEvent = groupEvents[0]
      if (firstEvent === undefined) continue

      // Group header
      const header = document.createElement('div')
      header.className = 'cmm-timeline__group'
      const label = document.createElement('span')
      label.className = 'cmm-timeline__group-label'
      label.textContent = defaultGroupDateFormat(toDate(firstEvent.timestamp))
      header.appendChild(label)
      this.el.appendChild(header)

      // Group events
      for (const evt of groupEvents) {
        const side = globalIndex % 2 === 0 ? 'left' : 'right'
        const el = this.createVerticalEvent(evt, side)
        this.el.appendChild(el)
        globalIndex++
      }
    }
  }

  private createVerticalEvent(evt: TimelineEvent, side: 'left' | 'right'): HTMLDivElement {
    const wrapper = document.createElement('div')
    wrapper.className = `cmm-timeline__event cmm-timeline__event--${side}`
    wrapper.setAttribute('data-event-id', evt.id)

    // Dot
    const dot = document.createElement('div')
    dot.className = 'cmm-timeline__dot'
    dot.setAttribute('aria-hidden', 'true')
    if (evt.color !== undefined) {
      dot.style.background = evt.color
    }
    wrapper.appendChild(dot)

    // Content
    const content = this.createEventContent(evt)
    wrapper.appendChild(content)

    return wrapper
  }

  private renderHorizontal(events: TimelineEvent[]): void {
    const track = document.createElement('div')
    track.className = 'cmm-timeline__track'

    // Horizontal line
    const line = document.createElement('div')
    line.className = 'cmm-timeline__line'
    line.setAttribute('aria-hidden', 'true')
    track.appendChild(line)

    for (let i = 0; i < events.length; i++) {
      const evt = events[i]
      if (evt === undefined) continue
      const above = i % 2 === 0
      const el = this.createHorizontalEvent(evt, above)
      track.appendChild(el)
    }

    this.el.appendChild(track)
  }

  private createHorizontalEvent(evt: TimelineEvent, above: boolean): HTMLDivElement {
    const wrapper = document.createElement('div')
    wrapper.className = `cmm-timeline__event${above ? ' cmm-timeline__event--above' : ''}`
    wrapper.setAttribute('data-event-id', evt.id)

    // Dot
    const dot = document.createElement('div')
    dot.className = 'cmm-timeline__dot'
    dot.setAttribute('aria-hidden', 'true')
    if (evt.color !== undefined) {
      dot.style.background = evt.color
    }
    wrapper.appendChild(dot)

    // Content
    const content = this.createEventContent(evt)
    wrapper.appendChild(content)

    return wrapper
  }

  private createEventContent(evt: TimelineEvent): HTMLDivElement {
    const content = document.createElement('div')
    content.className = 'cmm-timeline__content'
    content.setAttribute('tabindex', '0')
    content.setAttribute('role', 'button')
    content.setAttribute('aria-label', evt.title)

    // Title line with optional icon
    const title = document.createElement('div')
    title.className = 'cmm-timeline__title'

    if (evt.icon !== undefined) {
      const icon = document.createElement('span')
      icon.className = 'cmm-timeline__icon'
      icon.textContent = evt.icon
      icon.setAttribute('aria-hidden', 'true')
      title.appendChild(icon)
    }

    const titleText = document.createTextNode(evt.title)
    title.appendChild(titleText)
    content.appendChild(title)

    // Description
    if (evt.description !== undefined) {
      const desc = document.createElement('div')
      desc.className = 'cmm-timeline__description'
      desc.textContent = evt.description
      content.appendChild(desc)
    }

    // Timestamp
    if (this.showTimestamps) {
      const ts = document.createElement('div')
      ts.className = 'cmm-timeline__timestamp'
      ts.textContent = this.dateFormat(toDate(evt.timestamp))
      content.appendChild(ts)
    }

    // Metadata
    if (evt.metadata !== undefined) {
      const meta = document.createElement('div')
      meta.className = 'cmm-timeline__metadata'
      for (const [key, value] of Object.entries(evt.metadata)) {
        const item = document.createElement('span')
        item.innerHTML =
          `<span class="cmm-timeline__metadata-key">${escapeHtml(key)}:</span> ` +
          `<span class="cmm-timeline__metadata-value">${escapeHtml(value)}</span>`
        meta.appendChild(item)
      }
      content.appendChild(meta)
    }

    // Click and keyboard handlers
    if (this.onEventClick !== undefined) {
      const clickHandler = (): void => {
        this.onEventClick?.(evt)
      }
      const keydownHandler = (e: Event): void => {
        const keyEvent = e as KeyboardEvent
        if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {
          keyEvent.preventDefault()
          clickHandler()
        }
      }
      content.addEventListener('click', clickHandler)
      content.addEventListener('keydown', keydownHandler)
      this.eventHandlers.push({ element: content, type: 'click', handler: clickHandler })
      this.eventHandlers.push({ element: content, type: 'keydown', handler: keydownHandler })
    }

    return content
  }

  // ---------------------------------------------------------------------------
  // Helpers
  // ---------------------------------------------------------------------------

  private buildRootClassName(extra: string | undefined): string {
    const classes = ['cmm-timeline', `cmm-timeline--${this.orientation}`]
    if (extra !== undefined) {
      classes.push(extra)
    }
    return classes.join(' ')
  }

  private cleanupHandlers(): void {
    for (const entry of this.eventHandlers) {
      entry.element.removeEventListener(entry.type, entry.handler)
    }
    this.eventHandlers = []
  }
}
