/**
 * Frontend Test Suite for Arduino Simulation
 * Tests AVRSimulator, PinManager, and component integration
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { AVRSimulator } from '../simulation/AVRSimulator';
import { PinManager } from '../simulation/PinManager';

// requestAnimationFrame is not available in Node — mock it as a no-op
beforeEach(() => {
  let counter = 0;
  vi.stubGlobal('requestAnimationFrame', (_cb: FrameRequestCallback) => ++counter);
  vi.stubGlobal('cancelAnimationFrame', vi.fn());
});
afterEach(() => vi.unstubAllGlobals());

describe('PinManager', () => {
  let pinManager: PinManager;

  beforeEach(() => {
    pinManager = new PinManager();
  });

  it('should create instance successfully', () => {
    expect(pinManager).toBeDefined();
    expect(pinManager.getListenersCount()).toBe(0);
  });

  it('should register pin change listeners', () => {
    const callback = vi.fn();
    const unsubscribe = pinManager.onPinChange(13, callback);

    expect(pinManager.getListenersCount()).toBe(1);
    expect(typeof unsubscribe).toBe('function');
  });

  it('should notify listeners on port change', () => {
    const callback = vi.fn();
    pinManager.onPinChange(13, callback);

    // Pin 13 is PORTB bit 5
    // Change PORTB from 0x00 to 0x20 (bit 5 HIGH)
    pinManager.updatePort('PORTB', 0x20, 0x00);

    expect(callback).toHaveBeenCalledWith(13, true);
  });

  it('should handle multiple listeners on same pin', () => {
    const callback1 = vi.fn();
    const callback2 = vi.fn();

    pinManager.onPinChange(13, callback1);
    pinManager.onPinChange(13, callback2);

    pinManager.updatePort('PORTB', 0x20, 0x00);

    expect(callback1).toHaveBeenCalledWith(13, true);
    expect(callback2).toHaveBeenCalledWith(13, true);
  });

  it('should unsubscribe listeners', () => {
    const callback = vi.fn();
    const unsubscribe = pinManager.onPinChange(13, callback);

    unsubscribe();
    pinManager.updatePort('PORTB', 0x20, 0x00);

    expect(callback).not.toHaveBeenCalled();
  });

  it('should track pin states', () => {
    expect(pinManager.getPinState(13)).toBe(false);

    pinManager.updatePort('PORTB', 0x20, 0x00);
    expect(pinManager.getPinState(13)).toBe(true);

    pinManager.updatePort('PORTB', 0x00, 0x20);
    expect(pinManager.getPinState(13)).toBe(false);
  });

  it('should handle PORTC (analog pins)', () => {
    const callback = vi.fn();
    pinManager.onPinChange(14, callback); // A0 = pin 14

    pinManager.updatePort('PORTC', 0x01, 0x00);
    expect(callback).toHaveBeenCalledWith(14, true);
  });

  it('should handle PORTD (digital pins 0-7)', () => {
    const callback = vi.fn();
    pinManager.onPinChange(7, callback);

    pinManager.updatePort('PORTD', 0x80, 0x00); // bit 7
    expect(callback).toHaveBeenCalledWith(7, true);
  });
});

describe('AVRSimulator', () => {
  let simulator: AVRSimulator;
  let pinManager: PinManager;

  beforeEach(() => {
    pinManager = new PinManager();
    simulator = new AVRSimulator(pinManager);
  });

  it('should create instance successfully', () => {
    expect(simulator).toBeDefined();
    expect(simulator.isRunning()).toBe(false);
  });

  it('should load HEX file', () => {
    // Minimal valid Intel HEX (empty program + EOF)
    const hexContent = ':00000001FF';

    expect(() => simulator.loadHex(hexContent)).not.toThrow();
  });

  it('should start and stop simulation', () => {
    const hexContent = ':00000001FF';
    simulator.loadHex(hexContent);

    expect(simulator.isRunning()).toBe(false);

    simulator.start();
    expect(simulator.isRunning()).toBe(true);

    simulator.stop();
    expect(simulator.isRunning()).toBe(false);
  });

  it('should reset simulation', () => {
    const hexContent = ':00000001FF';
    simulator.loadHex(hexContent);
    simulator.start();

    expect(simulator.isRunning()).toBe(true);

    simulator.reset();
    expect(simulator.isRunning()).toBe(false);
  });

  it('should set simulation speed', () => {
    expect(simulator.getSpeed()).toBe(1.0);

    simulator.setSpeed(2.0);
    expect(simulator.getSpeed()).toBe(2.0);

    simulator.setSpeed(0.5);
    expect(simulator.getSpeed()).toBe(0.5);

    // Should clamp to min/max
    simulator.setSpeed(100);
    expect(simulator.getSpeed()).toBe(10.0);

    simulator.setSpeed(0.01);
    expect(simulator.getSpeed()).toBe(0.1);
  });
});

describe('Simulator Integration', () => {
  it('should propagate pin changes from CPU to components', async () => {
    const pinManager = new PinManager();
    const simulator = new AVRSimulator(pinManager);

    // Register listener for pin 13 (LED_BUILTIN)
    const pinChangeCallback = vi.fn();
    pinManager.onPinChange(13, pinChangeCallback);

    // Load a simple HEX that should toggle pin 13
    // Note: This is a minimal test - real integration requires full compiled HEX
    const hexContent = ':00000001FF';
    simulator.loadHex(hexContent);

    // Manually trigger port change to simulate CPU execution
    // In real scenario, this would happen via avrInstruction() execution
    pinManager.updatePort('PORTB', 0x20, 0x00);

    expect(pinChangeCallback).toHaveBeenCalledWith(13, true);
  });
});

// Export for documentation
export const testDocumentation = `
Frontend Test Suite Coverage:

1. PinManager Tests:
   - Instance creation
   - Listener registration and unsubscription
   - Port change notifications
   - Multiple listeners on same pin
   - Pin state tracking
   - All three ports (PORTB, PORTC, PORTD)

2. AVRSimulator Tests:
   - Instance creation
   - HEX file loading
   - Start/Stop simulation
   - Reset functionality
   - Simulation speed control

3. Integration Tests:
   - Pin change propagation from CPU to components
   - Data flow through the simulation stack

To run these tests:
  cd frontend
  npm test

To run with coverage:
  npm test -- --coverage
`;
