/**
 * タイルのドット絵スプライト。全てコードで描いてオフスクリーンにキャッシュする。
 * 「無機質でインダストリアルな工場」を意識して、彩度の低い金属色でまとめている。
 */
import { DIR_VEC, type Tile } from '../core/types';
import { TILE_H, TILE_W, WALL_H, fillBox, fillDiamond, isoVec, sprite } from './iso';

/** タイルスプライトの縦の余白 (高いものを描くぶん) */
const HEAD = WALL_H + 16;
const SPRITE_W = TILE_W + 2;
const SPRITE_H = TILE_H + HEAD + 2;
/** スプライト内での「タイル中心」の位置 */
export const ANCHOR_X = SPRITE_W / 2;
export const ANCHOR_Y = SPRITE_H - TILE_H / 2 - 1;

export const PALETTE = {
  floorA: '#333844',
  floorB: '#2f3440',
  floorEdge: '#272b35',
  grout: '#272c36',

  wallTop: '#59616f',
  wallLeft: '#252932',
  wallRight: '#343945',

  frame: '#4c5361',
  frameDark: '#31363f',
  belt: '#2b313b',
  beltLit: '#3d4450',
  chevron: '#93a4bb',

  machineTop: '#6d7c93',
  machineLeft: '#39424f',
  machineRight: '#4d586a',

  junctionTop: '#5d7688',
  junctionLeft: '#2f3c47',
  junctionRight: '#41525f',

  stockTop: '#7a6b4c',
  stockLeft: '#3d3626',
  stockRight: '#584d37',

  intake: '#43704f',
  output: '#7a6338',
  boiler: '#7d3527',
  boilerHot: '#d4693f',

  itemRaw: '#96a3b8',
  itemMid: '#dcb45f',
  itemDone: '#63dda2',
  itemBad: '#e35d5d',

  catwalk: '#c9a227',
  catwalkDark: '#7d6716',

  latheTop: '#7d8a63',
  latheLeft: '#333a26',
  latheRight: '#57603f',
  pressTop: '#8a6a72',
  pressLeft: '#3a2a2f',
  pressRight: '#63484f',
  robotTop: '#c08a3a',
  robotLeft: '#4a3418',
  robotRight: '#8a622a',
  packerTop: '#6a7f8a',
  packerLeft: '#2c363b',
  packerRight: '#4a5c65',

  palletWood: '#9a7a4a',
  palletDark: '#4a3a22',
  dockTop: '#5a6470',
  dockLeft: '#2a3037',
  dockRight: '#414b55',
  forkYellow: '#e0a92e',
  forkDark: '#6b4f12',

  labTop: '#5b7a86',
  labLeft: '#26363c',
  labRight: '#3f5860',
  poleTop: '#7a8290',
  poleLeft: '#2c313a',
  poleRight: '#4a515c',
  genTop: '#8a7a3a',
  solarTop: '#2f4a7a',

  breakFloor: '#63474a',
  breakFloorAlt: '#6d4f52',
  officeTop: '#9a7d5c',
  officeLeft: '#3f3226',
  officeRight: '#6b5740',
} as const;

/** 菱形のパスだけを作る (塗らずに使いたい時用) */
function diamondPath(
  ctx: CanvasRenderingContext2D,
  cx: number,
  cy: number,
  w: number,
  h: number,
): void {
  ctx.beginPath();
  ctx.moveTo(cx, cy - h / 2);
  ctx.lineTo(cx + w / 2, cy);
  ctx.lineTo(cx, cy + h / 2);
  ctx.lineTo(cx - w / 2, cy);
  ctx.closePath();
}

function tileSprite(
  key: string,
  draw: (ctx: CanvasRenderingContext2D, cx: number, cy: number) => void,
): HTMLCanvasElement {
  return sprite(key, SPRITE_W, SPRITE_H, (ctx) => draw(ctx, ANCHOR_X, ANCHOR_Y));
}

/** 床。市松にして工場の床パネルらしくする。 */
export function floorSprite(alt: boolean): HTMLCanvasElement {
  return tileSprite(`floor:${alt}`, (ctx, cx, cy) => {
    fillDiamond(ctx, cx, cy, TILE_W, TILE_H, alt ? PALETTE.floorB : PALETTE.floorA);
    // 目地
    ctx.strokeStyle = PALETTE.grout;
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(cx, cy - TILE_H / 2 + 0.5);
    ctx.lineTo(cx + TILE_W / 2 - 0.5, cy);
    ctx.lineTo(cx, cy + TILE_H / 2 - 0.5);
    ctx.lineTo(cx - TILE_W / 2 + 0.5, cy);
    ctx.closePath();
    ctx.stroke();
    // 汚れのドット
    if (alt) {
      ctx.fillStyle = PALETTE.floorEdge;
      ctx.fillRect(cx - 5, cy - 1, 2, 1);
      ctx.fillRect(cx + 4, cy + 2, 2, 1);
    }
  });
}

export function wallSprite(): HTMLCanvasElement {
  return tileSprite('wall', (ctx, cx, cy) => {
    fillBox(ctx, cx, cy, TILE_W, TILE_H, WALL_H, {
      top: PALETTE.wallTop,
      left: PALETTE.wallLeft,
      right: PALETTE.wallRight,
    });
    // side panel seams
    ctx.fillStyle = '#1d2028';
    ctx.fillRect(cx - TILE_W / 2, cy - TILE_H / 2 - 8, TILE_W / 2, 1);
    ctx.fillStyle = '#3d434f';
    ctx.fillRect(cx, cy - 8, TILE_W / 2, 1);
  });
}

