import 'dart:math';
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
import 'package:pyramid/core/theme.dart';

class PyramidLoader extends StatefulWidget {
  final double size;
  final bool useLottie;

  const PyramidLoader({super.key, this.size = 60, this.useLottie = false});

  @override
  State<PyramidLoader> createState() => _PyramidLoaderState();
}

class _PyramidLoaderState extends State<PyramidLoader>
    with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;

  // Mirror the JS constants exactly
  static const _hold = 0.2;
  static const _curveExp = 3.0;
  static const _tiltDeg = 20.0;

  double _ease(double t, double exp) =>
      t < 0.5 ? 0.5 * pow(2 * t, exp) : 1 - 0.5 * pow(2 * (1 - t), exp);

  double _yawAt(double t) {
    final a = 1.0 - _hold;
    if (t >= a) return pi / 4;
    return _ease(t / a, _curveExp) * 2 * pi + pi / 4;
  }

  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 2000),
    )..repeat();
  }

  @override
  void dispose() {
    _ctrl.dispose();
    super.dispose();
  }

  Color _accent(BuildContext ctx) {
    try {
      return PyramidTheme.of(ctx).accent;
    } catch (_) {
      return const Color(0xFFF5A524);
    }
  }

  @override
  Widget build(BuildContext context) {
    final accent = _accent(context);

    final painter = AnimatedBuilder(
      animation: _ctrl,
      builder: (_, _) => CustomPaint(
        painter: _PyramidPainter(
          yaw: _yawAt(_ctrl.value),
          accent: accent,
          tiltDeg: _tiltDeg,
        ),
        size: Size(widget.size, widget.size),
      ),
    );

    if (widget.useLottie) {
      return Center(
        child: SizedBox(
          width: widget.size,
          height: widget.size,
          child: ColorFiltered(
            colorFilter: ColorFilter.mode(accent, BlendMode.srcIn),
            child: Lottie.asset(
              'assets/pyramid-loader.json',
              fit: BoxFit.contain,
              repeat: true,
              animate: true,
              errorBuilder: (_, _, _) => painter,
            ),
          ),
        ),
      );
    }

    return Center(child: painter);
  }
}

class _PyramidPainter extends CustomPainter {
  final double yaw;
  final Color accent;
  final double tiltDeg;

  const _PyramidPainter({
    required this.yaw,
    required this.accent,
    required this.tiltDeg,
  });

  List<double> _rotY(List<double> v, double a) {
    final c = cos(a), s = sin(a);
    return [v[0] * c + v[2] * s, v[1], -v[0] * s + v[2] * c];
  }

  List<List<double>> _clipZ(List<List<double>> poly) {
    final out = <List<double>>[];
    for (var i = 0; i < poly.length; i++) {
      final a = poly[i];
      final b = poly[(i + 1) % poly.length];
      final ai = a[2] >= 0, bi = b[2] >= 0;
      if (ai) out.add(a);
      if (ai != bi) {
        final t = a[2] / (a[2] - b[2]);
        out.add([a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1]), 0.0]);
      }
    }
    return out;
  }

  Offset _proj(List<double> v, double sc, double cx, double cy, double tr) =>
      Offset(cx + v[0] * sc, cy + (v[1] * cos(tr) + v[2] * sin(tr)) * sc);

  Color _darken(Color c, double amount) => c.withValues(
    red: max(0.0, c.r * (1 - amount)),
    green: max(0.0, c.g * (1 - amount)),
    blue: max(0.0, c.b * (1 - amount)),
  );

  @override
  void paint(Canvas canvas, Size size) {
    final sc = size.width * 0.38;
    final cx = size.width / 2;
    final cy = size.height / 2;
    final tr = -tiltDeg * pi / 180;

    const base = 0.7;
    final faces = [
      [[0.0, -1.0, 0.0], [base, 1.0, base],  [base, 1.0, -base]],
      [[0.0, -1.0, 0.0], [base, 1.0, -base], [-base, 1.0, -base]],
      [[0.0, -1.0, 0.0], [-base, 1.0, -base],[-base, 1.0, base]],
      [[0.0, -1.0, 0.0], [-base, 1.0, base], [base, 1.0, base]],
    ];

    final items = <({List<Offset> pts, double depth})>[];

    for (final face in faces) {
      final rot = face.map((v) => _rotY(v, yaw)).toList();
      final cl = _clipZ(rot);
      if (cl.length < 3) continue;
      final depth = cl.fold(0.0, (s, v) => s + v[2]) / cl.length;
      items.add((
        pts: cl.map((v) => _proj(v, sc, cx, cy, tr)).toList(),
        depth: depth,
      ));
    }

    items.sort((a, b) => a.depth.compareTo(b.depth));

    final fillPaint = Paint()
      ..color = _darken(accent, 0.45)
      ..style = PaintingStyle.fill;
    final strokePaint = Paint()
      ..color = accent
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.4 * (size.width / 96)
      ..strokeJoin = StrokeJoin.round
      ..strokeCap = StrokeCap.round;

    for (final item in items) {
      final path = Path()..moveTo(item.pts[0].dx, item.pts[0].dy);
      for (var i = 1; i < item.pts.length; i++) {
        path.lineTo(item.pts[i].dx, item.pts[i].dy);
      }
      path.close();
      canvas.drawPath(path, fillPaint);
      canvas.drawPath(path, strokePaint);
    }
  }

  @override
  bool shouldRepaint(_PyramidPainter old) =>
      old.yaw != yaw || old.accent != accent;
}
