/// Frequency-based rather than instance-based: a single miss costs nothing,
/// falling below the target in the rolling window is what accrues debt.
class Habit {
  String? id;

  String title;

  String category;

  /// Target completions per rolling window, e.g. 5.
  int targetPerWindow;

  /// Rolling window length in days, e.g. 7.
  int windowDays;

  /// Completions inside the current window.
  int completionsInWindow;

  /// Marked keystone once the data says its completion predicts day quality —
  /// the app works this out after ~60 days rather than taking your word.
  bool keystone;

  /// 0..1 correlation with overall day quality; -1 until there is enough data.
  double predictiveStrength;

  Habit({
    this.id,
    this.title = "",
    this.category = "",
    this.targetPerWindow = 0,
    this.windowDays = 7,
    this.completionsInWindow = 0,
    this.keystone = false,
    this.predictiveStrength = -1,
  });

  factory Habit.fromJson(Map<String, dynamic> json) {
    return Habit(
      id: json['id'],
      title: json['title'] ?? "",
      category: json['category'] ?? "",
      targetPerWindow: json['targetPerWindow'] ?? 0,
      windowDays: json['windowDays'] ?? 7,
      completionsInWindow: json['completionsInWindow'] ?? 0,
      keystone: json['keystone'] ?? false,
      predictiveStrength: (json['predictiveStrength'] ?? -1).toDouble(),
    );
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['id'] = id;
    data['title'] = title;
    data['category'] = category;
    data['targetPerWindow'] = targetPerWindow;
    data['windowDays'] = windowDays;
    data['completionsInWindow'] = completionsInWindow;
    data['keystone'] = keystone;
    data['predictiveStrength'] = predictiveStrength;
    return data;
  }

  /// Behind target for the window — the only condition under which a habit
  /// accrues debt.
  bool get behindTarget {
    return completionsInWindow < targetPerWindow;
  }

  int get shortfall {
    final int gap = targetPerWindow - completionsInWindow;
    return gap > 0 ? gap : 0;
  }
}
