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

part of '../../models.dart';

/// Starter time range value used by scheduling helpers.
///
/// [TimeInterval] is the scheduler's neutral representation of a time span. It
/// is used for scheduled task slots, locked blocks, required visible blocks, and
/// candidate placements. The interval convention is start-inclusive and
/// end-exclusive, which avoids treating two back-to-back blocks as overlapping.
class TimeInterval {
  /// Creates a `TimeInterval` instance with the values required by its invariants.
  /// Callers should pass already-normalized scheduler data because validation and defaulting belong at the model or service boundary, not in arbitrary UI code.
  TimeInterval({
    required this.start,
    required this.end,
    this.label,
  }) {
    if (!start.isBefore(end)) {
      throw DomainValidationException(
        code: DomainValidationCode.invalidTimeInterval,
        invalidValue: {'start': start, 'end': end},
        name: 'TimeInterval',
        message: 'Interval end must be after interval start.',
      );
    }
  }

  /// Inclusive beginning of the interval.
  final DateTime start;

  /// Exclusive ending of the interval.
  final DateTime end;

  /// Optional debug/UI label, often a task id or locked block name.
  final String? label;

  /// Raw duration between [start] and [end]. Callers are responsible for only
  /// constructing meaningful positive intervals when required by a rule.
  Duration get duration => end.difference(start);

  /// Whether this interval shares any actual time with [other].
  ///
  /// Adjacent intervals do not overlap: `9:00-10:00` and `10:00-11:00` are safe
  /// to place back-to-back because the first interval's end is the second
  /// interval's start.
  bool overlaps(TimeInterval other) {
    return start.isBefore(other.end) && end.isAfter(other.start);
  }
}

/// Top-level helper that performs the `_requiredTrimmed` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
String _requiredTrimmed(
  String value,
  String name,
  DomainValidationCode code,
  String message,
) {
  final trimmed = value.trim();
  if (trimmed.isEmpty) {
    throw DomainValidationException(
      code: code,
      invalidValue: value,
      name: name,
      message: message,
    );
  }
  return trimmed;
}

/// Top-level helper that performs the `_nullableTrimmed` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
String? _nullableTrimmed(
  String? value,
  String name,
  DomainValidationCode code,
  String message,
) {
  if (value == null) {
    return null;
  }
  final trimmed = value.trim();
  if (trimmed.isEmpty) {
    throw DomainValidationException(
      code: code,
      invalidValue: value,
      name: name,
      message: message,
    );
  }
  return trimmed;
}

/// Top-level helper that performs the `_validatePositiveDuration` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
void _validatePositiveDuration(int? durationMinutes, String name) {
  if (durationMinutes == null) {
    return;
  }
  if (durationMinutes <= 0) {
    throw DomainValidationException(
      code: DomainValidationCode.nonPositiveDuration,
      invalidValue: durationMinutes,
      name: name,
      message: 'Duration must be positive when present.',
    );
  }
}

/// Top-level helper that performs the `_validateOptionalInterval` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
void _validateOptionalInterval({
  required DateTime? start,
  required DateTime? end,
  required DomainValidationCode partialCode,
  required DomainValidationCode invalidCode,
  required String name,
  required String partialMessage,
  required String invalidMessage,
}) {
  if ((start == null) != (end == null)) {
    throw DomainValidationException(
      code: partialCode,
      invalidValue: {'start': start, 'end': end},
      name: name,
      message: partialMessage,
    );
  }
  if (start != null && end != null && !start.isBefore(end)) {
    throw DomainValidationException(
      code: invalidCode,
      invalidValue: {'start': start, 'end': end},
      name: name,
      message: invalidMessage,
    );
  }
}
