import {
  BASKET_TIER,
  BENCH_QUEUE_CAP,
  CONTAINER_CAP,
  HANDLER_CARRY_CAP,
  HANDLER_DROP_TICKS,
  HANDLER_EQUIPMENT,
  HANDLER_PICK_TICKS,
  PALLET_TIER,
  STOCKER_CAP,
  TRUCK_CAP,
  machineDef,
} from '../core/config';
import { findPath, findPathToIndex, findPathToKind } from '../core/pathfinding';
import { idx, tx, ty } from '../core/grid';
import { CatState, Dir, type Cat, type Tile } from '../core/types';
import { raiseHazard } from './incidents';
import { MATERIALS } from '../core/research';
import {
  hasForwardConnection,
  isFinished,
  matchesYardMode,
  newItem,
  shipItem,
} from './factory';
import {
  activeCats,
  breakRecovery,
  isBreakTime,
  pushLog,
  type GameState,
} from './state';

/** 1ティックあたりの移動距離 (タイル) */
const WALK_SPEED = 0.062;

/** breakTile に入れる「この休憩では報告済み」の目印 */
const UNREPORTED_SENTINEL = -1;

const CAT_NAMES = [
  'ミケ', 'クロ', 'シロ', 'トラ', 'タマ', 'ハチ', 'アズキ', 'モモ',
  'レオ', 'ソラ', 'キナコ', 'ダイフク', 'チャチャ', 'ムギ', 'ゴマ', 'ルナ',
];
const CAT_COLORS = [
  '#e8c39e', '#4a4a52', '#f0ece4', '#c8944a',
  '#d9a066', '#8c6b52', '#b8b0a4', '#6b5b73',
];

export function createCat(s: GameState, x: number, y: number): Cat {
  const id = s.catSeq++;
  const suffix = id >= CAT_NAMES.length ? String(Math.floor(id / CAT_NAMES.length) + 1) : '';
  return {
    id,
    name: CAT_NAMES[id % CAT_NAMES.length] + suffix,
    color: CAT_COLORS[id % CAT_COLORS.length],
    lives: 9,
    hp: s.rng.range(70, 100),
    mood: s.rng.range(60, 90),
    hunger: s.rng.range(0, 30),
    bladder: s.rng.range(0, 30),
    boredom: 0,
    focus: s.rng.range(45, 90),
    memory: s.rng.range(30, 90),
    x,
    y,
    state: CatState.Idle,
    stateTimer: 0,
    path: [],
    carrying: [],
    targetDropoff: null,
    stationIndex: null,
    breakTile: null,
    helmet: true,
    rageTargetCat: null,
    rageTargetTile: null,
    offDutyDays: 0,
    injury: 'none',
    pathFails: 0,
    producedToday: 0,
    wobble: s.rng.range(0, Math.PI * 2),
    facing: Dir.South,
    equipment: 'none',
  };
}

export function catTileIndex(cat: Cat): number {
  return idx(Math.round(cat.x), Math.round(cat.y));
}

function tileAt(s: GameState, cat: Cat): Tile {
  return s.tiles[catTileIndex(cat)];
}

/** 機嫌の下限。高級猫おやつを配給しているほど底上げされる。 */
function moodFloor(s: GameState): number {
  return Math.min(45, 8 + (s.upgrades['treats'] ?? 0) * 12);
}

export function tickCats(s: GameState): void {
  for (const cat of s.cats) {
    if (cat.offDutyDays > 0 || cat.lives <= 0) {
      cat.state = CatState.OffDuty;
      continue;
    }
    updateNeeds(s, cat);
    decideState(s, cat);
    advance(s, cat);
    passiveHazards(s, cat);
    cat.wobble += 0.12;
  }
}

// 1シフト(1800ティック)のあいだに、トイレ1〜2回・食事1回・休憩1回ほど席を立つ配分。
// 休憩所やトイレをラインから遠くに置くほど、この往復がそのまま生産ロスになる。
const HUNGER_RATE = 0.055;
const BLADDER_RATE = 0.075;
const FATIGUE_RATE = 0.035;
/** 1個加工するごとの体力消費 */
export const WORK_FATIGUE = 1.2;

function updateNeeds(s: GameState, cat: Cat): void {
  cat.hunger = Math.min(100, cat.hunger + HUNGER_RATE);
  cat.bladder = Math.min(100, cat.bladder + BLADDER_RATE);
  cat.hp = Math.max(0, cat.hp - FATIGUE_RATE);

  const station = cat.stationIndex !== null ? s.tiles[cat.stationIndex] : null;
  const starved =
    station !== null &&
    machineDef(station.kind) !== null &&
    station.queue.length === 0 &&
    station.brokenFor <= 0;
  if (cat.state === CatState.Working && starved) {
    // ボトルネック: 部品が届かないと退屈して破壊衝動が育つ
    cat.boredom = Math.min(100, cat.boredom + 0.22);
  } else {
    cat.boredom = Math.max(0, cat.boredom - 0.05);
  }

  // 機嫌は空腹・尿意・疲労・退屈で削られ、下限に向かって漂う
  const pressure =
    (cat.hunger > 70 ? 0.03 : 0) +
    (cat.bladder > 75 ? 0.038 : 0) +
    (cat.hp < 30 ? 0.026 : 0) +
    (cat.boredom > 70 ? 0.022 : 0);
  const floor = moodFloor(s);
  if (pressure > 0) {
    cat.mood = Math.max(0, cat.mood - pressure);
  } else if (cat.mood < 78) {
    cat.mood = Math.min(78, cat.mood + 0.014);
  }
  if (cat.mood < floor) cat.mood = Math.min(floor, cat.mood + 0.05);

  // 集中力は機嫌と体力に引きずられる
  const targetFocus = 25 + (cat.mood * 0.4 + cat.hp * 0.35);
  cat.focus += (targetFocus - cat.focus) * 0.002;
}

