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

part of '../../time_contracts.dart';

/// Wall-clock time without a date or timezone.
class WallTime implements Comparable<WallTime> {
  /// Creates a `WallTime` 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.
  WallTime({
    required this.hour,
    required this.minute,
  }) {
    if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60) {
      throw DomainValidationException(
        code: DomainValidationCode.invalidClockTime,
        invalidValue: {'hour': hour, 'minute': minute},
        name: 'WallTime',
        message: 'Wall time must use hour 0-23 and minute 0-59.',
      );
    }
  }

  /// Creates a `WallTime.fromIsoString` 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.
  factory WallTime.fromIsoString(String value) {
    if (!RegExp(r'^\d{2}:\d{2}$').hasMatch(value)) {
      throw DomainValidationException(
        code: DomainValidationCode.invalidClockTime,
        invalidValue: value,
        name: 'WallTime',
        message: 'Wall time must use HH:MM format.',
      );
    }
    final parts = value.split(':');
    return WallTime(hour: int.parse(parts[0]), minute: int.parse(parts[1]));
  }

  /// Stores the `hour` value for this object.
  /// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
  final int hour;

  /// Stores the `minute` value for this object.
  /// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
  final int minute;

  /// Returns the derived `minutesSinceMidnight` value for this object.
  /// Keeping this as an accessor gives callers a stable read surface while the owning type controls how the value is calculated.
  int get minutesSinceMidnight => hour * 60 + minute;

  /// Converts scheduler data for `toIsoString` without changing the source object.
  /// Conversion helpers keep persistence and export shapes explicit so domain models do not depend on storage-specific details.
  String toIsoString() {
    return '${hour.toString().padLeft(2, '0')}:'
        '${minute.toString().padLeft(2, '0')}';
  }

  /// Performs the `compareTo` behavior for this scheduler component.
  /// The method provides a named entry point for this operation so callers do not need to know the lower-level state or persistence steps involved.
  @override
  int compareTo(WallTime other) {
    return minutesSinceMidnight.compareTo(other.minutesSinceMidnight);
  }

  /// Performs the `operator ==` behavior for this scheduler component.
  /// The method provides a named entry point for this operation so callers do not need to know the lower-level state or persistence steps involved.
  @override
  bool operator ==(Object other) {
    return other is WallTime && other.hour == hour && other.minute == minute;
  }

  /// Returns the derived `hashCode` value for this object.
  /// Keeping this as an accessor gives callers a stable read surface while the owning type controls how the value is calculated.
  @override
  int get hashCode => Object.hash(hour, minute);

  /// Converts scheduler data for `toString` without changing the source object.
  /// Conversion helpers keep persistence and export shapes explicit so domain models do not depend on storage-specific details.
  @override
  String toString() => toIsoString();
}
