/**
 * CopyButton component
 * @countermeasure/web-components/components/copy-button
 *
 * Vanilla TS counterpart of the React `CopyButton` primitive. Kept at 1:1
 * parity with that component's configurable surface and visual design.
 */

import { announce } from '../../utils/a11y'

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

/** Visual style of the button. */
export type CopyButtonVariant = 'default' | 'ghost'

/** Size of the button. `icon` / `icon-sm` are square (no label padding). */
export type CopyButtonSize = 'default' | 'sm' | 'icon' | 'icon-sm'

export interface CopyButtonConfig {
  /** Container element (or selector) the button is mounted into. */
  container: HTMLElement | string
  /** Function that returns the text to copy. Called on each click. */
  getText: () => string
  /**
   * Default button label.
   * @default 'Copy'
   */
  label?: string
  /**
   * Label shown briefly after a successful copy.
   * @default 'Copied!'
   */
  copiedLabel?: string
  /**
   * Duration in milliseconds the `copiedLabel` is displayed.
   * @default 2000
   */
  copiedDuration?: number
  /**
   * Visual style.
   * @default 'default'
   */
  variant?: CopyButtonVariant
  /**
   * Button size. When `iconOnly` is set, the effective size is forced to
   * `icon` (matching the React primitive).
   * @default 'default'
   */
  size?: CopyButtonSize
  /**
   * Render only the icon, with the label exposed via `aria-label`.
   * @default false
   */
  iconOnly?: boolean
  /**
   * Disable the button.
   * @default false
   */
  disabled?: boolean
  /** Extra class names appended to the button (space-separated). */
  className?: string
  /** Called after the text is successfully copied to the clipboard. */
  onCopy?: (text: string) => void
}

function resolveContainer(container: HTMLElement | string): HTMLElement {
  if (typeof container === 'string') {
    const el = document.querySelector(container)
    if (!el) {
      throw new Error(`CopyButton: 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-copy-button__icon')
  return svg
}

function createCopyIcon(): SVGSVGElement {
  const svg = baseIcon()
  svg.appendChild(svgEl('rect', { width: '14', height: '14', x: '8', y: '8', rx: '2', ry: '2' }))
  svg.appendChild(svgEl('path', { d: 'M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2' }))
  return svg
}

function createCheckIcon(): SVGSVGElement {
  const svg = baseIcon()
  svg.appendChild(svgEl('path', { d: 'M20 6 9 17l-5-5' }))
  return svg
}

export class CopyButton {
  private readonly config: CopyButtonConfig
  private readonly containerEl: HTMLElement
  private readonly buttonEl: HTMLButtonElement
  private readonly defaultLabel: string
  private readonly copiedLabel: string
  private readonly copiedDuration: number
  private readonly iconOnly: boolean
  private resetTimer: ReturnType<typeof setTimeout> | null = null
  private destroyed = false

  constructor(config: CopyButtonConfig) {
    this.config = config
    this.containerEl = resolveContainer(config.container)
    this.defaultLabel = config.label ?? 'Copy'
    this.copiedLabel = config.copiedLabel ?? 'Copied!'
    this.copiedDuration = config.copiedDuration ?? 2000
    this.iconOnly = config.iconOnly ?? false

    // Effective size mirrors React: icon-only always renders at `icon` size.
    const variant = config.variant ?? 'default'
    const size: CopyButtonSize = this.iconOnly ? 'icon' : (config.size ?? 'default')

    this.buttonEl = document.createElement('button')
    this.buttonEl.type = 'button'
    this.buttonEl.dataset.slot = 'copy-button'
    this.buttonEl.className = 'cmm-copy-button'
    this.buttonEl.classList.add(`cmm-copy-button--${variant}`)
    this.buttonEl.classList.add(`cmm-copy-button--size-${size}`)

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

    if (config.disabled === true) {
      this.buttonEl.disabled = true
    }

    this.renderContent(false)
    this.buttonEl.addEventListener('click', this.handleClick)

    this.containerEl.appendChild(this.buttonEl)
  }

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

    if (this.resetTimer !== null) {
      clearTimeout(this.resetTimer)
    }

    this.buttonEl.removeEventListener('click', this.handleClick)

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

  /** Render the icon (and label, unless icon-only) for the given state. */
  private renderContent(copied: boolean): void {
    const icon = copied ? createCheckIcon() : createCopyIcon()
    this.buttonEl.replaceChildren(icon)

    const label = copied ? this.copiedLabel : this.defaultLabel
    if (this.iconOnly) {
      this.buttonEl.setAttribute('aria-label', label)
    } else {
      const labelEl = document.createElement('span')
      labelEl.className = 'cmm-copy-button__label'
      labelEl.textContent = label
      this.buttonEl.appendChild(labelEl)
    }

    if (copied) {
      this.buttonEl.dataset.copied = 'true'
    } else {
      delete this.buttonEl.dataset.copied
    }
  }

  private readonly handleClick = (): void => {
    const text = this.config.getText()
    void this.copyToClipboard(text)
  }

  private async copyToClipboard(text: string): Promise<void> {
    try {
      await navigator.clipboard.writeText(text)
      this.showCopiedFeedback()
      this.config.onCopy?.(text)
    } catch {
      // Clipboard API not available or permission denied
    }
  }

  private showCopiedFeedback(): void {
    if (this.resetTimer !== null) {
      clearTimeout(this.resetTimer)
    }

    this.renderContent(true)
    this.buttonEl.classList.add('cmm-copy-button--copied')
    announce(this.copiedLabel, 'polite')

    this.resetTimer = setTimeout(() => {
      if (!this.destroyed) {
        this.renderContent(false)
        this.buttonEl.classList.remove('cmm-copy-button--copied')
      }
      this.resetTimer = null
    }, this.copiedDuration)
  }
}
