import type { DynamicTextFn, EventDefinition, EventPlayerContext } from '../types.js';
import { pickVariant } from '../../../utils/textVariations.js';

/**
 * Date-seeded variant seed. The random passives recur on a day-of-year modulo,
 * so seeding on the day keeps copy stable within a firing but varied across the
 * (many) firings over a lifetime.
 */
function variantSeed(player: EventPlayerContext, salt: string): string {
  const day = (player as { dayOfYear?: unknown }).dayOfYear ?? 0;
  return `${salt}:${player.c.ageYears ?? 0}:${String(day)}`;
}

function seeded(salt: string, variants: string[]): DynamicTextFn {
  return (player) => pickVariant(variants, variantSeed(player, salt));
}

/**
 * Affinity threshold above which a relation counts as "close" — i.e. likes the
 * player enough to seek them out. Reading affinity in `isEligible` is the T008
 * payoff: a neglected (low-affinity) social circle stops generating these warm
 * "a friend reaches out" moments, while a well-tended one keeps inviting you.
 */
export const CLOSE_FRIEND_AFFINITY = 65;

/** True if any living relation likes the player at/above the given affinity. */
function hasCloseRelation(player: EventPlayerContext, minAffinity: number): boolean {
  const roster = (player as { r?: unknown }).r;
  if (!Array.isArray(roster)) return false;
  return roster.some((p) => {
    const person = p as { status?: unknown; affinity?: unknown };
    if (typeof person.status === 'string' && person.status !== 'alive') return false;
    return typeof person.affinity === 'number' && person.affinity >= minAffinity;
  });
}

/**
 * Stat thresholds that turn raised character stats (built by Wave 1's
 * performActivity trajectories) into UNLOCKED content (T007). Reading a stat in
 * isEligible needs no engine change. Both gate BONUS payoff passives — never a
 * life milestone — so high stats open extra doors but low stats never block
 * core progression.
 *
 *  - CREATIVITY: a creative breakthrough you can actually monetize only happens
 *    once your creativity is real (>= threshold).
 *  - SOCIAL: a warm, high-value networking opportunity finds people whose social
 *    skill is strong (>= threshold).
 */
export const CREATIVE_BREAKTHROUGH_CREATIVITY = 60;
export const NETWORKING_SOCIAL = 60;

/** Read a numeric character stat off the event context, defaulting to 50. */
function stat(player: EventPlayerContext, key: string): number {
  const value = (player.c as Record<string, unknown>)[key];
  return typeof value === 'number' ? value : 50;
}

const closeFriendInvitePrompts = [
  'A close friend who clearly adores you reaches out, wanting to spend the day together.',
  'Someone who thinks the world of you texts: they miss you and want to hang out.',
  'A dear friend carves out time just for you and invites you along.',
];

const closeFriendInviteResolutions = [
  'You spent quality time with someone who genuinely treasures you. It recharged you.',
  'The day with a true friend left you feeling seen and lighter.',
  'Time with someone who really cares reminded you how good close bonds feel.',
];

const kindnessPrompts = [
  'A stranger unexpectedly helped you with a small problem today.',
  'Someone held the door, picked up what you dropped, and made your day a little lighter.',
  'A kind stranger went out of their way to help you with something small but annoying.',
  'You hit a small snag today, and an unexpected good Samaritan stepped in to help.',
];

const kindnessResolutions = [
  'A small act of kindness improved your outlook for the day.',
  'The unexpected kindness stuck with you and put a little spring in your step.',
  'You paid the good feeling forward later and the whole day felt brighter.',
];

const setbackPrompts = [
  'A small setback disrupted your routine today.',
  'A minor mishap threw a wrench in your plans this morning.',
  'Something small went sideways today and put you a little behind.',
  'You hit one of those petty annoyances that just sour an otherwise fine day.',
];

const setbackResolutions = [
  'You absorbed a small setback and moved forward.',
  'You grumbled, regrouped, and got back on track.',
  'It was annoying, but you shook it off and kept going.',
];