/**
 * コンベア。frame で搬送方向にシェブロンが流れる。
 * 「どちらへ流れているか」が一目で分かることを最優先にしている。
 */
export function conveyorSprite(dir: number, frame: number, broken: boolean): HTMLCanvasElement {
  return tileSprite(`conv:${dir}:${frame}:${broken}`, (ctx, cx, cy) => {
    const lift = 5;
    fillBox(ctx, cx, cy, TILE_W, TILE_H, lift, {
      top: PALETTE.belt,
      left: PALETTE.frameDark,
      right: PALETTE.frame,
    });

    const top = cy - lift;
    const [gx, gy] = DIR_VEC[dir];
    // 進行方向と、その直交方向をスクリーン空間へ
    const fwd = isoVec(gx, gy);
    const side = isoVec(gy, -gx);

    ctx.save();
    ctx.beginPath();
    ctx.moveTo(cx, top - TILE_H / 2);
    ctx.lineTo(cx + TILE_W / 2, top);
    ctx.lineTo(cx, top + TILE_H / 2);
    ctx.lineTo(cx - TILE_W / 2, top);
    ctx.closePath();
    ctx.clip();

    // ベルト本体
    fillDiamond(ctx, cx, top, TILE_W - 4, TILE_H - 2, PALETTE.beltLit);
    // 両脇のレール
    ctx.strokeStyle = PALETTE.frame;
    ctx.lineWidth = 1;
    for (const sgn of [-1, 1]) {
      ctx.beginPath();
      ctx.moveTo(cx + side.x * 0.42 * sgn - fwd.x * 0.5, top + side.y * 0.42 * sgn - fwd.y * 0.5);
      ctx.lineTo(cx + side.x * 0.42 * sgn + fwd.x * 0.5, top + side.y * 0.42 * sgn + fwd.y * 0.5);
      ctx.stroke();
    }

    // 流れる矢印。細く控えめにして、ラインが白い梯子に見えないようにする。
    ctx.strokeStyle = broken ? '#a05a5a' : PALETTE.chevron;
    ctx.lineWidth = 1;
    ctx.lineCap = 'butt';
    const phase = (frame / 8) * 0.66;
    for (let k = -1; k <= 1; k++) {
      const t = k * 0.66 + phase;
      const bx = cx + fwd.x * t;
      const by = top + fwd.y * t;
      ctx.beginPath();
      ctx.moveTo(bx - fwd.x * 0.18 + side.x * 0.28, by - fwd.y * 0.18 + side.y * 0.28);
      ctx.lineTo(bx + fwd.x * 0.16, by + fwd.y * 0.16);
      ctx.lineTo(bx - fwd.x * 0.18 - side.x * 0.28, by - fwd.y * 0.18 - side.y * 0.28);
      ctx.stroke();
    }
    ctx.restore();
  });
}

/** 作業台 */
export function benchSprite(broken: boolean): HTMLCanvasElement {
  return tileSprite(`bench:${broken}`, (ctx, cx, cy) => {
    const h = 13;
    fillBox(ctx, cx, cy, TILE_W - 2, TILE_H - 1, h, {
      top: broken ? '#8a5a5a' : PALETTE.machineTop,
      left: broken ? '#4a2f2f' : PALETTE.machineLeft,
      right: broken ? '#6a4040' : PALETTE.machineRight,
    });
    // 天板の作業面
    fillDiamond(ctx, cx, cy - h - TILE_H / 2 + TILE_H / 2, TILE_W - 14, TILE_H - 7, '#8c9cb5');
    // 側面のパネルライン
    ctx.fillStyle = '#252b34';
    ctx.fillRect(cx - TILE_W / 2 + 4, cy - TILE_H / 2 - 5, TILE_W / 2 - 6, 1);
  });
}

/** 旋盤: 横向きの主軸と回転するチャック。 */
export function latheSprite(broken: boolean, frame: number): HTMLCanvasElement {
  return tileSprite(`lathe:${broken}:${frame}`, (ctx, cx, cy) => {
    const h = 11;
    fillBox(ctx, cx, cy, TILE_W - 4, TILE_H - 2, h, {
      top: broken ? '#8a5a5a' : PALETTE.latheTop,
      left: broken ? '#4a2f2f' : PALETTE.latheLeft,
      right: broken ? '#6a4040' : PALETTE.latheRight,
    });
    // ベッド (往復台の走る面)
    fillDiamond(ctx, cx, cy - h, TILE_W - 14, TILE_H - 8, '#2c3222');
    // 主軸台
    fillBox(ctx, cx - 8, cy - h + 4, 10, 5, 8, {
      top: '#9aa87c', left: '#333a26', right: '#6b7550',
    });
    // 回転するチャック
    ctx.fillStyle = broken ? '#a05a5a' : '#d8e0c0';
    const a = (frame / 8) * Math.PI * 2;
    for (let k = 0; k < 3; k++) {
      const t = a + (k * Math.PI * 2) / 3;
      ctx.fillRect(
        Math.round(cx - 4 + Math.cos(t) * 3),
        Math.round(cy - h - 3 + Math.sin(t) * 1.6),
        2, 2,
      );
    }
    // 加工中の材料
    ctx.fillStyle = '#b9c2cf';
    ctx.fillRect(cx - 1, cy - h - 3, 9, 2);
  });
}

