// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:wyatt_component_copy_with_extension/component_copy_with_extension.dart';
import 'package:wyatt_ui_components/wyatt_ui_components.dart';
import 'package:wyatt_ui_kit/wyatt_ui_kit.dart';

part 'loader.g.dart';

@ComponentCopyWithExtension()
class Loader extends LoaderComponent with $LoaderCWMixin {
  const Loader({
    super.colors,
    super.radius,
    super.stroke,
    super.duration,
    super.flip,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final dimension =
        (radius != null) ? radius! * 2 : context.buttonTheme.height;
    return SizedBox.square(
      dimension: dimension,
      child: RepaintBoundary(
        child: CustomPaint(
          painter: _LoaderPainter(
            ThemeHelper.getThemeElement<MultiColor, MultiColor>(
              [
                colors,
                Theme.of(context).extension<LoaderThemeExtension>()?.colors,
                MultiColor([
                  Theme.of(context).progressIndicatorTheme.color ??
                      context.colorScheme.primary,
                  context.colorScheme.onPrimary,
                ]),

                /// This is the default value. So the final
                /// value cannot be null.
                const MultiColor([])
              ],
              valueValidator: (multiColor) =>
                  multiColor != null && multiColor.isColor,
              transform: (multiColor) => multiColor,
            )!,
            dimension / 2,
            ThemeHelper.getThemeElement<double, double>(
              [
                stroke,
                Theme.of(context).extension<LoaderThemeExtension>()?.stroke,
                4,
              ],
              valueValidator: (stroke) => stroke != null,
              transform: (stroke) => stroke,
            )!,
            flip: flip ?? false,
          ),
        )
            .animate(
              onPlay: (controller) => controller.repeat(),
            )
            .rotate(
              duration: duration ?? 900.ms,
              begin: (flip ?? false) ? 0 : 1,
              end: (flip ?? false) ? 1 : 0,
            ),
      ),
    );
  }
}

class _LoaderPainter extends CustomPainter {
  _LoaderPainter(
    this.colors,
    this.radius,
    this.stroke, {
    required this.flip,
  });

  final MultiColor colors;
  final double radius;
  final double stroke;
  final bool flip;

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final circleSurface = Rect.fromCircle(center: center, radius: radius);

    final dotColor = colors.color;
    final dotCenter =
        Offset(size.width / 2 + (flip ? -radius : radius), size.height / 2);
    final gradient =
        colors.isGradient ? colors.colors : [colors.color, colors.color];

    final gradientCirclePainter = Paint()
      ..shader = SweepGradient(
        colors: (flip ? gradient.reversed : gradient).toList(),
        transform: flip ? const GradientRotation(pi) : null,
      ).createShader(circleSurface)
      ..strokeWidth = stroke
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;

    final dotPainter = Paint()
      ..color = dotColor
      ..style = PaintingStyle.fill
      ..strokeCap = StrokeCap.round;

    canvas
      ..drawCircle(center, radius, gradientCirclePainter)
      ..drawCircle(dotCenter, stroke / 2, dotPainter);
  }

  @override
  bool shouldRepaint(_LoaderPainter oldDelegate) => false;

  @override
  bool shouldRebuildSemantics(_LoaderPainter oldDelegate) => false;
}
