// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import 'dart:async';

import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_ui_components/wyatt_ui_components.dart';

part 'button_state.dart';

class ButtonCubit extends Cubit<ButtonState> {
  ButtonCubit() : super(const ButtonState.initial(ControlState.normal)) {
    _previousState = state.copyWith();
  }

  /// The previous state of the button.
  /// This is used to restore the state of the button when
  /// the button is enabled again.
  ButtonState? _previousState;

  /// When the mouse enters the button. The button will change
  /// its state to [ControlState.hovered].
  ///
  /// If the button is disabled or freezed, this method does nothing.
  FutureOr<void> onMouseEnter() async {
    if (state.isDisabled || state.isFreezed) {
      return;
    }
    emit(state.copyWith(state: ControlState.hovered));
  }

  /// When the mouse leaves the button. The button will change
  /// its state to [ControlState.normal].
  ///
  /// If the button is disabled or freezed, this method does nothing.
  FutureOr<void> onMouseLeave() async {
    if (state.isDisabled || state.isFreezed) {
      return;
    }

    emit(state.copyWith(state: ControlState.normal));
  }

  /// When the button is focused. The button will change
  /// its state to [ControlState.focused].
  ///
  /// If the button is disabled or freezed, this method does nothing.
  FutureOr<void> onFocus() async {
    if (state.isDisabled || state.isFreezed) {
      return;
    }
    emit(state.copyWith(state: ControlState.focused));
  }

  /// When the button is unfocused. The button will change
  /// its state to [ControlState.normal].
  ///
  /// If the button is disabled or freezed, this method does nothing.
  FutureOr<void> onUnfocus() async {
    if (state.isDisabled || state.isFreezed) {
      return;
    }
    emit(state.copyWith(state: ControlState.normal));
  }

  /// When the mouse is pressed on the button. The button will change
  /// its state to [ControlState.tapped].
  ///
  /// If the button is disabled or freezed, this method does nothing.
  FutureOr<void> onClickDown() async {
    if (state.isDisabled || state.isFreezed) {
      return;
    }
    emit(state.copyWith(state: ControlState.tapped));
  }

  /// When the mouse is released on the button. The button will change
  /// its state to [ControlState.hovered].
  ///
  /// If the button is disabled or freezed, this method does nothing.
  FutureOr<void> onClickUpIn() async {
    if (state.isDisabled || state.isFreezed) {
      return;
    }
    emit(state.copyWith(state: ControlState.hovered));
  }

  /// When the mouse is released outside the button. The button will change
  /// its state to [ControlState.normal].
  ///
  /// If the button is disabled or freezed, this method does nothing.
  FutureOr<void> onClickUpOut() async {
    if (state.isDisabled || state.isFreezed) {
      return;
    }
    emit(state.copyWith(state: ControlState.normal));
  }

  /// Programmatically change the state or extra states of the button.
  FutureOr<void> setState({
    ControlState? state,
    List<ExtraState>? extraStates,
  }) async {
    emit(this.state.copyWith(state: state, extraStates: extraStates));
  }

  /// Disable the button.
  ///
  /// This will remove all extra states and set
  /// the state to [ControlState.disabled] and add [ExtraState.freezed].
  FutureOr<void> disable() async {
    /// Save the previous state.
    /// copyWith() is used to make sure the previous state is not
    /// affected by any changes to the current state.
    _previousState = state.copyWith();

    emit(
      const ButtonState(
        state: ControlState.disabled,
        extraStates: [ExtraState.freezed],
      ),
    );
  }

  /// Enable the button.
  ///
  /// This will restore the previous state of the button.
  FutureOr<void> enable() async {
    if (_previousState == null) {
      return;
    }

    emit(_previousState!);
    _previousState = null;
  }

  /// Set the button to hovered state and freeze it.
  ///
  /// Use this method to programmatically set the button to hovered state.
  FutureOr<void> hover() async {
    emit(
      state.copyWith(
        state: ControlState.hovered,
        extraStates: state.extraStates + [ExtraState.freezed],
      ),
    );
  }

  /// Set the button to focused state and freeze it.
  ///
  /// Use this method to programmatically set the button to focused state.
  FutureOr<void> focus() async {
    emit(
      state.copyWith(
        state: ControlState.focused,
        extraStates: state.extraStates + [ExtraState.freezed],
      ),
    );
  }

  /// Set the button to tapped state and freeze it.
  ///
  /// Use this method to programmatically set the button to tapped state.
  FutureOr<void> tap() async {
    emit(
      state.copyWith(
        state: ControlState.tapped,
        extraStates: state.extraStates + [ExtraState.freezed],
      ),
    );
  }

  /// Freeze the button in its current state.
  ///
  /// Shortcut for [setState] with extra state [ExtraState.freezed].
  ///
  /// Use this method to programmatically freeze the button.
  FutureOr<void> freeze() async {
    emit(
      state.copyWith(
        extraStates: state.extraStates + [ExtraState.freezed],
      ),
    );
  }

  /// Unfreeze the button.
  ///
  /// Shortcut for [setState] without extra state [ExtraState.freezed].
  ///
  /// Use this method to programmatically unfreeze the button.
  FutureOr<void> unfreeze() async {
    emit(
      state.copyWith(
        extraStates: state.extraStates
            .where((element) => element != ExtraState.freezed)
            .toList(),
      ),
    );
  }
}