/** プレス機: 上下するラムと金型。 */
export function pressSprite(broken: boolean, frame: number): HTMLCanvasElement {
  return tileSprite(`press:${broken}:${frame}`, (ctx, cx, cy) => {
    const h = 8;
    // ボルスタ (下型)
    fillBox(ctx, cx, cy, TILE_W - 4, TILE_H - 2, h, {
      top: broken ? '#8a5a5a' : PALETTE.pressTop,
      left: broken ? '#4a2f2f' : PALETTE.pressLeft,
      right: broken ? '#6a4040' : PALETTE.pressRight,
    });
    // 門型のフレーム
    ctx.fillStyle = '#4a3a40';
    ctx.fillRect(cx - 11, cy - h - 26, 4, 26);
    ctx.fillRect(cx + 7, cy - h - 26, 4, 26);
    ctx.fillStyle = '#6b5560';
    ctx.fillRect(cx - 12, cy - h - 30, 24, 5);
    // 上下するラム
    const stroke = Math.round(Math.abs(Math.sin((frame / 8) * Math.PI)) * 12);
    ctx.fillStyle = broken ? '#a05a5a' : '#c8b0b8';
    ctx.fillRect(cx - 8, cy - h - 24 + stroke, 17, 7);
    ctx.fillStyle = '#8a7078';
    ctx.fillRect(cx - 8, cy - h - 18 + stroke, 17, 2);
  });
}

/** アームロボット: 台座から伸びる多関節アーム。担当猫は要らない。 */
export function robotSprite(broken: boolean, frame: number): HTMLCanvasElement {
  return tileSprite(`robot:${broken}:${frame}`, (ctx, cx, cy) => {
    const h = 6;
    fillBox(ctx, cx, cy, TILE_W - 8, TILE_H - 4, h, {
      top: broken ? '#8a5a5a' : PALETTE.robotTop,
      left: broken ? '#4a2f2f' : PALETTE.robotLeft,
      right: broken ? '#6a4040' : PALETTE.robotRight,
    });

    const swing = Math.sin((frame / 8) * Math.PI * 2);
    const base = cy - h - 2;
    // 第1アーム
    ctx.strokeStyle = broken ? '#a05a5a' : '#e0a94e';
    ctx.lineWidth = 4;
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.moveTo(cx, base);
    const e1x = cx + swing * 5;
    const e1y = base - 13;
    ctx.lineTo(e1x, e1y);
    ctx.stroke();
    // 第2アーム
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(e1x, e1y);
    ctx.lineTo(e1x + swing * 9, e1y - 6);
    ctx.stroke();
    // 先端のハンド
    ctx.fillStyle = '#f0d9a0';
    ctx.fillRect(Math.round(e1x + swing * 9) - 2, Math.round(e1y - 8), 4, 4);
    // 回転灯
    ctx.fillStyle = frame % 4 < 2 ? '#ffdf6b' : '#7a5a1a';
    ctx.fillRect(cx - 2, base - 2, 4, 2);
  });
}

/** 梱包機: 箱を封するトンネル。猫が入り込みたくなる。 */
export function packerSprite(broken: boolean): HTMLCanvasElement {
  return tileSprite(`packer:${broken}`, (ctx, cx, cy) => {
    const h = 9;
    fillBox(ctx, cx, cy, TILE_W - 4, TILE_H - 2, h, {
      top: broken ? '#8a5a5a' : PALETTE.packerTop,
      left: broken ? '#4a2f2f' : PALETTE.packerLeft,
      right: broken ? '#6a4040' : PALETTE.packerRight,
    });
    // トンネルの覆い
    ctx.fillStyle = '#3a464c';
    ctx.fillRect(cx - 12, cy - h - 16, 24, 14);
    ctx.fillStyle = '#5c7078';
    ctx.fillRect(cx - 12, cy - h - 16, 24, 3);
    // 投入口 (ここに猫が入る)
    ctx.fillStyle = '#12171a';
    ctx.fillRect(cx - 7, cy - h - 12, 14, 9);
    // テープカッター
    ctx.fillStyle = '#c9a227';
    ctx.fillRect(cx - 2, cy - h - 19, 4, 3);
  });
}

/** 分岐機・合流機の筐体 */
export function junctionSprite(kind: 'splitter' | 'merger'): HTMLCanvasElement {
  return tileSprite(`junction:${kind}`, (ctx, cx, cy) => {
    const h = 7;
    fillBox(ctx, cx, cy, TILE_W - 2, TILE_H - 1, h, {
      top: PALETTE.junctionTop,
      left: PALETTE.junctionLeft,
      right: PALETTE.junctionRight,
    });
    fillDiamond(ctx, cx, cy - h, TILE_W - 12, TILE_H - 6, '#2a353e');
  });
}

/** ストッカー(棚) */
export function stockerSprite(tiers = 1, load = 0): HTMLCanvasElement {
  return tileSprite(`stocker:${tiers}:${Math.min(load, 24)}`, (ctx, cx, cy) => {
    // 段ごとに棚を積み上げる
    for (let tier = 0; tier < tiers; tier++) {
      const base = cy - tier * 9;
      fillBox(ctx, cx, base, TILE_W - 2 - tier * 2, TILE_H - 1 - tier, 6, {
        top: PALETTE.stockTop,
        left: PALETTE.stockLeft,
        right: PALETTE.stockRight,
      });
      ctx.fillStyle = '#2e2819';
      fillDiamond(ctx, cx, base - 6, TILE_W - 16, 3, '#3b331f');
      // その段に載っている在庫
      const inTier = Math.max(0, Math.min(8, load - tier * 8));
      if (inTier > 0) {
        ctx.fillStyle = '#d8c89a';
        for (let k = 0; k < Math.min(inTier, 4); k++) {
          ctx.fillRect(cx - 8 + k * 5, base - 9, 4, 3);
        }
      }
    }
    // 二段目以降は崩落の危険があるので警告色の帯を入れる
    if (tiers > 1) {
      ctx.fillStyle = '#e0a92e';
      ctx.fillRect(cx - TILE_W / 2 + 4, cy - 2, TILE_W - 8, 1);
    }
  });
}

