// 《崩坏童话：蜜糖村》10 张地图生成脚本
// 生成 maps/*.tmx（Tiled 源文件），随后由 Tiled CLI 导出 JSON 到 public/assets/maps/
// 运行：node tools/gen_maps.mjs
import { writeFileSync, mkdirSync } from "fs";

// 瓦片索引（与 tools/gen_art.lua 的 tileset 一一对应），GID = 索引 + 1
const I = {
  grass: 0, grass2: 1, grassWF: 2, grassPF: 3, path: 4, path2: 5, pathEdge: 6,
  water1: 7, water2: 8, shore: 9, flowerBush: 10, berryFull: 11, berryPicked: 12,
  treeTL: 13, treeTR: 14, treeBL: 15, treeBR: 16, bush: 17, wall: 18, wallWin: 19,
  door: 20, roofL: 21, roofR: 22, eave: 23, floorW: 24, floorW2: 25, floorC: 26,
  inWall: 27, inWallLow: 28, rug: 29, bedHead: 30, bedFoot: 31, counter: 32,
  oven: 33, shelfEmpty: 34, shelfGoods: 35, table: 36, chair: 37, wellRoof: 38,
  wellRing: 39, fogLight: 40, fogDense: 41, wWall: 42, wFloor: 43, wDoor: 44,
  benchL: 45, benchR: 46, boardL: 47, boardR: 48, mailbox: 49, fenceH: 50,
  fencePost: 51, sign: 52, picnic1: 53, picnic2: 54, bedBloom: 55, dirt: 56,
  sprout: 57, bunting: 58, lantern: 59, sparkle: 60, winIn: 61, wBedHead: 62,
  wBedFoot: 63, desk: 64, calendar: 65, counterPaper: 66, crate: 67, basket: 68,
  bedDry: 69, wWin: 70, doormat: 71, tallGrass: 72, plazaBrick: 73, wCurtain: 74,
  monitor: 75, stoneWall: 76, fogGrass: 77, black: 78, wBlank: 79,
};

class M {
  constructor(name, w, h) {
    this.name = name; this.w = w; this.h = h;
    this.ground = new Array(w * h).fill(0);
    this.obst = new Array(w * h).fill(0);
    this.objects = []; this.oid = 1;
  }
  g(x, y, t) { this.ground[y * this.w + x] = t + 1; }
  o(x, y, t) { this.obst[y * this.w + x] = t + 1; }
  gFill(t) { for (let i = 0; i < this.ground.length; i++) this.ground[i] = t + 1; }
  gRect(x, y, w, h, t) { for (let j = y; j < y + h; j++) for (let i = x; i < x + w; i++) this.g(i, j, t); }
  oRect(x, y, w, h, t) { for (let j = y; j < y + h; j++) for (let i = x; i < x + w; i++) this.o(i, j, t); }
  // 装饰草：确定性伪随机
  grassDecor() {
    for (let j = 0; j < this.h; j++) for (let i = 0; i < this.w; i++) {
      const r = (i * 73 + j * 149 + i * j * 7) % 23;
      if (this.ground[j * this.w + i] === I.grass + 1) {
        if (r === 0) this.g(i, j, I.grassWF); else if (r === 1) this.g(i, j, I.grassPF); else if (r < 6) this.g(i, j, I.grass2);
      }
    }
  }
  tree(x, y) { this.o(x, y, I.treeTL); this.o(x + 1, y, I.treeTR); this.o(x, y + 1, I.treeBL); this.o(x + 1, y + 1, I.treeBR); }
  // 4 宽 ×3 高的房屋立面，door 在底行 dx 处
  facade(x, y, dx) {
    this.o(x, y, I.roofL); this.o(x + 1, y, I.roofL); this.o(x + 2, y, I.roofR); this.o(x + 3, y, I.roofR);
    for (let i = 0; i < 4; i++) this.o(x + i, y + 1, I.eave);
    for (let i = 0; i < 4; i++) this.o(x + i, y + 2, i % 3 === 1 ? I.wallWin : I.wall);
    this.obst[(y + 2) * this.w + (x + dx)] = 0; // 门位可走
    this.g(x + dx, y + 2, I.door);
    this.g(x + dx, y + 3, I.doormat);
  }
  obj(name, type, tx, ty, opts = {}) {
    const o = { id: this.oid++, name, type, x: tx * 16, y: ty * 16, w: (opts.w ?? 0) * 16, h: (opts.h ?? 0) * 16, props: opts.props ?? {} };
    this.objects.push(o); return o;
  }
  spawn(name, tx, ty) { this.obj(name, "spawn", tx + 0.5, ty + 0.5); }
  door(tx, ty, w, h, target, spawn) { this.obj("to_" + target, "door", tx, ty, { w, h, props: { target, spawn } }); }
  npc(name, tx, ty, props = {}) { this.obj(name, "npc", tx + 0.5, ty + 0.5, { props }); }
  poi(name, tx, ty, props = {}) { this.obj(name, "poi", tx + 0.5, ty + 0.5, { props }); }

