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 '../models/playlist.dart';
import '../models/track.dart';
import '../player/player_provider.dart';
import '../shared/live_events_provider.dart';
import '../shared/widgets/track_actions/track_actions_button.dart';
import '../theme/theme_extension.dart';
import 'playlists_provider.dart';

class PlaylistDetailScreen extends ConsumerWidget {
  const PlaylistDetailScreen({required this.id, this.seed, super.key});
  final String id;

  /// Optional Playlist passed via go_router extra so the header
  /// (title, cover, track count, play/download CTAs) can render
  /// before the full detail fetch resolves. Same pattern as album
  /// + artist nav hydration.
  final Playlist? seed;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final fs = Theme.of(context).extension<FabledSwordTheme>()!;
    // #402 wire-up: invalidate the detail provider on playlist.updated /
    // .tracks_changed events whose payload matches the visible id. On
    // .deleted matching this id, navigate back so the user isn't left
    // staring at a gone-from-server playlist.
    ref.listen<AsyncValue<LiveEvent>>(liveEventsProvider, (_, next) {
      final e = next.asData?.value;
      if (e == null) return;
      final eventPlaylistId = e.data['playlist_id'] as String?;
      if (eventPlaylistId != id) return;
      switch (e.kind) {
        case 'playlist.updated':
        case 'playlist.tracks_changed':
          ref.invalidate(playlistDetailProvider(id));
        case 'playlist.deleted':
          if (context.mounted) context.pop();
      }
    });
    final detail = ref.watch(playlistDetailProvider(id));

    // Resolve the best playlist info available for the AppBar title.
    final livePlaylist = detail.value?.playlist;
    final headerName = (livePlaylist != null && livePlaylist.name.isNotEmpty)
        ? livePlaylist.name
        : (seed?.name ?? '');

    return Scaffold(
      backgroundColor: fs.obsidian,
      appBar: AppBar(
        backgroundColor: fs.obsidian,
        elevation: 0,
        leading: IconButton(
          icon: Icon(LucideIcons.arrow_left, color: fs.parchment),
          onPressed: () => context.pop(),
        ),
        title: headerName.isEmpty
            ? const SizedBox.shrink()
            : Text(
                headerName,
                style: TextStyle(color: fs.parchment),
                overflow: TextOverflow.ellipsis,
              ),
      ),
      // AnimatedSwitcher between the skeleton body and the real body
      // smooths the cold-visit moment when bulk detail lands. 220ms
      // matches the per-tile reveal feel used on home / liked tabs.
      body: AnimatedSwitcher(
        duration: const Duration(milliseconds: 220),
        switchInCurve: Curves.easeOut,
        child: detail.when(
          loading: () => _SkeletonBody(
            key: const ValueKey('skeleton'),
            seed: seed,
          ),
          error: (e, _) => Center(
            key: const ValueKey('error'),
            child: Text('$e', style: TextStyle(color: fs.error)),
          ),
          data: (d) => _Body(key: const ValueKey('body'), detail: d),
        ),
      ),
    );
  }
}

/// Cold-visit body: header from the seed (if any) + N skeleton rows.
/// N comes from the seed's trackCount so the row count matches the
/// real list when it lands — no layout jump on swap. Without a seed
/// (deep link straight to a playlist with no prior cache) the
/// skeleton renders a small default and grows when real data arrives.
class _SkeletonBody extends StatelessWidget {
  const _SkeletonBody({super.key, this.seed});
  final Playlist? seed;

  static const _defaultSkeletonCount = 8;

  @override
  Widget build(BuildContext context) {
    final fs = Theme.of(context).extension<FabledSwordTheme>()!;
    final count = seed?.trackCount ?? _defaultSkeletonCount;
    return ListView.builder(
      itemCount: count + 1, // +1 for header
      itemBuilder: (ctx, i) {
        if (i == 0) {
          return Padding(
            padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (seed != null && seed!.description.isNotEmpty)
                  Padding(
                    padding: const EdgeInsets.only(bottom: 12),
                    child: Text(
                      seed!.description,
                      style: TextStyle(color: fs.ash, fontSize: 13),
                    ),
                  ),
                Text(
                  seed == null
                      ? 'Loading…'
                      : '${seed!.trackCount} ${seed!.trackCount == 1 ? "track" : "tracks"}',
                  style: TextStyle(color: fs.ash, fontSize: 12),
                ),
              ],
            ),
          );
        }
        return const _PlaylistTrackSkeleton();
      },
    );
  }
}

