/**
 * Shared economy / survival constants for BaoLife.
 *
 * This is the SINGLE SOURCE OF TRUTH for the numbers that drive resource
 * tension, consumed by BOTH game-loop paths so online (PlayerSession) and
 * offline (GameEngine) can never diverge:
 *   - hourly hunger / thirst / health rates
 *   - overnight energy restore
 *   - starvation thresholds and drains
 *   - energy-scarcity penalties
 *   - weekly-finance parameters (scaling rent, lifestyle, savings)
 *
 * The audit found money trended to infinite accumulation (flat $50/wk vs
 * $2000+/mo income), energy/hunger/thirst had no real consequences, and the
 * two engines diverged on numbers. These constants fix all three.
 */

import type { SpendingHabit } from '../stats/stats_manager.js';
import type { Person } from '../models/index.js';

// ============================================================================
// Survival: hourly stat rates (processHourTick — both engines)
// ============================================================================

/** Hunger increase per in-game hour. */
export const HUNGER_RATE_PER_HOUR = 3;

/** Thirst increase per in-game hour. */
export const THIRST_RATE_PER_HOUR = 4;

/**
 * Hunger/thirst level above which health begins to decay (mild pressure).
 * Below the starvation threshold this is a slow -1/hr drain.
 */
export const HEALTH_DECAY_THRESHOLD = 80;

/** Mild health drain per hour while hunger/thirst is above HEALTH_DECAY_THRESHOLD. */
export const HEALTH_DECAY_PER_HOUR = 1;

/** Health regen per hour when fed/hydrated and below max. */
export const HEALTH_REGEN_PER_HOUR = 0.5;

// ============================================================================
// Starvation pressure (hunger/thirst pegged at the cap)
// ============================================================================

/** A stat is "starving" when it reaches this hard cap. */
export const STARVATION_LEVEL = 100;

/** Severe health drain per hour while a stat is starving (hunger/thirst == 100). */
export const STARVATION_HEALTH_DRAIN_PER_HOUR = 3;

/**
 * Once starving, the mild +0.5/hr health regen is SUPPRESSED until the
 * offending stat drops back below this recovery level.
 */
export const STARVATION_RECOVERY_LEVEL = 60;

// ============================================================================
// Meal sink (the ONLY hunger/thirst sink — both engines)
// ============================================================================

/**
 * Hunger/thirst removed per meal slot (breakfast/lunch/dinner) when the daily-plan
 * executor (getIntradayActivity) runs a meal entry. This is the offsetting feed to
 * applyHourlySurvival's +3 hunger/hr, +4 thirst/hr. Without it, every character
 * starves to death at ~29-40 (hunger/thirst peg at 100 → health crashes →
 * deathChance = base/health explodes).
 *
 * Tuning target: a healthy adult should oscillate in a healthy band (peaks
 * comfortably below the starvation cap), NOT pegged at 100 and NOT trivially 0
 * forever. The worst-case meal cadence is the WEEKDAY WORKER plan, which only
 * has two meals (lunch @12, dinner @19) and therefore a long ~17h dinner→lunch
 * gap. Over that gap hunger accrues ~+51 and thirst ~+68, so each meal must
 * offset roughly that much to keep the peaks bounded. Retirees / students /
 * weekend plans eat three meals and so run lower.
 *
 * Chosen values: hunger -50, thirst -65 per meal (clamped at 0). These cover the
 * two-meal worker worst case so hunger/thirst peak in the ~70-90 pressure band
 * (food MATTERS — a skipped meal or long gap still pushes toward starvation) but
 * no longer PIN at 100 and trigger the severe starvation drain, and they never
 * trivialize survival to a flat 0 (steady-state means ~35 hunger / ~50 thirst).
 */
export const MEAL_HUNGER_REDUCTION = 50;

/** Thirst removed per meal slot (clamped at 0). */
export const MEAL_THIRST_REDUCTION = 65;

// ============================================================================
// Energy restore (overnight sleep — both engines)
// ============================================================================

/** Energy restored per night of sleep (processDayTick — both engines). */
export const ENERGY_RESTORE_PER_NIGHT = 35;

/** Maximum energy a character can have. */
export const ENERGY_MAX = 100;

// ============================================================================
// Energy scarcity (calcEnergy exhausted)
// ============================================================================

/** Per-hour health penalty applied while calcEnergy is exhausted (forced to push on no rest). */
export const EXHAUSTION_HEALTH_PENALTY_PER_HOUR = 1;

/** Per-hour happiness penalty applied while calcEnergy is exhausted. */
export const EXHAUSTION_HAPPINESS_PENALTY_PER_HOUR = 1;

// ============================================================================
// Weekly finances (applyWeeklyFinances — both engines)
// ============================================================================

/** Occupations that earn NO income (income only accrues for working roles). */
export const NON_EARNING_OCCUPATIONS = new Set<string>(['student', 'preschool', 'retired']);

