import 'dart:async';
import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../../core/contracts/danmaku.dart';
import '../../../core/infra/player_engine/player_engine.dart';
import '../../../core/media/danmaku_heatmap.dart';
import '../../../providers/danmaku_settings_provider.dart';
import '../../../shared/utils/format_utils.dart';
import '../services/danmaku/danmaku_session_notifier.dart';


const double _kSeekBarHeight = 28.0;


class DebouncedSeekBar extends ConsumerStatefulWidget {
  final PlayerEngine player;


  final void Function(Duration?)? onDragChanged;

  const DebouncedSeekBar({super.key, required this.player, this.onDragChanged});

  @override
  ConsumerState<DebouncedSeekBar> createState() => _DebouncedSeekBarState();
}

class _DebouncedSeekBarState extends ConsumerState<DebouncedSeekBar> {
  StreamSubscription<Duration>? _posSub;
  StreamSubscription<Duration>? _durSub;
  StreamSubscription<Duration>? _bufSub;

  Duration _position = Duration.zero;
  Duration _duration = Duration.zero;
  Duration _buffer = Duration.zero;

  bool _dragging = false;
  double _dragFraction = 0.0;
  bool _hovering = false;
  double? _hoverFraction;

  List<DanmakuComment>? _cachedComments;
  double? _cachedDurationSec;
  DanmakuHeatmapData? _cachedHeatmap;

  PlayerEngine get _player => widget.player;

  @override
  void initState() {
    super.initState();
    _position = _player.state.position;
    _duration = _player.state.duration;
    _buffer = _player.state.buffer;
    _subscribeStreams();
  }