/// Skeleton matched to _PlaylistTrackRow's layout (no cover image —
/// playlist rows are text-only). Two text-shaped placeholders for
/// title + secondary line, plus a duration block on the right.
class _PlaylistTrackSkeleton extends StatelessWidget {
  const _PlaylistTrackSkeleton();

  @override
  Widget build(BuildContext context) {
    final fs = Theme.of(context).extension<FabledSwordTheme>()!;
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                Container(width: 200, height: 14, color: fs.slate),
                const SizedBox(height: 6),
                Container(width: 140, height: 12, color: fs.slate),
              ],
            ),
          ),
          const SizedBox(width: 16),
          Container(width: 32, height: 12, color: fs.slate),
        ],
      ),
    );
  }
}

class _Body extends ConsumerWidget {
  const _Body({super.key, required this.detail});
  final PlaylistDetail detail;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final fs = Theme.of(context).extension<FabledSwordTheme>()!;
    final tracks = detail.tracks;
    final playable = tracks.where((t) => t.isAvailable).toList();

    return RefreshIndicator(
      onRefresh: () async =>
          ref.refresh(playlistDetailProvider(detail.playlist.id).future),
      child: ListView.builder(
        // Header + (track rows | empty hint).
        itemCount: tracks.isEmpty ? 2 : tracks.length + 1,
        itemBuilder: (ctx, i) {
          if (i == 0) return _Header(detail: detail, playable: playable);
          if (tracks.isEmpty) {
            return Padding(
              padding: const EdgeInsets.all(24),
              child: Center(
                child: Text(
                  'No tracks yet. Add some via the "Add to playlist…" entry on any track row.',
                  style: TextStyle(color: fs.ash),
                  textAlign: TextAlign.center,
                ),
              ),
            );
          }
          final t = tracks[i - 1];
          return _PlaylistTrackRow(
            row: t,
            onTap: t.isAvailable
                ? () {
                    final ref = ProviderScope.containerOf(ctx);
                    final liveTrack = _toTrackRef(t);
                    final playableRefs =
                        playable.map(_toTrackRef).toList(growable: false);
                    final startIdx = playable.indexWhere((p) => p.trackId == t.trackId);
                    ref.read(playerActionsProvider).playTracks(
                          playableRefs,
                          initialIndex: startIdx >= 0 ? startIdx : 0,
                          source: detail.playlist.refreshable
                              ? detail.playlist.systemVariant
                              : null,
                        );
                    // Keep liveTrack referenced to avoid an unused-variable
                    // warning while we leave hooks for menu wiring later.
                    assert(liveTrack.id == t.trackId);
                  }
                : null,
          );
        },
      ),
    );
  }
}

TrackRef _toTrackRef(PlaylistTrack t) => TrackRef(
      id: t.trackId ?? '',
      title: t.title,
      albumId: t.albumId ?? '',
      albumTitle: t.albumTitle,
      artistId: t.artistId ?? '',
      artistName: t.artistName,
      durationSec: t.durationSec,
      streamUrl: t.streamUrl ?? '',
    );

