/**
 * ScenePresetManager unit tests.
 *
 * Import rule: tests may import from src/ but test files themselves are not subject to
 * the src/ import boundary rules (they're in tests/).
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { ScenePresetManager } from '../../../src/core/ScenePresetManager.js';
import { createScenePreset, MAX_PRESETS_PER_WORLD } from '../../../src/contracts/scene-preset.js';
import { SOCKET_EVENTS } from '../../../src/contracts/socket-message.js';

// Test helper: create a mock FoundryAdapter surface
/**
 * Creates a mock adapter with minimal required surfaces for ScenePresetManager testing.
 * @param {Partial<import('../../../src/foundry/FoundryAdapter.js').FoundryAdapter>} overrides
 * @returns {import('../../../src/foundry/FoundryAdapter.js').FoundryAdapter}
 */
function createMockAdapter(overrides = {}) {
  return {
    scenes: {
      current: vi.fn(() => ({ id: 'scene1', getFlag: vi.fn(), setFlag: vi.fn().mockResolvedValue({}) })),
      ...overrides.scenes,
    },
    users: {
      isGM: vi.fn(() => true),
      current: vi.fn(() => ({ id: 'gm1', name: 'Test GM' })),
      all: vi.fn(() => [{ id: 'user1' }, { id: 'user2' }]),
      ...overrides.users,
    },
    settings: {
      get: vi.fn((key) => {
        // Default: auto-apply enabled
        if (key === 'autoApplyEnabled') return true;
        return null;
      }),
      set: vi.fn().mockResolvedValue({}),
      ...overrides.settings,
    },
    hooks: {
      on: vi.fn(() => 42),
      off: vi.fn(),
      callAll: vi.fn(),
      once: vi.fn(),
      ...overrides.hooks,
    },
    socket: {
      emit: vi.fn(),
      on: vi.fn(),
      off: vi.fn(),
      ...overrides.socket,
    },
    notifications: {
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
      ...overrides.notifications,
    },
    i18n: {
      localize: vi.fn((key) => key),
      ...overrides.i18n,
    },
    ...overrides,
  };
}

// Test helper: create a mock StateStore surface
function createMockStateStore(overrides = {}) {
  return {
    getState: vi.fn(() => 'active'),
    getMatrix: vi.fn(() => ({ _version: 1, matrix: { user1: 'active', user2: 'active' } })),
    setMatrix: vi.fn().mockResolvedValue({}),
    init: vi.fn(),
    ...overrides,
  };
}

// Test helper: create a mock SocketHandler surface
function createMockSocketHandler(overrides = {}) {
  return {
    emit: vi.fn(),
    registerPendingOp: vi.fn(),
    confirmPendingOp: vi.fn(),
    setReady: vi.fn(),
    destroy: vi.fn(),
    ...overrides,
  };
}

// Test helper: create a valid ScenePreset
function createTestPreset(name = 'Test Preset', matrix = { user1: 'active', user2: 'hidden' }) {
  return createScenePreset(name, matrix);
}

// Test helper: create a scene flag presets object
function createSceneFlagPresets(presets = {}) {
  return { _version: 1, presets };
}

// ============================================================================
// ScenePresetManager Tests
// ============================================================================

