/**
 * Favors & Crisis Support — affinity as a GAMEPLAY GATE.
 *
 * The audit (goal `baolife-fun-balance`, task T008) found that an NPC's
 * `affinity` was meticulously tracked but almost never READ: a 100-affinity
 * parent and a 10-affinity parent were mechanically identical. This module
 * gives affinity real payoff by making the SUCCESS and MAGNITUDE of favors a
 * function of how much the NPC actually likes the player.
 *
 * The three closeness scalars (see relationship_manager.ts for the canonical
 * definition) play distinct roles here:
 *   - `affinity` (-100..100): how the NPC FEELS about the player. This is the
 *     scalar that drives every payoff/gate in this file. Loans, referrals, and
 *     crisis support all scale off affinity.
 *   - `familiarity` (0..100): breadth of shared history (conversation memory
 *     depth). Used only as a small secondary modifier, never the gate itself.
 *   - `relationshipScore` (romantic only): homeostatic toward-50 dating health.
 *     Deliberately NOT consulted here — favors work for ANY relation (parent,
 *     friend, sibling), not just active romances.
 *
 * Everything here is a PURE function over a minimal NPC/player shape so it is
 * trivially unit-testable and free of game-loop side effects. The callers
 * (tool_processor / pending_events / catalog events) apply the returned deltas.
 */

/** Minimal NPC shape this module reads. Compatible with the Person model. */
export interface FavorTarget {
  id?: string;
  firstname?: string;
  /** How the NPC feels about the player, -100..100. Missing -> neutral 50. */
  affinity?: number;
  /** Shared-history breadth, 0..100. Missing -> 0. Secondary modifier only. */
  familiarity?: number;
  relationships?: string[];
}

/** The kinds of favor a player can ask an NPC for. */
export type FavorKind = 'loan' | 'job_referral' | 'childcare' | 'practical_help';

/** Minimum affinity below which an NPC will simply decline any favor. */
export const FAVOR_DECLINE_THRESHOLD = 0;

/**
 * Affinity threshold above which a crisis (health/financial hardship) NPC will
 * step in to help. Below this, even family stays on the sidelines — which is
 * the whole point: neglected relationships stop paying out.
 */
export const CRISIS_SUPPORT_THRESHOLD = 60;

function clampAffinity(value: number | undefined): number {
  const a = typeof value === 'number' && Number.isFinite(value) ? value : 50;
  return Math.max(-100, Math.min(100, a));
}

function familiarityBonus(value: number | undefined): number {
  const f = typeof value === 'number' && Number.isFinite(value) ? value : 0;
  // Up to +0.1 multiplier from deep shared history; never the deciding factor.
  return Math.max(0, Math.min(100, f)) / 1000;
}

export interface FavorResult {
  /** Whether the NPC agreed to the favor at all. */
  granted: boolean;
  /** Money handed to the player (only for `loan`; 0 otherwise / on decline). */
  moneyGranted: number;
  /**
   * For `job_referral`: a 0..1 multiplier the caller can apply to hiring/level
   * odds (1 = strong vouch). 0 when not a referral or declined.
   */
  referralStrength: number;
  /** Player-facing message describing the outcome. */
  message: string;
  /**
   * How the favor changed the NPC's feelings toward the player. A granted favor
   * the player can repay strengthens the bond slightly; a decline at low
   * affinity nudges it further down (asking too much of someone who dislikes
   * you).
   */
  affinityDelta: number;
}

/**
 * Evaluate a favor the PLAYER asks an NPC for, scaled by the NPC's affinity.
 *
 * Design (all thresholds documented so the PM/designers can tune):
 *   - affinity <= FAVOR_DECLINE_THRESHOLD (0): always declined. They don't like
 *     you enough to put themselves out.
 *   - LOAN: max loan = round(affinity * 10) dollars (so affinity 80 -> ~$800,
 *     affinity 30 -> ~$300), scaled up to +10% by familiarity, then capped by
 *     the `requested` amount and a hard $5000 ceiling. Below affinity 20 no
 *     money changes hands even though they "want" to help.
 *   - JOB_REFERRAL: referralStrength = clamp((affinity - 20) / 80, 0..1). A
 *     beloved contact (affinity 100) gives a full-strength vouch (1.0); a
 *     lukewarm one (affinity 40) gives 0.25. The caller multiplies hiring/level
 *     odds by (1 + referralStrength) or similar.
 *   - CHILDCARE / PRACTICAL_HELP: binary — granted iff affinity > 25, no money.
 *
 * @param target    the NPC being asked
 * @param kind      what is being requested
 * @param requested optional requested loan amount (loan only); defaults to the
 *                  affinity-derived maximum
 */