/** 経路をセットする。到達不能なら false。 */
function setPathTo(
  s: GameState,
  cat: Cat,
  result: ReturnType<typeof findPath>,
  reportBlocked: boolean,
): boolean {
  if (!result.path) {
    cat.path = [];
    cat.pathFails++;
    if (reportBlocked && cat.pathFails % 3 === 1) {
      raiseHazard(s, 'blocked_route', cat, catTileIndex(cat));
    }
    return false;
  }
  cat.path = result.path;
  cat.pathFails = 0;
  if (result.usedHazard) {
    // 正規ルートが無いのでコンベアをショートカットしている
    raiseHazard(s, 'cross_line', cat, result.path[0] ?? null);
  }
  return true;
}

function decideState(s: GameState, cat: Cat): void {
  // --- 定時休憩 ---
  if (handleBreak(s, cat)) return;

  // --- 拘束状態の消化 ---
  if (cat.stateTimer > 0) {
    cat.stateTimer--;
    switch (cat.state) {
      case CatState.Resting:
        cat.hp = Math.min(100, cat.hp + 0.34);
        cat.mood = Math.min(100, cat.mood + 0.12);
        return;
      case CatState.Eating:
        cat.hunger = Math.max(0, cat.hunger - 1.6);
        cat.mood = Math.min(100, cat.mood + 0.22);
        return;
      case CatState.UsingLitter:
        cat.bladder = Math.max(0, cat.bladder - 3.2);
        return;
      case CatState.Scratching:
        cat.boredom = Math.max(0, cat.boredom - 1.1);
        cat.mood = Math.min(100, cat.mood + 0.18);
        return;
      case CatState.Boxed:
        // 段ボール箱は最強の鎮静装置
        cat.mood = Math.min(100, cat.mood + 0.42);
        cat.boredom = Math.max(0, cat.boredom - 0.6);
        cat.hp = Math.min(100, cat.hp + 0.14);
        return;
      case CatState.Sleeping:
        cat.hp = Math.min(100, cat.hp + 0.22);
        return;
      default:
        return;
    }
  }

  // タイマー切れ → 拘束解除
  switch (cat.state) {
    case CatState.Resting:
    case CatState.Eating:
    case CatState.UsingLitter:
    case CatState.Scratching:
    case CatState.Boxed:
    case CatState.Sleeping:
    case CatState.Panic:
    case CatState.Fighting:
      cat.state = CatState.Idle;
      cat.path = [];
      break;
    case CatState.Picking:
      handlePickingComplete(s, cat);
      return;
    case CatState.Dropping:
      handleDroppingComplete(s, cat);
      return;
    default:
      break;
  }

  // --- フォークリフト運転中 ---
  // 位置は車両側が決めるので、通常の「持ち場へ戻る」判断からは外す。
  // これを入れないと、詰所を離れた瞬間に猫が歩いて戻ろうとして車両が進まない。
  if (cat.state === CatState.Driving) {
    const mustDismount =
      cat.bladder > 82 || cat.hunger > 78 || cat.hp < 32 || cat.mood < 22;
    if (!mustDismount) return;
    cat.state = CatState.Idle;
    cat.path = [];
  }

  // --- 荷役作業員が荷物を持っている場合は荷降ろし先へ ---
  if (cat.carrying.length > 0 && cat.state !== CatState.GoToDropoff) {
    if (decideDropoff(s, cat)) return;
  }

  // --- 移動中なら継続 ---
  if (cat.path.length > 0) return;

  // --- 到着処理 ---
  if (arriveIfPossible(s, cat)) return;

  // --- 新しい目的を決める ---
  // 1) 体力切れ: その場で居眠り
  if (cat.hp < 12) {
    beginSleep(s, cat);
    return;
  }

  // 2) 暴走状態: 八つ当たり先を探す
  if (cat.mood < 22 || cat.state === CatState.Rage) {
    startRage(s, cat);
    return;
  }

  // 3) 生理的欲求
  if (cat.bladder > 82 && goToKind(s, cat, 'litter', CatState.GoToLitter)) return;
  if (cat.hunger > 78 && goToKind(s, cat, 'feeder', CatState.GoToFeeder)) return;
  if (cat.hp < 32 && tryGoRestOrBoiler(s, cat)) return;

  if (cat.boredom > 84) {
    if (goToKind(s, cat, 'scratch', CatState.GoToScratch)) return;
    // 逃がし先が無ければ機械を壊しにかかる
    if (cat.stationIndex !== null && machineDef(s.tiles[cat.stationIndex].kind)) {
      raiseHazard(s, 'cable_bite', cat, cat.stationIndex);
      cat.boredom = 40;
      return;
    }
  }

  // 4) 通常業務 (作業台、事務机、開発室、詰所、バース、荷役ステーション)
  if (cat.stationIndex !== null) {
    const station = s.tiles[cat.stationIndex];
    if (station.kind === 'handler_post') {
      if (decideRouteLogisticsTask(s, cat)) return;
      if (decideHandlerTask(s, cat)) return;
    } else {
      const isStationTile =
        machineDef(station.kind) !== null ||
        station.kind === 'office' ||
        station.kind === 'lab' ||
        station.kind === 'forkdepot' ||
        station.kind === 'dock';
      if (!isStationTile) {
        cat.stationIndex = null;
      } else if (catTileIndex(cat) === cat.stationIndex) {
        beginWork(s, cat);
        return;
      } else {
        const r = findPathToIndex(s.tiles, catTileIndex(cat), cat.stationIndex);
        if (setPathTo(s, cat, r, false)) {
          cat.state = CatState.GoToBench;
          return;
        }
      }
    }
  } else {
    // 持ち場未割当の手すきの猫: 手作業運搬ラインがあれば荷物を運ぶ
    if (decideRouteLogisticsTask(s, cat)) return;
    if (decideHandlerTask(s, cat)) return;
  }

  // 5) 手持ち無沙汰: うろつく
  wander(s, cat);
}