  tmx() {
    const csv = a => { let out = []; for (let j = 0; j < this.h; j++) out.push(a.slice(j * this.w, (j + 1) * this.w).join(",")); return out.join(",\n"); };
    const props = p => {
      const e = Object.entries(p); if (!e.length) return "";
      return `<properties>${e.map(([k, v]) => `<property name="${k}" value="${v}"/>`).join("")}</properties>`;
    };
    const objs = this.objects.map(o => {
      const size = o.w || o.h ? ` width="${o.w}" height="${o.h}"` : "";
      const body = props(o.props);
      return `  <object id="${o.id}" name="${o.name}" type="${o.type}" x="${o.x}" y="${o.y}"${size}>${body}${o.w || o.h ? "" : "<point/>"}</object>`;
    }).join("\n");
    return `<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="${this.w}" height="${this.h}" tilewidth="16" tileheight="16" infinite="0" nextlayerid="4" nextobjectid="${this.oid}">
 <tileset firstgid="1" source="tiles.tsx"/>
 <layer id="1" name="ground" width="${this.w}" height="${this.h}">
  <data encoding="csv">
${csv(this.ground)}
</data>
 </layer>
 <layer id="2" name="obstacles" width="${this.w}" height="${this.h}">
  <properties><property name="collides" type="bool" value="true"/></properties>
  <data encoding="csv">
${csv(this.obst)}
</data>
 </layer>
 <objectgroup id="3" name="objects">
${objs}
 </objectgroup>
</map>
`;
  }
}

mkdirSync("maps", { recursive: true });
mkdirSync("public/assets/maps", { recursive: true });

const maps = [];

// ============ 小满小屋 12×8 ============
{
  const m = new M("house", 12, 8);
  m.gFill(I.floorW);
  for (let i = 0; i < 12; i++) { m.o(i, 0, I.inWall); m.o(i, 1, i === 6 ? I.winIn : I.inWallLow); }
  m.o(2, 1, I.calendar);
  m.o(1, 2, I.bedHead); m.o(1, 3, I.bedFoot);
  m.o(7, 4, I.table); m.o(8, 4, I.chair);
  m.o(10, 2, I.crate);
  m.g(5, 7, I.rug); m.g(6, 7, I.rug);
  m.o(0, 2, I.crate); // 角落杂物
  m.spawn("bed", 2, 3);
  m.spawn("entry", 5, 6);
  m.door(5, 7.5, 2, 0.5, "plaza", "fromHouse");
  m.poi("calendar", 2, 2, { clue: "C1" });
  m.poi("bed", 1, 4);
  maps.push(m);
}

