// 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 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/authentication_change_event/authentication_change_event.dart';
import 'package:wyatt_authentication_bloc/src/features/authentication/cubit/authentication_cubit.dart';

/// Extension that helps to quickly access useful resources
/// from the context.
extension BuildContextExtension on BuildContext {
  /// Read session in context from a specific AuthenticationCubit type [T]
  AuthenticationSession<Data>?
      readSessionFrom<T extends AuthenticationCubit<Data>, Data>() =>
          read<T>().currentSession();

  /// Watch session in context from a specific AuthenticationCubit type [T]
  AuthenticationSession<Data>?
      watchSessionFrom<T extends AuthenticationCubit<Data>, Data>() =>
          watch<T>().currentSession();

  /// Read session in context from generic AuthenticationCubit type
  AuthenticationSession<Data>? readSession<Data>() =>
      read<AuthenticationCubit<Data>>().currentSession();

  /// Watch session in context from generic AuthenticationCubit type
  AuthenticationSession<Data>? watchSession<Data>() =>
      watch<AuthenticationCubit<Data>>().currentSession();

  /// Read event in context from a specific AuthenticationCubit type [T]
  AuthenticationChangeEvent?
      readEventFrom<T extends AuthenticationCubit<Data>, Data>() =>
          readSessionFrom<T, Data>()?.latestEvent;

  /// Watch event in context from a specific AuthenticationCubit type [T]
  AuthenticationChangeEvent?
      watchEventFrom<T extends AuthenticationCubit<Data>, Data>() =>
          watchSessionFrom<T, Data>()?.latestEvent;

  /// Read event in context from generic AuthenticationCubit type
  AuthenticationChangeEvent? readEvent<Data>() =>
      readSession<Data>()?.latestEvent;

  /// Watch event in context from generic AuthenticationCubit type
  AuthenticationChangeEvent? watchEvent<Data>() =>
      watchSession<Data>()?.latestEvent;

  /// Read account in context from a specific AuthenticationCubit type [T]
  Account? readAccountFrom<T extends AuthenticationCubit<Data>, Data>() =>
      readSessionFrom<T, Data>()?.account;

  /// Watch account in context from a specific AuthenticationCubit type [T]
  Account? watchAccountFrom<T extends AuthenticationCubit<Data>, Data>() =>
      watchSessionFrom<T, Data>()?.account;

  /// Read account in context from generic AuthenticationCubit type
  Account? readAccount<Data>() => readSession<Data>()?.account;

  /// Watch account in context from generic AuthenticationCubit type
  Account? watchAccount<Data>() => watchSession<Data>()?.account;

  /// Read data in context from a specific AuthenticationCubit type [T]
  Data? readDataFrom<T extends AuthenticationCubit<Data>, Data>() =>
      readSessionFrom<T, Data>()?.data;

  /// Watch data in context from a specific AuthenticationCubit type [T]
  Data? watchDataFrom<T extends AuthenticationCubit<Data>, Data>() =>
      watchSessionFrom<T, Data>()?.data;

  /// Read data in context from generic AuthenticationCubit type
  Data? readData<Data>() => readSession<Data>()?.data;

  /// Watch data in context from generic AuthenticationCubit type
  Data? watchData<Data>() => watchSession<Data>()?.data;

  /// Check if user is authenticated from a
  /// specific AuthenticationCubit type [T]
  bool isAuthenticatedFrom<T extends AuthenticationCubit<Data>, Data>() =>
      readEventFrom<T, Data>() is AuthenticatedChangeEvent;

  /// Check if user is authenticated from generic AuthenticationCubit type
  bool isAuthenticated<Data>() => readEvent<Data>() is AuthenticatedChangeEvent;
}
