/**
 * Non-side-effect class export for `<password-strength-ui>`.
 *
 * Importing this file gives you the class without auto-registering the
 * tag. Useful for test isolation, subclassing with tag-name override,
 * or selective composition.
 *
 * The auto-register path stays at `@adia-ai/web-components/components/password-strength`
 * (which imports this file + calls `defineIfFree()`).
 *
 * @see ../../USAGE.md#registration--auto-vs-explicit
 */

/**
 * <password-strength-ui></password-strength-ui>
 *
 * Layout:
 *   ▮▮▮▯  ← 4-segment bar, lit segments coloured per score
 *   Good   ← optional text label
 *
 * Pair with `<input-ui type="password">`:
 *   input.addEventListener('input', e => meter.value = e.target.value);
 *
 * Score buckets (heuristic):
 *   0 Weak    — short or single-class
 *   1 Fair    — 8+ chars OR mixed classes
 *   2 Good    — 12+ chars + ≥3 classes
 *   3 Strong  — 16+ chars + 4 classes
 *
 * NOT zxcvbn — this is a tiny first-line heuristic. For security-
 * critical surfaces, consumers can override the score by listening
 * to `input` on the field and setting their own `data-score` on the
 * meter, OR by extending this class.
 *
 * Security: `value` is JS-property only. NOT reflected to a DOM
 * attribute; the password never appears in rendered HTML.
 */

import { UIElement } from '../../core/element.js';

const LABELS = ['Weak', 'Fair', 'Good', 'Strong'];

/**
 * Compute a 0..3 score for the given password string.
 * Returns -1 for empty (sentinel — caller uses to grey out the bar).
 *
 * Scoring rubric:
 *   +1 length >= 8
 *   +1 length >= 12
 *   +1 length >= 16
 *   +1 ≥ 2 character classes (lower / upper / digit / symbol)
 *   +1 ≥ 3 character classes
 *   +1 all 4 character classes
 *   -1 contains 3+ same-char run (e.g. "aaa")
 *   -1 password is too short (< 8 chars) — clamps score to ≤ 0
 *
 * Clamped to [0, 3].
 */
function scorePassword(pwd) {
  if (!pwd) return -1;
  let score = 0;
  if (pwd.length >= 8) score++;
  if (pwd.length >= 12) score++;
  if (pwd.length >= 16) score++;

  const classes = [
    /[a-z]/.test(pwd),
    /[A-Z]/.test(pwd),
    /[0-9]/.test(pwd),
    /[^a-zA-Z0-9]/.test(pwd),
  ].filter(Boolean).length;
  if (classes >= 2) score++;
  if (classes >= 3) score++;
  if (classes === 4) score++;

  // Penalty: same-char run of 3+ (aaa, 111)
  if (/(.)\1{2,}/.test(pwd)) score--;

  // Hard floor for very short passwords — never above "Weak"
  if (pwd.length < 8) score = Math.min(score, 0);

  return Math.max(0, Math.min(3, score));
}

export class UIPasswordStrength extends UIElement {
  static properties = {
    // JS-property only — no `reflect: true` so the password never
    // appears in the DOM attribute set. `dynamic: true` keeps it out
    // of any static-attribute extraction the a2ui pipeline does.
    value:     { type: String,  default: '' },
    minScore:  { type: Number,  default: 2,    reflect: true, attribute: 'min-score' },
    showLabel: { type: Boolean, default: true, reflect: true, attribute: 'show-label' },
  };

  static template = () => null;

  #lastEmittedScore = -2; // sentinel — different from any valid score / -1

  connected() {
    super.connected();
    if (!this.querySelector('[slot="bar"]')) {
      this.innerHTML = `
        <div slot="bar" aria-hidden="true">
          <div slot="segment" data-i="0"></div>
          <div slot="segment" data-i="1"></div>
          <div slot="segment" data-i="2"></div>
          <div slot="segment" data-i="3"></div>
        </div>
        <span slot="label" aria-live="polite"></span>
      `;
    }
    // ARIA meter semantics
    this.setAttribute('role', 'meter');
    this.setAttribute('aria-valuemin', '0');
    this.setAttribute('aria-valuemax', '3');
  }

  render() {
    const score = scorePassword(this.value);

    if (score < 0) {
      this.removeAttribute('data-score');
      this.removeAttribute('aria-valuenow');
      this.setAttribute('aria-valuetext', 'Empty');
    } else {
      this.setAttribute('data-score', String(score));
      this.setAttribute('aria-valuenow', String(score));
      this.setAttribute('aria-valuetext', LABELS[score]);
    }

    // Update segment lit state via [data-lit]
    const segments = this.querySelectorAll('[slot="segment"]');
    segments.forEach((seg, i) => {
      if (score >= 0 && i <= score) seg.setAttribute('data-lit', '');
      else seg.removeAttribute('data-lit');
    });

    // Update label text
    const labelEl = this.querySelector('[slot="label"]');
    if (labelEl) {
      labelEl.textContent = score >= 0 && this.showLabel !== false ? LABELS[score] : '';
    }

    // Emit score-change only on bucket transitions
    if (score !== this.#lastEmittedScore) {
      this.#lastEmittedScore = score;
      this.dispatchEvent(new CustomEvent('score-change', {
        bubbles: true,
        detail: {
          score,
          label: score >= 0 ? LABELS[score] : '',
          satisfied: score >= this.minScore,
        },
      }));
    }
  }
}