export function intakeSprite(): HTMLCanvasElement {
  return tileSprite('intake', (ctx, cx, cy) => {
    fillBox(ctx, cx, cy, TILE_W, TILE_H, WALL_H, {
      top: PALETTE.intake,
      left: '#1f3527',
      right: '#2f5239',
    });
    ctx.fillStyle = '#a8e2ba';
    ctx.fillRect(cx - 6, cy - TILE_H / 2 - 14, 12, 2);
    ctx.fillRect(cx - 3, cy - TILE_H / 2 - 11, 6, 2);
  });
}

export function outputSprite(hasTruck = true, load = 0): HTMLCanvasElement {
  return tileSprite(`output:${hasTruck}:${Math.min(load, 12)}`, (ctx, cx, cy) => {
    // プラットホーム (壁・開口部)
    fillBox(ctx, cx, cy, TILE_W, TILE_H, WALL_H, {
      top: PALETTE.output,
      left: '#3a2f1c',
      right: '#5b4a2a',
    });
    // 開口部のシャッター枠
    ctx.fillStyle = '#f2dda6';
    ctx.fillRect(cx - 6, cy - TILE_H / 2 - 14, 12, 2);
    ctx.fillRect(cx - 6, cy - TILE_H / 2 - 11, 12, 2);

    // ハザードストライプ
    ctx.save();
    diamondPath(ctx, cx, cy - 6, TILE_W - 4, TILE_H - 2);
    ctx.clip();
    ctx.fillStyle = '#c9a227';
    for (let k = -TILE_W; k < TILE_W; k += 8) ctx.fillRect(cx + k, cy - 14, 2, TILE_H + 4);
    ctx.restore();

    if (!hasTruck) return;

    // 着車中のトラックの荷台
    fillBox(ctx, cx, cy - 6, TILE_W - 10, TILE_H - 5, 14, {
      top: '#7c8899',
      left: '#28313b',
      right: '#45505e',
    });
    // トラックのキャブ・ルーフ
    ctx.fillStyle = '#1c242c';
    ctx.fillRect(cx - 9, cy - 19, 18, 2);

    // 積載量ゲージ
    const w = 16;
    ctx.fillStyle = '#000000aa';
    ctx.fillRect(cx - w / 2, cy - 23, w, 3);
    ctx.fillStyle = '#4ade80';
    ctx.fillRect(cx - w / 2, cy - 23, Math.round((w * Math.min(load, 12)) / 12), 3);
  });
}

/** ボイラー: 立入禁止の熱源。ハザードストライプを入れる。 */
export function boilerSprite(): HTMLCanvasElement {
  return tileSprite('boiler', (ctx, cx, cy) => {
    const h = 10;
    fillBox(ctx, cx, cy, TILE_W, TILE_H, h, {
      top: PALETTE.boiler,
      left: '#4b1f16',
      right: '#65291e',
    });
    // 熱を持った上面
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(cx, cy - h - TILE_H / 2);
    ctx.lineTo(cx + TILE_W / 2, cy - h);
    ctx.lineTo(cx, cy - h + TILE_H / 2);
    ctx.lineTo(cx - TILE_W / 2, cy - h);
    ctx.closePath();
    ctx.clip();
    ctx.fillStyle = PALETTE.boilerHot;
    for (let k = -TILE_W; k < TILE_W; k += 8) {
      ctx.fillRect(cx + k, cy - h - TILE_H, 3, TILE_H * 2);
    }
    ctx.restore();
  });
}

/** 休憩室の床。隣り合わせて敷くと1つの部屋に見えるよう、目地を控えめにしてある。 */
export function breakroomSprite(alt: boolean): HTMLCanvasElement {
  return tileSprite(`breakroom:${alt}`, (ctx, cx, cy) => {
    fillDiamond(ctx, cx, cy, TILE_W, TILE_H, alt ? PALETTE.breakFloorAlt : PALETTE.breakFloor);
    // 毛足の長いカーペットらしい細かいドット
    ctx.fillStyle = '#7d5b5e';
    for (let k = -3; k <= 3; k++) {
      ctx.fillRect(cx + k * 4, cy + (k % 2 === 0 ? -2 : 2), 2, 1);
    }
    // 縁だけ少し明るくして部屋の輪郭を出す
    ctx.strokeStyle = '#8a6669';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(cx, cy - TILE_H / 2 + 0.5);
    ctx.lineTo(cx + TILE_W / 2 - 0.5, cy);
    ctx.stroke();
  });
}

/** 事務机。書類の山と小さなモニタを載せる。 */
export function officeSprite(paperwork: number): HTMLCanvasElement {
  const stack = Math.min(4, Math.ceil(paperwork / 3));
  return tileSprite(`office:${stack}`, (ctx, cx, cy) => {
    const h = 11;
    fillBox(ctx, cx, cy, TILE_W - 6, TILE_H - 3, h, {
      top: PALETTE.officeTop,
      left: PALETTE.officeLeft,
      right: PALETTE.officeRight,
    });
    // モニタ
    ctx.fillStyle = '#22262e';
    ctx.fillRect(cx + 2, cy - h - 11, 9, 7);
    ctx.fillStyle = '#5fd39a';
    ctx.fillRect(cx + 3, cy - h - 10, 7, 5);
    ctx.fillStyle = '#3a4049';
    ctx.fillRect(cx + 5, cy - h - 4, 3, 2);

    // 未処理書類の山。溜まるほど高くなる。
    for (let k = 0; k < stack; k++) {
      ctx.fillStyle = k % 2 === 0 ? '#e8e2d2' : '#cfc8b6';
      ctx.fillRect(cx - 11, cy - h - 3 - k * 2, 10, 2);
    }
    if (stack > 0) {
      ctx.fillStyle = '#00000044';
      ctx.fillRect(cx - 11, cy - h - 1, 10, 1);
    }
  });
}

