/**
 * Life Goals / Aspirations System
 *
 * Forward-looking goals that give players a REASON to keep playing each
 * session — a destination to work toward, in contrast to the backward-looking
 * achievement catalog (achievements.ts) which only records what already
 * happened.
 *
 * Design notes:
 * - A small SLATE of active goals (3-5) is maintained per player. Goals unlock
 *   by life stage (age), and completed goals make room for newly-eligible ones.
 * - Progress is recomputed from a snapshot of the player's live state plus the
 *   retention `statistics` record. This mirrors the gating approach used by
 *   achievements.checkAchievements (live playerData + stats), so the two
 *   systems agree on thresholds (e.g. earn_1m_lifetime <-> reach_millionaire).
 * - On completion a goal grants diamonds (via the shared diamond economy) and a
 *   life-score contribution that accumulates on the player's statistics record.
 *
 * State is held in-memory (Map keyed by playerId) and persisted into the player
 * JSON blob by Player.toJSON / constructor (see lifeGoals field).
 */

import { awardDiamonds } from '../../monetization/diamondEconomy.js';
import { getPlayerStatistics, type PlayerStatistics } from './statistics.js';

// =====================================================
// TYPES
// =====================================================

/**
 * Coarse life stage used to gate which goals are eligible to enter a player's
 * active slate. A goal becomes eligible once the character's age falls within
 * [minAge, maxAge].
 */
export type LifeStage = 'child' | 'teen' | 'youngAdult' | 'adult' | 'senior';

export interface LifeGoalProgress {
  /** Current value toward the target. */
  current: number;
  /** Target value for completion. */
  target: number;
  /** 0-100 progress percentage (floored, clamped). */
  progressPercent: number;
}

/**
 * Snapshot of the player state needed to evaluate goal progress. Built from the
 * live player object + retention statistics so goal checks line up with the
 * achievement gating data (money / prestige / age + lifetime stats).
 */
export interface GoalEvalContext {
  age: number;
  money: number;
  prestige: number;
  jobLevel: number;
  childrenCount: number;
  friendsCount: number;
  everMarried: boolean;
  lifetimeEarnings: number;
}

export interface LifeGoalDefinition {
  id: string;
  title: string;
  description: string;
  icon: string;
  /** Target value the progress function counts toward. */
  target: number;
  /** Diamonds granted on completion. */
  reward: number;
  /** Life-score points contributed on completion. */
  lifeScore: number;
  /** Inclusive min age at which this goal can enter the active slate. */
  minAge: number;
  /** Inclusive max age at which this goal can enter the active slate. */
  maxAge: number;
  /**
   * Compute the current progress value from the eval context. The framework
   * derives progressPercent from current/target.
   */
  progress: (ctx: GoalEvalContext) => number;
}

export interface ActiveLifeGoal {
  id: string;
  progressPercent: number;
  current: number;
}

export interface CompletedLifeGoal {
  id: string;
  completedAt: string;
}

export interface PlayerLifeGoals {
  active: ActiveLifeGoal[];
  completed: CompletedLifeGoal[];
}

/** A goal that just completed during an evaluation pass. */
export interface CompletedGoalResult {
  id: string;
  title: string;
  description: string;
  icon: string;
  reward: number;
  lifeScore: number;
}

/** Client-facing shape of an active goal (definition + live progress). */
export interface FormattedLifeGoal {
  id: string;
  title: string;
  description: string;
  icon: string;
  target: number;
  reward: number;
  lifeScore: number;
  current: number;
  progressPercent: number;
}

// =====================================================
// GOAL CATALOG
// =====================================================

