/**
 * クォータービュー(2:1アイソメトリック)のドット絵描画。
 * 論理キャンバスは 1論理px = 1ドット で持ち、CSS側で整数倍に拡大する。
 * 工場は無機質な金属色、猫だけ柔らかい色でコミカルに動かす。
 */
import { GRID_H, GRID_W, MERGER_CAP, STOCKER_CAP, machineDef } from '../core/config';
import { idx, tx, ty } from '../core/grid';
import { CatState, DIR_VEC, Dir, Phase, type Cat, type Tile } from '../core/types';
import type { GameState } from '../sim/state';
import {
  TILE_H,
  TILE_W,
  VIEW_H,
  VIEW_W,
  blit,
  diamond,
  isoCenterX,
  isoCenterY,
  shadow,
  unproject,
} from './iso';
import {
  ANCHOR_X,
  ANCHOR_Y,
  catwalkSprite,
  itemSprite,
  lampRow,
  shade,
  tileSpriteFor,
} from './sprites';
import { analyzeLineFlow, hasForwardConnection } from '../sim/factory';

export interface ViewState {
  /** マウスが乗っているタイル (無ければ -1) */
  hover: number;
  /** 選択中の建設種別ラベル (ゴースト表示用) */
  ghost: string | null;
  /** 選択中の猫ID (割り当てハイライト用) */
  selectedCat: number | null;
  /** 経路デバッグ表示 */
  showPaths: boolean;
  /** ライン確認モード */
  showLineFlow: boolean;
  /** ライン確認モード中のドラッグ配線始点 */
  dragStart: number | null;
  /** ライン確認モード中のドラッグ操作種別 ('add' = 左ドラッグ設定, 'remove' = 右ドラッグ削除) */
  dragMode?: 'add' | 'remove';
  /** ライン確認モード中のドラッグ配線プレビュー経路 */
  dragPath: number[];
}

/** タイルの上面までの高さ。猫や荷物を載せる基準になる。 */
const SURFACE_LIFT: Partial<Record<Tile['kind'], number>> = {
  conveyor: 4,
  bench: 13,
  splitter: 7,
  merger: 7,
  stocker: 6,
  basket: 6,
  container: 14,
  handler_post: 3,
  boiler: 10,
  office: 11,
  pallet: 4,
  dock: 6,
  lab: 11,
  pole: 12,
  generator: 12,
  solar: 11,
  lathe: 11,
  press: 8,
  robot: 6,
  packer: 9,
};

/** キャットウォークの床の高さ */
const CATWALK_LIFT = 13;

export function resizeCanvas(canvas: HTMLCanvasElement): void {
  canvas.width = VIEW_W;
  canvas.height = VIEW_H;

  const stage = canvas.parentElement;
  const availW = (stage?.clientWidth ?? VIEW_W) - 12;
  const availH = (stage?.clientHeight ?? VIEW_H) - 12;
  const fit = Math.min(availW / VIEW_W, availH / VIEW_H);
  // ドットを保つため拡大は整数倍。入りきらない時だけ縮小する。
  const scale = fit >= 2 ? 2 : fit >= 1 ? 1 : fit;

  canvas.style.width = `${Math.round(VIEW_W * scale)}px`;
  canvas.style.height = `${Math.round(VIEW_H * scale)}px`;

  const ctx = canvas.getContext('2d');
  if (ctx) {
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.imageSmoothingEnabled = false;
  }
}

/** タイルの「載せ面」の高さ */
function liftOf(t: Tile): number {
  if (t.catwalk) return CATWALK_LIFT;
  return SURFACE_LIFT[t.kind] ?? 0;
}

export function render(ctx: CanvasRenderingContext2D, s: GameState, view: ViewState): void {
  ctx.imageSmoothingEnabled = false;
  ctx.fillStyle = '#15171d';
  ctx.fillRect(0, 0, VIEW_W, VIEW_H);

  const frame = s.phase === Phase.Do ? Math.floor(performance.now() / 90) % 8 : 0;

  // 奥から手前へ。行優先で描けばアイソメトリックの前後関係は自然に解決する。
  for (let y = 0; y < GRID_H; y++) {
    for (let x = 0; x < GRID_W; x++) {
      const t = s.tiles[idx(x, y)];
      drawTile(ctx, t, x, y, frame, s.paperwork.length, s.powerCoverage[idx(x, y)] !== 0);
      if (t.catwalk) {
        blit(ctx, catwalkSprite(), isoCenterX(x, y), isoCenterY(x, y), ANCHOR_X, ANCHOR_Y);
      }
      drawTileContents(ctx, t, x, y);
      drawCatsAt(ctx, s, x, y, view);
    }
  }

  drawForklifts(ctx, s);
  if (view.showPaths) drawPaths(ctx, s);
  if (view.showLineFlow) drawLineInspector(ctx, s, view);
  drawSelection(ctx, s, view);
  drawHover(ctx, s, view);
}