const FACILITY_DRAW: Record<
  'rest' | 'litter' | 'scratch' | 'feeder' | 'box',
  (ctx: CanvasRenderingContext2D, cx: number, cy: number) => void
> = {
  // 休憩所: 猫用クッション
  rest: (ctx, cx, cy) => {
    fillDiamond(ctx, cx, cy, TILE_W - 4, TILE_H - 2, '#3a3140');
    fillDiamond(ctx, cx, cy - 3, TILE_W - 10, TILE_H - 5, '#9c637f');
    fillDiamond(ctx, cx, cy - 5, TILE_W - 18, TILE_H - 9, '#c07f9a');
  },
  // トイレ: 砂の入った箱
  litter: (ctx, cx, cy) => {
    fillBox(ctx, cx, cy, TILE_W - 6, TILE_H - 3, 6, {
      top: '#d8cca8',
      left: '#4a4638',
      right: '#6b6450',
    });
    ctx.fillStyle = '#a89b78';
    ctx.fillRect(cx - 4, cy - 7, 3, 1);
    ctx.fillRect(cx + 2, cy - 5, 3, 1);
  },
  // 爪とぎタワー: 縄巻きの柱
  scratch: (ctx, cx, cy) => {
    fillDiamond(ctx, cx, cy, TILE_W - 8, TILE_H - 4, '#3d3428');
    fillBox(ctx, cx, cy - 1, 13, 7, 22, {
      top: '#d3b078',
      left: '#6b573a',
      right: '#9a7f50',
    });
    ctx.fillStyle = '#6b573a';
    for (let k = 0; k < 6; k++) ctx.fillRect(cx - 6, cy - 5 - k * 4, 13, 1);
  },
  // 自動給餌器: 器とドライフード
  feeder: (ctx, cx, cy) => {
    fillBox(ctx, cx, cy, TILE_W - 12, TILE_H - 6, 11, {
      top: '#6c7787',
      left: '#333a45',
      right: '#4c5768',
    });
    fillDiamond(ctx, cx, cy - 12, TILE_W - 16, TILE_H - 9, '#2b3038');
    ctx.fillStyle = '#c98a4b';
    ctx.fillRect(cx - 4, cy - 11, 3, 2);
    ctx.fillRect(cx, cy - 10, 3, 2);
    ctx.fillRect(cx + 3, cy - 12, 3, 2);
  },
  // 段ボール箱: 最強のトラップ
  box: (ctx, cx, cy) => {
    fillBox(ctx, cx, cy, TILE_W - 8, TILE_H - 4, 12, {
      top: '#1c1710',
      left: '#8a6a42',
      right: '#b88d55',
    });
    // 開いたフタ
    ctx.fillStyle = '#c39a5e';
    ctx.fillRect(cx - 10, cy - 13, 20, 2);
    ctx.fillStyle = '#8a6a42';
    ctx.fillRect(cx - 10, cy - 11, 20, 1);
  },
};

export function facilitySprite(kind: keyof typeof FACILITY_DRAW): HTMLCanvasElement {
  return tileSprite(`fac:${kind}`, (ctx, cx, cy) => FACILITY_DRAW[kind](ctx, cx, cy));
}

/** パレット置き場。木製パレットの上に段積みされた荷。 */
export function palletSprite(tiers: number, load: number): HTMLCanvasElement {
  return tileSprite(`pallet:${tiers}:${Math.min(load, 12)}`, (ctx, cx, cy) => {
    // 床の区画線
    fillDiamond(ctx, cx, cy, TILE_W - 2, TILE_H - 1, '#2b2f26');
    ctx.strokeStyle = '#8a7a3a';
    ctx.lineWidth = 1;
    ctx.setLineDash([3, 3]);
    diamondPath(ctx, cx, cy, TILE_W - 4, TILE_H - 2);
    ctx.stroke();
    ctx.setLineDash([]);

    // 木製パレット
    fillBox(ctx, cx, cy, TILE_W - 10, TILE_H - 5, 4, {
      top: PALETTE.palletWood,
      left: PALETTE.palletDark,
      right: '#7a5f38',
    });

    // 積み荷を段ごとに積む
    for (let k = 0; k < Math.min(load, tiers * 4); k++) {
      const tier = Math.floor(k / 4);
      const slot = k % 4;
      const ox = (slot % 2 === 0 ? -4 : 4);
      const oy = (slot < 2 ? -2 : 2);
      fillBox(ctx, cx + ox, cy + oy - 4 - tier * 6, 11, 6, 6, {
        top: '#c8b48a', left: '#5e5137', right: '#8f7d55',
      });
    }

    // 段数の表示
    if (tiers > 1) {
      ctx.fillStyle = '#ffdf6b';
      for (let k = 0; k < tiers; k++) ctx.fillRect(cx + TILE_W / 2 - 7, cy - 2 - k * 3, 4, 2);
    }
  });
}

