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

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

import '../servicios/servicio_audio.dart';

/// Visualizador de audio para el reproductor.
///
/// En Android intenta capturar la forma de onda real del audio mediante un
/// canal nativo. Si el dispositivo o los permisos no lo permiten, mantiene un
/// fallback animado para que la interfaz nunca quede rota.
class VisualizadorAudio extends StatefulWidget {
  final Stream<EstadoReproduccion> estadoStream;
  final Stream<int?>? androidAudioSessionIdStream;
  final int barras;
  final Color? color;
  final double altura;
  final double anchuraTotal;

  const VisualizadorAudio({
    super.key,
    required this.estadoStream,
    this.androidAudioSessionIdStream,
    this.barras = 20,
    this.color,
    this.altura = 48,
    this.anchuraTotal = double.infinity,
  });

  @override
  State<VisualizadorAudio> createState() => _VisualizadorAudioState();
}

class _VisualizadorAudioState extends State<VisualizadorAudio>
    with SingleTickerProviderStateMixin {
  static const _eventChannel = EventChannel('pluriwave/audio_visualizer');

  late final AnimationController _controller;
  bool _activo = false;
  int? _sessionId;
  List<double> _ondaObjetivo = const [];
  List<double> _ondaVisual = const [];
  DateTime? _ultimaOndaReal;
  StreamSubscription<EstadoReproduccion>? _estadoSubscription;
  StreamSubscription<int?>? _sessionSubscription;
  StreamSubscription<dynamic>? _ondaSubscription;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    )..addListener(() {
      if (mounted) {
        _actualizarOndaVisual();
        setState(() {});
      }
    });
    _estadoSubscription = widget.estadoStream.listen(_onEstado);
    _sessionSubscription = widget.androidAudioSessionIdStream?.listen(
      _onSessionId,
    );
  }

  void _onEstado(EstadoReproduccion estado) {
    final nuevoActivo =
        estado == EstadoReproduccion.reproduciendo ||
        estado == EstadoReproduccion.cargando;
    if (!mounted) return;
    if (nuevoActivo != _activo) {
      setState(() => _activo = nuevoActivo);
      nuevoActivo ? _controller.repeat() : _controller.stop();
    }
    _sincronizarOndaReal();
  }

  void _onSessionId(int? sessionId) {
    if (!mounted || sessionId == _sessionId) return;
    _sessionId = sessionId;
    unawaited(_ondaSubscription?.cancel());
    _ondaSubscription = null;
    _sincronizarOndaReal();
  }

  void _sincronizarOndaReal() {
    final sessionId = _sessionId;
    final puedeCapturar = sessionId != null && sessionId > 0 && _activo;

    if (!puedeCapturar) {
      unawaited(_ondaSubscription?.cancel());
      _ondaSubscription = null;
      _ultimaOndaReal = null;
      return;
    }

    if (_ondaSubscription != null) return;
    _ondaSubscription = _eventChannel
        .receiveBroadcastStream({
          'sessionId': sessionId,
          'bands': widget.barras,
        })
        .listen(
          (event) {
            if (!mounted || event is! List) return;
            final muestras = event
                .whereType<num>()
                .map((v) => v.toDouble().clamp(0.0, 1.0))
                .toList(growable: false);
            if (muestras.isNotEmpty) {
              _ultimaOndaReal = DateTime.now();
              _ondaObjetivo = _normalizar(muestras);
            }
          },
          onError: (_) {
            unawaited(_ondaSubscription?.cancel());
            _ondaSubscription = null;
            _ultimaOndaReal = null;
          },
          cancelOnError: false,
        );
  }

  void _actualizarOndaVisual() {
    final objetivo = _objetivoActual();
    if (_ondaVisual.length != objetivo.length) {
      _ondaVisual = List<double>.from(objetivo);
      return;
    }
    _ondaVisual = List<double>.generate(objetivo.length, (i) {
      final suavizado = _ondaVisual[i] + (objetivo[i] - _ondaVisual[i]) * 0.16;
      return suavizado.clamp(0.0, 1.0);
    }, growable: false);
  }

  List<double> _objetivoActual() {
    final ahora = DateTime.now();
    final tieneReal =
        _ultimaOndaReal != null &&
        ahora.difference(_ultimaOndaReal!) <
            const Duration(milliseconds: 900) &&
        _ondaObjetivo.isNotEmpty;
    if (tieneReal) return _ondaObjetivo;
    return _ondaOrganica();
  }

  List<double> _ondaOrganica() {
    final count = widget.barras.clamp(8, 96);
    final phase = _controller.value * pi * 2;
    final intensidad = _activo ? 1.0 : 0.18;
    return List<double>.generate(count, (i) {
      final p = count <= 1 ? 0.0 : i / (count - 1);
      final envelope = sin(pi * p).clamp(0.10, 1.0);
      final flow =
          sin(phase + p * pi * 2.2) * 0.24 +
          sin(phase * 0.63 - p * pi * 5.1) * 0.18 +
          sin(phase * 1.37 + p * pi * 9.0) * 0.08;
      final value = 0.5 + flow * envelope * intensidad;
      return value.clamp(0.12, 0.88);
    }, growable: false);
  }

  List<double> _normalizar(List<double> muestras) {
    final maximo = muestras.fold<double>(0, (max, v) => v > max ? v : max);
    if (maximo <= 0.001) return muestras;
    return muestras
        .map((v) => (0.08 + (v / maximo) * 0.84).clamp(0.0, 1.0))
        .toList(growable: false);
  }

  @override
  void dispose() {
    _estadoSubscription?.cancel();
    _sessionSubscription?.cancel();
    _ondaSubscription?.cancel();
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final color = widget.color ?? Theme.of(context).colorScheme.primary;
    final t = _controller.value * pi * 2;
    return SizedBox(
      height: widget.altura,
      width: widget.anchuraTotal,
      child: RepaintBoundary(
        child: CustomPaint(
          painter: _WaveFlowPainter(
            color: color,
            phase: t,
            active: _activo,
            waveform: _ondaVisual,
          ),
          child: const SizedBox.expand(),
        ),
      ),
    );
  }
}

