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

/// Coordinates Today Screen Controller state for the FocusFlow Flutter UI.
library;

import 'dart:async';

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

import '../models/today_screen_models.dart';
import 'selected_date_controller.dart';

/// Reads Today state from the scheduler application layer.
typedef TodayStateReader =
    Future<ApplicationResult<TodayState>> Function(CivilDate date);

/// Formats a selected date for Today screen loading and empty states.
typedef TodayDateLabelFormatter = String Function(CivilDate date);

/// Base state for the compact Today screen.
sealed class TodayScreenState {
  /// Creates a Today screen state.
  const TodayScreenState();
}

/// Indicates that the Today screen is loading.
class TodayScreenLoading extends TodayScreenState {
  /// Creates a loading Today screen state.
  const TodayScreenLoading(this.dateLabel);

  /// Display-ready date label for the read in progress.
  final String dateLabel;
}

/// Indicates that the Today screen has renderable timeline data.
class TodayScreenReady extends TodayScreenState {
  /// Creates a ready state with mapped screen [data].
  const TodayScreenReady(this.data);

  /// Presentation model for the Today screen.
  final TodayScreenData data;
}

/// Indicates that the Today screen loaded successfully with no cards.
class TodayScreenEmpty extends TodayScreenState {
  /// Creates an empty Today screen state.
  const TodayScreenEmpty(this.data);

  /// Empty presentation model for the selected date.
  final TodayScreenData data;
}

/// Indicates that the Today screen failed to load.
class TodayScreenFailure extends TodayScreenState {
  /// Creates a failure state with a displayable [message].
  const TodayScreenFailure({required this.message, required this.data});

  /// User-facing failure message or code.
  final String message;

  /// Empty presentation model for the selected date that failed to load.
  final TodayScreenData data;
}

/// Loads Today screen data and tracks selected timeline cards.
class TodayScreenController extends ChangeNotifier {
  /// Creates a Today screen controller from a read callback.
  TodayScreenController({
    required this.read,
    required this.selectedDates,
    required this.dateLabelFor,
    this.timeZoneOffset = Duration.zero,
    DateTime Function()? now,
  }) : now = now ?? _utcNow,
       _state = TodayScreenLoading(dateLabelFor(selectedDates.date)) {
    selectedDates.addListener(_handleSelectedDateChanged);
  }

  /// Callback used to read Today state.
  final TodayStateReader read;

  /// Selected planning date that drives Today reads.
  final SelectedDateController selectedDates;

  /// Formats dates before a backend read has returned screen data.
  final TodayDateLabelFormatter dateLabelFor;

  /// Clock used while mapping time-sensitive presentation state.
  final DateTime Function() now;

  /// UI-local offset used to format timeline instants as wall-clock values.
  final Duration timeZoneOffset;

  /// 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.
  TodayScreenState _state;

  /// Private state stored as `_selectedCard` for this implementation.
  /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
  TimelineCardModel? _selectedCard;

  /// Private state stored as `_loadSequence` for this implementation.
  /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
  var _loadSequence = 0;

  /// Private state stored as `_isDisposed` for this implementation.
  /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
  var _isDisposed = false;

  /// Current Today screen loading/data/failure state.
  TodayScreenState get state => _state;

  /// Currently selected timeline card, if a modal should be visible.
  TimelineCardModel? get selectedCard => _selectedCard;

  /// Current selected owner-local planning date.
  CivilDate get selectedDate => selectedDates.date;

