import 'dart:developer' as developer;
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:krow_domain/krow_domain.dart';

/// Mixin to standardize error handling across all BLoCs.
///
/// This mixin provides a centralized way to handle errors in BLoC event handlers,
/// reducing boilerplate and ensuring consistent error handling patterns.
///
/// **Benefits:**
/// - Eliminates repetitive try-catch blocks
/// - Automatically logs errors with technical details
/// - Converts AppException to localized error keys
/// - Handles unexpected errors gracefully
///
/// **Usage:**
/// ```dart
/// class MyBloc extends Bloc<MyEvent, MyState> with BlocErrorHandler<MyState> {
///   Future<void> _onEvent(MyEvent event, Emitter<MyState> emit) async {
///     await handleError(
///       emit: emit,
///       action: () async {
///         final result = await _useCase();
///         emit(MyState.success(result));
///       },
///       onError: (errorKey) => MyState.error(errorKey),
///     );
///   }
/// }
/// ```
mixin BlocErrorHandler<S> {
  /// Executes an async action with centralized error handling.
  ///
  /// [emit] - The state emitter from the event handler
  /// [action] - The async operation to execute (e.g., calling a use case)
  /// [onError] - Function that creates an error state from the error message key
  /// [loggerName] - Optional custom logger name (defaults to BLoC class name)
  ///
  /// **Error Flow:**
  /// 1. Executes the action
  /// 2. If AppException is thrown:
  ///    - Logs error code and technical message
  ///    - Emits error state with localization key
  /// 3. If unexpected error is thrown:
  ///    - Logs full error and stack trace
  ///    - Emits generic error state
  Future<void> handleError({
    required void Function(S) emit,
    required Future<void> Function() action,
    required S Function(String errorKey) onError,
    String? loggerName,
  }) async {
    try {
      await action();
    } on AppException catch (e) {
      // Known application error - log technical details
      developer.log(
        'Error ${e.code}: ${e.technicalMessage}',
        name: loggerName ?? runtimeType.toString(),
      );
      // Emit error state with localization key
      emit(onError(e.messageKey));
    } catch (e, stackTrace) {
      // Unexpected error - log everything for debugging
      developer.log(
        'Unexpected error: $e',
        name: loggerName ?? runtimeType.toString(),
        error: e,
        stackTrace: stackTrace,
      );
      // Emit generic error state
      emit(onError('errors.generic.unknown'));
    }
  }

  /// Executes an async action with error handling and returns a result.
  ///
  /// This variant is useful when you need to get a value from the action
  /// and handle errors without emitting states.
  ///
  /// Returns the result of the action, or null if an error occurred.
  ///
  /// **Usage:**
  /// ```dart
  /// final user = await handleErrorWithResult(
  ///   action: () => _getUserUseCase(),
  ///   onError: (errorKey) {
  ///     emit(MyState.error(errorKey));
  ///   },
  /// );
  /// if (user != null) {
  ///   emit(MyState.success(user));
  /// }
  /// ```
  Future<T?> handleErrorWithResult<T>({
    required Future<T> Function() action,
    required void Function(String errorKey) onError,
    String? loggerName,
  }) async {
    try {
      return await action();
    } on AppException catch (e) {
      developer.log(
        'Error ${e.code}: ${e.technicalMessage}',
        name: loggerName ?? runtimeType.toString(),
      );
      onError(e.messageKey);
      return null;
    } catch (e, stackTrace) {
      developer.log(
        'Unexpected error: $e',
        name: loggerName ?? runtimeType.toString(),
        error: e,
        stackTrace: stackTrace,
      );
      onError('errors.generic.unknown');
      return null;
    }
  }
}

/// Mixin to safely add events to a [Bloc] by checking if it is closed.
///
/// This prevents the [StateError] "Cannot add new events after calling close".
mixin SafeBloc<E, S> on Bloc<E, S> {
  @override
  void add(E event) {
    if (!isClosed) {
      super.add(event);
    } else {
      developer.log(
        'SafeBloc: Attempted to add event $event to closed Bloc $runtimeType',
        name: runtimeType.toString(),
        level: 900, // Warning level
      );
    }
  }
}
