import 'dart:async';
import 'dart:io' show Platform;

import 'package:flutter/material.dart';
import 'package:plezy/widgets/app_icon.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';

import '../focus/dpad_navigator.dart';
import '../focus/focus_memory_tracker.dart';
import '../media/media_library.dart';
import '../mixins/mounted_set_state_mixin.dart';
import '../navigation/navigation_tabs.dart';
import '../providers/hidden_libraries_provider.dart';
import '../providers/libraries_provider.dart';
import '../services/settings_service.dart';
import '../utils/platform_detector.dart';
import '../utils/library_grouping.dart';
import '../providers/multi_server_provider.dart';
import '../services/fullscreen_state_manager.dart';
import '../theme/mono_tokens.dart';
import '../widgets/backend_badge.dart';
import '../i18n/strings.g.dart';

enum _LibraryNavSection { visible, hidden }

sealed class _LibraryNavRow {
  final _LibraryNavSection section;

  const _LibraryNavRow({required this.section});
}

final class _LibraryServerHeaderRow extends _LibraryNavRow {
  final String serverId;
  final String serverName;

  const _LibraryServerHeaderRow({required super.section, required this.serverId, required this.serverName});
}

final class _LibraryItemRow extends _LibraryNavRow {
  final MediaLibrary library;
  final bool showServerName;

  const _LibraryItemRow({required super.section, required this.library, this.showServerName = false});
}

/// Reusable navigation rail item widget that handles focus, selection, and interaction
class NavigationRailItem extends StatelessWidget {
  final IconData icon;
  final IconData? selectedIcon;
  final Widget label;
  final bool isSelected;
  final bool isFocused;
  final bool isCollapsed;
  final bool useSimpleLayout;
  final VoidCallback onTap;
  final FocusNode focusNode;
  final bool autofocus;
  final BorderRadius borderRadius;
  final double iconSize;

  /// Called when RIGHT arrow is pressed to navigate to content area.
  final VoidCallback? onNavigateRight;

  const NavigationRailItem({
    super.key,
    required this.icon,
    this.selectedIcon,
    required this.label,
    required this.isSelected,
    required this.isFocused,
    this.isCollapsed = false,
    this.useSimpleLayout = false,
    required this.onTap,
    required this.focusNode,
    this.autofocus = false,
    this.borderRadius = const BorderRadius.all(Radius.circular(12)),
    this.iconSize = 22,
    this.onNavigateRight,
  });

