/** ヒヤリハット → 事故 のエスカレーション。
 *  同じ種類のヒヤリハットを放置するほどリスクスコアが積み上がり、事故確率が上がる。 */
import { ALL_ACTIONS, HARM_RULES, HAZARDS, SEVERITY_LABEL } from '../core/config';
import { tx, ty } from '../core/grid';
import {
  isOccupationalInjury,
  type Cat,
  type HazardKind,
  type IncidentSeverity,
} from '../core/types';
import { applyInjury, applyPropertyDamage } from './effects';
import { pushLog, type GameState } from './state';

/** 同一猫・同一種のハザードが連発しないようにするクールダウン (ティック) */
const REPEAT_COOLDOWN = 150;
const cooldowns = new Map<string, number>();

export function resetHazardCooldowns(): void {
  cooldowns.clear();
}

function onCooldown(s: GameState, key: string): boolean {
  const until = cooldowns.get(key);
  if (until !== undefined && until > s.day * 100000 + s.tick) return true;
  cooldowns.set(key, s.day * 100000 + s.tick + REPEAT_COOLDOWN);
  return false;
}

/** 購入済みアップグレードから低減率を再計算する。 */
export function recalcMitigation(s: GameState): void {
  for (const k of Object.keys(s.mitigation) as HazardKind[]) s.mitigation[k] = 0;
  for (const up of ALL_ACTIONS) {
    const count = s.upgrades[up.id] ?? 0;
    if (count <= 0 || up.reduction <= 0) continue;
    for (const kind of up.mitigates) {
      // 重ねがけは逓減 (1 - (1-r)^n)
      const combined = 1 - Math.pow(1 - up.reduction, count);
      s.mitigation[kind] = 1 - (1 - s.mitigation[kind]) * (1 - combined);
    }
  }
}

export interface HazardOptions {
  /** trueなら必ず事故として扱う (巻き込みなど、既に被害が出ている場合) */
  forceAccident?: boolean;
  /** 文面の {cat} 以外に差し込む追加テキスト */
  detail?: string;
}

/**
 * ハザードを1件発生させ、リスクスコアに応じてヒヤリハット／事故に振り分ける。
 * @returns 実際に記録された深刻度。クールダウン中なら null。
 */
export function raiseHazard(
  s: GameState,
  kind: HazardKind,
  cat: Cat | null,
  tileIndex: number | null,
  opts: HazardOptions = {},
): IncidentSeverity | null {
  const key = `${kind}:${cat?.id ?? 'x'}`;
  if (!opts.forceAccident && onCooldown(s, key)) return null;

  const def = HAZARDS[kind];
  const effRisk = s.risk[kind] * (1 - s.mitigation[kind]);
  const p = effRisk / 100;
  const bias = def.harmBias ?? 1;

  let severity: IncidentSeverity =
    opts.forceAccident || s.rng.chance(Math.min(0.85, p * 0.9 * bias)) ? 'minor' : 'nearmiss';

  // 物損で済むか、人身災害（労災）に至るかを判定する。
  // ヘルメット未着用・疲労・機嫌の悪化が、そのまま被害の大きさに跳ね返る。
  if (severity === 'minor' && cat && def.harm !== 'none') {
    const vulnerability =
      1 + (cat.helmet ? 0 : 0.55) + (cat.hp < 30 ? 0.35 : 0) + (cat.mood < 30 ? 0.2 : 0);
    const harmP = Math.min(0.8, Math.max(0.05, p * 0.8 * vulnerability * bias));
    if (s.rng.chance(harmP)) {
      severity = 'injury';
      if (
        def.harm !== 'injury' &&
        s.rng.chance(Math.min(0.7, Math.max(0.04, p * 0.5 * vulnerability * bias)))
      ) {
        severity = 'serious';
      }
      if (
        def.harm === 'fatal' &&
        severity === 'serious' &&
        s.rng.chance(Math.min(0.6, Math.max(0.03, p * 0.4 * vulnerability * bias)))
      ) {
        severity = 'fatal';
      }
    }
  }

  const name = cat ? cat.name : '不明な従業員';
  let text: string;
  if (severity === 'nearmiss') {
    text = def.nearmiss.replace('{cat}', name);
  } else if (isOccupationalInjury(severity)) {
    text = (def.harmText ?? def.accident).replace('{cat}', name);
    text = `【労災・${HARM_RULES[severity].label}】${text}`;
  } else {
    text = def.accident.replace('{cat}', name);
  }
  if (opts.detail) text += `（${opts.detail}）`;

  let cost = 0;
  let compensation = 0;
  if (severity !== 'nearmiss') {
    cost = Math.round(def.damage * (1 - s.mitigation[kind] * 0.5));
  }
  if (isOccupationalInjury(severity)) {
    compensation = HARM_RULES[severity].cost;
    cost += compensation;
    s.compensationToday += compensation;
    s.injuriesToday[severity]++;
  }

  const RISK_GAIN: Record<IncidentSeverity, number> = {
    nearmiss: 6, minor: 13, injury: 18, serious: 24, fatal: 30,
  };
  s.risk[kind] = Math.min(100, s.risk[kind] + RISK_GAIN[severity]);

  if (cost > 0) {
    s.money -= cost;
    s.damagesToday += cost;
  }

  applyEffects(s, kind, severity, cat, tileIndex);

  // 事案は必ず書類になる。放置すると事務作業が追いつかなくなる。
  s.paperwork.push({ kind, severity, day: s.day });

  s.incidentsToday.push({
    day: s.day,
    tick: s.tick,
    kind,
    severity,
    catId: cat?.id ?? null,
    tileIndex,
    text,
    cost,
  });

  const tone = severity === 'nearmiss' ? 'warn' : 'danger';
  const tag = SEVERITY_LABEL[severity];
  const where = tileIndex !== null ? ` @${tx(tileIndex)},${ty(tileIndex)}` : '';
  pushLog(s, `[${tag}]${where} ${text}${cost > 0 ? ` -¥${cost.toLocaleString()}` : ''}`, tone);

  return severity;
}

/** 深刻度に応じた実害をワールドに反映する。中身は effects.ts に分けてある。 */
function applyEffects(
  s: GameState,
  kind: HazardKind,
  severity: IncidentSeverity,
  cat: Cat | null,
  tileIndex: number | null,
): void {
  const maintenance = s.upgrades['maintenance'] ?? 0;
  const breakTicks = Math.max(60, Math.round(300 * Math.pow(0.75, maintenance)));

  if (isOccupationalInjury(severity) && cat) {
    applyInjury(s, cat, severity, tileIndex, breakTicks);
    return;
  }
  applyPropertyDamage(s, kind, severity, cat, tileIndex, breakTicks);
}