function drawTile(
  ctx: CanvasRenderingContext2D,
  t: Tile,
  x: number,
  y: number,
  frame: number,
  paperwork: number,
  powered: boolean,
): void {
  const img = tileSpriteFor(t, x, y, frame, paperwork);
  const cx = isoCenterX(x, y);
  const cy = isoCenterY(x, y);
  if (img) blit(ctx, img, cx, cy, ANCHOR_X, ANCHOR_Y);

  const mdef = machineDef(t.kind);
  if (mdef) {
    const lift = SURFACE_LIFT[t.kind] ?? 0;
    if (!powered) {
      // 配電盤の範囲外。電気が来ていないので動かない。
      drawGlyph(ctx, cx, cy - lift - 17, 'bolt', '#7fd7ff');
    } else if (t.brokenFor > 0) {
      drawGlyph(ctx, cx, cy - lift - 16, 'spark', '#ffcf5a');
    } else if (mdef.needsOperator && t.assigned === null) {
      // 担当者不在の機械。ここで荷が止まる。
      drawGlyph(ctx, cx, cy - lift - 17, 'bang', '#e05a5a');
    }
    lampRow(ctx, cx, cy - lift - 4, t.queue.length, 5, '#e8d9a0');
    if (t.progress > 0) {
      ctx.fillStyle = '#00000066';
      ctx.fillRect(Math.round(cx - 9), Math.round(cy - lift - 9), 18, 2);
      ctx.fillStyle = '#5fd39a';
      ctx.fillRect(Math.round(cx - 9), Math.round(cy - lift - 9), Math.round(18 * t.progress), 2);
    }
  }

  if (t.kind === 'office') {
    const lift = SURFACE_LIFT.office!;
    // 書類が溜まっているのに担当がいない机だけを警告する
    if (t.assigned === null && paperwork > 0) {
      drawGlyph(ctx, cx, cy - lift - 15, 'bang', '#e8c05a');
    }
    if (t.progress > 0) {
      ctx.fillStyle = '#00000066';
      ctx.fillRect(Math.round(cx - 9), Math.round(cy - lift - 7), 18, 2);
      ctx.fillStyle = '#7fd7ff';
      ctx.fillRect(Math.round(cx - 9), Math.round(cy - lift - 7), Math.round(18 * t.progress), 2);
    }
  }

  if (t.kind === 'pallet') {
    lampRow(ctx, cx, cy - 10, t.queue.length, 4 * t.tiers, '#d8c89a');
  }

  if (t.kind === 'dock') {
    const lift = SURFACE_LIFT.dock!;
    if (t.assigned === null) drawGlyph(ctx, cx, cy - lift - 18, 'bang', '#e05a5a');
    lampRow(ctx, cx, cy - lift - 2, Math.min(t.queue.length, 12), 12, '#e8d9a0');
    if (t.progress > 0) {
      ctx.fillStyle = '#00000066';
      ctx.fillRect(Math.round(cx - 9), Math.round(cy - lift - 6), 18, 2);
      ctx.fillStyle = '#5fd39a';
      ctx.fillRect(Math.round(cx - 9), Math.round(cy - lift - 6), Math.round(18 * t.progress), 2);
    }
  }

  if (t.kind === 'output') {
    if (t.truck !== null) {
      lampRow(ctx, cx, cy - 23, t.truck.length, 12, '#5fd39a');
    } else {
      ctx.fillStyle = '#000000aa';
      ctx.fillRect(cx - 12, cy - 25, 24, 6);
      ctx.fillStyle = '#fbbf24';
      ctx.font = 'bold 5px sans-serif';
      ctx.fillText('次便待ち', cx - 9, cy - 20);
    }
  }

  if (t.kind === 'forkdepot' && t.assigned === null) {
    drawGlyph(ctx, cx, cy - 14, 'bang', '#e8c05a');
  }

  if (t.kind === 'merger') {
    lampRow(ctx, cx, cy - 9, t.queue.length, MERGER_CAP, '#d8c89a');
    drawDirArrow(ctx, cx, cy - 8, t.dir, '#9fe6ff');
  }

  if (t.kind === 'splitter') {
    const dirs = [t.dir, (t.dir + 3) % 4, (t.dir + 1) % 4];
    dirs.forEach((d, k) => drawDirArrow(ctx, cx, cy - 8, d, k === t.rr ? '#9fe6ff' : '#5f7684'));
  }

  // 置き場・容器の受入モードインジケータ
  if (
    (t.kind === 'stocker' || t.kind === 'pallet' || t.kind === 'basket' || t.kind === 'container') &&
    t.yardMode &&
    t.yardMode !== 'all'
  ) {
    const badgeColor = t.yardMode === 'raw' ? '#60a5fa' : t.yardMode === 'wip' ? '#facc15' : '#4ade80';
    const badgeText = t.yardMode === 'raw' ? '材' : t.yardMode === 'wip' ? '仕' : '完';
    ctx.fillStyle = '#000000aa';
    ctx.fillRect(cx + 6, cy - 16, 12, 10);
    ctx.fillStyle = badgeColor;
    ctx.fillRect(cx + 7, cy - 15, 10, 8);
    ctx.fillStyle = '#000';
    ctx.font = 'bold 7px sans-serif';
    ctx.fillText(badgeText, cx + 8, cy - 9);
  }
}

