/**
 * IPInput - IP address input with segmented fields
 * @countermeasure/web-components/primitives/input
 */

import { type IPInputConfig } from './types'
import { resolveContainer, uniqueId } from '../utils'

const IPV4_SEGMENTS = 4
const IPV4_MAX_VALUE = 255
const IPV6_SEGMENTS = 8
const IPV4_SEPARATOR = '.'
const IPV6_SEPARATOR = ':'

export class IPInput {
  public readonly el: HTMLDivElement

  private readonly ipId: string
  private readonly version: 4 | 6
  private readonly segmentCount: number
  private readonly segmentInputs: HTMLInputElement[] = []
  private readonly separator: string
  private readonly onChange: ((value: string) => void) | undefined
  private readonly inputHandlers = new Map<
    HTMLInputElement,
    {
      input: (e: Event) => void
      keydown: (e: KeyboardEvent) => void
      focus: () => void
    }
  >()

  constructor(config: IPInputConfig) {
    this.ipId = uniqueId('cmm-ip')
    this.version = config.version ?? 4
    this.segmentCount = this.version === 4 ? IPV4_SEGMENTS : IPV6_SEGMENTS
    this.separator = this.version === 4 ? IPV4_SEPARATOR : IPV6_SEPARATOR
    this.onChange = config.onChange

    // Create container
    this.el = document.createElement('div')
    this.el.className = `cmm-ip-input cmm-ip-input--v${String(this.version)}`
    this.el.setAttribute('role', 'group')

    if (config.ariaLabel !== undefined) {
      this.el.setAttribute('aria-label', config.ariaLabel)
    } else {
      this.el.setAttribute('aria-label', `IPv${String(this.version)} address input`)
    }

    // Create segment inputs
    for (let i = 0; i < this.segmentCount; i++) {
      const input = document.createElement('input')
      input.type = 'text'
      input.className = 'cmm-ip-input__segment'
      input.id = `${this.ipId}-${String(i)}`
      input.inputMode = this.version === 4 ? 'numeric' : 'text'
      input.maxLength = this.version === 4 ? 3 : 4
      input.placeholder = this.version === 4 ? '0' : '0000'
      input.setAttribute('aria-label', `Segment ${String(i + 1)} of ${String(this.segmentCount)}`)

      if (config.disabled === true) {
        input.disabled = true
      }

      const handlers = {
        input: (_e: Event) => {
          this.handleSegmentInput(i)
        },
        keydown: (e: KeyboardEvent) => {
          this.handleSegmentKeydown(i, e)
        },
        focus: () => {
          input.select()
        },
      }

      input.addEventListener('input', handlers.input)
      input.addEventListener('keydown', handlers.keydown)
      input.addEventListener('focus', handlers.focus)

      this.inputHandlers.set(input, handlers)
      this.segmentInputs.push(input)

      this.el.appendChild(input)

      // Add separator between segments
      if (i < this.segmentCount - 1) {
        const sep = document.createElement('span')
        sep.className = 'cmm-ip-input__separator'
        sep.textContent = this.separator
        sep.setAttribute('aria-hidden', 'true')
        this.el.appendChild(sep)
      }
    }

    // Set initial value if provided
    if (config.value !== undefined) {
      this.setValue(config.value)
    }

    // Mount if container provided
    if (config.container !== undefined) {
      const parent = resolveContainer(config.container)
      parent.appendChild(this.el)
    }
  }

  /** Get the full IP address string */
  getValue(): string {
    return this.segmentInputs.map(input => input.value).join(this.separator)
  }

  /** Set the IP address value by parsing and distributing segments */
  setValue(value: string): void {
    const parts = value.split(this.separator)
    for (let i = 0; i < this.segmentCount; i++) {
      const input = this.segmentInputs[i]
      const part = parts[i]
      if (input !== undefined) {
        input.value = part ?? ''
      }
    }
  }

  /** Toggle disabled state on all segment inputs */
  setDisabled(disabled: boolean): void {
    for (const input of this.segmentInputs) {
      input.disabled = disabled
    }
  }

  /** Remove from the DOM and clean up event listeners */
  destroy(): void {
    for (const input of this.segmentInputs) {
      const handlers = this.inputHandlers.get(input)
      if (handlers !== undefined) {
        input.removeEventListener('input', handlers.input)
        input.removeEventListener('keydown', handlers.keydown)
        input.removeEventListener('focus', handlers.focus)
      }
    }
    this.inputHandlers.clear()
    this.el.remove()
  }

  private handleSegmentInput(index: number): void {
    const input = this.segmentInputs[index]
    if (input === undefined) {
      return
    }

    // Validate and sanitize input
    if (this.version === 4) {
      this.sanitizeIPv4Segment(input)
    } else {
      this.sanitizeIPv6Segment(input)
    }

    // Auto-advance when segment is full
    const maxLen = this.version === 4 ? 3 : 4
    if (input.value.length >= maxLen) {
      const next = this.segmentInputs[index + 1]
      if (next !== undefined) {
        next.focus()
      }
    }

    this.onChange?.(this.getValue())
  }

  private handleSegmentKeydown(index: number, e: KeyboardEvent): void {
    const input = this.segmentInputs[index]
    if (input === undefined) {
      return
    }

    // Auto-advance on separator key
    if ((this.version === 4 && e.key === '.') || (this.version === 6 && e.key === ':')) {
      e.preventDefault()
      const next = this.segmentInputs[index + 1]
      if (next !== undefined) {
        next.focus()
      }
      return
    }

    if (e.key === 'Backspace' && input.value === '') {
      const prev = this.segmentInputs[index - 1]
      if (prev !== undefined) {
        prev.focus()
      }
    } else if (e.key === 'ArrowLeft' && input.selectionStart === 0) {
      const prev = this.segmentInputs[index - 1]
      if (prev !== undefined) {
        prev.focus()
      }
    } else if (e.key === 'ArrowRight' && input.selectionStart === input.value.length) {
      const next = this.segmentInputs[index + 1]
      if (next !== undefined) {
        next.focus()
      }
    }
  }

  private sanitizeIPv4Segment(input: HTMLInputElement): void {
    // Remove non-numeric characters
    const cleaned = input.value.replace(/[^0-9]/g, '')
    const num = parseInt(cleaned, 10)

    if (cleaned === '') {
      input.value = ''
    } else if (Number.isNaN(num)) {
      input.value = ''
    } else if (num > IPV4_MAX_VALUE) {
      input.value = String(IPV4_MAX_VALUE)
    } else {
      input.value = cleaned
    }
  }

  private sanitizeIPv6Segment(input: HTMLInputElement): void {
    // Remove non-hex characters
    input.value = input.value.replace(/[^0-9a-fA-F]/g, '').slice(0, 4)
  }
}
