import { Application, Graphics, Text, TextStyle, Container } from "pixi.js";
import { CANVAS_WIDTH, CANVAS_HEIGHT } from "./game.config";

const app = new Application();

// ─────────────────────────────────────────
// 색상 팔레트
// ─────────────────────────────────────────
const C = {
  bg:        0x0f0f1a,
  panel:     0x1a1a2e,
  border:    0x3a3a5c,
  gold:      0xffd700,
  green:     0x9bbc0f,
  red:       0xff4444,
  blue:      0x4488ff,
  purple:    0x8b5cf6,
  pink:      0xff1b6d,
  cyan:      0x00ffff,
  white:     0xffffff,
  gray:      0x888888,
  darkgray:  0x333355,
  orange:    0xff8800,
  teal:      0x00ccaa,
  hp:        0x44dd44,
  mp:        0x4488ff,
  hpbg:      0x224422,
  mpbg:      0x222244,
  // 타이틀 화면용
  campBg:    0x0a0a14,
  fireOrange: 0xcc6633,
  bannerBurgundy: 0x5a2a3a,
  brassGold:  0xd4a574,
  shadowBrown: 0x3d2817,
};

// ─────────────────────────────────────────
// 타입 정의
// ─────────────────────────────────────────
type JobId = "knight" | "guardian" | "mage" | "cleric" | "archer" | "alchemist";
type Element = "fire" | "ice" | "lightning" | "holy" | "curse" | "poison" | "none";
type StatusEffect = "burn" | "freeze" | "stun" | "poison" | "shield" | "blessed";

interface Skill {
  id: string;
  name: string;
  mpCost: number;
  range: number;
  aoe: boolean;
  element: Element;
  baseDmg: number;
  healAmt: number;
  desc: string;
  status?: StatusEffect;
  statusChance?: number;
}

interface Unit {
  id: string;
  name: string;
  job: JobId;
  symbol: string;
  symbolColor: number;
  hp: number;
  maxHp: number;
  mp: number;
  maxMp: number;
  atk: number;
  def: number;
  move: number;
  skills: Skill[];
  isPlayer: boolean;
  row: number;
  col: number;
  statusEffects: StatusEffect[];
  alive: boolean;
  lv: number;
}

interface Cell {
  terrain: "normal" | "fire" | "ice" | "electric";
  terrainTimer: number;
}

interface FloatingText {
  text: string;
  x: number;
  y: number;
  color: number;
  age: number;
  lifetime: number;
}

type GamePhase =
  | "title"
  | "name_input"
  | "party_select"
  | "battle_start"
  | "player_turn"
  | "select_target"
  | "enemy_turn"
  | "battle_result"
  | "reward"
  | "gameover"
  | "victory";

// ─────────────────────────────────────────
// 직업 데이터
// ─────────────────────────────────────────
const JOB_DATA: Record<JobId, {
  label: string;
  symbol: string;
  color: number;
  hp: number; mp: number; atk: number; def: number; move: number;
  front: boolean;
  skills: Skill[];
}> = {
  knight: {
    label: "기사", symbol: "⚔", color: C.gold, hp: 120, mp: 30, atk: 35, def: 25, move: 2, front: true,
    skills: [
      { id:"slash",   name:"강격",      mpCost:5,  range:1, aoe:false, element:"none",  baseDmg:55, healAmt:0, desc:"근접 강타 (피해 ×1.6)" },
      { id:"shield",  name:"방패 막기", mpCost:8,  range:0, aoe:false, element:"none",  baseDmg:0,  healAmt:0, desc:"다음 피해 60% 감소", status:"shield", statusChance:1 },
      { id:"charge",  name:"돌진",      mpCost:12, range:2, aoe:false, element:"none",  baseDmg:45, healAmt:0, desc:"2칸 돌진 후 타격" },
    ]
  },
  guardian: {
    label: "수호자", symbol: "🛡", color: C.teal, hp: 150, mp: 25, atk: 20, def: 40, move: 1, front: true,
    skills: [
      { id:"taunt",   name:"도발",      mpCost:6,  range:1, aoe:true,  element:"none",  baseDmg:15, healAmt:0, desc:"주변 적 도발 + 소피해" },
      { id:"barrier", name:"장벽 전개", mpCost:10, range:1, aoe:false, element:"holy",  baseDmg:0,  healAmt:0, desc:"아군 1명에게 보호막", status:"shield", statusChance:1 },
      { id:"smash",   name:"대지 강타", mpCost:15, range:1, aoe:true,  element:"none",  baseDmg:30, healAmt:0, desc:"근접 전체 범위 타격" },
    ]
  },
  mage: {
    label: "마법사", symbol: "✦", color: C.purple, hp: 65, mp: 80, atk: 15, def: 8, move: 1, front: false,
    skills: [
      { id:"fireball",   name:"화염구",   mpCost:12, range:3, aoe:false, element:"fire",      baseDmg:60, healAmt:0, desc:"화염 폭발, 화상 확률", status:"burn",   statusChance:0.4 },
      { id:"icelance",   name:"냉기창",   mpCost:10, range:3, aoe:false, element:"ice",       baseDmg:45, healAmt:0, desc:"냉기 창 투사, 빙결 확률", status:"freeze", statusChance:0.35 },
      { id:"thunder",    name:"번개 폭풍",mpCost:20, range:3, aoe:true,  element:"lightning", baseDmg:50, healAmt:0, desc:"번개 광역, 기절 확률", status:"stun",   statusChance:0.3 },
    ]
  },
  cleric: {
    label: "성직자", symbol: "✝", color: C.white, hp: 80, mp: 70, atk: 12, def: 15, move: 1, front: false,
    skills: [
      { id:"heal",      name:"치유의 빛",  mpCost:10, range:2, aoe:false, element:"holy",  baseDmg:0,  healAmt:40, desc:"아군 1명 HP 회복" },
      { id:"holyflash", name:"신성 섬광",  mpCost:15, range:2, aoe:false, element:"holy",  baseDmg:40, healAmt:0,  desc:"신성 피해 + 언데드 특효" },
      { id:"bless",     name:"축복",       mpCost:12, range:2, aoe:false, element:"holy",  baseDmg:0,  healAmt:0,  desc:"아군 공격력 강화", status:"blessed", statusChance:1 },
    ]
  },
  archer: {
    label: "궁수", symbol: "🏹", color: C.green, hp: 75, mp: 40, atk: 30, def: 10, move: 2, front: false,
    skills: [
      { id:"precise",  name:"정밀 사격", mpCost:6,  range:4, aoe:false, element:"none",   baseDmg:50, healAmt:0, desc:"원거리 정밀 타격" },
      { id:"poison",   name:"독화살",    mpCost:8,  range:4, aoe:false, element:"poison", baseDmg:20, healAmt:0, desc:"독 상태이상 부여", status:"poison", statusChance:0.7 },
      { id:"rain",     name:"화살 폭우", mpCost:18, range:3, aoe:true,  element:"none",   baseDmg:30, healAmt:0, desc:"광역 화살 공격" },
    ]
  },
  alchemist: {
    label: "연금술사", symbol: "⚗", color: C.orange, hp: 70, mp: 60, atk: 18, def: 12, move: 1, front: false,
    skills: [
      { id:"bomb",    name:"폭발 물약",  mpCost:10, range:3, aoe:true,  element:"fire",   baseDmg:45, healAmt:0, desc:"범위 폭발 물약 투척" },
      { id:"acid",    name:"산성 연기",  mpCost:8,  range:3, aoe:true,  element:"poison", baseDmg:20, healAmt:0, desc:"광역 독 + 방어 감소", status:"poison", statusChance:0.6 },
      { id:"elixir",  name:"엘릭시르",   mpCost:15, range:2, aoe:false, element:"none",   baseDmg:0,  healAmt:35, desc:"아군 HP+MP 소량 회복" },
    ]
  },
};

// ─────────────────────────────────────────
// 9 스테이지 구조 정의
// ─────────────────────────────────────────
interface StageConfig {
  stageNum: number;
  name: string;
  desc: string;
  enemyCount: (isBoss: boolean) => number;
  bossThreshold: number; // 0 = 일반, 0.5 = 엘리트, 1 = 보스
  difficulty: number; // 1.0부터 시작
}

const STAGE_CONFIGS: StageConfig[] = [
  { stageNum: 1, name: "초반 1", desc: "적응 구간 - 기본 적들과 친해지기", enemyCount: () => 2, bossThreshold: 0, difficulty: 1.0 },
  { stageNum: 2, name: "초반 2", desc: "적응 구간 - 다양한 적 조합 경험", enemyCount: () => 2, bossThreshold: 0, difficulty: 1.1 },
  { stageNum: 3, name: "중반 1", desc: "중반 진입 - 상태이상 압박 증가", enemyCount: () => 3, bossThreshold: 0, difficulty: 1.25 },
  { stageNum: 4, name: "중반 2", desc: "중반 진입 - 적 조합의 시너지 등장", enemyCount: () => 3, bossThreshold: 0, difficulty: 1.4 },
  { stageNum: 5, name: "엘리트", desc: "중간 고비 - 엘리트 몬스터와의 대면", enemyCount: () => 2, bossThreshold: 0.5, difficulty: 1.65 },
  { stageNum: 6, name: "후반 1", desc: "후반 진입 - 광역 위협 증가", enemyCount: () => 3, bossThreshold: 0, difficulty: 1.8 },
  { stageNum: 7, name: "후반 2", desc: "후반 진입 - 후열 압박 강화", enemyCount: () => 3, bossThreshold: 0, difficulty: 1.95 },
  { stageNum: 8, name: "최종전 직전", desc: "고난도 구간 - 최종 준비 단계", enemyCount: () => 2, bossThreshold: 0, difficulty: 2.2 },
  { stageNum: 9, name: "최종 보스", desc: "최종 시험 - 모든 것을 걸다", enemyCount: () => 1, bossThreshold: 1, difficulty: 2.5 },
];