  @override
  Widget build(BuildContext context) {
    final t = tokens(context);

    return Focus(
      focusNode: focusNode,
      autofocus: autofocus,
      onKeyEvent: (node, event) {
        if (event is! KeyDownEvent) return KeyEventResult.ignored;
        if (event.logicalKey.isSelectKey) {
          onTap();
          return KeyEventResult.handled;
        }
        if (event.logicalKey == LogicalKeyboardKey.arrowRight && onNavigateRight != null) {
          onNavigateRight!();
          return KeyEventResult.handled;
        }
        return KeyEventResult.ignored;
      },
      child: Material(
        color: Colors.transparent,
        child: InkWell(
          canRequestFocus: false,
          onTap: onTap,
          borderRadius: borderRadius,
          child: Container(
            decoration: BoxDecoration(
              color: () {
                if (isSelected && isFocused) return t.text.withValues(alpha: 0.15);
                if (isSelected) return t.text.withValues(alpha: 0.1);
                if (isFocused) return t.text.withValues(alpha: 0.12);
                return null;
              }(),
              borderRadius: borderRadius,
            ),
            clipBehavior: Clip.hardEdge,
            child: UnconstrainedBox(
              alignment: Alignment.centerLeft,
              constrainedAxis: Axis.vertical,
              clipBehavior: Clip.hardEdge,
              child: SizedBox(
                width: SideNavigationRailState.expandedWidth - 24,
                child: Padding(
                  padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 17),
                  child: Row(
                    children: [
                      AppIcon(
                        isSelected && selectedIcon != null ? selectedIcon! : icon,
                        fill: 1,
                        size: iconSize,
                        color: isSelected ? t.text : t.textMuted,
                      ),
                      const SizedBox(width: 11),
                      Expanded(
                        child: () {
                          if (useSimpleLayout) return label;
                          final opacity = isCollapsed ? 0.0 : 1.0;
                          return AnimatedOpacity(opacity: opacity, duration: t.fast, child: label);
                        }(),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

/// Side navigation rail for Desktop and Android TV platforms
class SideNavigationRail extends StatefulWidget {
  final NavigationTabId selectedTab;
  final String? selectedLibraryKey;
  final bool isOfflineMode;
  final bool isSidebarFocused;
  final bool alwaysExpanded;
  final bool isReconnecting;
  final ValueChanged<NavigationTabId> onDestinationSelected;
  final ValueChanged<String> onLibrarySelected;

  /// Called when RIGHT arrow is pressed to navigate to content without selecting.
  final VoidCallback? onNavigateToContent;

  /// Called when the user taps the reconnect button in offline mode.
  final VoidCallback? onReconnect;

  const SideNavigationRail({
    super.key,
    required this.selectedTab,
    this.selectedLibraryKey,
    this.isOfflineMode = false,
    this.isSidebarFocused = false,
    this.alwaysExpanded = false,
    this.isReconnecting = false,
    required this.onDestinationSelected,
    required this.onLibrarySelected,
    this.onNavigateToContent,
    this.onReconnect,
  });

  @override
  State<SideNavigationRail> createState() => SideNavigationRailState();
}

class SideNavigationRailState extends State<SideNavigationRail> with MountedSetStateMixin {
  bool _librariesExpanded = true;

  bool _isHovered = false;
  bool _isTouchExpanded = false;
  Timer? _collapseTimer;
  static const double collapsedWidth = 80.0;
  static const double expandedWidth = 220.0;
  static const Duration _collapseDelay = Duration(milliseconds: 150);

  static const _kHome = 'home';
  static const _kLibraries = 'libraries';
  static const _kSearch = 'search';
  static const _kDownloads = 'downloads';
  static const _kSettings = 'settings';
  static const _kReconnect = 'reconnect';
  static const _kFullscreen = 'fullscreen';
  static const _kHiddenLibraries = 'hiddenLibraries';
  static const _kServerHeaderPrefix = 'serverHeader';
  static const _kLibraryItemPrefix = 'library';

  bool _hiddenLibrariesExpanded = false;
  final Set<String> _collapsedServerGroupKeys = {};

  // Unified focus state tracker for all nav items (main + libraries)
  late final FocusMemoryTracker _focusTracker;

  /// Whether the sidebar should be expanded (always, hover, or focus)
  bool get _shouldExpand => widget.alwaysExpanded || _isHovered || _isTouchExpanded || widget.isSidebarFocused;

  /// macOS has the system green button; mobile/TV have no OS fullscreen toggle.
  bool get _showFullscreenToggle => Platform.isWindows || Platform.isLinux;

  @override
  void initState() {
    super.initState();
    _focusTracker = FocusMemoryTracker(
      onFocusChanged: () {
        // ignore: no-empty-block - setState triggers rebuild to update focus styling
        setStateIfMounted(() {});
      },
      debugLabelPrefix: 'nav',
    );
  }

  @override
  void dispose() {
    _collapseTimer?.cancel();
    _focusTracker.dispose();
    super.dispose();
  }

  @override
  void didUpdateWidget(covariant SideNavigationRail oldWidget) {
    super.didUpdateWidget(oldWidget);
    // Auto-collapse after navigation (selection changed)
    if (oldWidget.selectedTab != widget.selectedTab || oldWidget.selectedLibraryKey != widget.selectedLibraryKey) {
      _isTouchExpanded = false;
    }
  }

  void _onHoverEnter() {
    _collapseTimer?.cancel();
    _isTouchExpanded = false; // Mouse takes over
    if (!_isHovered) {
      setState(() => _isHovered = true);
    }
  }

  void _onHoverExit() {
    _collapseTimer?.cancel();
    _collapseTimer = Timer(_collapseDelay, () {
      if (mounted && _isHovered) {
        setState(() => _isHovered = false);
      }
    });
  }

  /// The key of the last focused sidebar item (for pre-capture before focus shifts).
  String? get lastFocusedKey => _focusTracker.lastFocusedKey;

  /// Focus the last focused nav item, or Home as fallback.
  /// If [targetKey] is provided, try it first (used when the caller captured
  /// the intended target before a focus-scope switch overwrote it).
  void focusActiveItem({String? targetKey}) {
    if (targetKey != null) {
      final node = _focusTracker.nodeFor(targetKey);
      if (node != null) {
        node.requestFocus();
        return;
      }
    }
    _focusTracker.restoreFocus(fallbackKey: _kHome);
  }

  String _serverHeaderFocusKey(_LibraryNavSection section, String serverId) =>
      '$_kServerHeaderPrefix:${section.name}:$serverId';

  String _libraryItemFocusKey(_LibraryNavSection section, MediaLibrary library) =>
      '$_kLibraryItemPrefix:${section.name}:${library.globalKey}';

  String _serverGroupStateKey(_LibraryNavSection section, String serverId) => '${section.name}:$serverId';

  String _focusKeyForLibraryRow(_LibraryNavRow row) => switch (row) {
    _LibraryServerHeaderRow(:final section, :final serverId) => _serverHeaderFocusKey(section, serverId),
    _LibraryItemRow(:final section, :final library) => _libraryItemFocusKey(section, library),
  };

  Iterable<String> _focusKeysForLibraryRows(List<_LibraryNavRow> rows) => rows.map(_focusKeyForLibraryRow);

  /// Build the set of valid focus keys (main nav + currently rendered library rows).
  Set<String> _buildValidFocusKeys({
    required List<_LibraryNavRow> visibleRows,
    required List<_LibraryNavRow> hiddenRows,
    required bool hasHiddenLibraries,
    required bool hasLiveTv,
  }) {
    return {
      _kHome,
      _kLibraries,
      _kSearch,
      _kDownloads,
      _kSettings,
      _kReconnect,
      if (hasHiddenLibraries) _kHiddenLibraries,
      if (_showFullscreenToggle) _kFullscreen,
      if (hasLiveTv) 'liveTv',
      ..._focusKeysForLibraryRows(visibleRows),
      if (_hiddenLibrariesExpanded) ..._focusKeysForLibraryRows(hiddenRows),
    };
  }

  /// Build rendered rows inside one library section. This is the single source
  /// of truth for both widget rendering and D-pad focus ordering.
  List<_LibraryNavRow> _buildLibraryRows(
    List<MediaLibrary> libs, {
    required _LibraryNavSection section,
    required bool showServerHeaders,
  }) {
    if (!showServerHeaders) {
      final nonUniqueNames = _getNonUniqueLibraryNames(libs);
      return libs.map((lib) {
        return _LibraryItemRow(
          section: section,
          library: lib,
          showServerName: nonUniqueNames.contains(lib.title) && lib.serverName != null,
        );
      }).toList();
    }
    final grouped = groupLibrariesByFirstAppearance(libs);
    final result = <_LibraryNavRow>[];
    for (final serverKey in grouped.serverOrder) {
      final bucket = grouped.byServer[serverKey]!;
      if (serverKey.isNotEmpty) {
        result.add(
          _LibraryServerHeaderRow(
            section: section,
            serverId: serverKey,
            serverName: bucket.first.serverName ?? serverKey,
          ),
        );
      }
      if (serverKey.isEmpty || !_collapsedServerGroupKeys.contains(_serverGroupStateKey(section, serverKey))) {
        for (final lib in bucket) {
          result.add(_LibraryItemRow(section: section, library: lib));
        }
      }
    }
    return result;
  }

  Set<String> _buildServerGroupStateKeys(
    List<MediaLibrary> visibleLibraries,
    List<MediaLibrary> hiddenLibraries, {
    required bool showServerHeaders,
  }) {
    if (!showServerHeaders) return {};

    return {
      for (final lib in visibleLibraries)
        if (lib.serverId != null) _serverGroupStateKey(_LibraryNavSection.visible, lib.serverId!),
      for (final lib in hiddenLibraries)
        if (lib.serverId != null) _serverGroupStateKey(_LibraryNavSection.hidden, lib.serverId!),
    };
  }

  /// Ordered list of focusable keys matching visual top-to-bottom order.
  List<String> _buildFocusOrder(
    List<_LibraryNavRow> visibleRows,
    List<_LibraryNavRow> hiddenRows, {
    required bool hasHiddenLibraries,
    required bool hasLiveTv,
  }) {
    return [
      if (widget.isOfflineMode && widget.onReconnect != null) _kReconnect,
      if (!widget.isOfflineMode) ...[
        _kHome,
        _kLibraries,
        if (_librariesExpanded) ...[
          ..._focusKeysForLibraryRows(visibleRows),
          if (hasHiddenLibraries) ...[
            _kHiddenLibraries,
            if (_hiddenLibrariesExpanded) ..._focusKeysForLibraryRows(hiddenRows),
          ],
        ],
        if (hasLiveTv) 'liveTv',
        _kSearch,
      ],
      _kDownloads,
      _kSettings,
      if (_showFullscreenToggle) _kFullscreen,
    ];
  }

  void _debugAssertUniqueFocusOrder(List<String> focusOrder) {
    assert(() {
      final seen = <String>{};
      for (final key in focusOrder) {
        if (!seen.add(key)) {
          throw FlutterError('SideNavigationRail focus order contains duplicate key: $key');
        }
      }
      return true;
    }());
  }

  /// Handle D-pad UP/DOWN by explicitly moving focus to the next/previous item.
  KeyEventResult _handleVerticalNavigation(FocusNode _, KeyEvent event, List<String> focusOrder) {
    if (event is! KeyDownEvent) return KeyEventResult.ignored;

    final isDown = event.logicalKey == LogicalKeyboardKey.arrowDown;
    final isUp = event.logicalKey == LogicalKeyboardKey.arrowUp;
    if (!isDown && !isUp) return KeyEventResult.ignored;

    final currentKey = _focusTracker.lastFocusedKey;
    if (currentKey == null) return KeyEventResult.ignored;

    final currentIndex = focusOrder.indexOf(currentKey);
    if (currentIndex == -1) return KeyEventResult.ignored;

    final nextIndex = isDown ? currentIndex + 1 : currentIndex - 1;
    if (nextIndex < 0 || nextIndex >= focusOrder.length) return KeyEventResult.handled;

    final nextNode = _focusTracker.nodeFor(focusOrder[nextIndex]);
    if (nextNode == null) return KeyEventResult.ignored;

    nextNode.requestFocus();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      final ctx = nextNode.context;
      if (ctx != null) {
        Scrollable.ensureVisible(ctx, alignment: 0.5, duration: const Duration(milliseconds: 200));
      }
    });
    return KeyEventResult.handled;
  }

  /// Collapse the sidebar (resets touch-expand state).
  void collapse() {
    if (_isTouchExpanded) {
      setState(() => _isTouchExpanded = false);
    }
  }

  /// Reload libraries (called when servers change or profile switches)
  void reloadLibraries() {
    final librariesProvider = context.read<LibrariesProvider>();
    librariesProvider.refresh();
  }

  IconData _getLibraryIcon(String type) {
    switch (type.toLowerCase()) {
      case 'movie':
        return Symbols.movie_rounded;
      case 'show':
        return Symbols.tv_rounded;
      case 'artist':
        return Symbols.music_note_rounded;
      case 'photo':
        return Symbols.photo_rounded;
      case 'mixed':
        return Symbols.share_rounded;
      default:
        return Symbols.folder_rounded;
    }
  }

  /// Calculate top padding for macOS traffic lights
  double _getTopPadding(BuildContext context) {
    double basePadding = MediaQuery.paddingOf(context).top + 16;

    // On macOS, add extra padding for traffic lights (when not fullscreen)
    if (Platform.isMacOS) {
      final isFullscreen = FullscreenStateManager().isFullscreen;
      if (!isFullscreen) {
        // Traffic lights area is approximately 52 pixels high
        basePadding = basePadding < 52 ? 52 : basePadding;
      }
    }

    return basePadding;
  }

  @override
  Widget build(BuildContext context) {
    final t = tokens(context);
    final librariesProvider = context.watch<LibrariesProvider>();
    final hiddenLibrariesProvider = context.watch<HiddenLibrariesProvider>();
    final hiddenKeys = hiddenLibrariesProvider.hiddenLibraryKeys;

    final allLibraries = librariesProvider.libraries;
    final visibleLibraries = <MediaLibrary>[];
    final hiddenLibraries = <MediaLibrary>[];
    final serverIds = <String>{};
    for (final lib in allLibraries) {
      if (lib.serverId != null) serverIds.add(lib.serverId!);
      if (hiddenKeys.contains(lib.globalKey)) {
        hiddenLibraries.add(lib);
      } else {
        visibleLibraries.add(lib);
      }
    }

    final isCollapsed = !_shouldExpand;
    final hasLiveTv = context.watch<MultiServerProvider>().hasLiveTv;

    // Listen to fullscreen + groupLibrariesByServer setting so the rail
    // rebuilds when the user toggles "Group libraries by server" in Appearance.
    return ListenableBuilder(
      listenable: Listenable.merge([
        FullscreenStateManager(),
        SettingsService.instanceOrNull!.listenable(SettingsService.groupLibrariesByServer),
      ]),
      builder: (context, _) {
        // Server grouping: only when multi-server AND the user-facing toggle is on.
        final groupByServerSetting = SettingsService.instanceOrNull!.read(SettingsService.groupLibrariesByServer);
        final showServerHeaders = serverIds.length > 1 && groupByServerSetting;
        _collapsedServerGroupKeys.retainAll(
          _buildServerGroupStateKeys(visibleLibraries, hiddenLibraries, showServerHeaders: showServerHeaders),
        );
        final visibleRows = _buildLibraryRows(
          visibleLibraries,
          section: _LibraryNavSection.visible,
          showServerHeaders: showServerHeaders,
        );
        final hiddenRows = _buildLibraryRows(
          hiddenLibraries,
          section: _LibraryNavSection.hidden,
          showServerHeaders: showServerHeaders,
        );
        _focusTracker.pruneExcept(
          _buildValidFocusKeys(
            visibleRows: visibleRows,
            hiddenRows: hiddenRows,
            hasHiddenLibraries: hiddenLibraries.isNotEmpty,
            hasLiveTv: hasLiveTv,
          ),
        );
        final focusOrder = _buildFocusOrder(
          visibleRows,
          hiddenRows,
          hasHiddenLibraries: hiddenLibraries.isNotEmpty,
          hasLiveTv: hasLiveTv,
        );
        _debugAssertUniqueFocusOrder(focusOrder);
        return TapRegion(
          onTapOutside: (_) {
            if (_isTouchExpanded) {
              setState(() => _isTouchExpanded = false);
            }
          },
          child: MouseRegion(
            onEnter: (_) => _onHoverEnter(),
            onExit: (_) => _onHoverExit(),
            child: GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTap: isCollapsed ? () => setState(() => _isTouchExpanded = true) : null,
              child: AnimatedContainer(
                duration: t.normal,
                curve: Curves.easeOutCubic,
                width: isCollapsed ? collapsedWidth : expandedWidth,
                clipBehavior: Clip.hardEdge,
                decoration: BoxDecoration(color: t.surface),
                child: IgnorePointer(
                  ignoring: isCollapsed,
                  child: Focus(
                    canRequestFocus: false,
                    skipTraversal: true,
                    onKeyEvent: (node, event) => _handleVerticalNavigation(node, event, focusOrder),
                    child: Column(
                      children: [
                        SizedBox(height: _getTopPadding(context)),

                        Expanded(
                          child: ListView(
                            padding: const EdgeInsets.symmetric(horizontal: 12),
                            clipBehavior: Clip.hardEdge,
                            children: [
                              if (widget.isOfflineMode && widget.onReconnect != null) ...[
                                _buildReconnectItem(isCollapsed: isCollapsed),
                                const SizedBox(height: 8),
                              ],

                              if (!widget.isOfflineMode) ...[
                                _buildNavItem(
                                  icon: Symbols.home_rounded,
                                  selectedIcon: Symbols.home_rounded,
                                  label: Translations.of(context).common.home,
                                  isSelected: widget.selectedTab == NavigationTabId.discover,
                                  isFocused: _focusTracker.isFocused(_kHome),
                                  onTap: () => widget.onDestinationSelected(NavigationTabId.discover),
                                  focusNode: _focusTracker.get(_kHome),
                                  isCollapsed: isCollapsed,
                                ),

                                const SizedBox(height: 8),

                                _buildLibrariesSection(
                                  visibleRows,
                                  hiddenRows,
                                  hiddenLibraries.length,
                                  t,
                                  isCollapsed: isCollapsed,
                                ),

                                const SizedBox(height: 8),

                                if (context.watch<MultiServerProvider>().hasLiveTv) ...[
                                  _buildNavItem(
                                    icon: Symbols.live_tv_rounded,
                                    selectedIcon: Symbols.live_tv_rounded,
                                    label: Translations.of(context).navigation.liveTv,
                                    isSelected: widget.selectedTab == NavigationTabId.liveTv,
                                    isFocused: _focusTracker.isFocused('liveTv'),
                                    onTap: () => widget.onDestinationSelected(NavigationTabId.liveTv),
                                    focusNode: _focusTracker.get('liveTv'),
                                    isCollapsed: isCollapsed,
                                  ),

                                  const SizedBox(height: 8),
                                ],

                                _buildNavItem(
                                  icon: Symbols.search_rounded,
                                  selectedIcon: Symbols.search_rounded,
                                  label: Translations.of(context).common.search,
                                  isSelected: widget.selectedTab == NavigationTabId.search,
                                  isFocused: _focusTracker.isFocused(_kSearch),
                                  onTap: () => widget.onDestinationSelected(NavigationTabId.search),
                                  focusNode: _focusTracker.get(_kSearch),
                                  isCollapsed: isCollapsed,
                                ),

                                const SizedBox(height: 8),
                              ],

                              // Downloads (hidden on Apple TV — no user
                              // file storage)
                              if (!PlatformDetector.isAppleTV()) ...[
                                _buildNavItem(
                                  icon: Symbols.download_rounded,
                                  selectedIcon: Symbols.download_rounded,
                                  label: Translations.of(context).navigation.downloads,
                                  isSelected: widget.selectedTab == NavigationTabId.downloads,
                                  isFocused: _focusTracker.isFocused(_kDownloads),
                                  onTap: () => widget.onDestinationSelected(NavigationTabId.downloads),
                                  focusNode: _focusTracker.get(_kDownloads),
                                  isCollapsed: isCollapsed,
                                ),
                                const SizedBox(height: 8),
                              ],

                              _buildNavItem(
                                icon: Symbols.settings_rounded,
                                selectedIcon: Symbols.settings_rounded,
                                label: Translations.of(context).common.settings,
                                isSelected: widget.selectedTab == NavigationTabId.settings,
                                isFocused: _focusTracker.isFocused(_kSettings),
                                onTap: () => widget.onDestinationSelected(NavigationTabId.settings),
                                focusNode: _focusTracker.get(_kSettings),
                                isCollapsed: isCollapsed,
                              ),
                            ],
                          ),
                        ),

                        if (_showFullscreenToggle)
                          Padding(
                            padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
                            child: _buildFullscreenItem(isCollapsed: isCollapsed),
                          ),
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
        );
      },
    );
  }

  Widget _buildNavItem({
    required IconData icon,
    required IconData selectedIcon,
    required String label,
    required bool isSelected,
    required bool isFocused,
    required VoidCallback onTap,
    required FocusNode focusNode,
    required bool isCollapsed,
    bool autofocus = false,
  }) {
    final t = tokens(context);

    return NavigationRailItem(
      icon: icon,
      selectedIcon: selectedIcon,
      label: Text(
        label,
        style: TextStyle(
          fontSize: 14,
          fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
          color: isSelected ? t.text : t.textMuted,
        ),
        overflow: TextOverflow.ellipsis,
        maxLines: 1,
      ),
      isSelected: isSelected,
      isFocused: isFocused,
      isCollapsed: isCollapsed,
      onTap: onTap,
      focusNode: focusNode,
      autofocus: autofocus,
      onNavigateRight: widget.onNavigateToContent,
    );
  }

  Widget _buildReconnectItem({required bool isCollapsed}) {
    final t = tokens(context);
    final isFocused = _focusTracker.isFocused(_kReconnect);

    return NavigationRailItem(
      icon: widget.isReconnecting ? Symbols.sync_rounded : Symbols.wifi_rounded,
      label: widget.isReconnecting
          ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: t.text))
          : Text(
              Translations.of(context).common.reconnect,
              style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: t.textMuted),
              overflow: TextOverflow.ellipsis,
              maxLines: 1,
            ),
      isSelected: false,
      isFocused: isFocused,
      isCollapsed: isCollapsed,
      // ignore: no-empty-block - no-op tap handler while reconnecting
      onTap: widget.isReconnecting ? () {} : () => widget.onReconnect?.call(),
      focusNode: _focusTracker.get(_kReconnect),
      onNavigateRight: widget.onNavigateToContent,
    );
  }

  Widget _buildFullscreenItem({required bool isCollapsed}) {
    final t = tokens(context);
    final isFullscreen = FullscreenStateManager().isFullscreen;
    final isFocused = _focusTracker.isFocused(_kFullscreen);

    return NavigationRailItem(
      icon: isFullscreen ? Symbols.fullscreen_exit_rounded : Symbols.fullscreen_rounded,
      label: Text(
        isFullscreen ? Translations.of(context).common.exitFullscreen : Translations.of(context).common.fullscreen,
        style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: t.textMuted),
        overflow: TextOverflow.ellipsis,
        maxLines: 1,
      ),
      isSelected: false,
      isFocused: isFocused,
      isCollapsed: isCollapsed,
      onTap: () => unawaited(FullscreenStateManager().toggleFullscreen()),
      focusNode: _focusTracker.get(_kFullscreen),
      onNavigateRight: widget.onNavigateToContent,
    );
  }

  Widget _buildLibrariesSection(
    List<_LibraryNavRow> visibleRows,
    List<_LibraryNavRow> hiddenRows,
    int hiddenLibraryCount,
    dynamic t, {
    bool isCollapsed = false,
  }) {
    final librariesProvider = context.watch<LibrariesProvider>();
    final isLoading = librariesProvider.isLoading;
    final isLibrariesSelected = widget.selectedTab == NavigationTabId.libraries && widget.selectedLibraryKey == null;
    final isLibrariesFocused = _focusTracker.isFocused(_kLibraries);
    final allEmpty = visibleRows.isEmpty && hiddenLibraryCount == 0;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Focus(
          focusNode: _focusTracker.get(_kLibraries),
          onKeyEvent: (node, event) {
            if (event is! KeyDownEvent) return KeyEventResult.ignored;
            if (event.logicalKey.isSelectKey) {
              setState(() {
                _librariesExpanded = !_librariesExpanded;
              });
              return KeyEventResult.handled;
            }
            // RIGHT arrow navigates to content area
            if (event.logicalKey == LogicalKeyboardKey.arrowRight && widget.onNavigateToContent != null) {
              widget.onNavigateToContent!();
              return KeyEventResult.handled;
            }
            return KeyEventResult.ignored;
          },
          child: Material(
            color: Colors.transparent,
            child: InkWell(
              canRequestFocus: false,
              onTap: () {
                setState(() {
                  _librariesExpanded = !_librariesExpanded;
                });
              },
              borderRadius: BorderRadius.circular(tokens(context).radiusMd),
              child: Container(
                decoration: BoxDecoration(
                  color: () {
                    if (isLibrariesSelected) return t.text.withValues(alpha: 0.1);
                    if (isLibrariesFocused) return t.text.withValues(alpha: 0.08);
                    return null;
                  }(),
                  borderRadius: BorderRadius.circular(tokens(context).radiusMd),
                ),
                clipBehavior: Clip.hardEdge,
                child: UnconstrainedBox(
                  alignment: Alignment.centerLeft,
                  constrainedAxis: Axis.vertical,
                  clipBehavior: Clip.hardEdge,
                  child: SizedBox(
                    width: expandedWidth - 24,
                    child: Padding(
                      padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 17),
                      child: Row(
                        children: [
                          AppIcon(
                            Symbols.video_library_rounded,
                            fill: 1,
                            size: 22,
                            color: widget.selectedTab == NavigationTabId.libraries ? t.text : t.textMuted,
                          ),
                          const SizedBox(width: 11),
                          Expanded(
                            child: AnimatedOpacity(
                              opacity: isCollapsed ? 0.0 : 1.0,
                              duration: tokens(context).fast,
                              child: Text(
                                Translations.of(context).navigation.libraries,
                                style: TextStyle(
                                  fontSize: 14,
                                  fontWeight: widget.selectedTab == NavigationTabId.libraries
                                      ? FontWeight.w600
                                      : FontWeight.w400,
                                  color: widget.selectedTab == NavigationTabId.libraries ? t.text : t.textMuted,
                                ),
                              ),
                            ),
                          ),
                          AnimatedOpacity(
                            opacity: isCollapsed ? 0.0 : 1.0,
                            duration: tokens(context).fast,
                            child: AppIcon(
                              _librariesExpanded ? Symbols.expand_less_rounded : Symbols.expand_more_rounded,
                              fill: 1,
                              size: 20,
                              color: t.textMuted,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),

        TweenAnimationBuilder<double>(
          tween: Tween(end: (_librariesExpanded && !isCollapsed) ? 1.0 : 0.0),
          duration: tokens(context).normal,
          curve: Curves.easeOutCubic,
          builder: (context, value, child) {
            return ClipRect(
              child: Align(alignment: Alignment.topCenter, heightFactor: value, child: child),
            );
          },
          child: ExcludeFocus(
            excluding: !_librariesExpanded || isCollapsed,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const SizedBox(height: 4),
                if (isLoading)
                  Padding(
                    padding: const EdgeInsets.all(16),
                    child: Center(
                      child: SizedBox(
                        width: 20,
                        height: 20,
                        child: CircularProgressIndicator(strokeWidth: 2, color: t.textMuted),
                      ),
                    ),
                  )
                else if (allEmpty)
                  Padding(
                    padding: const EdgeInsets.all(16),
                    child: Text(
                      Translations.of(context).libraries.noLibrariesFound,
                      style: TextStyle(fontSize: 12, color: t.textMuted),
                    ),
                  )
                else ...[
                  if (visibleRows.isNotEmpty) _buildLibraryGroupedColumn(visibleRows, t),
                  if (hiddenLibraryCount > 0) ...[
                    _buildHiddenLibrariesHeader(hiddenLibraryCount, t),
                    if (_hiddenLibrariesExpanded) _buildLibraryGroupedColumn(hiddenRows, t),
                  ],
                ],
              ],
            ),
          ),
        ),
      ],
    );
  }

  /// Get set of library names that appear more than once (not globally unique)
  Set<String> _getNonUniqueLibraryNames(List<MediaLibrary> libraries) {
    final nameCounts = <String, int>{};
    for (final lib in libraries) {
      nameCounts[lib.title] = (nameCounts[lib.title] ?? 0) + 1;
    }
    return nameCounts.entries.where((e) => e.value > 1).map((e) => e.key).toSet();
  }

  Widget _buildLibraryGroupedColumn(List<_LibraryNavRow> rows, dynamic t) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: rows.map((row) {
        return switch (row) {
          _LibraryServerHeaderRow(:final section, :final serverId, :final serverName) => _buildServerHeader(
            section,
            serverId,
            serverName,
            t,
          ),
          _LibraryItemRow(:final section, :final library, :final showServerName) => _buildLibraryItem(
            section,
            library,
            t,
            showServerName: showServerName,
          ),
        };
      }).toList(),
    );
  }

  Widget _buildServerHeader(_LibraryNavSection section, String serverId, String serverName, dynamic t) {
    // Resolve backend per server so the badge matches the brand. Falls back
    // to the generic `dns` icon if the client isn't registered yet (rare —
    // can happen during a profile switch before the manager rehydrates).
    final backend = context.read<MultiServerProvider>().serverManager.getClient(serverId)?.backend;
    return _buildCollapsibleHeader(
      focusKey: _serverHeaderFocusKey(section, serverId),
      icon: Symbols.dns_rounded,
      iconSize: 14,
      leading: backend == null ? null : BackendBadge(backend: backend, size: 14, color: t.textMuted),
      label: serverName,
      labelStyle: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 0.4, color: t.textMuted),
      verticalPadding: 6,
      isExpanded: !_collapsedServerGroupKeys.contains(_serverGroupStateKey(section, serverId)),
      onToggle: () => _toggleServerCollapse(section, serverId),
      t: t,
    );
  }

  void _toggleServerCollapse(_LibraryNavSection section, String serverId) {
    final groupKey = _serverGroupStateKey(section, serverId);
    setState(() {
      if (!_collapsedServerGroupKeys.add(groupKey)) {
        _collapsedServerGroupKeys.remove(groupKey);
      }
    });
  }

  Widget _buildHiddenLibrariesHeader(int count, dynamic t) {
    return _buildCollapsibleHeader(
      focusKey: _kHiddenLibraries,
      icon: Symbols.visibility_off_rounded,
      iconSize: 16,
      label: Translations.of(context).libraries.hiddenLibrariesCount(count: count),
      labelStyle: TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: t.textMuted),
      verticalPadding: 8,
      isExpanded: _hiddenLibrariesExpanded,
      onToggle: () => setState(() => _hiddenLibrariesExpanded = !_hiddenLibrariesExpanded),
      t: t,
    );
  }

  Widget _buildCollapsibleHeader({
    required String focusKey,
    required IconData icon,
    required double iconSize,
    Widget? leading,
    required String label,
    required TextStyle labelStyle,
    required double verticalPadding,
    required bool isExpanded,
    required VoidCallback onToggle,
    required dynamic t,
  }) {
    final isFocused = _focusTracker.isFocused(focusKey);
    final radius = BorderRadius.circular(tokens(context).radiusSm);
    // Match library-item indent: outer Padding(left: 12) + inner horizontal 17.
    return Padding(
      padding: const EdgeInsets.only(left: 12),
      child: Focus(
        focusNode: _focusTracker.get(focusKey),
        onKeyEvent: (node, event) {
          if (event is! KeyDownEvent) return KeyEventResult.ignored;
          if (event.logicalKey.isSelectKey) {
            onToggle();
            return KeyEventResult.handled;
          }
          if (event.logicalKey == LogicalKeyboardKey.arrowRight && widget.onNavigateToContent != null) {
            widget.onNavigateToContent!();
            return KeyEventResult.handled;
          }
          return KeyEventResult.ignored;
        },
        child: Material(
          color: Colors.transparent,
          child: InkWell(
            canRequestFocus: false,
            onTap: onToggle,
            borderRadius: radius,
            child: Container(
              decoration: BoxDecoration(color: isFocused ? t.text.withValues(alpha: 0.08) : null, borderRadius: radius),
              clipBehavior: Clip.hardEdge,
              child: UnconstrainedBox(
                alignment: Alignment.centerLeft,
                constrainedAxis: Axis.vertical,
                clipBehavior: Clip.hardEdge,
                child: SizedBox(
                  width: expandedWidth - 24,
                  child: Padding(
                    padding: EdgeInsets.symmetric(vertical: verticalPadding, horizontal: 17),
                    child: Row(
                      children: [
                        leading ?? AppIcon(icon, fill: 1, size: iconSize, color: t.textMuted),
                        const SizedBox(width: 11),
                        Expanded(
                          child: Text(label, style: labelStyle, overflow: TextOverflow.ellipsis),
                        ),
                        AppIcon(
                          isExpanded ? Symbols.expand_less_rounded : Symbols.expand_more_rounded,
                          fill: 1,
                          size: 16,
                          color: t.textMuted,
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildLibraryItem(_LibraryNavSection section, MediaLibrary library, dynamic t, {bool showServerName = false}) {
    final isSelected =
        widget.selectedTab == NavigationTabId.libraries && widget.selectedLibraryKey == library.globalKey;
    final focusKey = _libraryItemFocusKey(section, library);
    final isFocused = _focusTracker.isFocused(focusKey);
    final focusNode = _focusTracker.get(focusKey);

    return Padding(
      padding: const EdgeInsets.only(left: 12),
      child: NavigationRailItem(
        icon: _getLibraryIcon(library.kind.id),
        selectedIcon: _getLibraryIcon(library.kind.id),
        label: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              library.title,
              style: TextStyle(
                fontSize: 13,
                fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
                color: isSelected ? t.text : t.textMuted,
              ),
              overflow: TextOverflow.ellipsis,
            ),
            if (showServerName)
              Text(
                library.serverName!,
                style: TextStyle(fontSize: 9, color: t.textMuted.withValues(alpha: 0.4)),
                overflow: TextOverflow.ellipsis,
              ),
          ],
        ),
        isSelected: isSelected,
        isFocused: isFocused,
        useSimpleLayout: true,
        onTap: () => widget.onLibrarySelected(library.globalKey),
        focusNode: focusNode,
        borderRadius: BorderRadius.circular(tokens(context).radiusSm),
        iconSize: 18,
        onNavigateRight: widget.onNavigateToContent,
      ),
    );
  }
}
