import 'package:drift/drift.dart' as drift;
import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';

import '../../../api/endpoints/likes.dart';
import '../../../cache/audio_cache_manager.dart' show appDbProvider;
import '../../../cache/db.dart';
import '../../../cache/mutation_queue.dart';
import '../../../likes/likes_provider.dart';
import '../../../models/track.dart';
import '../../../player/player_provider.dart';
import '../../../playlists/playlists_provider.dart';
import '../../../quarantine/quarantine_provider.dart';
import '../../../theme/theme_extension.dart';
import 'add_to_playlist_sheet.dart';
import 'hide_track_sheet.dart';

/// Modal bottom sheet that lists the 7 canonical track actions.
/// Pops first when an item is tapped (immediate visual feedback) and
/// then runs the action — for actions that open a sub-sheet, the
/// sub-sheet opens after the parent pops.
class TrackActionsSheet extends ConsumerWidget {
  const TrackActionsSheet({
    super.key,
    required this.track,
    required this.hideQueueActions,
    this.onNavigate,
  });

  final TrackRef track;
  final bool hideQueueActions;

  /// Optional hook for "Go to album" / "Go to artist". Called with
  /// the destination route path AFTER the sheet has popped. Lets
  /// callers that live OUTSIDE the ShellRoute (e.g. the full player
  /// at /now-playing) pop themselves first then push, so go_router
  /// doesn't try to mount a second ShellRoute on top of the active
  /// top-level route. When null, the sheet does its own
  /// context.push, which is correct for surfaces already inside the
  /// shell (mini player, track rows on detail screens, etc.).
  final Future<void> Function(String path)? onNavigate;

  static Future<void> show(
    BuildContext context,
    TrackRef track, {
    bool hideQueueActions = false,
    Future<void> Function(String path)? onNavigate,
  }) {
    return showModalBottomSheet<void>(
      context: context,
      isScrollControlled: true,
      builder: (_) => TrackActionsSheet(
        track: track,
        hideQueueActions: hideQueueActions,
        onNavigate: onNavigate,
      ),
    );
  }

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final fs = Theme.of(context).extension<FabledSwordTheme>()!;
    final liked = ref.watch(likedIdsProvider).value?.has(LikeKind.track, track.id) ?? false;
    final hidden = ref.watch(myQuarantineProvider).value?.any((r) => r.trackId == track.id) ?? false;
    return SafeArea(
      child: Container(
        color: fs.iron,
        padding: const EdgeInsets.symmetric(vertical: 8),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            if (!hideQueueActions) ...[
              _MenuItem(
                key: const Key('track_actions_play_next'),
                icon: LucideIcons.list_video,
                label: 'Play next',
                onTap: () async {
                  Navigator.pop(context);
                  await ref.read(playerActionsProvider).playNext(track);
                  if (context.mounted) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Added to queue (next)')),
                    );
                  }
                },
              ),
              _MenuItem(
                key: const Key('track_actions_enqueue'),
                icon: LucideIcons.list_music,
                label: 'Add to queue',
                onTap: () async {
                  Navigator.pop(context);
                  await ref.read(playerActionsProvider).enqueue(track);
                  if (context.mounted) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Added to queue')),
                    );
                  }
                },
              ),
              const _Divider(),
            ],
            _MenuItem(
              key: const Key('track_actions_like'),
              icon: LucideIcons.heart,
              label: liked ? 'Unlike' : 'Like',
              onTap: () async {
                Navigator.pop(context);
                await ref.read(likesControllerProvider).toggle(LikeKind.track, track.id);
              },
            ),
            _MenuItem(
              key: const Key('track_actions_add_to_playlist'),
              icon: LucideIcons.list_plus,
              label: 'Add to playlist…',
              onTap: () async {
                Navigator.pop(context);
                final playlistId = await AddToPlaylistSheet.show(context);
                if (playlistId == null || !context.mounted) return;
                try {
                  await ref.read(addToPlaylistActionProvider).call(playlistId, track.id);
                  if (context.mounted) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Added to playlist')),
                    );
                  }
                } catch (e) {
                  if (context.mounted) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text("Couldn't add to playlist: $e")),
                    );
                  }
                }
              },
            ),
            _MenuItem(
              key: const Key('track_actions_start_radio'),
              icon: LucideIcons.radio,
              label: 'Start radio',
              onTap: () async {
                Navigator.pop(context);
                try {
                  await ref.read(playerActionsProvider).startRadio(track.id);
                } catch (e) {
                  if (context.mounted) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text("Couldn't start radio: $e")),
                    );
                  }
                }
              },
            ),
            const _Divider(),
            _MenuItem(
              key: const Key('track_actions_go_to_album'),
              icon: LucideIcons.disc_3,
              label: 'Go to album',
              onTap: () {
                Navigator.pop(context);
                final path = '/albums/${track.albumId}';
                if (onNavigate != null) {
                  onNavigate!(path);
                } else {
                  context.push(path);
                }
              },
            ),
            _MenuItem(
              key: const Key('track_actions_go_to_artist'),
              icon: LucideIcons.user,
              label: 'Go to artist',
              onTap: () {
                Navigator.pop(context);
                final path = '/artists/${track.artistId}';
                if (onNavigate != null) {
                  onNavigate!(path);
                } else {
                  context.push(path);
                }
              },
            ),
            const _Divider(),
            _MenuItem(
              key: const Key('track_actions_hide'),
              icon: hidden ? LucideIcons.eye : LucideIcons.eye_off,
              label: hidden ? 'Unhide' : 'Hide',
              onTap: () async {
                Navigator.pop(context);
                if (hidden) {
                  try {
                    await ref.read(myQuarantineProvider.notifier).unflag(track.id);
                  } catch (e) {
                    if (context.mounted) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text("Couldn't unhide: $e")),
                      );
                    }
                  }
                  return;
                }
                final result = await HideTrackSheet.show(context);
                if (result == null || !context.mounted) return;
                try {
                  await ref
                      .read(myQuarantineProvider.notifier)
                      .flag(track, result.reason, result.notes);
                } catch (e) {
                  if (context.mounted) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text("Couldn't hide: $e")),
                    );
                  }
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}