/** カゴ置き場。金網メッシュのカゴに部品を入れる。 */
export function basketSprite(tiers: number, load: number): HTMLCanvasElement {
  return tileSprite(`basket:${tiers}:${Math.min(load, 8)}`, (ctx, cx, cy) => {
    // 床の区画線
    fillDiamond(ctx, cx, cy, TILE_W - 4, TILE_H - 2, '#242a30');
    ctx.strokeStyle = '#6a8095';
    ctx.lineWidth = 1;
    diamondPath(ctx, cx, cy, TILE_W - 6, TILE_H - 3);
    ctx.stroke();

    for (let t = 0; t < tiers; t++) {
      const baseY = cy - t * 7;
      // カゴの枠
      fillBox(ctx, cx, baseY, TILE_W - 12, TILE_H - 6, 7, {
        top: '#3a4450',
        left: '#20262d',
        right: '#2d353f',
      });
      // メッシュの格子線
      ctx.strokeStyle = '#7c92a6';
      ctx.lineWidth = 1;
      for (let k = -6; k <= 6; k += 3) {
        ctx.beginPath();
        ctx.moveTo(cx + k, baseY - 7);
        ctx.lineTo(cx + k, baseY);
        ctx.stroke();
      }
    }

    // 中の積荷
    for (let k = 0; k < Math.min(load, tiers * 4); k++) {
      const tier = Math.floor(k / 4);
      const slot = k % 4;
      const ox = (slot % 2 === 0 ? -3 : 3);
      const oy = (slot < 2 ? -1 : 1);
      fillBox(ctx, cx + ox, cy + oy - 4 - tier * 7, 8, 4, 5, {
        top: '#d4a86a', left: '#6e512d', right: '#9c7746',
      });
    }
  });
}

/** 大型鉄製コンテナ。大容量の頑丈なコンテナボックス。 */
export function containerSprite(load: number): HTMLCanvasElement {
  return tileSprite(`container:${Math.min(load, 12)}`, (ctx, cx, cy) => {
    // コンテナ本体
    fillBox(ctx, cx, cy, TILE_W - 6, TILE_H - 3, 16, {
      top: '#2b5278',
      left: '#14293d',
      right: '#1e3c59',
    });
    // コルゲート壁の縦リブ
    ctx.fillStyle = '#14293d';
    for (let k = -12; k <= 0; k += 3) {
      ctx.fillRect(cx + k - 1, cy - 14 + Math.round((k + 12) * 0.4), 1, 14);
    }
    ctx.fillStyle = '#1e3c59';
    for (let k = 2; k <= 14; k += 3) {
      ctx.fillRect(cx + k - 1, cy - 8 - Math.round((k - 2) * 0.4), 1, 14);
    }
    // コーナー金具
    ctx.fillStyle = '#e8b838';
    ctx.fillRect(cx - TILE_W / 2 + 5, cy - 15, 3, 3);
    ctx.fillRect(cx + TILE_W / 2 - 8, cy - 15, 3, 3);
    ctx.fillRect(cx - 2, cy - 23, 4, 3);

    // 積載量ゲージ
    const w = 18;
    ctx.fillStyle = '#00000088';
    ctx.fillRect(cx - w / 2, cy - 25, w, 3);
    ctx.fillStyle = '#4cd98b';
    ctx.fillRect(cx - w / 2, cy - 25, Math.round((w * Math.min(load, 12)) / 12), 3);
  });
}

/** 荷役ステーション。台車と指示ボードのある作業区画。 */
export function handlerPostSprite(): HTMLCanvasElement {
  return tileSprite('handler_post', (ctx, cx, cy) => {
    // 区画
    fillDiamond(ctx, cx, cy, TILE_W - 2, TILE_H - 1, '#343842');
    ctx.strokeStyle = '#e0a92e';
    ctx.lineWidth = 1;
    diamondPath(ctx, cx, cy, TILE_W - 4, TILE_H - 2);
    ctx.stroke();

    // 手押し台車
    fillBox(ctx, cx - 4, cy - 2, 14, 8, 4, {
      top: '#5a82a6',
      left: '#263b4d',
      right: '#3d5e7d',
    });
    // 台車のハンドル
    ctx.fillStyle = '#222';
    ctx.fillRect(cx + 4, cy - 14, 2, 10);
    ctx.fillRect(cx + 2, cy - 14, 5, 2);

    // 指示ボード
    fillBox(ctx, cx - 8, cy + 2, 6, 4, 10, {
      top: '#c8a870',
      left: '#5e4c30',
      right: '#8c7248',
    });
  });
}

/** トラックバース。荷台の付いたトラックが着車する。 */
export function dockSprite(hasTruck: boolean, load: number): HTMLCanvasElement {
  return tileSprite(`dock:${hasTruck}:${Math.min(load, 12)}`, (ctx, cx, cy) => {
    // プラットホーム
    fillBox(ctx, cx, cy, TILE_W - 2, TILE_H - 1, 6, {
      top: PALETTE.dockTop,
      left: PALETTE.dockLeft,
      right: PALETTE.dockRight,
    });
    // 縞のハザードマーキング
    ctx.save();
    diamondPath(ctx, cx, cy - 6, TILE_W - 2, TILE_H - 1);
    ctx.clip();
    ctx.fillStyle = '#c9a227';
    for (let k = -TILE_W; k < TILE_W; k += 10) ctx.fillRect(cx + k, cy - 14, 3, TILE_H + 4);
    ctx.restore();

    if (!hasTruck) return;

    // トラックの荷台
    fillBox(ctx, cx, cy - 6, TILE_W - 12, TILE_H - 7, 15, {
      top: '#8d97a5', left: '#2f3740', right: '#4c5865',
    });
    ctx.fillStyle = '#39424d';
    ctx.fillRect(cx - 11, cy - 20, 22, 2);
    // 積載量のインジケータ
    const w = 18;
    ctx.fillStyle = '#00000088';
    ctx.fillRect(cx - w / 2, cy - 24, w, 3);
    ctx.fillStyle = '#5fd39a';
    ctx.fillRect(cx - w / 2, cy - 24, Math.round((w * Math.min(load, 12)) / 12), 3);
  });
}

