import 'dart:async';
import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:plezy/widgets/app_icon.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:path/path.dart' as path;

import 'package:provider/provider.dart';

import '../../../models/shader_preset.dart';
import '../../../models/transcode_quality_preset.dart';
import '../../../media/media_version.dart';
import '../../../mpv/mpv.dart';
import '../../../providers/shader_provider.dart';
import '../../../services/file_picker_service.dart';
import '../../../services/settings_service.dart';
import '../../../services/shader_service.dart';
import '../../../services/sleep_timer_service.dart';
import '../../../focus/focusable_wrapper.dart';
import '../../../utils/dialogs.dart';
import '../../../utils/formatters.dart';
import '../../../utils/platform_detector.dart';
import '../../../utils/quality_preset_labels.dart';
import '../../../utils/snackbar_helper.dart';
import '../../../theme/mono_tokens.dart';
import '../../../widgets/focusable_list_tile.dart';
import '../../../widgets/overlay_sheet.dart';
import '../widgets/sync_offset_control.dart';
import '../widgets/sleep_timer_content.dart';
import '../../../i18n/strings.g.dart';
import 'base_video_control_sheet.dart';
import 'version_quality_sheet.dart';

enum _SettingsView { menu, speed, versionQuality, sleep, audioSync, subtitleSync, audioDevice, shader, dvConversion }

class _SettingsMenuItem extends StatelessWidget {
  final IconData icon;
  final String title;
  final String valueText;
  final VoidCallback onTap;
  final bool isHighlighted;
  final bool allowValueOverflow;

  const _SettingsMenuItem({
    required this.icon,
    required this.title,
    required this.valueText,
    required this.onTap,
    this.isHighlighted = false,
    this.allowValueOverflow = false,
  });

  @override
  Widget build(BuildContext context) {
    final t = tokens(context);
    final valueWidget = Text(
      valueText,
      style: TextStyle(color: isHighlighted ? Colors.amber : t.textMuted, fontSize: 14),
      overflow: allowValueOverflow ? TextOverflow.ellipsis : null,
    );

    return FocusableListTile(
      leading: AppIcon(icon, fill: 1, color: isHighlighted ? Colors.amber : t.textMuted),
      title: Text(title),
      trailing: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          if (allowValueOverflow) Flexible(child: valueWidget) else valueWidget,
          const SizedBox(width: 8),
          AppIcon(Symbols.chevron_right_rounded, fill: 1, color: t.textMuted),
        ],
      ),
      onTap: onTap,
    );
  }
}

class _SettingsToggleItem extends StatelessWidget {
  final Pref<bool> pref;
  final IconData icon;
  final String title;
  final FutureOr<void> Function(bool value)? onAfterWrite;

  const _SettingsToggleItem({required this.pref, required this.icon, required this.title, this.onAfterWrite});

  @override
  Widget build(BuildContext context) {
    final settings = SettingsService.instanceOrNull!;
    return ValueListenableBuilder<bool>(
      valueListenable: settings.listenable(pref),
      builder: (context, value, _) {
        Future<void> write(bool next) async {
          await settings.write(pref, next);
          final callback = onAfterWrite;
          if (callback != null) await callback(next);
        }

        return FocusableListTile(
          leading: AppIcon(icon, fill: 1, color: value ? Colors.amber : tokens(context).textMuted),
          title: Text(title),
          trailing: Switch(value: value, onChanged: write, activeThumbColor: Colors.amber),
          onTap: () => write(!value),
        );
      },
    );
  }
}

/// Unified settings sheet for playback adjustments with in-sheet navigation
class VideoSettingsSheet extends StatefulWidget {
  final Player player;
  final int audioSyncOffset;
  final int subtitleSyncOffset;

  /// Whether the user can control playback (false hides speed option in host-only mode).
  final bool canControl;

  /// Whether this is a live TV stream (hides speed settings).
  final bool isLive;

  /// Available media versions and quality controls shown inside playback settings.
  final List<MediaVersion> availableVersions;
  final int selectedMediaIndex;
  final TranscodeQualityPreset selectedQualityPreset;
  final bool serverSupportsTranscoding;
  final int? sourceDurationMs;
  final ValueChanged<int>? onVersionSelected;
  final ValueChanged<TranscodeQualityPreset>? onQualitySelected;

  /// Optional shader service for MPV shader control
  final ShaderService? shaderService;

  /// Called when shader preset changes
  final VoidCallback? onShaderChanged;

