const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

const introOverlay = document.getElementById('introOverlay');
const dialogueOverlay = document.getElementById('dialogueOverlay');
const endingOverlay = document.getElementById('endingOverlay');
const startButton = document.getElementById('startButton');
const restartButton = document.getElementById('restartButton');
const dialogueClose = document.getElementById('dialogueClose');

const dialogueTitle = document.getElementById('dialogueTitle');
const dialogueSpeaker = document.getElementById('dialogueSpeaker');
const dialogueText = document.getElementById('dialogueText');
const choiceButtons = document.getElementById('choiceButtons');
const dialogueActions = document.getElementById('dialogueActions');
const npcPortrait = document.getElementById('npcPortrait');
const npcPlaceholder = document.getElementById('npcPlaceholder');
const npcLabel = document.getElementById('npcLabel');
const statusCount = document.getElementById('statusCount');
const objectiveBox = document.getElementById('objectiveBox');

const images = {};
const imageSources = {
  map: 'assets/akiba_map.png',
  heroSprite: 'assets/hero_sprite_directions.png',
  heroPortrait: 'assets/hero_portrait.png',
  idolPortrait: 'assets/idol_otaku_portrait.png',
  idolBadge: 'assets/idol_otaku_badge.png',
  audioPortrait: 'assets/audio_otaku_portrait.png',
  audioBadge: 'assets/audio_otaku_badge.png',
  animePortrait: 'assets/anime_otaku_portrait.png',
  animeBadge: 'assets/anime_otaku_badge.png'
};

const WORLD = { width: 1600, height: 1200 };
const VIEW = { width: canvas.width, height: canvas.height };
const PLAYER_SIZE = 42;
const PLAYER_SPEED = 3.2;
const ICON_SIZE = 58;

// Walkable areas matched to the illustrated Akihabara-style map.
const walkableRects = [
  // central vertical road
  { x: 622, y: 0, w: 356, h: 1200 },
  // horizontal roads
  { x: 0, y: 322, w: 1600, h: 130 },
  { x: 0, y: 710, w: 1600, h: 130 },
  // top row storefront sidewalks
  { x: 0, y: 238, w: 560, h: 84 },
  { x: 1010, y: 238, w: 590, h: 84 },
  // middle row storefront sidewalks
  { x: 0, y: 625, w: 560, h: 85 },
  { x: 1010, y: 625, w: 590, h: 85 },
  // bottom row storefront sidewalks
  { x: 0, y: 1015, w: 560, h: 84 },
  { x: 842, y: 1015, w: 758, h: 84 },
  // connector aprons from center road to side blocks
  { x: 560, y: 238, w: 62, h: 214 },
  { x: 978, y: 238, w: 62, h: 214 },
  { x: 560, y: 625, w: 62, h: 215 },
  { x: 978, y: 625, w: 62, h: 215 },
  { x: 560, y: 1015, w: 62, h: 184 },
  { x: 780, y: 1015, w: 62, h: 184 }
];

let npcs = [];
let player;
let companions;
let keys = {};
let camera = { x: 0, y: 0 };
let dialogueState = null;
let gameWon = false;
let started = false;