/** タイルの上に載っているもの (搬送物・在庫) */
function drawTileContents(
  ctx: CanvasRenderingContext2D,
  t: Tile,
  x: number,
  y: number,
): void {
  const lift = liftOf(t);

  if (t.kind === 'conveyor' && t.cargo) {
    const [dx, dy] = DIR_VEC[t.dir];
    const gx = x + dx * t.cargo.slide;
    const gy = y + dy * t.cargo.slide;
    blit(ctx, itemSprite(t.cargo.stage, t.cargo.defective),
      isoCenterX(gx, gy), isoCenterY(gx, gy) - lift, 8, 13);
  }

  if (t.kind === 'splitter' && t.cargo) {
    blit(ctx, itemSprite(t.cargo.stage, t.cargo.defective),
      isoCenterX(x, y), isoCenterY(x, y) - lift, 8, 13);
  }

  if (t.kind === 'stocker') {
    // 在庫を斜めに積む
    const shown = Math.min(t.queue.length, 4);
    for (let k = 0; k < shown; k++) {
      const item = t.queue[k];
      blit(ctx, itemSprite(item.stage, item.defective),
        isoCenterX(x, y) + (k - 1.5) * 3, isoCenterY(x, y) - lift - k * 3, 8, 13);
    }
    if (t.queue.length > 0) {
      lampRow(ctx, isoCenterX(x, y), isoCenterY(x, y) - 8, t.queue.length, STOCKER_CAP, '#d8c89a');
    }
  }

  if (machineDef(t.kind) && t.outSlot) {
    blit(ctx, itemSprite(t.outSlot.stage, t.outSlot.defective),
      isoCenterX(x, y), isoCenterY(x, y) - lift - 2, 8, 13);
  }
}

// ------------------------------------------------------------------ 猫

function drawCatsAt(
  ctx: CanvasRenderingContext2D,
  s: GameState,
  x: number,
  y: number,
  view: ViewState,
): void {
  for (const cat of s.cats) {
    if (cat.offDutyDays > 0 || cat.lives <= 0) continue;
    if (Math.round(cat.x) !== x || Math.round(cat.y) !== y) continue;
    drawCat(ctx, s, cat, view);
  }
}

/** ドット絵の猫。工場の直線的な造形に対して、丸くて柔らかい輪郭にする。 */
function drawCat(ctx: CanvasRenderingContext2D, s: GameState, cat: Cat, view: ViewState): void {
  const tile = s.tiles[idx(Math.round(cat.x), Math.round(cat.y))];
  const cx = Math.round(isoCenterX(cat.x, cat.y));
  const baseY = Math.round(isoCenterY(cat.x, cat.y) - liftOf(tile));

  const moving = cat.path.length > 0;
  const bob = moving ? Math.round(Math.abs(Math.sin(cat.wobble)) * 2) : 0;
  const cy = baseY - bob;

  shadow(ctx, cx, baseY, 14);

  const back = cat.facing === Dir.West || cat.facing === Dir.North;
  const flip = cat.facing === Dir.South || cat.facing === Dir.West ? -1 : 1;

  const body = cat.color;
  const dark = shade(body, -0.35);
  const light = shade(body, 0.18);

  const px = (ox: number, oy: number, w: number, h: number, color: string): void => {
    ctx.fillStyle = color;
    ctx.fillRect(cx + ox, cy + oy, w, h);
  };

  if (cat.state === CatState.Driving) {
    // フォークリフトの上に座っている
    ctx.fillStyle = cat.color;
    ctx.fillRect(cx - 4, cy - 20, 8, 6);
    ctx.fillRect(cx - 4, cy - 23, 2, 3);
    ctx.fillRect(cx + 2, cy - 23, 2, 3);
    ctx.fillStyle = '#f2c14e';
    ctx.fillRect(cx - 5, cy - 24, 10, 2);
    drawCatBadge(ctx, cat, cx, cy - 28, view);
    return;
  }

  if (cat.state === CatState.Boxed) {
    drawBoxedCat(ctx, cx, cy, body);
    drawCatBadge(ctx, cat, cx, cy - 24, view);
    return;
  }

  if (
    cat.state === CatState.Sleeping ||
    cat.state === CatState.Resting ||
    cat.state === CatState.OnBreak
  ) {
    // 丸くなって寝ている
    px(-8, -7, 16, 7, body);
    px(-7, -9, 14, 2, light);
    px(-8, -2, 16, 2, dark);
    px(2 * flip - 4, -12, 8, 6, body);
    px(2 * flip - 4, -14, 2, 3, body);
    px(2 * flip + 1, -14, 2, 3, body);
    px(2 * flip - 3, -10, 2, 1, '#1a1a1a');
    px(2 * flip + 1, -10, 2, 1, '#1a1a1a');
    px(-9 * flip, -5, 4, 2, dark);
    drawGlyph(ctx, cx + 9, cy - 18, 'zzz', '#9fd8ff');
    drawCatBadge(ctx, cat, cx, cy - 22, view);
    return;
  }

  // --- 立ち姿 ---
  const step = moving ? Math.round(Math.sin(cat.wobble)) : 0;

  // しっぽ
  const tailX = Math.round(-9 * flip);
  px(tailX, -12, 3, 2, dark);
  px(tailX - flip, -16, 3, 5, dark);
  px(tailX - flip, -19, 3, 3, body);

  // 脚
  px(-5 + step, -4, 3, 4, dark);
  px(2 - step, -4, 3, 4, dark);

  // 胴
  px(-7, -13, 14, 10, body);
  px(-7, -13, 14, 2, light);
  px(-7, -5, 14, 2, dark);

  // 頭
  const hx = Math.round(2 * flip);
  px(hx - 6, -21, 12, 9, body);
  px(hx - 6, -21, 12, 2, light);
  px(hx - 6, -24, 3, 4, body);
  px(hx + 3, -24, 3, 4, body);
  px(hx - 5, -23, 1, 2, '#d59a9a');
  px(hx + 4, -23, 1, 2, '#d59a9a');

  if (!back) {
    const angry = cat.mood < 30;
    px(hx - 4, -18, 2, 2, angry ? '#ff8080' : '#f7f3e8');
    px(hx + 2, -18, 2, 2, angry ? '#ff8080' : '#f7f3e8');
    px(hx - 4, -18, 1, 2, '#1a1a1a');
    px(hx + 3, -18, 1, 2, '#1a1a1a');
    px(hx - 1, -15, 2, 1, '#d59a9a');
    ctx.fillStyle = '#00000055';
    ctx.fillRect(cx + hx - 9, cy - 15, 3, 1);
    ctx.fillRect(cx + hx + 6, cy - 15, 3, 1);
  }

  // ヘルメット
  if (cat.helmet) {
    px(hx - 7, -24, 14, 3, '#f2c14e');
    px(hx - 6, -26, 12, 2, '#f2c14e');
    px(hx - 6, -26, 12, 1, '#ffe08a');
  }

  // 抱えている荷物
  if (cat.carrying && cat.carrying.length > 0) {
    const carryX = Math.round(5 * flip);
    const boxCount = Math.min(cat.carrying.length, 3);
    for (let b = 0; b < boxCount; b++) {
      px(carryX - 4, -14 - b * 5, 8, 4, '#c8a870');
      px(carryX - 4, -14 - b * 5, 8, 1, '#e2c696');
      px(carryX - 4, -11 - b * 5, 8, 1, '#8c7248');
    }
  }

  drawCatBadge(ctx, cat, cx, cy - (cat.helmet ? 30 : 27), view);
}

