// SPDX-FileCopyrightText: 2026 FocusFlow contributors
// SPDX-License-Identifier: AGPL-3.0-only

/// Coordinates Scheduler Read Controller state for the FocusFlow Flutter UI.
library;

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

/// Reads an application value for a UI surface.
typedef ApplicationReader<T> = Future<ApplicationResult<T>> Function();

/// Determines whether a successful read should be rendered as empty state.
typedef EmptyPredicate<T> = bool Function(T value);

/// Shared interface for read controllers consumed by UI widgets.
abstract class UiReadController<T> extends ChangeNotifier {
  /// Current read state.
  SchedulerReadState<T> get state;

  /// Loads the backing value.
  Future<void> load();

  /// Retries the latest read.
  Future<void> retry();
}

/// Base state for scheduler read operations.
sealed class SchedulerReadState<T> {
  /// Creates a scheduler read state.
  const SchedulerReadState();
}

/// Indicates that a scheduler read is in progress.
class SchedulerReadLoading<T> extends SchedulerReadState<T> {
  /// Creates a loading read state.
  const SchedulerReadLoading();
}

/// Indicates that a scheduler read returned renderable data.
class SchedulerReadData<T> extends SchedulerReadState<T> {
  /// Creates a data state from [value].
  const SchedulerReadData(this.value);

  /// Value returned by the read operation.
  final T value;
}

/// Indicates that a scheduler read succeeded but has no primary content.
class SchedulerReadEmpty<T> extends SchedulerReadState<T> {
  /// Creates an empty state that still carries the read [value].
  const SchedulerReadEmpty(this.value);

  /// Value returned by the read operation.
  final T value;
}

/// Indicates that a scheduler read failed.
class SchedulerReadFailure<T> extends SchedulerReadState<T> {
  /// Creates a failure state from an application [failure] or unexpected [error].
  const SchedulerReadFailure({this.failure, this.error});

  /// Typed application failure returned by the scheduler core.
  final ApplicationFailure? failure;

  /// Unexpected error thrown while running the read.
  final Object? error;

  /// Stable label for display and diagnostics.
  String get codeLabel {
    final typed = failure;
    if (typed != null) {
      return typed.detailCode ?? typed.code.name;
    }
    return 'unexpected';
  }
}

/// Default read controller backed by an [ApplicationReader].
class ApplicationReadController<T> extends UiReadController<T> {
  /// Creates a controller from a read callback and empty-state predicate.
  ApplicationReadController({required this.read, required this.isEmpty});

  /// Callback used to load the application value.
  final ApplicationReader<T> read;

  /// Predicate used to classify a successful value as empty or populated.
  final EmptyPredicate<T> isEmpty;

  /// Private state stored as `_state` for this implementation.
  /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
  SchedulerReadState<T> _state = SchedulerReadLoading<T>();

  /// Current read state.
  @override
  SchedulerReadState<T> get state => _state;

  /// Loads the current value and updates [state].
  @override
  Future<void> load() async {
    logger.debug(() => 'Scheduler read started. valueType=$T');
    _setState(SchedulerReadLoading<T>());
    try {
      final result = await read();
      final failure = result.failure;
      if (failure != null) {
        logger.warn(
          () =>
              'Scheduler read returned failure. valueType=$T '
              'code=${failure.code.name} detailCode=${failure.detailCode} '
              'entityId=${failure.entityId}',
        );
        _setState(SchedulerReadFailure<T>(failure: failure));
        return;
      }
      final value = result.requireValue;
      final empty = isEmpty(value);
      logger.debug(
        () => 'Scheduler read finished. valueType=$T isEmpty=$empty',
      );
      _setState(
        empty ? SchedulerReadEmpty<T>(value) : SchedulerReadData<T>(value),
      );
    } on Object catch (error, stackTrace) {
      logger.error(
        () => 'Scheduler read threw unexpectedly. valueType=$T',
        error: error,
        stackTrace: stackTrace,
      );
      _setState(SchedulerReadFailure<T>(error: error));
    }
  }

  /// Retries by running [load] again.
  @override
  Future<void> retry() => load();

  /// Runs the `_setState` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  void _setState(SchedulerReadState<T> next) {
    _state = next;
    notifyListeners();
  }
}