function makeNpcData() {
  return [
    {
      id: 'maid-cafe', label: 'メイドカフェ', x: 160, y: 278, type: 'hint', icon: '☕', color: '#ff8dc0',
      speaker: 'メイド店員',
      hint: '「アイドルオタクさんは “推しへの愛と節度ある応援” って言葉に弱いですよっ！」\n\nアイドル系の重要キャラは、ライブ愛とマナーを大切にするみたい。'
    },
    {
      id: 'pc-parts', label: 'PCショップ', x: 455, y: 278, type: 'hint', icon: '🖥️', color: '#78c7ff',
      speaker: 'パソコンショップ店員',
      hint: '「街の真ん中から左右の歩道に入れるよ。オーディオ派は “自分の耳で確かめる” を信じる人が多いんだ。」'
    },
    {
      id: 'figure-shop', label: 'K-BOOKS前', x: 1455, y: 278, type: 'hint', icon: '🧸', color: '#d792ff',
      speaker: 'フィギュア店員',
      hint: '「アニメオタクさんには、作品の好きなシーンやキャラ愛を具体的に語ると刺さるかも！」'
    },
    {
      id: 'arcade', label: 'ゲームセンター', x: 915, y: 666, type: 'hint', icon: '🎮', color: '#ffb268',
      speaker: 'ゲーセン店員',
      hint: '「秋葉原は情報の街！ いろんなお店を回って答えを集めるのが近道だよ。」'
    },
    {
      id: 'ramen', label: 'イヤホン専門店', x: 455, y: 666, type: 'hint', icon: '🎧', color: '#ffd468',
      speaker: 'イヤホン店スタッフ',
      hint: '「オーディオオタクさんは “高い機材だから良い” より “自分の耳で確かめる” を大事にしてるよ。」'
    },
    {
      id: 'capsule', label: 'サブカル雑貨', x: 1455, y: 1055, type: 'hint', icon: '🪀', color: '#85f0be',
      speaker: '雑貨店スタッフ',
      hint: '「重要キャラは3人。全員集めると街のゲートが開くって噂だよ！」'
    },
    {
      id: 'idol-important', label: 'アイドルグッズ館', x: 1125, y: 278, type: 'important', icon: '🎤', color: '#ff6fb1',
      speaker: 'アイドルオタク', portrait: 'idolPortrait', badge: 'idolBadge', companionName: 'アイドルオタク',
      intro: '「おっ、キミも現場の熱を感じに来たのかい！？ じゃあ質問だ！\n\nライブ現場で一番大切なのは何だと思う？」',
      success: '「わかってるじゃないか！ 推しへの愛と節度、これぞ最高の現場力！\n\nよし、オレもキミについていく！ アイドルオタク、参戦だ！」',
      choices: [
        { text: '推しへの愛と、まわりへの配慮を忘れない応援', correct: true, response: '正解！ その心意気に感動した！' },
        { text: 'とにかく最前列を奪うこと', correct: false, response: 'うーん、それだけじゃダメだ！ 推し活にはマナーも必要だよ！' },
        { text: 'グッズを一番たくさん買うことだけ', correct: false, response: '熱量は大事だけど、それだけじゃ推し活は語れない！' }
      ]
    },
    {
      id: 'audio-important', label: '高音質オーディオサロン', x: 1180, y: 666, type: 'important', icon: '🎼', color: '#71d7d0',
      speaker: 'オーディオオタク', portrait: 'audioPortrait', badge: 'audioBadge', companionName: 'オーディオオタク',
      intro: '「足を止めたってことは、キミも音に興味があるのかな。では問題。\n\n良い音を語るとき、一番大事なのはどれだ？」',
      success: '「その通り。音はスペックだけじゃなく、自分の耳で味わうものだ。\n\n気に入った！ これからはオレが音の道案内をしよう。」',
      choices: [
        { text: '値段が高い機材を信じること', correct: false, response: '価格は参考にはなるが、答えそのものじゃない。' },
        { text: '自分の耳で違いを確かめ、楽しむこと', correct: true, response: '正解。耳で確かめる、それが一番大切だ。' },
        { text: '見た目が重厚なら音も良いと決めつけること', correct: false, response: '外見だけでは本質はわからない。もっと耳を澄ませてごらん。' }
      ]
    },
    {
      id: 'anime-important', label: 'アニメ専門店', x: 430, y: 1055, type: 'important', icon: '📚', color: '#8a8dff',
      speaker: 'アニメオタク', portrait: 'animePortrait', badge: 'animeBadge', companionName: 'アニメオタク',
      intro: '「おお、この店に来るとは見どころがあるね。さっそく質問だよ！\n\n推し作品への愛をいちばん伝えられるのはどれ？」',
      success: '「最高だ！ 好きな場面を具体的に語れる人に悪い人はいない！\n\nボクも仲間になるよ。さあ、次は異世界だ！」',
      choices: [
        { text: 'タイトルだけ大量に並べること', correct: false, response: '量も迫力だけど、愛はもっと具体的な言葉に宿るんだ。' },
        { text: '好きな場面やキャラの魅力を具体的に語ること', correct: true, response: '正解！ それこそ本物の作品愛だよ。' },
        { text: 'ネタバレを気にせず語り尽くすこと', correct: false, response: 'それは危険！ 作品愛には相手への思いやりも必要だよ。' }
      ]
    },
    {
      id: 'cafe', label: '@Home Cafe前', x: 940, y: 1055, type: 'hint', icon: '🍰', color: '#ff9dd0',
      speaker: 'カフェスタッフ',
      hint: '「重要キャラはそれぞれ、自分の“好き”を尊重してくれる相手を求めているみたいですよ。」'
    }
  ];
}