class _MenuItem extends StatelessWidget {
  const _MenuItem({
    super.key,
    required this.icon,
    required this.label,
    required this.onTap,
  });
  final IconData icon;
  final String label;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    final fs = Theme.of(context).extension<FabledSwordTheme>()!;
    // Transparency-typed Material sits between the outer Container's color
    // paint and the ListTile so ListTile can paint its own ink splashes.
    // Flutter 3.44 promoted the "ListTile inside ColoredBox without Material"
    // warning to a hard assertion.
    return Material(
      type: MaterialType.transparency,
      child: ListTile(
        leading: Icon(icon, color: fs.parchment),
        title: Text(label, style: TextStyle(color: fs.parchment)),
        onTap: onTap,
      ),
    );
  }
}

class _Divider extends StatelessWidget {
  const _Divider();
  @override
  Widget build(BuildContext context) {
    final fs = Theme.of(context).extension<FabledSwordTheme>()!;
    return Divider(height: 1, color: fs.slate);
  }
}

/// Convenience callable for "append a single track to a playlist."
/// Lives here (not in playlists_provider) because it's purely a
/// menu-flow concern — no other caller.
typedef AddToPlaylistAction = Future<void> Function(String playlistId, String trackId);

final addToPlaylistActionProvider = Provider<AddToPlaylistAction>((ref) {
  return (playlistId, trackId) async {
    final db = ref.read(appDbProvider);
    // Optimistic drift write so the playlist detail screen shows the
    // new track instantly. The position is best-effort — append after
    // the current max for this playlist. The server's authoritative
    // position lands on next playlistDetailProvider fetch (SWR), or
    // when the queued mutation replays.
    final maxPos = await (db.selectOnly(db.cachedPlaylistTracks)
          ..addColumns([db.cachedPlaylistTracks.position.max()])
          ..where(db.cachedPlaylistTracks.playlistId.equals(playlistId)))
        .getSingleOrNull();
    final nextPos =
        ((maxPos?.read(db.cachedPlaylistTracks.position.max()) ?? -1) + 1);
    await db.into(db.cachedPlaylistTracks).insertOnConflictUpdate(
          CachedPlaylistTracksCompanion.insert(
            playlistId: playlistId,
            trackId: trackId,
            position: drift.Value(nextPos),
          ),
        );

    try {
      final api = await ref.read(playlistsApiProvider.future);
      await api.appendTracks(playlistId, [trackId]);
    } catch (_) {
      // REST failed — keep the optimistic drift row; queue the call.
      await ref.read(mutationQueueProvider).enqueue(
        MutationKinds.playlistAppend,
        {
          'playlistId': playlistId,
          'trackIds': [trackId],
        },
      );
    }
  };
});
