/**
 * End-to-end: load → walk → till → plant → water → sleep ×4 (watering each
 * morning) → harvest → sell → money increases. No console errors anywhere.
 *
 * Drives the game through real input (keyboard + button clicks) and reads
 * state back through the window.__game debug hook.
 */
import { test, expect, type Page } from '@playwright/test';

declare global {
  interface Window {
    __game: {
      phase: string;
      day: number;
      energy: number;
      money: number;
      inventory: Record<string, number>;
      playerPos: { x: number; y: number };
      time: number;
      tool: string;
      targetTile: { x: number; y: number };
      tileAt: (x: number, y: number) => { kind: string; watered: boolean; crop: { type: string; stage: number } | null } | null;
      screenPos: (x: number, y: number) => { x: number; y: number };
    };
  }
}

const state = (page: Page) =>
  page.evaluate(() => ({
    phase: window.__game.phase,
    day: window.__game.day,
    energy: window.__game.energy,
    money: window.__game.money,
    inventory: window.__game.inventory,
    pos: window.__game.playerPos,
    time: window.__game.time,
    target: window.__game.targetTile,
  }));

const tileAt = (page: Page, x: number, y: number) => page.evaluate(([tx, ty]) => window.__game.tileAt(tx, ty), [x, y]);

/** walk in axis-aligned bursts until the player's centre is inside tile (tx,ty) */
async function walkTo(page: Page, tx: number, ty: number): Promise<void> {
  for (let i = 0; i < 80; i++) {
    const p = await page.evaluate(() => window.__game.playerPos);
    const dx = tx + 0.5 - p.x;
    const dy = ty + 0.5 - p.y;
    if (Math.abs(dx) < 0.25 && Math.abs(dy) < 0.25) return;
    const key = Math.abs(dx) > Math.abs(dy) ? (dx > 0 ? 'ArrowRight' : 'ArrowLeft') : (dy > 0 ? 'ArrowDown' : 'ArrowUp');
    const dist = Math.max(Math.abs(dx), Math.abs(dy));
    await page.keyboard.down(key);
    await page.waitForTimeout(Math.min(500, Math.max(50, dist * 210)));
    await page.keyboard.up(key);
    await page.waitForTimeout(70);
  }
  throw new Error(`walkTo(${tx},${ty}) did not converge`);
}

/** tap a direction key briefly to set facing without travelling far */
async function face(page: Page, dir: 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight'): Promise<void> {
  await page.keyboard.down(dir);
  await page.waitForTimeout(35);
  await page.keyboard.up(dir);
  await page.waitForTimeout(60);
}

async function useTool(page: Page): Promise<void> {
  await page.keyboard.press(' ');
  await page.waitForTimeout(330); // wait out the swing animation
}

test('full farming loop: till → plant → water → grow → harvest → sell', async ({ page }) => {
  const errors: string[] = [];
  page.on('pageerror', (e) => errors.push(`pageerror: ${e.message}`));
  page.on('console', (m) => {
    if (m.type() === 'error') errors.push(`console: ${m.text()}`);
  });

  await page.goto('/');
  await expect(page).toHaveTitle(/Hearthroot Hollow/);
  await expect(page.locator('#title-screen h1')).toContainText('Hearthroot');

  // fresh farm
  await page.evaluate(() => localStorage.clear());
  await page.click('#btn-new');
  await expect.poll(() => page.evaluate(() => window.__game.phase)).toBe('play');

  const start = await state(page);
  expect(start.day).toBe(1);
  expect(start.energy).toBe(30);

  // --- walk: position must actually change ---
  await page.keyboard.down('ArrowUp');
  await page.waitForTimeout(400);
  await page.keyboard.up('ArrowUp');
  const afterWalk = await state(page);
  expect(afterWalk.pos.y).toBeLessThan(start.pos.y - 0.5);

  // --- stand below field tile (4,3), facing up ---
  await walkTo(page, 8, 4);
  await walkTo(page, 4, 4);
  await face(page, 'ArrowUp');
  expect(await state(page).then((s) => s.target)).toEqual({ x: 4, y: 3 });
  expect((await tileAt(page, 4, 3))!.kind).toBe('dirt');

  // --- till ---
  await page.keyboard.press('1');
  await useTool(page);
  expect((await tileAt(page, 4, 3))!.kind).toBe('soil');

  // --- plant (default seed = turnip) ---
  await page.keyboard.press('3');
  await useTool(page);
  expect((await tileAt(page, 4, 3))!.crop).toEqual({ type: 'turnip', stage: 0 });

  // --- water ---
  await page.keyboard.press('2');
  await useTool(page);
  expect((await tileAt(page, 4, 3))!.watered).toBe(true);

  const energyAfterWork = (await state(page)).energy;
  expect(energyAfterWork).toBe(27); // 3 tool uses

  // --- sleep through 4 days, watering each morning ---
  for (let i = 0; i < 4; i++) {
    await page.click('#sleep-btn');
    await expect.poll(() => page.evaluate(() => window.__game.phase)).toBe('sleep');
    await page.click('#btn-wake');
    await expect.poll(() => page.evaluate(() => window.__game.phase)).toBe('play');
    // energy refilled, clock reset
    const s = await state(page);
    expect(s.energy).toBe(30);
    expect(s.time).toBeLessThan(0.05);
    // water the crop again (player hasn't moved; still facing the tile)
    await page.keyboard.press('2');
    await useTool(page);
  }

  const grown = await state(page);
  expect(grown.day).toBe(5);
  expect((await tileAt(page, 4, 3))!.crop).toEqual({ type: 'turnip', stage: 2 }); // mature

  // --- harvest ---
  await page.keyboard.press('4');
  await useTool(page);
  expect((await tileAt(page, 4, 3))!.crop).toBeNull();
  expect((await state(page)).inventory).toEqual({ turnip: 1 });

  // --- sell: shipping happens on sleep ---
  const moneyBefore = (await state(page)).money;
  await page.click('#sleep-btn');
  await expect(page.locator('#sleep-screen')).toBeVisible();
  await expect(page.locator('#shipped-list')).toContainText('Turnip');
  await page.click('#btn-wake');
  const moneyAfter = (await state(page)).money;
  expect(moneyAfter).toBeGreaterThan(moneyBefore);
  expect(moneyAfter).toBe(35); // turnip sell price
  expect((await state(page)).inventory).toEqual({});

  // --- save survives reload ---
  await page.reload();
  await page.click('#btn-continue');
  await expect.poll(() => page.evaluate(() => window.__game.phase)).toBe('play');
  const restored = await state(page);
  expect(restored.money).toBe(35);
  expect(restored.day).toBe(6);

  expect(errors, `console/page errors:\n${errors.join('\n')}`).toEqual([]);
});