export const LIFE_GOAL_DEFINITIONS: LifeGoalDefinition[] = [
  // === CHILD / TEEN (forward-looking schooling + first milestones) ===
  {
    id: 'graduate_high_school',
    title: 'Earn Your Diploma',
    description: 'Graduate from high school.',
    icon: 'graduationcap',
    target: 1,
    reward: 25,
    lifeScore: 50,
    minAge: 5,
    maxAge: 19,
    progress: (ctx) => (ctx.jobLevel >= 0 && ctx.age >= 18 ? 1 : 0),
  },
  {
    id: 'make_three_friends',
    title: 'Build Your Circle',
    description: 'Make 3 friends.',
    icon: 'person.3',
    target: 3,
    reward: 20,
    lifeScore: 30,
    minAge: 5,
    maxAge: 25,
    progress: (ctx) => ctx.friendsCount,
  },

  // === YOUNG ADULT (career launch + family start) ===
  {
    id: 'graduate_college',
    title: 'Get a Degree',
    description: 'Graduate from college.',
    icon: 'scroll',
    target: 1,
    reward: 50,
    lifeScore: 80,
    minAge: 17,
    maxAge: 30,
    progress: (ctx) => (ctx.jobLevel >= 1 && ctx.age >= 22 ? 1 : 0),
  },
  {
    id: 'land_first_job',
    title: 'Start Your Career',
    description: 'Land your first job.',
    icon: 'briefcase',
    target: 1,
    reward: 20,
    lifeScore: 40,
    minAge: 16,
    maxAge: 35,
    progress: (ctx) => (ctx.jobLevel >= 1 ? 1 : 0),
  },
  {
    id: 'tie_the_knot',
    title: 'Tie the Knot',
    description: 'Get married.',
    icon: 'heart',
    target: 1,
    reward: 30,
    lifeScore: 60,
    minAge: 18,
    maxAge: 70,
    progress: (ctx) => (ctx.everMarried ? 1 : 0),
  },

  // === ADULT (wealth, family, status — the long-haul aspirations) ===
  {
    id: 'raise_two_children',
    title: 'Raise a Family',
    description: 'Have 2 children.',
    icon: 'figure.2.and.child.holdinghands',
    target: 2,
    reward: 40,
    lifeScore: 70,
    minAge: 18,
    maxAge: 55,
    progress: (ctx) => ctx.childrenCount,
  },
  {
    id: 'reach_millionaire',
    title: 'Become a Millionaire',
    description: 'Reach a $1,000,000 net worth.',
    icon: 'banknote',
    target: 1_000_000,
    reward: 100,
    lifeScore: 120,
    minAge: 18,
    maxAge: 120,
    progress: (ctx) => Math.max(ctx.money, ctx.lifetimeEarnings),
  },
  {
    id: 'climb_the_ladder',
    title: 'Climb the Ladder',
    description: 'Reach career level 5.',
    icon: 'arrow.up.right',
    target: 5,
    reward: 60,
    lifeScore: 80,
    minAge: 18,
    maxAge: 80,
    progress: (ctx) => ctx.jobLevel,
  },
  {
    id: 'rising_star',
    title: 'Rising Star',
    description: 'Reach 100 prestige.',
    icon: 'star',
    target: 100,
    reward: 25,
    lifeScore: 50,
    minAge: 16,
    maxAge: 120,
    progress: (ctx) => ctx.prestige,
  },
  {
    id: 'household_name',
    title: 'Household Name',
    description: 'Reach 500 prestige.',
    icon: 'star.circle',
    target: 500,
    reward: 75,
    lifeScore: 110,
    minAge: 25,
    maxAge: 120,
    progress: (ctx) => ctx.prestige,
  },

  // === SENIOR (legacy + longevity) ===
  {
    id: 'live_to_eighty',
    title: 'Live a Long Life',
    description: 'Live to 80 years old.',
    icon: 'sparkles',
    target: 80,
    reward: 50,
    lifeScore: 100,
    minAge: 55,
    maxAge: 120,
    progress: (ctx) => ctx.age,
  },
];

/** Target slate size: how many active goals a player should carry. */
export const ACTIVE_GOAL_SLATE_SIZE = 4;

// =====================================================
// IN-MEMORY STATE
// =====================================================

const playerGoals: Map<string, PlayerLifeGoals> = new Map();

function getOrInit(playerId: string): PlayerLifeGoals {
  let state = playerGoals.get(playerId);
  if (!state) {
    state = { active: [], completed: [] };
    playerGoals.set(playerId, state);
  }
  return state;
}

// =====================================================
// HELPERS
// =====================================================

export function getLifeGoalById(id: string): LifeGoalDefinition | undefined {
  return LIFE_GOAL_DEFINITIONS.find((g) => g.id === id);
}

export function ageToLifeStage(age: number): LifeStage {
  if (age < 13) return 'child';
  if (age < 18) return 'teen';
  if (age < 30) return 'youngAdult';
  if (age < 60) return 'adult';
  return 'senior';
}

function computeProgressPercent(current: number, target: number): number {
  if (target <= 0) return current > 0 ? 100 : 0;
  return Math.max(0, Math.min(100, Math.floor((current / target) * 100)));
}

