import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';

import '../widgets/app_icon.dart';
import '../widgets/clickable_cursor.dart';
import 'focus_theme.dart';
import 'input_mode_tracker.dart';
import 'key_event_utils.dart';
import 'owned_focus_node_binding.dart';

typedef FocusableActionBuilder = Widget Function(BuildContext context, FocusableActionBuildState state);

class FocusableActionBuildState {
  final FocusNode focusNode;
  final bool isFocused;
  final bool showFocus;
  final bool isKeyboardMode;
  final Duration animationDuration;

  const FocusableActionBuildState({
    required this.focusNode,
    required this.isFocused,
    required this.showFocus,
    required this.isKeyboardMode,
    required this.animationDuration,
  });
}

class FocusableAction {
  final IconData icon;
  final Color? iconColor;
  final double iconFill;
  final double iconSize;

  final String? debugLabel;
  final FocusNode? focusNode;
  final bool autofocus;

  final String? tooltip;
  final VoidCallback? onPressed;
  final Widget? child;
  final FocusableActionBuilder? builder;

  const FocusableAction({
    this.icon = Symbols.circle_rounded,
    this.iconColor,
    this.iconFill = 1.0,
    this.iconSize = 24,
    this.debugLabel,
    this.focusNode,
    this.autofocus = false,
    this.tooltip,
    this.onPressed,
    this.child,
    this.builder,
  });
}

class FocusableActionBar extends StatefulWidget {
  final List<FocusableAction> actions;

  /// Called when the user presses down from any action button.
  final VoidCallback? onNavigateDown;

  /// Called when the user presses up from any action button.
  final VoidCallback? onNavigateUp;

  /// Called when the user presses left from the leftmost button.
  final VoidCallback? onNavigateLeft;

  /// Called when the user presses right from the rightmost button.
  final VoidCallback? onNavigateRight;

  /// Called when the user presses the back key while an action is focused.
  final VoidCallback? onBack;

  /// Called when any action in the row gains or loses focus.
  final ValueChanged<bool>? onFocusChange;

  final double spacing;
  final MainAxisSize mainAxisSize;

  const FocusableActionBar({
    super.key,
    required this.actions,
    this.onNavigateDown,
    this.onNavigateUp,
    this.onNavigateLeft,
    this.onNavigateRight,
    this.onBack,
    this.onFocusChange,
    this.spacing = 0,
    this.mainAxisSize = MainAxisSize.min,
  });

  @override
  State<FocusableActionBar> createState() => FocusableActionBarState();
}

class FocusableActionBarState extends State<FocusableActionBar> {
  late List<OwnedFocusNodeBinding> _focusBindings;
  late List<FocusNode> _focusNodes;
  late List<bool> _focusStates;
  bool _hasAnyFocus = false;

  FocusNode? getFocusNode(int index) => index >= 0 && index < _focusNodes.length ? _focusNodes[index] : null;

  void requestFocusOnFirst() {
    final index = _nextEnabledIndex(-1);
    if (index != null) _focusNodes[index].requestFocus();
  }

  int? _previousEnabledIndex(int index) {
    for (var candidate = index - 1; candidate >= 0; candidate--) {
      if (widget.actions[candidate].onPressed != null) return candidate;
    }
    return null;
  }

  int? _nextEnabledIndex(int index) {
    for (var candidate = index + 1; candidate < widget.actions.length; candidate++) {
      if (widget.actions[candidate].onPressed != null) return candidate;
    }
    return null;
  }

  @override
  void initState() {
    super.initState();
    _initNodes();
  }

  @override
  void didUpdateWidget(FocusableActionBar oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (_shouldRebuildFocusNodes(oldWidget)) {
      _disposeNodes();
      _initNodes();
    }
  }

  bool _shouldRebuildFocusNodes(FocusableActionBar oldWidget) {
    if (oldWidget.actions.length != widget.actions.length) return true;
    for (var i = 0; i < widget.actions.length; i++) {
      if (oldWidget.actions[i].focusNode != widget.actions[i].focusNode) return true;
      if (oldWidget.actions[i].debugLabel != widget.actions[i].debugLabel) return true;
    }
    return false;
  }

