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

part of '../task_timeline_card.dart';

/// Private implementation type for `_CardMetrics` 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 _CardMetrics {
  /// Creates a `_CardMetrics` 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 _CardMetrics({required this.scale, required this.height});

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

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

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

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

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

  /// Creates a `_CardMetrics.fromConstraints` 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 _CardMetrics.fromConstraints(
    BoxConstraints constraints, {
    required TaskVisualKind kind,
  }) {
    final width = constraints.hasBoundedWidth
        ? constraints.maxWidth
        : _referenceWidth;
    final height = constraints.hasBoundedHeight
        ? constraints.maxHeight
        : _referenceHeight;
    final widthScale = width / _referenceWidth;
    final heightScale =
        height /
        (kind == TaskVisualKind.freeSlot
            ? _freeSlotReferenceHeight
            : _referenceHeight);
    final scale = math.min(widthScale, heightScale).clamp(_minimumScale, 1.0);
    return _CardMetrics(scale: scale.toDouble(), height: height);
  }

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

  /// 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;

  /// Returns the derived `isCompact` 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.
  bool get isCompact => height < _compactHeightThreshold;

  /// Returns the derived `cardRadius` 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.
  double get cardRadius =>
      math.min(FocusFlowTokens.cardRadius * scale, height / 2);

  /// Returns the derived `borderWidth` 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.
  double get borderWidth => math.max(0.75, 1.2 * scale);

  /// Returns the derived `shadowBlur` 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.
  double get shadowBlur => isCompact ? 0 : 18 * scale;

  /// Returns the derived `shadowSpread` 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.
  double get shadowSpread => isCompact ? 0 : scale;

  /// Returns the derived `statusRingSize` 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.
  double get statusRingSize {
    final availableHeight = math.max(8, height - padding.vertical);
    return math.min(24, availableHeight * 0.78).toDouble();
  }

  /// Returns the derived `statusBorderWidth` 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.
  double get statusBorderWidth => math.max(1.4, statusRingSize * 0.08);

  /// Returns the derived `statusIconSize` 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.
  double get statusIconSize => statusRingSize * 0.58;

  /// Returns the derived `statusGap` 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.
  double get statusGap => isCompact ? 7 : 22 * scale;

  /// Returns the derived `actionButtonSize` 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.
  double get actionButtonSize => (30 * scale).clamp(14.0, 22.0).toDouble();

  /// Returns the derived `actionIconSize` 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.
  double get actionIconSize => (16 * scale).clamp(9.0, 13.0).toDouble();

  /// Returns the derived `actionGap` 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.
  double get actionGap => math.max(3, 5 * scale);

  /// Returns the derived `actionsWidth` 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.
  double get actionsWidth => actionButtonSize * 3 + actionGap;

  /// Returns the derived `pushButtonHeight` 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.
  double get pushButtonHeight => (28 * scale).clamp(18.0, 24.0).toDouble();

  /// Returns the derived `pushButtonWidth` 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.
  double get pushButtonWidth => (62 * scale).clamp(42.0, 58.0).toDouble();

  /// Returns the derived `pushButtonGap` 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.
  double get pushButtonGap => math.max(5, 8 * scale);

  /// Returns the derived `indicatorTop` 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.
  double get indicatorTop => isCompact ? 0 : math.max(1, 2 * scale);

  /// Returns the derived `indicatorGap` 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.
  double get indicatorGap => math.max(3, 5 * scale);

  /// Returns the derived `rewardIconSize` 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.
  double get rewardIconSize => 14;

  /// Returns the derived `titleSize` 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.
  double get titleSize => 10;

  /// Returns the derived `metaSize` 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.
  double get metaSize => 7;

  /// Returns the derived `titleMetaGap` 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.
  double get titleMetaGap => 4 * scale;

  /// Returns the derived `freeSlotTimeGap` 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.
  double get freeSlotTimeGap => 10 * scale;

  /// Returns the derived `compactTextGap` 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.
  double get compactTextGap => math.max(3, 8 * scale);

  /// Returns the derived `indicatorWidth` 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.
  double get indicatorWidth =>
      rewardIconSize + indicatorGap + difficultySize.width;

  /// Returns the derived `expandedTrailingReserve` 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.
  double get expandedTrailingReserve =>
      indicatorWidth + math.max(8, 12 * scale);

  /// Returns the derived `expandedPushTrailingReserve` 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.
  double get expandedPushTrailingReserve =>
      indicatorWidth +
      pushButtonGap +
      pushButtonWidth +
      math.max(8, 12 * scale);

  /// Returns the derived `compactTrailingReserve` 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.
  double get compactTrailingReserve => indicatorWidth + math.max(6, 8 * scale);

  /// Returns the derived `compactPushTrailingReserve` 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.
  double get compactPushTrailingReserve =>
      indicatorWidth + pushButtonGap + pushButtonWidth + math.max(6, 8 * scale);

  /// Returns the derived `difficultySize` 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.
  Size get difficultySize => Size(26, 14);

  /// Returns the derived `padding` 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.
  EdgeInsets get padding {
    if (isCompact) {
      return EdgeInsets.symmetric(
        horizontal: math.max(4, 8 * scale),
        vertical: math.max(1, 3 * scale),
      );
    }
    return EdgeInsets.fromLTRB(18 * scale, 10 * scale, 14 * scale, 10 * scale);
  }
}