/**
 * 定時休憩の処理。休憩時間になったら全員が休憩室へ向かう。
 * 休憩室が無い・狭くて入れない場合は「休憩の未確保」として計上する。
 * @returns この関数が状態を決めたら true (以降の判断は行わない)
 */
function handleBreak(s: GameState, cat: Cat): boolean {
  const onBreak = isBreakTime(s);

  // 休憩終了 → 持ち場へ戻す
  if (!onBreak) {
    if (cat.state === CatState.OnBreak || cat.state === CatState.GoToBreak) {
      cat.state = CatState.Idle;
      cat.path = [];
    }
    cat.breakTile = null;
    return false;
  }

  // 暴走中・箱の中・喧嘩中は休憩どころではない
  switch (cat.state) {
    case CatState.Boxed:
    case CatState.Fighting:
    case CatState.Panic:
      return false;
    default:
      break;
  }

  if (cat.state === CatState.OnBreak) {
    restOnBreak(s, cat);
    return true;
  }

  if (cat.state === CatState.GoToBreak) {
    if (cat.path.length > 0) return true;
    // 到着した
    if (s.tiles[catTileIndex(cat)].kind === 'breakroom') {
      cat.state = CatState.OnBreak;
      s.tiles[catTileIndex(cat)].uses++;
      restOnBreak(s, cat);
      return true;
    }
    // 着いていない = 入れなかった
    cat.state = CatState.Idle;
    cat.breakTile = null;
    return false;
  }

  // これから休憩に入る
  const here = catTileIndex(cat);
  // 交替制を導入していれば1マスに2匹まで詰められる
  const perTile = 1 + (s.upgrades['shift_break'] ?? 0);
  const used = new Map<number, number>();
  for (const other of s.cats) {
    if (other.id === cat.id || other.breakTile === null || other.breakTile < 0) continue;
    used.set(other.breakTile, (used.get(other.breakTile) ?? 0) + 1);
  }

  const r = findPath(
    s.tiles,
    here,
    (t, i) => t.kind === 'breakroom' && (used.get(i) ?? 0) < perTile,
  );
  if (r.path) {
    cat.breakTile = r.goal;
    cat.path = r.path;
    cat.state = CatState.GoToBreak;
    cat.stateTimer = 0;
    if (r.path.length === 0) {
      cat.state = CatState.OnBreak;
      s.tiles[here].uses++;
    }
    return true;
  }

  // 休憩室が無い／全部埋まっている／辿り着けない。
  // 1回の休憩につき1件だけ計上する (-1 は「この休憩では報告済み」の目印)。
  if (cat.breakTile !== UNREPORTED_SENTINEL) {
    raiseHazard(s, 'no_break', cat, here);
    cat.breakTile = UNREPORTED_SENTINEL;
  }
  // 休めないぶん機嫌が削れる
  cat.mood = Math.max(0, cat.mood - 0.05);
  return false;
}

/** 休憩室での回復。部屋が広いほどよく休める。 */
function restOnBreak(s: GameState, cat: Cat): void {
  const q = breakRecovery(s);
  cat.path = [];
  cat.hp = Math.min(100, cat.hp + 0.30 * q);
  cat.mood = Math.min(100, cat.mood + 0.22 * q);
  cat.hunger = Math.max(0, cat.hunger - 0.10 * q);
  cat.boredom = Math.max(0, cat.boredom - 0.25 * q);
}

function beginSleep(s: GameState, cat: Cat): void {
  cat.state = CatState.Sleeping;
  cat.stateTimer = s.rng.int(240, 480);
  cat.path = [];
  const here = catTileIndex(cat);
  const t = s.tiles[here];
  if (t.kind === 'conveyor' && !t.catwalk) {
    raiseHazard(s, 'sleep_on_line', cat, here);
  } else if (t.kind === 'boiler') {
    raiseHazard(s, 'boiler_nap', cat, here);
  }
}

