/**
 * Pagination component
 * @countermeasure/web-components/components/pagination
 *
 * Vanilla TS counterpart of the React `Pagination` primitive. Kept at parity
 * with that component's visual design (glass tokens, chevron icons, active
 * `shadow-glow`) and accessibility surface (`data-slot`, `role="navigation"`,
 * `aria-current`, per-button `aria-label`, `type="button"`).
 *
 * The React primitive is a headless/compound API where the consumer composes
 * the numbered page links by hand. This vanilla component is self-contained and
 * stateful: it renders Previous / Next controls and (optionally, when
 * `totalPages` is provided) the numbered page links + ellipsis itself.
 */

const SVG_NS = 'http://www.w3.org/2000/svg'

export interface PaginationConfig {
  /** Container element (or selector) the controls are mounted into. */
  container: HTMLElement | string
  /**
   * Initial page number (1-based).
   * @default 1
   */
  page?: number
  /**
   * Whether further pages are available beyond the current one.
   *
   * Ignored when `totalPages` is provided (the more/less state is derived from
   * `page < totalPages` instead).
   * @default false
   */
  hasMore?: boolean
  /**
   * Total number of pages. When provided, the component renders numbered page
   * links (with an active page and ellipsis when the count is large), matching
   * the React primitive's `PaginationLink` / `PaginationEllipsis`. When omitted,
   * only the Previous / page-info / Next controls are rendered.
   */
  totalPages?: number
  /**
   * Maximum number of numbered page links to show before collapsing the middle
   * into ellipsis. Only relevant when `totalPages` is set. Must be >= 5 to leave
   * room for first/last + ellipsis; smaller values are clamped to 5.
   * @default 7
   */
  siblingCount?: number
  /** Extra class names appended to the root element (space-separated). */
  className?: string
  /** Called whenever the user navigates to a different page. */
  onPageChange?: (page: number) => void
}

function resolveContainer(container: HTMLElement | string): HTMLElement {
  if (typeof container === 'string') {
    const el = document.querySelector(container)
    if (!el) {
      throw new Error(`Pagination: container not found for selector "${container}"`)
    }
    return el as HTMLElement
  }
  return container
}

/** Create an SVG element in the SVG namespace with the given attributes. */
function svgEl(tag: string, attrs: Record<string, string>): SVGElement {
  const el = document.createElementNS(SVG_NS, tag)
  for (const [key, value] of Object.entries(attrs)) {
    el.setAttribute(key, value)
  }
  return el
}

/** Shared base for the stroke icons (lucide-compatible geometry). */
function baseIcon(): SVGSVGElement {
  const svg = svgEl('svg', {
    viewBox: '0 0 24 24',
    fill: 'none',
    stroke: 'currentColor',
    'stroke-width': '2',
    'stroke-linecap': 'round',
    'stroke-linejoin': 'round',
    'aria-hidden': 'true',
  }) as SVGSVGElement
  svg.classList.add('cmm-pagination__icon')
  return svg
}

/** lucide `chevron-left`. */
function createChevronLeftIcon(): SVGSVGElement {
  const svg = baseIcon()
  svg.appendChild(svgEl('path', { d: 'm15 18-6-6 6-6' }))
  return svg
}

/** lucide `chevron-right`. */
function createChevronRightIcon(): SVGSVGElement {
  const svg = baseIcon()
  svg.appendChild(svgEl('path', { d: 'm9 18 6-6-6-6' }))
  return svg
}

/** lucide `more-horizontal` (three dots). */
function createEllipsisIcon(): SVGSVGElement {
  const svg = baseIcon()
  svg.appendChild(svgEl('circle', { cx: '12', cy: '12', r: '1' }))
  svg.appendChild(svgEl('circle', { cx: '19', cy: '12', r: '1' }))
  svg.appendChild(svgEl('circle', { cx: '5', cy: '12', r: '1' }))
  return svg
}

/** An item in the computed page sequence: a concrete page or an ellipsis gap. */
type PageItem = number | 'ellipsis'

/**
 * Compute the sequence of page numbers / ellipsis markers to display.
 * Always shows the first and last page; collapses the middle with ellipsis
 * when `totalPages` exceeds `maxButtons`.
 */
