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

/**
 * Family milestone arc catalog (v2 / live runtime).
 *
 * Ported from the legacy class-based arcs in
 * `server/src/events/family/familyMilestones.ts` (UNREACHABLE in the v2
 * runtime) into declarative `EventDefinition`s, following the T006b recipe.
 *
 * Kept SEPARATE from the existing `catalog/family.ts` (which holds the passive
 * `family_weekly_checkin` / `family_shared_meal` events) to avoid id collisions.
 * All ids here are namespaced under `familyArc.` and are globally unique.
 *
 *  - Independent legacy events -> one INTERACTIVE stage-1 `EventDefinition`.
 *  - `scheduleFollowUp(...)` stages -> SEPARATE `kind: 'passive'` follow-ups,
 *    gated on `wasAsked` of the prior stage (+ a `setFlags`/`playerHasFlag`
 *    flag when only SOME stage-1 choices should unlock the follow-up).
 *  - Legacy NPC affinity deltas, random recovery rolls, and family-wide loops
 *    are FLATTENED to fixed player `effects.stats` / `effects.resources.money`
 *    using the representative branch (no per-NPC relationship plumbing in v2).
 *  - ChildMilestones (legacy fires once per call but covers many ages) is made
 *    `repeatable` with a ~yearly cooldown so children hit multiple milestones.
 */

function wasAsked(player: EventPlayerContext, eventId: string): boolean {
  const asked = (player as { askedQuestions?: unknown }).askedQuestions;
  if (asked instanceof Set) return asked.has(eventId);
  if (Array.isArray(asked)) return asked.includes(eventId);
  return false;
}

function hasRelation(player: EventPlayerContext, ...rels: string[]): boolean {
  const roster = (player as { r?: unknown }).r;
  if (!Array.isArray(roster)) return false;
  return roster.some((p) => {
    const person = p as { status?: unknown; relationships?: unknown };
    if (person.status !== 'alive') return false;
    const personRels = person.relationships;
    if (!Array.isArray(personRels)) return false;
    return rels.some((r) => personRels.includes(r));
  });
}

function hasDeceasedFamily(player: EventPlayerContext): boolean {
  const roster = (player as { r?: unknown }).r;
  if (!Array.isArray(roster)) return false;
  return roster.some((p) => {
    const person = p as { status?: unknown; relationships?: unknown };
    if (person.status !== 'dead') return false;
    const personRels = person.relationships;
    if (!Array.isArray(personRels)) return false;
    return personRels.includes('mother') || personRels.includes('father');
  });
}

function choice(
  choiceId: string,
  text: string,
  resolutionText: string,
  overrides?: Partial<EventDefinition['choices'][number]>
): EventDefinition['choices'][number] {
  return {
    choiceId,
    text,
    resolutionText,
    ...overrides,
  };
}

function variantSeed(player: EventPlayerContext, salt: string): string {
  const day =
    (player as { dayOfYear?: unknown }).dayOfYear ??
    (player.c as { date?: unknown }).date ??
    (player as { date?: unknown }).date ??
    0;
  return `${salt}:${player.c.ageYears ?? 0}:${String(day)}`;
}

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

// Flags set by stage-1 choices to drive choice-branching follow-ups.
export const FAMILY_ARC_FLAGS = {
  weddingAttending: 'familyArc.weddingAttending',
  weddingSkipped: 'familyArc.weddingSkipped',
  reunionAttending: 'familyArc.reunionAttending',
  secretConfronted: 'familyArc.secretConfronted',
  secretInvestigated: 'familyArc.secretInvestigated',
} as const;