  /// Whether ambient lighting is currently enabled
  final bool isAmbientLightingEnabled;

  /// Called to toggle ambient lighting on/off (null if unsupported)
  final VoidCallback? onToggleAmbientLighting;

  /// Called to cancel the video controls auto-hide timer.
  final VoidCallback? onCancelAutoHide;

  /// Called to restart the video controls auto-hide timer.
  final VoidCallback? onStartAutoHide;

  /// Called when a sync offset changes (so the parent can update its state).
  final void Function(String propertyName, int offset)? onSyncOffsetChanged;

  const VideoSettingsSheet({
    super.key,
    required this.player,
    required this.audioSyncOffset,
    required this.subtitleSyncOffset,
    this.canControl = true,
    this.isLive = false,
    this.availableVersions = const [],
    this.selectedMediaIndex = 0,
    this.selectedQualityPreset = TranscodeQualityPreset.original,
    this.serverSupportsTranscoding = false,
    this.sourceDurationMs,
    this.onVersionSelected,
    this.onQualitySelected,
    this.shaderService,
    this.onShaderChanged,
    this.isAmbientLightingEnabled = false,
    this.onToggleAmbientLighting,
    this.onCancelAutoHide,
    this.onStartAutoHide,
    this.onSyncOffsetChanged,
  });

  @override
  State<VideoSettingsSheet> createState() => _VideoSettingsSheetState();
}