function resetGame() {
  player = {
    x: 800,
    y: 388,
    size: PLAYER_SIZE,
    speed: PLAYER_SPEED,
    moving: false,
    frame: 0,
    frameTimer: 0,
    direction: 0 // 0 down, 1 left, 2 right, 3 up
  };
  companions = [];
  npcs = makeNpcData().map(n => ({ ...n, visited: false, recruited: false }));
  keys = {};
  camera = { x: 0, y: 0 };
  dialogueState = null;
  gameWon = false;
  updateStatusText();
  objectiveBox.textContent = '店舗アイコンを訪れて、3人の重要キャラを仲間にしよう！';
  endingOverlay.classList.remove('visible');
  dialogueOverlay.classList.remove('visible');
}

function loadImages() {
  const entries = Object.entries(imageSources);
  return Promise.all(entries.map(([key, src]) => new Promise(resolve => {
    const img = new Image();
    img.onload = () => { images[key] = img; resolve(); };
    img.onerror = () => { console.warn('Image load failed:', src); resolve(); };
    img.src = src;
  })));
}

function clamp(value, min, max) {
  return Math.max(min, Math.min(max, value));
}

function pointInRect(px, py, r) {
  return px >= r.x && px <= r.x + r.w && py >= r.y && py <= r.y + r.h;
}

function isWalkable(x, y) {
  return walkableRects.some(r => pointInRect(x, y, r));
}

function updateDirection(dx, dy) {
  if (Math.abs(dx) > Math.abs(dy)) {
    player.direction = dx < 0 ? 1 : 2;
  } else if (Math.abs(dy) > 0) {
    player.direction = dy < 0 ? 3 : 0;
  }
}

function tryMove(nextX, nextY) {
  const cx = clamp(nextX, player.size / 2, WORLD.width - player.size / 2);
  const cy = clamp(nextY, player.size / 2, WORLD.height - player.size / 2);
  if (isWalkable(cx, cy)) {
    player.x = cx;
    player.y = cy;
  }
}

function updatePlayer() {
  if (!started || dialogueState || gameWon) return;

  let dx = 0;
  let dy = 0;
  if (keys['ArrowLeft'] || keys['a'] || keys['A']) dx -= 1;
  if (keys['ArrowRight'] || keys['d'] || keys['D']) dx += 1;
  if (keys['ArrowUp'] || keys['w'] || keys['W']) dy -= 1;
  if (keys['ArrowDown'] || keys['s'] || keys['S']) dy += 1;

  player.moving = dx !== 0 || dy !== 0;

  if (player.moving) {
    const length = Math.hypot(dx, dy) || 1;
    dx = (dx / length) * player.speed;
    dy = (dy / length) * player.speed;
    updateDirection(dx, dy);

    tryMove(player.x + dx, player.y);
    tryMove(player.x, player.y + dy);

    player.frameTimer += 1;
    if (player.frameTimer > 8) {
      player.frame = (player.frame + 1) % 4;
      player.frameTimer = 0;
    }
  } else {
    player.frame = 0;
  }

  camera.x = clamp(player.x - VIEW.width / 2, 0, WORLD.width - VIEW.width);
  camera.y = clamp(player.y - VIEW.height / 2, 0, WORLD.height - VIEW.height);

  for (const npc of npcs) {
    if ((npc.visited && npc.type === 'hint') || npc.recruited) continue;
    const dist = Math.hypot(player.x - npc.x, player.y - npc.y);
    if (dist < 44) {
      beginDialogue(npc);
      break;
    }
  }
}

function beginDialogue(npc) {
  dialogueState = { npc, phase: npc.type === 'important' ? 'intro' : 'hint' };
  dialogueOverlay.classList.add('visible');
  renderDialogue();
}

