/**
 * @jest-environment jsdom
 */

import { StateManager } from '../../resources/js/core/StateManager.js';

// Mock Logger
jest.mock('../../resources/js/core/logger.js', () => ({
  Logger: {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn()
  }
}));

describe('StateManager', () => {
  let stateManager;

  beforeEach(() => {
    stateManager = new StateManager();
    stateManager.setContext('test-module');
  });

  describe('State registration', () => {
    test('should register state with default value', () => {
      stateManager.register('testKey', 'defaultValue');
      
      expect(stateManager.get('testKey')).toBe('defaultValue');
    });

    test('should not allow duplicate registration', () => {
      stateManager.register('testKey', 'value1');
      stateManager.register('testKey', 'value2');
      
      expect(stateManager.get('testKey')).toBe('value1');
    });

    test('should track state ownership', () => {
      stateManager.setContext('module-a');
      stateManager.register('keyA', 'valueA');
      
      stateManager.setContext('module-b');
      stateManager.register('keyB', 'valueB');
      
      expect(stateManager.stateOwners.get('keyA')).toBe('module-a');
      expect(stateManager.stateOwners.get('keyB')).toBe('module-b');
    });
  });

  describe('State access', () => {
    beforeEach(() => {
      stateManager.register('testKey', 42);
    });

    test('should get current state value', () => {
      expect(stateManager.get('testKey')).toBe(42);
    });

    test('should return undefined for unknown keys', () => {
      expect(stateManager.get('unknownKey')).toBeUndefined();
    });

    test('should allow owner to set value', () => {
      const success = stateManager.set('testKey', 100);
      
      expect(success).toBe(true);
      expect(stateManager.get('testKey')).toBe(100);
    });

    test('should prevent non-owner from setting value', () => {
      stateManager.setContext('other-module');
      const success = stateManager.set('testKey', 200);
      
      expect(success).toBe(false);
      expect(stateManager.get('testKey')).toBe(42);
    });

    test('should allow forced set by non-owner', () => {
      stateManager.setContext('other-module');
      const success = stateManager.set('testKey', 300, true);
      
      expect(success).toBe(true);
      expect(stateManager.get('testKey')).toBe(300);
    });

    test('should not trigger subscribers for same value', () => {
      const callback = jest.fn();
      stateManager.subscribe('testKey', callback);
      
      stateManager.set('testKey', 42); // Same value
      
      expect(callback).not.toHaveBeenCalled();
    });
  });

  describe('State subscriptions', () => {
    beforeEach(() => {
      stateManager.register('testKey', 'initial');
    });

    test('should subscribe to state changes', () => {
      const callback = jest.fn();
      const subscriptionId = stateManager.subscribe('testKey', callback);
      
      expect(subscriptionId).toBeTruthy();
      expect(typeof subscriptionId).toBe('string');
    });

    test('should notify subscribers on state change', () => {
      const callback = jest.fn();
      stateManager.subscribe('testKey', callback);
      
      stateManager.set('testKey', 'changed');
      
      expect(callback).toHaveBeenCalledWith('changed', 'initial', 'testKey');
    });

    test('should handle multiple subscribers', () => {
      const callback1 = jest.fn();
      const callback2 = jest.fn();
      
      stateManager.subscribe('testKey', callback1);
      stateManager.subscribe('testKey', callback2);
      
      stateManager.set('testKey', 'updated');
      
      expect(callback1).toHaveBeenCalledWith('updated', 'initial', 'testKey');
      expect(callback2).toHaveBeenCalledWith('updated', 'initial', 'testKey');
    });

    test('should unsubscribe correctly', () => {
      const callback = jest.fn();
      const subscriptionId = stateManager.subscribe('testKey', callback);
      
      stateManager.unsubscribe(subscriptionId);
      stateManager.set('testKey', 'after-unsubscribe');
      
      expect(callback).not.toHaveBeenCalled();
    });

    test('should handle subscriber errors gracefully', () => {
      const errorCallback = jest.fn(() => {
        throw new Error('Subscriber error');
      });
      const goodCallback = jest.fn();
      
      stateManager.subscribe('testKey', errorCallback);
      stateManager.subscribe('testKey', goodCallback);
      
      expect(() => {
        stateManager.set('testKey', 'trigger-error');
      }).not.toThrow();
      
      expect(goodCallback).toHaveBeenCalled();
    });

    test('should not subscribe to unknown keys', () => {
      const callback = jest.fn();
      const subscriptionId = stateManager.subscribe('unknownKey', callback);
      
      expect(subscriptionId).toBeNull();
    });
  });

  describe('State reset', () => {
    beforeEach(() => {
      stateManager.register('testKey', 'default');
      stateManager.set('testKey', 'changed');
    });

    test('should reset to default value', () => {
      const success = stateManager.reset('testKey');
      
      expect(success).toBe(true);
      expect(stateManager.get('testKey')).toBe('default');
    });

    test('should notify subscribers on reset', () => {
      const callback = jest.fn();
      stateManager.subscribe('testKey', callback);
      
      stateManager.reset('testKey');
      
      expect(callback).toHaveBeenCalledWith('default', 'changed', 'testKey');
    });

    test('should not reset unknown keys', () => {
      const success = stateManager.reset('unknownKey');
      expect(success).toBe(false);
    });
  });

  describe('Module scoped interface', () => {
    test('should create scoped state manager', () => {
      const scoped = stateManager.createScope('scoped-module');
      
      expect(scoped).toHaveProperty('register');
      expect(scoped).toHaveProperty('get');
      expect(scoped).toHaveProperty('set');
      expect(scoped).toHaveProperty('subscribe');
      expect(scoped).toHaveProperty('cleanup');
    });

    test('should use module context in scoped operations', () => {
      const scoped = stateManager.createScope('scoped-module');
      
      scoped.register('scopedKey', 'scopedValue');
      
      expect(stateManager.stateOwners.get('scopedKey')).toBe('scoped-module');
    });

    test('should cleanup module subscriptions', () => {
      const scoped = stateManager.createScope('cleanup-module');
      const callback = jest.fn();
      
      scoped.register('cleanupKey', 'value');
      const subscriptionId = scoped.subscribe('cleanupKey', callback);
      
      scoped.cleanup();
      
      // Subscription should be cleaned up
      stateManager.set('cleanupKey', 'after-cleanup', true);
      expect(callback).not.toHaveBeenCalled();
    });
  });

  describe('Debugging and introspection', () => {
    test('should provide state snapshot', () => {
      stateManager.register('key1', 'value1');
      stateManager.register('key2', 42);
      
      const snapshot = stateManager.getSnapshot();
      
      expect(snapshot).toHaveProperty('state');
      expect(snapshot).toHaveProperty('owners');
      expect(snapshot).toHaveProperty('subscriptions');
      expect(snapshot.state.key1).toBe('value1');
      expect(snapshot.state.key2).toBe(42);
    });

    test('should reset entire state manager', () => {
      stateManager.register('key1', 'value1');
      stateManager.subscribe('key1', jest.fn());
      
      stateManager.resetAll();
      
      expect(stateManager.state.size).toBe(0);
      expect(stateManager.subscribers.size).toBe(0);
      expect(stateManager.stateOwners.size).toBe(0);
    });
  });

  describe('Performance', () => {
    test('should handle many state operations efficiently', () => {
      const start = performance.now();
      
      // Register many state keys
      for (let i = 0; i < 100; i++) {
        stateManager.register(`key${i}`, i);
      }
      
      // Subscribe to all keys
      for (let i = 0; i < 100; i++) {
        stateManager.subscribe(`key${i}`, jest.fn());
      }
      
      // Update all keys
      for (let i = 0; i < 100; i++) {
        stateManager.set(`key${i}`, i * 2);
      }
      
      const end = performance.now();
      const duration = end - start;
      
      // Should complete in reasonable time
      expect(duration).toBeLessThan(100);
    });
  });
});