class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
  _SettingsView _currentView = _SettingsView.menu;
  late int _audioSyncOffset;
  late int _subtitleSyncOffset;
  String _dvConversionMode = 'auto';

  @override
  void initState() {
    super.initState();
    _audioSyncOffset = widget.audioSyncOffset;
    _subtitleSyncOffset = widget.subtitleSyncOffset;
    _loadDebugDvConversionMode();
  }

  Future<void> _loadDebugDvConversionMode() async {
    if (!kDebugMode || !Platform.isAndroid || widget.player.playerType != 'exoplayer') return;
    final dvConversionMode = await widget.player.getProperty('dv-conversion-mode');
    if (!mounted) return;
    setState(() {
      _dvConversionMode = _normalizeDvConversionMode(dvConversionMode);
    });
  }

  Future<void> _setDebugDvConversionMode(String mode) async {
    await widget.player.setProperty('dv-conversion-mode', mode);
    if (!mounted) return;
    setState(() {
      _dvConversionMode = mode;
    });
    OverlaySheetController.of(context).close();
  }

  void _navigateTo(_SettingsView view) {
    // Sync views open as a compact top bar instead of a sub-view
    if (view == _SettingsView.audioSync || view == _SettingsView.subtitleSync) {
      _openSyncBar(view);
      return;
    }
    setState(() {
      _currentView = view;
    });
    OverlaySheetController.maybeOf(context)?.refocus();
  }

  void _openSyncBar(_SettingsView view) {
    final controller = OverlaySheetController.maybeOf(context);
    if (controller == null) return;

    final isSubtitle = view == _SettingsView.subtitleSync;
    final title = isSubtitle ? t.videoSettings.subtitleSync : t.videoSettings.audioSync;
    final icon = isSubtitle ? Symbols.subtitles_rounded : Symbols.sync_rounded;
    final propertyName = isSubtitle ? 'sub-delay' : 'audio-delay';
    final initialOffset = isSubtitle ? _subtitleSyncOffset : _audioSyncOffset;

    // Created here so we can pass it as initialFocusNode to the overlay sheet,
    // ensuring the slider gets focus when the bar opens. Disposed by _CompactSyncBar.
    final sliderFocusNode = FocusNode(debugLabel: 'SyncSlider');

    // show() with new alignment replaces the current sheet (completing the
    // settings sheet future, which restarts the auto-hide timer via
    // whenComplete in track_chapter_controls). Cancel it again here.
    controller
        .show(
          alignment: Alignment.topCenter,
          constraints: const BoxConstraints(maxHeight: 80, maxWidth: 900),
          initialFocusNode: sliderFocusNode,
          builder: (_) => _CompactSyncBar(
            title: title,
            icon: icon,
            player: widget.player,
            propertyName: propertyName,
            initialOffset: initialOffset,
            sliderFocusNode: sliderFocusNode,
            onOffsetChanged: (offset) async {
              final settings = SettingsService.instanceOrNull!;
              if (isSubtitle) {
                await settings.write(SettingsService.subtitleSyncOffset, offset);
              } else {
                await settings.write(SettingsService.audioSyncOffset, offset);
              }
              widget.onSyncOffsetChanged?.call(propertyName, offset);
            },
          ),
        )
        .whenComplete(() {
          widget.onStartAutoHide?.call();
        });

    // Cancel auto-hide after show() — the previous sheet's whenComplete
    // fires as a microtask and restarts the timer, so schedule our cancel
    // to run after that microtask.
    Future.microtask(() => widget.onCancelAutoHide?.call());
  }

  void _navigateBack() {
    setState(() {
      _currentView = _SettingsView.menu;
    });
    OverlaySheetController.maybeOf(context)?.refocus();
  }

  String _getTitle() {
    switch (_currentView) {
      case _SettingsView.menu:
        return t.videoSettings.playbackSettings;
      case _SettingsView.speed:
        return t.videoSettings.playbackSpeed;
      case _SettingsView.versionQuality:
        return _versionQualityTitle();
      case _SettingsView.sleep:
        return t.videoSettings.sleepTimer;
      case _SettingsView.audioSync:
        return t.videoSettings.audioSync;
      case _SettingsView.subtitleSync:
        return t.videoSettings.subtitleSync;
      case _SettingsView.audioDevice:
        return t.videoSettings.audioOutput;
      case _SettingsView.shader:
        return t.shaders.title;
      case _SettingsView.dvConversion:
        return 'DV Conversion Mode';
    }
  }

  IconData _getIcon() {
    switch (_currentView) {
      case _SettingsView.menu:
        return Symbols.tune_rounded;
      case _SettingsView.speed:
        return Symbols.speed_rounded;
      case _SettingsView.versionQuality:
        return Symbols.art_track;
      case _SettingsView.sleep:
        return Symbols.bedtime_rounded;
      case _SettingsView.audioSync:
        return Symbols.sync_rounded;
      case _SettingsView.subtitleSync:
        return Symbols.subtitles_rounded;
      case _SettingsView.audioDevice:
        return Symbols.speaker_rounded;
      case _SettingsView.shader:
        return Symbols.auto_fix_high_rounded;
      case _SettingsView.dvConversion:
        return Symbols.hdr_strong_rounded;
    }
  }

  String _normalizeDvConversionMode(String? mode) {
    return switch (mode?.trim().toLowerCase()) {
      'disabled' || 'native' => 'disabled',
      'dv81' || 'p8' || 'p7_to_p8' || 'p7-to-p8' => 'dv81',
      'hevc' || 'hevc_strip' || 'p7_to_hevc' || 'p7-to-hevc' => 'hevc_strip',
      _ => 'auto',
    };
  }

  String _formatDvConversionMode(String mode) {
    return switch (_normalizeDvConversionMode(mode)) {
      'disabled' => 'Native / Disabled',
      'dv81' => 'P7 → P8.1',
      'hevc_strip' => 'P7 → HEVC',
      _ => 'Auto',
    };
  }

  String _formatSleepTimer(SleepTimerService sleepTimer) {
    if (!sleepTimer.isActive) return 'Off';
    final remaining = sleepTimer.remainingTime;
    if (remaining == null) return 'Off';
    return 'Active (${formatDurationWithSeconds(remaining)})';
  }

  bool get _hasVersionQuality {
    return (widget.availableVersions.length > 1 || widget.serverSupportsTranscoding) &&
        (widget.onVersionSelected != null || widget.onQualitySelected != null);
  }

  String _versionQualityTitle() {
    return versionQualityPickerTitle(
      showVersions: widget.availableVersions.length > 1,
      showQuality: widget.serverSupportsTranscoding,
    );
  }

  String _versionQualityValueText() {
    final values = <String>[];
    if (widget.availableVersions.length > 1) values.add(_selectedVersionLabel());
    if (widget.serverSupportsTranscoding) values.add(qualityPresetLabel(widget.selectedQualityPreset));
    return values.join(' / ');
  }

  String _selectedVersionLabel() {
    final index = widget.selectedMediaIndex;
    if (index >= 0 && index < widget.availableVersions.length) {
      return widget.availableVersions[index].displayLabel;
    }
    return t.videoControls.versionColumnHeader;
  }

  Widget _buildMenuView() {
    final sleepTimer = SleepTimerService();
    final isDesktop = PlatformDetector.isDesktop(context);

    return ListView(
      children: [
        // Playback Speed - hidden for live TV and when user cannot control playback
        if (widget.canControl && !widget.isLive)
          StreamBuilder<double>(
            stream: widget.player.streams.rate,
            initialData: widget.player.state.rate,
            builder: (context, snapshot) {
              final currentRate = snapshot.data ?? 1.0;
              return _SettingsMenuItem(
                icon: Symbols.speed_rounded,
                title: t.videoSettings.playbackSpeed,
                valueText: formatPlaybackRate(currentRate, normalAtOne: true),
                onTap: () => _navigateTo(_SettingsView.speed),
              );
            },
          ),

        if (_hasVersionQuality)
          _SettingsMenuItem(
            icon: Symbols.art_track,
            title: _versionQualityTitle(),
            valueText: _versionQualityValueText(),
            allowValueOverflow: true,
            onTap: () => _navigateTo(_SettingsView.versionQuality),
          ),

        // Sleep Timer
        ListenableBuilder(
          listenable: sleepTimer,
          builder: (context, _) {
            final isActive = sleepTimer.isActive;
            return _SettingsMenuItem(
              icon: Symbols.bedtime_rounded,
              title: t.videoSettings.sleepTimer,
              valueText: _formatSleepTimer(sleepTimer),
              isHighlighted: isActive,
              onTap: () => _navigateTo(_SettingsView.sleep),
            );
          },
        ),

        // Audio Sync
        _SettingsMenuItem(
          icon: Symbols.sync_rounded,
          title: t.videoSettings.audioSync,
          valueText: formatSyncOffset(_audioSyncOffset.toDouble()),
          isHighlighted: _audioSyncOffset != 0,
          onTap: () => _navigateTo(_SettingsView.audioSync),
        ),

        // Subtitle Sync
        _SettingsMenuItem(
          icon: Symbols.subtitles_rounded,
          title: t.videoSettings.subtitleSync,
          valueText: formatSyncOffset(_subtitleSyncOffset.toDouble()),
          isHighlighted: _subtitleSyncOffset != 0,
          onTap: () => _navigateTo(_SettingsView.subtitleSync),
        ),

        // HDR Toggle (iOS, macOS, and Windows)
        if (Platform.isIOS || Platform.isMacOS || Platform.isWindows)
          _SettingsToggleItem(
            pref: SettingsService.enableHDR,
            icon: Symbols.hdr_strong_rounded,
            title: t.videoSettings.hdr,
            onAfterWrite: (value) => widget.player.setProperty('hdr-enabled', value ? 'yes' : 'no'),
          ),

        // Auto-Play Next Episode Toggle
        _SettingsToggleItem(
          pref: SettingsService.autoPlayNextEpisode,
          icon: Symbols.skip_next_rounded,
          title: t.videoControls.autoPlayNext,
        ),

        // Audio Output Device (Desktop only)
        if (isDesktop)
          StreamBuilder<AudioDevice>(
            stream: widget.player.streams.audioDevice,
            initialData: widget.player.state.audioDevice,
            builder: (context, snapshot) {
              final currentDevice = snapshot.data ?? widget.player.state.audioDevice;
              final deviceLabel = currentDevice.description.isEmpty ? currentDevice.name : currentDevice.description;

              return _SettingsMenuItem(
                icon: Symbols.speaker_rounded,
                title: t.videoSettings.audioOutput,
                valueText: deviceLabel,
                allowValueOverflow: true,
                onTap: () => _navigateTo(_SettingsView.audioDevice),
              );
            },
          ),

        // Audio Passthrough (Desktop only)
        if (isDesktop)
          _SettingsToggleItem(
            pref: SettingsService.audioPassthrough,
            icon: Symbols.surround_sound_rounded,
            title: t.videoSettings.audioPassthrough,
            onAfterWrite: widget.player.setAudioPassthrough,
          ),

        // Audio Normalization (MPV only)
        if (widget.player.playerType == 'mpv')
          _SettingsToggleItem(
            pref: SettingsService.audioNormalization,
            icon: Symbols.graphic_eq_rounded,
            title: t.videoSettings.audioNormalization,
            onAfterWrite: (value) => widget.player.setProperty('af', value ? 'loudnorm=I=-14:TP=-3:LRA=4' : ''),
          ),

        // Shader Preset (MPV only)
        if (widget.shaderService != null && widget.shaderService!.isSupported)
          _SettingsMenuItem(
            icon: Symbols.auto_fix_high_rounded,
            title: t.shaders.title,
            valueText: widget.shaderService!.currentPreset.name,
            isHighlighted: widget.shaderService!.currentPreset.isEnabled,
            onTap: () => _navigateTo(_SettingsView.shader),
          ),

        // Ambient Lighting (MPV only)
        if (widget.onToggleAmbientLighting != null)
          FocusableListTile(
            leading: AppIcon(
              Symbols.blur_on,
              fill: 1,
              color: widget.isAmbientLightingEnabled ? Colors.amber : tokens(context).textMuted,
            ),
            title: Text(t.videoControls.ambientLighting),
            trailing: Switch(
              value: widget.isAmbientLightingEnabled,
              onChanged: (_) {
                widget.onToggleAmbientLighting?.call();
                OverlaySheetController.of(context).close();
              },
              activeThumbColor: Colors.amber,
            ),
            onTap: () {
              widget.onToggleAmbientLighting?.call();
              OverlaySheetController.of(context).close();
            },
          ),

        // Performance Overlay Toggle
        _SettingsToggleItem(
          pref: SettingsService.showPerformanceOverlay,
          icon: Symbols.analytics_rounded,
          title: t.videoSettings.performanceOverlay,
        ),

        if (kDebugMode && Platform.isAndroid && widget.player.playerType == 'exoplayer')
          _SettingsMenuItem(
            icon: Symbols.hdr_strong_rounded,
            title: 'DV Conversion Mode',
            valueText: _formatDvConversionMode(_dvConversionMode),
            isHighlighted: _dvConversionMode != 'auto',
            onTap: () => _navigateTo(_SettingsView.dvConversion),
          ),

        // Debug: Trigger MPV Fallback (Android ExoPlayer only)
        if (kDebugMode && Platform.isAndroid && widget.player.playerType == 'exoplayer')
          FocusableListTile(
            leading: AppIcon(Symbols.swap_horiz_rounded, fill: 1, color: tokens(context).textMuted),
            title: const Text('Trigger MPV Fallback'),
            onTap: () {
              const MethodChannel('com.plezy/exo_player').invokeMethod('triggerFallback');
              OverlaySheetController.of(context).close();
            },
          ),

        if (kDebugMode)
          FocusableListTile(
            leading: AppIcon(Symbols.bug_report_rounded, fill: 1, color: tokens(context).textMuted),
            title: const Text('Simulate HTTP 500 from server'),
            onTap: () {
              final player = widget.player;
              OverlaySheetController.of(context).close();
              if (player is PlayerBase) {
                player.debugSimulateServer500();
              }
            },
          ),
      ],
    );
  }

  Widget _buildDvConversionView() {
    const modes = [
      (value: 'auto', title: 'Auto', subtitle: 'Use device capability detection and normal fallback behavior'),
      (value: 'disabled', title: 'Native / Disabled', subtitle: 'Force native DV7 and suppress DV conversion retry'),
      (value: 'dv81', title: 'P7 → P8.1', subtitle: 'Force inline RPU conversion to Dolby Vision profile 8.1'),
      (value: 'hevc_strip', title: 'P7 → HEVC', subtitle: 'Strip Dolby Vision RPU/EL layers and present plain HEVC'),
    ];
    final primary = Theme.of(context).colorScheme.primary;

    return ListView(
      children: [
        for (final mode in modes)
          FocusableListTile(
            title: Text(mode.title, style: TextStyle(color: _dvConversionMode == mode.value ? primary : null)),
            subtitle: Text(mode.subtitle, style: TextStyle(color: tokens(context).textMuted, fontSize: 12)),
            trailing: _dvConversionMode == mode.value ? AppIcon(Symbols.check_rounded, fill: 1, color: primary) : null,
            onTap: () => _setDebugDvConversionMode(mode.value),
          ),
      ],
    );
  }

  Widget _buildSpeedView() {
    return StreamBuilder<double>(
      stream: widget.player.streams.rate,
      initialData: widget.player.state.rate,
      builder: (context, snapshot) {
        final currentRate = snapshot.data ?? 1.0;
        final speeds = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0];

        return ListView.builder(
          itemCount: speeds.length,
          itemBuilder: (context, index) {
            final speed = speeds[index];
            final isSelected = (currentRate - speed).abs() < 0.01;
            final label = formatPlaybackRate(speed, normalAtOne: true);

            final primary = Theme.of(context).colorScheme.primary;
            return FocusableListTile(
              title: Text(label, style: TextStyle(color: isSelected ? primary : null)),
              trailing: isSelected ? AppIcon(Symbols.check_rounded, fill: 1, color: primary) : null,
              onTap: () async {
                await widget.player.setRate(speed);
                // Save as default playback speed
                await SettingsService.instanceOrNull!.write(SettingsService.defaultPlaybackSpeed, speed);
                if (context.mounted) {
                  OverlaySheetController.of(context).close(); // Close sheet after selection
                }
              },
            );
          },
        );
      },
    );
  }

  Widget _buildSleepView() {
    final sleepTimer = SleepTimerService();

    return SleepTimerContent(
      player: widget.player,
      sleepTimer: sleepTimer,
      onCancel: () => OverlaySheetController.of(context).close(),
    );
  }

  Widget _buildVersionQualityView() {
    return VersionQualityPicker(
      availableVersions: widget.availableVersions,
      selectedMediaIndex: widget.selectedMediaIndex,
      selectedQualityPreset: widget.selectedQualityPreset,
      serverSupportsTranscoding: widget.serverSupportsTranscoding,
      sourceDurationMs: widget.sourceDurationMs,
      onVersionSelected: (index) => widget.onVersionSelected?.call(index),
      onQualitySelected: (preset) => widget.onQualitySelected?.call(preset),
    );
  }

  /// Extract the audio backend name from a device name (e.g. "coreaudio" from "coreaudio/BuiltIn").
  static String _audioBackend(String name) {
    final slash = name.indexOf('/');
    return slash > 0 ? name.substring(0, slash) : name;
  }

  /// Pretty-print a backend identifier.
  static String _formatBackend(String backend) {
    const labels = {
      'coreaudio': 'CoreAudio',
      'avfoundation': 'AVFoundation',
      'wasapi': 'WASAPI',
      'pulse': 'PulseAudio',
      'pipewire': 'PipeWire',
      'alsa': 'ALSA',
      'jack': 'JACK',
      'oss': 'OSS',
    };
    return labels[backend] ?? backend;
  }

  Widget _buildAudioDeviceView() {
    return StreamBuilder<List<AudioDevice>>(
      stream: widget.player.streams.audioDevices,
      initialData: widget.player.state.audioDevices,
      builder: (context, snapshot) {
        final devices = snapshot.data ?? [];

        return StreamBuilder<AudioDevice>(
          stream: widget.player.streams.audioDevice,
          initialData: widget.player.state.audioDevice,
          builder: (context, selectedSnapshot) {
            final currentDevice = selectedSnapshot.data ?? widget.player.state.audioDevice;

            // Check for duplicate descriptions (same physical device across multiple backends).
            final descCounts = <String, int>{};
            for (final d in devices) {
              final desc = d.description.isEmpty ? d.name : d.description;
              descCounts[desc] = (descCounts[desc] ?? 0) + 1;
            }
            final hasDuplicates = descCounts.values.any((c) => c > 1);

            if (!hasDuplicates) {
              return _buildFlatDeviceList(devices, currentDevice);
            }

            final ungrouped = <AudioDevice>[];
            final groups = <String, List<AudioDevice>>{};
            for (final d in devices) {
              final backend = _audioBackend(d.name);
              if (!d.name.contains('/')) {
                ungrouped.add(d);
              } else {
                (groups[backend] ??= []).add(d);
              }
            }

            return ListView(
              children: [
                for (final d in ungrouped) _buildDeviceTile(d, currentDevice),
                for (final entry in groups.entries) ...[
                  Padding(
                    padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
                    child: Text(
                      _formatBackend(entry.key),
                      style: TextStyle(color: tokens(context).textMuted, fontSize: 12, fontWeight: FontWeight.w600),
                    ),
                  ),
                  for (final d in entry.value) _buildDeviceTile(d, currentDevice),
                ],
              ],
            );
          },
        );
      },
    );
  }

  Widget _buildFlatDeviceList(List<AudioDevice> devices, AudioDevice currentDevice) {
    return ListView.builder(
      itemCount: devices.length,
      itemBuilder: (context, index) => _buildDeviceTile(devices[index], currentDevice),
    );
  }

  Widget _buildDeviceTile(AudioDevice device, AudioDevice currentDevice) {
    final isSelected = device.name == currentDevice.name;
    final label = device.description.isEmpty ? device.name : device.description;

    final primary = Theme.of(context).colorScheme.primary;
    return FocusableListTile(
      title: Text(label, style: TextStyle(color: isSelected ? primary : null)),
      trailing: isSelected ? AppIcon(Symbols.check_rounded, fill: 1, color: primary) : null,
      onTap: () {
        widget.player.setAudioDevice(device);
        OverlaySheetController.of(context).close();
      },
    );
  }

  Widget _buildShaderView() {
    if (widget.shaderService == null) return const SizedBox.shrink();

    return Consumer<ShaderProvider>(
      builder: (context, shaderProvider, _) {
        final currentPreset = widget.shaderService!.currentPreset;
        final presets = shaderProvider.allPresets;

        // +1 for the import button at the end
        return ListView.builder(
          itemCount: presets.length + 1,
          itemBuilder: (context, index) {
            if (index == presets.length) {
              return FocusableListTile(
                leading: AppIcon(Symbols.add_rounded, fill: 1, color: tokens(context).textMuted),
                title: Text(t.shaders.importShader),
                onTap: () => _importCustomShader(shaderProvider),
              );
            }

            final preset = presets[index];
            final isSelected = preset.id == currentPreset.id;
            final isCustom = preset.type == ShaderPresetType.custom;

            return FocusableListTile(
              title: Text(preset.name, style: TextStyle(color: isSelected ? Colors.amber : null)),
              subtitle: _getShaderSubtitle(preset) != null
                  ? Text(_getShaderSubtitle(preset)!, style: TextStyle(color: tokens(context).textMuted, fontSize: 12))
                  : null,
              trailing: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  if (isSelected) const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.amber),
                  if (isCustom) ...[
                    if (isSelected) const SizedBox(width: 8),
                    GestureDetector(
                      onTap: () => _deleteCustomShader(shaderProvider, preset),
                      child: AppIcon(Symbols.delete_rounded, fill: 1, color: tokens(context).textMuted, size: 20),
                    ),
                  ],
                ],
              ),
              onTap: () async {
                // Disable ambient lighting when selecting a shader
                if (preset.type != ShaderPresetType.none && widget.isAmbientLightingEnabled) {
                  widget.onToggleAmbientLighting?.call();
                }
                await widget.shaderService!.applyPreset(preset);
                await shaderProvider.setPreset(preset);
                if (!context.mounted) return;
                widget.onShaderChanged?.call();
                OverlaySheetController.of(context).close();
              },
            );
          },
        );
      },
    );
  }

  Future<void> _importCustomShader(ShaderProvider shaderProvider) async {
    final result = await FilePickerService.instance.pickFiles(type: FileType.custom, allowedExtensions: ['glsl']);

    if (result == null || result.files.isEmpty || !mounted) return;

    final filePath = result.files.first.path;
    if (filePath == null) return;

    try {
      final displayName = path.basenameWithoutExtension(filePath);
      final preset = await shaderProvider.importCustomShader(filePath, displayName);

      if (widget.shaderService != null && mounted) {
        if (preset.type != ShaderPresetType.none && widget.isAmbientLightingEnabled) {
          widget.onToggleAmbientLighting?.call();
        }
        await widget.shaderService!.applyPreset(preset);
        await shaderProvider.setPreset(preset);
        if (!mounted) return;
        widget.onShaderChanged?.call();
      }

      if (mounted) showSuccessSnackBar(context, t.shaders.shaderImported);
    } catch (_) {
      if (mounted) showErrorSnackBar(context, t.shaders.shaderImportFailed);
    }
  }

  Future<void> _deleteCustomShader(ShaderProvider shaderProvider, ShaderPreset preset) async {
    final confirmed = await showDeleteConfirmation(
      context,
      title: t.shaders.deleteShader,
      message: t.shaders.deleteShaderConfirm(name: preset.name),
    );
    if (!confirmed || !mounted) return;

    // If the deleted shader is active, clear it from the player first
    if (widget.shaderService!.currentPreset.id == preset.id) {
      await widget.shaderService!.applyPreset(ShaderPreset.none);
      if (mounted) widget.onShaderChanged?.call();
    }

    await shaderProvider.deleteCustomShader(preset);
  }

  String? _getShaderSubtitle(ShaderPreset preset) {
    switch (preset.type) {
      case ShaderPresetType.none:
        return t.shaders.noShaderDescription;
      case ShaderPresetType.nvscaler:
        return t.shaders.nvscalerDescription;
      case ShaderPresetType.artcnn:
        if (preset.artcnnConfig != null) {
          final variant = switch (preset.artcnnConfig!.variant) {
            ArtCNNVariant.neutral => t.shaders.artcnnVariantNeutral,
            ArtCNNVariant.denoise => t.shaders.artcnnVariantDenoise,
            ArtCNNVariant.denoiseSharpen => t.shaders.artcnnVariantDenoiseSharpen,
          };
          return '${preset.artcnnModelDisplayName} - $variant';
        }
        return null;
      case ShaderPresetType.anime4k:
        if (preset.anime4kConfig != null) {
          final quality = preset.anime4kConfig!.quality == Anime4KQuality.fast
              ? t.shaders.qualityFast
              : t.shaders.qualityHQ;
          final mode = preset.modeDisplayName;
          return '$quality - ${t.shaders.mode} $mode';
        }
        return null;
      case ShaderPresetType.custom:
        return t.shaders.customShaderDescription;
    }
  }

  @override
  Widget build(BuildContext context) {
    final sleepTimer = SleepTimerService();
    final isShaderActive = widget.shaderService != null && widget.shaderService!.currentPreset.isEnabled;
    final isIconActive =
        _currentView == _SettingsView.menu &&
        (sleepTimer.isActive || _audioSyncOffset != 0 || _subtitleSyncOffset != 0 || isShaderActive);

    return BaseVideoControlSheet(
      title: _getTitle(),
      icon: _getIcon(),
      iconColor: () {
        if (isIconActive) return Colors.amber;
        if (_currentView == _SettingsView.shader && isShaderActive) return Colors.amber;
        return null;
      }(),
      onBack: _currentView != _SettingsView.menu ? _navigateBack : null,
      child: () {
        switch (_currentView) {
          case _SettingsView.menu:
            return _buildMenuView();
          case _SettingsView.speed:
            return _buildSpeedView();
          case _SettingsView.versionQuality:
            return _buildVersionQualityView();
          case _SettingsView.sleep:
            return _buildSleepView();
          case _SettingsView.audioSync:
          case _SettingsView.subtitleSync:
            return _buildMenuView(); // Sync views open as top bars, fallback to menu
          case _SettingsView.audioDevice:
            return _buildAudioDeviceView();
          case _SettingsView.shader:
            return _buildShaderView();
          case _SettingsView.dvConversion:
            return _buildDvConversionView();
        }
      }(),
    );
  }
}