// ── Ambient "life texture" pools ────────────────────────────────────────────
// Low-stakes recurring passives that drip through the long quiet stretches
// (especially mid-life) so no in-game month is silent. These are deliberately
// NOT milestones — they recur, are date-seeded for copy variation, and carry
// small mood/stat effects. Spread across the age range with staggered modulo
// offsets + cooldowns so they don't all land on the same day.

const quietMomentPrompts = [
  'An ordinary afternoon turns unexpectedly peaceful. You take a moment just to breathe.',
  'A small, quiet pleasure - a good cup of coffee, a song you love - lifts your whole day.',
  'You catch a slice of unremarkable, comfortable contentment in the middle of a normal day.',
  'The weather, the light, the quiet - everything lines up for one small good moment today.',
];
const quietMomentResolutions = [
  'You let the small good moment land. It carried you through the day.',
  'A pocket of calm did more for you than you expected.',
  'You savored it, and the ordinary day felt a little richer.',
];

const reconnectPrompts = [
  'An old acquaintance crosses your mind, and you find yourself reminiscing.',
  'A familiar place stirs up a wave of memories from years ago.',
  'You stumble on something - a photo, a song - that pulls you back through the years.',
  'Out of nowhere, a memory from a different chapter of your life surfaces, warm and vivid.',
];
const reconnectResolutions = [
  'The memory left you a little nostalgic, in the good way.',
  'You sat with the recollection for a while before getting on with your day.',
  'The flashback reminded you how much living you have already done.',
];

const errandPrompts = [
  'The mundane machinery of life needs tending today - errands, chores, the usual.',
  'A pile of small ordinary tasks has quietly accumulated and wants attention.',
  'It is one of those days that is mostly logistics: lists, errands, loose ends.',
  'Real life sends its little bills due - nothing dramatic, just the daily upkeep.',
];
const errandResolutions = [
  'You worked through the list and felt the small satisfaction of a tidy day.',
  'Nothing glamorous, but getting it done lifted a weight you barely noticed.',
  'You handled the ordinary stuff and earned yourself a clearer evening.',
];

// ── Stat-gated payoff pools (T007) ──────────────────────────────────────────
const creativeBreakthroughPrompts = [
  'A creative idea you have been turning over suddenly clicks into something real - something people might actually pay for.',
  'The thing you make for fun catches a stranger\'s eye, and they ask if you sell it. A door opens.',
  'A flash of inspiration turns your hobby into a project with real momentum - and real potential.',
];
const creativeBreakthroughResolutions = [
  'You leaned into the breakthrough and turned your creativity into a little income. It felt incredible to be paid for what you love.',
  'You ran with it. The side project found an audience, and the first sale proved your imagination has value.',
  'Your creative spark paid off - literally. A small but real return on the thing that lights you up.',
];

const networkingPrompts = [
  'At an event, a well-connected stranger gravitates toward you - your easy way with people opens a door most never get.',
  'Someone influential is impressed by how you carry a room and wants to introduce you around.',
  'Your knack for people pays off: a warm introduction lands you in exactly the right conversation.',
];
const networkingResolutions = [
  'You worked the room with ease and walked away with a connection that could genuinely change things.',
  'Your social instincts turned a chance meeting into a real opportunity. Relationships are currency, and you are rich.',
  'You charmed the right person at the right time. The introduction alone was worth showing up for.',
];

