import styles from './PasswordStrengthMeter.module.css'

const PASSWORD_RULES = [
  {
    id: 'length',
    test: (value: string) => value.length >= 8,
  },
  {
    id: 'upper',
    test: (value: string) => /[A-Z]/.test(value),
  },
  {
    id: 'number',
    test: (value: string) => /\d/.test(value),
  },
  {
    id: 'special',
    test: (value: string) => /[^A-Za-z0-9]/.test(value),
  },
] as const

export type PasswordRuleId = (typeof PASSWORD_RULES)[number]['id']
export type StrengthTone = 'empty' | 'weak' | 'fair' | 'good' | 'strong'

export type PasswordStrengthLabels = {
  title: string
  empty: string
  weak: string
  fair: string
  good: string
  strong: string
  rules: Record<PasswordRuleId, string>
}

const DEFAULT_LABELS: PasswordStrengthLabels = {
  title: 'Password strength',
  empty: 'Enter a password',
  weak: 'Weak',
  fair: 'Fair',
  good: 'Good',
  strong: 'Strong',
  rules: {
    length: 'At least 8 characters',
    upper: 'One uppercase letter',
    number: 'One number',
    special: 'One special character',
  },
}

export function getPasswordChecks(password: string, labels: PasswordStrengthLabels = DEFAULT_LABELS) {
  return PASSWORD_RULES.map((rule) => ({
    id: rule.id,
    label: labels.rules[rule.id],
    met: rule.test(password),
  }))
}

export function getStrengthMeta(
  metCount: number,
  hasInput: boolean,
  labels: PasswordStrengthLabels = DEFAULT_LABELS,
): {
  tone: StrengthTone
  label: string
  percent: number
} {
  if (!hasInput || metCount === 0) {
    return { tone: 'empty', label: labels.empty, percent: 0 }
  }
  if (metCount === 1) return { tone: 'weak', label: labels.weak, percent: 25 }
  if (metCount === 2) return { tone: 'fair', label: labels.fair, percent: 50 }
  if (metCount === 3) return { tone: 'good', label: labels.good, percent: 75 }
  return { tone: 'strong', label: labels.strong, percent: 100 }
}

export function isPasswordStrong(password: string): boolean {
  return PASSWORD_RULES.every((rule) => rule.test(password))
}

export function getPasswordValidationError(
  password: string,
  labels: PasswordStrengthLabels = DEFAULT_LABELS,
): string | null {
  const unmet = PASSWORD_RULES.filter((rule) => !rule.test(password))
  if (unmet.length === 0) return null
  return unmet.map((rule) => labels.rules[rule.id]).join(', ')
}

export interface PasswordStrengthMeterProps {
  password: string
  labels?: PasswordStrengthLabels
  id?: string
}

export function PasswordStrengthMeter({
  password,
  labels = DEFAULT_LABELS,
  id = 'password-strength',
}: PasswordStrengthMeterProps) {
  const checks = getPasswordChecks(password, labels)
  const metCount = checks.filter((check) => check.met).length
  const strength = getStrengthMeta(metCount, password.length > 0, labels)

  return (
    <div className={styles.strength} id={id}>
      <div className={styles.strengthHeader}>
        <span className={styles.strengthTitle}>{labels.title}</span>
        <span className={`${styles.strengthLabel} ${styles[`tone_${strength.tone}`]}`}>
          {strength.label}
        </span>
      </div>
      <div
        className={styles.strengthTrack}
        role="progressbar"
        aria-valuemin={0}
        aria-valuemax={100}
        aria-valuenow={strength.percent}
        aria-label={labels.title}
      >
        <div
          className={`${styles.strengthFill} ${styles[`fill_${strength.tone}`]}`}
          style={{ width: `${strength.percent}%` }}
        />
      </div>
      <ul className={styles.rules} id={`${id}-rules`}>
        {checks.map((check) => (
          <li key={check.id} className={`${styles.rule} ${check.met ? styles.ruleMet : ''}`}>
            <span className={styles.ruleDot} aria-hidden="true" />
            {check.label}
          </li>
        ))}
      </ul>
    </div>
  )
}