function goToKind(s: GameState, cat: Cat, kind: Tile['kind'], state: CatState): boolean {
  const here = catTileIndex(cat);
  const r = findPathToKind(s.tiles, here, kind);
  if (r.path && r.path.length === 0) {
    // 既にその上にいる
    cat.state = state;
    return arriveIfPossible(s, cat);
  }
  if (setPathTo(s, cat, r, kind === 'rest' || kind === 'litter')) {
    cat.state = state;
    return true;
  }
  return false;
}

/** 休憩所へ向かう。ただし暖かいボイラー室の誘惑に負けることがある。 */
function tryGoRestOrBoiler(s: GameState, cat: Cat): boolean {
  const here = catTileIndex(cat);
  const restPath = findPathToKind(s.tiles, here, 'rest');
  const locked = s.upgrades['boiler_lock'] ? 0.85 : 0;
  const temptation = (restPath.path ? 0.1 : 0.55) * (1 - locked);

  if (s.rng.chance(temptation)) {
    const boiler = findPathToKind(s.tiles, here, 'boiler');
    if (boiler.path) {
      setPathTo(s, cat, boiler, false);
      cat.state = CatState.GoToRest;
      return true;
    }
  }

  if (setPathTo(s, cat, restPath, true)) {
    cat.state = CatState.GoToRest;
    return true;
  }
  // 休憩所へ行けない → その場で寝る
  beginSleep(s, cat);
  return true;
}

/** 八つ当たり: 段ボール箱 > 爪とぎ > 近くの猫 > 機械 の優先度で対象を決める。 */
function startRage(s: GameState, cat: Cat): void {
  const here = catTileIndex(cat);

  // 段ボール箱が近くにあれば吸い込まれて鎮静化する
  const box = findPathToKind(s.tiles, here, 'box');
  if (box.path && box.path.length <= 10) {
    setPathTo(s, cat, box, false);
    cat.state = CatState.Rage;
    cat.rageTargetTile = box.goal;
    cat.rageTargetCat = null;
    return;
  }

  // 爪とぎタワーがあればヘイトがそちらに逸れる
  const scratch = findPathToKind(s.tiles, here, 'scratch');
  if (scratch.path && scratch.path.length <= 12 && s.rng.chance(0.7)) {
    setPathTo(s, cat, scratch, false);
    cat.state = CatState.GoToScratch;
    return;
  }

  // 近くの同僚にネコパンチ
  const victim = activeCats(s).find(
    (o) => o.id !== cat.id && Math.abs(o.x - cat.x) + Math.abs(o.y - cat.y) < 2.2,
  );
  if (victim) {
    const sev = raiseHazard(s, 'cat_punch', cat, here);
    if (sev !== null && sev !== 'nearmiss') {
      victim.state = CatState.Fighting;
      victim.stateTimer = 200;
      victim.mood = Math.max(0, victim.mood - 18);
      victim.path = [];
    }
    cat.state = CatState.Fighting;
    cat.stateTimer = 160;
    return;
  }

  // 対象が無ければ機械で爪を研ぐ
  if (cat.stationIndex !== null && machineDef(s.tiles[cat.stationIndex].kind)) {
    if (here === cat.stationIndex) {
      raiseHazard(s, 'scratch_machine', cat, cat.stationIndex);
      cat.mood = Math.min(100, cat.mood + 14);
      cat.state = CatState.Idle;
      return;
    }
    const r = findPathToIndex(s.tiles, here, cat.stationIndex);
    if (setPathTo(s, cat, r, false)) {
      cat.state = CatState.Rage;
      cat.rageTargetTile = cat.stationIndex;
      return;
    }
  }

  wander(s, cat);
}

function wander(s: GameState, cat: Cat): void {
  const here = catTileIndex(cat);
  const target = s.rng.int(0, s.tiles.length - 1);
  const r = findPath(s.tiles, here, (_t, i) => i === target);
  if (r.path && r.path.length > 0 && r.path.length < 26) {
    cat.path = r.path;
    cat.state = CatState.Idle;
  } else {
    cat.state = CatState.Idle;
    cat.stateTimer = 40;
  }
}

