import 'package:flutter/foundation.dart';
import 'package:scheduler_core/scheduler_core.dart';

typedef ApplicationReader<T> = Future<ApplicationResult<T>> Function();
typedef EmptyPredicate<T> = bool Function(T value);

abstract class UiReadController<T> extends ChangeNotifier {
  SchedulerReadState<T> get state;

  Future<void> load();

  Future<void> retry();
}

sealed class SchedulerReadState<T> {
  const SchedulerReadState();
}

class SchedulerReadLoading<T> extends SchedulerReadState<T> {
  const SchedulerReadLoading();
}

class SchedulerReadData<T> extends SchedulerReadState<T> {
  const SchedulerReadData(this.value);

  final T value;
}

class SchedulerReadEmpty<T> extends SchedulerReadState<T> {
  const SchedulerReadEmpty(this.value);

  final T value;
}

class SchedulerReadFailure<T> extends SchedulerReadState<T> {
  const SchedulerReadFailure({this.failure, this.error});

  final ApplicationFailure? failure;
  final Object? error;

  String get codeLabel {
    final typed = failure;
    if (typed != null) {
      return typed.detailCode ?? typed.code.name;
    }
    return 'unexpected';
  }
}

class ApplicationReadController<T> extends UiReadController<T> {
  ApplicationReadController({required this.read, required this.isEmpty});

  final ApplicationReader<T> read;
  final EmptyPredicate<T> isEmpty;

  SchedulerReadState<T> _state = SchedulerReadLoading<T>();

  @override
  SchedulerReadState<T> get state => _state;

  @override
  Future<void> load() async {
    _setState(SchedulerReadLoading<T>());
    try {
      final result = await read();
      final failure = result.failure;
      if (failure != null) {
        _setState(SchedulerReadFailure<T>(failure: failure));
        return;
      }
      final value = result.requireValue;
      _setState(
        isEmpty(value)
            ? SchedulerReadEmpty<T>(value)
            : SchedulerReadData<T>(value),
      );
    } on Object catch (error) {
      _setState(SchedulerReadFailure<T>(error: error));
    }
  }

  @override
  Future<void> retry() => load();

  void _setState(SchedulerReadState<T> next) {
    _state = next;
    notifyListeners();
  }
}
