import 'dart:collection';

import 'package:result_monad/result_monad.dart';

import '../models/exec_error.dart';
import 'paged_response.dart';
import 'paging_data.dart';

class PagesManager<TResult, TID> {
  final _pages = <PagedResponse<bool>>[];
  final List<TID> Function(TResult) idMapper;
  final FutureResult<PagedResponse<TResult>, ExecError> Function(PagingData)
      onRequest;

  PagesManager({
    required this.idMapper,
    required this.onRequest,
    List<PagedResponse>? initialPages,
  }) {
    if (initialPages == null) {
      return;
    }

    initialPages.map((op) => _fromResultPage(op)).forEach(_pages.add);
  }

  UnmodifiableListView<PagedResponse> get pages => UnmodifiableListView(_pages);

  void clear() {
    _pages.clear();
  }

  PagedResponse<bool> _fromResultPage(PagedResponse newPage) {
    return PagedResponse(
      true,
      id: newPage.id,
      previous: newPage.previous,
      next: newPage.next,
    );
  }

  FutureResult<PagedResponse<TResult>, ExecError> initialize(int limit) async {
    if (_pages.isNotEmpty) {
      return buildErrorResult(
          type: ErrorType.rangeError,
          message: 'Cannot initialize a loaded manager');
    }
    final result = await onRequest(PagingData(limit: limit));
    if (result.isSuccess) {
      if (result.value.previous != null || result.value.next != null) {
        final newPage = result.value.map((data) => idMapper(data));
        _pages.add(_fromResultPage(newPage));
      }
    }
    return result;
  }

  FutureResult<PagedResponse<TResult>, ExecError> nextWithPage(
      PagedResponse<List<TID>> currentPage) async {
    return _previousOrNext(currentPage.id, false);
  }

  FutureResult<PagedResponse<TResult>, ExecError> previousWithPage(
      PagedResponse<List<TID>> currentPage) async {
    return _previousOrNext(currentPage.id, true);
  }

  FutureResult<PagedResponse<TResult>, ExecError> nextWithResult(
      PagedResponse<TResult> currentPage) async {
    return _previousOrNext(currentPage.id, false);
  }

  FutureResult<PagedResponse<TResult>, ExecError> previousWithResult(
      PagedResponse<TResult> currentPage) async {
    return _previousOrNext(currentPage.id, true);
  }

  FutureResult<PagedResponse<TResult>, ExecError> nextFromEnd() async {
    if (_pages.isEmpty) {
      return buildErrorResult(type: ErrorType.rangeError);
    }
    return _previousOrNext(_pages.last.id, false);
  }

  FutureResult<PagedResponse<TResult>, ExecError>
      previousFromBeginning() async {
    if (_pages.isEmpty) {
      return buildErrorResult(type: ErrorType.rangeError);
    }
    return _previousOrNext(_pages.first.id, true);
  }

  FutureResult<PagedResponse<TResult>, ExecError> _previousOrNext(
      String id, bool asPrevious) async {
    final currentIndex = _pages.indexWhere((p) => p.id == id);
    if (currentIndex < 0) {
      return buildErrorResult(
        type: ErrorType.notFound,
        message: 'Passed in page is not part of this manager',
      );
    }

    final currentPage = _pages[currentIndex];
    final newPagingData = asPrevious ? currentPage.previous : currentPage.next;
    if (newPagingData == null) {
      return buildErrorResult(
        type: ErrorType.rangeError,
        message: asPrevious ? 'No previous page' : 'No next page',
      );
    }

    final result = await onRequest(newPagingData);
    if (result.isSuccess && result.value.hasMorePages) {
      final newPage = result.value.map((data) => idMapper(data));
      if (asPrevious) {
        _pages.insert(currentIndex, _fromResultPage(newPage));
      } else {
        _pages.insert(currentIndex + 1, _fromResultPage(newPage));
      }
    }
    return result;
  }
}