// ─────────────────────────────────────────
// 몬스터 프리셋 (HP 2배 적용 후 1.5배 추가)
// ─────────────────────────────────────────
const MONSTER_PRESETS: Array<{
  name: string; asciiArt: string[]; color: number;
  hp: number; atk: number; def: number;
  skill: string; element: Element;
}> = [
  {
    name:"고블린 전사", color:C.green, hp:1260,  atk:18, def:5,
    skill:"할퀴기", element:"none",
    asciiArt: [
      "  /---\\\\  ",
      " | O O |  ",
      "  \\---/   ",
      "    | |    ",
      "   /| |\\  "
    ]
  },
  {
    name:"오크 투사", color:C.orange, hp:2205, atk:28, def:15,
    skill:"도끼 강타", element:"none",
    asciiArt: [
      "   /|\\\\  ",
      "  / | \\\\  ",
      " |  O  | ",
      "  \\ | /  ",
      "   \\|/   "
    ]
  },
  {
    name:"스켈레톤", color:C.white, hp:1102, atk:15, def:8,
    skill:"뼈 투척", element:"none",
    asciiArt: [
      "   .-.-. ",
      "  ( o.o )",
      "   > ^ <  ",
      "  /|   |\\  ",
      "   |   |   "
    ]
  },
  {
    name:"불꽃 임프", color:C.red, hp:972,  atk:25, def:3,
    skill:"화염 브레스", element:"fire",
    asciiArt: [
      "    ^    ",
      "   /|\\\\  ",
      "  / | \\\\  ",
      "  | *|* |  ",
      "   \\|/   "
    ]
  },
  {
    name:"빙결 슬라임", color:C.cyan, hp:1369, atk:20, def:10,
    skill:"냉기 분사", element:"ice",
    asciiArt: [
      "   ~~~   ",
      "  ~ ~ ~  ",
      " ~ O O ~ ",
      "  ~ ~ ~  ",
      "   ~~~   "
    ]
  },
  {
    name:"천둥 매", color:C.blue, hp:825,  atk:30, def:4,
    skill:"번개 강타", element:"lightning",
    asciiArt: [
      "    W    ",
      "   W W   ",
      "  W O W  ",
      "   / \\\\  ",
      "  /   \\\\  "
    ]
  },
  {
    name:"다크 좀비", color:C.purple, hp:1774, atk:22, def:12,
    skill:"저주의 손", element:"curse",
    asciiArt: [
      "   [__] ",
      "  [O O] ",
      "   [>]  ",
      "  [| |] ",
      "  [___] "
    ]
  },
  {
    name:"독거미", color:C.teal, hp:1152, atk:18, def:6,
    skill:"독 주입", element:"poison",
    asciiArt: [
      "  /\\\\ /\\\\  ",
      " ( O X O ) ",
      "  \\\\ /\\\\ ",
      "   |||||   ",
      "   |||||   "
    ]
  },
];

const BOSS_PRESETS: Array<{
  name: string; asciiArt: string[]; color: number;
  hp: number; atk: number; def: number;
  skill: string; element: Element;
}> = [
  {
    name:"암흑 드래곤", color:C.pink, hp:6012, atk:55, def:25,
    skill:"암흑 브레스", element:"curse",
    asciiArt: [
      "    /^^^\\\\  ",
      "   (  O O  ) ",
      "    \\ === /  ",
      "  ~~|~|~|~~ ",
      " ~~~|~|~|~~~ "
    ]
  },
  {
    name:"지옥 골렘", color:C.orange, hp:7686, atk:45, def:40,
    skill:"지진 강타", element:"none",
    asciiArt: [
      "   [===] ",
      "  [O O] ",
      "  [===] ",
      "  [| |] ",
      "  [___] "
    ]
  },
  {
    name:"리치 군주", color:C.purple, hp:4932, atk:60, def:15,
    skill:"죽음의 손길", element:"curse",
    asciiArt: [
      "   @@@   ",
      "  @ O @  ",
      "   @@@   ",
      "  /| |\\\\  ",
      " / | | \\\\  "
    ]
  },
];

// ─────────────────────────────────────────
// 게임 상태
// ─────────────────────────────────────────
let phase: GamePhase = "title";
let partyName = "";
let partyUnits: Unit[] = [];
let enemies: Unit[] = [];
let grid: Cell[][] = [];
let battleIndex = 0;
let totalBattles = 9;
let currentUnitIdx = 0;
let allTurnUnits: Unit[] = [];
let selectedSkill: Skill | null = null;
let log: string[] = [];
let pendingRewards: string[] = [];
let inputBuffer = "";
let cursorVisible = true;
let _cursorTimer = 0;
let selectedPartyJobs: JobId[] = [];
let unitIdCounter = 0;
let floatingTexts: FloatingText[] = [];
let enemyTurnDelay = 0;
let currentEnemyTurnIdx = 0;

// ─────────────────────────────────────────
// PixiJS 컨테이너 & 텍스트 요소
// ─────────────────────────────────────────
let rootContainer: Container;
let mapContainer: Container;
let uiContainer: Container;
let floatingTextContainer: Container;
let logContainer: Container;

// 재사용 텍스트 스타일
const TS = {
  title:   new TextStyle({ fontFamily:"Russo One", fontSize:22, fill:C.gold,  letterSpacing:3 }),
  sub:     new TextStyle({ fontFamily:"Russo One", fontSize:13, fill:C.white, letterSpacing:1 }),
  label:   new TextStyle({ fontFamily:"Russo One", fontSize:11, fill:C.gray,  letterSpacing:1 }),
  log:     new TextStyle({ fontFamily:"Russo One", fontSize:10, fill:C.white, letterSpacing:0.5 }),
  logGold: new TextStyle({ fontFamily:"Russo One", fontSize:10, fill:C.gold,  letterSpacing:0.5 }),
  logRed:  new TextStyle({ fontFamily:"Russo One", fontSize:10, fill:C.red,   letterSpacing:0.5 }),
  logGrn:  new TextStyle({ fontFamily:"Russo One", fontSize:10, fill:C.hp,    letterSpacing:0.5 }),
  logBlue: new TextStyle({ fontFamily:"Russo One", fontSize:10, fill:C.blue,  letterSpacing:0.5 }),
  input:   new TextStyle({ fontFamily:"Russo One", fontSize:13, fill:C.cyan,  letterSpacing:1 }),
  hpbar:   new TextStyle({ fontFamily:"Russo One", fontSize:9,  fill:C.hp,    letterSpacing:0 }),
  mpbar:   new TextStyle({ fontFamily:"Russo One", fontSize:9,  fill:C.mp,    letterSpacing:0 }),
  sym:     new TextStyle({ fontFamily:"Russo One", fontSize:14, fill:C.white, letterSpacing:0 }),
  big:     new TextStyle({ fontFamily:"Russo One", fontSize:28, fill:C.gold,  letterSpacing:4, dropShadow:{ alpha:0.9, angle:0, blur:18, color:C.gold, distance:0 } }),
  phase:   new TextStyle({ fontFamily:"Russo One", fontSize:11, fill:C.cyan,  letterSpacing:2 }),
  damage:  new TextStyle({ fontFamily:"Russo One", fontSize:14, fill:C.red,   letterSpacing:1 }),
  heal:    new TextStyle({ fontFamily:"Russo One", fontSize:14, fill:C.hp,    letterSpacing:1 }),
  // 타이틀 화면용
  titleMain: new TextStyle({ fontFamily:"Russo One", fontSize:48, fill:C.brassGold, letterSpacing:4, dropShadow:{ alpha:0.6, angle:0.3, blur:8, color:C.shadowBrown, distance:2 } }),
  titleSub:  new TextStyle({ fontFamily:"Russo One", fontSize:14, fill:C.brassGold, letterSpacing:2 }),
  titleDesc: new TextStyle({ fontFamily:"Russo One", fontSize:11, fill:0xc9b89a, letterSpacing:1 }),
};

// ─────────────────────────────────────────
// 유틸
// ─────────────────────────────────────────
function rnd(min: number, max: number) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
function clamp(v: number, a: number, b: number) {
  return Math.max(a, Math.min(b, v));
}
function chance(p: number) {
  return Math.random() < p;
}
function addLog(msg: string) {
  log.push(msg);
  if (log.length > 80) log.shift();
}
function makeUnit(job: JobId, name: string, isPlayer: boolean, row: number, col: number): Unit {
  const d = JOB_DATA[job];
  return {
    id: `u${unitIdCounter++}`,
    name, job, symbol: d.symbol, symbolColor: d.color,
    hp: d.hp, maxHp: d.hp, mp: d.mp, maxMp: d.mp,
    atk: d.atk, def: d.def, move: d.move,
    skills: d.skills.map(s => ({ ...s })),
    isPlayer, row, col,
    statusEffects: [],
    alive: true, lv: 1,
  };
}
function makeEnemy(preset: typeof MONSTER_PRESETS[0], row: number, col: number, idx: number): Unit {
  const base: Unit = {
    id: `e${unitIdCounter++}`,
    name: preset.name,
    job: "knight" as JobId,
    symbol: preset.asciiArt[2].charAt(5) || "E",
    symbolColor: preset.color,
    hp: preset.hp, maxHp: preset.hp,
    mp: 20, maxMp: 20,
    atk: preset.atk, def: preset.def, move: 1,
    skills: [{
      id: `esk${idx}`, name: preset.skill, mpCost: 0, range: 1,
      aoe: false, element: preset.element, baseDmg: preset.atk,
      healAmt: 0, desc: preset.skill,
    }],
    isPlayer: false, row, col,
    statusEffects: [], alive: true, lv: 1,
  };
  return base;
}
function initGrid(): void {
  grid = [];
  for (let r = 0; r < 5; r++) {
    grid[r] = [];
    for (let c = 0; c < 5; c++) {
      grid[r][c] = { terrain: "normal", terrainTimer: 0 };
    }
  }
}
function getUnit(row: number, col: number): Unit | null {
  return [...partyUnits, ...enemies].find(u => u.alive && u.row === row && u.col === col) || null;
}
function isFrontJob(job: JobId) {
  return JOB_DATA[job].front;
}

// 스킬이 아군 대상인지 확인
function isAllyTargetSkill(skill: Skill): boolean {
  return (
    skill.healAmt > 0 ||
    skill.status === "shield" ||
    skill.status === "blessed"
  );
}

// 플로팅 텍스트 추가
function addFloatingText(x: number, y: number, text: string, color: number, lifetime: number = 1.2) {
  floatingTexts.push({
    text,
    x,
    y,
    color,
    age: 0,
    lifetime,
  });
}

