import 'dart:collection';

/// Canonical Wolf3D-style 17-bit Galois LFSR used for Fizzle Fade ordering.
///
/// The sequence mirrors the original coordinate extraction strategy:
/// low 8 bits minus one form `y`, the next 9 bits form `x`, and out-of-bounds
/// points are skipped. For menu and intro rendering we use the original
/// canonical 320x200 coordinate space and let renderers scale that to their
/// output buffer.
abstract final class FizzleFade {
  static const int canonicalWidth = 320;
  static const int canonicalHeight = 200;
  static const int canonicalPixelCount = canonicalWidth * canonicalHeight;
  static const int seed = 1;
  static const int _xorMask = 0x12000;
  static final List<int> _canonicalSequence = List<int>.unmodifiable(
    _buildSequence(width: canonicalWidth, height: canonicalHeight),
  );

  static List<int> get canonicalSequence => _canonicalSequence;

  static int nextState(int state) {
    final bool lsbSet = (state & 1) != 0;
    int next = state >> 1;
    if (lsbSet) {
      next ^= _xorMask;
    }
    return next;
  }

  static int xForState(int state) => (state >> 8) & 0x1FF;

  static int yForState(int state) => ((state & 0xFF) - 1) & 0xFF;

  static bool isInBounds(int state, {required int width, required int height}) {
    return xForState(state) < width && yForState(state) < height;
  }

  static int revealCountForProgress(double progress, {int? pixelCount}) {
    final int total = pixelCount ?? canonicalPixelCount;
    return (progress.clamp(0.0, 1.0) * total).round().clamp(0, total);
  }

  static void forEachCanonicalPixel(
    int count,
    void Function(int x, int y) plot,
  ) {
    final int limit = count.clamp(0, canonicalPixelCount);
    for (int i = 0; i < limit; i++) {
      final int packed = _canonicalSequence[i];
      plot(packed & 0xFFFF, packed >> 16);
    }
  }

  static List<int> _buildSequence({required int width, required int height}) {
    final List<int> points = <int>[];
    int state = seed;
    do {
      if (isInBounds(state, width: width, height: height)) {
        points.add((yForState(state) << 16) | xForState(state));
      }
      state = nextState(state);
    } while (state != seed);
    return UnmodifiableListView<int>(points);
  }
}