describe('ScenePresetManager', () => {
  let adapter;
  let stateStore;
  let socketHandler;
  let manager;

  beforeEach(() => {
    vi.clearAllMocks();
    adapter = createMockAdapter();
    stateStore = createMockStateStore();
    socketHandler = createMockSocketHandler();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  // --------------------------------------------------------------------------
  // Constructor Tests
  // --------------------------------------------------------------------------

  describe('constructor()', () => {
    it('should throw TypeError when adapter is null', () => {
      expect(() => new ScenePresetManager(null, stateStore, socketHandler)).toThrow(TypeError);
    });

    it('should throw TypeError when adapter is not an object', () => {
      expect(() => new ScenePresetManager('invalid', stateStore, socketHandler)).toThrow(TypeError);
    });

    it('should throw TypeError when stateStore is null', () => {
      expect(() => new ScenePresetManager(adapter, null, socketHandler)).toThrow(TypeError);
    });

    it('should throw TypeError when stateStore is not an object', () => {
      expect(() => new ScenePresetManager(adapter, 'invalid', socketHandler)).toThrow(TypeError);
    });

    it('should throw TypeError when socketHandler is null', () => {
      expect(() => new ScenePresetManager(adapter, stateStore, null)).toThrow(TypeError);
    });

    it('should throw TypeError when socketHandler is not an object', () => {
      expect(() => new ScenePresetManager(adapter, stateStore, 'invalid')).toThrow(TypeError);
    });

    it('should accept valid dependencies and initialize internal state', () => {
      manager = new ScenePresetManager(adapter, stateStore, socketHandler);
      expect(manager._adapter).toBe(adapter);
      expect(manager._stateStore).toBe(stateStore);
      expect(manager._socketHandler).toBe(socketHandler);
      expect(manager._presetsCache).toBeInstanceOf(Map);
    });

    it('should be side-effect-free: no hooks registered in constructor', () => {
      manager = new ScenePresetManager(adapter, stateStore, socketHandler);
      expect(adapter.hooks.on).not.toHaveBeenCalled();
      expect(adapter.socket.on).not.toHaveBeenCalled();
      expect(socketHandler.emit).not.toHaveBeenCalled();
    });
  });

  // --------------------------------------------------------------------------
  // init() Tests
  // --------------------------------------------------------------------------

  describe('init()', () => {
    beforeEach(() => {
      manager = new ScenePresetManager(adapter, stateStore, socketHandler);
    });

    it('should load presets from current scene on init', () => {
      const mockScene = { id: 'scene1', getFlag: vi.fn().mockReturnValue(null) };
      adapter.scenes.current.mockReturnValue(mockScene);
      
      manager.init();
      
      expect(adapter.scenes.current).toHaveBeenCalled();
    });

    it('should handle missing current scene gracefully', () => {
      adapter.scenes.current.mockReturnValue(null);
      
      expect(() => manager.init()).not.toThrow();
    });

    it('should be idempotent: calling init() multiple times should not cause issues', () => {
      const mockScene = { id: 'scene1', getFlag: vi.fn().mockReturnValue(null) };
      adapter.scenes.current.mockReturnValue(mockScene);
      
      expect(() => {
        manager.init();
        manager.init();
      }).not.toThrow();
    });
  });

  // --------------------------------------------------------------------------
  // teardown() Tests
  // --------------------------------------------------------------------------

  describe('teardown()', () => {
    beforeEach(() => {
      manager = new ScenePresetManager(adapter, stateStore, socketHandler);
      // Pre-populate cache
      manager._presetsCache.set('preset1', createTestPreset('preset1'));
    });

    it('should clear the presets cache', () => {
      expect(manager._presetsCache.size).toBe(1);
      manager.teardown();
      expect(manager._presetsCache.size).toBe(0);
    });

    it('should be idempotent: calling teardown() multiple times should not cause issues', () => {
      expect(() => {
        manager.teardown();
        manager.teardown();
      }).not.toThrow();
    });

    it('should not throw when called before init()', () => {
      const freshManager = new ScenePresetManager(adapter, stateStore, socketHandler);
      expect(() => freshManager.teardown()).not.toThrow();
    });
  });

  // --------------------------------------------------------------------------
  // save() Tests
  // --------------------------------------------------------------------------

  describe('save()', () => {
    beforeEach(() => {
      manager = new ScenePresetManager(adapter, stateStore, socketHandler);
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(null),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
    });

    it('should throw TypeError when name is null', async () => {
      await expect(manager.save(null)).rejects.toThrow(TypeError);
    });

    it('should throw TypeError when name is empty string', async () => {
      await expect(manager.save('')).rejects.toThrow(TypeError);
    });

    it('should throw TypeError when name is not a string', async () => {
      await expect(manager.save(123)).rejects.toThrow(TypeError);
    });

    it('should throw TypeError when max presets (50) is reached', async () => {
      // Pre-populate with 50 presets
      const presets = {};
      for (let i = 0; i < MAX_PRESETS_PER_WORLD; i++) {
        presets[`preset${i}`] = createTestPreset(`preset${i}`);
      }
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(createSceneFlagPresets(presets)),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      
      const freshManager = new ScenePresetManager(adapter, stateStore, socketHandler);
      freshManager.init();
      
      await expect(freshManager.save('new-preset')).rejects.toThrow(TypeError);
    });

    it('should save preset with current visibility matrix', async () => {
      const matrix = { user1: 'active', user2: 'hidden' };
      stateStore.getMatrix.mockReturnValue({ _version: 1, matrix });
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(null),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      
      const preset = await manager.save('Combat View');
      
      expect(preset.name).toBe('Combat View');
      expect(preset.matrix).toEqual(matrix);
      expect(mockScene.setFlag).toHaveBeenCalled();
    });

    it('should return the created preset', async () => {
      const matrix = { user1: 'active', user2: 'hidden' };
      stateStore.getMatrix.mockReturnValue({ _version: 1, matrix });
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(null),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      
      const preset = await manager.save('Test Preset');
      
      expect(preset).toBeDefined();
      expect(preset.name).toBe('Test Preset');
      expect(preset._version).toBe(1);
      expect(preset.createdAt).toBeDefined();
      expect(preset.updatedAt).toBeDefined();
    });

    it('should use adapter.i18n.localize for notification messages', async () => {
      const matrix = { user1: 'active' };
      stateStore.getMatrix.mockReturnValue({ _version: 1, matrix });
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(null),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      adapter.i18n.localize.mockReturnValue('Preset saved');
      
      await manager.save('Test Preset');
      
      expect(adapter.i18n.localize).toHaveBeenCalled();
    });
  });

  // --------------------------------------------------------------------------
  // Duplicate Name Handling Tests
  // --------------------------------------------------------------------------

  describe('save() duplicate name handling', () => {
    beforeEach(() => {
      manager = new ScenePresetManager(adapter, stateStore, socketHandler);
      // Start with empty presets to allow first save to succeed
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(createSceneFlagPresets({})),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
    });

    it('should detect duplicate preset name', async () => {
      // First save should succeed
      stateStore.getMatrix.mockReturnValue({ _version: 1, matrix: { user1: 'active' } });
      await manager.save('Existing');
      
      // Second save with same name should fail
      await expect(manager.save('Existing')).rejects.toThrow(TypeError);
    });
  });

  // --------------------------------------------------------------------------
  // load() Tests
  // --------------------------------------------------------------------------

  describe('load()', () => {
    beforeEach(() => {
      manager = new ScenePresetManager(adapter, stateStore, socketHandler);
      const presets = {
        'Test Preset': createTestPreset('Test Preset', { user1: 'active', user2: 'hidden' }),
      };
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(createSceneFlagPresets(presets)),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
    });

    it('should throw TypeError when name is null', async () => {
      await expect(manager.load(null)).rejects.toThrow(TypeError);
    });

    it('should throw TypeError when name is empty string', async () => {
      await expect(manager.load('')).rejects.toThrow(TypeError);
    });

    it('should throw TypeError when preset not found', async () => {
      await expect(manager.load('NonExistent')).rejects.toThrow(TypeError);
    });

    it('should load preset and apply its matrix via StateStore.setMatrix()', async () => {
      await manager.load('Test Preset');
      
      expect(stateStore.setMatrix).toHaveBeenCalledWith({
        _version: 1,
        matrix: { user1: 'active', user2: 'hidden' },
      });
    });

    it('should emit socket message for preset applied', async () => {
      await manager.load('Test Preset');
      
      expect(socketHandler.emit).toHaveBeenCalledWith(
        SOCKET_EVENTS.PRESET_APPLIED,
        expect.objectContaining({
          presetName: 'Test Preset',
        })
      );
    });

    it('should emit notification via adapter.notifications', async () => {
      adapter.i18n.localize.mockReturnValue('GM applied preset: Test Preset');
      
      await manager.load('Test Preset');
      
      expect(adapter.notifications.info).toHaveBeenCalledWith('GM applied preset: Test Preset');
    });

    it('should return without error on successful load', async () => {
      await expect(manager.load('Test Preset')).resolves.not.toThrow();
    });
  });

  // --------------------------------------------------------------------------
  // list() Tests
  // --------------------------------------------------------------------------

  describe('list()', () => {
    beforeEach(() => {
      manager = new ScenePresetManager(adapter, stateStore, socketHandler);
    });

    it('should return empty array when no scene is active', () => {
      adapter.scenes.current.mockReturnValue(null);
      manager.init();
      
      const presets = manager.list();
      expect(presets).toEqual([]);
    });

    it('should return all presets for current scene', () => {
      const presets = {
        'Preset 1': createTestPreset('Preset 1'),
        'Preset 2': createTestPreset('Preset 2'),
      };
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(createSceneFlagPresets(presets)),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
      
      const result = manager.list();
      expect(result).toHaveLength(2);
      expect(result[0].name).toBe('Preset 1');
      expect(result[1].name).toBe('Preset 2');
    });

    it('should return empty array when no presets exist', () => {
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(null),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
      
      const presets = manager.list();
      expect(presets).toEqual([]);
    });

    it('should validate and filter invalid presets', () => {
      const presets = {
        'Valid': createTestPreset('Valid'),
        'Invalid': { invalid: true },
      };
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(createSceneFlagPresets(presets)),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
      
      const result = manager.list();
      expect(result).toHaveLength(1);
      expect(result[0].name).toBe('Valid');
    });
  });

  // --------------------------------------------------------------------------
  // delete() Tests
  // --------------------------------------------------------------------------

  describe('delete()', () => {
    beforeEach(() => {
      manager = new ScenePresetManager(adapter, stateStore, socketHandler);
    });

    it('should throw TypeError when name is null', async () => {
      await expect(manager.delete(null)).rejects.toThrow(TypeError);
    });

    it('should throw TypeError when name is empty string', async () => {
      await expect(manager.delete('')).rejects.toThrow(TypeError);
    });

    it('should delete preset from scene flag', async () => {
      const presets = {
        'To Delete': createTestPreset('To Delete'),
        'To Keep': createTestPreset('To Keep'),
      };
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(createSceneFlagPresets(presets)),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
      
      await manager.delete('To Delete');
      
      const setFlagCall = mockScene.setFlag.mock.calls[0];
      expect(setFlagCall[2].presets).toHaveProperty('To Keep');
      expect(setFlagCall[2].presets).not.toHaveProperty('To Delete');
    });

    it('should return without error on successful delete', async () => {
      const presets = { 'To Delete': createTestPreset('To Delete') };
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(createSceneFlagPresets(presets)),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
      
      await expect(manager.delete('To Delete')).resolves.not.toThrow();
    });
  });

  // --------------------------------------------------------------------------
  // rename() Tests
  // --------------------------------------------------------------------------

  describe('rename()', () => {
    beforeEach(() => {
      manager = new ScenePresetManager(adapter, stateStore, socketHandler);
    });

    it('should throw TypeError when oldName is null', async () => {
      await expect(manager.rename(null, 'New Name')).rejects.toThrow(TypeError);
    });

    it('should throw TypeError when newName is null', async () => {
      await expect(manager.rename('Old Name', null)).rejects.toThrow(TypeError);
    });

    it('should throw TypeError when oldName not found', async () => {
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(createSceneFlagPresets({})),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
      
      await expect(manager.rename('NonExistent', 'New Name')).rejects.toThrow(TypeError);
    });

    it('should throw TypeError when newName conflicts with existing preset', async () => {
      const presets = {
        'Existing': createTestPreset('Existing'),
        'Old Name': createTestPreset('Old Name'),
      };
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(createSceneFlagPresets(presets)),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
      
      await expect(manager.rename('Old Name', 'Existing')).rejects.toThrow(TypeError);
    });

    it('should rename preset and update timestamps', async () => {
      const presets = { 'Old Name': createTestPreset('Old Name') };
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(createSceneFlagPresets(presets)),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
      
      await manager.rename('Old Name', 'New Name');
      
      const setFlagCall = mockScene.setFlag.mock.calls[0];
      expect(setFlagCall[2].presets['New Name']).toBeDefined();
      expect(setFlagCall[2].presets['Old Name']).toBeUndefined();
    });

    it('should return the renamed preset', async () => {
      const presets = { 'Old Name': createTestPreset('Old Name') };
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(createSceneFlagPresets(presets)),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      manager.init();
      
      const preset = await manager.rename('Old Name', 'New Name');
      expect(preset.name).toBe('New Name');
    });
  });

  // --------------------------------------------------------------------------
  // Story 3.2: Auto-Apply Tests
  // NOTE: These tests are written BEFORE implementation (TDD Red Phase)
  // They will FAIL until the Story 3.2 implementation is complete.
  // --------------------------------------------------------------------------

  describe('constructor() with visibilityManager [Story 3.2]', () => {
    let visibilityManager;

    beforeEach(() => {
      visibilityManager = {
        applyMatrix: vi.fn().mockResolvedValue({}),
        getMatrix: vi.fn(() => ({ _version: 1, matrix: {} })),
      };
    });

    it('STORY32-TDD: should accept visibilityManager as 4th parameter', () => {
      // This test drives the constructor extension for Story 3.2
      // Currently fails because constructor only accepts 3 parameters
      expect(() => {
        // @ts-expect-error - Adding 4th parameter for Story 3.2
        new ScenePresetManager(adapter, stateStore, socketHandler, visibilityManager);
      }).not.toThrow();
    });

    it('STORY32-TDD: should store visibilityManager reference', () => {
      // This test drives the internal field storage
      // @ts-expect-error - Adding 4th parameter for Story 3.2
      const manager = new ScenePresetManager(adapter, stateStore, socketHandler, visibilityManager);
      expect(manager._visibilityManager).toBe(visibilityManager);
    });

    it('STORY32-TDD: should still validate first 3 parameters', () => {
      // Ensure backward compatibility with validation
      expect(() => {
        // @ts-expect-error - Adding 4th parameter for Story 3.2
        new ScenePresetManager(null, stateStore, socketHandler, visibilityManager);
      }).toThrow(TypeError);
    });
  });

  describe('onSceneActivate() [Story 3.2]', () => {
    let mockScene;
    let visibilityManager;

    beforeEach(() => {
      visibilityManager = {
        applyMatrix: vi.fn().mockResolvedValue({}),
        getMatrix: vi.fn(() => ({ _version: 1, matrix: {} })),
      };
      
      mockScene = {
        id: 'scene1',
        getFlag: vi.fn(),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      adapter.settings.get.mockReturnValue(true); // Global auto-apply enabled
      
      // @ts-expect-error - Adding 4th parameter for Story 3.2
      manager = new ScenePresetManager(adapter, stateStore, socketHandler, visibilityManager);
      manager.init();
    });

    it('STORY32-TDD: should do nothing when auto-apply is globally disabled', async () => {
      adapter.settings.get.mockReturnValue(false);
      await manager.onSceneActivate(mockScene);
      expect(visibilityManager.applyMatrix).not.toHaveBeenCalled();
    });

    it('STORY32-TDD: should do nothing when scene has no auto-apply config', async () => {
      mockScene.getFlag.mockReturnValue(null);
      await manager.onSceneActivate(mockScene);
      expect(visibilityManager.applyMatrix).not.toHaveBeenCalled();
    });

    it('STORY32-TDD: should do nothing when auto-apply is disabled for this scene', async () => {
      mockScene.getFlag.mockReturnValue({
        _version: 1,
        presets: {},
        autoApply: { enabled: false, presetName: null, preDelay: 0 }
      });
      await manager.onSceneActivate(mockScene);
      expect(visibilityManager.applyMatrix).not.toHaveBeenCalled();
    });

    it('STORY32-TDD: should apply preset after pre-delay when auto-apply enabled', async () => {
      vi.useFakeTimers();
      
      const preset = createTestPreset('Combat', { user1: 'hidden', user2: 'active' });
      manager._presetsCache.set('Combat', preset);
      
      mockScene.getFlag.mockReturnValue({
        _version: 1,
        presets: { Combat: preset },
        autoApply: { enabled: true, presetName: 'Combat', preDelay: 1000 }
      });
      
      const promise = manager.onSceneActivate(mockScene);
      
      // Fast-forward past pre-delay
      vi.advanceTimersByTime(1000);
      await promise;
      
      expect(visibilityManager.applyMatrix).toHaveBeenCalledWith(preset.matrix);
      expect(socketHandler.emit).toHaveBeenCalledWith(
        SOCKET_EVENTS.PRESET_APPLIED,
        expect.objectContaining({ presetName: 'Combat', autoApplied: true })
      );
      
      vi.useRealTimers();
    });

    it('STORY32-TDD: should clear pre-delay timer on new scene activation', async () => {
      vi.useFakeTimers();
      
      const preset = createTestPreset('Combat', { user1: 'hidden' });
      manager._presetsCache.set('Combat', preset);
      
      const mockScene2 = {
        id: 'scene2',
        getFlag: vi.fn().mockReturnValue({
          _version: 1,
          presets: {},
          autoApply: { enabled: true, presetName: 'Combat', preDelay: 5000 }
        }),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      
      // Start first scene activation
      const promise1 = manager.onSceneActivate(mockScene);
      
      // Activate second scene before first timer fires
      vi.advanceTimersByTime(1000);
      const promise2 = manager.onSceneActivate(mockScene2);
      
      // First scene's timer should be cleared
      vi.advanceTimersByTime(4000);
      
      // Second scene should be applied
      vi.advanceTimersByTime(1000);
      await Promise.all([promise1, promise2]);
      
      expect(visibilityManager.applyMatrix).toHaveBeenCalledTimes(1);
      
      vi.useRealTimers();
    });
  });

  describe('applyPreset() with auto-apply [Story 3.2]', () => {
    let visibilityManager;

    beforeEach(() => {
      visibilityManager = {
        applyMatrix: vi.fn().mockResolvedValue({}),
      };
      
      // @ts-expect-error - Adding 4th parameter for Story 3.2
      manager = new ScenePresetManager(adapter, stateStore, socketHandler, visibilityManager);
      
      const preset = createTestPreset('Test Preset', { user1: 'active', user2: 'hidden' });
      manager._presetsCache.set('Test Preset', preset);
    });

    it('STORY32-TDD: should apply preset matrix via visibilityManager', async () => {
      await manager.applyPreset('Test Preset', { autoApplied: true });
      expect(visibilityManager.applyMatrix).toHaveBeenCalledWith({ user1: 'active', user2: 'hidden' });
    });

    it('STORY32-TDD: should emit socket message with autoApplied flag', async () => {
      await manager.applyPreset('Test Preset', { autoApplied: true });
      expect(socketHandler.emit).toHaveBeenCalledWith(
        SOCKET_EVENTS.PRESET_APPLIED,
        expect.objectContaining({ presetName: 'Test Preset', autoApplied: true })
      );
    });

    it('STORY32-TDD: should throw when preset not found', async () => {
      await expect(manager.applyPreset('NonExistent', { autoApplied: true }))
        .rejects.toThrow(TypeError);
    });
  });

  describe('configureAutoApply() [Story 3.2]', () => {
    beforeEach(() => {
      // @ts-expect-error - Adding 4th parameter for Story 3.2
      manager = new ScenePresetManager(adapter, stateStore, socketHandler, {});
    });

    it('STORY32-TDD: should update scene flag with auto-apply config', async () => {
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(null),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      
      await manager.configureAutoApply(mockScene, { 
        enabled: true, 
        presetName: 'Combat', 
        preDelay: 1000 
      });
      
      expect(mockScene.setFlag).toHaveBeenCalledWith(
        'scrying-pool',
        'presets',
        expect.objectContaining({
          _version: 1,
          presets: {},
          autoApply: { enabled: true, presetName: 'Combat', preDelay: 1000 }
        })
      );
    });

    it('STORY32-TDD: should validate config parameters', async () => {
      const mockScene = {
        id: 'scene1',
        getFlag: vi.fn().mockReturnValue(null),
        setFlag: vi.fn().mockResolvedValue({}),
      };
      adapter.scenes.current.mockReturnValue(mockScene);
      
      // Invalid preDelay (negative)
      await expect(manager.configureAutoApply(mockScene, { 
        enabled: true, 
        presetName: 'Combat', 
        preDelay: -100 
      })).rejects.toThrow(TypeError);
      
      // Invalid preDelay (over max)
      await expect(manager.configureAutoApply(mockScene, { 
        enabled: true, 
        presetName: 'Combat', 
        preDelay: 6000 
      })).rejects.toThrow(TypeError);
    });
  });

  describe('_getAutoApplyConfig() [Story 3.2]', () => {
    beforeEach(() => {
      // @ts-expect-error - Adding 4th parameter for Story 3.2
      manager = new ScenePresetManager(adapter, stateStore, socketHandler, {});
    });

    it('STORY32-TDD: should return default config when autoApply field missing', () => {
      // _getAutoApplyConfig takes flagData, not scene
      const flagData = {
        _version: 1,
        presets: {}
        // autoApply field missing
      };
      
      const config = manager._getAutoApplyConfig(flagData);
      expect(config).toEqual({ enabled: false, presetName: null, preDelay: 0 });
    });

    it('STORY32-TDD: should return stored config when present', () => {
      // _getAutoApplyConfig takes flagData, not scene
      const flagData = {
        _version: 1,
        presets: {},
        autoApply: { enabled: true, presetName: 'Combat', preDelay: 500 }
      };
      
      const config = manager._getAutoApplyConfig(flagData);
      expect(config).toEqual({ enabled: true, presetName: 'Combat', preDelay: 500 });
    });

    it('STORY32-TDD: should return defaults for invalid flagData', () => {
      const config = manager._getAutoApplyConfig(null);
      expect(config).toEqual({ enabled: false, presetName: null, preDelay: 0 });
      
      const config2 = manager._getAutoApplyConfig({});
      expect(config2).toEqual({ enabled: false, presetName: null, preDelay: 0 });
    });
  });

  describe('_applyWithDelay() [Story 3.2]', () => {
    let visibilityManager;

    beforeEach(() => {
      visibilityManager = {
        applyMatrix: vi.fn().mockResolvedValue({}),
      };
      
      // @ts-expect-error - Adding 4th parameter for Story 3.2
      manager = new ScenePresetManager(adapter, stateStore, socketHandler, visibilityManager);
      vi.useFakeTimers();
    });

    afterEach(() => {
      vi.useRealTimers();
    });

    it('STORY32-TDD: should apply preset after specified delay', async () => {
      const mockScene = { id: 'scene1' };
      const preset = createTestPreset('Test', { user1: 'hidden' });
      manager._presetsCache.set('Test', preset);
      
      // @ts-expect-error - _applyWithDelay is private, accessing for testing
      manager._applyWithDelay(mockScene, 'Test', 500);
      
      // Before delay, not applied
      expect(visibilityManager.applyMatrix).not.toHaveBeenCalled();
      
      // After delay, applied
      vi.advanceTimersByTime(500);
      
      // Need to await any async operations
      await vi.waitFor(() => {
        expect(visibilityManager.applyMatrix).toHaveBeenCalled();
      });
    });

    it('STORY32-TDD: should be cancellable via clear method', async () => {
      const mockScene = { id: 'scene1' };
      const preset = createTestPreset('Test', { user1: 'hidden' });
      manager._presetsCache.set('Test', preset);
      
      // @ts-expect-error - _applyWithDelay is private, accessing for testing
      const timerId = manager._applyWithDelay(mockScene, 'Test', 500);
      
      // Clear before delay
      clearTimeout(timerId);
      
      vi.advanceTimersByTime(1000);
      
      expect(visibilityManager.applyMatrix).not.toHaveBeenCalled();
    });
  });
});