/** 目的地タイルに着いた時の処理。何かしたら true。 */
function arriveIfPossible(s: GameState, cat: Cat): boolean {
  const here = catTileIndex(cat);
  const t = tileAt(s, cat);
  switch (cat.state) {
    case CatState.GoToRest:
      if (t.kind === 'rest') {
        cat.state = CatState.Resting;
        cat.stateTimer = s.rng.int(200, 360);
        t.uses++;
        return true;
      }
      if (t.kind === 'boiler') {
        beginSleep(s, cat);
        return true;
      }
      return false;
    case CatState.GoToLitter:
      if (t.kind === 'litter') {
        cat.state = CatState.UsingLitter;
        cat.stateTimer = s.rng.int(60, 110);
        t.uses++;
        return true;
      }
      return false;
    case CatState.GoToFeeder:
      if (t.kind === 'feeder') {
        cat.state = CatState.Eating;
        cat.stateTimer = s.rng.int(70, 120);
        t.uses++;
        return true;
      }
      return false;
    case CatState.GoToScratch:
      if (t.kind === 'scratch') {
        cat.state = CatState.Scratching;
        cat.stateTimer = s.rng.int(110, 200);
        t.uses++;
        return true;
      }
      return false;
    case CatState.Rage:
      if (t.kind === 'box') {
        cat.state = CatState.Boxed;
        cat.stateTimer = s.rng.int(260, 460);
        t.uses++;
        pushLog(s, `${cat.name} が段ボール箱に吸い込まれて鎮静化しました`, 'good');
        return true;
      }
      if (cat.rageTargetTile !== null && here === cat.rageTargetTile) {
        raiseHazard(s, 'scratch_machine', cat, cat.rageTargetTile);
        cat.mood = Math.min(100, cat.mood + 14);
        cat.rageTargetTile = null;
        cat.state = CatState.Idle;
        return true;
      }
      return false;
    case CatState.GoToPickup: {
      const isPickupTile =
        t.kind === 'intake' ||
        t.kind === 'basket' ||
        t.kind === 'stocker' ||
        t.kind === 'pallet' ||
        t.kind === 'container' ||
        (machineDef(t.kind) !== null && t.outSlot !== null);
      if (isPickupTile) {
        cat.state = CatState.Picking;
        cat.stateTimer = HANDLER_PICK_TICKS;
        return true;
      }
      return false;
    }
    case CatState.GoToDropoff: {
      const isDropTile =
        t.kind === 'output' ||
        t.kind === 'dock' ||
        machineDef(t.kind) !== null ||
        t.kind === 'basket' ||
        t.kind === 'stocker' ||
        t.kind === 'pallet' ||
        t.kind === 'container';
      if (isDropTile) {
        cat.state = CatState.Dropping;
        cat.stateTimer = HANDLER_DROP_TICKS;
        return true;
      }
      return false;
    }
    case CatState.GoToBench:
      if (cat.stationIndex !== null && here === cat.stationIndex) {
        beginWork(s, cat);
        return true;
      }
      return false;
    default:
      return false;
  }
}

/** 荷役作業員・手すきの猫: 荷物ピックアップ完了 */
function handlePickingComplete(s: GameState, cat: Cat): void {
  const here = catTileIndex(cat);
  const t = s.tiles[here];

  // 搬入口から直接購入・ピックアップ
  if (t.kind === 'intake') {
    const mat = MATERIALS[s.materialGrade] ?? MATERIALS[0];
    if (s.money >= mat.cost) {
      s.money -= mat.cost;
      s.materialCostToday += mat.cost;
      cat.carrying.push(newItem(s));
      t.uses++;
      cat.hp = Math.max(0, cat.hp - 0.3);
    }
  } else if (machineDef(t.kind) && t.outSlot !== null) {
    // 機械の完成スロットから回収
    cat.carrying.push(t.outSlot);
    t.outSlot = null;
    t.uses++;
    cat.hp = Math.max(0, cat.hp - 0.3);
  } else if (t.queue.length > 0) {
    const cap = HANDLER_EQUIPMENT[cat.equipment]?.carryCap ?? HANDLER_CARRY_CAP;
    const count = Math.min(cap, t.queue.length);
    cat.carrying = t.queue.splice(0, count);
    t.uses++;
    cat.hp = Math.max(0, cat.hp - 0.4);
  }

  if (cat.carrying.length > 0 && s.rng.chance(cat.hp < 40 ? 0.003 : 0.0008)) {
    raiseHazard(s, 'heavy_lifting', cat, here);
  }

  decideDropoff(s, cat);
}

/** 荷役作業員: 荷降ろし完了 */
function handleDroppingComplete(s: GameState, cat: Cat): void {
  const here = catTileIndex(cat);
  const t = s.tiles[here];
  const items = cat.carrying;
  cat.carrying = [];
  if (items.length === 0) {
    cat.state = CatState.Idle;
    return;
  }

  if (t.kind === 'output') {
    if (t.truck !== null && t.truck.length < TRUCK_CAP) {
      const room = TRUCK_CAP - t.truck.length;
      t.truck.push(...items.slice(0, room));
      t.queue.push(...items.slice(room));
    } else {
      t.queue.push(...items);
    }
    t.uses++;
    cat.producedToday += items.length;
    s.basketsHandledToday += items.length;
  } else if (t.kind === 'dock') {
    const room = Math.max(0, TRUCK_CAP * 2 - t.queue.length);
    t.queue.push(...items.slice(0, room));
    t.uses++;
    cat.producedToday += items.length;
    s.basketsHandledToday += items.length;
  } else if (machineDef(t.kind)) {
    // 機械の入力キューへ投入（工程間連携）
    const room = Math.max(0, BENCH_QUEUE_CAP - t.queue.length);
    t.queue.push(...items.slice(0, room));
    t.uses++;
    cat.producedToday += items.length;
    s.basketsHandledToday += items.length;
  } else if (t.kind === 'basket' || t.kind === 'stocker' || t.kind === 'pallet' || t.kind === 'container') {
    t.queue.push(...items);
    t.uses++;
    cat.producedToday += items.length;
    s.basketsHandledToday += items.length;
  } else {
    for (const item of items) shipItem(s, item);
  }
  cat.targetDropoff = null;
  cat.hp = Math.max(0, cat.hp - 0.3);
  cat.state = CatState.Idle;
}