// ============ 村广场 30×20 ============
{
  const m = new M("plaza", 30, 20);
  m.gFill(I.grass);
  // 道路
  m.gRect(14, 0, 2, 20, I.path); m.gRect(0, 8, 30, 2, I.path);
  for (let i = 0; i < 30; i += 2) m.g(i, 8, I.path2);
  for (let j = 1; j < 20; j += 3) m.g(14, j, I.path2);
  // 庆典场地
  m.gRect(12, 11, 6, 4, I.plazaBrick);
  m.grassDecor();
  // 边界：树与栅栏（留出四个出口）
  for (let i = 0; i < 30; i += 2) { if (i < 12 || i > 17) m.tree(i, 0); }
  for (let i = 0; i < 30; i += 2) { if (i < 13 || i > 16) { m.o(i, 19, I.fenceH); m.o(i + 1, 19, I.fenceH); } }
  for (let j = 2; j < 18; j += 2) { if (j < 7 || j > 10) { m.o(0, j, I.fencePost); m.o(29, j, I.fencePost); } }
  // 房屋立面：小屋 / 面包房 / 杂货店 / 诊所
  m.facade(2, 2, 2); m.facade(7, 2, 2); m.facade(19, 2, 2); m.facade(24, 2, 2);
  m.poi("sign_house", 1, 5); m.o(1, 5, I.sign);
  // 公告板
  m.o(11, 6, I.boardL); m.o(12, 6, I.boardR);
  m.poi("board", 11.5, 7.5);
  // 邮筒与散落信件（C3）
  m.o(17, 1, I.mailbox);
  m.poi("letters", 17.5, 2.5, { clue: "C3" });
  // 双子鸟树
  m.tree(20, 6);
  m.poi("birdtree", 21, 8.5);
  // 花坛 ×3（T2 浇水）
  m.o(7, 11, I.bedDry); m.o(7, 13, I.bedDry); m.o(7, 15, I.bedDry);
  m.poi("flowerbed1", 7.5, 12); m.poi("flowerbed2", 7.5, 14); m.poi("flowerbed3", 7.5, 16);
  // 庆典灯笼杆 ×4（T13 挂彩带）
  m.o(11, 10, I.lantern); m.o(18, 10, I.lantern); m.o(11, 15, I.lantern); m.o(18, 15, I.lantern);
  m.poi("ribbon1", 11.5, 11.2); m.poi("ribbon2", 18.5, 11.2); m.poi("ribbon3", 11.5, 16.2); m.poi("ribbon4", 18.5, 16.2);
  m.poi("festival", 15, 13);
  // 装饰
  m.o(4, 12, I.flowerBush); m.o(23, 12, I.flowerBush); m.o(25, 15, I.bush); m.o(3, 16, I.bush);
  m.tree(26, 16); m.tree(21, 17);
  // NPC
  m.npc("afu", 12.5, 8.5);
  m.npc("dabao", 16.5, 3.5);
  m.npc("bird1", 20.4, 6.3); m.npc("bird2", 21.6, 6.3);
  m.npc("extra1", 5, 10, { patrol: 40 }); m.npc("extra2", 22, 9, { patrol: 56 });
  m.npc("extra3", 24, 17, { patrol: 40 }); m.npc("extra4", 5, 17, { patrol: 24 });
  m.npc("extra5", 22, 17, { patrol: 16 });
  // 出生点
  m.spawn("fromHouse", 4, 6.2); m.spawn("fromBakery", 9, 6.2); m.spawn("fromStore", 21, 6.2); m.spawn("fromClinic", 26, 6.2);
  m.spawn("fromLake", 1.6, 9); m.spawn("fromField", 28.4, 9); m.spawn("fromWell", 14.5, 1.6); m.spawn("fromFog", 14.5, 17.2);
  m.spawn("center", 15, 12);
  // 门与出口
  m.door(4, 4.2, 1, 0.8, "house", "entry");
  m.door(9, 4.2, 1, 0.8, "bakery", "entry");
  m.door(21, 4.2, 1, 0.8, "store", "entry");
  m.door(26, 4.2, 1, 0.8, "clinic", "entry");
  m.door(0, 7, 0.6, 4, "lake", "entry");
  m.door(29.4, 7, 0.6, 4, "field", "entry");
  m.door(12.5, 0, 5, 0.7, "well", "entry");
  m.door(12.5, 19.3, 5, 0.7, "fogwall", "entry");
  maps.push(m);
}

// ============ 面包房 15×10 ============
{
  const m = new M("bakery", 15, 10);
  m.gFill(I.floorW);
  for (let i = 0; i < 15; i++) { m.o(i, 0, I.inWall); m.o(i, 1, (i === 4 || i === 10) ? I.winIn : I.inWallLow); }
  m.o(12, 1, I.oven);
  for (let i = 2; i < 7; i++) m.o(i, 3, I.counter);
  m.o(7, 3, I.counterPaper);
  m.o(1, 5, I.basket); m.o(1, 7, I.crate); m.o(13, 5, I.crate);
  m.o(10, 5, I.table); m.o(11, 5, I.chair);
  m.g(7, 9, I.rug); m.g(8, 9, I.rug);
  m.npc("yuanyuan", 4.5, 2.6);
  m.poi("recipe", 7.5, 4.2, { clue: "C2" });
  m.poi("knead", 10.5, 6.2);
  m.spawn("entry", 7.5, 8);
  m.door(7, 9.5, 2, 0.5, "plaza", "fromBakery");
  maps.push(m);
}

