import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
import 'package:wolf_3d_input/wolf_3d_input.dart';

class WolfRenderer extends StatefulWidget {
  const WolfRenderer(
    this.data, {
    required this.difficulty,
    required this.startingEpisode,
    required this.audio,
    super.key,
  });

  final WolfensteinData data;
  final Difficulty difficulty;
  final int startingEpisode;
  final EngineAudio audio;

  @override
  State<WolfRenderer> createState() => _WolfRendererState();
}

class _WolfRendererState extends State<WolfRenderer>
    with SingleTickerProviderStateMixin {
  final WolfInput inputManager = WolfInput();
  late final WolfEngine engine;
  late Ticker _gameLoop;
  final FocusNode _focusNode = FocusNode();

  // --- NEW RASTERIZER STATE ---
  // Lock the internal rendering resolution to the classic 320x200
  final FrameBuffer _frameBuffer = FrameBuffer(320, 200);
  final SoftwareRasterizer _rasterizer = SoftwareRasterizer();

  ui.Image? _renderedFrame;
  bool _isRendering = false;

  @override
  void initState() {
    super.initState();

    engine = WolfEngine(
      data: widget.data,
      difficulty: widget.difficulty,
      startingEpisode: widget.startingEpisode,
      audio: widget.audio,
      onGameWon: () {
        Navigator.of(context).pop();
      },
    );

    engine.init();

    _gameLoop = createTicker(_tick)..start();
    _focusNode.requestFocus();
  }

  void _tick(Duration elapsed) {
    if (!engine.isInitialized) return;

    inputManager.update();
    engine.tick(elapsed, inputManager.currentInput);

    // Only start rendering a new frame if the previous one is finished.
    // This prevents memory leaks and stuttering on lower-end hardware!
    if (!_isRendering) {
      _isRendering = true;

      // 1. Crunch the math and fill the 1D memory array
      _rasterizer.render(engine, _frameBuffer);

      // 2. Convert the raw Uint32List memory into a Flutter ui.Image
      ui.decodeImageFromPixels(
        // Extract the underlying byte buffer from our 32-bit integer array
        _frameBuffer.pixels.buffer.asUint8List(),
        _frameBuffer.width,
        _frameBuffer.height,
        ui.PixelFormat.rgba8888, // Standard 32-bit color format
        (ui.Image image) {
          if (mounted) {
            setState(() {
              // ALWAYS dispose the old frame before assigning the new one
              // to prevent massive memory leaks on the GPU!
              _renderedFrame?.dispose();
              _renderedFrame = image;
            });
          }
          _isRendering = false;
        },
      );
    }
  }

  @override
  void dispose() {
    _gameLoop.dispose();
    _focusNode.dispose();
    _renderedFrame?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (!engine.isInitialized) {
      return const Center(child: CircularProgressIndicator(color: Colors.teal));
    }

    return Scaffold(
      backgroundColor: Colors.black,
      body: KeyboardListener(
        focusNode: _focusNode,
        autofocus: true,
        onKeyEvent: (_) {},
        child: Column(
          children: [
            Expanded(
              child: LayoutBuilder(
                builder: (context, constraints) {
                  return Center(
                    child: AspectRatio(
                      aspectRatio: 320 / 200,
                      child: CustomPaint(
                        size: Size(constraints.maxWidth, constraints.maxHeight),
                        painter: BufferPainter(_renderedFrame),
                      ),
                    ),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class BufferPainter extends CustomPainter {
  final ui.Image? frame;

  BufferPainter(this.frame);

  @override
  void paint(Canvas canvas, Size size) {
    if (frame == null) return;

    final Paint paint = Paint()..filterQuality = FilterQuality.none;

    final Rect srcRect = Rect.fromLTWH(
      0,
      0,
      frame!.width.toDouble(),
      frame!.height.toDouble(),
    );
    final Rect dstRect = Rect.fromLTWH(0, 0, size.width, size.height);

    canvas.drawImageRect(frame!, srcRect, dstRect, paint);
  }

  @override
  bool shouldRepaint(covariant BufferPainter oldDelegate) {
    return oldDelegate.frame != frame;
  }
}
