// @ts-nocheck
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { PresetLoadDialog } from '../../../../src/ui/gm/PresetLoadDialog.js';

// Test helper: create a mock ScenePresetManager surface
function createMockScenePresetManager(overrides = {}) {
  return {
    save: vi.fn().mockResolvedValue({ _version: 1, name: 'Test Preset', matrix: {}, createdAt: Date.now(), updatedAt: Date.now() }),
    list: vi.fn().mockReturnValue([]),
    get: vi.fn().mockReturnValue(null),
    load: vi.fn().mockResolvedValue({}),
    delete: vi.fn().mockResolvedValue({}),
    rename: vi.fn().mockResolvedValue({}),
    init: vi.fn(),
    teardown: vi.fn(),
    ...overrides,
  };
}

// Test helper: create a mock adapter surface
function createMockAdapter(overrides = {}) {
  return {
    i18n: {
      localize: vi.fn((key) => key),
      ...overrides.i18n,
    },
    notifications: {
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    },
    ...overrides,
  };
}

// Test helper: create a mock preset
function createMockPreset(name = 'Test Preset') {
  return {
    _version: 1,
    name,
    matrix: { user1: 'active', user2: 'hidden' },
    createdAt: Date.now(),
    updatedAt: Date.now(),
  };
}

// ============================================================================
// PresetLoadDialog Tests
// ============================================================================