// ============ 杂货店 15×10 ============
{
  const m = new M("store", 15, 10);
  m.gFill(I.floorW2);
  for (let i = 0; i < 15; i++) { m.o(i, 0, I.inWall); m.o(i, 1, i === 12 ? I.winIn : I.inWallLow); }
  for (let i = 2; i < 11; i++) m.o(i, 2, I.shelfGoods);
  m.o(13, 4, I.shelfGoods); m.o(13, 5, I.shelfGoods); m.o(13, 6, I.shelfGoods);
  m.o(3, 5, I.counter); m.o(4, 5, I.counter);
  m.o(1, 7, I.crate); m.o(11, 8, I.basket);
  m.g(7, 9, I.rug); m.g(8, 9, I.rug);
  m.npc("mimi", 3.5, 4.4);
  m.poi("shelfA", 3.5, 3.2); m.poi("shelfB", 6.5, 3.2); m.poi("shelfC", 9.5, 3.2);
  m.poi("shelf_c5", 12.4, 5.5, { clue: "C5" });
  m.spawn("entry", 7.5, 8);
  m.door(7, 9.5, 2, 0.5, "plaza", "fromStore");
  maps.push(m);
}

// ============ 诊所 12×8 ============
{
  const m = new M("clinic", 12, 8);
  m.gFill(I.floorC);
  for (let i = 0; i < 12; i++) { m.o(i, 0, I.wWall); m.o(i, 1, i === 3 ? I.wWin : I.inWallLow); }
  m.o(5, 3, I.desk);
  m.o(9, 2, I.wBedHead); m.o(9, 3, I.wBedFoot);
  m.o(8, 1, I.wCurtain);
  m.o(1, 2, I.crate);
  m.g(5, 7, I.rug); m.g(6, 7, I.rug);
  m.npc("fu", 6.5, 2.6);
  m.poi("chart", 4.5, 4.2, { clue: "C8" });
  m.spawn("entry", 5.5, 6);
  m.door(5, 7.5, 2, 0.5, "plaza", "fromClinic");
  maps.push(m);
}

// ============ 湖畔 24×16 ============
{
  const m = new M("lake", 24, 16);
  m.gFill(I.grass);
  for (let i = 0; i < 24; i++) {
    m.g(i, 9, I.shore);
    for (let j = 10; j < 16; j++) m.g(i, j, (i * 31 + j * 17) % 7 === 0 ? I.sparkle : ((i + j) % 2 ? I.water1 : I.water2));
  }
  m.oRect(0, 10, 24, 6, I.water1); // 水面不可走（碰撞用，绘制层在 ground）
  for (let j = 10; j < 16; j++) for (let i = 0; i < 24; i++) m.obst[j * m.w + i] = m.ground[j * m.w + i]; // 与水面同贴图
  m.grassDecor();
  for (let i = 0; i < 24; i += 2) m.tree(i, 0);
  for (let j = 2; j < 8; j += 2) { m.o(0, j, I.fencePost); }
  m.tree(2, 3); m.tree(11, 2); m.o(6, 4, I.flowerBush);
  // 长椅（龟爷爷）
  m.o(16, 5, I.benchL); m.o(17, 5, I.benchR);
  m.npc("gui", 18.9, 5.5);
  m.poi("bench", 16.5, 6.5);
  // 野餐布
  m.g(7, 6, I.picnic1); m.g(8, 6, I.picnic2); m.g(7, 7, I.picnic2); m.g(8, 7, I.picnic1);
  m.poi("picnic", 8, 7);
  m.spawn("entry", 22, 7);
  m.spawn("picnicSpot", 8, 7.6);
  m.door(23.4, 5, 0.6, 5, "plaza", "fromLake");
  maps.push(m);
}

