import 'dart:typed_data';

import 'package:test/test.dart';
import 'package:wolf_3d_dart/src/menu/menu_manager.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
import 'package:wolf_3d_dart/wolf_3d_input.dart';

void main() {
  group('Change View menu wiring', () {
    test('CHANGE VIEW is wired and enabled in main menu', () {
      final engine = _buildEngine(difficulty: null);
      engine.init();
      final entries = engine.menuManager.mainMenuEntries;
      final changeViewEntry = entries.firstWhere(
        (e) => e.action == WolfMenuMainAction.changeView,
      );
      expect(changeViewEntry.isEnabled, isTrue);
    });

    test('selecting CHANGE VIEW transitions to changeView screen', () {
      final input = _TestInput();
      final engine = _buildEngine(difficulty: null, input: input);
      engine.init();

      // Navigate to CHANGE VIEW (index 5 in the default menu)
      engine.menuManager.showMainMenu(hasResumableGame: false);
      final int cvIndex = engine.menuManager.mainMenuEntries.indexWhere(
        (e) => e.action == WolfMenuMainAction.changeView,
      );
      // Move cursor down to CHANGE VIEW
      for (int i = 0; i < cvIndex; i++) {
        input.isMovingBackward = true;
        engine.tick(const Duration(milliseconds: 16));
        input.isMovingBackward = false;
        engine.tick(const Duration(milliseconds: 16));
      }
      expect(engine.menuManager.selectedMainIndex, cvIndex);

      // Confirm
      input.isInteracting = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isInteracting = false;

      expect(
        engine.menuManager.activeMenu,
        WolfMenuScreen.changeView,
      );
    });

    test('back from changeView returns to main menu', () {
      final input = _TestInput();
      final engine = _buildEngine(difficulty: null, input: input);
      engine.init();

      engine.menuManager.showChangeViewMenu();

      input.isBack = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isBack = false;

      expect(engine.menuManager.activeMenu, WolfMenuScreen.mainMenu);
    });

    test('renderer selection toggles mode and stays on changeView', () {
      final input = _TestInput();
      final engine = _buildEngine(
        difficulty: null,
        input: input,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.software, WolfRendererMode.ascii},
          supportsAsciiThemes: true,
          supportsFpsCounter: false,
        ),
        rendererSettings: const WolfRendererSettings(
          mode: WolfRendererMode.software,
        ),
      );
      engine.init();
      engine.menuManager.showChangeViewMenu();

      // Move from SOFTWARE to ASCII.
      input.isMovingBackward = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isMovingBackward = false;
      engine.tick(const Duration(milliseconds: 16));

      input.isInteracting = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isInteracting = false;
      engine.tick(const Duration(milliseconds: 16));

      expect(engine.rendererSettings.mode, WolfRendererMode.ascii);
      expect(engine.menuManager.activeMenu, WolfMenuScreen.changeView);
    });

    test('renderer option toggles are available inline in changeView', () {
      final input = _TestInput();
      final engine = _buildEngine(
        difficulty: null,
        input: input,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.software, WolfRendererMode.ascii},
          supportsAsciiThemes: true,
          supportsFpsCounter: false,
        ),
        rendererSettings: const WolfRendererSettings(
          mode: WolfRendererMode.software,
        ),
      );
      engine.init();
      engine.menuManager.showChangeViewMenu();

      // Select ASCII so mode-specific settings become available.
      input.isMovingBackward = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isMovingBackward = false;
      engine.tick(const Duration(milliseconds: 16));
      input.isInteracting = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isInteracting = false;
      engine.tick(const Duration(milliseconds: 16));

      // Move to first inline option row and toggle it.
      input.isMovingBackward = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isMovingBackward = false;
      engine.tick(const Duration(milliseconds: 16));
      input.isInteracting = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isInteracting = false;
      engine.tick(const Duration(milliseconds: 16));

      expect(engine.menuManager.activeMenu, WolfMenuScreen.changeView);
      expect(
        engine.rendererSettings.asciiThemeId,
        WolfRendererSettings.asciiThemeQuadrant,
      );
    });

    test('toggling renderer option keeps cursor on selected option', () {
      final input = _TestInput();
      final engine = _buildEngine(
        difficulty: null,
        input: input,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.ascii},
          supportsAsciiThemes: true,
          supportsFpsCounter: true,
        ),
        rendererSettings: const WolfRendererSettings(
          mode: WolfRendererMode.ascii,
        ),
      );
      engine.init();
      engine.menuManager.showChangeViewMenu();

      // Move from renderer row to first option row, then to second option row.
      input.isMovingBackward = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isMovingBackward = false;
      engine.tick(const Duration(milliseconds: 16));

      input.isMovingBackward = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isMovingBackward = false;
      engine.tick(const Duration(milliseconds: 16));
      final int modeCount = engine.menuManager.changeViewEntries.length;
      expect(engine.menuManager.selectedChangeViewIndex, modeCount + 1);

      input.isInteracting = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isInteracting = false;
      engine.tick(const Duration(milliseconds: 16));

      expect(engine.menuManager.selectedChangeViewIndex, modeCount + 1);
      expect(engine.rendererSettings.fpsCounterEnabled, isTrue);
    });
  });

  group('Renderer settings model', () {
    test('default capabilities include software mode', () {
      final engine = _buildEngine(difficulty: null);
      engine.init();
      expect(
        engine.rendererCapabilities.supportsMode(WolfRendererMode.software),
        isTrue,
      );
    });

    test('updateRendererSettings mutates mode and notifies callback', () {
      WolfRendererSettings? notified;
      final engine = _buildEngine(
        difficulty: null,
        onRendererSettingsChanged: (s) => notified = s,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.software, WolfRendererMode.ascii},
          supportsAsciiThemes: true,
          supportsFpsCounter: true,
        ),
      );
      engine.init();

      engine.updateRendererSettings(
        const WolfRendererSettings(mode: WolfRendererMode.ascii),
      );

      expect(engine.rendererSettings.mode, WolfRendererMode.ascii);
      expect(notified?.mode, WolfRendererMode.ascii);
    });

    test('cycleRendererMode cycles through supported modes', () {
      final engine = _buildEngine(
        difficulty: null,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.software, WolfRendererMode.ascii},
          supportsAsciiThemes: true,
          supportsFpsCounter: true,
        ),
        rendererSettings: const WolfRendererSettings(
          mode: WolfRendererMode.software,
        ),
      );
      engine.init();

      engine.cycleRendererMode();
      expect(engine.rendererSettings.mode, WolfRendererMode.ascii);

      engine.cycleRendererMode();
      expect(engine.rendererSettings.mode, WolfRendererMode.software);
    });

    test('cycleAsciiTheme cycles to next theme', () {
      final engine = _buildEngine(
        difficulty: null,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.ascii},
          supportsAsciiThemes: true,
          supportsFpsCounter: false,
        ),
        rendererSettings: const WolfRendererSettings(
          asciiThemeId: WolfRendererSettings.asciiThemeBlocks,
        ),
      );
      engine.init();

      engine.cycleAsciiTheme();
      expect(
        engine.rendererSettings.asciiThemeId,
        WolfRendererSettings.asciiThemeQuadrant,
      );

      engine.cycleAsciiTheme();
      expect(
        engine.rendererSettings.asciiThemeId,
        WolfRendererSettings.asciiThemeBlocks,
      );
    });

    test('toggleFpsCounter toggles and syncs showFpsCounter', () {
      final engine = _buildEngine(
        difficulty: null,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.software},
          supportsFpsCounter: true,
        ),
      );
      engine.init();
      expect(engine.showFpsCounter, isFalse);

      engine.toggleFpsCounter();
      expect(engine.rendererSettings.fpsCounterEnabled, isTrue);
      expect(engine.showFpsCounter, isTrue);

      engine.toggleFpsCounter();
      expect(engine.rendererSettings.fpsCounterEnabled, isFalse);
      expect(engine.showFpsCounter, isFalse);
    });

    test('toggleHardwareEffects no-ops when capability is absent', () {
      final engine = _buildEngine(
        difficulty: null,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.software},
          supportsHardwareEffects: false,
        ),
        rendererSettings: const WolfRendererSettings(
          hardwareEffectsEnabled: false,
        ),
      );
      engine.init();

      engine.toggleHardwareEffects();
      expect(engine.rendererSettings.hardwareEffectsEnabled, isFalse);
    });
  });

  group('Renderer menu visibility', () {
    test('changeViewEntries only contain supported modes', () {
      final engine = _buildEngine(
        difficulty: null,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.ascii, WolfRendererMode.software},
          supportsAsciiThemes: true,
          supportsFpsCounter: false,
        ),
      );
      engine.init();

      final modes = engine.menuManager.changeViewEntries
          .map((e) => e.mode)
          .toSet();
      expect(
        modes,
        equals({WolfRendererMode.ascii, WolfRendererMode.software}),
      );
    });

    test('checked renderer entry matches active mode', () {
      final engine = _buildEngine(
        difficulty: null,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.ascii, WolfRendererMode.software},
        ),
        rendererSettings: const WolfRendererSettings(
          mode: WolfRendererMode.ascii,
        ),
      );
      engine.init();

      final checked = engine.menuManager.changeViewEntries
          .where((e) => e.isChecked)
          .toList();
      expect(checked.length, 1);
      expect(checked.first.mode, WolfRendererMode.ascii);
    });

    test('ASCII theme option is shown when mode is ascii', () {
      final engine = _buildEngine(
        difficulty: null,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.ascii},
          supportsAsciiThemes: true,
          supportsFpsCounter: false,
        ),
        rendererSettings: const WolfRendererSettings(
          mode: WolfRendererMode.ascii,
        ),
      );
      engine.init();

      final optionIds = engine.menuManager.rendererOptionEntries
          .map((e) => e.id)
          .toSet();
      expect(optionIds.contains(WolfRendererOptionId.asciiTheme), isTrue);
    });

    test('ASCII theme option is hidden when mode is not ascii', () {
      final engine = _buildEngine(
        difficulty: null,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.software},
          supportsAsciiThemes: false,
          supportsFpsCounter: false,
        ),
        rendererSettings: const WolfRendererSettings(
          mode: WolfRendererMode.software,
        ),
      );
      engine.init();

      final optionIds = engine.menuManager.rendererOptionEntries
          .map((e) => e.id)
          .toSet();
      expect(optionIds.contains(WolfRendererOptionId.asciiTheme), isFalse);
    });

    test('hardware effects option only shown when hardware mode is active', () {
      final engineHardware = _buildEngine(
        difficulty: null,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.hardware},
          supportsHardwareEffects: true,
          supportsFpsCounter: false,
        ),
        rendererSettings: const WolfRendererSettings(
          mode: WolfRendererMode.hardware,
        ),
      );
      engineHardware.init();
      final hardwareOptIds = engineHardware.menuManager.rendererOptionEntries
          .map((e) => e.id)
          .toSet();
      expect(
        hardwareOptIds.contains(WolfRendererOptionId.hardwareEffects),
        isTrue,
      );

      final engineSoftware = _buildEngine(
        difficulty: null,
        rendererCapabilities: const WolfRendererCapabilities(
          supportedModes: {WolfRendererMode.software},
          supportsHardwareEffects: false,
          supportsFpsCounter: false,
        ),
        rendererSettings: const WolfRendererSettings(
          mode: WolfRendererMode.software,
        ),
      );
      engineSoftware.init();
      final softwareOptIds = engineSoftware.menuManager.rendererOptionEntries
          .map((e) => e.id)
          .toSet();
      expect(
        softwareOptIds.contains(WolfRendererOptionId.hardwareEffects),
        isFalse,
      );
    });
  });

  group('WolfRendererSettings serialization', () {
    test('round-trip through JSON is lossless', () {
      const WolfRendererSettings original = WolfRendererSettings(
        mode: WolfRendererMode.ascii,
        asciiThemeId: WolfRendererSettings.asciiThemeQuadrant,
        hardwareEffectsEnabled: true,
        fpsCounterEnabled: true,
      );
      final json = original.toJson();
      final restored = WolfRendererSettings.fromJson(json);

      expect(restored.mode, original.mode);
      expect(restored.asciiThemeId, original.asciiThemeId);
      expect(restored.hardwareEffectsEnabled, original.hardwareEffectsEnabled);
      expect(restored.fpsCounterEnabled, original.fpsCounterEnabled);
    });

    test('fromJson tolerates unknown mode with safe default', () {
      final settings = WolfRendererSettings.fromJson(<String, Object?>{
        'mode': 'unknown_mode_xyz',
      });
      expect(settings.mode, WolfRendererMode.software);
    });

    test('fromJson tolerates missing fields', () {
      final settings = WolfRendererSettings.fromJson(<String, Object?>{});
      expect(settings.mode, WolfRendererMode.software);
      expect(settings.asciiThemeId, WolfRendererSettings.asciiThemeBlocks);
      expect(settings.hardwareEffectsEnabled, isFalse);
      expect(settings.fpsCounterEnabled, isFalse);
    });
  });
}

