export type EventInstanceStatus = 'pending' | 'answered' | 'resolved' | 'cancelled';
export type EventKind = 'interactive' | 'passive';

/**
 * A player-derived text producer. Used by the OPTIONAL `*Fn` fields below to
 * vary repeatable-event copy across firings without changing the existing
 * required string fields (so the many static catalogs / `resolveXChoice`
 * helpers keep compiling). Returning a date-seeded variant keeps the text fixed
 * WITHIN a single firing but varied across firings/years. When a `*Fn` is
 * present the engine renders it and falls back to the static string otherwise.
 */
export type DynamicTextFn = (player: EventPlayerContext) => string;

export interface EventChoice {
  choiceId: string;
  text: string;
  energyCost?: number;
  moneyCost?: number;
  diamondCost?: number;
  resolutionText?: string;
  /**
   * Optional player-derived override for `resolutionText`. When set, the engine
   * renders this at resolution time (enabling per-firing text variation) and
   * uses `resolutionText` only as a static fallback.
   */
  resolutionTextFn?: DynamicTextFn;
  effects?: EventEffects;
  /**
   * Persistent string flags set on the player when this choice is resolved.
   * Used to gate multi-stage arcs that branch on WHICH choice was made (not
   * merely that a stage-1 event fired). Read in a downstream event's
   * `isEligible` via `playerHasFlag`. Stored on the player as a Set (in-memory)
   * / array (JSON), mirroring `askedQuestions`.
   */
  setFlags?: string[];
}

export interface EventEffects {
  stats?: Record<string, number>;
  resources?: {
    energy?: number;
    money?: number;
    diamonds?: number;
  };
  relationships?: Array<{
    personId: string;
    affinityDelta: number;
  }>;
}

/**
 * An affinity change that was actually applied to a specific NPC during event
 * resolution, enriched with that character's display name. Surfaced in the
 * event_resolved payload so the client can show e.g. "Mom +5 affinity" on the
 * decision-confirmation screen without a separate name lookup.
 */
export interface ResolvedRelationshipEffect {
  personId: string;
  name: string;
  affinityDelta: number;
}

export interface EventDefinition {
  id: string;
  category: string;
  kind?: EventKind;
  prompt: string;
  /**
   * Optional player-derived override for `prompt`. When set, the engine renders
   * this at prompt time (enabling per-firing text variation) and uses `prompt`
   * only as a static fallback.
   */
  promptFn?: DynamicTextFn;
  choices: EventChoice[];
  minAge?: number;
  maxAge?: number;
  isEligible?: (player: EventPlayerContext) => boolean;
  /**
   * Relative selection weight for weighted-random picking among eligible
   * events. Higher weight = more likely to be chosen. Treated as 1 when
   * omitted (or when <= 0, which is clamped up to a minimum positive weight).
   */
  weight?: number;
  /**
   * Minimum number of in-game days that must elapse between firings of this
   * event. When set, the event is ineligible if it last fired fewer than
   * `cooldownDays` in-game days ago. Only meaningful for repeatable events
   * (a non-repeatable event already fires at most once).
   */
  cooldownDays?: number;
  /**
   * When true, the event may fire more than once over the player's lifetime.
   * The default (false/undefined) preserves the legacy once-ever behavior:
   * after firing, the event id is recorded in `askedQuestions` and never
   * fires again. Repeatable events skip that once-ever gate and are governed
   * instead by `cooldownDays` (if set).
   */
  repeatable?: boolean;
}

export interface EventPlayerContext {
  userId: string;
  c: {
    ageYears?: number;
    [key: string]: unknown;
  };
  /**
   * Per-event last-fired record, keyed by event id, valued by the in-game day
   * the event last fired on. Drives `cooldownDays` eligibility. A plain object
   * (not a Set), so it round-trips through JSON save/load natively without the
   * Set<->array conversion that `askedQuestions` requires.
   */
  eventLastFired?: Record<string, number>;
  [key: string]: unknown;
}

export interface EventInstance {
  instanceId: string;
  eventId: string;
  playerId: string;
  status: EventInstanceStatus;
  createdAt: string;
  answeredAt?: string;
  resolvedAt?: string;
  selectedChoiceId?: string;
  context?: Record<string, unknown>;
}

export interface EventPromptEnvelope {
  type: 'event_prompt';
  eventId: string;
  instanceId: string;
  prompt: string;
  choices: EventChoice[];
  metadata?: Record<string, unknown>;
}

export interface EventResolvedEnvelope {
  type: 'event_resolved';
  eventId: string;
  instanceId: string;
  resolutionText: string;
  effects?: EventEffects;
  /**
   * Affinity changes that were actually applied to named NPCs, so the client
   * can attribute them on the confirmation screen (e.g. "Mom +5 affinity").
   * Derived from effects.relationships + the player's relationship roster.
   */
  resolvedRelationships?: ResolvedRelationshipEffect[];
  metadata?: Record<string, unknown>;
}

export interface EventErrorEnvelope {
  type: 'event_error';
  code: string;
  message: string;
  eventId?: string;
  instanceId?: string;
  details?: Record<string, unknown>;
}

export type OutboundEventEnvelope =
  | EventPromptEnvelope
  | EventResolvedEnvelope
  | EventErrorEnvelope;

export interface EventResponseCommand {
  type: 'eventResponse';
  message: {
    eventId: string;
    choiceId: string;
  };
}
