// The bartender shift (docs/SCENARIOS.md, design §6 role ladder). Same split
// as djShift.ts: pure pricing here, tested; the scene owns the overlay, the
// teleport and the taps.
//
// The shape of the fantasy: the other side of the counter. Every order is a
// sobriety read at arm's length — the stage is written on the doll and in the
// spelling — and SERVE / WATER / CUT OFF are all legal, all priced (§4.3).
// Serving someone visibly cooked is the one the licence remembers.

import type { DrunkStage } from '../../data/types';
import { WATER_SOBER_STEP } from './hazards';

export type BarCall = 'serve' | 'water' | 'cutoff';

/** What one served drink adds — a real pour, on top of the bar-anchor drift. */
export const SERVE_DRINK_STEP = 0.06;

/** Chance-per-second an order lands while you hold the taps (~one per 18s). */
export const ORDER_CHANCE_PER_S = 0.055;
/** One patron doesn't re-order inside this window. */
export const ORDER_COOLDOWN_MS = 60_000;

/**
 * The RSA line (rules/drunk.ts stages): messy and maggot are the breach.
 * 'loose' has visible tells but stays legal — that band is where the player
 * has to actually decide something, same as the floor cut-off.
 */
export function isRsaBreach(stage: DrunkStage): boolean {
  return stage === 'messy' || stage === 'maggot';
}

/** Stable reason so shouldRecordStrike folds a whole bad shift into ONE write-up. */
export const RSA_STRIKE_REASON = 'served alcohol to a visibly intoxicated patron';
/** A breach pour only becomes a strike if somebody official SAW it. */
export const BREACH_SEEN_CHANCE = 0.35;
/** Clock-minutes later the write-up lands: base + up-to spread. */
export const BREACH_SEEN_AFTER: readonly [number, number] = [6, 20];

export interface BarOutcome {
  vibe?: number;
  aggro?: number;
  /** Applied to the patron's intoxication (scene clamps to 0..1). */
  intoxDelta: number;
  /** An RSA breach pour — may earn the deferred write-up. */
  breach: boolean;
  /** Done for the night; the crowd walks them out itself. */
  sendsHome: boolean;
}

/**
 * Price a call against what the patron actually is (§4.3: priced, never
 * graded). The breach pour still pays its vibe up front — the bribe is
 * immediate, the price arrives later.
 */
export function judgeCall(call: BarCall, stage: DrunkStage): BarOutcome {
  if (call === 'serve') {
    return {
      vibe: 1,
      intoxDelta: SERVE_DRINK_STEP,
      breach: isRsaBreach(stage),
      sendsHome: false,
    };
  }
  if (call === 'water') {
    const needed = stage === 'loose' || isRsaBreach(stage);
    return needed
      ? { vibe: 1, intoxDelta: -WATER_SOBER_STEP, breach: false, sendsHome: false }
      : { aggro: 1, intoxDelta: -WATER_SOBER_STEP, breach: false, sendsHome: false };
  }
  // cutoff: right on a breach-stage patron, a whole scene on anyone upright.
  return isRsaBreach(stage)
    ? { vibe: 1, aggro: 1, intoxDelta: 0, breach: false, sendsHome: true }
    : { vibe: -1, aggro: 2, intoxDelta: 0, breach: false, sendsHome: true };
}

// ---- the pour (SERVE is a hand, not a menu click) ---------------------------
// A sweeping marker, one key to stop it. The line lives near the top because a
// good pour is nearly a mistake.

export type PourVerdict = 'short' | 'clean' | 'overflow';

/** Marker sweep 0→1→0, this long each way. */
export const POUR_SWEEP_MS = 1300;
export const POUR_CLEAN_FROM = 0.68;
export const POUR_OVER_FROM = 0.93;
/** What a heavy hand adds on top of SERVE_DRINK_STEP. */
export const OVERFLOW_EXTRA_INTOX = 0.03;

export function judgePour(fill: number): PourVerdict {
  if (fill >= POUR_OVER_FROM) return 'overflow';
  if (fill >= POUR_CLEAN_FROM) return 'clean';
  return 'short';
}

/**
 * Fold the pour into the serve. Short: half a drink, no thanks, a look.
 * Overflow: the room loves a heavy pour and the patron wears more of it.
 * The RSA breach flag rides the SERVE decision, not the pour — a stingy pour
 * to a maggot is still a pour to a maggot.
 */
export function pourAdjust(out: BarOutcome, pour: PourVerdict): BarOutcome {
  if (pour === 'short') {
    const adjusted: BarOutcome = { ...out, aggro: (out.aggro ?? 0) + 1, intoxDelta: out.intoxDelta / 2 };
    delete adjusted.vibe;
    return adjusted;
  }
  if (pour === 'overflow') {
    return { ...out, vibe: (out.vibe ?? 0) + 1, intoxDelta: out.intoxDelta + OVERFLOW_EXTRA_INTOX };
  }
  return out;
}

// ---- tabs (the jar of other people's cards) ---------------------------------

/** Chance a served tipsy+ patron opens a tab ("start a tab? legend"). */
export const TAB_OPEN_CHANCE = 0.3;
/** A drink, for the tab total the toast reads out. */
export const TAB_DRINK_PRICE = 14;

export interface Tab {
  /** The name printed on the card in the jar. */
  name: string;
  drinks: number;
}

/** Stages loose+ can open a tab — sober people pay as they go. */
export function canOpenTab(stage: DrunkStage): boolean {
  return stage !== 'sober';
}

/**
 * The card-return line-up: the right name plus two strangers' cards from the
 * jar/room. Order is seeded so the correct answer isn't always slot one.
 */
export function cardLineup(
  right: string,
  others: readonly string[],
  rng: { int(min: number, max: number): number },
): string[] {
  const decoys = [...new Set(others.filter((n) => n !== right))];
  // Fisher–Yates on the decoy pool, take two.
  for (let i = decoys.length - 1; i > 0; i--) {
    const j = rng.int(0, i);
    [decoys[i], decoys[j]] = [decoys[j]!, decoys[i]!];
  }
  const lineup = [right, ...decoys.slice(0, 2)];
  for (let i = lineup.length - 1; i > 0; i--) {
    const j = rng.int(0, i);
    [lineup[i], lineup[j]] = [lineup[j]!, lineup[i]!];
  }
  return lineup;
}