class _Header extends ConsumerWidget {
  const _Header({required this.detail, required this.playable});
  final PlaylistDetail detail;
  final List<PlaylistTrack> playable;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final fs = Theme.of(context).extension<FabledSwordTheme>()!;
    final p = detail.playlist;
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
      child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
        if (p.description.isNotEmpty)
          Padding(
            padding: const EdgeInsets.only(bottom: 12),
            child: Text(
              p.description,
              style: TextStyle(color: fs.ash, fontSize: 13),
            ),
          ),
        Row(children: [
          Text(
            '${p.trackCount} ${p.trackCount == 1 ? "track" : "tracks"}',
            style: TextStyle(color: fs.ash, fontSize: 12),
          ),
          const Spacer(),
          if (playable.isNotEmpty) ...[
            if (p.refreshable) ...[
              OutlinedButton.icon(
                key: const Key('regenerate_playlist_button'),
                onPressed: () => _regenerate(context, ref),
                icon: const Icon(LucideIcons.refresh_cw, size: 16),
                label: const Text('Regenerate'),
              ),
              const SizedBox(width: 8),
            ],
            FilledButton.icon(
              onPressed: () {
                final refs = playable.map(_toTrackRef).toList(growable: false);
                ref.read(playerActionsProvider).playTracks(
                      refs,
                      source: p.refreshable ? p.systemVariant : null,
                    );
              },
              icon: const Icon(LucideIcons.play),
              label: const Text('Play'),
              style: FilledButton.styleFrom(
                backgroundColor: fs.accent,
                foregroundColor: fs.parchment,
              ),
            ),
          ],
        ]),
      ]),
    );
  }

  /// Forces a server-side rebuild of this system playlist. The
  /// rebuild rotates the playlist UUID, so the old detail route now
  /// 404s — rebind by pushReplacement-ing to the new id. The
  /// aggregate list is invalidated too so the home row's tile points
  /// at the fresh UUID. ScaffoldMessenger + router captured before
  /// the await so we don't touch a stale BuildContext after.
  Future<void> _regenerate(BuildContext context, WidgetRef ref) async {
    final messenger = ScaffoldMessenger.of(context);
    final router = GoRouter.of(context);
    final p = detail.playlist;
    try {
      final api = await ref.read(playlistsApiProvider.future);
      final newId = await api.refreshSystem(p.systemVariant!);
      ref.invalidate(playlistsListProvider);
      if (newId == null) {
        messenger.showSnackBar(const SnackBar(
          content: Text('Nothing to build yet — library is empty.'),
        ));
        return;
      }
      messenger.showSnackBar(
        SnackBar(content: Text('${p.name} regenerated')),
      );
      router.pushReplacement('/playlists/$newId');
    } catch (e) {
      messenger.showSnackBar(
        SnackBar(content: Text("Couldn't regenerate: $e")),
      );
    }
  }
}

class _PlaylistTrackRow extends ConsumerWidget {
  const _PlaylistTrackRow({required this.row, required this.onTap});
  final PlaylistTrack row;
  final VoidCallback? onTap;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final fs = Theme.of(context).extension<FabledSwordTheme>()!;
    final mins = (row.durationSec ~/ 60).toString().padLeft(2, '0');
    final secs = (row.durationSec % 60).toString().padLeft(2, '0');
    // "Now playing" highlight — matches _QueueRow and TrackRow so the
    // user sees which playlist row is current without reading the
    // player bar. Unavailable rows never match.
    final currentId = ref.watch(mediaItemProvider).value?.id;
    final isCurrent = row.isAvailable &&
        currentId != null &&
        currentId == row.trackId;
    final baseColor = row.isAvailable ? fs.parchment : fs.ash;
    final titleColor = isCurrent ? fs.accent : baseColor;
    return Container(
      decoration: BoxDecoration(
        color: isCurrent ? fs.iron : null,
        border: isCurrent
            ? Border(left: BorderSide(color: fs.accent, width: 2))
            : null,
      ),
      child: InkWell(
        onTap: onTap,
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
          child: Row(children: [
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    row.title,
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(
                      color: titleColor,
                      fontSize: 14,
                      fontWeight:
                          isCurrent ? FontWeight.w500 : FontWeight.w400,
                      decoration: row.isAvailable
                          ? null
                          : TextDecoration.lineThrough,
                    ),
                  ),
                  Text(
                    '${row.artistName} · ${row.albumTitle}',
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(color: fs.ash, fontSize: 12),
                  ),
                ],
              ),
            ),
            Text('$mins:$secs', style: TextStyle(color: fs.ash, fontSize: 12)),
            if (row.trackId != null)
              TrackActionsButton(track: _toTrackRef(row)),
          ]),
        ),
      ),
    );
  }
}