function yardCapacity(t: Tile): number {
  if (t.kind === 'basket') return BASKET_TIER * t.tiers;
  if (t.kind === 'stocker') return STOCKER_CAP * t.tiers;
  if (t.kind === 'pallet') return PALLET_TIER * t.tiers;
  if (t.kind === 'container') return CONTAINER_CAP;
  return 999;
}

/** 手作業運搬ライン（s.routes）に従ってタスクを決定 */
function decideRouteLogisticsTask(s: GameState, cat: Cat): boolean {
  if (!s.routes || s.routes.length === 0) return false;
  if (cat.carrying.length > 0) return decideDropoff(s, cat);
  const here = catTileIndex(cat);

  for (const route of s.routes) {
    const fromTile = s.tiles[route.from];
    const toTile = s.tiles[route.to];
    if (!fromTile || !toTile) continue;

    // 終点が受け入れ可能か
    let canAccept = false;
    if (toTile.kind === 'output' || toTile.kind === 'dock') {
      canAccept = true;
    } else if (machineDef(toTile.kind)) {
      canAccept = toTile.queue.length < BENCH_QUEUE_CAP;
    } else if (
      toTile.kind === 'basket' ||
      toTile.kind === 'stocker' ||
      toTile.kind === 'pallet' ||
      toTile.kind === 'container'
    ) {
      canAccept = toTile.queue.length < yardCapacity(toTile);
    }

    if (!canAccept) continue;

    // 始点に品物があるか
    let hasItem = false;
    if (fromTile.kind === 'intake') {
      const mat = MATERIALS[s.materialGrade] ?? MATERIALS[0];
      hasItem = s.money >= mat.cost;
    } else if (machineDef(fromTile.kind)) {
      hasItem = fromTile.outSlot !== null;
    } else if (
      fromTile.kind === 'basket' ||
      fromTile.kind === 'stocker' ||
      fromTile.kind === 'pallet' ||
      fromTile.kind === 'container'
    ) {
      hasItem = fromTile.queue.length > 0;
    }

    if (!hasItem) continue;

    cat.targetDropoff = route.to;
    if (here === route.from) {
      cat.state = CatState.Picking;
      cat.stateTimer = HANDLER_PICK_TICKS;
      return true;
    }
    const r = findPathToIndex(s.tiles, here, route.from);
    if (setPathTo(s, cat, r, false)) {
      cat.state = CatState.GoToPickup;
      return true;
    }
  }

  return false;
}

/** 荷役作業員・手すきの猫: 荷降ろし先の決定 */
function decideDropoff(s: GameState, cat: Cat): boolean {
  if (cat.carrying.length === 0) return false;
  const here = catTileIndex(cat);

  // 1. 手作業運搬ラインで指定された目的地があれば最優先で向かう
  if (cat.targetDropoff !== null) {
    const target = cat.targetDropoff;
    if (here === target) {
      cat.state = CatState.Dropping;
      cat.stateTimer = HANDLER_DROP_TICKS;
      return true;
    }
    const r = findPathToIndex(s.tiles, here, target);
    if (setPathTo(s, cat, r, false)) {
      cat.state = CatState.GoToDropoff;
      return true;
    }
  }

  const head = cat.carrying[0];
  const finished = isFinished(head);

  if (finished) {
    // 1. 出荷口
    const output = findPathToKind(s.tiles, here, 'output');
    if (output.path && setPathTo(s, cat, output, false)) {
      cat.state = CatState.GoToDropoff;
      return true;
    }
    // 2. トラックバース
    const dock = findPathToKind(s.tiles, here, 'dock');
    if (dock.path && setPathTo(s, cat, dock, false)) {
      cat.state = CatState.GoToDropoff;
      return true;
    }
    // 3. 完成品置き場 (前方接続がある投入用カゴを優先)
    for (let i = 0; i < s.tiles.length; i++) {
      if (i === here) continue;
      const t = s.tiles[i];
      if (
        (t.kind === 'pallet' || t.kind === 'container' || t.kind === 'basket' || t.kind === 'stocker') &&
        (t.yardMode === 'done' || t.yardMode === 'all') &&
        t.queue.length < yardCapacity(t)
      ) {
        const r = findPathToIndex(s.tiles, here, i);
        if (setPathTo(s, cat, r, false)) {
          cat.state = CatState.GoToDropoff;
          return true;
        }
      }
    }
  } else {
    // 未完成品・仕掛品:
    // 1. 【最優先】下流のラインへ繋がっている投入用カゴ・ストッカー（前方に接続あり）
    for (let i = 0; i < s.tiles.length; i++) {
      if (i === here) continue;
      const t = s.tiles[i];
      if (
        (t.kind === 'basket' || t.kind === 'stocker') &&
        hasForwardConnection(s.tiles, i) &&
        matchesYardMode(head, t.yardMode) &&
        t.queue.length < yardCapacity(t)
      ) {
        const r = findPathToIndex(s.tiles, here, i);
        if (setPathTo(s, cat, r, false)) {
          cat.state = CatState.GoToDropoff;
          return true;
        }
      }
    }

    // 2. 受け入れ可能な加工機械へ直接投入
    let bestMachine = -1;
    let minQueue = 999;
    for (let i = 0; i < s.tiles.length; i++) {
      const t = s.tiles[i];
      const mdef = machineDef(t.kind);
      if (!mdef) continue;
      if (t.queue.length >= BENCH_QUEUE_CAP) continue;
      if (mdef.packs && head.packed) continue;

      if (t.queue.length < minQueue) {
        minQueue = t.queue.length;
        bestMachine = i;
      }
    }

    if (bestMachine >= 0) {
      const r = findPathToIndex(s.tiles, here, bestMachine);
      if (setPathTo(s, cat, r, false)) {
        cat.state = CatState.GoToDropoff;
        return true;
      }
    }

    // 3. 次工程の機械や投入用カゴが満杯の場合は、仕掛品置き場へ退避
    for (let i = 0; i < s.tiles.length; i++) {
      if (i === here) continue;
      const t = s.tiles[i];
      if (
        (t.kind === 'basket' || t.kind === 'stocker' || t.kind === 'container' || t.kind === 'pallet') &&
        matchesYardMode(head, t.yardMode) &&
        t.queue.length < yardCapacity(t)
      ) {
        const r = findPathToIndex(s.tiles, here, i);
        if (setPathTo(s, cat, r, false)) {
          cat.state = CatState.GoToDropoff;
          return true;
        }
      }
    }
  }

  return false;
}

