// @ts-nocheck
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { AVTileAdapter } from '../../../../src/ui/shared/AVTileAdapter.js';
import { createFoundryAdapterMock } from '../../../helpers/foundryAdapterMock.js';

describe('AVTileAdapter', () => {
  let adapter;
  let avAdapter;

  beforeEach(() => {
    document.body.innerHTML = `
      <div class="camera-view" data-user-id="user-1"></div>
      <div class="camera-view" data-user-id="user-2"></div>
    `;
    adapter = createFoundryAdapterMock();
    avAdapter = new AVTileAdapter(adapter);
  });

  afterEach(() => {
    avAdapter.disconnect();
  });

  describe('constructor', () => {
    it('stores adapter reference without side effects', () => {
      expect(avAdapter._adapter).toBe(adapter);
      expect(avAdapter._observers).toBeInstanceOf(Map);
      expect(avAdapter._observers.size).toBe(0);
    });
  });

  describe('mount()', () => {
    it('appends element to the matching AV tile', () => {
      const el = document.createElement('div');
      el.dataset.spRole = 'lock-overlay';
      avAdapter.mount('user-1', el);
      const tile = document.querySelector('[data-user-id="user-1"]');
      expect(tile.contains(el)).toBe(true);
    });

    it('marks mounted element with data-sp-mount attribute', () => {
      const el = document.createElement('div');
      el.dataset.spRole = 'lock-overlay';
      avAdapter.mount('user-1', el);
      expect(el.dataset.spMount).toBe('1');
    });

    it('warns and does not throw when tile not found', () => {
      const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
      const el = document.createElement('div');
      expect(() => avAdapter.mount('unknown-user', el)).not.toThrow();
      expect(warnSpy).toHaveBeenCalledWith(
        '[ScryingPool] AVTileAdapter.mount: tile not found for',
        'unknown-user'
      );
      warnSpy.mockRestore();
    });

    it('is idempotent: calling twice with same element does not duplicate', () => {
      const el = document.createElement('div');
      el.dataset.spRole = 'lock-overlay';
      avAdapter.mount('user-1', el);
      avAdapter.mount('user-1', el);
      const tile = document.querySelector('[data-user-id="user-1"]');
      expect(tile.querySelectorAll('[data-sp-role="lock-overlay"]').length).toBe(1);
    });

    it('replaces existing element with same data-sp-role', () => {
      const el1 = document.createElement('div');
      el1.dataset.spRole = 'lock-overlay';
      el1.textContent = 'first';
      avAdapter.mount('user-1', el1);

      const el2 = document.createElement('div');
      el2.dataset.spRole = 'lock-overlay';
      el2.textContent = 'second';
      avAdapter.mount('user-1', el2);

      const tile = document.querySelector('[data-user-id="user-1"]');
      const overlays = tile.querySelectorAll('[data-sp-role="lock-overlay"]');
      expect(overlays.length).toBe(1);
      expect(overlays[0].textContent).toBe('second');
    });

    it('appends multiple elements with different roles', () => {
      const el1 = document.createElement('div');
      el1.dataset.spRole = 'lock-overlay';
      const el2 = document.createElement('div');
      el2.dataset.spRole = 'portrait-fallback';
      avAdapter.mount('user-1', el1);
      avAdapter.mount('user-1', el2);
      const tile = document.querySelector('[data-user-id="user-1"]');
      expect(tile.querySelectorAll('[data-sp-mount]').length).toBe(2);
    });

    it('does not affect other tiles', () => {
      const el = document.createElement('div');
      el.dataset.spRole = 'lock-overlay';
      avAdapter.mount('user-1', el);
      const tile2 = document.querySelector('[data-user-id="user-2"]');
      expect(tile2.querySelectorAll('[data-sp-mount]').length).toBe(0);
    });
  });

  describe('unmount()', () => {
    it('removes all data-sp-mount children from tile', () => {
      const el1 = document.createElement('div');
      el1.dataset.spRole = 'lock-overlay';
      const el2 = document.createElement('div');
      el2.dataset.spRole = 'portrait-fallback';
      avAdapter.mount('user-1', el1);
      avAdapter.mount('user-1', el2);
      avAdapter.unmount('user-1');
      const tile = document.querySelector('[data-user-id="user-1"]');
      expect(tile.querySelectorAll('[data-sp-mount]').length).toBe(0);
    });

    it('does not remove non-managed children', () => {
      const native = document.createElement('video');
      document.querySelector('[data-user-id="user-1"]').appendChild(native);
      avAdapter.unmount('user-1');
      const tile = document.querySelector('[data-user-id="user-1"]');
      expect(tile.contains(native)).toBe(true);
    });

    it('is no-op if tile not found', () => {
      expect(() => avAdapter.unmount('unknown-user')).not.toThrow();
    });

    it('does not affect other tiles', () => {
      const el1 = document.createElement('div');
      el1.dataset.spRole = 'lock-overlay';
      const el2 = document.createElement('div');
      el2.dataset.spRole = 'lock-overlay';
      avAdapter.mount('user-1', el1);
      avAdapter.mount('user-2', el2);
      avAdapter.unmount('user-1');
      const tile2 = document.querySelector('[data-user-id="user-2"]');
      expect(tile2.querySelectorAll('[data-sp-mount]').length).toBe(1);
    });
  });

  describe('setStateClass()', () => {
    it('adds sp-state-{stateName} class to tile', () => {
      avAdapter.setStateClass('user-1', 'hidden');
      const tile = document.querySelector('[data-user-id="user-1"]');
      expect(tile.classList.contains('sp-state-hidden')).toBe(true);
    });

    it('removes all previous sp-state-* classes before adding new', () => {
      const tile = document.querySelector('[data-user-id="user-1"]');
      tile.classList.add('sp-state-active');
      tile.classList.add('sp-state-pending');
      avAdapter.setStateClass('user-1', 'hidden');
      expect(tile.classList.contains('sp-state-active')).toBe(false);
      expect(tile.classList.contains('sp-state-pending')).toBe(false);
      expect(tile.classList.contains('sp-state-hidden')).toBe(true);
    });

    it('clears all sp-state-* classes when stateName is null', () => {
      const tile = document.querySelector('[data-user-id="user-1"]');
      tile.classList.add('sp-state-active');
      avAdapter.setStateClass('user-1', null);
      expect(tile.classList.contains('sp-state-active')).toBe(false);
    });

    it('warns and does not throw when tile not found', () => {
      const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
      expect(() => avAdapter.setStateClass('unknown-user', 'hidden')).not.toThrow();
      expect(warnSpy).toHaveBeenCalledWith(
        '[ScryingPool] AVTileAdapter.setStateClass: tile not found for',
        'unknown-user'
      );
      warnSpy.mockRestore();
    });

    it('does not affect non-sp-* classes on tile', () => {
      const tile = document.querySelector('[data-user-id="user-1"]');
      tile.classList.add('camera-view');
      tile.classList.add('some-other-class');
      avAdapter.setStateClass('user-1', 'hidden');
      expect(tile.classList.contains('camera-view')).toBe(true);
      expect(tile.classList.contains('some-other-class')).toBe(true);
    });
  });

  describe('onTileRerender()', () => {
    it('calls callback when tile children change', async () => {
      const cb = vi.fn();
      avAdapter.onTileRerender('user-1', cb);
      const tile = document.querySelector('[data-user-id="user-1"]');
      tile.appendChild(document.createElement('span'));
      await new Promise(resolve => setTimeout(resolve, 20));
      expect(cb).toHaveBeenCalledWith(tile);
    });

    it('replaces existing observer when called again for same userId', async () => {
      const cb1 = vi.fn();
      const cb2 = vi.fn();
      avAdapter.onTileRerender('user-1', cb1);
      avAdapter.onTileRerender('user-1', cb2);
      // Only one observer should be active per userId
      expect(avAdapter._observers.size).toBe(1);
    });

    it('stores one observer per userId', () => {
      avAdapter.onTileRerender('user-1', vi.fn());
      avAdapter.onTileRerender('user-2', vi.fn());
      expect(avAdapter._observers.size).toBe(2);
    });

    it('is no-op if tile not found', () => {
      expect(() => avAdapter.onTileRerender('unknown-user', vi.fn())).not.toThrow();
      expect(avAdapter._observers.size).toBe(0);
    });
  });

  describe('disconnect()', () => {
    it('stops observers from firing after disconnect', async () => {
      const cb = vi.fn();
      avAdapter.onTileRerender('user-1', cb);
      avAdapter.disconnect();
      const tile = document.querySelector('[data-user-id="user-1"]');
      tile.appendChild(document.createElement('span'));
      await new Promise(resolve => setTimeout(resolve, 20));
      expect(cb).not.toHaveBeenCalled();
    });

    it('clears observer map', () => {
      avAdapter.onTileRerender('user-1', vi.fn());
      avAdapter.onTileRerender('user-2', vi.fn());
      avAdapter.disconnect();
      expect(avAdapter._observers.size).toBe(0);
    });

    it('is safe to call multiple times', () => {
      avAdapter.onTileRerender('user-1', vi.fn());
      expect(() => {
        avAdapter.disconnect();
        avAdapter.disconnect();
      }).not.toThrow();
    });
  });
});