export const familyArcCatalog: EventDefinition[] = [
  // ===========================================================================
  // 1. ParentGetsSick (MULTI-STAGE: diagnosis INTERACTIVE -> support -> outcome)
  // Legacy: requires a living parent; each branch hits happiness/stress (+ cost
  // & affinity) and schedules a 14-day support follow-up plus a 60-day random
  // recovery/worsening follow-up. Flatten: branches keep representative costs;
  // affinity is dropped; support (stage 2) and the recovery outcome (stage 3,
  // flattened to the 70%-likely RECOVERY branch) are passives gated by wasAsked.
  // ===========================================================================
  {
    id: 'familyArc.parentGetsSick',
    category: 'family',
    prompt:
      'You get a phone call that stops you in your tracks. A parent has been diagnosed with a serious illness. The doctor says they need treatment and support. How do you respond?',
    minAge: 20,
    maxAge: 80,
    weight: 1,
    isEligible: (player) => hasRelation(player, 'mother', 'father'),
    choices: [
      choice(
        'rush',
        'Drop everything and go to them',
        'You dropped everything and drove straight to your parent. When they opened the door, the relief on their face was overwhelming. You held them tight and promised to be there through this.',
        { energyCost: 20, moneyCost: 500, effects: { stats: { happiness: -15, stress: 20 } } }
      ),
      choice(
        'research',
        'Start researching the best doctors',
        'You spent the evening researching specialists and treatment options. Knowledge is power, and you want your parent to have the best care possible. You called with a plan.',
        { energyCost: 15, moneyCost: 200, effects: { stats: { happiness: -15, stress: 20 } } }
      ),
      choice(
        'call',
        'Call them and offer support',
        'You called your parent immediately. The conversation was hard. They tried to be strong but their voice cracked. You promised to visit soon and check in every day.',
        { energyCost: 10, effects: { stats: { happiness: -15, stress: 20 } } }
      ),
      choice(
        'text',
        "Send a text, you'll visit when you can",
        "You sent a supportive text to your parent. You meant to call but couldn't find the words. Life feels overwhelming right now, and the guilt is already starting to weigh on you.",
        { effects: { stats: { happiness: -15, stress: 25 } } }
      ),
    ],
  },

  // 1b. Parent treatment support (stage 2; PASSIVE, gated on wasAsked stage 1).
  {
    id: 'familyArc.parentSickSupport',
    category: 'family',
    kind: 'passive',
    prompt: 'By Their Side',
    minAge: 20,
    maxAge: 80,
    weight: 1,
    isEligible: (player) => wasAsked(player, 'familyArc.parentGetsSick'),
    choices: [
      choice(
        'support',
        'Continue',
        "Two weeks of treatment have passed. Your parent started their plan, and being there for appointments and meals is exhausting. But they squeeze your hand and say \"Thank you for being here.\" It means everything.",
        { effects: { stats: { energy: -10, stress: 5 } } }
      ),
    ],
  },

  // 1c. Parent outcome (stage 3; PASSIVE, gated on support having fired).
  // Legacy 70% recovery roll flattened to the representative RECOVERY outcome.
  {
    id: 'familyArc.parentSickOutcome',
    category: 'family',
    kind: 'passive',
    prompt: 'Two Months Later',
    minAge: 20,
    maxAge: 81,
    weight: 1,
    isEligible: (player) => wasAsked(player, 'familyArc.parentSickSupport'),
    choices: [
      choice(
        'recovery',
        'Continue',
        'After two months, your parent is showing real improvement. The treatment is working and the color is coming back to their face. They are not out of the woods yet, but the worst seems to be over. The family lets out a collective sigh of relief.',
        { effects: { stats: { happiness: 10, stress: -10 } } }
      ),
    ],
  },

  // ===========================================================================
  // 2. SiblingsWedding (MULTI-STAGE: news INTERACTIVE -> prep -> day, OR skip)
  // Legacy: requires a sibling aged 20+. Attending branches incur a cost, boost
  // affinity/happiness, and schedule prep (30d) + day (90d). Skip drops affinity
  // and schedules an aftermath (90d). Flatten: attend branches set
  // `weddingAttending`; skip sets `weddingSkipped`. Prep+day are flag-gated
  // passives; aftermath is gated on the skip flag.
  // ===========================================================================
  {
    id: 'familyArc.siblingsWedding',
    category: 'family',
    prompt:
      'Your sibling is getting married! They called to share the news and want you in the wedding party. The wedding is in a few months. How do you feel about it?',
    minAge: 18,
    maxAge: 80,
    weight: 1,
    isEligible: (player) => hasRelation(player, 'sibling', 'brother', 'sister'),
    choices: [
      choice(
        'allout',
        "Thrilled! I'll go all out ($500)",
        "You're going all out for the wedding! You volunteered for the planning committee and started shopping for the perfect gift. This is going to be amazing.",
        {
          energyCost: 10,
          moneyCost: 500,
          effects: { stats: { happiness: 10, social: 5 } },
          setFlags: [FAMILY_ARC_FLAGS.weddingAttending],
        }
      ),
      choice(
        'attend',
        'Happy for them, attend normally ($200)',
        "You're happy for your sibling and looking forward to the wedding. You RSVP'd and marked the date. It'll be a great celebration.",
        {
          energyCost: 5,
          moneyCost: 200,
          effects: { stats: { happiness: 5, social: 3 } },
          setFlags: [FAMILY_ARC_FLAGS.weddingAttending],
        }
      ),
      choice(
        'minimal',
        'Attend but keep it minimal ($100)',
        "You'll attend the wedding but keep things simple. A card, a modest gift, and your presence. That should be enough.",
        {
          moneyCost: 100,
          effects: { stats: { happiness: 5 } },
          setFlags: [FAMILY_ARC_FLAGS.weddingAttending],
        }
      ),
      choice(
        'skip',
        'Skip the wedding',
        'You told your sibling you can\'t make it to the wedding. The silence on the other end of the phone said everything.',
        { effects: { stats: { happiness: -10, social: -8 } }, setFlags: [FAMILY_ARC_FLAGS.weddingSkipped] }
      ),
    ],
  },

  // 2b. Wedding prep (stage 2; flag-gated PASSIVE).
  {
    id: 'familyArc.weddingPrep',
    category: 'family',
    kind: 'passive',
    prompt: 'Wedding Prep',
    minAge: 18,
    maxAge: 80,
    weight: 1,
    isEligible: (player) =>
      wasAsked(player, 'familyArc.siblingsWedding') &&
      playerHasFlag(player, FAMILY_ARC_FLAGS.weddingAttending),
    choices: [
      choice(
        'prep',
        'Continue',
        "Wedding prep is in full swing. You picked out your outfit, RSVP'd, and the family group chat is buzzing with excitement. Your sibling keeps saying how much it means to have you involved.",
        { effects: { stats: { happiness: 5, social: 3 } } }
      ),
    ],
  },

  // 2c. Wedding day (stage 3; gated on prep having fired + attending flag).
  {
    id: 'familyArc.weddingDay',
    category: 'family',
    kind: 'passive',
    prompt: 'The Wedding Day',
    minAge: 18,
    maxAge: 80,
    weight: 1,
    isEligible: (player) =>
      wasAsked(player, 'familyArc.weddingPrep') &&
      playerHasFlag(player, FAMILY_ARC_FLAGS.weddingAttending),
    choices: [
      choice(
        'celebrate',
        'Continue',
        'The wedding day. The ceremony was beautiful. When your sibling said "I do," you couldn\'t help but tear up. The reception was incredible - you danced, laughed, and made a toast that had everyone crying.',
        { effects: { stats: { happiness: 12, social: 8 } } }
      ),
    ],
  },

  // 2d. Wedding aftermath (skip branch; flag-gated PASSIVE).
  {
    id: 'familyArc.weddingAftermath',
    category: 'family',
    kind: 'passive',
    prompt: 'Three Months After Skipping the Wedding',
    minAge: 18,
    maxAge: 80,
    weight: 1,
    isEligible: (player) =>
      wasAsked(player, 'familyArc.siblingsWedding') &&
      playerHasFlag(player, FAMILY_ARC_FLAGS.weddingSkipped),
    choices: [
      choice(
        'regret',
        'Continue',
        "It's been three months since you skipped your sibling's wedding. They haven't called. The rest of the family is giving you the cold shoulder. You're starting to wonder if you made a mistake.",
        { effects: { stats: { happiness: -8, social: -5 } } }
      ),
    ],
  },

  // ===========================================================================
  // 3. ChildMilestones (RECURRING - one milestone per firing across many ages)
  // Legacy: requires a living child; question text varies by child age and the
  // three branches (celebrate/warm/busy) adjust happiness + child affinity.
  // Flatten: a single repeatable event (~yearly cooldown) with representative
  // milestone copy; affinity is dropped (no per-NPC plumbing).
  // ===========================================================================
  {
    id: 'familyArc.childMilestones',
    category: 'family',
    prompt:
      'Your child just hit a milestone - a first word, a first day of school, an award, or a graduation. How do you respond?',
    minAge: 20,
    maxAge: 70,
    repeatable: true,
    cooldownDays: 360,
    weight: 2,
    isEligible: (player) => hasRelation(player, 'child'),
    choices: [
      choice(
        'celebrate',
        'Celebrate this moment fully',
        "You celebrated your child's milestone in style - photos, treats, the whole family told. This might be one of the best moments of your life so far.",
        { energyCost: 5, moneyCost: 50, effects: { stats: { happiness: 15, social: 5 } } }
      ),
      choice(
        'warm',
        'Acknowledge it warmly',
        "You acknowledged your child's milestone with genuine warmth. A hug, kind words, and your full attention for a moment. Sometimes that's all a kid needs.",
        { effects: { stats: { happiness: 8, social: 3 } } }
      ),
      choice(
        'busy',
        "Note it, but you're preoccupied",
        'You were distracted when the milestone happened. You said "that\'s nice" without really looking up. Their face fell just a little. You\'ll remember this later and wish you\'d been more present.',
        { effects: { stats: { happiness: -5, social: -3 } } }
      ),
    ],
  },

  // ===========================================================================
  // 4. FamilySecretRevealed (MULTI-STAGE: discovery INTERACTIVE -> follow-ups)
  // Legacy: random secret text; confront/investigate schedule follow-ups,
  // confide/ignore do not. Flatten: confront sets `secretConfronted` (unlocks
  // aftermath), investigate sets `secretInvestigated` (unlocks investigation).
  // ===========================================================================
  {
    id: 'familyArc.familySecret',
    category: 'family',
    prompt:
      'You discover a long-hidden family secret - a half-sibling no one mentioned, or a relative who lived a completely different life. How do you handle this?',
    minAge: 16,
    maxAge: 80,
    weight: 1,
    choices: [
      choice(
        'confront',
        'Confront the family about it',
        'You brought it up at the next family gathering. The room went silent. Then everyone started talking at once. The truth is out now, for better or worse.',
        {
          energyCost: 15,
          effects: { stats: { happiness: -10, stress: 15 } },
          setFlags: [FAMILY_ARC_FLAGS.secretConfronted],
        }
      ),
      choice(
        'investigate',
        'Investigate quietly on your own',
        "You decided to dig deeper on your own. There's something unsettling about learning your family isn't quite what you thought. But you need to know the full story.",
        {
          energyCost: 10,
          effects: { stats: { stress: 15 } },
          setFlags: [FAMILY_ARC_FLAGS.secretInvestigated],
        }
      ),
      choice(
        'confide',
        'Talk to one trusted family member',
        'You confided in one family member you trust. They listened, shared what they knew, and together you decided how to move forward. Having an ally in this makes it easier.',
        { energyCost: 5, effects: { stats: { stress: 15, social: 3 } } }
      ),
      choice(
        'ignore',
        'Let it go and move on',
        'You decided some things are better left in the past. The secret stays buried, but a small part of you will always wonder.',
        { effects: { stats: { happiness: -5, stress: 15 } } }
      ),
    ],
  },

  // 4b. Secret confrontation aftermath (flag-gated PASSIVE).
  {
    id: 'familyArc.secretAftermath',
    category: 'family',
    kind: 'passive',
    prompt: 'After the Confrontation',
    minAge: 16,
    maxAge: 80,
    weight: 1,
    isEligible: (player) =>
      wasAsked(player, 'familyArc.familySecret') &&
      playerHasFlag(player, FAMILY_ARC_FLAGS.secretConfronted),
    choices: [
      choice(
        'aftermath',
        'Continue',
        'The family confrontation about the secret was intense. Some members were angry, others relieved it was finally out. Things are different now, but at least there are no more lies.',
        { effects: { stats: { stress: -5 } } }
      ),
    ],
  },

  // 4c. Secret investigation findings (flag-gated PASSIVE).
  {
    id: 'familyArc.secretInvestigation',
    category: 'family',
    kind: 'passive',
    prompt: 'The Investigation',
    minAge: 16,
    maxAge: 80,
    weight: 1,
    isEligible: (player) =>
      wasAsked(player, 'familyArc.familySecret') &&
      playerHasFlag(player, FAMILY_ARC_FLAGS.secretInvestigated),
    choices: [
      choice(
        'findings',
        'Continue',
        'Your quiet investigation uncovered more than you expected. The truth is complicated, but understanding it helps you make sense of things that never quite added up.',
        { effects: { stats: { happiness: 3, stress: -3 } } }
      ),
    ],
  },

  // ===========================================================================
  // 5. FamilyReunion (MULTI-STAGE: invite INTERACTIVE -> reunion day, OR skip)
  // Legacy: requires 2+ living family members; organize/attend/brief incur cost,
  // boost happiness, and schedule a 30-day reunion-day follow-up; skip drops
  // happiness/affinity (no follow-up). Flatten: attending branches set
  // `reunionAttending` (unlocks the day); skip sets no flag.
  // ===========================================================================
  {
    id: 'familyArc.familyReunion',
    category: 'family',
    prompt:
      'Your family is organizing a big reunion this summer - aunts, uncles, cousins, the whole extended family. Everyone is expected to contribute. Are you going?',
    minAge: 12,
    maxAge: 100,
    weight: 1,
    isEligible: (player) => hasRelation(player, 'mother', 'father', 'sibling', 'brother', 'sister'),
    choices: [
      choice(
        'organize',
        "Yes, I'll help organize! ($200)",
        'You threw yourself into organizing the family reunion - reserved the pavilion, coordinated the potluck, and even made a family trivia game. It is going to be great.',
        {
          energyCost: 15,
          moneyCost: 200,
          effects: { stats: { happiness: 10, social: 8 } },
          setFlags: [FAMILY_ARC_FLAGS.reunionAttending],
        }
      ),
      choice(
        'attend',
        "I'll attend and bring a dish ($50)",
        "You RSVP'd for the family reunion and signed up to bring a dish. Looking forward to seeing everyone.",
        {
          energyCost: 5,
          moneyCost: 50,
          effects: { stats: { happiness: 8, social: 5 } },
          setFlags: [FAMILY_ARC_FLAGS.reunionAttending],
        }
      ),
      choice(
        'brief',
        'Show up briefly',
        "You told the family you'd stop by the reunion but can't stay the whole time. At least you'll make an appearance.",
        { effects: { stats: { happiness: 3, social: 3 } }, setFlags: [FAMILY_ARC_FLAGS.reunionAttending] }
      ),
      choice(
        'skip',
        'Skip it this year',
        'You skipped the family reunion. Photos on social media show everyone having a great time. Your absence was noticed and mentioned more than once.',
        { effects: { stats: { happiness: -5, social: -5 } } }
      ),
    ],
  },

  // 5b. Reunion day (stage 2; flag-gated PASSIVE).
  {
    id: 'familyArc.reunionDay',
    category: 'family',
    kind: 'passive',
    prompt: 'The Reunion',
    minAge: 12,
    maxAge: 100,
    weight: 1,
    isEligible: (player) =>
      wasAsked(player, 'familyArc.familyReunion') &&
      playerHasFlag(player, FAMILY_ARC_FLAGS.reunionAttending),
    choices: [
      choice(
        'attend',
        'Continue',
        "The family reunion was wonderful. You reconnected with cousins you hadn't seen in years and met some new additions to the family. There was a moment when everyone sat together, all generations, and it just felt right.",
        { effects: { stats: { happiness: 10, social: 8, energy: -5 } } }
      ),
    ],
  },

  // ===========================================================================
  // 6. InheritanceDispute (MULTI-STAGE: dispute INTERACTIVE -> resolution)
  // Legacy: requires a deceased parent/grandparent; mediator/broker/step_back/
  // fight branches adjust money/stress (fight adds $5000 but tanks sibling
  // affinity) and schedule a resolution follow-up. Flatten: branches keep their
  // representative deltas; the resolution (stage 2) is a passive gated on
  // wasAsked stage 1; affinity is dropped (no per-NPC plumbing).
  // ===========================================================================
  {
    id: 'familyArc.inheritanceDispute',
    category: 'family',
    prompt:
      "After a family member's passing, their estate needs to be settled. The will is vague and family members are arguing about who gets what. The tension is rising. How do you handle this?",
    minAge: 25,
    maxAge: 80,
    weight: 1,
    isEligible: (player) => hasDeceasedFamily(player),
    choices: [
      choice(
        'mediator',
        'Suggest hiring a mediator ($1000)',
        'You suggested hiring a professional mediator. Some family members grumbled about the cost, but agreed it was better than destroying relationships over money.',
        { energyCost: 10, moneyCost: 1000, effects: { stats: { stress: 20 } } }
      ),
      choice(
        'broker',
        'Try to broker peace yourself',
        'You stepped in to mediate between family members. It is emotionally draining but someone has to be the voice of reason.',
        { energyCost: 15, effects: { stats: { stress: 20 } } }
      ),
      choice(
        'step_back',
        'Step back and let them sort it out',
        "You decided to stay out of the inheritance dispute. It's not worth damaging relationships over money. At least, that's what you tell yourself.",
        { effects: { stats: { stress: 20 } } }
      ),
      choice(
        'fight',
        'Fight for what you believe is fair',
        'You fought for what you believed was your fair share. The arguments were ugly, voices were raised, and some things were said that can\'t be unsaid. You inherited $5000.',
        {
          energyCost: 10,
          effects: { stats: { stress: 20, social: -10 }, resources: { money: 5000 } },
        }
      ),
    ],
  },

  // 6b. Inheritance resolution (stage 2; PASSIVE, gated on wasAsked stage 1).
  {
    id: 'familyArc.inheritanceResolution',
    category: 'family',
    kind: 'passive',
    prompt: 'The Estate Is Settled',
    minAge: 25,
    maxAge: 81,
    weight: 1,
    isEligible: (player) => wasAsked(player, 'familyArc.inheritanceDispute'),
    choices: [
      choice(
        'resolved',
        'Continue',
        'The dispute eventually resolved. Not everyone got what they wanted, but the estate is settled. Whether the family came through it closer or more strained is a story still being written.',
        { effects: { stats: { stress: -5 } } }
      ),
    ],
  },
];