function computePageItems(current: number, total: number, maxButtons: number): PageItem[] {
  if (total <= maxButtons) {
    return Array.from({ length: total }, (_, i) => i + 1)
  }

  // First and last pages are always shown. Reserve four slots — first, last,
  // and the two ellipses — and center the remaining window on the current page.
  // Sizing the window to `maxButtons - 4` guarantees the sequence (including
  // both ellipses) never exceeds `maxButtons`.
  const windowLen = Math.max(1, maxButtons - 4)
  const desiredStart = Math.max(2, current - Math.floor((windowLen - 1) / 2))
  let end = Math.min(total - 1, desiredStart + windowLen - 1)
  let start = Math.max(2, end - windowLen + 1)

  // Reclaim the slot freed by an absent ellipsis so the button count stays
  // stable near the edges (without ever exceeding `maxButtons`).
  if (start === 2 && end < total - 1) {
    end = Math.min(total - 1, end + 1)
  }
  if (end === total - 1 && start > 2) {
    start = Math.max(2, start - 1)
  }

  const items: PageItem[] = [1]
  if (start > 2) {
    items.push('ellipsis')
  }
  for (let p = start; p <= end; p++) {
    items.push(p)
  }
  if (end < total - 1) {
    items.push('ellipsis')
  }
  items.push(total)
  return items
}

export class Pagination {
  private readonly config: PaginationConfig
  private readonly containerEl: HTMLElement
  private readonly rootEl: HTMLElement
  private readonly prevBtn: HTMLButtonElement
  private readonly nextBtn: HTMLButtonElement
  private readonly infoEl: HTMLElement
  /** Holds the numbered page links / ellipsis, between prev and next. */
  private readonly pagesEl: HTMLElement
  private readonly totalPages: number | null
  private readonly maxButtons: number
  private currentPage: number
  private hasMore: boolean
  private destroyed = false
  /** Listeners attached to numbered page links, for cleanup on destroy. */
  private pageListeners: Array<{ el: HTMLElement; fn: (e: Event) => void }> = []

  constructor(config: PaginationConfig) {
    this.config = config
    this.containerEl = resolveContainer(config.container)
    this.currentPage = config.page ?? 1
    this.hasMore = config.hasMore === true
    this.totalPages =
      typeof config.totalPages === 'number' && config.totalPages > 0 ? config.totalPages : null
    this.maxButtons = Math.max(5, config.siblingCount ?? 7)

    this.rootEl = document.createElement('div')
    this.rootEl.className = 'cmm-pagination'
    this.rootEl.dataset.slot = 'pagination'
    this.rootEl.setAttribute('role', 'navigation')
    this.rootEl.setAttribute('aria-label', 'Pagination')

    if (config.className !== undefined && config.className.trim() !== '') {
      for (const cls of config.className.split(/\s+/)) {
        this.rootEl.classList.add(cls)
      }
    }

    this.prevBtn = document.createElement('button')
    this.prevBtn.type = 'button'
    this.prevBtn.className = 'cmm-pagination__button cmm-pagination__button--prev'
    this.prevBtn.dataset.slot = 'pagination-previous'
    this.prevBtn.setAttribute('aria-label', 'Go to previous page')
    this.prevBtn.appendChild(createChevronLeftIcon())
    this.prevBtn.appendChild(this.makeLabel('Previous'))
    this.prevBtn.addEventListener('click', this.handlePrev)

    this.pagesEl = document.createElement('div')
    this.pagesEl.className = 'cmm-pagination__pages'

    this.infoEl = document.createElement('span')
    this.infoEl.className = 'cmm-pagination__info'

    this.nextBtn = document.createElement('button')
    this.nextBtn.type = 'button'
    this.nextBtn.className = 'cmm-pagination__button cmm-pagination__button--next'
    this.nextBtn.dataset.slot = 'pagination-next'
    this.nextBtn.setAttribute('aria-label', 'Go to next page')
    this.nextBtn.appendChild(this.makeLabel('Next'))
    this.nextBtn.appendChild(createChevronRightIcon())
    this.nextBtn.addEventListener('click', this.handleNext)

    this.rootEl.appendChild(this.prevBtn)
    if (this.totalPages !== null) {
      this.rootEl.appendChild(this.pagesEl)
    } else {
      this.rootEl.appendChild(this.infoEl)
    }
    this.rootEl.appendChild(this.nextBtn)
    this.containerEl.appendChild(this.rootEl)

    this.render()
  }