/** 荷役作業員: タスク決定 */
function decideHandlerTask(s: GameState, cat: Cat): boolean {
  const here = catTileIndex(cat);
  if (cat.carrying.length > 0) {
    return decideDropoff(s, cat);
  }

  // 1. 機械の出口スロットに完成・仕掛品が滞留していれば最優先で回収
  for (let i = 0; i < s.tiles.length; i++) {
    const t = s.tiles[i];
    if (machineDef(t.kind) && t.outSlot !== null) {
      if (here === i) {
        cat.state = CatState.Picking;
        cat.stateTimer = HANDLER_PICK_TICKS;
        return true;
      }
      const r = findPathToIndex(s.tiles, here, i);
      if (setPathTo(s, cat, r, false)) {
        cat.state = CatState.GoToPickup;
        return true;
      }
    }
  }

  // 2. 荷物が溜まっている置き場（カゴ・ストッカー・パレット・コンテナ）を探索
  // 前方に接続がない回収用カゴ（Collector/Buffer）を最優先でピックアップ！
  let bestTarget = -1;
  let maxScore = 0;
  for (let i = 0; i < s.tiles.length; i++) {
    const t = s.tiles[i];
    if (t.kind !== 'basket' && t.kind !== 'stocker' && t.kind !== 'pallet' && t.kind !== 'container') continue;
    if (t.queue.length > 0) {
      const hasFinished = t.queue.some((it) => isFinished(it));
      const isCollector = !hasForwardConnection(s.tiles, i);

      // 回収専用カゴ（前方に接続がない）は優先度を大幅に上げる
      const score =
        t.queue.length +
        (isCollector ? 40 : 0) +
        (hasFinished ? 30 : 10) +
        (t.kind === 'basket' ? 15 : 0);

      if (score > maxScore) {
        maxScore = score;
        bestTarget = i;
      }
    }
  }

  if (bestTarget >= 0) {
    if (here === bestTarget) {
      cat.state = CatState.Picking;
      cat.stateTimer = HANDLER_PICK_TICKS;
      return true;
    }
    const r = findPathToIndex(s.tiles, here, bestTarget);
    if (setPathTo(s, cat, r, false)) {
      cat.state = CatState.GoToPickup;
      return true;
    }
  }

  // 荷物が無ければ荷役ステーションへ戻る
  if (cat.stationIndex !== null && here !== cat.stationIndex) {
    const r = findPathToIndex(s.tiles, here, cat.stationIndex);
    if (setPathTo(s, cat, r, false)) {
      cat.state = CatState.GoToBench;
      return true;
    }
  }

  return false;
}

/** 経路に沿って移動する。 */
function advance(s: GameState, cat: Cat): void {
  if (cat.path.length === 0) return;
  switch (cat.state) {
    case CatState.Sleeping:
    case CatState.Boxed:
    case CatState.Panic:
    case CatState.Fighting:
    case CatState.OnBreak:
    case CatState.Driving:
    case CatState.Picking:
    case CatState.Dropping:
      return;
    default:
      break;
  }

  const goal = cat.path[0];
  const gx = tx(goal);
  const gy = ty(goal);
  const dx = gx - cat.x;
  const dy = gy - cat.y;
  const dist = Math.hypot(dx, dy);
  // 機嫌が悪いとのろのろ歩く。荷役機材を使用中は速度補正を適用。
  let speed = WALK_SPEED * (0.6 + (cat.mood / 100) * 0.6);
  if (cat.carrying.length > 0) {
    speed *= HANDLER_EQUIPMENT[cat.equipment]?.speedMult ?? 1.0;
  }

  // 進行方向を向く (描画用)
  if (Math.abs(dx) > Math.abs(dy)) {
    cat.facing = dx > 0 ? Dir.East : Dir.West;
  } else if (dy !== 0) {
    cat.facing = dy > 0 ? Dir.South : Dir.North;
  }

  if (dist <= speed) {
    cat.x = gx;
    cat.y = gy;
    cat.path.shift();
    if (cat.path.length === 0) arriveIfPossible(s, cat);
  } else {
    cat.x += (dx / dist) * speed;
    cat.y += (dy / dist) * speed;
  }
}

