import 'dart:ui';

import 'package:flutter/material.dart';

class Point {
  final double x;
  final double y;

  const Point(this.x, this.y);

  @override
  String toString() {
    return 'Point{x: $x, y: $y}';
  }
}

class LineChart extends StatefulWidget {
  final List<Point> points;
  final Color color;
  final double height;
  final Duration duration;

  const LineChart({
    super.key,
    required this.points,
    required this.color,
    this.duration = const Duration(milliseconds: 0),
    required this.height,
  });

  @override
  State<LineChart> createState() => _LineChartState();
}

typedef ComputedPath = Path Function(Size size);

class _LineChartState extends State<LineChart>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  double progress = 0;
  List<Point> prevPoints = [];
  List<Point> nextPoints = [];
  List<Point> points = [];

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: widget.duration,
    );
    points = widget.points;
    prevPoints = points;
  }

  @override
  void didUpdateWidget(LineChart oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.points != points) {
      prevPoints = points;
      if (!_controller.isCompleted) {
        prevPoints = nextPoints;
      }
      points = widget.points;
      _controller.forward(from: 0);
    }
  }

  List<Point> getRenderPoints(List<Point> points) {
    if (points.isEmpty) return [];
    double maxX = points[0].x;
    double minX = points[0].x;
    double maxY = points[0].y;
    double minY = points[0].y;
    for (var point in points) {
      if (point.x > maxX) maxX = point.x;
      if (point.x < minX) minX = point.x;
      if (point.y > maxY) maxY = point.y;
      if (point.y < minY) minY = point.y;
    }
    return points.map((e) {
      var x = (e.x - minX) / (maxX - minX);
      if (x.isNaN) {
        x = 0.5;
      }

      var y = (e.y - minY) / (maxY - minY);
      if (y.isNaN) {
        y = 0.5;
      }

      return Point(
        x,
        y,
      );
    }).toList();
  }

  List<Point> getInterpolatePoints(
    List<Point> prevPoints,
    List<Point> points,
    double t,
  ) {
    var renderPrevPoints = getRenderPoints(prevPoints);
    var renderPotions = getRenderPoints(points);
    return List.generate(renderPotions.length, (i) {
      if (i > renderPrevPoints.length - 1) {
        return renderPotions[i];
      }
      var x = lerpDouble(renderPrevPoints[i].x, renderPotions[i].x, t)!;
      var y = lerpDouble(renderPrevPoints[i].y, renderPotions[i].y, t)!;
      return Point(
        x,
        y,
      );
    });
  }

  Path getPath(List<Point> points, Size size) {
    final path = Path()
      ..moveTo(points[0].x * size.width, (1 - points[0].y) * size.height);
    for (var i = 1; i < points.length; i++) {
      path.lineTo(points[i].x * size.width, (1 - points[i].y) * size.height);
    }
    return path;
  }

  ComputedPath getComputedPath({
    required List<Point> prevPoints,
    required List<Point> points,
    required progress,
  }) {
    nextPoints = getInterpolatePoints(prevPoints, points, progress);
    Path setSize(Size size) {
      final prevPath = getPath(prevPoints, size);
      final nextPath = getPath(nextPoints, size);
      final prevMetric = prevPath.computeMetrics().first;
      final nextMetric = nextPath.computeMetrics().first;
      final prevLength = prevMetric.length;
      final nextLength = nextMetric.length;
      return nextMetric.extractPath(
        0,
        prevLength + (nextLength - prevLength) * progress,
      );
    }

    return setSize;
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: _controller.view,
        builder: (_, __) {
          return CustomPaint(
            painter: LineChartPainter(
              color: widget.color,
              computedPath: getComputedPath(
                prevPoints: prevPoints,
                points: points,
                progress: _controller.value,
              ),
            ),
            child: SizedBox(
              height: widget.height,
              width: double.infinity,
            ),
          );
        });
  }
}

class LineChartPainter extends CustomPainter {
  final ComputedPath computedPath;
  final Color color;

  LineChartPainter({
    required this.computedPath,
    required this.color,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color
      ..strokeWidth = 2.0
      ..style = PaintingStyle.stroke;

    canvas.drawPath(computedPath(size), paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}