// ─────────────────────────────────────────
// 전투 설정
// ─────────────────────────────────────────
function setupBattle(): void {
  initGrid();
  floatingTexts = [];
  enemyTurnDelay = 0;
  currentEnemyTurnIdx = 0;

  // 직업별 정렬
  const knights = partyUnits.filter(u => u.job === "knight");
  const guardians = partyUnits.filter(u => u.job === "guardian");
  const mages = partyUnits.filter(u => u.job === "mage");
  const alchemists = partyUnits.filter(u => u.job === "alchemist");
  const clerics = partyUnits.filter(u => u.job === "cleric");
  const archers = partyUnits.filter(u => u.job === "archer");

  // 전위 배치: ROW 3
  const frontUnits = [...knights, ...guardians];
  const frontPositions = [
    { row: 3, col: 2 }, // 중앙
    { row: 3, col: 1 }, // 좌측
    { row: 3, col: 3 }, // 우측
  ];
  frontUnits.forEach((u, i) => {
    if (i < frontPositions.length) {
      u.row = frontPositions[i].row;
      u.col = frontPositions[i].col;
    }
  });

  // 후위 배치: ROW 4
  // 마법사/연금술사 같은 깊이, 성직자/궁수 더 뒤
  const backUnits = [...mages, ...alchemists, ...clerics, ...archers];
  const backOrder = backUnits.sort((a, b) => {
    const depthMap: Record<JobId, number> = {
      mage: 0, alchemist: 0,      // 가장 앞
      cleric: 1, archer: 1,        // 더 뒤
      knight: 2, guardian: 2,      // 최전방 (쓰이지 않음)
    };
    return depthMap[a.job] - depthMap[b.job];
  });

  const backCols = [0, 2, 4];
  let backIdx = 0;
  backOrder.forEach(u => {
    u.row = 4;
    u.col = backCols[backIdx % backCols.length];
    backIdx++;
  });

  partyUnits.forEach(u => {
    u.alive = true;
    u.statusEffects = [];
  });

  enemies = [];
  const stageConfig = STAGE_CONFIGS[battleIndex];
  const scale = stageConfig.difficulty;
  const isBoss = stageConfig.bossThreshold === 1;
  const isElite = stageConfig.bossThreshold === 0.5;

  if (isBoss) {
    const preset = BOSS_PRESETS[rnd(0, BOSS_PRESETS.length - 1)];
    const boss = makeEnemy(
      { ...preset, hp: Math.floor(preset.hp * scale), atk: Math.floor(preset.atk * scale) },
      0, 2, 0
    );
    enemies.push(boss);
    addLog(`⚠️  ${stageConfig.name} — 보스 등장: ${boss.name}!`);
  } else if (isElite) {
    const preset = MONSTER_PRESETS[rnd(0, MONSTER_PRESETS.length - 1)];
    const elite = makeEnemy(
      { ...preset, hp: Math.floor(preset.hp * scale), atk: Math.floor(preset.atk * scale) },
      0, 2, 0
    );
    enemies.push(elite);
    const companion = MONSTER_PRESETS[rnd(0, MONSTER_PRESETS.length - 1)];
    const comp = makeEnemy(
      { ...companion, hp: Math.floor(companion.hp * scale * 0.7), atk: Math.floor(companion.atk * scale * 0.8) },
      1, 2, 1
    );
    enemies.push(comp);
    addLog(`⚡ ${stageConfig.name} — 엘리트 ${elite.name}와 ${comp.name}!`);
  } else {
    const count = stageConfig.enemyCount(false);
    const used: number[] = [];
    const enemyCols = [0, 2, 4, 1, 3];
    for (let i = 0; i < count; i++) {
      let pIdx: number;
      do { pIdx = rnd(0, MONSTER_PRESETS.length - 1); } while (used.includes(pIdx) && used.length < MONSTER_PRESETS.length);
      used.push(pIdx);
      const p = MONSTER_PRESETS[pIdx];
      const e = makeEnemy(
        { ...p, hp: Math.floor(p.hp * scale), atk: Math.floor(p.atk * scale) },
        rnd(0, 1), enemyCols[i], i
      );
      enemies.push(e);
    }
    addLog(`${stageConfig.name} (${battleIndex + 1}/9) — 적 ${count}마리 등장!`);
  }

  buildTurnOrder();
  phase = "battle_start";
}

function buildTurnOrder(): void {
  allTurnUnits = [...partyUnits.filter(u => u.alive), ...enemies.filter(u => u.alive)];
  currentUnitIdx = 0;
}

function nextTurn(): void {
  let tries = 0;
  do {
    currentUnitIdx = (currentUnitIdx + 1) % allTurnUnits.length;
    tries++;
    if (tries > allTurnUnits.length) {
      buildTurnOrder();
      break;
    }
  } while (!allTurnUnits[currentUnitIdx]?.alive);

  const cur = allTurnUnits[currentUnitIdx];
  if (!cur || !cur.alive) { buildTurnOrder(); return; }

  const cell = grid[cur.row][cur.col];
  if (cell.terrain === "fire" && cur.alive) {
    const dmg = 8;
    const oldHp = cur.hp;
    cur.hp = clamp(cur.hp - dmg, 0, cur.maxHp);
    addFloatingText(MAP_OX + cur.col * CELL + CELL / 2, MAP_OY + cur.row * CELL + 10, `-${dmg}`, C.red);
    addLog(`🔥 ${cur.name} 불 지형 피해 -${dmg}`);
    if (cur.hp <= 0) { cur.alive = false; cur.hp = 0; }
  }
  if (cell.terrain === "ice" && cur.alive) {
    if (!cur.statusEffects.includes("freeze") && chance(0.25)) {
      cur.statusEffects.push("freeze");
      addLog(`❄️  ${cur.name} 빙결 지형 빙결!`);
    }
  }

  if (cur.statusEffects.includes("poison") && cur.alive) {
    const dmg = Math.max(5, Math.floor(cur.maxHp * 0.08));
    cur.hp = clamp(cur.hp - dmg, 0, cur.maxHp);
    addFloatingText(MAP_OX + cur.col * CELL + CELL / 2, MAP_OY + cur.row * CELL + 10, `-${dmg}`, C.red);
    addLog(`☠️  ${cur.name} 독 피해 -${dmg}`);
    if (cur.hp <= 0) { cur.alive = false; cur.hp = 0; }
  }
  if (cur.statusEffects.includes("burn") && cur.alive) {
    const dmg = Math.max(4, Math.floor(cur.maxHp * 0.06));
    cur.hp = clamp(cur.hp - dmg, 0, cur.maxHp);
    addFloatingText(MAP_OX + cur.col * CELL + CELL / 2, MAP_OY + cur.row * CELL + 10, `-${dmg}`, C.red);
    addLog(`🔥 ${cur.name} 화상 피해 -${dmg}`);
    if (cur.hp <= 0) { cur.alive = false; cur.hp = 0; }
  }

  for (let r = 0; r < 5; r++) {
    for (let cc = 0; cc < 5; cc++) {
      if (grid[r][cc].terrainTimer > 0) {
        grid[r][cc].terrainTimer--;
        if (grid[r][cc].terrainTimer <= 0) grid[r][cc].terrain = "normal";
      }
    }
  }

  if (!cur.alive) { nextTurn(); return; }

  if (!cur.isPlayer) {
    phase = "enemy_turn";
    enemyTurnDelay = 0;
    currentEnemyTurnIdx = 0;
    doNextEnemyTurn();
  } else {
    phase = "player_turn";
    addLog(``);
    addLog(`── ${cur.name}의 턴 ──`);
  }
}

function doNextEnemyTurn(): void {
  const enemies_alive = enemies.filter(e => e.alive);
  if (currentEnemyTurnIdx >= enemies_alive.length) {
    // 모든 적의 턴 종료
    if (!checkBattleEnd()) nextTurn();
    return;
  }

  const enemy = enemies_alive[currentEnemyTurnIdx];
  currentEnemyTurnIdx++;

  addLog(``);
  addLog(`👾 ${enemy.name}의 턴!`);
  
  if (enemy.statusEffects.includes("freeze") || enemy.statusEffects.includes("stun")) {
    addLog(`${enemy.name} 행동 불능! (${enemy.statusEffects.join("/")})`);
    enemy.statusEffects = enemy.statusEffects.filter(s => s !== "freeze" && s !== "stun");
    enemyTurnDelay = 1000; // 1초 지연
    return;
  }

  const alive = partyUnits.filter(u => u.alive);
  if (alive.length === 0) { checkBattleEnd(); return; }
  const target = alive[rnd(0, alive.length - 1)];
  const sk = enemy.skills[0];
  const result = applySkill(enemy, target, sk);
  addLog(`  → ${target.name}에게 [${sk.name}] 사용! ${result}`);

  if (sk.element === "fire" && chance(0.4)) {
    grid[target.row][target.col].terrain = "fire";
    grid[target.row][target.col].terrainTimer = 3;
    addLog(`  🔥 ${target.name} 위치에 불 지형 발생!`);
  }

  enemyTurnDelay = 1000; // 1초 지연
}

function checkBattleEnd(): boolean {
  const playerAlive = partyUnits.some(u => u.alive);
  const enemyAlive  = enemies.some(u => u.alive);
  if (!playerAlive) {
    phase = "gameover";
    addLog("💀 파티 전멸... 런 종료.");
    return true;
  }
  if (!enemyAlive) {
    phase = "battle_result";
    addLog("✨ 전투 승리!");
    return true;
  }
  return false;
}

function applySkill(caster: Unit, target: Unit, skill: Skill): string {
  if (skill.baseDmg > 0) {
    const isMagic = skill.element !== "none";
    let raw = skill.baseDmg + (isMagic ? caster.atk * 0.5 : caster.atk * 0.8);
    if (caster.statusEffects.includes("blessed")) raw *= 1.3;

    // 보호막이 있으면 피해 60% 감소
    if (target.statusEffects.includes("shield")) {
      raw *= 0.4;
      target.statusEffects = target.statusEffects.filter(s => s !== "shield");
      addLog(`  🛡️ ${target.name}의 보호막이 피해를 흡수했습니다!`);
    }

    const dmg = Math.max(1, Math.floor(raw - target.def * 0.3));
    target.hp = clamp(target.hp - dmg, 0, target.maxHp);
    addFloatingText(MAP_OX + target.col * CELL + CELL / 2, MAP_OY + target.row * CELL + 10, `-${dmg}`, C.red);
    if (target.hp <= 0) { target.alive = false; target.hp = 0; }

    if (skill.status && skill.statusChance && chance(skill.statusChance)) {
      if (!target.statusEffects.includes(skill.status)) {
        target.statusEffects.push(skill.status);
        addLog(`  → ${target.name}에게 [${statusLabel(skill.status)}] 부여!`);
      }
    }

    if (skill.element === "fire" && chance(0.35)) {
      grid[target.row][target.col].terrain = "fire";
      grid[target.row][target.col].terrainTimer = 3;
      addLog(`  🔥 불 지형 생성!`);
    } else if (skill.element === "ice" && chance(0.35)) {
      grid[target.row][target.col].terrain = "ice";
      grid[target.row][target.col].terrainTimer = 3;
      addLog(`  ❄️  빙결 지형 생성!`);
    } else if (skill.element === "lightning" && chance(0.35)) {
      grid[target.row][target.col].terrain = "electric";
      grid[target.row][target.col].terrainTimer = 2;
      addLog(`  ⚡ 전기 지형 생성!`);
    }

    return `-${dmg} HP`;
  } else if (skill.healAmt > 0) {
    const heal = Math.floor(skill.healAmt + caster.atk * 0.2);
    target.hp = clamp(target.hp + heal, 0, target.maxHp);
    addFloatingText(MAP_OX + target.col * CELL + CELL / 2, MAP_OY + target.row * CELL + 10, `+${heal}`, C.hp);
    if (skill.status && skill.statusChance && chance(skill.statusChance)) {
      if (!target.statusEffects.includes(skill.status)) {
        target.statusEffects.push(skill.status);
      }
    }
    return `+${heal} HP 회복`;
  } else if (skill.status && skill.statusChance && chance(skill.statusChance)) {
    if (!target.statusEffects.includes(skill.status)) {
      target.statusEffects.push(skill.status);
    }
    return `${statusLabel(skill.status)} 부여`;
  }
  return "효과 없음";
}