export const randomCatalog: EventDefinition[] = [
  {
    id: 'random_small_kindness',
    category: 'random',
    kind: 'passive',
    prompt: kindnessPrompts[0],
    promptFn: seeded('kindness:prompt', kindnessPrompts),
    // Ambient life-texture beat (NOT a milestone): designed to recur on a
    // day-of-year modulo. Was wrongly once-ever (no `repeatable`), so it fired
    // a single time per life then locked via askedQuestions — a dead-air source.
    // Date-seeded copy keeps recurrences from reading identically.
    repeatable: true,
    cooldownDays: 30,
    isEligible: (player) => {
      const dayOfYear = player.dayOfYear as number | undefined;
      if (typeof dayOfYear !== 'number') {
        return false;
      }
      return dayOfYear % 45 === 0;
    },
    choices: [
      {
        choiceId: 'receive_kindness',
        text: 'Receive kindness',
        resolutionText: kindnessResolutions[0],
        resolutionTextFn: seeded('kindness:res', kindnessResolutions),
        effects: {
          stats: {
            happiness: 5,
            social: 3,
          },
        },
      },
    ],
  },
  {
    id: 'random_minor_setback',
    category: 'random',
    kind: 'passive',
    prompt: setbackPrompts[0],
    promptFn: seeded('setback:prompt', setbackPrompts),
    // Ambient life-texture beat (NOT a milestone): same once-ever bug as
    // random_small_kindness. Now recurs on its day-of-year modulo, bounded by a
    // cooldown, with date-seeded copy variation.
    repeatable: true,
    cooldownDays: 45,
    isEligible: (player) => {
      const dayOfYear = player.dayOfYear as number | undefined;
      if (typeof dayOfYear !== 'number') {
        return false;
      }
      return dayOfYear % 60 === 0;
    },
    choices: [
      {
        choiceId: 'absorb_setback',
        text: 'Absorb setback',
        resolutionText: setbackResolutions[0],
        resolutionTextFn: seeded('setback:res', setbackResolutions),
        effects: {
          stats: {
            happiness: -4,
          },
        },
      },
    ],
  },
  // AFFINITY-GATED: only fires when the player has a relation who likes them a
  // lot (affinity >= CLOSE_FRIEND_AFFINITY). This makes a well-tended social
  // circle mechanically rewarding — a low-affinity roster never sees it.
  {
    id: 'random_close_friend_invite',
    category: 'social',
    kind: 'passive',
    prompt: closeFriendInvitePrompts[0],
    promptFn: seeded('closeFriend:prompt', closeFriendInvitePrompts),
    repeatable: true,
    cooldownDays: 30,
    isEligible: (player) => {
      const dayOfYear = player.dayOfYear as number | undefined;
      if (typeof dayOfYear !== 'number') {
        return false;
      }
      if (dayOfYear % 50 !== 0) {
        return false;
      }
      return hasCloseRelation(player, CLOSE_FRIEND_AFFINITY);
    },
    choices: [
      {
        choiceId: 'accept_close_friend',
        text: 'Spend the day together',
        resolutionText: closeFriendInviteResolutions[0],
        resolutionTextFn: seeded('closeFriend:res', closeFriendInviteResolutions),
        effects: {
          stats: {
            happiness: 8,
            social: 5,
            stress: -5,
          },
        },
      },
    ],
  },
  // ── Ambient life-texture drips (all ages) ─────────────────────────────────
  // These keep the long mid/late-life stretches from going silent. Each is
  // repeatable, gated on a staggered day-of-year modulo + a multi-week
  // cooldown, with date-seeded copy. Low weight so any genuine milestone that
  // is also eligible on a given day still tends to win the weighted pick.
  {
    id: 'random_quiet_moment',
    category: 'random',
    kind: 'passive',
    prompt: quietMomentPrompts[0],
    promptFn: seeded('quiet:prompt', quietMomentPrompts),
    repeatable: true,
    cooldownDays: 24,
    weight: 0.5,
    isEligible: (player) => {
      const dayOfYear = player.dayOfYear as number | undefined;
      if (typeof dayOfYear !== 'number') return false;
      return dayOfYear % 25 === 7;
    },
    choices: [
      {
        choiceId: 'savor_quiet_moment',
        text: 'Savor it',
        resolutionText: quietMomentResolutions[0],
        resolutionTextFn: seeded('quiet:res', quietMomentResolutions),
        effects: { stats: { happiness: 4, stress: -3 } },
      },
    ],
  },
  {
    id: 'random_reminisce',
    category: 'random',
    kind: 'passive',
    prompt: reconnectPrompts[0],
    promptFn: seeded('reminisce:prompt', reconnectPrompts),
    repeatable: true,
    cooldownDays: 36,
    weight: 0.5,
    isEligible: (player) => {
      const dayOfYear = player.dayOfYear as number | undefined;
      if (typeof dayOfYear !== 'number') return false;
      return dayOfYear % 38 === 19;
    },
    choices: [
      {
        choiceId: 'sit_with_memory',
        text: 'Sit with the memory',
        resolutionText: reconnectResolutions[0],
        resolutionTextFn: seeded('reminisce:res', reconnectResolutions),
        effects: { stats: { happiness: 3, social: 2 } },
      },
    ],
  },
  {
    id: 'random_daily_upkeep',
    category: 'random',
    kind: 'passive',
    prompt: errandPrompts[0],
    promptFn: seeded('errand:prompt', errandPrompts),
    repeatable: true,
    cooldownDays: 30,
    weight: 0.5,
    isEligible: (player) => {
      const dayOfYear = player.dayOfYear as number | undefined;
      if (typeof dayOfYear !== 'number') return false;
      return dayOfYear % 33 === 11;
    },
    choices: [
      {
        choiceId: 'handle_upkeep',
        text: 'Get it done',
        resolutionText: errandResolutions[0],
        resolutionTextFn: seeded('errand:res', errandResolutions),
        effects: { stats: { stress: 2, happiness: 1 } },
      },
    ],
  },
  // ── CREATIVITY-GATED payoff (T007) ─────────────────────────────────────────
  // Fires only once the player's creativity is real (>= threshold). A creative
  // hobby you can monetize is the payoff for building creativity. Repeatable on
  // a staggered cadence + cooldown, low weight so genuine milestones still win.
  {
    id: 'random_creative_breakthrough',
    category: 'random',
    kind: 'passive',
    prompt: creativeBreakthroughPrompts[0],
    promptFn: seeded('creativeBreak:prompt', creativeBreakthroughPrompts),
    repeatable: true,
    cooldownDays: 90,
    weight: 1,
    isEligible: (player) => {
      const dayOfYear = player.dayOfYear as number | undefined;
      if (typeof dayOfYear !== 'number') return false;
      if (dayOfYear % 40 !== 13) return false;
      return stat(player, 'creativity') >= CREATIVE_BREAKTHROUGH_CREATIVITY;
    },
    choices: [
      {
        choiceId: 'monetize_creativity',
        text: 'Turn it into something real',
        resolutionText: creativeBreakthroughResolutions[0],
        resolutionTextFn: seeded('creativeBreak:res', creativeBreakthroughResolutions),
        effects: { stats: { happiness: 10, creativity: 4 }, resources: { money: 400 } },
      },
    ],
  },
  // ── SOCIAL-GATED payoff (T007) ─────────────────────────────────────────────
  // Fires only for socially strong players (social >= threshold). The reward of
  // a high social stat is that doors open through people. Repeatable, staggered.
  {
    id: 'random_networking_opportunity',
    category: 'social',
    kind: 'passive',
    prompt: networkingPrompts[0],
    promptFn: seeded('networking:prompt', networkingPrompts),
    repeatable: true,
    cooldownDays: 90,
    weight: 1,
    isEligible: (player) => {
      const dayOfYear = player.dayOfYear as number | undefined;
      if (typeof dayOfYear !== 'number') return false;
      if (dayOfYear % 42 !== 17) return false;
      return stat(player, 'social') >= NETWORKING_SOCIAL;
    },
    choices: [
      {
        choiceId: 'work_the_room',
        text: 'Lean into the moment',
        resolutionText: networkingResolutions[0],
        resolutionTextFn: seeded('networking:res', networkingResolutions),
        effects: { stats: { happiness: 8, social: 4, prestige: 4 }, resources: { money: 300 } },
      },
    ],
  },
];