function renderDialogue() {
  if (!dialogueState) return;
  const npc = dialogueState.npc;

  dialogueTitle.textContent = npc.label;
  dialogueSpeaker.textContent = npc.speaker || 'NPC';
  choiceButtons.innerHTML = '';
  dialogueActions.innerHTML = '';

  if (npc.type === 'important') {
    npcPortrait.style.display = 'block';
    npcPortrait.src = imageSources[npc.portrait];
    npcPlaceholder.style.display = 'none';
    npcLabel.textContent = npc.companionName;
  } else {
    npcPortrait.style.display = 'none';
    npcPlaceholder.style.display = 'grid';
    npcPlaceholder.textContent = `${npc.label}\n${npc.icon}`;
    npcLabel.textContent = npc.speaker;
  }

  if (npc.type === 'hint') {
    dialogueText.textContent = npc.hint;
    const okBtn = document.createElement('button');
    okBtn.className = 'primary-btn';
    okBtn.textContent = '会話を終える';
    okBtn.onclick = () => {
      npc.visited = true;
      closeDialogue();
    };
    dialogueActions.appendChild(okBtn);
    return;
  }

  if (dialogueState.phase === 'intro') {
    dialogueText.textContent = npc.intro;
    const nextBtn = document.createElement('button');
    nextBtn.className = 'primary-btn';
    nextBtn.textContent = '答える';
    nextBtn.onclick = () => {
      dialogueState.phase = 'question';
      renderDialogue();
    };
    dialogueActions.appendChild(nextBtn);
    return;
  }

  if (dialogueState.phase === 'question') {
    dialogueText.textContent = '3つの選択肢から、いちばん良い答えを選んでください。';
    npc.choices.forEach(choice => {
      const btn = document.createElement('button');
      btn.className = 'choice-btn';
      btn.textContent = choice.text;
      btn.onclick = () => handleChoice(choice);
      choiceButtons.appendChild(btn);
    });
    return;
  }

  if (dialogueState.phase === 'retry') {
    dialogueText.textContent = dialogueState.feedback;
    const retryBtn = document.createElement('button');
    retryBtn.className = 'secondary-btn';
    retryBtn.textContent = 'もう一度考える';
    retryBtn.onclick = () => {
      dialogueState.phase = 'question';
      renderDialogue();
    };
    dialogueActions.appendChild(retryBtn);
    return;
  }

  if (dialogueState.phase === 'success') {
    dialogueText.textContent = npc.success;
    const joinBtn = document.createElement('button');
    joinBtn.className = 'primary-btn';
    joinBtn.textContent = '仲間になる';
    joinBtn.onclick = () => {
      recruitNpc(npc);
      closeDialogue();
    };
    dialogueActions.appendChild(joinBtn);
  }
}

function handleChoice(choice) {
  if (choice.correct) {
    dialogueState.phase = 'success';
  } else {
    dialogueState.phase = 'retry';
    dialogueState.feedback = choice.response;
  }
  renderDialogue();
}

function recruitNpc(npc) {
  npc.recruited = true;
  if (!companions.find(c => c.id === npc.id)) {
    companions.push({ id: npc.id, name: npc.companionName, badge: npc.badge });
  }
  updateStatusText();

  if (companions.length >= 3) {
    objectiveBox.textContent = '3人そろった！ 異世界ゲートが開く…';
    gameWon = true;
    setTimeout(() => endingOverlay.classList.add('visible'), 450);
  } else {
    objectiveBox.textContent = `${npc.companionName}が仲間になった！ 残り ${3 - companions.length} 人`;
  }
}

function updateStatusText() {
  statusCount.textContent = `仲間 ${companions.length} / 3`;
}

function closeDialogue() {
  dialogueState = null;
  dialogueOverlay.classList.remove('visible');
}

function drawWorld() {
  ctx.clearRect(0, 0, VIEW.width, VIEW.height);

  if (images.map) {
    ctx.drawImage(images.map, camera.x, camera.y, VIEW.width, VIEW.height, 0, 0, VIEW.width, VIEW.height);
  } else {
    ctx.fillStyle = '#e7edf5';
    ctx.fillRect(0, 0, VIEW.width, VIEW.height);
  }

  const grad = ctx.createLinearGradient(0, 0, 0, VIEW.height);
  grad.addColorStop(0, 'rgba(255,255,255,0.05)');
  grad.addColorStop(1, 'rgba(0,0,0,0.09)');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, VIEW.width, VIEW.height);

  npcs.forEach(drawNpc);
  drawPlayer();
  drawCompanions();
}

function drawNpc(npc) {
  if ((npc.type === 'hint' && npc.visited) || npc.recruited) return;
  const x = npc.x - camera.x;
  const y = npc.y - camera.y;

  // clear pulse marker for visibility over the illustrated background
  ctx.save();
  ctx.globalAlpha = 0.16;
  ctx.fillStyle = npc.color;
  ctx.beginPath();
  ctx.arc(x, y, 34 + Math.sin(Date.now() / 240) * 2, 0, Math.PI * 2);
  ctx.fill();
  ctx.restore();

  ctx.save();
  ctx.shadowColor = 'rgba(0,0,0,0.30)';
  ctx.shadowBlur = 12;
  ctx.shadowOffsetY = 4;
  roundRect(ctx, x - ICON_SIZE / 2, y - ICON_SIZE / 2, ICON_SIZE, ICON_SIZE, 17, true, false, npc.color);
  ctx.restore();

  ctx.fillStyle = 'rgba(255,255,255,0.95)';
  roundRect(ctx, x - 25, y - 24, 50, 15, 8, true, false);
  ctx.fillStyle = 'rgba(255,255,255,0.24)';
  for (let i = 0; i < 4; i++) {
    ctx.fillRect(x - 23 + i * 12, y - 24, 6, 15);
  }

  ctx.fillStyle = '#1e2545';
  ctx.font = '29px sans-serif';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(npc.icon, x, y + 7);

  ctx.fillStyle = 'rgba(18,22,38,0.86)';
  roundRect(ctx, x - 74, y + 38, 148, 28, 13, true, false);
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 13px sans-serif';
  ctx.fillText(npc.label, x, y + 52);

  ctx.textAlign = 'start';
  ctx.textBaseline = 'alphabetic';
}

