import { describe, it, expect, beforeEach } from 'vitest';
import { errorShake } from './error-shake.js';
import { mountHost, connectTrait, spyEvent, resetDOM, wait } from '../test-helpers.js';

describe('error-shake', () => {
  beforeEach(resetDOM);

  it('schema declares animation-feedback category', () => {
    expect(errorShake.schema.category).toBe('animation-feedback');
    expect(errorShake.schema.events).toContain('error-shake-done');
    expect(errorShake.schema.attributes).toContain('data-error-shake-active');
  });

  it('connect → disconnect leaves no managed attribute', () => {
    const host = mountHost();
    const inst = connectTrait(errorShake, host);
    inst.disconnect(host);
    expect(host.hasAttribute('data-error-shake-active')).toBe(false);
  });

  it('fires on validated event with detail.valid === false', async () => {
    const host = mountHost();
    connectTrait(errorShake, host);
    const spy = spyEvent(host, 'error-shake-done');

    host.dispatchEvent(new CustomEvent('validated', {
      detail: { valid: false, errors: ['nope'] },
    }));

    expect(host.hasAttribute('data-error-shake-active')).toBe(true);
    // Wait through the default 400ms duration + a small grace window.
    await wait(450);
    expect(spy.count).toBe(1);
    expect(host.hasAttribute('data-error-shake-active')).toBe(false);
  });

  it('does not fire on validated event with detail.valid === true', async () => {
    const host = mountHost();
    connectTrait(errorShake, host);
    const spy = spyEvent(host, 'error-shake-done');

    host.dispatchEvent(new CustomEvent('validated', {
      detail: { valid: true, errors: [] },
    }));

    await wait(50);
    expect(spy.count).toBe(0);
    expect(host.hasAttribute('data-error-shake-active')).toBe(false);
  });

  it('fires when data-validation-invalid is set (false → true)', async () => {
    const host = mountHost();
    connectTrait(errorShake, host);
    const spy = spyEvent(host, 'error-shake-done');

    host.setAttribute('data-validation-invalid', '');
    // MutationObserver microtask flush.
    await wait(0);
    expect(host.hasAttribute('data-error-shake-active')).toBe(true);
    await wait(450);
    expect(spy.count).toBe(1);
  });

  it('does not re-fire when data-validation-invalid stays true across mutations', async () => {
    const host = mountHost('div', { 'data-validation-invalid': '' });
    connectTrait(errorShake, host);
    const spy = spyEvent(host, 'error-shake-done');

    // Re-set the attribute (no false→true edge — it was already true).
    host.setAttribute('data-validation-invalid', '');
    await wait(20);
    expect(spy.count).toBe(0);
  });

  it('respects data-error-shake-trigger attribute toggle', async () => {
    const host = mountHost();
    connectTrait(errorShake, host);
    const spy = spyEvent(host, 'error-shake-done');

    host.setAttribute('data-error-shake-trigger', '');
    await wait(0);
    expect(host.hasAttribute('data-error-shake-active')).toBe(true);
    await wait(450);
    expect(spy.count).toBe(1);
  });

  it('respects data-error-shake-duration config', async () => {
    const host = mountHost('div', { 'data-error-shake-duration': '100' });
    connectTrait(errorShake, host);
    const spy = spyEvent(host, 'error-shake-done');

    host.dispatchEvent(new CustomEvent('validated', {
      detail: { valid: false, errors: [''] },
    }));

    // Should resolve in ~100ms, not the default 400ms.
    await wait(150);
    expect(spy.count).toBe(1);
  });

  it('disconnect clears the keyframe <style> from the document', () => {
    const host = mountHost();
    const inst = connectTrait(errorShake, host);
    host.dispatchEvent(new CustomEvent('validated', {
      detail: { valid: false, errors: [''] },
    }));
    const styleCount = document.querySelectorAll('style').length;
    inst.disconnect(host);
    // After disconnect the keyframe style should be gone — exact count
    // depends on what other styles exist, so we just assert non-greater.
    expect(document.querySelectorAll('style').length).toBeLessThan(styleCount + 1);
    expect(host.style.animation).toBe('');
  });
});