test('click-to-act, camera follow, tileAt safety', async ({ page }) => {
  const errors: string[] = [];
  page.on('pageerror', (e) => errors.push(e.message));
  page.on('console', (m) => {
    if (m.type() === 'error') errors.push(m.text());
  });

  await page.goto('/');
  await page.evaluate(() => localStorage.clear());
  await page.click('#btn-new');
  await expect.poll(() => page.evaluate(() => window.__game.phase)).toBe('play');

  // --- tileAt shrugs off garbage coords ---
  const safety = await page.evaluate(() => [
    window.__game.tileAt(NaN, NaN),
    window.__game.tileAt(-3, 999),
    window.__game.tileAt(2.7, 3.2)?.kind ?? null,
  ]);
  expect(safety[0]).toBeNull();
  expect(safety[1]).toBeNull();
  expect(typeof safety[2]).toBe('string'); // fractional coords floor, not crash

  // --- click an adjacent dirt tile with the hoe: tills it ---
  await page.keyboard.press('1');
  const feet = await page.evaluate(() => {
    const p = window.__game.playerPos;
    return { x: Math.floor(p.x), y: Math.floor(p.y) };
  });
  const target = { x: feet.x, y: feet.y - 1 };
  expect((await tileAt(page, target.x, target.y))!.kind).toBe('dirt');
  const clickPos = await page.evaluate(([x, y]) => window.__game.screenPos(x, y), [target.x, target.y]);
  await page.mouse.click(clickPos.x, clickPos.y);
  await page.waitForTimeout(350);
  expect((await tileAt(page, target.x, target.y))!.kind).toBe('soil');

  // --- clicking a tile out of reach does nothing ---
  const far = { x: feet.x, y: feet.y - 3 };
  const farPos = await page.evaluate(([x, y]) => window.__game.screenPos(x, y), [far.x, far.y]);
  await page.mouse.click(farPos.x, farPos.y);
  await page.waitForTimeout(350);
  expect((await tileAt(page, far.x, far.y))!.kind).toBe('dirt');

  // --- camera keeps the farmer comfortably on screen walking south ---
  // (regression: on wide viewports the camera used to pin while the player
  //  walked off the bottom edge)
  await page.keyboard.down('ArrowDown');
  await page.waitForTimeout(1600);
  await page.keyboard.up('ArrowDown');
  await page.keyboard.down('ArrowLeft');
  await page.waitForTimeout(1200);
  await page.keyboard.up('ArrowLeft');
  await page.keyboard.down('ArrowDown');
  await page.waitForTimeout(2600);
  await page.keyboard.up('ArrowDown');
  const sp = await page.evaluate(() => {
    const p = window.__game.playerPos;
    return window.__game.screenPos(Math.floor(p.x), Math.floor(p.y));
  });
  const vp = page.viewportSize()!;
  expect(sp.y).toBeGreaterThan(vp.height * 0.12);
  expect(sp.y).toBeLessThan(vp.height * 0.88);

  expect(errors).toEqual([]);
});

test('title screen, debug hook shape, hud', async ({ page }) => {
  const errors: string[] = [];
  page.on('pageerror', (e) => errors.push(e.message));
  page.on('console', (m) => {
    if (m.type() === 'error') errors.push(m.text());
  });

  await page.goto('/');
  await expect(page.locator('#btn-new')).toBeVisible();

  const hook = await page.evaluate(() => ({
    phase: window.__game.phase,
    day: typeof window.__game.day,
    energy: typeof window.__game.energy,
    money: typeof window.__game.money,
    inventory: typeof window.__game.inventory,
    playerPos: typeof window.__game.playerPos,
    time: typeof window.__game.time,
  }));
  expect(hook.phase).toBe('title');
  expect(hook.day).toBe('number');
  expect(hook.energy).toBe('number');
  expect(hook.money).toBe('number');
  expect(hook.inventory).toBe('object');
  expect(hook.playerPos).toBe('object');
  expect(hook.time).toBe('number');

  await page.evaluate(() => localStorage.clear());
  await page.click('#btn-new');
  await expect(page.locator('#hotbar .slot')).toHaveCount(4);
  await expect(page.locator('#slot-hoe')).toHaveClass(/selected/);
  // tool switching via keyboard reflects in the HUD
  await page.keyboard.press('4');
  await expect(page.locator('#slot-scythe')).toHaveClass(/selected/);
  // seed slot cycles crop types on re-select
  await page.keyboard.press('3');
  await expect(page.locator('#label-seeds')).toHaveText('Turnip');
  await page.keyboard.press('3');
  await expect(page.locator('#label-seeds')).toHaveText('Tomato');

  expect(errors).toEqual([]);
});
