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

/// Renders Timeline Geometry pieces for the FocusFlow Flutter timeline.
library;

/// Converts timeline clock values into vertical pixel positions.
class TimelineGeometry {
  /// Creates geometry for a visible time range.
  const TimelineGeometry({
    required this.startMinutes,
    required this.endMinutes,
    required this.pixelsPerMinute,
    this.minCardHeight = 0,
  }) : assert(startMinutes >= 0),
       assert(endMinutes <= 24 * 60),
       assert(endMinutes > startMinutes),
       assert(minCardHeight >= 0);

  /// First visible minute from midnight.
  final int startMinutes;

  /// Last visible minute from midnight.
  final int endMinutes;

  /// Vertical scale used to convert minutes into pixels.
  final double pixelsPerMinute;

  /// Minimum rendered height for a timeline card.
  ///
  /// Defaults to zero so scheduled cards match their exact duration on the
  /// timeline.
  final double minCardHeight;

  /// Total vertical height of the visible timeline.
  double get totalHeight => (endMinutes - startMinutes) * pixelsPerMinute;

  /// Returns the vertical offset for [minutes] since midnight.
  double yForMinutesSinceMidnight(int minutes) {
    return (minutes - startMinutes) * pixelsPerMinute;
  }

  /// Returns the rendered height for a duration in minutes.
  double heightForDuration(int minutes) {
    final height = minutes * pixelsPerMinute;
    return height < minCardHeight ? minCardHeight : height;
  }

  /// Generates timeline ticks at [stepMinutes] intervals.
  List<TimelineTick> ticks({int stepMinutes = 15}) {
    return [
      for (
        var minute = startMinutes;
        minute <= endMinutes;
        minute += stepMinutes
      )
        TimelineTick(
          minutesSinceMidnight: minute,
          y: yForMinutesSinceMidnight(minute),
          label: _labelFor(minute),
          major: minute % 60 == 0,
        ),
    ];
  }

  /// 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 identical(this, other) ||
        other is TimelineGeometry &&
            other.startMinutes == startMinutes &&
            other.endMinutes == endMinutes &&
            other.pixelsPerMinute == pixelsPerMinute &&
            other.minCardHeight == minCardHeight;
  }

  /// 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 {
    return Object.hash(
      startMinutes,
      endMinutes,
      pixelsPerMinute,
      minCardHeight,
    );
  }

  /// Runs the `_labelFor` 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 String _labelFor(int minutes) {
    final hour24 = (minutes ~/ 60) % 24;
    final minute = minutes % 60;
    final period = hour24 >= 12 ? 'PM' : 'AM';
    final rawHour = hour24 % 12;
    final hour = rawHour == 0 ? 12 : rawHour;
    if (minute == 0) {
      return '$hour:00 $period';
    }
    return '$hour:${minute.toString().padLeft(2, '0')}';
  }
}

/// A labeled tick on the compact timeline axis.
class TimelineTick {
  /// Creates a timeline tick.
  const TimelineTick({
    required this.minutesSinceMidnight,
    required this.y,
    required this.label,
    required this.major,
  });

  /// Minute from midnight represented by this tick.
  final int minutesSinceMidnight;

  /// Vertical pixel offset for this tick.
  final double y;

  /// Display label for this tick.
  final String label;

  /// Whether this tick represents a major interval.
  final bool major;
}