/** Weeks per month, used to convert a monthly salary into a weekly slice. */
export const WEEKS_PER_MONTH = 4;

/**
 * Scaling rent: rent = clamp(weeklyIncome * RENT_INCOME_RATE, RENT_FLOOR, RENT_CAP).
 * Replaces the old flat $50/wk so income is actually meaningful and unbounded
 * accumulation is throttled. Audit target: monthly $200-$5000, i.e. weekly
 * floor ~$50, cap ~$1250.
 */
export const RENT_INCOME_RATE = 0.3;

/** Weekly rent floor (audit: ~$200/month). */
export const RENT_FLOOR = 50;

/** Weekly rent cap (audit: ~$5000/month). */
export const RENT_CAP = 1250;

/**
 * Compute the scaling weekly rent for a given weekly income.
 * Always at least RENT_FLOOR (even non-earners owe baseline housing), capped at
 * RENT_CAP so a high earner doesn't bleed out faster than they can earn.
 */
export function computeWeeklyRent(weeklyIncome: number): number {
  const raw = (weeklyIncome > 0 ? weeklyIncome : 0) * RENT_INCOME_RATE;
  return Math.min(RENT_CAP, Math.max(RENT_FLOOR, raw));
}

/**
 * Re-exported from stats_manager so the live finance path and tests have a
 * single import surface for the lifestyle tables. The authoritative tables
 * (EXPENSE_MODIFIERS, SAVINGS_RATES) live in stats_manager.ts; we keep the
 * defaulting helper here typed against the SpendingHabit union.
 */
export type Lifestyle = SpendingHabit;

// ============================================================================
// Shared hourly survival tick (one source of truth for both engines)
// ============================================================================

/**
 * Apply one hour of survival pressure to a person, mutating in place. This is
 * the SINGLE implementation consumed by both PlayerSession (online) and
 * GameEngine (offline) so the two paths can never diverge.
 *
 * Mechanics:
 *   - Hunger += HUNGER_RATE_PER_HOUR, Thirst += THIRST_RATE_PER_HOUR (clamped 0-100).
 *   - Starvation: if hunger OR thirst is at STARVATION_LEVEL (100), health drains
 *     hard (-STARVATION_HEALTH_DRAIN_PER_HOUR/hr) and the mild regen is SUPPRESSED
 *     until the offending stat falls below STARVATION_RECOVERY_LEVEL.
 *   - Mild pressure: above HEALTH_DECAY_THRESHOLD (but not starving) health drains
 *     slowly (-HEALTH_DECAY_PER_HOUR/hr).
 *   - Otherwise: health regenerates +HEALTH_REGEN_PER_HOUR/hr up to 100, unless
 *     starvation recovery is still suppressing it.
 *   - Energy scarcity: when calcEnergy is exhausted (0), apply small per-hour
 *     health and happiness penalties (the cost of pushing on with no reserve).
 *
 * Energy itself does NOT passively drain here — it is spent by actions/events and
 * restored overnight in the day tick (ENERGY_RESTORE_PER_NIGHT).
 */
export function applyHourlySurvival(person: Person): void {
  const hunger = Math.min(100, Math.max(0, (person.hunger ?? 0) + HUNGER_RATE_PER_HOUR));
  const thirst = Math.min(100, Math.max(0, (person.thirst ?? 0) + THIRST_RATE_PER_HOUR));
  person.hunger = hunger;
  person.thirst = thirst;

  const isStarving = hunger >= STARVATION_LEVEL || thirst >= STARVATION_LEVEL;
  // Once a stat hits the cap it must drop below the recovery level before regen
  // resumes; while either remains at/above recovery level we keep draining/suppressing.
  const recovering = hunger < STARVATION_RECOVERY_LEVEL && thirst < STARVATION_RECOVERY_LEVEL;

  let health = person.health ?? 100;

  if (isStarving) {
    // Severe drain while pegged at the cap.
    health = Math.max(0, health - STARVATION_HEALTH_DRAIN_PER_HOUR);
  } else if (hunger > HEALTH_DECAY_THRESHOLD || thirst > HEALTH_DECAY_THRESHOLD) {
    // Mild drain in the high-but-not-maxed band.
    health = Math.max(0, health - HEALTH_DECAY_PER_HOUR);
  } else if (recovering && health < 100) {
    // Fed/hydrated and out of the post-starvation suppression window: regen.
    health = Math.min(100, health + HEALTH_REGEN_PER_HOUR);
  }
  // else: regen suppressed (post-starvation window, still above recovery level).

  // Energy scarcity penalty: exhausted characters take a small hit each hour.
  if ((person.calcEnergy ?? 0) <= 0) {
    health = Math.max(0, health - EXHAUSTION_HEALTH_PENALTY_PER_HOUR);
    person.happiness = Math.max(0, (person.happiness ?? 0) - EXHAUSTION_HAPPINESS_PENALTY_PER_HOUR);
  }

  person.health = health;
}