WolfEngine _buildEngine({
  required Difficulty? difficulty,
  _TestInput? input,
  WolfRendererCapabilities? rendererCapabilities,
  WolfRendererSettings? rendererSettings,
  void Function(WolfRendererSettings)? onRendererSettingsChanged,
}) {
  return WolfEngine(
    data: _buildTestData(),
    difficulty: difficulty,
    startingEpisode: 0,
    frameBuffer: FrameBuffer(64, 64),
    input: input ?? _TestInput(),
    engineAudio: _SilentAudio(),
    onGameWon: () {},
    rendererCapabilities: rendererCapabilities,
    rendererSettings: rendererSettings,
    onRendererSettingsChanged: onRendererSettingsChanged,
  );
}

WolfensteinData _buildTestData() {
  final wallGrid = List.generate(64, (_) => List.filled(64, 0));
  final objGrid = List.generate(64, (_) => List.filled(64, 0));
  _fillBoundaries(wallGrid, 2);
  objGrid[2][2] = MapObject.playerEast;

  return WolfensteinData(
    version: GameVersion.retail,
    dataVersion: DataVersion.unknown,
    registry: RetailAssetRegistry(),
    walls: [_sprite(1), _sprite(1), _sprite(2), _sprite(2)],
    sprites: List.generate(436, (_) => _sprite(255)),
    sounds: List.generate(200, (_) => PcmSound(Uint8List(1))),
    adLibSounds: const [],
    music: const [],
    vgaImages: const [],
    episodes: [
      Episode(
        name: 'Test Episode',
        levels: [
          WolfLevel(
            name: 'Test Level',
            wallGrid: wallGrid,
            areaGrid: List.generate(64, (_) => List.filled(64, -1)),
            objectGrid: objGrid,
            music: Music.level01,
          ),
        ],
      ),
    ],
  );
}

void _fillBoundaries(List<List<int>> grid, int wallId) {
  for (int i = 0; i < 64; i++) {
    grid[0][i] = wallId;
    grid[63][i] = wallId;
    grid[i][0] = wallId;
    grid[i][63] = wallId;
  }
}

Sprite _sprite(int c) => Sprite(Uint8List.fromList(List.filled(64 * 64, c)));

class _TestInput extends Wolf3dInput {
  @override
  void update() {}
}

class _SilentAudio implements EngineAudio {
  @override
  WolfensteinData? activeGame;
  @override
  Future<void> debugSoundTest() async {}
  @override
  Future<void> init() async {}
  @override
  void playLevelMusic(Music music) {}
  @override
  void playMenuMusic() {}
  @override
  void playSoundEffect(SoundEffect effect) {}
  @override
  void playSoundEffectId(int sfxId) {}
  @override
  void stopMusic() {}
  @override
  Future<void> stopAllAudio() async {}
  @override
  void dispose() {}
}