export function evaluateFavor(
  target: FavorTarget,
  kind: FavorKind,
  requested?: number
): FavorResult {
  const affinity = clampAffinity(target.affinity);
  const name = target.firstname?.trim() || 'They';
  const noGrant: FavorResult = {
    granted: false,
    moneyGranted: 0,
    referralStrength: 0,
    message: `${name} doesn't feel close enough to you to help with that right now.`,
    affinityDelta: -2,
  };

  if (affinity <= FAVOR_DECLINE_THRESHOLD) {
    return noGrant;
  }

  switch (kind) {
    case 'loan': {
      // Even mildly-positive affinity can't unlock a loan; you need a real bond.
      if (affinity < 20) {
        return {
          ...noGrant,
          message: `${name} is friendly but not comfortable lending you money.`,
          affinityDelta: -1,
        };
      }
      const maxLoan = Math.round(affinity * 10 * (1 + familiarityBonus(target.familiarity)));
      const cappedMax = Math.min(maxLoan, 5000);
      const amount =
        requested !== undefined && requested > 0
          ? Math.min(Math.round(requested), cappedMax)
          : cappedMax;
      const granted = amount > 0;
      return {
        granted,
        moneyGranted: granted ? amount : 0,
        referralStrength: 0,
        message: granted
          ? `${name} trusts you and lends you $${amount}.`
          : `${name} can't spare anything right now.`,
        // Borrowing from someone who likes you is a small bonding moment.
        affinityDelta: granted ? 2 : 0,
      };
    }

    case 'job_referral': {
      const referralStrength = Math.max(0, Math.min(1, (affinity - 20) / 80));
      const granted = referralStrength > 0;
      return {
        granted,
        moneyGranted: 0,
        referralStrength,
        message: granted
          ? `${name} puts in a good word for you${
              referralStrength >= 0.75 ? ' — a glowing recommendation' : ''
            }.`
          : `${name} would rather not vouch for you right now.`,
        affinityDelta: granted ? 1 : -1,
      };
    }

    case 'childcare':
    case 'practical_help': {
      const granted = affinity > 25;
      return {
        granted,
        moneyGranted: 0,
        referralStrength: 0,
        message: granted
          ? `${name} is happy to help you out.`
          : `${name} politely declines to help this time.`,
        affinityDelta: granted ? 2 : -1,
      };
    }

    default:
      return noGrant;
  }
}

export interface CrisisSupportResult {
  /** Whether a high-affinity relation stepped in. */
  supported: boolean;
  /** Emergency money provided by the supporter (0 if none). */
  moneyProvided: number;
  /** Mood/happiness buffer to add to the player (0 if none). */
  moodBuffer: number;
  /** The supporting NPC, if any. */
  supporter: FavorTarget | null;
  /** Player-facing message. */
  message: string;
}

/**
 * During a hardship (health or financial setback), decide whether a
 * high-affinity family member or friend rallies to support the player. Only
 * relations at or above `CRISIS_SUPPORT_THRESHOLD` affinity step in — a
 * neglected (low-affinity) family is mechanically absent, which is exactly the
 * payoff this task introduces.
 *
 * The strongest available supporter (highest affinity) is chosen. Support
 * magnitude scales with how far above the threshold they sit.
 *
 * @param relations    candidate NPCs (e.g. player.r entries)
 * @param severity     1 (minor) .. 3 (major); scales money & mood buffer
 * @param livingOnly   if a `status` field exists, require 'alive' (default true)
 */
export function evaluateCrisisSupport(
  relations: Array<FavorTarget & { status?: string }>,
  severity: 1 | 2 | 3 = 2,
  livingOnly = true
): CrisisSupportResult {
  const none: CrisisSupportResult = {
    supported: false,
    moneyProvided: 0,
    moodBuffer: 0,
    supporter: null,
    message: 'You faced this one largely on your own.',
  };

  const eligible = (relations ?? []).filter((r) => {
    if (livingOnly && typeof r.status === 'string' && r.status !== 'alive') {
      return false;
    }
    return clampAffinity(r.affinity) >= CRISIS_SUPPORT_THRESHOLD;
  });

  if (eligible.length === 0) {
    return none;
  }

  // Pick the relation who cares most.
  const supporter = eligible.reduce((best, cur) =>
    clampAffinity(cur.affinity) > clampAffinity(best.affinity) ? cur : best
  );

  const affinity = clampAffinity(supporter.affinity);
  // Headroom above the threshold (0..40) scales the help.
  const headroom = affinity - CRISIS_SUPPORT_THRESHOLD;
  const moneyProvided = Math.round(severity * 100 * (1 + headroom / 40));
  const moodBuffer = Math.min(30, severity * 5 + Math.round(headroom / 4));
  const name = supporter.firstname?.trim() || 'A loved one';

  return {
    supported: true,
    moneyProvided,
    moodBuffer,
    supporter,
    message: `${name} steps up to support you, easing the burden ($${moneyProvided}).`,
  };
}
