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

part of '../timeline_view.dart';

/// Private implementation type for `_TimelineCardPlacement` in this library.
/// It keeps supporting state and behavior scoped to the owning file so the public API stays focused on the scheduler concepts that callers use directly.
class _TimelineCardPlacement {
  /// Creates a `_TimelineCardPlacement` 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.
  const _TimelineCardPlacement({
    required this.card,
    required this.lane,
    required this.laneCount,
    required this.top,
    required this.height,
  });

  /// Shared constant value for `_laneGap`.
  /// The constant gives this repeated scheduler or UI value one documented source of truth instead of scattering literals across call sites.
  static const _laneGap = 8.0;

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

  /// Stores the `lane` 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 lane;

  /// Stores the `laneCount` 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 laneCount;

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

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

  /// Performs the `withCard` 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.
  _TimelineCardPlacement withCard(TimelineCardModel nextCard) {
    if (identical(card, nextCard)) {
      return this;
    }
    return _TimelineCardPlacement(
      card: nextCard,
      lane: lane,
      laneCount: laneCount,
      top: top,
      height: height,
    );
  }

  /// Performs the `pack` 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.
  static List<_TimelineCardPlacement> pack(
    List<TimelineCardModel> cards, {
    required TimelineGeometry geometry,
  }) {
    if (cards.isEmpty) {
      return const [];
    }
    final sorted = [...cards]
      ..sort((a, b) {
        final startComparison = a.startMinutes.compareTo(b.startMinutes);
        if (startComparison != 0) {
          return startComparison;
        }
        final endComparison = a.endMinutes.compareTo(b.endMinutes);
        if (endComparison != 0) {
          return endComparison;
        }
        return a.id.compareTo(b.id);
      });
    final placements = <_TimelineCardPlacement>[];
    var groupStart = 0;
    var groupEnd = _visualEnd(sorted.first, geometry);
    for (var index = 1; index < sorted.length; index += 1) {
      final card = sorted[index];
      final cardStart = _visualStart(card, geometry);
      final cardEnd = _visualEnd(card, geometry);
      if (cardStart < groupEnd) {
        if (cardEnd > groupEnd) {
          groupEnd = cardEnd;
        }
        continue;
      }
      placements.addAll(
        _packGroup(sorted.sublist(groupStart, index), geometry: geometry),
      );
      groupStart = index;
      groupEnd = cardEnd;
    }
    placements.addAll(
      _packGroup(sorted.sublist(groupStart), geometry: geometry),
    );
    return placements;
  }

  /// Runs the `_packGroup` 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 List<_TimelineCardPlacement> _packGroup(
    List<TimelineCardModel> group, {
    required TimelineGeometry geometry,
  }) {
    final laneEnds = <double>[];
    final laneByCard = <TimelineCardModel, int>{};
    for (final card in group) {
      final cardStart = _visualStart(card, geometry);
      final cardEnd = _visualEnd(card, geometry);
      var assignedLane = -1;
      for (var lane = 0; lane < laneEnds.length; lane += 1) {
        if (laneEnds[lane] <= cardStart) {
          assignedLane = lane;
          break;
        }
      }
      if (assignedLane == -1) {
        assignedLane = laneEnds.length;
        laneEnds.add(cardEnd);
      } else {
        laneEnds[assignedLane] = cardEnd;
      }
      laneByCard[card] = assignedLane;
    }
    final laneCount = laneEnds.length;
    return [
      for (final card in group)
        _TimelineCardPlacement(
          card: card,
          lane: laneByCard[card]!,
          laneCount: laneCount,
          top: _visualStart(card, geometry),
          height: geometry.heightForDuration(
            card.endMinutes - card.startMinutes,
          ),
        ),
    ];
  }

  /// Runs the `_visualStart` 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 double _visualStart(
    TimelineCardModel card,
    TimelineGeometry geometry,
  ) {
    return geometry.yForMinutesSinceMidnight(card.startMinutes);
  }

  /// Runs the `_visualEnd` 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 double _visualEnd(TimelineCardModel card, TimelineGeometry geometry) {
    final duration = card.endMinutes - card.startMinutes;
    return _visualStart(card, geometry) + geometry.heightForDuration(duration);
  }

  /// Performs the `left` 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.
  double left(double trackWidth) {
    final laneWidth = _laneWidth(trackWidth);
    final laneGap = _laneGapForWidth(trackWidth);
    return FocusFlowTokens.cardHorizontalPadding + lane * (laneWidth + laneGap);
  }

  /// Performs the `width` 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.
  double width(double trackWidth) {
    return _laneWidth(trackWidth);
  }

  /// Runs the `_availableWidth` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  double _availableWidth(double trackWidth) {
    final availableWidth = trackWidth - FocusFlowTokens.cardHorizontalPadding;
    if (availableWidth < 0) {
      return 0;
    }
    return availableWidth;
  }

  /// Runs the `_laneWidth` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  double _laneWidth(double trackWidth) {
    final availableWidth = _availableWidth(trackWidth);
    final laneGap = _laneGapForWidth(trackWidth);
    return (availableWidth - laneGap * (laneCount - 1)) / laneCount;
  }

  /// Runs the `_laneGapForWidth` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  double _laneGapForWidth(double trackWidth) {
    if (laneCount == 1) {
      return 0;
    }
    final availableWidth = _availableWidth(trackWidth);
    final totalGap = _laneGap * (laneCount - 1);
    if (availableWidth <= totalGap) {
      return 0;
    }
    return _laneGap;
  }
}
