import { describe, it, expect, beforeEach } from 'vitest';
import { anchorPositioning } from './anchor-positioning.js';
import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';

/**
 * Note on test environment: happy-dom does not implement
 * `CSS.supports('anchor-name', '--x')` or the Popover API surface that
 * actually moves an element into the top layer. Tests therefore land on
 * the fallback path and assert the public contract (attrs, event,
 * connect/disconnect symmetry) — not specific viewport coordinates.
 * Real layout is exercised in the live browser demo.
 */
describe('anchor-positioning', () => {
  beforeEach(resetDOM);

  it('connect with valid anchor sets placed + actual + mode attributes', () => {
    const anchor = document.createElement('div');
    anchor.id = 'anchor-x';
    document.body.appendChild(anchor);
    const host = mountHost('div', {
      'data-anchor': '#anchor-x',
      'data-anchor-placement': 'bottom',
    });
    connectTrait(anchorPositioning, host);
    expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(true);
    expect(host.getAttribute('data-anchor-placement-actual')).toBeTruthy();
    expect(host.getAttribute('data-anchor-mode')).toMatch(/^(native|fallback)$/);
    // Both paths set position:fixed — native because that's what the
    // CSS Anchor Positioning helpers expect, fallback because it's the
    // measured-coords positioning context.
    expect(host.style.position).toBe('fixed');
  });

  it('dispatches anchor-placed event with the actual placement + mode in detail', () => {
    const anchor = document.createElement('div');
    anchor.id = 'anchor-y';
    document.body.appendChild(anchor);
    const host = mountHost('div', { 'data-anchor': '#anchor-y' });
    const spy = spyEvent(host, 'anchor-placed');
    connectTrait(anchorPositioning, host);
    expect(spy.count).toBeGreaterThanOrEqual(1);
    expect(spy.last.actual).toBeTruthy();
    expect(spy.last.mode).toMatch(/^(native|fallback)$/);
  });

  it('resolves anchor by selector OR by bare id', () => {
    const anchor = document.createElement('div');
    anchor.id = 'bare-id-anchor';
    document.body.appendChild(anchor);
    const host = mountHost('div', { 'data-anchor': 'bare-id-anchor' });
    connectTrait(anchorPositioning, host);
    expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(true);
  });

  it('missing anchor: does not throw, does not place', () => {
    const host = mountHost('div', { 'data-anchor': '#nope' });
    expect(() => connectTrait(anchorPositioning, host)).not.toThrow();
    expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(false);
    expect(host.hasAttribute('data-anchor-mode')).toBe(false);
  });

  it('missing data-anchor attribute: does not throw, does not place', () => {
    const host = mountHost('div');
    expect(() => connectTrait(anchorPositioning, host)).not.toThrow();
    expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(false);
  });

  it('disconnect clears all three managed attributes and stops listening', () => {
    const anchor = document.createElement('div');
    anchor.id = 'anchor-z';
    document.body.appendChild(anchor);
    const host = mountHost('div', { 'data-anchor': '#anchor-z' });
    const inst = connectTrait(anchorPositioning, host);
    inst.disconnect(host);
    expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(false);
    expect(host.hasAttribute('data-anchor-placement-actual')).toBe(false);
    expect(host.hasAttribute('data-anchor-mode')).toBe(false);
  });

  it('connect → disconnect → connect again does not throw', () => {
    const anchor = document.createElement('div');
    anchor.id = 'anchor-recycle';
    document.body.appendChild(anchor);
    const host = mountHost('div', { 'data-anchor': '#anchor-recycle' });
    const inst1 = connectTrait(anchorPositioning, host);
    inst1.disconnect(host);
    expect(() => {
      const inst2 = connectTrait(anchorPositioning, host);
      inst2.disconnect(host);
    }).not.toThrow();
  });

  it('honors explicit placements (top / left / right)', () => {
    for (const placement of ['top', 'left', 'right', 'bottom-start', 'top-end']) {
      const anchor = document.createElement('div');
      anchor.id = `anchor-${placement}`;
      document.body.appendChild(anchor);
      const host = mountHost('div', {
        'data-anchor': `#anchor-${placement}`,
        'data-anchor-placement': placement,
      });
      const inst = connectTrait(anchorPositioning, host);
      expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(true);
      expect(host.getAttribute('data-anchor-placement-actual')).toBeTruthy();
      inst.disconnect(host);
      anchor.remove();
      host.remove();
    }
  });

  it('honors data-anchor-gap (numeric, defaults to 0)', () => {
    const anchor = document.createElement('div');
    anchor.id = 'anchor-gap';
    document.body.appendChild(anchor);
    const host = mountHost('div', {
      'data-anchor': '#anchor-gap',
      'data-anchor-gap': '12',
    });
    expect(() => connectTrait(anchorPositioning, host)).not.toThrow();
    expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(true);
  });
});