/** フォークリフト詰所。区画線とパイロン。 */
export function forkDepotSprite(): HTMLCanvasElement {
  return tileSprite('forkdepot', (ctx, cx, cy) => {
    fillDiamond(ctx, cx, cy, TILE_W - 2, TILE_H - 1, '#3a3524');
    ctx.strokeStyle = PALETTE.forkYellow;
    ctx.lineWidth = 1;
    diamondPath(ctx, cx, cy, TILE_W - 8, TILE_H - 4);
    ctx.stroke();
    // パイロン
    for (const sgn of [-1, 1]) {
      ctx.fillStyle = '#d4642e';
      ctx.fillRect(cx + sgn * 12 - 1, cy - 7, 3, 6);
      ctx.fillStyle = '#f0f0f0';
      ctx.fillRect(cx + sgn * 12 - 1, cy - 5, 3, 1);
    }
  });
}

/** 開発室: 実験台とフラスコ。 */
export function labSprite(): HTMLCanvasElement {
  return tileSprite('lab', (ctx, cx, cy) => {
    const h = 11;
    fillBox(ctx, cx, cy, TILE_W - 6, TILE_H - 3, h, {
      top: PALETTE.labTop, left: PALETTE.labLeft, right: PALETTE.labRight,
    });
    // 資料の束
    ctx.fillStyle = '#e8e2d2';
    ctx.fillRect(cx - 11, cy - h - 4, 8, 3);
    // フラスコ
    ctx.fillStyle = '#9fd8ff';
    ctx.fillRect(cx + 2, cy - h - 8, 5, 5);
    ctx.fillStyle = '#cfe9ff';
    ctx.fillRect(cx + 3, cy - h - 11, 3, 3);
    // 黒板
    ctx.fillStyle = '#1c2b22';
    ctx.fillRect(cx - 6, cy - h - 16, 13, 8);
    ctx.fillStyle = '#7fd7ff';
    ctx.fillRect(cx - 4, cy - h - 14, 8, 1);
    ctx.fillRect(cx - 4, cy - h - 12, 5, 1);
  });
}

/** 配電盤。ここを中心に電気が届く。 */
export function poleSprite(): HTMLCanvasElement {
  return tileSprite('pole', (ctx, cx, cy) => {
    fillDiamond(ctx, cx, cy, TILE_W - 8, TILE_H - 4, '#2b2f36');
    // 支柱
    ctx.fillStyle = PALETTE.poleLeft;
    ctx.fillRect(cx - 2, cy - 26, 4, 26);
    // キャビネット
    fillBox(ctx, cx, cy, 14, 7, 12, {
      top: PALETTE.poleTop, left: PALETTE.poleLeft, right: PALETTE.poleRight,
    });
    ctx.fillStyle = '#9fd8ff';
    ctx.fillRect(cx - 4, cy - 16, 8, 4);
    // 碍子
    ctx.fillStyle = '#c9d2dd';
    ctx.fillRect(cx - 7, cy - 27, 14, 2);
    ctx.fillStyle = '#ffdf6b';
    ctx.fillRect(cx - 1, cy - 30, 2, 3);
  });
}

/** ディーゼル発電機。排気口から煙が出る。 */
export function generatorSprite(frame: number): HTMLCanvasElement {
  return tileSprite(`gen:${frame}`, (ctx, cx, cy) => {
    const h = 12;
    fillBox(ctx, cx, cy, TILE_W - 6, TILE_H - 3, h, {
      top: PALETTE.genTop, left: '#3a3416', right: '#6b5f2a',
    });
    // ラジエータのフィン
    ctx.fillStyle = '#4a4420';
    for (let k = -3; k <= 3; k++) ctx.fillRect(cx + k * 3, cy - h - 3, 1, 5);
    // 排気管
    ctx.fillStyle = '#3a3a3a';
    ctx.fillRect(cx + 8, cy - h - 14, 3, 12);
    // 煙
    ctx.fillStyle = '#6b6b6b';
    const p = frame % 4;
    ctx.fillRect(cx + 8, cy - h - 17 - p, 3, 2);
    ctx.fillRect(cx + 9, cy - h - 20 - p * 2, 2, 2);
  });
}

/** 太陽光パネル。 */
export function solarSprite(): HTMLCanvasElement {
  return tileSprite('solar', (ctx, cx, cy) => {
    // 架台
    ctx.fillStyle = '#3a4048';
    ctx.fillRect(cx - 9, cy - 8, 2, 8);
    ctx.fillRect(cx + 7, cy - 5, 2, 5);
    // 傾いたパネル
    fillDiamond(ctx, cx, cy - 11, TILE_W - 8, TILE_H - 5, PALETTE.solarTop);
    ctx.strokeStyle = '#5c86c9';
    ctx.lineWidth = 1;
    for (let k = -2; k <= 2; k++) {
      ctx.beginPath();
      ctx.moveTo(cx + k * 6, cy - 11 - 4);
      ctx.lineTo(cx + k * 6 + 6, cy - 11 + 2);
      ctx.stroke();
    }
  });
}

