/// Flutter widget that renders Wolf3D frames using the ASCII renderer.
library;

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

/// Displays the game using a text-mode approximation of the original renderer.
class WolfAsciiRenderer extends BaseWolfRenderer {
  final AsciiTheme theme;

  /// Creates an ASCII renderer bound to [engine].
  const WolfAsciiRenderer({
    required super.engine,
    this.theme = AsciiThemes.blocks,
    super.onKeyEvent,
    super.key,
  });

  @override
  State<WolfAsciiRenderer> createState() => _WolfAsciiRendererState();
}

class _WolfAsciiRendererState extends BaseWolfRendererState<WolfAsciiRenderer> {
  static const int _renderWidth = 160;
  static const int _renderHeight = 100;

  List<List<ColoredChar>> _asciiFrame = [];
  final AsciiRenderer _asciiRenderer = AsciiRenderer(
    mode: AsciiRendererMode.terminalGrid,
  );

  @override
  void initState() {
    super.initState();
    _asciiRenderer.activeTheme = widget.theme;
    // ASCII output uses a reduced logical framebuffer because glyph rendering
    // expands the final view significantly once laid out in Flutter text.
    if (widget.engine.frameBuffer.width != _renderWidth ||
        widget.engine.frameBuffer.height != _renderHeight) {
      widget.engine.setFrameBuffer(_renderWidth, _renderHeight);
    }
  }

  @override
  void didUpdateWidget(covariant WolfAsciiRenderer oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.theme != widget.theme) {
      _asciiRenderer.activeTheme = widget.theme;
    }
  }

  @override
  Color get scaffoldColor => Colors.black;

  @override
  void performRender() {
    setState(() {
      _asciiFrame = _asciiRenderer.render(widget.engine);
    });
  }

  @override
  Widget buildViewport(BuildContext context) {
    return _asciiFrame.isEmpty
        ? const SizedBox.shrink()
        : AsciiFrameWidget(frameData: _asciiFrame);
  }
}

/// Paints a pre-rasterized ASCII frame using grouped text spans per color run.
class AsciiFrameWidget extends StatelessWidget {
  /// Two-dimensional text grid generated by [AsciiRenderer.render].
  final List<List<ColoredChar>> frameData;

  /// Creates a widget that displays [frameData].
  const AsciiFrameWidget({super.key, required this.frameData});

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 4 / 3,
      child: FittedBox(
        fit: BoxFit.fill,
        child: Container(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: frameData.map((row) {
              List<TextSpan> optimizedSpans = [];
              if (row.isNotEmpty) {
                // Merge adjacent cells with the same color to keep the rich
                // text tree smaller and reduce per-frame layout overhead.
                Color currentColor = Color(row[0].argb);
                Color? currentBackground = row[0].backgroundArgb == null
                    ? null
                    : Color(row[0].backgroundArgb!);
                StringBuffer currentSegment = StringBuffer(row[0].char);

                for (int i = 1; i < row.length; i++) {
                  final Color nextColor = Color(row[i].argb);
                  final Color? nextBackground = row[i].backgroundArgb == null
                      ? null
                      : Color(row[i].backgroundArgb!);
                  if (nextColor == currentColor &&
                      nextBackground == currentBackground) {
                    currentSegment.write(row[i].char);
                  } else {
                    optimizedSpans.add(
                      TextSpan(
                        text: currentSegment.toString(),
                        style: TextStyle(
                          color: currentColor,
                          backgroundColor: currentBackground,
                        ),
                      ),
                    );
                    currentColor = nextColor;
                    currentBackground = nextBackground;
                    currentSegment = StringBuffer(row[i].char);
                  }
                }
                optimizedSpans.add(
                  TextSpan(
                    text: currentSegment.toString(),
                    style: TextStyle(
                      color: currentColor,
                      backgroundColor: currentBackground,
                    ),
                  ),
                );
              }

              return RichText(
                text: TextSpan(
                  style: const TextStyle(fontFamily: 'monospace', height: 1.0),
                  children: optimizedSpans,
                ),
              );
            }).toList(),
          ),
        ),
      ),
    );
  }
}