  @override
  void didUpdateWidget(covariant DebouncedSeekBar oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.player != widget.player) {
      unawaited(_posSub?.cancel());
      unawaited(_durSub?.cancel());
      unawaited(_bufSub?.cancel());
      _position = _player.state.position;
      _duration = _player.state.duration;
      _buffer = _player.state.buffer;
      _subscribeStreams();
    }
  }

  void _subscribeStreams() {
    _posSub = _player.stream.position.listen((p) {
      if (!_dragging && mounted) setState(() => _position = p);
    });
    _durSub = _player.stream.duration.listen((d) {
      if (mounted) setState(() => _duration = d);
    });
    _bufSub = _player.stream.buffer.listen((b) {
      if (mounted) setState(() => _buffer = b);
    });
  }

  @override
  void dispose() {
    _posSub?.cancel();
    _durSub?.cancel();
    _bufSub?.cancel();
    super.dispose();
  }

  double _fraction(Duration d) {
    final total = _duration.inMilliseconds;
    if (total <= 0) return 0.0;
    return (d.inMilliseconds / total).clamp(0.0, 1.0);
  }

  void _onDown(PointerDownEvent e, double width) {
    final fraction = (e.localPosition.dx / width).clamp(0.0, 1.0);
    setState(() {
      _dragging = true;
      _dragFraction = fraction;
    });
    widget.onDragChanged?.call(_duration * fraction);
  }

  void _onMove(PointerMoveEvent e, double width) {
    if (!_dragging) return;
    final fraction = (e.localPosition.dx / width).clamp(0.0, 1.0);
    setState(() {
      _dragFraction = fraction;
    });
    widget.onDragChanged?.call(_duration * fraction);
  }

  void _onUp(PointerUpEvent e, double width) {
    if (!_dragging) return;
    final fraction = (e.localPosition.dx / width).clamp(0.0, 1.0);
    final target = _duration * fraction;
    unawaited(_player.seek(target));
    setState(() {
      _dragging = false;
      _position = target;
      _hovering = false;
      _hoverFraction = null;
    });
    widget.onDragChanged?.call(null);
  }

  void _onCancel(PointerCancelEvent e) {
    if (!_dragging) return;
    setState(() {
      _dragging = false;
      _hovering = false;
      _hoverFraction = null;
    });
    widget.onDragChanged?.call(null);
  }


  List<DanmakuComment>? _watchDanmakuComments() {
    final state = ref.watch(danmakuSessionProvider).value;
    if (state is DanmakuSessionMatched &&
        state.commentStatus == DanmakuCommentStatus.ready) {
      return state.comments;
    }
    return null;
  }


  DanmakuHeatmapData? _resolveHeatmap(
    List<DanmakuComment>? comments,
    double durationSec,
    bool enabled,
  ) {
    if (!enabled || comments == null) {
      _cachedComments = null;
      _cachedDurationSec = null;
      _cachedHeatmap = null;
      return null;
    }
    if (identical(comments, _cachedComments) &&
        _cachedDurationSec == durationSec) {
      return _cachedHeatmap;
    }
    final h = computeHeatmap(comments: comments, durationSec: durationSec);
    _cachedComments = comments;
    _cachedDurationSec = durationSec;
    _cachedHeatmap = h;
    return h;
  }

  @override
  Widget build(BuildContext context) {
    final posFrac = _dragging ? _dragFraction : _fraction(_position);
    final bufFrac = _fraction(_buffer);
    final active = _hovering || _dragging;

    final showBubble = _dragging || (_hovering && _hoverFraction != null);
    final bubbleFrac = _dragging ? _dragFraction : (_hoverFraction ?? 0.0);
    final bubbleTime = _duration * bubbleFrac;

    final heatmapEnabled = ref.watch(
      danmakuSettingsProvider.select(
        (v) => v.value?.panelSettings.heatmapEnabled ?? true,
      ),
    );
    final comments = heatmapEnabled ? _watchDanmakuComments() : null;
    final durationSec = _duration.inMilliseconds / 1000.0;
    final heatmap = _resolveHeatmap(comments, durationSec, heatmapEnabled);
    final heatmapVisible = heatmap?.visible == true;

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: LayoutBuilder(
        builder: (context, constraints) {
          final w = constraints.maxWidth;
          const bubbleWidth = 60.0;
          const bubbleHalfWidth = bubbleWidth / 2;
          final bubbleLeft = showBubble
              ? (w * bubbleFrac - bubbleHalfWidth).clamp(0.0, w - bubbleWidth)
              : 0.0;

          return MouseRegion(
            cursor: SystemMouseCursors.click,
            onEnter: (_) => setState(() => _hovering = true),
            onHover: (event) {
              final frac = (event.localPosition.dx / w).clamp(0.0, 1.0);
              setState(() => _hoverFraction = frac);
            },
            onExit: (_) {
              if (!_dragging) {
                setState(() {
                  _hovering = false;
                  _hoverFraction = null;
                });
              }
            },
            child: Stack(
              clipBehavior: Clip.none,
              children: [
                Listener(
                  behavior: HitTestBehavior.translucent,
                  onPointerDown: (e) => _onDown(e, w),
                  onPointerMove: (e) => _onMove(e, w),
                  onPointerUp: (e) => _onUp(e, w),
                  onPointerCancel: _onCancel,
                  child: SizedBox(
                    height: _kSeekBarHeight,
                    child: CustomPaint(
                      size: Size(w, _kSeekBarHeight),
                      painter: _SeekBarPainter(
                        position: posFrac,
                        buffer: bufFrac,
                        active: active,
                        heatmap: heatmapVisible ? heatmap : null,
                      ),
                    ),
                  ),
                ),
                if (showBubble)
                  Positioned(
                    bottom: _kSeekBarHeight + 6,
                    left: bubbleLeft,
                    child: IgnorePointer(
                      child: Container(
                        width: bubbleWidth,
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 3,
                        ),
                        decoration: BoxDecoration(
                          color: const Color(0xCC000000),
                          borderRadius: BorderRadius.circular(4),
                        ),
                        child: Text(
                          formatDurationClock(bubbleTime),
                          textAlign: TextAlign.center,
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 12,
                            fontFeatures: [FontFeature.tabularFigures()],
                          ),
                        ),
                      ),
                    ),
                  ),
              ],
            ),
          );
        },
      ),
    );
  }
}

