import { describe, it, expect, beforeEach } from 'vitest';
import { inputMask } from './input-mask.js';
import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';

/**
 * Drive an `input` event with a new value and a caret-position guess.
 * Mirrors what a real browser dispatches when the user types.
 */
function type(host, value, caret) {
  host.value = value;
  if (caret != null) {
    try { host.setSelectionRange(caret, caret); } catch { /* number-typed inputs throw */ }
  }
  host.dispatchEvent(new Event('input'));
}

/**
 * Microtask-flush helper — caret writes are queued via queueMicrotask
 * so the DOM write doesn't fight the browser's own caret reposition.
 */
const flush = () => new Promise(r => queueMicrotask(r));

describe('input-mask', () => {
  beforeEach(resetDOM);

  it('schema declares forms-data category + the documented surface', () => {
    expect(inputMask.schema.category).toBe('forms-data');
    expect(inputMask.schema.attributes).toContain('data-input-mask-active');
    expect(inputMask.schema.attributes).toContain('data-input-mask-complete');
    expect(inputMask.schema.events).toContain('mask-commit');
    expect(inputMask.schema.config).toContain('data-mask-pattern');
    expect(inputMask.schema.config).toContain('data-mask-strip-on-commit');
  });

  it('phone-us named pattern: 4155551234 → (415) 555-1234', () => {
    const host = mountHost('input', { 'data-mask-pattern': 'phone-us' });
    connectTrait(inputMask, host);
    type(host, '4155551234');
    expect(host.value).toBe('(415) 555-1234');
    expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
  });

  it('credit-card pattern: 16 digits → 4-4-4-4 grouping', () => {
    const host = mountHost('input', { 'data-mask-pattern': 'credit-card' });
    connectTrait(inputMask, host);
    type(host, '4111111111111111');
    expect(host.value).toBe('4111 1111 1111 1111');
    expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
  });

  it('partial input does not set complete; full input does', () => {
    const host = mountHost('input', { 'data-mask-pattern': 'date-iso' });
    connectTrait(inputMask, host);
    type(host, '2026');
    expect(host.value).toBe('2026');
    expect(host.hasAttribute('data-input-mask-complete')).toBe(false);
    type(host, '20260504');
    expect(host.value).toBe('2026-05-04');
    expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
  });

  it('user-supplied pattern overrides any naming collision', () => {
    // Pure literal pattern — should be taken verbatim, not matched as a name.
    const host = mountHost('input', { 'data-mask-pattern': 'AAA-####' });
    connectTrait(inputMask, host);
    type(host, 'ABC1234');
    expect(host.value).toBe('ABC-1234');
  });

  it('rejects placeholder-mismatching chars (digit-only mask drops letters)', () => {
    const host = mountHost('input', { 'data-mask-pattern': 'cvv' });
    connectTrait(inputMask, host);
    type(host, '12a3b4');
    // letters skipped, only the 4 digits land in the 4 # slots
    expect(host.value).toBe('1234');
    expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
  });

  it('caret preservation: typing in the middle keeps the cursor anchored', async () => {
    const host = mountHost('input', { 'data-mask-pattern': 'phone-us' });
    connectTrait(inputMask, host);
    // Seed with 4 digits → "(415) " has 4 fillable slots reached with
    // 3 area-code digits + 1 exchange digit: "(415) 5".
    type(host, '4155');
    expect(host.value).toBe('(415) 5');
    // Append a 5th digit. The trait should reformat to "(415) 55" and
    // place the caret at the position after 5 real chars (index 8).
    type(host, '41555', 8);
    await flush();
    expect(host.value).toBe('(415) 55');
    expect(host.selectionStart).toBe(8);
  });

  it('mask-commit on blur fires with raw + formatted detail', () => {
    const host = mountHost('input', { 'data-mask-pattern': 'phone-us' });
    connectTrait(inputMask, host);
    const spy = spyEvent(host, 'mask-commit');
    type(host, '4155551234');
    host.dispatchEvent(new Event('blur'));
    expect(spy.count).toBe(1);
    expect(spy.last.formatted).toBe('(415) 555-1234');
    expect(spy.last.raw).toBe('4155551234');
  });

  it('strip-on-commit replaces the value with the raw form on blur', () => {
    const host = mountHost('input', {
      'data-mask-pattern': 'phone-us',
      'data-mask-strip-on-commit': '',
    });
    connectTrait(inputMask, host);
    type(host, '4155551234');
    expect(host.value).toBe('(415) 555-1234');
    host.dispatchEvent(new Event('blur'));
    expect(host.value).toBe('4155551234');
  });

  it('disconnect clears managed attributes', () => {
    const host = mountHost('input', { 'data-mask-pattern': 'phone-us' });
    const inst = connectTrait(inputMask, host);
    type(host, '4155551234');
    expect(host.hasAttribute('data-input-mask-active')).toBe(true);
    expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
    inst.disconnect(host);
    expect(host.hasAttribute('data-input-mask-active')).toBe(false);
    expect(host.hasAttribute('data-input-mask-complete')).toBe(false);
  });

  it('missing pattern is a no-op (value untouched, no active flag)', () => {
    const host = mountHost('input');
    connectTrait(inputMask, host);
    type(host, 'whatever-the-user-types');
    expect(host.value).toBe('whatever-the-user-types');
    expect(host.hasAttribute('data-input-mask-active')).toBe(false);
  });

  it('AAA letter slot only accepts letters (digits skipped)', () => {
    const host = mountHost('input', { 'data-mask-pattern': 'AAA' });
    connectTrait(inputMask, host);
    type(host, 'A1B2C3');
    expect(host.value).toBe('ABC');
    expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
  });

  it('star slot accepts any char', () => {
    const host = mountHost('input', { 'data-mask-pattern': '***-***' });
    connectTrait(inputMask, host);
    type(host, 'aB1xY9');
    expect(host.value).toBe('aB1-xY9');
    expect(host.hasAttribute('data-input-mask-complete')).toBe(true);
  });
});