function drawBoxedCat(ctx: CanvasRenderingContext2D, cx: number, cy: number, body: string): void {
  // 箱からはみ出た耳だけが見える
  ctx.fillStyle = body;
  ctx.fillRect(cx - 5, cy - 17, 3, 4);
  ctx.fillRect(cx + 2, cy - 17, 3, 4);
  ctx.fillStyle = '#a8834f';
  ctx.fillRect(cx - 10, cy - 14, 20, 12);
  ctx.fillStyle = '#8a6a42';
  ctx.fillRect(cx - 10, cy - 14, 20, 2);
  ctx.fillRect(cx - 10, cy - 4, 20, 2);
  ctx.fillStyle = '#c39a5e';
  ctx.fillRect(cx - 11, cy - 16, 22, 2);
}

/** 猫の頭上に出す状態アイコンと機嫌ゲージ */
function drawCatBadge(
  ctx: CanvasRenderingContext2D,
  cat: Cat,
  cx: number,
  cy: number,
  view: ViewState,
): void {
  const glyph = STATE_GLYPH[cat.state];
  if (glyph) drawGlyph(ctx, cx, cy, glyph.g, glyph.c);

  const w = 14;
  const barY = cy - 6;
  ctx.fillStyle = '#00000099';
  ctx.fillRect(cx - w / 2 - 1, barY - 1, w + 2, 4);
  ctx.fillStyle = cat.mood > 55 ? '#5fd39a' : cat.mood > 28 ? '#e8c05a' : '#e05a5a';
  ctx.fillRect(cx - w / 2, barY, Math.round((w * cat.mood) / 100), 2);

  if (view.selectedCat === cat.id) {
    ctx.strokeStyle = '#7fd7ff';
    ctx.lineWidth = 1;
    ctx.strokeRect(cx - w / 2 - 2, barY - 2, w + 4, 6);
  }
}

type GlyphName =
  | 'zzz' | 'bang' | 'spark' | 'anger' | 'claw' | 'wrench' | 'heart' | 'fish' | 'paper' | 'cup' | 'box' | 'bolt';

const STATE_GLYPH: Partial<Record<CatState, { g: GlyphName; c: string }>> = {
  [CatState.Working]: { g: 'wrench', c: '#9fd8ff' },
  [CatState.Panic]: { g: 'bang', c: '#ff6b6b' },
  [CatState.Rage]: { g: 'anger', c: '#ff6b6b' },
  [CatState.Fighting]: { g: 'spark', c: '#ffcf5a' },
  [CatState.Scratching]: { g: 'claw', c: '#e8c05a' },
  [CatState.Eating]: { g: 'fish', c: '#ffb26b' },
  [CatState.UsingLitter]: { g: 'heart', c: '#8bd8a8' },
  [CatState.Clerical]: { g: 'paper', c: '#e8e2d2' },
  [CatState.Loading]: { g: 'box', c: '#c8b48a' },
  [CatState.GoToPickup]: { g: 'box', c: '#e8c05a' },
  [CatState.Picking]: { g: 'box', c: '#ffcf5a' },
  [CatState.GoToDropoff]: { g: 'box', c: '#5fd39a' },
  [CatState.Dropping]: { g: 'box', c: '#5fd39a' },
  [CatState.Research]: { g: 'paper', c: '#9fd8ff' },
  [CatState.OnBreak]: { g: 'cup', c: '#c9a227' },
};