class _WaveFlowPainter extends CustomPainter {
  const _WaveFlowPainter({
    required this.color,
    required this.phase,
    required this.active,
    required this.waveform,
  });

  final Color color;
  final double phase;
  final bool active;
  final List<double> waveform;

  @override
  void paint(Canvas canvas, Size size) {
    final center = size.height / 2;
    final amp = active ? size.height * 0.24 : size.height * 0.06;
    final glowPaint =
        Paint()
          ..style = PaintingStyle.stroke
          ..strokeWidth = active ? 8 : 4
          ..strokeCap = StrokeCap.round
          ..color = color.withValues(alpha: active ? 0.18 : 0.08);
    final linePaint =
        Paint()
          ..style = PaintingStyle.stroke
          ..strokeWidth = 3
          ..strokeCap = StrokeCap.round
          ..shader = LinearGradient(
            colors: [
              color.withValues(alpha: 0.08),
              color.withValues(alpha: active ? 0.95 : 0.35),
              const Color(0xFFF4B860).withValues(alpha: active ? 0.75 : 0.20),
              color.withValues(alpha: 0.08),
            ],
          ).createShader(Offset.zero & size);

    Path pathFor(double shift, double amplitude, {bool real = false}) {
      final path = Path()..moveTo(0, center);
      for (double x = 0; x <= size.width; x += 8) {
        final p = x / size.width;
        final muestra = real ? _muestraReal(p) : null;
        final sintetica =
            sin((p * pi * 2.4) + phase + shift) +
            sin((p * pi * 5.2) - phase * 0.7 + shift) * 0.32;
        final forma = muestra == null ? sintetica : (muestra * 2.0) - 1.0;
        final y = center + forma * amplitude;
        path.lineTo(x, y);
      }
      return path;
    }

    final usaReal = waveform.isNotEmpty;
    canvas.drawPath(pathFor(0, amp, real: usaReal), glowPaint);
    canvas.drawPath(
      pathFor(0.6, amp * 0.62, real: usaReal),
      glowPaint..color = color.withValues(alpha: active ? 0.10 : 0.05),
    );
    canvas.drawPath(pathFor(0, amp, real: usaReal), linePaint);
    canvas.drawPath(
      pathFor(0.9, amp * 0.58, real: usaReal),
      Paint()
        ..style = PaintingStyle.stroke
        ..strokeWidth = 2
        ..strokeCap = StrokeCap.round
        ..color = Colors.white.withValues(alpha: active ? 0.35 : 0.12),
    );
  }

  double? _muestraReal(double p) {
    if (waveform.isEmpty) return null;
    final index = (p * (waveform.length - 1)).round().clamp(
      0,
      waveform.length - 1,
    );
    return waveform[index];
  }

  @override
  bool shouldRepaint(covariant _WaveFlowPainter oldDelegate) {
    return oldDelegate.phase != phase ||
        oldDelegate.active != active ||
        oldDelegate.color != color ||
        oldDelegate.waveform != waveform;
  }
}

/// Versión compacta del visualizador para el MiniReproductor.
class IndicadorReproduccion extends StatefulWidget {
  final Stream<EstadoReproduccion> estadoStream;
  final Color? color;
  final double size;

  const IndicadorReproduccion({
    super.key,
    required this.estadoStream,
    this.color,
    this.size = 16,
  });

  @override
  State<IndicadorReproduccion> createState() => _IndicadorReproduccionState();
}

class _IndicadorReproduccionState extends State<IndicadorReproduccion>
    with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;
  bool _reproduciendo = false;
  StreamSubscription<EstadoReproduccion>? _estadoSubscription;

  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 600),
    )..addListener(() {
      if (mounted) setState(() {});
    });
    _estadoSubscription = widget.estadoStream.listen((s) {
      final rep = s == EstadoReproduccion.reproduciendo;
      if (rep == _reproduciendo || !mounted) return;
      setState(() => _reproduciendo = rep);
      rep ? _ctrl.repeat(reverse: true) : _ctrl.stop();
    });
  }

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

  @override
  Widget build(BuildContext context) {
    final color = widget.color ?? Theme.of(context).colorScheme.primary;
    if (!_reproduciendo) {
      return Icon(
        Icons.radio,
        size: widget.size,
        color: Theme.of(context).colorScheme.onSurfaceVariant,
      );
    }
    return Row(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.end,
      children: List.generate(3, (i) {
        final alts = [0.5, 1.0, 0.7];
        final fases = [0.0, 0.3, 0.6];
        final h =
            ((sin(_ctrl.value * pi + fases[i]) + 1) / 2 * alts[i] + 0.2).clamp(
              0.15,
              1.0,
            ) *
            widget.size;
        return Container(
          width: widget.size * 0.2,
          height: h,
          margin: EdgeInsets.only(right: i < 2 ? widget.size * 0.1 : 0),
          decoration: BoxDecoration(
            color: color,
            borderRadius: BorderRadius.circular(1),
          ),
        );
      }),
    );
  }
}