function applySkillAoe(caster: Unit, skill: Skill, targets: Unit[]): void {
  targets.forEach(t => {
    const res = applySkill(caster, t, skill);
    addLog(`  💥 ${t.name}: ${res}`);
  });
}

function statusLabel(s: StatusEffect): string {
  const m: Record<StatusEffect, string> = {
    burn:"화상", freeze:"빙결", stun:"기절", poison:"독", shield:"보호막", blessed:"축복"
  };
  return m[s] || s;
}

function getRewardOptions(): string[] {
  const opts: string[] = [];
  const livingParty = partyUnits.filter(u => u.alive);
  if (livingParty.length > 0) {
    const unit = livingParty[rnd(0, livingParty.length - 1)];
    const sk = unit.skills[rnd(0, unit.skills.length - 1)];
    opts.push(`${unit.name}의 [${sk.name}] 강화 (피해+20%, MP소모-2)`);
  }
  opts.push("HP 포션 (전원 HP 40 회복)");
  opts.push("MP 포션 (전원 MP 25 회복)");
  if (chance(0.5)) opts.push("신비한 유물 (무작위 강화)");
  return opts;
}

// ─────────────────────────────────────────
// 렌더링
// ─────────────────────────────────────────
const CELL = 54;
const MAP_OX = 10;
const MAP_OY = 55;
const MAP_W = 5 * CELL + 10;
const MAP_H = 5 * CELL + 10;

const LOG_X = MAP_OX + MAP_W + 12;
const LOG_Y = 10;
const LOG_W = CANVAS_WIDTH - LOG_X - 10;
const LOG_H = CANVAS_HEIGHT - 90;
const INPUT_Y = CANVAS_HEIGHT - 70;

let mapCells: Graphics[] = [];
let mapSymbols: Text[] = [];
let logTexts: Text[] = [];
let inputBg: Graphics;
let inputText: Text;
let titleContainer: Container;
let phaseText: Text;
let partyInfoContainer: Container;
let turnIndicator: Text;
let screenFlash: Graphics;
let rewardOverlay: Container;

function buildStaticUI(): void {
  const bg = new Graphics();
  bg.rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  bg.fill(C.bg);
  uiContainer.addChild(bg);

  const mapBorder = new Graphics();
  mapBorder.rect(MAP_OX - 4, MAP_OY - 24, MAP_W + 8, MAP_H + 28);
  mapBorder.fill(C.panel);
  mapBorder.rect(MAP_OX - 5, MAP_OY - 25, MAP_W + 10, MAP_H + 30);
  mapBorder.stroke({ color: C.border, width: 1.5 });
  uiContainer.addChild(mapBorder);

  const mapLabel = new Text({ text: "전술 맵", style: TS.label });
  mapLabel.x = MAP_OX;
  mapLabel.y = MAP_OY - 21;
  uiContainer.addChild(mapLabel);

  const logBg = new Graphics();
  logBg.rect(LOG_X - 4, LOG_Y, LOG_W + 8, LOG_H + 4);
  logBg.fill(C.panel);
  logBg.rect(LOG_X - 5, LOG_Y - 1, LOG_W + 10, LOG_H + 6);
  logBg.stroke({ color: C.border, width: 1.5 });
  uiContainer.addChild(logBg);

  const logLabel = new Text({ text: "전투 로그", style: TS.label });
  logLabel.x = LOG_X;
  logLabel.y = LOG_Y + 3;
  uiContainer.addChild(logLabel);

  inputBg = new Graphics();
  uiContainer.addChild(inputBg);

  inputText = new Text({ text: "", style: TS.input });
  inputText.x = 16;
  inputText.y = INPUT_Y + 10;
  uiContainer.addChild(inputText);

  phaseText = new Text({ text: "", style: TS.phase });
  phaseText.x = 16;
  phaseText.y = 8;
  uiContainer.addChild(phaseText);

  turnIndicator = new Text({ text: "", style: TS.sub });
  turnIndicator.x = MAP_OX;
  turnIndicator.y = MAP_OY + MAP_H + 8;
  uiContainer.addChild(turnIndicator);

  screenFlash = new Graphics();
  screenFlash.rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  screenFlash.fill(0xff0000);
  screenFlash.alpha = 0;
  app.stage.addChild(screenFlash);

  for (let r = 0; r < 5; r++) {
    for (let c = 0; c < 5; c++) {
      const cell = new Graphics();
      cell.x = MAP_OX + c * CELL;
      cell.y = MAP_OY + r * CELL;
      mapCells.push(cell);
      mapContainer.addChild(cell);

      const sym = new Text({ text: "", style: TS.sym });
      sym.x = MAP_OX + c * CELL + CELL / 2;
      sym.y = MAP_OY + r * CELL + CELL / 2 - 8;
      sym.anchor.set(0.5, 0);
      mapSymbols.push(sym);
      mapContainer.addChild(sym);
    }
  }

  for (let i = 0; i < 20; i++) {
    const lt = new Text({ text: "", style: TS.log });
    lt.x = LOG_X + 2;
    lt.y = LOG_Y + 16 + i * 18;
    logTexts.push(lt);
    uiContainer.addChild(lt);
  }

  partyInfoContainer = new Container();
  uiContainer.addChild(partyInfoContainer);

  titleContainer = new Container();
  app.stage.addChild(titleContainer);

  // 보상 오버레이 컨테이너 (항상 최상단)
  rewardOverlay = new Container();
  rewardOverlay.visible = false;
  app.stage.addChild(rewardOverlay);

  // 플로팅 텍스트 컨테이너
  floatingTextContainer = new Container();
  app.stage.addChild(floatingTextContainer);
}

let _lastPhase: GamePhase | null = null;
let _lastLogLen = 0;
let _lastInputBuffer = "";
let _lastAliveKey = "";
let _flashAlpha = 0;
let _flashColor = 0;

function flash(color: number, intensity: number = 0.35) {
  _flashColor = color;
  _flashAlpha = intensity;
}

function renderFloatingTexts(): void {
  // Update 및 제거
  floatingTexts = floatingTexts.filter(ft => {
    ft.age += 1/60; // 약 60fps 기준
    return ft.age < ft.lifetime;
  });

  // 렌더링
  while (floatingTextContainer.children.length > 0) {
    const ch = floatingTextContainer.children[0];
    floatingTextContainer.removeChild(ch);
    ch.destroy();
  }

  floatingTexts.forEach(ft => {
    const progress = ft.age / ft.lifetime;
    const alpha = Math.max(0, 1 - progress);
    const offsetY = -20 * progress;

    const style = ft.color === C.red ? TS.damage : TS.heal;
    const text = new Text({ text: ft.text, style });
    text.x = ft.x;
    text.y = ft.y + offsetY;
    text.anchor.set(0.5, 0);
    text.alpha = alpha;
    floatingTextContainer.addChild(text);
  });
}

function renderFrame(): void {
  if (_flashAlpha > 0) {
    screenFlash.clear();
    screenFlash.rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
    screenFlash.fill(_flashColor);
    screenFlash.alpha = _flashAlpha;
    _flashAlpha = Math.max(0, _flashAlpha - 0.04);
  } else {
    screenFlash.alpha = 0;
  }

  renderFloatingTexts();

  // 적 턴 지연 처리
  if (enemyTurnDelay > 0) {
    enemyTurnDelay -= 1000 / 60; // 프레임 기반 감소
    if (enemyTurnDelay <= 0) {
      enemyTurnDelay = 0;
      if (phase === "enemy_turn" && !checkBattleEnd()) {
        doNextEnemyTurn();
      }
    }
  }

  const aliveKey = [...partyUnits, ...enemies].map(u => `${u.id}:${u.hp}:${u.alive}:${u.row}:${u.col}:${u.statusEffects.join(",")}`).join("|");
  const dirty = phase !== _lastPhase || log.length !== _lastLogLen
    || inputBuffer !== _lastInputBuffer || aliveKey !== _lastAliveKey;
  if (!dirty) return;
  _lastPhase = phase;
  _lastLogLen = log.length;
  _lastInputBuffer = inputBuffer;
  _lastAliveKey = aliveKey;

  while (titleContainer.children.length > 0) {
    const ch = titleContainer.children[0];
    titleContainer.removeChild(ch);
    ch.destroy({ children: true });
  }

  // 보상 오버레이 초기화
  while (rewardOverlay.children.length > 0) {
    const ch = rewardOverlay.children[0];
    rewardOverlay.removeChild(ch);
    ch.destroy({ children: true });
  }
  rewardOverlay.visible = false;

  if (phase === "title" || phase === "name_input" || phase === "party_select") {
    renderTitleScreen();
    mapContainer.visible = false;
    uiContainer.visible = false;
  } else if (phase === "gameover" || phase === "victory") {
    renderEndScreen();
    mapContainer.visible = false;
    uiContainer.visible = false;
  } else if (phase === "reward") {
    renderRewardOverlay();
    rewardOverlay.visible = true;
  } else {
    mapContainer.visible = true;
    uiContainer.visible = true;
    renderBattleScreen();
  }
}