  void _initNodes() {
    _focusBindings = [];
    _focusNodes = [];
    _focusStates = List<bool>.filled(widget.actions.length, false);
    for (var i = 0; i < widget.actions.length; i++) {
      final index = i;
      final binding = OwnedFocusNodeBinding();
      binding.bind(
        externalNode: widget.actions[i].focusNode,
        debugLabel: widget.actions[i].debugLabel ?? 'ActionBar[$i]',
        listener: () {
          final hasFocus = _focusNodes[index].hasFocus;
          if (_focusStates[index] != hasFocus) {
            setState(() => _focusStates[index] = hasFocus);
          }
          _notifyRowFocusIfChanged();
        },
      );
      _focusBindings.add(binding);
      _focusNodes.add(binding.node);
      _focusStates[i] = binding.node.hasFocus;
    }
    _hasAnyFocus = _focusNodes.any((node) => node.hasFocus);
  }

  void _notifyRowFocusIfChanged() {
    final hasAnyFocus = _focusNodes.any((node) => node.hasFocus);
    if (_hasAnyFocus == hasAnyFocus) return;
    _hasAnyFocus = hasAnyFocus;
    widget.onFocusChange?.call(hasAnyFocus);
  }

  void _disposeNodes() {
    for (final binding in _focusBindings) {
      binding.dispose();
    }
  }

  @override
  void dispose() {
    _disposeNodes();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final isKeyboard = InputModeTracker.isKeyboardMode(context);
    final duration = FocusTheme.getAnimationDuration(context);

    final row = Row(
      mainAxisSize: widget.mainAxisSize,
      children: [
        for (var i = 0; i < widget.actions.length; i++) ...[
          if (i > 0 && widget.spacing > 0) SizedBox(width: widget.spacing),
          _buildButton(i, isKeyboard, duration),
        ],
      ],
    );
    // While focus is outside the row (the common state — the user is browsing
    // content), every button is equally dimmed, so dim once at row level:
    // each sub-1.0 opacity is its own saveLayer, i.e. a full render-pass
    // switch on the tiled GPUs low-end TVs use, and these bars sit on screen
    // permanently. Per-button dims (below) take over only while the row holds
    // focus; mid-transition the two multiply, which stays visually seamless.
    return AnimatedOpacity(opacity: isKeyboard && !_hasAnyFocus ? 0.6 : 1.0, duration: duration, child: row);
  }

  Widget _buildButton(int index, bool isKeyboard, Duration duration) {
    final action = widget.actions[index];
    final enabled = action.onPressed != null;
    final isFocused = _focusStates[index];
    final showFocus = isFocused && isKeyboard;
    final opacity = isKeyboard && _hasAnyFocus && !isFocused ? 0.6 : 1.0;
    final buildState = FocusableActionBuildState(
      focusNode: _focusNodes[index],
      isFocused: isFocused,
      showFocus: showFocus,
      isKeyboardMode: isKeyboard,
      animationDuration: duration,
    );
    final customChild = action.builder?.call(context, buildState);

    return Focus(
      focusNode: _focusNodes[index],
      canRequestFocus: enabled,
      autofocus: action.autofocus && enabled,
      descendantsAreFocusable: false,
      onKeyEvent: (node, event) {
        if (widget.onBack != null) {
          final backResult = handleBackKeyAction(event, widget.onBack!);
          if (backResult != KeyEventResult.ignored) return backResult;
        }
        final previousIndex = _previousEnabledIndex(index);
        final nextIndex = _nextEnabledIndex(index);
        return dpadKeyHandler(
          onSelect: action.onPressed,
          onLeft: previousIndex != null ? () => _focusNodes[previousIndex].requestFocus() : widget.onNavigateLeft,
          onRight: nextIndex != null ? () => _focusNodes[nextIndex].requestFocus() : widget.onNavigateRight,
          onDown: widget.onNavigateDown,
          onUp: widget.onNavigateUp,
          // Consume LEFT/RIGHT at the row's first/last enabled button when no
          // edge callback is wired, so focus can't fall off the row (#1181).
          trapHorizontalEdges: true,
        )(node, event);
      },
      child: ClickableCursor(
        enabled: enabled,
        child: AnimatedOpacity(
          opacity: showFocus ? 1.0 : opacity,
          duration: duration,
          child:
              customChild ??
              Container(
                decoration: FocusTheme.focusBackgroundDecoration(isFocused: showFocus, borderRadius: 20),
                child:
                    action.child ??
                    IconButton(
                      icon: AppIcon(action.icon, size: action.iconSize, fill: action.iconFill, color: action.iconColor),
                      tooltip: action.tooltip,
                      onPressed: action.onPressed,
                    ),
              ),
        ),
      ),
    );
  }
}