/**
 * Build the evaluation context from a live player object + retention stats.
 * Player shape is intentionally loose (matches the achievement-gating pattern).
 */
export function buildGoalContext(
  playerId: string,
  player: {
    c?: { ageYears?: number; prestige?: number; diamonds?: number };
    character?: { ageYears?: number; prestige?: number };
    money?: number;
  }
): GoalEvalContext {
  const stats: PlayerStatistics = getPlayerStatistics(playerId);
  const char = player.c ?? player.character ?? {};
  return {
    age: char.ageYears ?? 0,
    money: player.money ?? 0,
    prestige: char.prestige ?? 0,
    jobLevel: stats.highestJobLevel ?? 0,
    childrenCount: stats.childrenCount ?? 0,
    friendsCount: stats.friendsCount ?? 0,
    everMarried: stats.everMarried ?? false,
    lifetimeEarnings: stats.lifetimeEarnings ?? 0,
  };
}

function isEligible(def: LifeGoalDefinition, age: number): boolean {
  return age >= def.minAge && age <= def.maxAge;
}

// =====================================================
// SLATE MANAGEMENT
// =====================================================

/**
 * Refresh the active slate: drop nothing on its own here, but fill any open
 * slots with newly-eligible, not-yet-active, not-completed goals. Called on
 * stage changes (birthday) and after completions free up slots.
 *
 * Returns true if the active slate changed.
 */
export function refreshGoalSlate(playerId: string, ctx: GoalEvalContext): boolean {
  const state = getOrInit(playerId);
  const activeIds = new Set(state.active.map((g) => g.id));
  const completedIds = new Set(state.completed.map((g) => g.id));

  let changed = false;

  if (state.active.length >= ACTIVE_GOAL_SLATE_SIZE) {
    return false;
  }

  // Candidate goals: eligible for the current age, not active, not completed.
  // Ordered by the catalog ordering (roughly life-stage progression).
  for (const def of LIFE_GOAL_DEFINITIONS) {
    if (state.active.length >= ACTIVE_GOAL_SLATE_SIZE) break;
    if (activeIds.has(def.id) || completedIds.has(def.id)) continue;
    if (!isEligible(def, ctx.age)) continue;

    const current = def.progress(ctx);
    state.active.push({
      id: def.id,
      current,
      progressPercent: computeProgressPercent(current, def.target),
    });
    activeIds.add(def.id);
    changed = true;
  }

  return changed;
}

/**
 * Evaluate progress for all active goals against the supplied context. Any goal
 * whose progress reaches its target is completed: diamonds + life-score are
 * granted, it's moved to `completed`, and a new eligible goal is pulled into the
 * freed slot.
 *
 * Returns the list of goals that completed in this pass (possibly empty) and a
 * `changed` flag indicating whether any active progress or membership changed
 * (used to decide whether to emit a lifeGoalsUpdate to the client).
 */
export function evaluateGoals(
  playerId: string,
  ctx: GoalEvalContext,
  player?: { userId?: string; c?: { diamonds?: number }; character?: { diamonds?: number } }
): { completed: CompletedGoalResult[]; changed: boolean } {
  // Ensure the slate is seeded for the player's current stage before evaluating.
  let changed = refreshGoalSlate(playerId, ctx);

  const state = getOrInit(playerId);
  const completedThisPass: CompletedGoalResult[] = [];

  const stillActive: ActiveLifeGoal[] = [];
  for (const active of state.active) {
    const def = getLifeGoalById(active.id);
    if (!def) {
      // Unknown goal id (e.g. catalog change) — drop it.
      changed = true;
      continue;
    }

    const current = def.progress(ctx);
    const progressPercent = computeProgressPercent(current, def.target);

    if (current !== active.current || progressPercent !== active.progressPercent) {
      changed = true;
    }

    if (current >= def.target) {
      // Goal completed.
      awardDiamonds(playerId, `life_goal_${def.id}`, def.reward, player);
      addLifeScore(playerId, def.lifeScore);
      state.completed.push({ id: def.id, completedAt: new Date().toISOString() });
      completedThisPass.push({
        id: def.id,
        title: def.title,
        description: def.description,
        icon: def.icon,
        reward: def.reward,
        lifeScore: def.lifeScore,
      });
      changed = true;
    } else {
      stillActive.push({ id: def.id, current, progressPercent });
    }
  }

  state.active = stillActive;

  // Completions freed slots — pull in newly-eligible goals.
  if (completedThisPass.length > 0) {
    if (refreshGoalSlate(playerId, ctx)) {
      changed = true;
    }
  }

  return { completed: completedThisPass, changed };
}