function renderTitleScreen(): void {
  const c = titleContainer;

  // ── 배경: 캠프 장면 ──
  const bg = new Graphics();
  bg.rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  bg.fill(C.campBg);
  c.addChild(bg);

  // 밤하늘 그라데이션 표현 (위쪽 더 어두운 그라데이션)
  for (let i = 0; i < CANVAS_HEIGHT / 30; i++) {
    const grad = new Graphics();
    grad.rect(0, i * 30, CANVAS_WIDTH, 30);
    const alpha = Math.pow(i / (CANVAS_HEIGHT / 30), 1.2);
    const col = 0x0a0a14;
    grad.fill(col);
    grad.alpha = Math.min(1, 0.3 + alpha * 0.5);
    c.addChild(grad);
  }

  // ── 멀리 있는 던전/성벽 실루엣 (희미하게) ──
  const silhouette = new Graphics();
  silhouette.rect(0, CANVAS_HEIGHT * 0.55, CANVAS_WIDTH, CANVAS_HEIGHT * 0.45);
  silhouette.fill(0x05050a);
  silhouette.alpha = 0.4;
  c.addChild(silhouette);

  // 던전 입구 삼각형 실루엣
  const dungeon = new Graphics();
  dungeon.poly([
    [CANVAS_WIDTH * 0.25, CANVAS_HEIGHT * 0.55],
    [CANVAS_WIDTH * 0.35, CANVAS_HEIGHT * 0.75],
    [CANVAS_WIDTH * 0.15, CANVAS_HEIGHT * 0.75],
  ]);
  dungeon.fill(0x030308);
  dungeon.alpha = 0.25;
  c.addChild(dungeon);

  const dungeon2 = new Graphics();
  dungeon2.poly([
    [CANVAS_WIDTH * 0.7, CANVAS_HEIGHT * 0.6],
    [CANVAS_WIDTH * 0.78, CANVAS_HEIGHT * 0.8],
    [CANVAS_WIDTH * 0.62, CANVAS_HEIGHT * 0.8],
  ]);
  dungeon2.fill(0x030308);
  dungeon2.alpha = 0.2;
  c.addChild(dungeon2);

  // ── 모닥불 (화면 하단 중앙) ──
  // 불의 기본 원형
  const fireCore = new Graphics();
  fireCore.circle(CANVAS_WIDTH / 2, CANVAS_HEIGHT * 0.75, 20);
  fireCore.fill(0xff6600);
  c.addChild(fireCore);

  const fireInner = new Graphics();
  fireInner.circle(CANVAS_WIDTH / 2, CANVAS_HEIGHT * 0.75, 12);
  fireInner.fill(0xffaa44);
  c.addChild(fireInner);

  // 불의 주변 빛
  const fireGlow = new Graphics();
  fireGlow.circle(CANVAS_WIDTH / 2, CANVAS_HEIGHT * 0.75, 35);
  fireGlow.fill(0xff6600);
  fireGlow.alpha = 0.15;
  c.addChild(fireGlow);

  // ── 별들 (가끔) ──
  if (phase === "title") {
    for (let i = 0; i < 20; i++) {
      const star = new Graphics();
      star.circle(0, 0, 1);
      star.fill(C.white);
      star.x = rnd(0, CANVAS_WIDTH);
      star.y = rnd(0, CANVAS_HEIGHT * 0.5);
      star.alpha = Math.random() * 0.4 + 0.2;
      c.addChild(star);
    }
  }

  if (phase === "title") {
    // ── 천 배너 (제목 뒤) ──
    const bannerW = 360;
    const bannerH = 80;
    const bannerX = (CANVAS_WIDTH - bannerW) / 2;
    const bannerY = 100;

    // 배너 메인 (직사각형)
    const banner = new Graphics();
    banner.rect(bannerX, bannerY, bannerW, bannerH);
    banner.fill(C.bannerBurgundy);
    c.addChild(banner);

    // 배너 테두리 (약간의 질감)
    const bannerEdge = new Graphics();
    bannerEdge.rect(bannerX, bannerY, bannerW, bannerH);
    bannerEdge.stroke({ color: 0x3d1a2d, width: 2 });
    c.addChild(bannerEdge);

    // 배너 아래쪽 약간의 어두운 그림자 (천의 주름)
    const bannerShadow = new Graphics();
    bannerShadow.rect(bannerX, bannerY + bannerH - 8, bannerW, 8);
    bannerShadow.fill(0x3d1a2d);
    bannerShadow.alpha = 0.6;
    c.addChild(bannerShadow);

    // ── 제목 "Adventurers of Fate" ──
    const title = new Text({ text: "Adventurers of Fate", style: TS.titleMain });
    title.anchor.set(0.5);
    title.x = CANVAS_WIDTH / 2;
    title.y = bannerY + bannerH / 2 - 8;
    c.addChild(title);

    // ── 모닥불 빛 반사 (제목 하단) ──
    const fireReflect = new Graphics();
    fireReflect.rect(bannerX + 10, bannerY + bannerH - 4, bannerW - 20, 4);
    fireReflect.fill(0xffaa44);
    fireReflect.alpha = 0.08;
    c.addChild(fireReflect);

    // ── 부제 "파티 기반 전술 RPG" ──
    const subtitle = new Text({ text: "파티 기반 전술 RPG", style: TS.titleSub });
    subtitle.anchor.set(0.5);
    subtitle.x = CANVAS_WIDTH / 2;
    subtitle.y = 200;
    c.addChild(subtitle);

    // ── 시작 문구 (점멸 효과) ──
    const prompt = new Text({ text: "Enter 키를 눌러 여정을 시작하세요",
      style: new TextStyle({ fontFamily:"Russo One", fontSize:12, fill:0xc9b89a, letterSpacing:1 }) });
    prompt.anchor.set(0.5);
    prompt.x = CANVAS_WIDTH / 2;
    prompt.y = CANVAS_HEIGHT - 100;
    prompt.alpha = cursorVisible ? 1 : 0.5;
    c.addChild(prompt);

  } else if (phase === "name_input") {
    drawInputPanel(c,
      "파티 이름을 입력하세요",
      [
        "예시: 붉은 여명단 / 폭풍의 칼날 / 달빛 용병대",
        "이름 입력 후 Enter",
      ],
      `▶  ${inputBuffer}${cursorVisible ? "_" : " "}`
    );

  } else if (phase === "party_select") {
    const header = new Text({ text: `「${partyName}」 파티 결성`, style: TS.title });
    header.anchor.set(0.5);
    header.x = CANVAS_WIDTH / 2;
    header.y = 38;
    c.addChild(header);

    const bg2 = new Graphics();
    bg2.rect(30, 65, CANVAS_WIDTH - 60, CANVAS_HEIGHT - 120);
    bg2.fill(C.panel);
    bg2.rect(29, 64, CANVAS_WIDTH - 58, CANVAS_HEIGHT - 118);
    bg2.stroke({ color: C.border, width: 1.5 });
    c.addChild(bg2);

    const inst = new Text({ text: "2~3명을 선택하세요 (번호 입력, 예: 1 3 4  →  Enter)", style: new TextStyle({ fontFamily:"Russo One", fontSize:11, fill:C.cyan, letterSpacing:1 }) });
    inst.anchor.set(0.5);
    inst.x = CANVAS_WIDTH / 2;
    inst.y = 78;
    c.addChild(inst);

    const jobs: JobId[] = ["knight", "guardian", "mage", "cleric", "archer", "alchemist"];
    jobs.forEach((job, idx) => {
      const d = JOB_DATA[job];
      const selected = selectedPartyJobs.includes(job);
      const bx = 42 + (idx % 3) * 244;
      const by = 100 + Math.floor(idx / 3) * 160;

      const card = new Graphics();
      card.rect(bx, by, 230, 145);
      card.fill(selected ? 0x1a2a1a : C.darkgray);
      card.rect(bx, by, 230, 145);
      card.stroke({ color: selected ? C.green : C.border, width: selected ? 2 : 1 });
      c.addChild(card);

      const num = new Text({ text: `${idx + 1}`, style: new TextStyle({ fontFamily:"Russo One", fontSize:14, fill: selected ? C.gold : C.gray, letterSpacing:0 }) });
      num.x = bx + 8;
      num.y = by + 6;
      c.addChild(num);

      const sym = new Text({ text: d.symbol, style: new TextStyle({ fontFamily:"Russo One", fontSize:28, fill: d.color, letterSpacing:0 }) });
      sym.x = bx + 100;
      sym.y = by + 8;
      sym.anchor.set(0.5, 0);
      c.addChild(sym);

      const name = new Text({ text: d.label, style: new TextStyle({ fontFamily:"Russo One", fontSize:13, fill: selected ? C.green : C.white, letterSpacing:1 }) });
      name.anchor.set(0.5);
      name.x = bx + 115;
      name.y = by + 46;
      c.addChild(name);

      const statsStr = `HP:${d.hp} MP:${d.mp} 공:${d.atk} 방:${d.def}`;
      const stats = new Text({ text: statsStr, style: new TextStyle({ fontFamily:"Russo One", fontSize:9, fill:C.gray, letterSpacing:0 }) });
      stats.anchor.set(0.5);
      stats.x = bx + 115;
      stats.y = by + 64;
      c.addChild(stats);

      d.skills.forEach((sk, si) => {
        const st = new Text({ text: `• ${sk.name}`, style: new TextStyle({ fontFamily:"Russo One", fontSize:9, fill:C.cyan, letterSpacing:0 }) });
        st.x = bx + 10;
        st.y = by + 82 + si * 16;
        c.addChild(st);
      });

      if (selected) {
        const chk = new Text({ text: "✔ 선택됨", style: new TextStyle({ fontFamily:"Russo One", fontSize:10, fill:C.green, letterSpacing:0 }) });
        chk.anchor.set(0.5);
        chk.x = bx + 115;
        chk.y = by + 128;
        c.addChild(chk);
      }
    });

    const selInfo = new Text({
      text: selectedPartyJobs.length > 0
        ? `선택: ${selectedPartyJobs.map(j => JOB_DATA[j].label).join(" / ")}  (${selectedPartyJobs.length}/3)`
        : "직업을 선택하세요",
      style: new TextStyle({ fontFamily:"Russo One", fontSize:12, fill:C.gold, letterSpacing:1 })
    });
    selInfo.anchor.set(0.5);
    selInfo.x = CANVAS_WIDTH / 2;
    selInfo.y = CANVAS_HEIGHT - 55;
    c.addChild(selInfo);

    const cursor = new Text({
      text: `▶ ${inputBuffer}${cursorVisible ? "_" : " "}`,
      style: TS.input
    });
    cursor.anchor.set(0.5);
    cursor.x = CANVAS_WIDTH / 2;
    cursor.y = CANVAS_HEIGHT - 30;
    c.addChild(cursor);
  }
}

function drawInputPanel(c: Container, title: string, hints: string[], inputDisplay: string): void {
  const bg2 = new Graphics();
  bg2.rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  bg2.fill(C.bg);
  c.addChild(bg2);

  const t = new Text({ text: title, style: TS.title });
  t.anchor.set(0.5);
  t.x = CANVAS_WIDTH / 2;
  t.y = 180;
  c.addChild(t);

  hints.forEach((h, i) => {
    const ht = new Text({ text: h, style: TS.label });
    ht.anchor.set(0.5);
    ht.x = CANVAS_WIDTH / 2;
    ht.y = 230 + i * 22;
    c.addChild(ht);
  });

  const inp = new Text({ text: inputDisplay, style: TS.input });
  inp.anchor.set(0.5);
  inp.x = CANVAS_WIDTH / 2;
  inp.y = 310;
  c.addChild(inp);
}