function drawPlayer() {
  const x = player.x - camera.x;
  const y = player.y - camera.y;

  ctx.fillStyle = 'rgba(0,0,0,0.20)';
  ctx.beginPath();
  ctx.ellipse(x, y + 24, 20, 9, 0, 0, Math.PI * 2);
  ctx.fill();

  const sprite = images.heroSprite;
  if (sprite) {
    const frameWidth = sprite.width / 4;
    const frameHeight = sprite.height / 4;
    const sx = player.frame * frameWidth;
    const sy = player.direction * frameHeight;
    ctx.drawImage(sprite, sx, sy, frameWidth, frameHeight, x - 34, y - 44, 68, 68);
  } else {
    ctx.fillStyle = '#ff6fb1';
    ctx.beginPath();
    ctx.arc(x, y, 20, 0, Math.PI * 2);
    ctx.fill();
  }

  ctx.fillStyle = 'rgba(15,18,32,0.88)';
  roundRect(ctx, x - 42, y - 58, 84, 22, 11, true, false);
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 12px sans-serif';
  ctx.textAlign = 'center';
  ctx.fillText('主人公', x, y - 43);
  ctx.textAlign = 'start';
}

function drawCompanions() {
  ctx.save();
  const baseX = 18;
  const baseY = VIEW.height - 86;
  ctx.fillStyle = 'rgba(13,18,35,0.78)';
  roundRect(ctx, 12, VIEW.height - 98, 240, 78, 16, true, false);
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 14px sans-serif';
  ctx.fillText('仲間', 24, VIEW.height - 72);

  companions.forEach((companion, index) => {
    const img = images[companion.badge];
    const x = baseX + index * 42;
    const y = baseY;
    ctx.save();
    ctx.shadowColor = 'rgba(0,0,0,0.35)';
    ctx.shadowBlur = 10;
    ctx.fillStyle = '#ffffff';
    ctx.beginPath();
    ctx.arc(x + 24, y + 24, 26, 0, Math.PI * 2);
    ctx.fill();
    ctx.clip();
    if (img) {
      ctx.drawImage(img, x, y, 48, 48);
    } else {
      ctx.fillStyle = '#ddd';
      ctx.fillRect(x, y, 48, 48);
    }
    ctx.restore();
  });

  for (let i = companions.length; i < 3; i++) {
    const x = baseX + i * 42;
    const y = baseY;
    ctx.fillStyle = 'rgba(255,255,255,0.08)';
    ctx.beginPath();
    ctx.arc(x + 24, y + 24, 24, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = 'rgba(255,255,255,0.15)';
    ctx.stroke();
  }
  ctx.restore();
}

function roundRect(context, x, y, width, height, radius, fill, stroke, fillStyleOverride) {
  if (fillStyleOverride) context.fillStyle = fillStyleOverride;
  const r = Math.min(radius, width / 2, height / 2);
  context.beginPath();
  context.moveTo(x + r, y);
  context.arcTo(x + width, y, x + width, y + height, r);
  context.arcTo(x + width, y + height, x, y + height, r);
  context.arcTo(x, y + height, x, y, r);
  context.arcTo(x, y, x + width, y, r);
  context.closePath();
  if (fill) context.fill();
  if (stroke) context.stroke();
}

function gameLoop() {
  updatePlayer();
  drawWorld();
  requestAnimationFrame(gameLoop);
}

window.addEventListener('keydown', (e) => {
  keys[e.key] = true;
  if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(e.key)) {
    e.preventDefault();
  }
});
window.addEventListener('keyup', (e) => {
  keys[e.key] = false;
});

startButton.addEventListener('click', () => {
  started = true;
  introOverlay.classList.remove('visible');
});

restartButton.addEventListener('click', () => {
  resetGame();
  started = true;
  endingOverlay.classList.remove('visible');
});

dialogueClose.addEventListener('click', () => {
  if (!dialogueState) return;
  if (dialogueState.npc.type === 'hint') {
    dialogueState.npc.visited = true;
  }
  closeDialogue();
});

loadImages().then(() => {
  resetGame();
  gameLoop();
});