/** キャットウォーク: 猫専用の空中通路。床の上に浮かせて描く。 */
export function catwalkSprite(): HTMLCanvasElement {
  return tileSprite('catwalk', (ctx, cx, cy) => {
    const lift = 13;
    // 支柱
    ctx.fillStyle = PALETTE.catwalkDark;
    ctx.fillRect(cx - TILE_W / 2 + 3, cy - 2, 2, lift);
    ctx.fillRect(cx + TILE_W / 2 - 5, cy - 2, 2, lift);

    fillDiamond(ctx, cx, cy - lift, TILE_W - 4, TILE_H - 2, '#2a2a22');
    // 格子
    ctx.strokeStyle = PALETTE.catwalk;
    ctx.lineWidth = 1;
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(cx, cy - lift - TILE_H / 2 + 1);
    ctx.lineTo(cx + TILE_W / 2 - 2, cy - lift);
    ctx.lineTo(cx, cy - lift + TILE_H / 2 - 1);
    ctx.lineTo(cx - TILE_W / 2 + 2, cy - lift);
    ctx.closePath();
    ctx.stroke();
    ctx.clip();
    for (let k = -TILE_W; k < TILE_W; k += 6) {
      ctx.beginPath();
      ctx.moveTo(cx + k, cy - lift - TILE_H);
      ctx.lineTo(cx + k + TILE_H, cy - lift + TILE_H);
      ctx.stroke();
    }
    ctx.restore();
  });
}

/** 搬送物。ステージごとに色と形を変える。 */
export function itemSprite(stage: number, defective: boolean): HTMLCanvasElement {
  return sprite(`item:${stage}:${defective}`, 16, 18, (ctx) => {
    const cx = 8;
    const cy = 13;
    const color = defective
      ? PALETTE.itemBad
      : stage === 0
        ? PALETTE.itemRaw
        : stage === 1
          ? PALETTE.itemMid
          : PALETTE.itemDone;
    const dark = shade(color, -0.4);
    const mid = shade(color, -0.2);
    fillBox(ctx, cx, cy, 12, 6, 7, { top: color, left: dark, right: mid });
    if (defective) {
      ctx.fillStyle = '#ffffff';
      ctx.fillRect(cx - 3, cy - 9, 2, 2);
      ctx.fillRect(cx + 1, cy - 9, 2, 2);
      ctx.fillRect(cx - 1, cy - 7, 2, 2);
      ctx.fillRect(cx - 3, cy - 5, 2, 2);
      ctx.fillRect(cx + 1, cy - 5, 2, 2);
    }
  });
}

/** 作業台のバッファ残量などに使う小さなランプ */
export function lampRow(
  ctx: CanvasRenderingContext2D,
  cx: number,
  cy: number,
  filled: number,
  total: number,
  color: string,
): void {
  const w = 3;
  const gap = 1;
  const totalW = total * (w + gap) - gap;
  for (let i = 0; i < total; i++) {
    ctx.fillStyle = i < filled ? color : '#00000055';
    ctx.fillRect(Math.round(cx - totalW / 2 + i * (w + gap)), Math.round(cy), w, 2);
  }
}

/** 色を明暗させる。#rrggbb のみ対応。 */
export function shade(hex: string, amount: number): string {
  const n = parseInt(hex.slice(1), 16);
  const clamp = (v: number): number => Math.max(0, Math.min(255, Math.round(v)));
  const r = clamp(((n >> 16) & 255) * (1 + amount));
  const g = clamp(((n >> 8) & 255) * (1 + amount));
  const b = clamp((n & 255) * (1 + amount));
  return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
}

/** タイル種別に応じたスプライトを返す。 */
export function tileSpriteFor(
  t: Tile,
  x: number,
  y: number,
  frame: number,
  paperwork = 0,
): HTMLCanvasElement | null {
  switch (t.kind) {
    case 'floor':
      return floorSprite((x + y) % 2 === 0);
    case 'breakroom':
      return breakroomSprite((x + y) % 2 === 0);
    case 'office':
      return officeSprite(paperwork);
    case 'wall':
      return wallSprite();
    case 'conveyor':
      return conveyorSprite(t.dir, frame, t.brokenFor > 0);
    case 'bench':
      return benchSprite(t.brokenFor > 0);
    case 'lathe':
      return latheSprite(t.brokenFor > 0, frame);
    case 'press':
      return pressSprite(t.brokenFor > 0, frame);
    case 'robot':
      return robotSprite(t.brokenFor > 0, frame);
    case 'packer':
      return packerSprite(t.brokenFor > 0);
    case 'splitter':
      return junctionSprite('splitter');
    case 'merger':
      return junctionSprite('merger');
    case 'stocker':
      return stockerSprite(t.tiers, t.queue.length);
    case 'pallet':
      return palletSprite(t.tiers, t.queue.length);
    case 'basket':
      return basketSprite(t.tiers, t.queue.length);
    case 'container':
      return containerSprite(t.queue.length);
    case 'handler_post':
      return handlerPostSprite();
    case 'lab':
      return labSprite();
    case 'pole':
      return poleSprite();
    case 'generator':
      return generatorSprite(frame);
    case 'solar':
      return solarSprite();
    case 'forkdepot':
      return forkDepotSprite();
    case 'dock':
      return dockSprite(t.truck !== null, t.truck?.length ?? 0);
    case 'intake':
      return intakeSprite();
    case 'output':
      return outputSprite(t.truck !== null, t.truck?.length ?? 0);
    case 'boiler':
      return boilerSprite();
    case 'rest':
    case 'litter':
    case 'scratch':
    case 'feeder':
    case 'box':
      return facilitySprite(t.kind);
    default:
      return null;
  }
}
