/**
 * フォークリフトによるパレット輸送。
 *
 * パレット置き場が1段ぶん満載になると、詰所のフォークリフトが取りに行き、
 * トラックバースまで運んで降ろす。車両はコンベアや機械の上を走れないので、
 * プレイヤーは「モノの動線」「猫の動線」に加えて「車両の通路」も確保する必要がある。
 */
import {
  CONTAINER_CAP,
  CONTAINER_HANDLE_TICKS,
  FORKLIFT_SPEED,
  PALLET_HANDLE_TICKS,
  PALLET_TIER,
  TRUCK_CAP,
} from '../core/config';
import { drivableForForklift, idx, inBounds, tx, ty } from '../core/grid';
import { findPath } from '../core/pathfinding';
import { CatState, Dir, type Cat, type Forklift, type Tile } from '../core/types';
import { raiseHazard } from './incidents';
import { catById, pushLog, type GameState } from './state';

/** パレット置き場の積載上限 */
export function palletCap(t: Tile): number {
  return PALLET_TIER * t.tiers;
}

/** 車両用の経路探索。走行できるマスだけを辿る。 */
function drivePath(s: GameState, from: number, isGoal: (i: number) => boolean): number[] | null {
  const r = findPath(
    s.tiles,
    from,
    (t, i) => isGoal(i) && drivableForForklift(t),
  );
  if (!r.path) return null;
  // 経路上に走行できないマスが混じっていたら諦める (猫用の判定を借りているため)
  for (const p of r.path) {
    if (!drivableForForklift(s.tiles[p])) return null;
  }
  return r.path;
}

export function createForklift(s: GameState, depot: number): Forklift {
  return {
    id: s.forkliftSeq++,
    depot,
    driver: null,
    x: tx(depot),
    y: ty(depot),
    facing: Dir.South,
    state: 'parked',
    path: [],
    load: [],
    target: null,
    busy: 0,
  };
}

/** 詰所タイルとフォークリフトの台数を同期させる。 */
export function syncForklifts(s: GameState): void {
  const depots = s.tiles
    .map((t, i) => ({ t, i }))
    .filter(({ t }) => t.kind === 'forkdepot')
    .map(({ i }) => i);

  // 無くなった詰所のフォークリフトを片付ける
  s.forklifts = s.forklifts.filter((f) => depots.includes(f.depot));
  for (const depot of depots) {
    if (!s.forklifts.some((f) => f.depot === depot)) {
      s.forklifts.push(createForklift(s, depot));
    }
  }
}

export function tickForklifts(s: GameState): void {
  for (const f of s.forklifts) {
    const driver = catById(s, s.tiles[f.depot]?.assigned ?? null);
    const ready =
      driver !== null && driver.offDutyDays <= 0 && driver.state === CatState.Driving;

    if (!ready) {
      // 運転手がいなければ詰所で停車したまま
      f.driver = null;
      if (f.state !== 'parked' && f.load.length === 0) {
        f.state = 'parked';
        f.path = [];
        f.target = null;
      }
      continue;
    }

    f.driver = driver.id;
    if (f.busy > 0) {
      f.busy--;
      if (f.busy === 0) finishHandling(s, f);
      syncDriver(f, driver);
      continue;
    }

    if (f.path.length > 0) {
      advance(s, f);
      syncDriver(f, driver);
      checkRunOver(s, f);
      continue;
    }

    decide(s, f);
    syncDriver(f, driver);
  }
}

/** 運転手の猫はフォークリフトと一緒に動く。 */
function syncDriver(f: Forklift, driver: Cat): void {
  driver.x = f.x;
  driver.y = f.y;
  driver.facing = f.facing;
  driver.path = [];
}

