import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart' hide Order;
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order;

import '../../../domain/order/order.dart';

part 'order_loader_event.dart';
part 'order_loader_state.dart';
part 'order_loader_bloc.freezed.dart';

@injectable
class OrderLoaderBloc extends Bloc<OrderLoaderEvent, OrderLoaderState> {
  final IOrderRepository _repository;
  OrderLoaderBloc(this._repository) : super(OrderLoaderState.initial()) {
    on<OrderLoaderEvent>(_onOrderLoaderEvent);
  }

  Future<void> _onOrderLoaderEvent(
    OrderLoaderEvent event,
    Emitter<OrderLoaderState> emit,
  ) {
    return event.map(
      setSelectedOrder: (e) async {
        emit(state.copyWith(selectedOrder: e.order));
      },
      dateTimeRangeChange: (e) async {
        emit(state.copyWith(startDate: e.startDate, endDate: e.endDate));
      },
      searchChange: (e) async {
        emit(state.copyWith(search: e.search));
      },
      fetched: (e) async {
        var newState = state;

        if (e.isRefresh) {
          newState = newState.copyWith(isFetching: true);
          emit(newState);
        }

        newState = await _mapFetchedToState(
          newState,
          isRefresh: e.isRefresh,
          status: e.status,
        );
        emit(newState);
      },
      getById: (e) async {
        emit(state.copyWith(isFetchingById: true, failureOption: none()));

        final failureOrOrder = await _repository.getOrderById(e.id);
        failureOrOrder.fold(
          (f) => emit(
            state.copyWith(failureOption: optionOf(f), isFetching: false),
          ),
          (order) => emit(
            state.copyWith(
              order: order,
              isFetching: false,
              failureOption: none(),
            ),
          ),
        );

        emit(state.copyWith(isFetchingById: false));
      },
    );
  }

  Future<OrderLoaderState> _mapFetchedToState(
    OrderLoaderState state, {
    bool isRefresh = false,
    String status = 'completed',
  }) async {
    state = state.copyWith(isFetching: false);

    if (state.hasReachedMax && state.orders.isNotEmpty && !isRefresh) {
      return state;
    }

    if (isRefresh) {
      state = state.copyWith(
        page: 1,
        failureOption: none(),
        hasReachedMax: false,
        orders: [],
      );
    }

    final failureOrTable = await _repository.getOrders(
      page: state.page,
      status: status,
      startDate: state.startDate,
      endDate: state.endDate,
      search: state.search,
    );

    state = failureOrTable.fold(
      (f) {
        if (state.orders.isNotEmpty) {
          return state.copyWith(hasReachedMax: true);
        }
        return state.copyWith(failureOption: optionOf(f));
      },
      (orders) {
        return state.copyWith(
          orders: List.from(state.orders)..addAll(orders.orders),
          failureOption: none(),
          page: state.page + 1,
          hasReachedMax: orders.orders.length < 10,
          totalOrder: orders.totalCount,
        );
      },
    );

    return state;
  }
}