/** 小さなドット絵グリフ。emoji はドットに馴染まないので自前で描く。 */
function drawGlyph(
  ctx: CanvasRenderingContext2D,
  cx: number,
  cy: number,
  name: GlyphName,
  color: string,
): void {
  ctx.fillStyle = color;
  const p = (x: number, y: number, w = 1, h = 1): void => {
    ctx.fillRect(cx + x, cy + y, w, h);
  };
  switch (name) {
    case 'zzz':
      p(-4, -6, 4, 1); p(-2, -5, 2, 1); p(-4, -4, 4, 1);
      p(1, -3, 3, 1); p(2, -2, 2, 1); p(1, -1, 3, 1);
      break;
    case 'bang':
      p(-1, -8, 2, 5); p(-1, -2, 2, 2);
      break;
    case 'spark':
      p(-1, -8, 2, 2); p(-4, -6, 8, 2); p(-1, -4, 2, 2);
      p(-6, -7, 2, 1); p(4, -7, 2, 1);
      break;
    case 'anger':
      p(-4, -7, 8, 1); p(-4, -4, 8, 1);
      p(-2, -8, 1, 5); p(1, -8, 1, 5);
      break;
    case 'claw':
      p(-4, -7, 1, 5); p(-1, -8, 1, 6); p(2, -7, 1, 5);
      break;
    case 'wrench':
      p(-1, -7, 2, 6); p(-3, -8, 2, 2); p(1, -8, 2, 2);
      break;
    case 'heart':
      p(-3, -7, 2, 2); p(1, -7, 2, 2); p(-3, -5, 6, 2); p(-2, -3, 4, 1); p(-1, -2, 2, 1);
      break;
    case 'fish':
      p(-4, -6, 6, 3); p(2, -7, 2, 5); p(-3, -5, 1, 1);
      break;
    case 'paper':
      p(-3, -8, 6, 7); p(-2, -7, 4, 1); p(-2, -5, 4, 1); p(-2, -3, 3, 1);
      break;
    case 'cup':
      p(-3, -8, 6, 5); p(3, -7, 2, 2); p(-2, -3, 4, 1);
      p(-2, -10, 1, 2); p(1, -11, 1, 2);
      break;
    case 'box':
      p(-4, -8, 8, 7); p(-4, -8, 8, 1); p(-1, -8, 2, 7);
      break;
    case 'bolt':
      p(0, -9, 3, 3); p(-2, -6, 4, 3); p(-4, -3, 4, 3); p(-1, -5, 3, 2);
      break;
  }
}

/** フォークリフト。運転手の猫は上に座って描かれる。 */
function drawForklifts(ctx: CanvasRenderingContext2D, s: GameState): void {
  for (const f of s.forklifts) {
    const cx = Math.round(isoCenterX(f.x, f.y));
    const cy = Math.round(isoCenterY(f.x, f.y));
    shadow(ctx, cx, cy, 20);

    const [dx, dy] = DIR_VEC[f.facing];
    const fx = (dx - dy) * 8;
    const fy = (dx + dy) * 4;

    // フォーク (爪)
    ctx.fillStyle = '#9aa0a8';
    ctx.fillRect(Math.round(cx + fx - 5), Math.round(cy + fy - 2), 10, 2);

    // 積んでいるパレット
    if (f.load.length > 0) {
      ctx.fillStyle = '#9a7a4a';
      ctx.fillRect(Math.round(cx + fx - 7), Math.round(cy + fy - 7), 14, 4);
      ctx.fillStyle = '#c8b48a';
      for (let k = 0; k < Math.min(f.load.length, 4); k++) {
        ctx.fillRect(Math.round(cx + fx - 6 + k * 3), Math.round(cy + fy - 11), 3, 4);
      }
    }

    // 車体
    ctx.fillStyle = '#6b4f12';
    ctx.fillRect(cx - 7, cy - 8, 14, 8);
    ctx.fillStyle = '#e0a92e';
    ctx.fillRect(cx - 7, cy - 12, 14, 5);
    ctx.fillStyle = '#4a3a10';
    ctx.fillRect(cx - 7, cy - 2, 14, 2);
    // マスト
    ctx.fillStyle = '#8a99a8';
    ctx.fillRect(Math.round(cx + fx * 0.55) - 1, cy - 22, 2, 14);
    // 回転灯
    ctx.fillStyle = Math.floor(performance.now() / 220) % 2 ? '#ffdf6b' : '#7a5a1a';
    ctx.fillRect(cx - 1, cy - 15, 2, 2);

    if (f.driver === null) continue;
    // 運転席の猫 (簡易表示。本体は drawCat が描く)
    ctx.fillStyle = '#00000055';
    ctx.fillRect(cx - 4, cy - 16, 8, 3);
  }
}

// ------------------------------------------------------------------ オーバーレイ

/** 分岐機・合流機の小さな方向マーカー */
function drawDirArrow(
  ctx: CanvasRenderingContext2D,
  cx: number,
  cy: number,
  dir: number,
  color: string,
): void {
  const [dx, dy] = DIR_VEC[dir];
  const sx = (dx - dy) * 7;
  const sy = (dx + dy) * 3.5;
  ctx.fillStyle = color;
  ctx.fillRect(Math.round(cx + sx - 1), Math.round(cy + sy - 1), 3, 3);
  ctx.fillRect(Math.round(cx + sx * 0.55 - 1), Math.round(cy + sy * 0.55 - 1), 2, 2);
}