function decide(s: GameState, f: Forklift): void {
  const here = idx(Math.round(f.x), Math.round(f.y));

  // 積んでいるならバースへ
  if (f.load.length > 0) {
    if (s.tiles[here].kind === 'dock') {
      f.busy = f.state === 'to_container' ? CONTAINER_HANDLE_TICKS : PALLET_HANDLE_TICKS;
      f.state = 'to_dock';
      return;
    }
    const path = drivePath(s, here, (i) => s.tiles[i].kind === 'dock');
    if (path) {
      f.path = path;
      f.state = 'to_dock';
      return;
    }
    // 降ろす先が無い
    if (s.rng.chance(0.01)) {
      pushLog(s, 'フォークリフトが荷を降ろす先を見つけられません', 'warn');
    }
    return;
  }

  // 1. 満載のコンテナを最優先
  let target = -1;
  let targetKind: 'container' | 'pallet' = 'pallet';
  let best = 0;

  for (let i = 0; i < s.tiles.length; i++) {
    const t = s.tiles[i];
    if (t.kind === 'container' && t.queue.length >= CONTAINER_CAP) {
      target = i;
      targetKind = 'container';
      break;
    }
  }

  // 2. 満載のパレット
  if (target < 0) {
    for (let i = 0; i < s.tiles.length; i++) {
      const t = s.tiles[i];
      if (t.kind === 'pallet' && t.queue.length >= PALLET_TIER) {
        target = i;
        targetKind = 'pallet';
        break;
      }
    }
  }

  // 3. コンテナかパレットで最も溜まっているもの
  if (target < 0) {
    for (let i = 0; i < s.tiles.length; i++) {
      const t = s.tiles[i];
      if ((t.kind === 'container' || t.kind === 'pallet') && t.queue.length > best) {
        best = t.queue.length;
        target = i;
        targetKind = t.kind;
      }
    }
  }

  if (target >= 0) {
    const nextState = targetKind === 'container' ? 'to_container' : 'to_pallet';
    if (here === target) {
      f.busy = targetKind === 'container' ? CONTAINER_HANDLE_TICKS : PALLET_HANDLE_TICKS;
      f.state = nextState;
      f.target = target;
      return;
    }
    const path = drivePath(s, here, (i) => i === target);
    if (path) {
      f.path = path;
      f.state = nextState;
      f.target = target;
      return;
    }
  }

  // 仕事が無ければ詰所へ戻る
  if (here !== f.depot) {
    const path = drivePath(s, here, (i) => i === f.depot);
    if (path) {
      f.path = path;
      f.state = 'returning';
      return;
    }
  }
  f.state = 'parked';
}

/** 荷役の完了処理 */
function finishHandling(s: GameState, f: Forklift): void {
  const here = idx(Math.round(f.x), Math.round(f.y));
  const t = s.tiles[here];

  if (f.load.length === 0 && t.kind === 'pallet') {
    // パレットを1段ぶん積む
    f.load = t.queue.splice(0, PALLET_TIER);
    t.uses++;
    s.palletsHauledToday++;
    return;
  }

  if (f.load.length === 0 && t.kind === 'container') {
    // コンテナから最大CONTAINER_CAP個積む
    f.load = t.queue.splice(0, CONTAINER_CAP);
    t.uses++;
    s.containersHauledToday++;
    return;
  }

  if (f.load.length > 0 && t.kind === 'dock') {
    // バースの積み込み待ちへ降ろす
    const room = Math.max(0, TRUCK_CAP * 2 - t.queue.length);
    const moved = f.load.splice(0, room);
    t.queue.push(...moved);
    t.uses++;
    return;
  }
}

function advance(s: GameState, f: Forklift): void {
  const goal = f.path[0];
  const gx = tx(goal);
  const gy = ty(goal);
  const dx = gx - f.x;
  const dy = gy - f.y;
  const dist = Math.hypot(dx, dy);

  if (Math.abs(dx) > Math.abs(dy)) f.facing = dx > 0 ? Dir.East : Dir.West;
  else if (dy !== 0) f.facing = dy > 0 ? Dir.South : Dir.North;

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

/**
 * 走行中の接触判定。仕様書の「フォークリフトの進路上で寝転がっていた」がここで起きる。
 * 寝ている猫は避けられないので、そのまま人身災害になりやすい。
 */
function checkRunOver(s: GameState, f: Forklift): void {
  for (const cat of s.cats) {
    if (cat.id === f.driver) continue;
    if (cat.offDutyDays > 0 || cat.lives <= 0) continue;
    if (Math.abs(cat.x - f.x) > 0.75 || Math.abs(cat.y - f.y) > 0.75) continue;

    // キャットウォークの上にいる猫は立体交差なので安全
    const tile = s.tiles[idx(Math.round(cat.x), Math.round(cat.y))];
    if (tile.catwalk) continue;

    const asleep =
      cat.state === CatState.Sleeping ||
      cat.state === CatState.Resting ||
      cat.state === CatState.OnBreak;
    if (s.rng.chance(asleep ? 0.35 : 0.05)) {
      raiseHazard(s, 'forklift_run', cat, idx(Math.round(f.x), Math.round(f.y)), {
        forceAccident: asleep,
      });
    }
  }
}

/** 車両が通れるマスが1つも無い、といった詰みを検出する。 */
export function forkliftBlocked(s: GameState, f: Forklift): boolean {
  const here = idx(Math.round(f.x), Math.round(f.y));
  for (let d = 0; d < 4; d++) {
    const nx = tx(here) + (d === 0 ? 1 : d === 2 ? -1 : 0);
    const ny = ty(here) + (d === 1 ? 1 : d === 3 ? -1 : 0);
    if (!inBounds(nx, ny)) continue;
    if (drivableForForklift(s.tiles[idx(nx, ny)])) return false;
  }
  return true;
}