function renderEndScreen(): void {
  const c = titleContainer;
  const bg = new Graphics();
  bg.rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  bg.fill(C.bg);
  c.addChild(bg);

  if (phase === "gameover") {
    const t = new Text({ text: "GAME OVER",
      style: new TextStyle({ fontFamily:"Russo One", fontSize:36, fill:C.red, letterSpacing:6, dropShadow:{ alpha:0.9, angle:0, blur:20, color:C.red, distance:0 } }) });
    t.anchor.set(0.5);
    t.x = CANVAS_WIDTH / 2;
    t.y = 180;
    c.addChild(t);

    const s = new Text({ text: "파티가 전멸했습니다", style: new TextStyle({ fontFamily:"Russo One", fontSize:16, fill:C.gray, letterSpacing:2 }) });
    s.anchor.set(0.5);
    s.x = CANVAS_WIDTH / 2;
    s.y = 240;
    c.addChild(s);
  } else {
    const t = new Text({ text: "VICTORY!",
      style: new TextStyle({ fontFamily:"Russo One", fontSize:36, fill:C.gold, letterSpacing:6, dropShadow:{ alpha:0.9, angle:0, blur:20, color:C.gold, distance:0 } }) });
    t.anchor.set(0.5);
    t.x = CANVAS_WIDTH / 2;
    t.y = 180;
    c.addChild(t);

    const s = new Text({ text: "모든 전투를 돌파했습니다!", style: new TextStyle({ fontFamily:"Russo One", fontSize:16, fill:C.cyan, letterSpacing:2 }) });
    s.anchor.set(0.5);
    s.x = CANVAS_WIDTH / 2;
    s.y = 240;
    c.addChild(s);
  }

  const restart = new Text({ text: "Enter 키로 처음으로 돌아가기",
    style: new TextStyle({ fontFamily:"Russo One", fontSize:13, fill:C.gold, letterSpacing:2 }) });
  restart.anchor.set(0.5);
  restart.x = CANVAS_WIDTH / 2;
  restart.y = 310;
  c.addChild(restart);
}

// ─────────────────────────────────────────
// 보상 오버레이 (전체 화면, 맵 위)
// ─────────────────────────────────────────
function renderRewardOverlay(): void {
  const c = rewardOverlay;

  const bg = new Graphics();
  bg.rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  bg.fill(C.bg);
  c.addChild(bg);

  for (let i = 0; i < 30; i++) {
    const star = new Graphics();
    star.circle(0, 0, rnd(1, 2));
    star.fill(C.gold);
    star.x = rnd(0, CANVAS_WIDTH);
    star.y = rnd(0, CANVAS_HEIGHT);
    star.alpha = Math.random() * 0.4 + 0.1;
    c.addChild(star);
  }

  const rx = 60;
  const ry = 60;
  const rw = CANVAS_WIDTH - 120;
  const rh = CANVAS_HEIGHT - 120;

  const panel = new Graphics();
  panel.rect(rx, ry, rw, rh);
  panel.fill(C.panel);
  panel.rect(rx - 1, ry - 1, rw + 2, rh + 2);
  panel.stroke({ color: C.gold, width: 2 });
  c.addChild(panel);

  const rTitle = new Text({ text: "✦ 전투 승리 — 보상 선택 ✦",
    style: new TextStyle({ fontFamily:"Russo One", fontSize:20, fill:C.gold, letterSpacing:3,
      dropShadow:{ alpha:0.8, angle:0, blur:12, color:C.gold, distance:0 } }) });
  rTitle.anchor.set(0.5);
  rTitle.x = CANVAS_WIDTH / 2;
  rTitle.y = ry + 28;
  c.addChild(rTitle);

  const line = new Graphics();
  line.rect(rx + 20, ry + 52, rw - 40, 1);
  line.fill(C.border);
  c.addChild(line);

  const partyLabel = new Text({ text: "현재 파티 상태", style: TS.label });
  partyLabel.x = rx + 20;
  partyLabel.y = ry + 62;
  c.addChild(partyLabel);

  partyUnits.forEach((u, i) => {
    const hpR = u.hp / u.maxHp;
    const uText = new Text({
      text: `${u.name}  HP:${u.hp}/${u.maxHp}  MP:${u.mp}/${u.maxMp}`,
      style: new TextStyle({ fontFamily:"Russo One", fontSize:10,
        fill: u.alive ? (hpR > 0.5 ? C.hp : hpR > 0.25 ? C.orange : C.red) : C.gray,
        letterSpacing:0 })
    });
    uText.x = rx + 20 + i * Math.floor((rw - 40) / Math.max(partyUnits.length, 1));
    uText.y = ry + 78;
    c.addChild(uText);
  });

  const line2 = new Graphics();
  line2.rect(rx + 20, ry + 96, rw - 40, 1);
  line2.fill(C.border);
  c.addChild(line2);

  const chooseLabel = new Text({ text: "번호를 입력하여 보상을 선택하세요:",
    style: new TextStyle({ fontFamily:"Russo One", fontSize:12, fill:C.cyan, letterSpacing:1 }) });
  chooseLabel.x = rx + 20;
  chooseLabel.y = ry + 106;
  c.addChild(chooseLabel);

  pendingRewards.forEach((reward, i) => {
    const cardY = ry + 132 + i * 62;

    const cardBg = new Graphics();
    cardBg.rect(rx + 16, cardY, rw - 32, 54);
    cardBg.fill(C.darkgray);
    cardBg.rect(rx + 15, cardY - 1, rw - 30, 56);
    cardBg.stroke({ color: C.border, width: 1 });
    c.addChild(cardBg);

    const numCircle = new Graphics();
    numCircle.circle(rx + 36, cardY + 27, 14);
    numCircle.fill(C.gold);
    c.addChild(numCircle);

    const numT = new Text({ text: `${i + 1}`,
      style: new TextStyle({ fontFamily:"Russo One", fontSize:14, fill:C.bg, letterSpacing:0 }) });
    numT.anchor.set(0.5);
    numT.x = rx + 36;
    numT.y = cardY + 19;
    c.addChild(numT);

    const rewardT = new Text({ text: reward,
      style: new TextStyle({ fontFamily:"Russo One", fontSize:12, fill:C.white, letterSpacing:0.5 }) });
    rewardT.x = rx + 58;
    rewardT.y = cardY + 18;
    c.addChild(rewardT);
  });

  const inputBgR = new Graphics();
  inputBgR.rect(0, CANVAS_HEIGHT - 70, CANVAS_WIDTH, 70);
  inputBgR.fill(C.panel);
  inputBgR.rect(0, CANVAS_HEIGHT - 71, CANVAS_WIDTH, 1);
  inputBgR.stroke({ color: C.border, width: 1 });
  c.addChild(inputBgR);

  const inputT = new Text({
    text: `보상 번호 입력 (1~${pendingRewards.length}):  ${inputBuffer}${cursorVisible ? "│" : " "}`,
    style: TS.input
  });
  inputT.x = 16;
  inputT.y = CANVAS_HEIGHT - 55;
  c.addChild(inputT);
}

function renderBattleScreen(): void {
  renderMap();
  renderLog();
  renderInputBar();
  renderPhaseInfo();
  renderPartyInfo();
}

function terrainColor(t: Cell["terrain"]): number {
  if (t === "fire")     return 0x3a1500;
  if (t === "ice")      return 0x001a3a;
  if (t === "electric") return 0x1a1a00;
  return C.darkgray;
}
function terrainBorder(t: Cell["terrain"]): number {
  if (t === "fire")     return C.orange;
  if (t === "ice")      return C.cyan;
  if (t === "electric") return C.gold;
  return C.border;
}

function renderMap(): void {
  const cur = allTurnUnits[currentUnitIdx];
  for (let r = 0; r < 5; r++) {
    for (let c = 0; c < 5; c++) {
      const idx = r * 5 + c;
      const cell = grid[r] ? grid[r][c] : null;
      const terrain = cell ? cell.terrain : "normal";
      const g = mapCells[idx];
      const sym = mapSymbols[idx];

      g.clear();
      g.rect(2, 2, CELL - 4, CELL - 4);
      g.fill(terrainColor(terrain));
      g.rect(1, 1, CELL - 2, CELL - 2);
      g.stroke({ color: terrainBorder(terrain), width: 1 });

      if (cur && cur.row === r && cur.col === c) {
        g.rect(2, 2, CELL - 4, CELL - 4);
        g.fill(cur.isPlayer ? 0x002200 : 0x220000);
        g.rect(1, 1, CELL - 2, CELL - 2);
        g.stroke({ color: cur.isPlayer ? C.green : C.red, width: 2 });
      }

      if ((phase === "select_target") && selectedSkill) {
        const actingUnit = allTurnUnits[currentUnitIdx];
        if (actingUnit && actingUnit.isPlayer) {
          if (isAllyTargetSkill(selectedSkill)) {
            const allyOnCell = partyUnits.find(u => u.alive && u.row === r && u.col === c);
            if (allyOnCell) {
              g.rect(2, 2, CELL - 4, CELL - 4);
              g.fill(0x002800);
              g.rect(1, 1, CELL - 2, CELL - 2);
              g.stroke({ color: C.hp, width: 2 });
            }
          } else {
            const enemyOnCell = enemies.find(e => e.alive && e.row === r && e.col === c);
            if (enemyOnCell) {
              g.rect(2, 2, CELL - 4, CELL - 4);
              g.fill(0x220022);
              g.rect(1, 1, CELL - 2, CELL - 2);
              g.stroke({ color: C.pink, width: 2 });
            }
          }
        }
      }

      let terrainIcon = "";
      if (terrain === "fire")     terrainIcon = "🔥";
      if (terrain === "ice")      terrainIcon = "❄";
      if (terrain === "electric") terrainIcon = "⚡";

      const unit = getUnit(r, c);
      if (unit) {
        sym.style = new TextStyle({ fontFamily:"Russo One", fontSize: 18, fill: unit.symbolColor });
        sym.text = unit.symbol;

        const hpRatio = unit.hp / unit.maxHp;
        const barW = CELL - 8;
        const barX = 4;
        const barY = CELL - 13;

        // HP 바 배경
        g.rect(barX, barY, barW, 5);
        g.fill(C.hpbg);
        // HP 바 채우기
        g.rect(barX, barY, Math.floor(barW * hpRatio), 5);
        g.fill(hpRatio > 0.5 ? C.hp : hpRatio > 0.25 ? C.orange : C.red);

        // 보호막 상태일 때 HP 바 흰색 테두리
        if (unit.statusEffects.includes("shield")) {
          g.rect(barX - 1, barY - 1, barW + 2, 7);
          g.stroke({ color: C.white, width: 1.5 });
        }

        const statusIcons: Record<StatusEffect, string> = {
          burn:"🔥", freeze:"❄", stun:"💫", poison:"☠", shield:"🛡", blessed:"✨"
        };
        const iconsStr = unit.statusEffects.map(s => statusIcons[s]).join("");
        if (iconsStr) {
          const st = new Text({ text: iconsStr, style: new TextStyle({ fontFamily:"Russo One", fontSize:8, fill:C.white }) });
          st.x = MAP_OX + c * CELL + 2;
          st.y = MAP_OY + r * CELL + 2;
          mapContainer.addChild(st);
          setTimeout(() => {
            if (st.parent) { st.parent.removeChild(st); st.destroy(); }
          }, 0);
        }
      } else {
        sym.text = terrainIcon;
        sym.style = new TextStyle({ fontFamily:"Russo One", fontSize:14, fill:C.orange });
      }
    }
  }
}