  /// Loads Today state and maps it into presentation data.
  Future<void> load() async {
    final date = selectedDates.date;
    final sequence = _nextLoadSequence();
    logger.debug(
      () =>
          'Today screen load started. date=${date.toIsoString()} sequence=$sequence',
    );
    _state = TodayScreenLoading(dateLabelFor(date));
    notifyListeners();
    try {
      final result = await read(date);
      if (_isStale(sequence)) {
        logger.finer(
          () =>
              'Ignoring stale Today screen load result. '
              'date=${date.toIsoString()} sequence=$sequence currentSequence=$_loadSequence',
        );
        return;
      }
      final failure = result.failure;
      if (failure != null) {
        logger.warn(
          () =>
              'Today screen load returned failure. '
              'date=${date.toIsoString()} code=${failure.code.name} '
              'detailCode=${failure.detailCode} entityId=${failure.entityId}',
        );
        _state = TodayScreenFailure(
          message: failure.detailCode ?? failure.code.name,
          data: _emptyDataFor(date),
        );
        notifyListeners();
        return;
      }

      final data = TodayScreenData.fromTodayState(
        result.requireValue,
        now: now(),
        timeZoneOffset: timeZoneOffset,
      );
      _syncSelectedCard(data);
      _state = data.cards.isEmpty
          ? TodayScreenEmpty(data)
          : TodayScreenReady(data);
      logger.debug(
        () =>
            'Today screen load finished. date=${date.toIsoString()} '
            'sequence=$sequence cardCount=${data.cards.length}',
      );
      notifyListeners();
    } on Object catch (error, stackTrace) {
      if (_isStale(sequence)) {
        logger.finer(
          () =>
              'Ignoring stale Today screen load exception. '
              'date=${date.toIsoString()} sequence=$sequence currentSequence=$_loadSequence',
        );
        return;
      }
      logger.error(
        () =>
            'Today screen load threw unexpectedly. date=${date.toIsoString()} sequence=$sequence',
        error: error,
        stackTrace: stackTrace,
      );
      _state = TodayScreenFailure(
        message: error.toString(),
        data: _emptyDataFor(date),
      );
      notifyListeners();
    }
  }

  /// Selects [card] when the card allows selection.
  void selectCard(TimelineCardModel card) {
    if (!card.isSelectable) {
      logger.finer(
        () => 'Today screen card selection ignored. cardId=${card.id}',
      );
      return;
    }
    logger.finer(() => 'Today screen card selected. cardId=${card.id}');
    _selectedCard = card;
    notifyListeners();
  }

  /// Clears the selected timeline card.
  void clearSelection() {
    if (_selectedCard == null) {
      return;
    }
    logger.finer(
      () => 'Today screen card selection cleared. cardId=${_selectedCard!.id}',
    );
    _selectedCard = null;
    notifyListeners();
  }

  /// Releases selected-date listeners owned by this controller.
  @override
  void dispose() {
    _isDisposed = true;
    selectedDates.removeListener(_handleSelectedDateChanged);
    super.dispose();
  }

  /// Runs the `_handleSelectedDateChanged` 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 _handleSelectedDateChanged() {
    logger.debug(
      () =>
          'Today screen selected date changed. date=${selectedDates.date.toIsoString()}',
    );
    _selectedCard = null;
    unawaited(load());
  }

  /// Runs the `_nextLoadSequence` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  int _nextLoadSequence() {
    _loadSequence += 1;
    return _loadSequence;
  }

  /// Runs the `_isStale` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  bool _isStale(int sequence) {
    return _isDisposed || sequence != _loadSequence;
  }

  /// Runs the `_emptyDataFor` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  TodayScreenData _emptyDataFor(CivilDate date) {
    return TodayScreenData.empty(
      dateLabel: dateLabelFor(date),
      timeZoneOffset: timeZoneOffset,
    );
  }

  /// Runs the `_syncSelectedCard` 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 _syncSelectedCard(TodayScreenData data) {
    final selected = _selectedCard;
    if (selected == null) {
      return;
    }
    for (final card in data.cards) {
      if (card.id == selected.id) {
        _selectedCard = card;
        return;
      }
    }
    logger.finer(
      () =>
          'Today screen selected card no longer present. cardId=${selected.id}',
    );
    _selectedCard = null;
  }

  /// Runs the `_utcNow` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  static DateTime _utcNow() => DateTime.now().toUtc();
}