function drawPaths(ctx: CanvasRenderingContext2D, s: GameState): void {
  ctx.save();
  ctx.globalAlpha = 0.6;
  ctx.lineWidth = 1;
  ctx.setLineDash([3, 3]);
  for (const cat of s.cats) {
    if (cat.path.length === 0) continue;
    ctx.strokeStyle = cat.color;
    ctx.beginPath();
    ctx.moveTo(isoCenterX(cat.x, cat.y), isoCenterY(cat.x, cat.y));
    for (const p of cat.path) {
      ctx.lineTo(isoCenterX(tx(p), ty(p)), isoCenterY(tx(p), ty(p)));
    }
    ctx.stroke();
  }
  ctx.restore();
}

function drawSelection(ctx: CanvasRenderingContext2D, s: GameState, view: ViewState): void {
  if (view.selectedCat === null) return;
  const cat = s.cats.find((c) => c.id === view.selectedCat);
  if (!cat) return;

  ctx.save();
  ctx.strokeStyle = '#7fd7ff';
  ctx.lineWidth = 1;
  ctx.setLineDash([3, 2]);
  diamond(ctx, isoCenterX(cat.x, cat.y), isoCenterY(cat.x, cat.y), TILE_W - 6, TILE_H - 3);
  ctx.stroke();

  if (cat.stationIndex !== null) {
    const bx = tx(cat.stationIndex);
    const by = ty(cat.stationIndex);
    ctx.beginPath();
    ctx.moveTo(isoCenterX(cat.x, cat.y), isoCenterY(cat.x, cat.y));
    ctx.lineTo(isoCenterX(bx, by), isoCenterY(bx, by));
    ctx.stroke();
  }
  ctx.restore();
}

function drawHover(ctx: CanvasRenderingContext2D, s: GameState, view: ViewState): void {
  if (view.hover < 0) return;
  const x = tx(view.hover);
  const y = ty(view.hover);
  const cx = isoCenterX(x, y);
  const cy = isoCenterY(x, y);

  ctx.save();
  const active = s.phase === Phase.Plan;
  ctx.strokeStyle = active ? '#7fd7ff' : '#ffffff66';
  ctx.lineWidth = 1;
  diamond(ctx, cx, cy, TILE_W - 2, TILE_H - 1);
  ctx.stroke();
  if (active && view.ghost) {
    ctx.fillStyle = '#7fd7ff22';
    ctx.fill();
  }
  ctx.restore();
}

/** キャンバス上の位置からタイルindexを求める。 */
export function pickTile(canvas: HTMLCanvasElement, ev: MouseEvent): number {
  const rect = canvas.getBoundingClientRect();
  // CSSで拡縮しているので論理座標へ戻す
  const sx = ((ev.clientX - rect.left) / rect.width) * VIEW_W;
  const sy = ((ev.clientY - rect.top) / rect.height) * VIEW_H;
  const g = unproject(sx, sy);
  const x = Math.floor(g.x + 0.5);
  const y = Math.floor(g.y + 0.5);
  if (x < 0 || y < 0 || x >= GRID_W || y >= GRID_H) return -1;
  return idx(x, y);
}