describe('PresetLoadDialog', () => {
  let scenePresetManager;
  let adapter;
  let dialog;

  beforeEach(() => {
    scenePresetManager = createMockScenePresetManager();
    adapter = createMockAdapter();
    vi.clearAllMocks();
  });

  afterEach(() => {
    dialog = null;
    vi.clearAllMocks();
  });

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

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

    it('should throw TypeError when scenePresetManager is not an object', () => {
      expect(() => new PresetLoadDialog('not an object', adapter)).toThrow(TypeError);
    });

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

    it('should throw TypeError when adapter is not an object', () => {
      expect(() => new PresetLoadDialog(scenePresetManager, 'not an object')).toThrow(TypeError);
    });

    it('should accept valid dependencies and initialize internal state', () => {
      dialog = new PresetLoadDialog(scenePresetManager, adapter);
      
      expect(dialog).toBeDefined();
      expect(dialog._scenePresetManager).toBe(scenePresetManager);
      expect(dialog._adapter).toBe(adapter);
      expect(dialog._presets).toEqual([]);
    });

    it('should be side-effect-free: no hooks registered in constructor', () => {
      const originalError = console.error;
      console.error = vi.fn();
      
      dialog = new PresetLoadDialog(scenePresetManager, adapter);
      
      expect(console.error).not.toHaveBeenCalled();
      
      console.error = originalError;
    });

    it('should have DEFAULT_OPTIONS defined', () => {
      expect(PresetLoadDialog.DEFAULT_OPTIONS).toBeDefined();
      expect(PresetLoadDialog.DEFAULT_OPTIONS.id).toBe('scrying-pool-preset-load-dialog');
      expect(PresetLoadDialog.DEFAULT_OPTIONS.classes).toEqual(expect.arrayContaining(['scrying-pool', 'preset-load-dialog']));
      expect(PresetLoadDialog.DEFAULT_OPTIONS.window.title).toBe('Load Scene Preset');
    });

    it('should have PARTS defined with template', () => {
      expect(PresetLoadDialog.PARTS).toBeDefined();
      expect(PresetLoadDialog.PARTS.dialog).toBeDefined();
      expect(PresetLoadDialog.PARTS.dialog.template).toContain('preset-load-dialog.hbs');
    });
  });

  // --------------------------------------------------------------------------
  // _prepareContext() Tests
  // --------------------------------------------------------------------------

  describe('_prepareContext()', () => {
    beforeEach(() => {
      dialog = new PresetLoadDialog(scenePresetManager, adapter);
    });

    it('should return an object with presets array', async () => {
      const context = await dialog._prepareContext();
      
      expect(context).toBeDefined();
      expect(typeof context).toBe('object');
      expect(context.presets).toBeDefined();
      expect(Array.isArray(context.presets)).toBe(true);
    });

    it('should return hasPresets false when no presets exist', async () => {
      scenePresetManager.list.mockReturnValue([]);
      adapter.i18n.localize = vi.fn((key) => {
        if (key === 'scrying-pool.presets.load.emptyMessage') return 'No presets available';
        return key;
      });
      
      const context = await dialog._prepareContext();
      
      expect(context.hasPresets).toBe(false);
      expect(context.emptyMessage).toBe('No presets available');
    });

    it('should return hasPresets true when presets exist', async () => {
      const presets = [createMockPreset('Preset 1'), createMockPreset('Preset 2')];
      scenePresetManager.list.mockReturnValue(presets);
      
      const context = await dialog._prepareContext();
      
      expect(context.hasPresets).toBe(true);
      expect(context.presets).toHaveLength(2);
    });

    it('should use i18n for labels', async () => {
      adapter.i18n.localize = vi.fn((key) => {
        const translations = {
          'scrying-pool.presets.load.loadButton': 'Load',
          'scrying-pool.presets.load.cancelButton': 'Cancel',
          'scrying-pool.presets.load.title': 'Load Preset',
          'scrying-pool.presets.load.emptyMessage': 'No presets',
        };
        return translations[key] || key;
      });
      
      const context = await dialog._prepareContext();
      
      expect(context.loadLabel).toBe('Load');
      expect(context.cancelLabel).toBe('Cancel');
      expect(context.title).toBe('Load Preset');
      expect(context.emptyMessage).toBe('No presets');
    });

    it('should store presets in internal _presets array', async () => {
      const presets = [createMockPreset('Preset 1')];
      scenePresetManager.list.mockReturnValue(presets);
      
      await dialog._prepareContext();
      
      expect(dialog._presets).toEqual(presets);
    });
  });

  // --------------------------------------------------------------------------
  // _onRender() Tests
  // --------------------------------------------------------------------------

  describe('_onRender()', () => {
    let mockElement;

    beforeEach(() => {
      dialog = new PresetLoadDialog(scenePresetManager, adapter);
      
      mockElement = {
        querySelector: vi.fn(),
        querySelectorAll: vi.fn().mockReturnValue([]),
        addEventListener: vi.fn(),
      };
      
      dialog.element = mockElement;
      dialog.rendered = true;
    });

    it('should set up load button handlers for each preset', () => {
      const loadBtn1 = { addEventListener: vi.fn(), dataset: { action: 'load', presetName: 'Preset 1' } };
      const loadBtn2 = { addEventListener: vi.fn(), dataset: { action: 'load', presetName: 'Preset 2' } };
      mockElement.querySelectorAll = vi.fn().mockReturnValue([loadBtn1, loadBtn2]);
      
      dialog._onRender(mockElement);
      
      expect(loadBtn1.addEventListener).toHaveBeenCalledWith('click', expect.any(Function));
      expect(loadBtn2.addEventListener).toHaveBeenCalledWith('click', expect.any(Function));
    });

    it('should set up cancel button handler', () => {
      const cancelBtn = { addEventListener: vi.fn() };
      mockElement.querySelector = vi.fn((selector) => {
        if (selector === '[data-action="cancel"]') return cancelBtn;
        return null;
      });
      
      dialog._onRender(mockElement);
      
      expect(cancelBtn.addEventListener).toHaveBeenCalledWith('click', expect.any(Function));
    });

    it('should set up keyboard handlers', () => {
      dialog._onRender(mockElement);
      
      expect(mockElement.addEventListener).toHaveBeenCalledWith('keydown', expect.any(Function));
    });
  });

  // --------------------------------------------------------------------------
  // _onLoad() Tests
  // --------------------------------------------------------------------------

  describe('_onLoad()', () => {
    beforeEach(() => {
      dialog = new PresetLoadDialog(scenePresetManager, adapter);
      scenePresetManager.load = vi.fn().mockResolvedValue({});
      dialog.close = vi.fn().mockResolvedValue({});
    });

    it('should throw TypeError when presetName is null', async () => {
      await expect(dialog._onLoad(null)).rejects.toThrow(TypeError);
    });

    it('should throw TypeError when presetName is empty string', async () => {
      await expect(dialog._onLoad('')).rejects.toThrow(TypeError);
    });

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

    it('should call scenePresetManager.load with the preset name', async () => {
      await dialog._onLoad('My Preset');
      
      expect(scenePresetManager.load).toHaveBeenCalledWith('My Preset');
    });

    it('should close the dialog on successful load', async () => {
      await dialog._onLoad('My Preset');
      
      expect(dialog.close).toHaveBeenCalled();
    });

    it('should show notification on successful load via adapter.notifications', async () => {
      adapter.i18n.localize = vi.fn((key) => {
        if (key === 'scrying-pool.presets.notifications.applied') return 'Applied preset: {name}';
        return key;
      });
      
      await dialog._onLoad('My Preset');
      
      expect(adapter.notifications.info).toHaveBeenCalledWith('Applied preset: My Preset');
    });

    it('should re-throw TypeError from load', async () => {
      const error = new TypeError('preset "My Preset" not found');
      scenePresetManager.load = vi.fn().mockRejectedValue(error);
      
      await expect(dialog._onLoad('My Preset')).rejects.toThrow(TypeError);
      expect(dialog.close).not.toHaveBeenCalled();
    });
  });

  // --------------------------------------------------------------------------
  // _onCancel() Tests
  // --------------------------------------------------------------------------

  describe('_onCancel()', () => {
    beforeEach(() => {
      dialog = new PresetLoadDialog(scenePresetManager, adapter);
      dialog.close = vi.fn().mockResolvedValue({});
    });

    it('should close the dialog', () => {
      dialog._onCancel();
      
      expect(dialog.close).toHaveBeenCalled();
    });

    it('should not throw when called multiple times', () => {
      dialog._onCancel();
      dialog._onCancel();
      
      expect(dialog.close).toHaveBeenCalledTimes(2);
    });
  });

  // --------------------------------------------------------------------------
  // _onKeydown() Tests
  // --------------------------------------------------------------------------

  describe('_onKeydown()', () => {
    let mockEvent;

    beforeEach(() => {
      dialog = new PresetLoadDialog(scenePresetManager, adapter);
      scenePresetManager.load = vi.fn().mockResolvedValue({});
      dialog.close = vi.fn().mockResolvedValue({});
      adapter.i18n.localize = vi.fn((key) => key);
      
      mockEvent = {
        preventDefault: vi.fn(),
        stopPropagation: vi.fn(),
        key: '',
        target: {},
      };
    });

    it('should handle Escape key to cancel', () => {
      mockEvent.key = 'Escape';
      
      dialog._onKeydown(mockEvent);
      
      expect(mockEvent.preventDefault).toHaveBeenCalled();
      expect(mockEvent.stopPropagation).toHaveBeenCalled();
      expect(dialog.close).toHaveBeenCalled();
    });

    it('should handle Enter key on load button', async () => {
      mockEvent.key = 'Enter';
      mockEvent.target = { dataset: { action: 'load', presetName: 'My Preset' } };
      
      await dialog._onKeydown(mockEvent);
      
      expect(mockEvent.preventDefault).toHaveBeenCalled();
      expect(mockEvent.stopPropagation).toHaveBeenCalled();
      expect(scenePresetManager.load).toHaveBeenCalledWith('My Preset');
    });

    it('should ignore Enter key on non-load button', async () => {
      mockEvent.key = 'Enter';
      mockEvent.target = { dataset: { action: 'other' } };
      
      await dialog._onKeydown(mockEvent);
      
      expect(scenePresetManager.load).not.toHaveBeenCalled();
    });

    it('should ignore other keys', () => {
      mockEvent.key = 'A';
      
      dialog._onKeydown(mockEvent);
      
      expect(mockEvent.preventDefault).not.toHaveBeenCalled();
      expect(dialog.close).not.toHaveBeenCalled();
    });
  });

  // --------------------------------------------------------------------------
  // Integration Tests
  // --------------------------------------------------------------------------

  describe('integration', () => {
    beforeEach(() => {
      dialog = new PresetLoadDialog(scenePresetManager, adapter);
    });

    it('should have all required methods defined', () => {
      expect(dialog._prepareContext).toBeDefined();
      expect(dialog._onRender).toBeDefined();
      expect(dialog._onLoad).toBeDefined();
      expect(dialog._onCancel).toBeDefined();
      expect(dialog._onKeydown).toBeDefined();
    });

    it('should use the correct template path', () => {
      expect(PresetLoadDialog.PARTS.dialog.template).toBe(
        'modules/scrying-pool/templates/preset-load-dialog.hbs'
      );
    });

    it('should have correct window options', () => {
      const options = PresetLoadDialog.DEFAULT_OPTIONS;
      
      expect(options.id).toBe('scrying-pool-preset-load-dialog');
      expect(options.classes).toContain('scrying-pool');
      expect(options.classes).toContain('preset-load-dialog');
      expect(options.window.title).toBe('Load Scene Preset');
      expect(options.window.resizable).toBe(false);
      expect(options.position.width).toBe(320);
    });

    it('should store references to dependencies', () => {
      expect(dialog._scenePresetManager).toBe(scenePresetManager);
      expect(dialog._adapter).toBe(adapter);
    });

    it('should initialize _presets to empty array', () => {
      expect(dialog._presets).toEqual([]);
    });
  });
});
