/**
 * --------------------------------------------------------------------------
 * Bootstrap strength.ts
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 * --------------------------------------------------------------------------
 */

import BaseComponent from './base-component.js'
import EventHandler from './dom/event-handler.js'
import SelectorEngine from './dom/selector-engine.js'

/**
 * Constants
 */

const NAME = 'strength'
const DATA_KEY = 'bs.strength'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'

const EVENT_STRENGTH_CHANGE = `strengthChange${EVENT_KEY}`

const SELECTOR_DATA_STRENGTH = '[data-bs-strength]'

const STRENGTH_LEVELS = ['weak', 'fair', 'good', 'strong']

type StrengthConfig = {
  input: string | HTMLInputElement | null
  minLength: number
  messages: Record<string, string>
  weights: Record<string, number>
  thresholds: number[]
  scorer: ((password: string) => number) | null
}

const Default: StrengthConfig = {
  input: null, // Selector or element for password input
  minLength: 8,
  messages: {
    weak: 'Weak',
    fair: 'Fair',
    good: 'Good',
    strong: 'Strong'
  },
  weights: {
    minLength: 1,
    extraLength: 1,
    lowercase: 1,
    uppercase: 1,
    numbers: 1,
    special: 1,
    multipleSpecial: 1,
    longPassword: 1
  },
  thresholds: [2, 4, 6], // weak ≤2, fair ≤4, good ≤6, strong >6
  scorer: null // Custom scoring function (password) => number
}

const DefaultType = {
  input: '(string|element|null)',
  minLength: 'number',
  messages: 'object',
  weights: 'object',
  thresholds: 'array',
  scorer: '(function|null)'
}

/**
 * Class definition
 */

class Strength extends BaseComponent {
  protected declare _config: StrengthConfig
  protected declare _input: HTMLInputElement | null
  protected declare _segments: HTMLElement[]
  protected declare _textElement: HTMLElement | null
  protected declare _currentStrength: string | null

  constructor(element?: string | Element | null, config?: Partial<StrengthConfig> | null) {
    super(element, config)

    this._input = this._getInput()
    this._segments = SelectorEngine.find('.strength-segment', this._element)
    this._textElement = SelectorEngine.findOne('.strength-text', this._element.parentElement!)
    this._currentStrength = null

    if (this._input) {
      this._addEventListeners()
      // Check initial value
      this._evaluate()
    }
  }

  // Getters
  static override get Default(): StrengthConfig {
    return Default
  }

  static override get DefaultType(): Record<string, string> {
    return DefaultType
  }

  static override get NAME(): string {
    return NAME
  }

  // Public
  getStrength(): string | null {
    return this._currentStrength
  }

  evaluate(): void {
    this._evaluate()
  }

  override dispose(): void {
    EventHandler.off(this._input, EVENT_KEY)
    super.dispose()
  }

  // Private
  protected _getInput(): HTMLInputElement | null {
    if (this._config.input) {
      return typeof this._config.input === 'string' ?
        SelectorEngine.findOne<HTMLInputElement>(this._config.input) :
        this._config.input
    }

    // Look for preceding password input
    const parent = this._element.parentElement
    return SelectorEngine.findOne<HTMLInputElement>('input[type="password"]', parent!)
  }

  protected _addEventListeners(): void {
    EventHandler.on(this._input, `input${EVENT_KEY}`, () => this._evaluate())
    EventHandler.on(this._input, `change${EVENT_KEY}`, () => this._evaluate())
  }

  protected _evaluate(): void {
    const password = this._input!.value
    const score = this._calculateScore(password)
    const strength = this._scoreToStrength(score)

    if (strength !== this._currentStrength) {
      this._currentStrength = strength
      this._updateUI(strength)

      EventHandler.trigger(this._element, EVENT_STRENGTH_CHANGE, {
        strength,
        score,
        password: password.length > 0 ? '***' : '' // Don't expose actual password
      })
    }
  }

  protected _calculateScore(password: string): number {
    if (!password) {
      return 0
    }

    // Use custom scorer if provided
    if (typeof this._config.scorer === 'function') {
      return this._config.scorer(password)
    }

    const { weights } = this._config
    let score = 0

    // Length scoring
    if (password.length >= this._config.minLength) {
      score += weights.minLength
    }

    if (password.length >= this._config.minLength + 4) {
      score += weights.extraLength
    }

    // Character variety
    if (/[a-z]/.test(password)) {
      score += weights.lowercase
    }

    if (/[A-Z]/.test(password)) {
      score += weights.uppercase
    }

    if (/\d/.test(password)) {
      score += weights.numbers
    }

    // Special characters
    if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
      score += weights.special
    }

    // Extra points for more special chars or length
    if (/[!@#$%^&*(),.?":{}|<>].*[!@#$%^&*(),.?":{}|<>]/.test(password)) {
      score += weights.multipleSpecial
    }

    if (password.length >= 16) {
      score += weights.longPassword
    }

    return score
  }

  protected _scoreToStrength(score: number): string | null {
    if (score === 0) {
      return null
    }

    const [weak, fair, good] = this._config.thresholds

    if (score <= weak) {
      return 'weak'
    }

    if (score <= fair) {
      return 'fair'
    }

    if (score <= good) {
      return 'good'
    }

    return 'strong'
  }

  protected _updateUI(strength: string | null): void {
    // Update data attribute on element
    if (strength) {
      this._element.dataset.bsStrength = strength
    } else {
      delete this._element.dataset.bsStrength
    }

    // Update segmented meter
    const strengthIndex = strength ? STRENGTH_LEVELS.indexOf(strength) : -1

    for (const [index, segment] of this._segments.entries()) {
      if (index <= strengthIndex) {
        segment.classList.add('active')
      } else {
        segment.classList.remove('active')
      }
    }

    // Update text feedback
    if (this._textElement) {
      if (strength && this._config.messages[strength]) {
        this._textElement.textContent = this._config.messages[strength]
        this._textElement.dataset.bsStrength = strength

        // Also set the color via inheriting from parent or using CSS variable
        const colorMap: Record<string, string> = {
          weak: 'danger',
          fair: 'warning',
          good: 'info',
          strong: 'success'
        }
        this._textElement.style.setProperty('--strength-color', `var(--${colorMap[strength]}-text)`)
      } else {
        this._textElement.textContent = ''
        delete this._textElement.dataset.bsStrength
      }
    }
  }
}

/**
 * Data API implementation
 */

EventHandler.on(document, `DOMContentLoaded${EVENT_KEY}${DATA_API_KEY}`, () => {
  for (const element of SelectorEngine.find(SELECTOR_DATA_STRENGTH)) {
    Strength.getOrCreateInstance(element)
  }
})

export default Strength
export type { StrengthConfig }
