/// Flutter widget that applies a GLSL post-process over Wolf3D frames.
library;

import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_renderer.dart';
import 'package:wolf_3d_renderer/base_renderer.dart';
import 'package:wolf_3d_renderer/wolf_3d_asset_painter.dart';

/// Displays software-rendered frames through a GLSL post-processing pass.
class WolfGlslRenderer extends BaseWolfRenderer {
  /// Whether CRT-like post effects are enabled in the shader pass.
  final bool effectsEnabled;

  /// Callback when shader loading fails and software fallback should be used.
  final VoidCallback? onUnavailable;

  /// Creates a GLSL renderer bound to [engine].
  const WolfGlslRenderer({
    required super.engine,
    this.effectsEnabled = false,
    super.onKeyEvent,
    this.onUnavailable,
    super.key,
  });

  @override
  State<WolfGlslRenderer> createState() => _WolfGlslRendererState();
}

class _WolfGlslRendererState extends BaseWolfRendererState<WolfGlslRenderer> {
  static const int _renderWidth = 960;
  static const int _renderHeight = 600;

  final SoftwareRenderer _renderer = SoftwareRenderer();

  ui.Image? _renderedFrame;
  ui.FragmentProgram? _shaderProgram;
  ui.FragmentShader? _shader;
  bool _isRendering = false;
  bool _isShaderUnavailable = false;

  @override
  void initState() {
    super.initState();
    if (widget.engine.frameBuffer.width != _renderWidth ||
        widget.engine.frameBuffer.height != _renderHeight) {
      widget.engine.setFrameBuffer(_renderWidth, _renderHeight);
    }
    _loadShader();
  }

  @override
  Color get scaffoldColor => Colors.black;

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

  @override
  void performRender() {
    if (_isRendering) {
      return;
    }
    _isRendering = true;

    final FrameBuffer frameBuffer = widget.engine.frameBuffer;
    _renderer.render(widget.engine);

    ui.decodeImageFromPixels(
      frameBuffer.pixels.buffer.asUint8List(),
      frameBuffer.width,
      frameBuffer.height,
      ui.PixelFormat.rgba8888,
      (ui.Image image) {
        if (mounted) {
          setState(() {
            _renderedFrame?.dispose();
            _renderedFrame = image;
          });
        } else {
          image.dispose();
        }
        _isRendering = false;
      },
    );
  }

  @override
  Widget buildViewport(BuildContext context) {
    if (_renderedFrame == null) {
      return const CircularProgressIndicator(color: Colors.white24);
    }

    if (_isShaderUnavailable || _shader == null) {
      // Keep frames visible even if GLSL initialization failed.
      return Padding(
        padding: const EdgeInsets.all(16.0),
        child: AspectRatio(
          aspectRatio: 4 / 3,
          child: WolfAssetPainter.frame(_renderedFrame),
        ),
      );
    }

    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: AspectRatio(
        aspectRatio: 4 / 3,
        child: CustomPaint(
          painter: _GlslFramePainter(
            frame: _renderedFrame!,
            shader: _shader!,
            effectsEnabled: widget.effectsEnabled,
            elapsedSeconds: widget.engine.timeAliveMs / 1000.0,
          ),
          child: const SizedBox.expand(),
        ),
      ),
    );
  }

  Future<void> _loadShader() async {
    try {
      final ui.FragmentProgram program = await ui.FragmentProgram.fromAsset(
        'packages/wolf_3d_renderer/shaders/wolf_world.frag',
      );
      if (!mounted) {
        return;
      }
      setState(() {
        _shaderProgram = program;
        _shader = _shaderProgram!.fragmentShader();
        _isShaderUnavailable = false;
      });
    } catch (_) {
      if (!mounted) {
        return;
      }
      setState(() {
        _isShaderUnavailable = true;
      });
      widget.onUnavailable?.call();
    }
  }
}

class _GlslFramePainter extends CustomPainter {
  final ui.Image frame;
  final ui.FragmentShader shader;
  final bool effectsEnabled;
  final double elapsedSeconds;

  _GlslFramePainter({
    required this.frame,
    required this.shader,
    required this.effectsEnabled,
    required this.elapsedSeconds,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final double texelX = frame.width > 0 ? 1.0 / frame.width : 1.0;
    final double texelY = frame.height > 0 ? 1.0 / frame.height : 1.0;
    shader
      ..setFloat(0, size.width)
      ..setFloat(1, size.height)
      ..setFloat(2, texelX)
      ..setFloat(3, texelY)
      ..setFloat(4, effectsEnabled ? 1.0 : 0.0)
      ..setFloat(5, elapsedSeconds)
      ..setImageSampler(0, frame);

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

    canvas.drawRect(Offset.zero & size, paint);
  }

  @override
  bool shouldRepaint(covariant _GlslFramePainter oldDelegate) {
    return oldDelegate.frame != frame ||
        oldDelegate.shader != shader ||
        oldDelegate.effectsEnabled != effectsEnabled ||
        oldDelegate.elapsedSeconds != elapsedSeconds;
  }
}
