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

// Mock ScryingPoolStrip before it's imported so Application global isn't needed
vi.mock('../../../src/ui/gm/ScryingPoolStrip.js', () => ({
  ScryingPoolStrip: vi.fn().mockImplementation(() => ({
    render: vi.fn().mockResolvedValue(undefined),
    close: vi.fn(),
    rendered: false,
  })),
}));

import { RoleRenderer } from '../../../src/ui/RoleRenderer.js';

function makeAVTileAdapter() {
  return {
    mount: vi.fn(),
    unmount: vi.fn(),
    setStateClass: vi.fn(),
    disconnect: vi.fn(),
    onTileRerender: vi.fn(),
  };
}

function makeStateStore() {
  const states = new Map();
  return {
    getState: vi.fn(userId => states.get(userId) ?? 'active'),
    _states: states,
  };
}

function makeController() {
  return {
    action: vi.fn(),
    getRevision: vi.fn(() => 0),
    hasPendingOp: vi.fn(() => false),
  };
}

describe('RoleRenderer', () => {
  let adapter;
  let avTileAdapter;
  let stateStore;
  let controller;
  let renderer;
  let hooksStub;

  beforeEach(() => {
    hooksStub = { on: vi.fn(), off: vi.fn(), once: vi.fn(), callAll: vi.fn() };
    vi.stubGlobal('Hooks', hooksStub);
    vi.stubGlobal('game', { webrtc: {}, user: { setFlag: vi.fn(), getFlag: vi.fn(() => null) } });

    adapter = createFoundryAdapterMock();
    avTileAdapter = makeAVTileAdapter();
    stateStore = makeStateStore();
    controller = makeController();
    renderer = new RoleRenderer(stateStore, controller, avTileAdapter, adapter);
  });

  afterEach(() => {
    vi.unstubAllGlobals();
    vi.clearAllMocks();
  });

  describe('constructor', () => {
    it('stores all injected dependencies without side effects', () => {
      expect(renderer._stateStore).toBe(stateStore);
      expect(renderer._controller).toBe(controller);
      expect(renderer._avTileAdapter).toBe(avTileAdapter);
      expect(renderer._adapter).toBe(adapter);
    });

    it('does not register any Hooks in constructor', () => {
      expect(hooksStub.on).not.toHaveBeenCalled();
    });

    it('_strip is null before openStrip()', () => {
      expect(renderer._strip).toBeNull();
    });
  });

  describe('init()', () => {
    it('registers scrying-pool:stateChanged hook', () => {
      renderer.init();
      expect(hooksStub.on).toHaveBeenCalledWith(
        'scrying-pool:stateChanged',
        expect.any(Function)
      );
    });

    it('registers scrying-pool:controllerAction hook', () => {
      renderer.init();
      expect(hooksStub.on).toHaveBeenCalledWith(
        'scrying-pool:controllerAction',
        expect.any(Function)
      );
    });

    it('registers updateUser hook', () => {
      renderer.init();
      expect(hooksStub.on).toHaveBeenCalledWith('updateUser', expect.any(Function));
    });
  });

  describe('_applyAVTileState()', () => {
    it('calls setStateClass on avTileAdapter with userId and state', () => {
      renderer._applyAVTileState('user-1', 'active');
      expect(avTileAdapter.setStateClass).toHaveBeenCalledWith('user-1', 'active');
    });

    it('mounts lock-overlay element when state is hidden', () => {
      renderer._applyAVTileState('user-1', 'hidden');
      expect(avTileAdapter.mount).toHaveBeenCalled();
      const el = avTileAdapter.mount.mock.calls[0][1];
      expect(el.dataset.spRole).toBe('lock-overlay');
    });

    it('unmounts lock-overlay when state transitions away from hidden', () => {
      renderer._applyAVTileState('user-1', 'active');
      expect(avTileAdapter.unmount).toHaveBeenCalledWith('user-1');
    });

    it('mounts portrait-fallback when state is never-connected', () => {
      renderer._applyAVTileState('user-1', 'never-connected');
      expect(avTileAdapter.mount).toHaveBeenCalled();
      const el = avTileAdapter.mount.mock.calls[0][1];
      expect(el.dataset.spRole).toBe('portrait-fallback');
    });

    it('mounts portrait-fallback when state is cam-lost', () => {
      renderer._applyAVTileState('user-1', 'cam-lost');
      expect(avTileAdapter.mount).toHaveBeenCalled();
      const el = avTileAdapter.mount.mock.calls[0][1];
      expect(el.dataset.spRole).toBe('portrait-fallback');
    });

    it('unmounts portrait-fallback when state is not camera-absent', () => {
      renderer._applyAVTileState('user-1', 'active');
      expect(avTileAdapter.unmount).toHaveBeenCalled();
    });

    it('does not mount any overlay for active state', () => {
      renderer._applyAVTileState('user-1', 'active');
      expect(avTileAdapter.mount).not.toHaveBeenCalled();
    });
  });

  describe('stateChanged hook handler', () => {
    it('calls _applyAVTileState when scrying-pool:stateChanged fires', () => {
      renderer.init();
      const spy = vi.spyOn(renderer, '_applyAVTileState');
      const handler = hooksStub.on.mock.calls.find(
        c => c[0] === 'scrying-pool:stateChanged'
      )[1];
      handler({ userId: 'user-1', state: 'hidden' });
      expect(spy).toHaveBeenCalledWith('user-1', 'hidden');
    });

    it('handles bulk matrix payload gracefully', () => {
      renderer.init();
      const handler = hooksStub.on.mock.calls.find(
        c => c[0] === 'scrying-pool:stateChanged'
      )[1];
      // bulk payload has no userId
      expect(() => handler({ matrix: {}, timestamp: Date.now(), revision: 1 })).not.toThrow();
    });
  });

  describe('openStrip()', () => {
    it('constructs ScryingPoolStrip lazily on first call', async () => {
      const { ScryingPoolStrip } = await import('../../../src/ui/gm/ScryingPoolStrip.js');
      vi.clearAllMocks();
      renderer.openStrip();
      expect(ScryingPoolStrip).toHaveBeenCalledOnce();
    });

    it('reuses existing strip instance on second call', async () => {
      const { ScryingPoolStrip } = await import('../../../src/ui/gm/ScryingPoolStrip.js');
      vi.clearAllMocks();
      renderer.openStrip();
      renderer.openStrip();
      expect(ScryingPoolStrip).toHaveBeenCalledOnce();
    });

    it('calls render on the strip', () => {
      renderer.openStrip();
      expect(renderer._strip.render).toHaveBeenCalled();
    });
  });

  describe('closeStrip()', () => {
    it('calls close on the strip if it exists', () => {
      renderer.openStrip();
      const strip = renderer._strip;
      renderer.closeStrip();
      expect(strip.close).toHaveBeenCalled();
    });

    it('is no-op if strip is not open', () => {
      expect(() => renderer.closeStrip()).not.toThrow();
    });
  });
});