// ============ 花田 20×14 ============
{
  const m = new M("field", 20, 14);
  m.gFill(I.grass);
  m.gRect(3, 3, 14, 8, I.dirt);
  for (let j = 3; j < 11; j++) for (let i = 3; i < 17; i++) if ((i + j) % 3 === 0) m.g(i, j, I.sprout);
  m.grassDecor();
  for (let i = 0; i < 20; i += 2) { m.tree(i, 0); if (i < 4 || i > 8) { m.o(i, 13, I.fenceH); m.o(i + 1, 13, I.fenceH); } }
  for (let j = 2; j < 12; j += 2) { if (j < 5 || j > 8) m.o(19, j, I.fencePost); }
  // 莓果丛 ×5（T6）
  const berries = [[4, 4], [8, 5], [12, 4], [15, 7], [6, 9]];
  berries.forEach(([x, y], k) => { m.o(x, y, I.berryFull); m.poi("berry" + (k + 1), x + 0.5, y + 1.5); });
  // 浇花点
  m.o(3, 11, I.bedDry);
  m.poi("fieldwater", 3.5, 12.2);
  m.o(17, 11, I.flowerBush); m.o(2, 2, I.bush);
  m.spawn("entry", 1.6, 6);
  m.door(0, 4.5, 0.6, 5, "plaza", "fromField");
  maps.push(m);
}

// ============ 古井空地 14×10 ============
{
  const m = new M("well", 14, 10);
  m.gFill(I.grass);
  m.gRect(6, 6, 2, 4, I.path);
  m.grassDecor();
  for (let i = 0; i < 14; i += 2) m.tree(i, 0);
  for (let j = 2; j < 9; j += 2) { m.o(0, j, I.fencePost); m.o(13, j, I.fencePost); }
  for (let i = 0; i < 14; i += 2) { if (i < 5 || i > 8) { m.o(i, 9, I.fenceH); m.o(i + 1, 9, I.fenceH); } }
  m.o(2, 4, I.tallGrass); m.o(3, 4, I.tallGrass); m.o(11, 5, I.tallGrass); m.o(11, 3, I.bush)
  m.o(6, 3, I.wellRoof); m.o(6, 4, I.wellRing);
  m.o(9, 4, I.sign);
  m.poi("well", 6.5, 5.5, { clue: "C7" });
  m.spawn("entry", 6.5, 7.6);
  m.door(4.5, 9.3, 5, 0.7, "plaza", "fromWell");
  maps.push(m);
}

// ============ 雾墙边界 20×8 ============
{
  const m = new M("fogwall", 20, 8);
  m.gFill(I.grass);
  for (let i = 0; i < 20; i++) {
    m.g(i, 4, I.fogGrass);
    m.g(i, 5, I.fogLight);
    m.g(i, 6, I.fogDense); m.g(i, 7, I.fogDense);
    m.o(i, 6, I.fogDense); m.o(i, 7, I.fogDense);
  }
  m.grassDecor();
  for (let j = 0; j < 6; j += 2) { m.o(0, j, I.fencePost); m.o(19, j, I.fencePost); }
  m.tree(2, 1); m.tree(16, 1);
  m.o(4, 3, I.sign);
  m.poi("fog", 10, 5.5);
  m.spawn("entry", 10, 2.4);
  m.door(7.5, 0, 5, 0.7, "plaza", "fromFog");
  maps.push(m);
}

// ============ 白色层·走廊 20×6 ============
{
  const m = new M("white_corridor", 20, 6);
  m.gFill(I.wFloor);
  for (let i = 0; i < 20; i++) m.o(i, 0, (i % 5 === 2) ? I.wWin : I.wWall);
  m.o(6, 0, I.wDoor); m.o(13, 0, I.wDoor);
  m.spawn("entry", 1, 3);
  m.door(19.5, 0, 0.5, 6, "white_ward", "entry");
  maps.push(m);
}

// ============ 白色层·病房 10×8 ============
{
  const m = new M("white_ward", 10, 8);
  m.gFill(I.wFloor);
  for (let i = 0; i < 10; i++) m.o(i, 0, i === 6 ? I.wWin : I.wWall);
  m.o(2, 0, I.wCurtain);
  m.o(4, 2, I.wBedHead); m.o(4, 3, I.wBedFoot);
  m.o(6, 2, I.monitor);
  m.poi("chart_ward", 5, 4.5);
  m.spawn("entry", 1, 5);
  maps.push(m);
}

// tiles.tsx
writeFileSync("maps/tiles.tsx", `<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.12.2" name="tiles" tilewidth="16" tileheight="16" tilecount="80" columns="10">
 <image source="../public/assets/tiles.png" width="160" height="128"/>
</tileset>
`);

for (const m of maps) writeFileSync(`maps/${m.name}.tmx`, m.tmx());
console.log("TMX written:", maps.map(m => m.name).join(", "));