function renderLog(): void {
  const visible = log.slice(-20);
  for (let i = 0; i < 20; i++) {
    const line = visible[i] || "";
    const lt = logTexts[i];
    let style = TS.log;
    if (line.includes("승리") || line.includes("+") || line.includes("회복")) style = TS.logGrn;
    else if (line.includes("피해") || line.includes("전멸") || line.includes("💀") || line.includes("-")) style = TS.logRed;
    else if (line.includes("턴") || line.includes("──")) style = TS.logBlue;
    else if (line.includes("보스") || line.includes("⚠") || line.includes("✨") || line.includes("⚡")) style = TS.logGold;
    lt.style = style;
    lt.text = line.length > 34 ? line.slice(0, 33) + "…" : line;
  }
}

function renderInputBar(): void {
  inputBg.clear();
  inputBg.rect(0, INPUT_Y, CANVAS_WIDTH, 70);
  inputBg.fill(C.panel);
  inputBg.rect(0, INPUT_Y, CANVAS_WIDTH, 1);
  inputBg.stroke({ color: C.border, width: 1 });

  const hint = getInputHint();
  inputText.text = `${hint}  ${inputBuffer}${cursorVisible ? "│" : " "}`;
  inputText.x = 16;
  inputText.y = INPUT_Y + 12;
}

function getInputHint(): string {
  switch (phase) {
    case "battle_start": return "▶ Enter: 전투 시작";
    case "player_turn":  {
      const cur = allTurnUnits[currentUnitIdx];
      return cur ? `[${cur.name}] 스킬 번호 입력 (1~${cur.skills.length}) 또는 대기 (0):` : "";
    }
    case "select_target": {
      if (selectedSkill && isAllyTargetSkill(selectedSkill)) {
        return "아군 번호 입력 (취소: 0):";
      }
      return "적 번호 입력 (취소: 0):";
    }
    case "battle_result": return "▶ Enter: 다음 단계";
    default: return "";
  }
}

function renderPhaseInfo(): void {
  const phaseLabels: Partial<Record<GamePhase, string>> = {
    battle_start: `전투 ${battleIndex + 1}/9  — ${STAGE_CONFIGS[battleIndex].name}: ${STAGE_CONFIGS[battleIndex].desc}`,
    player_turn:  `아군 턴  ·  ${STAGE_CONFIGS[battleIndex].name}`,
    select_target: "대상 선택 중",
    enemy_turn:   "적 턴 진행 중...",
    battle_result: "전투 결과",
    reward:       "보상 선택",
  };
  phaseText.text = phaseLabels[phase] || "";

  const cur = allTurnUnits[currentUnitIdx];
  if (cur && cur.alive && (phase === "player_turn" || phase === "select_target")) {
    turnIndicator.text = `현재: ${cur.name}  HP:${cur.hp}/${cur.maxHp}  MP:${cur.mp}/${cur.maxMp}`;
  } else {
    turnIndicator.text = "";
  }
}

function renderPartyInfo(): void {
  while (partyInfoContainer.children.length > 0) {
    const ch = partyInfoContainer.children[0];
    partyInfoContainer.removeChild(ch);
    ch.destroy({ children: true });
  }

  const startX = MAP_OX;
  const startY = MAP_OY + MAP_H + 30;
  const cardW = (MAP_W) / Math.max(partyUnits.length, 1) - 4;

  partyUnits.forEach((u, i) => {
    const bx = startX + i * (cardW + 4);
    const by = startY;

    const hasShield = u.statusEffects.includes("shield");

    const bg = new Graphics();
    bg.rect(bx, by, cardW, 52);
    bg.fill(u.alive ? (allTurnUnits[currentUnitIdx]?.id === u.id ? 0x002800 : C.panel) : 0x1a0000);
    bg.rect(bx, by, cardW, 52);
    bg.stroke({ color: u.alive ? (allTurnUnits[currentUnitIdx]?.id === u.id ? C.green : C.border) : C.red, width: 1 });
    partyInfoContainer.addChild(bg);

    const nameT = new Text({ text: u.name, style: new TextStyle({ fontFamily:"Russo One", fontSize:9, fill: u.alive ? C.white : C.gray, letterSpacing:0 }) });
    nameT.x = bx + 4;
    nameT.y = by + 4;
    partyInfoContainer.addChild(nameT);

    const hpR = u.hp / u.maxHp;
    const bw = cardW - 8;

    // HP 바 배경
    const hpBg = new Graphics();
    hpBg.rect(bx + 4, by + 18, bw, 6);
    hpBg.fill(C.hpbg);
    partyInfoContainer.addChild(hpBg);

    // HP 바 채우기
    const hpFg = new Graphics();
    hpFg.rect(bx + 4, by + 18, Math.floor(bw * hpR), 6);
    hpFg.fill(hpR > 0.5 ? C.hp : hpR > 0.25 ? C.orange : C.red);
    partyInfoContainer.addChild(hpFg);

    // 보호막 상태일 때 HP 바 흰색 테두리
    if (hasShield) {
      const shieldBorder = new Graphics();
      shieldBorder.rect(bx + 3, by + 17, bw + 2, 8);
      shieldBorder.stroke({ color: C.white, width: 1.5 });
      partyInfoContainer.addChild(shieldBorder);
    }

    const hpT = new Text({ text: `${u.hp}/${u.maxHp}`, style: TS.hpbar });
    hpT.x = bx + 4;
    hpT.y = by + 26;
    partyInfoContainer.addChild(hpT);

    const mpR = u.mp / u.maxMp;
    const mpBg = new Graphics();
    mpBg.rect(bx + 4, by + 36, bw, 4);
    mpBg.fill(C.mpbg);
    partyInfoContainer.addChild(mpBg);
    const mpFg = new Graphics();
    mpFg.rect(bx + 4, by + 36, Math.floor(bw * mpR), 4);
    mpFg.fill(C.mp);
    partyInfoContainer.addChild(mpFg);
    const mpT = new Text({ text: `MP ${u.mp}/${u.maxMp}`, style: TS.mpbar });
    mpT.x = bx + 4;
    mpT.y = by + 42;
    partyInfoContainer.addChild(mpT);
  });

  // 스킬 목록
  if ((phase === "player_turn" || phase === "select_target") && allTurnUnits[currentUnitIdx]?.isPlayer) {
    const cur = allTurnUnits[currentUnitIdx];
    const sx = startX;
    const sy = startY + 60;

    const skillBg = new Graphics();
    skillBg.rect(sx, sy, MAP_W, 14 + cur.skills.length * 18);
    skillBg.fill(C.panel);
    skillBg.rect(sx, sy, MAP_W, 14 + cur.skills.length * 18);
    skillBg.stroke({ color: C.border, width: 1 });
    partyInfoContainer.addChild(skillBg);

    const skLabel = new Text({ text: "스킬", style: TS.label });
    skLabel.x = sx + 4;
    skLabel.y = sy + 3;
    partyInfoContainer.addChild(skLabel);

    cur.skills.forEach((sk, si) => {
      const selected = selectedSkill?.id === sk.id;
      const elColor: Record<Element, number> = {
        fire: C.red, ice: C.cyan, lightning: C.gold, holy: C.white,
        curse: C.purple, poison: C.teal, none: C.gray
      };
      const skT = new Text({
        text: `${si + 1}. ${sk.name}  MP:${sk.mpCost}  ${sk.desc}`,
        style: new TextStyle({
          fontFamily: "Russo One", fontSize: 9,
          fill: selected ? C.gold : elColor[sk.element],
          letterSpacing: 0
        })
      });
      skT.x = sx + 4;
      skT.y = sy + 14 + si * 18;
      partyInfoContainer.addChild(skT);
    });
  }

  // ── 적/아군 목록 패널 ──
  if (phase === "player_turn" || phase === "select_target" || phase === "battle_start") {
    const aliveEnemies = enemies.filter(e => e.alive);
    const aliveAllies = partyUnits.filter(u => u.alive);

    const showAllyList = (phase === "select_target") && selectedSkill && isAllyTargetSkill(selectedSkill);

    if (showAllyList) {
      // ── 아군 목록 표시 ──
      const ex = LOG_X;
      const rowH = 20;
      const panelH = aliveAllies.length * rowH + 28;
      const ey = LOG_Y + LOG_H - panelH;

      const eBg = new Graphics();
      eBg.rect(ex - 4, ey - 4, LOG_W + 8, panelH + 4);
      eBg.fill(0x001a00);
      eBg.rect(ex - 5, ey - 5, LOG_W + 10, panelH + 6);
      eBg.stroke({ color: C.hp, width: 1.5 });
      partyInfoContainer.addChild(eBg);

      const eLabel = new Text({ text: "아군 목록 (버프/힐 대상)",
        style: new TextStyle({ fontFamily:"Russo One", fontSize:10, fill:C.hp, letterSpacing:0.5 }) });
      eLabel.x = ex;
      eLabel.y = ey;
      partyInfoContainer.addChild(eLabel);

      aliveAllies.forEach((u, i) => {
        const hpR = u.hp / u.maxHp;
        const statusStr = u.statusEffects.length > 0
          ? `  [${u.statusEffects.map(s => statusLabel(s as StatusEffect)).join(",")}]`
          : "";
        const uT = new Text({
          text: `${i + 1}. ${u.name}  HP:${u.hp}/${u.maxHp}  MP:${u.mp}/${u.maxMp}${statusStr}`,
          style: new TextStyle({ fontFamily:"Russo One", fontSize:9,
            fill: hpR < 0.3 ? C.red : hpR < 0.6 ? C.orange : C.hp,
            letterSpacing:0 })
        });
        uT.x = ex;
        uT.y = ey + 16 + i * rowH;
        partyInfoContainer.addChild(uT);
      });
    } else {
      // ── 적 목록 표시 ──
      const ex = LOG_X;
      const rowH = 18;
      const panelH = aliveEnemies.length * rowH + 24;
      const ey = LOG_Y + LOG_H - panelH;

      const eBg = new Graphics();
      eBg.rect(ex - 4, ey - 4, LOG_W + 8, panelH + 4);
      eBg.fill(0x1a0000);
      eBg.rect(ex - 5, ey - 5, LOG_W + 10, panelH + 6);
      eBg.stroke({ color: C.red, width: 1 });
      partyInfoContainer.addChild(eBg);

      const eLabel = new Text({ text: "적 목록",
        style: new TextStyle({ fontFamily:"Russo One", fontSize:10, fill:C.red, letterSpacing:0.5 }) });
      eLabel.x = ex;
      eLabel.y = ey;
      partyInfoContainer.addChild(eLabel);

      aliveEnemies.forEach((e, i) => {
        const hpR = e.hp / e.maxHp;
        const eT = new Text({
          text: `${i + 1}. ${e.name}  HP:${e.hp}/${e.maxHp}  [${e.statusEffects.map(s => statusLabel(s as StatusEffect)).join(",") || "-"}]`,
          style: new TextStyle({ fontFamily:"Russo One", fontSize:9, fill: hpR < 0.3 ? C.red : C.white, letterSpacing:0 })
        });
        eT.x = ex;
        eT.y = ey + 14 + i * rowH;
        partyInfoContainer.addChild(eT);
      });
    }
  }
}