/// Compact sync bar shown at the top of the screen so subtitles remain visible.
class _CompactSyncBar extends StatefulWidget {
  final String title;
  final IconData icon;
  final Player player;
  final String propertyName;
  final int initialOffset;
  final Future<void> Function(int offset) onOffsetChanged;
  final FocusNode sliderFocusNode;

  const _CompactSyncBar({
    required this.title,
    required this.icon,
    required this.player,
    required this.propertyName,
    required this.initialOffset,
    required this.onOffsetChanged,
    required this.sliderFocusNode,
  });

  @override
  State<_CompactSyncBar> createState() => _CompactSyncBarState();
}

class _CompactSyncBarState extends State<_CompactSyncBar> {
  final _resetFocusNode = FocusNode(debugLabel: 'SyncResetButton');
  final _closeFocusNode = FocusNode(debugLabel: 'SyncCloseButton');

  @override
  void dispose() {
    widget.sliderFocusNode.dispose();
    _resetFocusNode.dispose();
    _closeFocusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        const SizedBox(width: 16),
        AppIcon(widget.icon, fill: 1, color: tokens(context).textMuted, size: 20),
        const SizedBox(width: 8),
        Text(widget.title, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
        Expanded(
          child: SyncOffsetControl(
            player: widget.player,
            propertyName: widget.propertyName,
            initialOffset: widget.initialOffset,
            labelText: widget.title,
            onOffsetChanged: widget.onOffsetChanged,
            compact: true,
            sliderFocusNode: widget.sliderFocusNode,
            resetFocusNode: _resetFocusNode,
            closeFocusNode: _closeFocusNode,
          ),
        ),
        const SizedBox(width: 8),
        FocusableWrapper(
          focusNode: _closeFocusNode,
          onSelect: () => OverlaySheetController.of(context).close(),
          onNavigateLeft: () => _resetFocusNode.requestFocus(),
          borderRadius: 18,
          autoScroll: false,
          useBackgroundFocus: true,
          child: GestureDetector(
            onTap: () => OverlaySheetController.of(context).close(),
            child: Container(
              width: 36,
              height: 36,
              alignment: Alignment.center,
              child: AppIcon(Symbols.close_rounded, fill: 1, color: tokens(context).textMuted, size: 22),
            ),
          ),
        ),
        const SizedBox(width: 12),
      ],
    );
  }
}