  private makeLabel(text: string): HTMLSpanElement {
    const span = document.createElement('span')
    span.className = 'cmm-pagination__label'
    span.textContent = text
    return span
  }

  setPage(page: number, hasMore?: boolean): void {
    this.currentPage = page
    if (hasMore !== undefined) {
      this.hasMore = hasMore
    }
    this.render()
  }

  getPage(): number {
    return this.currentPage
  }

  destroy(): void {
    if (this.destroyed) {
      return
    }
    this.destroyed = true

    this.prevBtn.removeEventListener('click', this.handlePrev)
    this.nextBtn.removeEventListener('click', this.handleNext)
    this.clearPageListeners()

    if (this.rootEl.parentNode) {
      this.rootEl.parentNode.removeChild(this.rootEl)
    }
  }

  /** Whether a next page exists, derived from `totalPages` when available. */
  private get canGoNext(): boolean {
    if (this.totalPages !== null) {
      return this.currentPage < this.totalPages
    }
    return this.hasMore
  }

  private render(): void {
    this.infoEl.textContent = `Page ${String(this.currentPage)}`

    if (this.totalPages !== null) {
      this.renderPages()
    }

    if (this.currentPage <= 1) {
      this.prevBtn.disabled = true
      this.prevBtn.classList.add('cmm-pagination__button--disabled')
    } else {
      this.prevBtn.disabled = false
      this.prevBtn.classList.remove('cmm-pagination__button--disabled')
    }

    if (this.canGoNext) {
      this.nextBtn.disabled = false
      this.nextBtn.classList.remove('cmm-pagination__button--disabled')
    } else {
      this.nextBtn.disabled = true
      this.nextBtn.classList.add('cmm-pagination__button--disabled')
    }
  }

  /** Render numbered page links + ellipsis (only when `totalPages` is set). */
  private renderPages(): void {
    if (this.totalPages === null) {
      return
    }
    this.clearPageListeners()
    this.pagesEl.replaceChildren()

    const items = computePageItems(this.currentPage, this.totalPages, this.maxButtons)
    for (const item of items) {
      if (item === 'ellipsis') {
        this.pagesEl.appendChild(this.makeEllipsis())
      } else {
        this.pagesEl.appendChild(this.makePageLink(item))
      }
    }
  }

  private makePageLink(pageNum: number): HTMLButtonElement {
    const link = document.createElement('button')
    link.type = 'button'
    link.className = 'cmm-pagination__button cmm-pagination__button--page'
    link.dataset.slot = 'pagination-link'
    link.dataset.page = String(pageNum)
    link.textContent = String(pageNum)

    const isActive = pageNum === this.currentPage
    if (isActive) {
      link.classList.add('cmm-pagination__button--active')
      link.setAttribute('aria-current', 'page')
    }

    const fn = (): void => {
      this.goToPage(pageNum)
    }
    link.addEventListener('click', fn)
    this.pageListeners.push({ el: link, fn })
    return link
  }

  private makeEllipsis(): HTMLSpanElement {
    const span = document.createElement('span')
    span.className = 'cmm-pagination__ellipsis'
    span.dataset.slot = 'pagination-ellipsis'
    span.setAttribute('aria-hidden', 'true')
    span.appendChild(createEllipsisIcon())

    const sr = document.createElement('span')
    sr.className = 'cmm-pagination__sr-only'
    sr.textContent = 'More pages'
    span.appendChild(sr)
    return span
  }

  private clearPageListeners(): void {
    for (const { el, fn } of this.pageListeners) {
      el.removeEventListener('click', fn)
    }
    this.pageListeners = []
  }

  /** Navigate to an explicit page (used by numbered links). */
  private goToPage(page: number): void {
    const total = this.totalPages
    const clamped = total !== null ? Math.min(Math.max(1, page), total) : Math.max(1, page)
    if (clamped === this.currentPage) {
      return
    }
    this.currentPage = clamped
    this.render()
    this.config.onPageChange?.(this.currentPage)
  }

  private readonly handlePrev = (): void => {
    if (this.currentPage > 1) {
      this.currentPage -= 1
      this.render()
      if (this.config.onPageChange) {
        this.config.onPageChange(this.currentPage)
      }
    }
  }

  private readonly handleNext = (): void => {
    if (this.canGoNext) {
      this.currentPage += 1
      this.render()
      if (this.config.onPageChange) {
        this.config.onPageChange(this.currentPage)
      }
    }
  }
}