// Date-seeded variation for the REPEATABLE child-milestones event so repeated
// firings across the years do not read identically. Applied via the optional
// `*Fn` override; the static text remains the fallback.
const childCelebrateVariants = [
  "You celebrated your child's milestone in style - photos, treats, the whole family told. One of the best moments of your life so far.",
  "You went all out for your child's big moment. You'll be replaying this memory for years. They beamed at all the attention.",
  "You made a real occasion of your child's milestone. Cake, photos, and a happy dance. These are the days you'll never forget.",
  "You dropped everything to celebrate your child fully. The pure joy on their face was worth every bit of the fuss.",
];

const childWarmVariants = [
  "You acknowledged your child's milestone with genuine warmth. A hug, kind words, and your full attention. Sometimes that's all a kid needs.",
  'You met your child where they were - a warm smile, a "I\'m so proud of you," and a moment that was just theirs. It landed.',
  "You gave your child a quiet, heartfelt acknowledgment. No big production, just genuine pride. They felt seen.",
];

const childBusyVariants = [
  'You were distracted when the milestone happened. A half-hearted "that\'s nice." Their face fell a little, and you wish you\'d been more present.',
  "Work was on your mind and you barely looked up. Your child noticed. You'll carry a small twinge of guilt about this one.",
  "You let the moment slip by while preoccupied. A missed chance to connect - the kind you only recognize in hindsight.",
];