// =====================================================
// LIFE SCORE
// =====================================================

// Life score accumulates on a side map (it's a goals-specific aggregate; the
// retention statistics record has no field for it and we must not refactor it).
const playerLifeScore: Map<string, number> = new Map();

export function getLifeScore(playerId: string): number {
  return playerLifeScore.get(playerId) ?? 0;
}

export function addLifeScore(playerId: string, points: number): void {
  if (points <= 0) return;
  playerLifeScore.set(playerId, getLifeScore(playerId) + points);
}

export function setLifeScore(playerId: string, value: number): void {
  playerLifeScore.set(playerId, Math.max(0, value));
}

// =====================================================
// QUERIES + CLIENT FORMATTING
// =====================================================

export function getPlayerLifeGoals(playerId: string): PlayerLifeGoals {
  const state = getOrInit(playerId);
  return {
    active: state.active.map((g) => ({ ...g })),
    completed: state.completed.map((g) => ({ ...g })),
  };
}

/**
 * Format active goals for the client, merging the live definition (title,
 * description, target, reward) with the stored progress.
 */
export function formatActiveGoals(playerId: string): FormattedLifeGoal[] {
  const state = getOrInit(playerId);
  const out: FormattedLifeGoal[] = [];
  for (const active of state.active) {
    const def = getLifeGoalById(active.id);
    if (!def) continue;
    out.push({
      id: def.id,
      title: def.title,
      description: def.description,
      icon: def.icon,
      target: def.target,
      reward: def.reward,
      lifeScore: def.lifeScore,
      current: active.current,
      progressPercent: active.progressPercent,
    });
  }
  return out;
}

export function formatCompletedGoals(playerId: string): Array<CompletedLifeGoal & { title: string; icon: string }> {
  const state = getOrInit(playerId);
  return state.completed.map((c) => {
    const def = getLifeGoalById(c.id);
    return {
      ...c,
      title: def?.title ?? c.id,
      icon: def?.icon ?? 'star',
    };
  });
}

// =====================================================
// PERSISTENCE
// =====================================================

/**
 * Hydrate in-memory goal state from a persisted player blob. Tolerant of
 * missing / partial data (older saves, fresh players).
 */
export function loadPlayerLifeGoals(
  playerId: string,
  persisted?: {
    active?: Array<{ id?: string; current?: number; progressPercent?: number }>;
    completed?: Array<{ id?: string; completedAt?: string }>;
    lifeScore?: number;
  } | null
): void {
  const active: ActiveLifeGoal[] = [];
  const completed: CompletedLifeGoal[] = [];

  if (persisted?.active && Array.isArray(persisted.active)) {
    for (const g of persisted.active) {
      if (!g || typeof g.id !== 'string') continue;
      if (!getLifeGoalById(g.id)) continue;
      active.push({
        id: g.id,
        current: typeof g.current === 'number' ? g.current : 0,
        progressPercent: typeof g.progressPercent === 'number' ? g.progressPercent : 0,
      });
    }
  }

  if (persisted?.completed && Array.isArray(persisted.completed)) {
    for (const c of persisted.completed) {
      if (!c || typeof c.id !== 'string') continue;
      completed.push({
        id: c.id,
        completedAt: typeof c.completedAt === 'string' ? c.completedAt : new Date().toISOString(),
      });
    }
  }

  playerGoals.set(playerId, { active, completed });
  setLifeScore(playerId, typeof persisted?.lifeScore === 'number' ? persisted.lifeScore : 0);
}

/**
 * Serialize goal state for persistence in the player JSON blob.
 */
export function serializePlayerLifeGoals(playerId: string): {
  active: ActiveLifeGoal[];
  completed: CompletedLifeGoal[];
  lifeScore: number;
} {
  const state = getOrInit(playerId);
  return {
    active: state.active.map((g) => ({ ...g })),
    completed: state.completed.map((g) => ({ ...g })),
    lifeScore: getLifeScore(playerId),
  };
}

// =====================================================
// CLEANUP (tests / new game)
// =====================================================

export function clearPlayerLifeGoals(playerId: string): void {
  playerGoals.delete(playerId);
  playerLifeScore.delete(playerId);
}

export function clearAllLifeGoals(): void {
  playerGoals.clear();
  playerLifeScore.clear();
}