/** 移動中・作業中に起きる受動的なハザード。 */
function passiveHazards(s: GameState, cat: Cat): void {
  const here = catTileIndex(cat);
  const t = s.tiles[here];

  // 荷物を持って移動中の腰痛・転倒ハザード
  if (cat.carrying.length > 0 && s.rng.chance(0.0012)) {
    raiseHazard(s, 'heavy_lifting', cat, here);
  }

  // 稼働中のコンベア上に居ると抜け毛が混入する
  if (t.kind === 'conveyor' && !t.catwalk && t.cargo && s.rng.chance(0.05)) {
    const sev = raiseHazard(s, 'fur_contamination', cat, here);
    if (sev === 'minor' && t.cargo) t.cargo.defective = true;
  }

  // 立入禁止のボイラー室にいる
  if (t.kind === 'boiler' && cat.state !== CatState.Sleeping && s.rng.chance(0.004)) {
    raiseHazard(s, 'boiler_nap', cat, here);
  }

  if (cat.state === CatState.Working) {
    // 作業中でも気まぐれに脱ぎ捨てることがある
    if (cat.helmet && s.rng.chance(0.0006)) complianceCheck(s, cat);
    if (!cat.helmet && s.rng.chance(0.0022)) {
      const sev = raiseHazard(s, 'no_helmet', cat, here);
      // 指摘されればひとまず被り直す。同じ違反が延々と計上されるのを防ぐ。
      if (sev !== null) {
        cat.helmet = true;
        pushLog(s, `${cat.name} にヘルメットを被り直させました`, 'info');
      }
    }
  }
}

/**
 * 作業開始。作業台なら現場作業、事務机なら事務作業に入る。
 * ヘルメットの着脱判定は、持ち場に着いた瞬間にだけ行う。
 */
function beginWork(s: GameState, cat: Cat): void {
  const station = cat.stationIndex !== null ? s.tiles[cat.stationIndex] : null;
  if (station?.kind === 'office') {
    cat.state = CatState.Clerical;
    return;
  }
  if (station?.kind === 'handler_post') {
    decideHandlerTask(s, cat);
    return;
  }
  if (station?.kind === 'forkdepot') {
    // 詰所に着いたらフォークリフトに乗る
    cat.state = CatState.Driving;
    return;
  }
  if (station?.kind === 'dock') {
    cat.state = CatState.Loading;
    return;
  }
  if (station?.kind === 'lab') {
    cat.state = CatState.Research;
    return;
  }
  const wasWorking = cat.state === CatState.Working;
  cat.state = CatState.Working;
  if (!wasWorking) complianceCheck(s, cat);
}

/** 安全手順の遵守判定。記憶力と機嫌が低いほどヘルメットを脱ぎ捨てる。 */
function complianceCheck(s: GameState, cat: Cat): void {
  const drill = s.upgrades['helmet_drill'] ?? 0;
  const recall = Math.min(100, cat.memory + drill * 14);
  const keep = 0.25 + (recall / 100) * 0.55 + (cat.mood / 100) * 0.2;
  const wasWearing = cat.helmet;
  cat.helmet = s.rng.chance(keep);
  if (wasWearing && !cat.helmet) {
    pushLog(s, `${cat.name} がヘルメットを脱ぎ捨てました`, 'warn');
  }
}

/** 日をまたぐ時のリセット。 */
export function startCatDay(s: GameState, cat: Cat): void {
  if (cat.offDutyDays > 0) {
    cat.offDutyDays--;
    if (cat.offDutyDays === 0) {
      cat.state = CatState.Idle;
      cat.hp = 80;
      cat.mood = 60;
      const reason = cat.injury === 'none' ? '休職' : '労災による休業';
      cat.injury = 'none';
      pushLog(s, `${cat.name} が${reason}から復帰しました`, 'good');
    }
    return;
  }
  cat.producedToday = 0;
  cat.hunger = Math.max(0, cat.hunger - 60);
  cat.bladder = 0;
  cat.hp = Math.min(100, cat.hp + 45);
  cat.mood = Math.min(100, cat.mood + 22);
  cat.boredom = 0;
  cat.pathFails = 0;
  cat.carrying = [];
  cat.state = CatState.Idle;
  cat.path = [];
  cat.breakTile = null;
  cat.helmet = true;
  const drill = s.upgrades['helmet_drill'] ?? 0;
  cat.memory = Math.min(100, cat.memory + drill * 1.5);
}