const reunionDayVariants = [
  "The family reunion was wonderful. You reconnected with cousins you hadn't seen in years. A moment when all the generations sat together just felt right.",
  'The reunion was a hit. Old stories, new faces, and your relatives genuinely glad you came. You left full of food and good feeling.',
  "The reunion reminded you why family matters. Laughter, a few happy tears, and the kind of belonging you can't manufacture.",
];

for (const def of familyArcCatalog) {
  if (def.id === 'familyArc.childMilestones') {
    const celebrate = def.choices.find((c) => c.choiceId === 'celebrate');
    const warm = def.choices.find((c) => c.choiceId === 'warm');
    const busy = def.choices.find((c) => c.choiceId === 'busy');
    if (celebrate) celebrate.resolutionTextFn = seeded('childCelebrate', childCelebrateVariants);
    if (warm) warm.resolutionTextFn = seeded('childWarm', childWarmVariants);
    if (busy) busy.resolutionTextFn = seeded('childBusy', childBusyVariants);
  }
  if (def.id === 'familyArc.reunionDay') {
    def.choices[0].resolutionTextFn = seeded('reunionDay', reunionDayVariants);
  }
}

/**
 * Deterministic resolution-text lookup for a family-arc choice (static fallback
 * text). Used by tests and any caller needing the resolved copy without firing
 * the engine.
 */
export function resolveFamilyArcChoice(eventId: string, choiceId: string): string | null {
  const definition = familyArcCatalog.find((event) => event.id === eventId);
  if (!definition) {
    return null;
  }
  const selectedChoice = definition.choices.find((item) => item.choiceId === choiceId);
  return selectedChoice?.resolutionText ?? null;
}
