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

part of '../../locked_time.dart';

/// One-day change to locked time that leaves the base recurrence unchanged.
///
/// Overrides are applied during expansion for a single date only. This is safer
/// than modifying the recurring block because the normal schedule remains intact
/// for every other day.
///
/// Use the named factories to create valid override shapes:
/// - [remove] references a base block and suppresses that occurrence.
/// - [replace] references a base block and swaps its details for one day.
/// - [add] creates an extra locked occurrence that has no base block.
class LockedBlockOverride {
  /// Creates a `LockedBlockOverride` 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.
  LockedBlockOverride({
    required String id,
    required this.date,
    required this.type,
    required this.createdAt,
    required this.updatedAt,
    String? lockedBlockId,
    String? name,
    this.startTime,
    this.endTime,
    this.hiddenByDefault = true,
    this.projectId,
  })  : id =
            _requiredLockedString(id, 'id', DomainValidationCode.blankStableId),
        lockedBlockId = _nullableLockedString(
          lockedBlockId,
          'lockedBlockId',
          DomainValidationCode.blankStableId,
        ),
        name = _nullableLockedString(
          name,
          'name',
          DomainValidationCode.blankName,
        ) {
    _validateOverrideShape(
      type: type,
      lockedBlockId: this.lockedBlockId,
      name: this.name,
      startTime: startTime,
      endTime: endTime,
    );
  }

  /// Removes a recurring occurrence for one date.
  factory LockedBlockOverride.remove({
    required String id,
    required String lockedBlockId,
    required CivilDate date,
    required DateTime createdAt,
    DateTime? updatedAt,
  }) {
    return LockedBlockOverride(
      id: id,
      lockedBlockId: lockedBlockId,
      date: date,
      type: LockedBlockOverrideType.remove,
      createdAt: createdAt,
      updatedAt: updatedAt ?? createdAt,
    );
  }

  /// Replaces a recurring occurrence with different times for one date.
  factory LockedBlockOverride.replace({
    required String id,
    required String lockedBlockId,
    required CivilDate date,
    required ClockTime startTime,
    required ClockTime endTime,
    required DateTime createdAt,
    String? name,
    bool hiddenByDefault = true,
    String? projectId,
    DateTime? updatedAt,
  }) {
    return LockedBlockOverride(
      id: id,
      lockedBlockId: lockedBlockId,
      date: date,
      type: LockedBlockOverrideType.replace,
      name: name,
      startTime: startTime,
      endTime: endTime,
      hiddenByDefault: hiddenByDefault,
      projectId: projectId,
      createdAt: createdAt,
      updatedAt: updatedAt ?? createdAt,
    );
  }

  /// Adds surprise locked time for one date.
  factory LockedBlockOverride.add({
    required String id,
    required CivilDate date,
    required String name,
    required ClockTime startTime,
    required ClockTime endTime,
    required DateTime createdAt,
    bool hiddenByDefault = true,
    String? projectId,
    DateTime? updatedAt,
  }) {
    return LockedBlockOverride(
      id: id,
      date: date,
      type: LockedBlockOverrideType.add,
      name: name,
      startTime: startTime,
      endTime: endTime,
      hiddenByDefault: hiddenByDefault,
      projectId: projectId,
      createdAt: createdAt,
      updatedAt: updatedAt ?? createdAt,
    );
  }

  /// Stable id for persistence and debugging.
  final String id;

  /// Base block id affected by remove/replace overrides. Null for add overrides.
  final String? lockedBlockId;

  /// Date the override applies to.
  final CivilDate date;

  /// Kind of override operation.
  final LockedBlockOverrideType type;

  /// Optional replacement/addition name.
  final String? name;

  /// Optional replacement/addition start time.
  final ClockTime? startTime;

  /// Optional replacement/addition end time.
  final ClockTime? endTime;

  /// Visibility hint for UI; scheduling still blocks this time.
  final bool hiddenByDefault;

  /// Optional project/category association for UI colors or reports.
  final String? projectId;

  /// Creation timestamp for persistence/auditing.
  final DateTime createdAt;

  /// Last update timestamp for persistence/auditing.
  final DateTime updatedAt;

  /// Whether this override targets [blockId] on [occurrenceDate].
  bool appliesTo({
    required String blockId,
    required CivilDate occurrenceDate,
  }) {
    return lockedBlockId == blockId && _sameDate(date, occurrenceDate);
  }

  /// Build a concrete interval for [targetDate] when this override has enough
  /// time data and applies to that date.
  ///
  /// Remove overrides intentionally return null because they do not create a new
  /// interval. Replacement/add overrides need both [startTime] and [endTime].
  TimeInterval? intervalForDate({
    required CivilDate targetDate,
    required String timeZoneId,
    required TimeZoneResolver timeZoneResolver,
    TimeZoneResolutionOptions resolutionOptions =
        const TimeZoneResolutionOptions(),
  }) {
    final start = startTime;
    final end = endTime;

    if (start == null || end == null || !_sameDate(date, targetDate)) {
      return null;
    }

    return _resolvedInterval(
      date: targetDate,
      startTime: start,
      endTime: end,
      label: name,
      timeZoneId: timeZoneId,
      timeZoneResolver: timeZoneResolver,
      resolutionOptions: resolutionOptions,
    );
  }
}