/** ライン確認モード: 工場内のマテリアルフロー、接続状況、機械の入出力、荷役連携を可視化 */
function drawLineInspector(ctx: CanvasRenderingContext2D, s: GameState, view: ViewState): void {
  const analysis = analyzeLineFlow(s);
  const now = performance.now();
  const animPhase = (now / 400) % 1; // 0..1

  ctx.save();

  // 背景を軽く暗転させてラインを浮き上がらせる
  ctx.fillStyle = 'rgba(10, 15, 25, 0.4)';
  ctx.fillRect(0, 0, VIEW_W, VIEW_H);

  // 1. 各タイルのライン接続とフロー描画
  for (let y = 0; y < GRID_H; y++) {
    for (let x = 0; x < GRID_W; x++) {
      const i = idx(x, y);
      const t = s.tiles[i];
      const cx = isoCenterX(x, y);
      const cy = isoCenterY(x, y);
      const isConnected = analysis.connectedTiles.has(i);
      const isDeadEnd = analysis.deadEndTiles.has(i);

      if (
        t.kind === 'conveyor' ||
        t.kind === 'merger' ||
        t.kind === 'splitter' ||
        t.kind === 'stocker' ||
        t.kind === 'basket'
      ) {
        const [dx, dy] = DIR_VEC[t.dir];
        const lineColor = isConnected ? '#38bdf8' : isDeadEnd ? '#ef4444' : '#94a3b8';

        // タイル外枠のハイライト
        ctx.strokeStyle = isConnected
          ? 'rgba(56, 189, 248, 0.4)'
          : isDeadEnd
            ? 'rgba(239, 68, 68, 0.4)'
            : 'rgba(148, 163, 184, 0.2)';
        ctx.lineWidth = 1;
        diamond(ctx, cx, cy, TILE_W - 2, TILE_H - 1);
        ctx.stroke();

        // 搬送矢印
        drawDirArrow(ctx, cx, cy - 8, t.dir, lineColor);

        // 流れる光のパーティクル
        if (isConnected) {
          const px = cx + (dx - dy) * (TILE_W / 4) * animPhase;
          const py = cy - 8 + (dx + dy) * (TILE_H / 4) * animPhase;
          ctx.fillStyle = '#ffffff';
          ctx.fillRect(Math.round(px - 1), Math.round(py - 1), 2, 2);
        }

        if (isDeadEnd) {
          // 行き止まり警告
          ctx.fillStyle = '#ef4444';
          ctx.fillRect(cx - 3, cy - 14, 6, 6);
          ctx.fillStyle = '#fff';
          ctx.font = 'bold 5px sans-serif';
          ctx.fillText('!', cx - 1, cy - 9);
        }

        // カゴ・ストッカーの役割バッジ (供給用 / 回収用)
        if (t.kind === 'basket' || t.kind === 'stocker') {
          const isFeeder = hasForwardConnection(s.tiles, i);
          ctx.fillStyle = '#000000cc';
          ctx.fillRect(cx - 16, cy - 20, 32, 8);
          ctx.fillStyle = isFeeder ? '#38bdf8' : '#fbbf24';
          ctx.font = 'bold 6px sans-serif';
          ctx.fillText(isFeeder ? '供給 ➜' : '回収 ⤹', cx - 12, cy - 14);
        }
      }

      // 機械のIN/OUT/PWR状態
      if (machineDef(t.kind)) {
        const lift = SURFACE_LIFT[t.kind] ?? 0;
        ctx.strokeStyle = isConnected ? '#34d399' : '#f59e0b';
        ctx.lineWidth = 1.5;
        diamond(ctx, cx, cy - lift, TILE_W - 4, TILE_H - 2);
        ctx.stroke();

        // IN/OUT バッジ
        const badgeW = 28;
        const badgeH = 9;
        const badgeX = cx - badgeW / 2;
        const badgeY = cy - lift - 18;

        ctx.fillStyle = '#000000cc';
        ctx.fillRect(badgeX, badgeY, badgeW, badgeH);

        // IN状態
        ctx.fillStyle = isConnected ? '#34d399' : '#f87171';
        ctx.font = 'bold 6px monospace';
        ctx.fillText(`IN${isConnected ? '✔' : '✖'}`, badgeX + 2, badgeY + 7);

        // OUT状態
        const outOk = !isDeadEnd || analysis.hasHandler;
        ctx.fillStyle = outOk ? '#38bdf8' : '#f87171';
        ctx.fillText(`OUT${outOk ? '✔' : '✖'}`, badgeX + 14, badgeY + 7);
      }

      // 搬入口
      if (t.kind === 'intake') {
        ctx.strokeStyle = '#4ade80';
        ctx.lineWidth = 2;
        diamond(ctx, cx, cy, TILE_W - 2, TILE_H - 1);
        ctx.stroke();
        ctx.fillStyle = '#4ade80';
        ctx.font = 'bold 7px sans-serif';
        ctx.fillText('IN', cx - 4, cy - 10);
      }

      // 出荷口・バース
      if (t.kind === 'output' || t.kind === 'dock') {
        ctx.strokeStyle = isConnected ? '#4ade80' : '#94a3b8';
        ctx.lineWidth = 2;
        diamond(ctx, cx, cy, TILE_W - 2, TILE_H - 1);
        ctx.stroke();
        ctx.fillStyle = isConnected ? '#4ade80' : '#94a3b8';
        ctx.font = 'bold 7px sans-serif';
        ctx.fillText('OUT', cx - 6, cy - 10);
      }
    }
  }

  // 2. 荷役ネットワークの連携アーク描画
  if (analysis.hasHandler) {
    ctx.setLineDash([2, 2]);
    ctx.strokeStyle = 'rgba(251, 191, 36, 0.7)';
    ctx.lineWidth = 1;

    for (const p of analysis.handlerPickups) {
      const px = isoCenterX(tx(p), ty(p));
      const py = isoCenterY(tx(p), ty(p)) - 8;

      for (const d of analysis.handlerDropoffs) {
        if (p === d) continue;
        const dx = isoCenterX(tx(d), ty(d));
        const dy = isoCenterY(tx(d), ty(d)) - 8;

        const dist = Math.hypot(tx(p) - tx(d), ty(p) - ty(d));
        if (dist <= 6) {
          ctx.beginPath();
          ctx.moveTo(px, py);
          const midX = (px + dx) / 2;
          const midY = (py + dy) / 2 - Math.min(18, dist * 3);
          ctx.quadraticCurveTo(midX, midY, dx, dy);
          ctx.stroke();
        }
      }
    }
    ctx.setLineDash([]);
  }

  // 2. 登録されている手作業運搬ライン (s.routes) のアーク・動線描画
  if (s.routes && s.routes.length > 0) {
    for (const route of s.routes) {
      const px = isoCenterX(tx(route.from), ty(route.from));
      const py = isoCenterY(tx(route.from), ty(route.from)) - 8;
      const dx = isoCenterX(tx(route.to), ty(route.to));
      const dy = isoCenterY(tx(route.to), ty(route.to)) - 8;

      const dist = Math.hypot(tx(route.from) - tx(route.to), ty(route.from) - ty(route.to));
      const midX = (px + dx) / 2;
      const midY = (py + dy) / 2 - Math.min(24, Math.max(12, dist * 3.5));

      // 光る点線アーク
      ctx.setLineDash([3, 3]);
      ctx.strokeStyle = '#fbbf24';
      ctx.lineWidth = 1.5;
      ctx.beginPath();
      ctx.moveTo(px, py);
      ctx.quadraticCurveTo(midX, midY, dx, dy);
      ctx.stroke();
      ctx.setLineDash([]);

      // アーク上を流れる光の粒子 (猫の手作業搬送を表現)
      const t = animPhase;
      const qx = (1 - t) * (1 - t) * px + 2 * (1 - t) * t * midX + t * t * dx;
      const qy = (1 - t) * (1 - t) * py + 2 * (1 - t) * t * midY + t * t * dy;
      ctx.fillStyle = '#ffffff';
      ctx.beginPath();
      ctx.arc(qx, qy, 2.5, 0, Math.PI * 2);
      ctx.fill();

      // 始点と終点のマーカー
      ctx.strokeStyle = '#fbbf24';
      ctx.lineWidth = 1;
      diamond(ctx, px, py + 8, TILE_W - 4, TILE_H - 2);
      ctx.stroke();
      diamond(ctx, dx, dy + 8, TILE_W - 4, TILE_H - 2);
      ctx.stroke();

      // 中間地点に「🐾 荷役」バッジ
      ctx.fillStyle = '#000000bb';
      ctx.fillRect(midX - 16, midY - 6, 32, 9);
      ctx.strokeStyle = '#fbbf24';
      ctx.strokeRect(midX - 16, midY - 6, 32, 9);
      ctx.fillStyle = '#fbbf24';
      ctx.font = 'bold 6px sans-serif';
      ctx.fillText('🐾 搬送ライン', midX - 14, midY + 1);
    }
  }

  // 3. ドラッグによる手作業運搬ライン操作プレビュー (左: 設定, 右: 削除)
  if (view.dragStart !== null) {
    const isRemove = view.dragMode === 'remove';
    const sx = tx(view.dragStart);
    const sy = ty(view.dragStart);
    const scx = isoCenterX(sx, sy);
    const scy = isoCenterY(sx, sy);

    // 始点マーカー
    ctx.strokeStyle = isRemove ? '#ef4444' : '#38bdf8';
    ctx.lineWidth = 2;
    diamond(ctx, scx, scy, TILE_W - 2, TILE_H - 1);
    ctx.stroke();

    if (view.hover >= 0 && view.hover !== view.dragStart) {
      const hx = tx(view.hover);
      const hy = ty(view.hover);
      const hcx = isoCenterX(hx, hy);
      const hcy = isoCenterY(hx, hy);

      const dist = Math.hypot(sx - hx, sy - hy);
      const midX = (scx + hcx) / 2;
      const midY = (scy + hcy) / 2 - Math.min(28, Math.max(14, dist * 4));

      // プレビューアーク
      ctx.setLineDash([2, 2]);
      ctx.strokeStyle = isRemove ? '#ef4444' : '#38bdf8';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(scx, scy - 8);
      ctx.quadraticCurveTo(midX, midY, hcx, hcy - 8);
      ctx.stroke();
      ctx.setLineDash([]);

      // 終点マーカー
      ctx.strokeStyle = isRemove ? '#f87171' : '#34d399';
      ctx.lineWidth = 2;
      diamond(ctx, hcx, hcy, TILE_W - 2, TILE_H - 1);
      ctx.stroke();

      // プレビューバッジ表示
      const badgeText = isRemove ? '❌ 手作業運搬ラインを削除' : '🐾 手作業運搬ラインを設定 (費用¥0)';
      ctx.font = 'bold 7px sans-serif';
      const tw = ctx.measureText(badgeText).width + 8;
      ctx.fillStyle = '#000000dd';
      ctx.fillRect(hcx - tw / 2, hcy - 26, tw, 11);
      ctx.strokeStyle = isRemove ? '#ef4444' : '#34d399';
      ctx.lineWidth = 1;
      ctx.strokeRect(hcx - tw / 2, hcy - 26, tw, 11);
      ctx.fillStyle = isRemove ? '#ef4444' : '#34d399';
      ctx.fillText(badgeText, hcx - tw / 2 + 4, hcy - 18);
    }
  }

  // 4. 左上のHUD情報
  ctx.fillStyle = 'rgba(15, 23, 42, 0.85)';
  ctx.fillRect(8, 8, 235, 58);
  ctx.strokeStyle = '#38bdf8';
  ctx.lineWidth = 1;
  ctx.strokeRect(8, 8, 235, 58);

  ctx.fillStyle = '#38bdf8';
  ctx.font = 'bold 9px sans-serif';
  ctx.fillText('🔍 ライン確認モード [Lキーで閉じる]', 14, 21);

  ctx.font = '8px sans-serif';
  ctx.fillStyle = '#34d399';
  ctx.fillText('■ 有効ライン', 14, 34);
  ctx.fillStyle = '#ef4444';
  ctx.fillText('■ 行き止まり/未接続', 72, 34);

  ctx.fillStyle = '#fbbf24';
  ctx.fillText('🖱️ 左ドラッグ: 設定  /  🖱️ 右ドラッグ: 削除', 14, 46);

  const handlerCount = s.tiles.filter((t) => t.kind === 'handler_post' && t.assigned !== null).length;
  const freeCats = s.cats.filter((c) => c.stationIndex === null && c.lives > 0 && c.offDutyDays <= 0).length;
  ctx.fillStyle = '#94a3b8';
  ctx.fillText(`運搬担当: 荷役作業員 ${handlerCount}匹 / 手すき猫 ${freeCats}匹`, 14, 58);

  ctx.restore();
}