class _SeekBarPainter extends CustomPainter {
  final double position;
  final double buffer;
  final bool active;
  final DanmakuHeatmapData? heatmap;

  const _SeekBarPainter({
    required this.position,
    required this.buffer,
    required this.active,
    this.heatmap,
  });


  static const double _heatmapBaselineY = 11.0;
  static const double _heatmapMaxHeight = 10.0;
  static const double _heatmapPixelsPerBucket = 4.0;


  static const double _heatmapGamma = 0.6;


  static const Color _heatmapLowColor = Color(0xCC82B1FF);
  static const Color _heatmapHighColor = Color(0xFF2962FF);

  @override
  void paint(Canvas canvas, Size size) {
    _paintHeatmap(canvas, size);
    _paintSeekBar(canvas, size);
  }

  void _paintHeatmap(Canvas canvas, Size size) {
    final h = heatmap;
    if (h == null || h.buckets.isEmpty || size.width <= 0) return;

    final targetCount = (size.width / _heatmapPixelsPerBucket).floor().clamp(
      1,
      h.buckets.length,
    );
    final samples = downsampleHeatmap(h.buckets, targetCount);
    if (samples.isEmpty) return;

    final points = <Offset>[];
    var peak = 0.0;
    for (var i = 0; i < samples.length; i++) {
      final shaped = math
          .pow(samples[i].clamp(0.0, 1.0), _heatmapGamma)
          .toDouble();
      if (shaped > peak) peak = shaped;
      final cx = samples.length == 1
          ? size.width / 2
          : i / (samples.length - 1) * size.width;
      points.add(Offset(cx, _heatmapBaselineY - _heatmapMaxHeight * shaped));
    }

    final linePath = Path()..moveTo(points.first.dx, points.first.dy);
    for (var i = 1; i < points.length; i++) {
      linePath.lineTo(points[i].dx, points[i].dy);
    }

    final color = Color.lerp(_heatmapLowColor, _heatmapHighColor, peak)!;

    final fillPath = Path.from(linePath)
      ..lineTo(points.last.dx, _heatmapBaselineY)
      ..lineTo(points.first.dx, _heatmapBaselineY)
      ..close();
    canvas.drawPath(
      fillPath,
      Paint()
        ..style = PaintingStyle.fill
        ..color = color.withValues(alpha: color.a * 0.18),
    );

    canvas.drawPath(
      linePath,
      Paint()
        ..style = PaintingStyle.stroke
        ..strokeWidth = 1.4
        ..strokeJoin = StrokeJoin.round
        ..strokeCap = StrokeCap.round
        ..color = color,
    );
  }

  void _paintSeekBar(Canvas canvas, Size size) {
    final trackH = active ? 5.0 : 3.0;
    final cy = size.height / 2;
    final top = cy - trackH / 2;
    final bottom = cy + trackH / 2;
    final r = Radius.circular(trackH / 2);

    canvas.drawRRect(
      RRect.fromRectAndRadius(Rect.fromLTRB(0, top, size.width, bottom), r),
      Paint()..color = const Color(0x3DFFFFFF),
    );

    if (buffer > 0) {
      canvas.drawRRect(
        RRect.fromRectAndRadius(
          Rect.fromLTRB(0, top, size.width * buffer, bottom),
          r,
        ),
        Paint()..color = const Color(0x3DFFFFFF),
      );
    }

    if (position > 0) {
      canvas.drawRRect(
        RRect.fromRectAndRadius(
          Rect.fromLTRB(0, top, size.width * position, bottom),
          r,
        ),
        Paint()..color = const Color(0xFFFF0000),
      );
    }

    if (active) {
      canvas.drawCircle(
        Offset(size.width * position, cy),
        6.4,
        Paint()..color = const Color(0xFFFF0000),
      );
    }
  }

  @override
  bool shouldRepaint(_SeekBarPainter old) =>
      position != old.position ||
      buffer != old.buffer ||
      active != old.active ||
      !identical(heatmap, old.heatmap);
}