// ─────────────────────────────────────────
// 입력 처리
// ─────────────────────────────────────────
function handleInput(raw: string): void {
  const input = raw.trim();

  switch (phase) {
    case "title":
      phase = "name_input";
      inputBuffer = "";
      break;

    case "name_input":
      if (input.length === 0) return;
      partyName = input;
      selectedPartyJobs = [];
      inputBuffer = "";
      phase = "party_select";
      break;

    case "party_select": {
      const jobList: JobId[] = ["knight", "guardian", "mage", "cleric", "archer", "alchemist"];
      const nums = input.split(/\s+/).map(n => parseInt(n)).filter(n => !isNaN(n) && n >= 1 && n <= 6);
      if (nums.length < 2 || nums.length > 3) {
        addLog("2~3명을 선택하세요 (예: 1 3 4)");
        return;
      }
      selectedPartyJobs = [...new Set(nums)].slice(0, 3).map(n => jobList[n - 1]);
      partyUnits = [];
      unitIdCounter = 0;
      const names: Record<JobId, string> = {
        knight: "기사 레온", guardian: "수호자 아이라", mage: "마법사 제라드",
        cleric: "성직자 리나", archer: "궁수 카엘", alchemist: "연금술사 벨로"
      };
      selectedPartyJobs.forEach(job => {
        partyUnits.push(makeUnit(job, names[job], true, 0, 0));
      });
      log = [];
      battleIndex = 0;
      addLog(`파티 「${partyName}」 결성!`);
      addLog(`멤버: ${partyUnits.map(u => u.name).join(", ")}`);
      inputBuffer = "";
      setupBattle();
      break;
    }

    case "battle_start":
      addLog("=== 전투 시작! ===");
      buildTurnOrder();
      {
        const firstUnit = allTurnUnits[0];
        if (firstUnit) {
          if (firstUnit.isPlayer) {
            phase = "player_turn";
            addLog(``);
            addLog(`── ${firstUnit.name}의 턴 ──`);
          } else {
            phase = "enemy_turn";
            currentEnemyTurnIdx = 0;
            doNextEnemyTurn();
          }
        }
      }
      inputBuffer = "";
      break;

    case "player_turn": {
      const cur = allTurnUnits[currentUnitIdx];
      if (!cur || !cur.alive) { nextTurn(); return; }

      if (input === "대기" || input === "0") {
        cur.mp = Math.min(cur.maxMp, cur.mp + 5);
        addLog(`${cur.name} 대기 (MP +5)`);
        inputBuffer = "";
        if (!checkBattleEnd()) nextTurn();
        return;
      }

      const skillNum = parseInt(input);
      if (isNaN(skillNum) || skillNum < 1 || skillNum > cur.skills.length) {
        addLog(`1~${cur.skills.length} 사이의 번호를 입력하세요. (0: 대기)`);
        return;
      }
      selectedSkill = cur.skills[skillNum - 1];
      if (selectedSkill.mpCost > cur.mp) {
        addLog(`MP 부족! (필요: ${selectedSkill.mpCost}, 현재: ${cur.mp})`);
        selectedSkill = null;
        return;
      }
      // 대상 선택 단계로 이동
      phase = "select_target";
      const isAlly = isAllyTargetSkill(selectedSkill);
      const targetPool = isAlly
        ? partyUnits.filter(u => u.alive)
        : enemies.filter(u => u.alive);
      const targetType = isAlly ? "아군" : "적";
      addLog(``);
      addLog(`[${selectedSkill.name}] 선택 — ${targetType} 번호 입력 (1~${targetPool.length}), 취소: 0`);
      inputBuffer = "";
      break;
    }

    case "select_target": {
      const cur = allTurnUnits[currentUnitIdx];
      if (!cur || !selectedSkill) { phase = "player_turn"; return; }

      // 취소
      if (input === "0" || input === "취소") {
        addLog(`[${selectedSkill.name}] 취소 — 다시 스킬을 선택하세요.`);
        selectedSkill = null;
        phase = "player_turn";
        inputBuffer = "";
        return;
      }

      const isAlly = isAllyTargetSkill(selectedSkill);
      const targetPool = isAlly
        ? partyUnits.filter(u => u.alive)
        : enemies.filter(u => u.alive);

      const targetNum = parseInt(input);
      if (isNaN(targetNum) || targetNum < 1 || targetNum > targetPool.length) {
        addLog(`1~${targetPool.length} 사이의 번호를 입력하세요. (0: 취소)`);
        return;
      }

      // 타겟 선택 완료 — 즉시 실행
      const target = targetPool[targetNum - 1];
      cur.mp -= selectedSkill.mpCost;

      const elemFx: Record<Element, string> = {
        fire: "🔥 화염이 작렬합니다!",
        ice:  "❄️  냉기가 폭발합니다!",
        lightning: "⚡ 번개가 작렬합니다!",
        holy: "✨ 신성한 빛이 퍼집니다!",
        curse: "💀 저주의 기운이 퍼집니다!",
        poison: "☠️  독이 번집니다!",
        none: ""
      };
      const fx = elemFx[selectedSkill.element];
      if (fx) addLog(fx);

      if (selectedSkill.aoe) {
        addLog(`${cur.name} → [${selectedSkill.name}] 광역 시전!`);
        applySkillAoe(cur, selectedSkill, targetPool);
        flash(selectedSkill.element === "fire" ? C.orange : selectedSkill.element === "ice" ? C.cyan : C.purple, 0.3);
      } else {
        const result = applySkill(cur, target, selectedSkill);
        addLog(`${cur.name} → ${target.name}: [${selectedSkill.name}]  ${result}`);
        if (selectedSkill.baseDmg > 0) flash(C.red, 0.25);
        else if (selectedSkill.healAmt > 0) flash(C.hp, 0.2);
        else flash(C.purple, 0.2);

        if (selectedSkill.baseDmg > 0 && !target.alive) {
          addLog(`  ☠️  ${target.name} 쓰러짐!`);
        } else if (selectedSkill.baseDmg > 0) {
          addLog(`  💥 ${target.name} 피격!`);
        }
      }

      selectedSkill = null;
      inputBuffer = "";

      if (!checkBattleEnd()) nextTurn();
      break;
    }

    case "battle_result":
      battleIndex++;
      if (battleIndex >= totalBattles) {
        phase = "victory";
      } else {
        pendingRewards = getRewardOptions();
        phase = "reward";
        addLog("");
        addLog("✦ 보상을 선택하세요 ✦");
        pendingRewards.forEach((r, i) => addLog(`  ${i + 1}. ${r}`));
      }
      inputBuffer = "";
      break;

    case "reward": {
      const rNum = parseInt(input);
      if (isNaN(rNum) || rNum < 1 || rNum > pendingRewards.length) {
        addLog(`1~${pendingRewards.length} 중 선택하세요.`);
        return;
      }
      const chosen = pendingRewards[rNum - 1];
      applyReward(chosen);
      addLog(`✅ 선택: ${chosen}`);
      pendingRewards = [];
      inputBuffer = "";
      setupBattle();
      break;
    }

    case "gameover":
    case "victory":
      phase = "title";
      log = [];
      partyUnits = [];
      enemies = [];
      inputBuffer = "";
      break;
  }
}

function applyReward(reward: string): void {
  if (reward.includes("HP 포션")) {
    partyUnits.filter(u => u.alive).forEach(u => {
      u.hp = clamp(u.hp + 40, 0, u.maxHp);
    });
    addLog("파티 전원 HP +40!");
  } else if (reward.includes("MP 포션")) {
    partyUnits.filter(u => u.alive).forEach(u => {
      u.mp = clamp(u.mp + 25, 0, u.maxMp);
    });
    addLog("파티 전원 MP +25!");
  } else if (reward.includes("강화")) {
    partyUnits.forEach(u => {
      u.skills.forEach(sk => {
        if (reward.includes(sk.name)) {
          sk.baseDmg = Math.floor(sk.baseDmg * 1.2);
          sk.healAmt = Math.floor(sk.healAmt * 1.2);
          sk.mpCost = Math.max(0, sk.mpCost - 2);
          addLog(`${u.name}의 [${sk.name}] 강화 완료!`);
        }
      });
    });
  } else if (reward.includes("유물")) {
    const u = partyUnits.filter(p => p.alive)[rnd(0, partyUnits.length - 1)];
    if (u) {
      const roll = rnd(0, 2);
      if (roll === 0) { u.maxHp += 20; u.hp += 20; addLog(`${u.name} 최대 HP +20!`); }
      else if (roll === 1) { u.atk += 8; addLog(`${u.name} 공격력 +8!`); }
      else { u.def += 6; addLog(`${u.name} 방어력 +6!`); }
    }
  }
}

// ─────────────────────────────────────────
// 메인 초기화
// ─────────────────────────────────────────
async function init(): Promise<void> {
  await app.init({
    width: CANVAS_WIDTH,
    height: CANVAS_HEIGHT,
    backgroundColor: C.bg,
    antialias: false,
  });
  document.body.appendChild(app.canvas);

  rootContainer = new Container();
  app.stage.addChild(rootContainer);

  uiContainer = new Container();
  mapContainer = new Container();
  rootContainer.addChild(uiContainer);
  rootContainer.addChild(mapContainer);

  logContainer = new Container();

  buildStaticUI();

  window.addEventListener("keydown", (e: KeyboardEvent) => {
    if (e.key === "Enter") {
      const toProcess = inputBuffer;
      inputBuffer = "";
      handleInput(toProcess);
    } else if (e.key === "Backspace") {
      inputBuffer = inputBuffer.slice(0, -1);
      e.preventDefault();
    } else if (e.key.length === 1 && inputBuffer.length < 40) {
      inputBuffer += e.key;
    } else if (e.key === " ") {
      if (phase === "title") {
        handleInput("");
        inputBuffer = "";
      } else {
        if (inputBuffer.length < 40) inputBuffer += " ";
      }
      e.preventDefault();
    }
  });

  app.ticker.add((ticker) => {
    _cursorTimer += ticker.deltaTime;
    if (_cursorTimer > 35) {
      cursorVisible = !cursorVisible;
      _cursorTimer = 0;
    }
    renderFrame();
  });
}

document.fonts.ready.then(() => {
  init();
});