/**
 * Snake game extension - play snake with /snake command
 */

import type { ExtensionAPI } from 'shortcutxl/app';
import { matchesKey, visibleWidth } from 'shortcutxl/tui';

const GAME_WIDTH = 40;
const GAME_HEIGHT = 15;
const TICK_MS = 100;

type Direction = 'up' | 'down' | 'left' | 'right';
type Point = { x: number; y: number };

interface GameState {
  snake: Point[];
  food: Point;
  direction: Direction;
  nextDirection: Direction;
  score: number;
  gameOver: boolean;
  highScore: number;
}

function createInitialState(): GameState {
  const startX = Math.floor(GAME_WIDTH / 2);
  const startY = Math.floor(GAME_HEIGHT / 2);
  return {
    snake: [
      { x: startX, y: startY },
      { x: startX - 1, y: startY },
      { x: startX - 2, y: startY }
    ],
    food: spawnFood([{ x: startX, y: startY }]),
    direction: 'right',
    nextDirection: 'right',
    score: 0,
    gameOver: false,
    highScore: 0
  };
}

function spawnFood(snake: Point[]): Point {
  let food: Point;
  do {
    food = {
      x: Math.floor(Math.random() * GAME_WIDTH),
      y: Math.floor(Math.random() * GAME_HEIGHT)
    };
  } while (snake.some((s) => s.x === food.x && s.y === food.y));
  return food;
}

class SnakeComponent {
  private state: GameState;
  private interval: ReturnType<typeof setInterval> | null = null;
  private onClose: () => void;
  private onSave: (state: GameState | null) => void;
  private tui: { requestRender: () => void };
  private cachedLines: string[] = [];
  private cachedWidth = 0;
  private version = 0;
  private cachedVersion = -1;
  private paused: boolean;

  constructor(
    tui: { requestRender: () => void },
    onClose: () => void,
    onSave: (state: GameState | null) => void,
    savedState?: GameState
  ) {
    this.tui = tui;
    if (savedState && !savedState.gameOver) {
      // Resume from saved state, start paused
      this.state = savedState;
      this.paused = true;
    } else {
      // New game or saved game was over
      this.state = createInitialState();
      if (savedState) {
        this.state.highScore = savedState.highScore;
      }
      this.paused = false;
      this.startGame();
    }
    this.onClose = onClose;
    this.onSave = onSave;
  }

  private startGame(): void {
    this.interval = setInterval(() => {
      if (!this.state.gameOver) {
        this.tick();
        this.version++;
        this.tui.requestRender();
      }
    }, TICK_MS);
  }

  private tick(): void {
    // Apply queued direction change
    this.state.direction = this.state.nextDirection;

    // Calculate new head position
    const head = this.state.snake[0];
    let newHead: Point;

    switch (this.state.direction) {
      case 'up':
        newHead = { x: head.x, y: head.y - 1 };
        break;
      case 'down':
        newHead = { x: head.x, y: head.y + 1 };
        break;
      case 'left':
        newHead = { x: head.x - 1, y: head.y };
        break;
      case 'right':
        newHead = { x: head.x + 1, y: head.y };
        break;
    }

    // Check wall collision
    if (newHead.x < 0 || newHead.x >= GAME_WIDTH || newHead.y < 0 || newHead.y >= GAME_HEIGHT) {
      this.state.gameOver = true;
      return;
    }

    // Check self collision
    if (this.state.snake.some((s) => s.x === newHead.x && s.y === newHead.y)) {
      this.state.gameOver = true;
      return;
    }

    // Move snake
    this.state.snake.unshift(newHead);

    // Check food collision
    if (newHead.x === this.state.food.x && newHead.y === this.state.food.y) {
      this.state.score += 10;
      if (this.state.score > this.state.highScore) {
        this.state.highScore = this.state.score;
      }
      this.state.food = spawnFood(this.state.snake);
    } else {
      this.state.snake.pop();
    }
  }

  handleInput(data: string): void {
    // If paused (resuming), wait for any key
    if (this.paused) {
      if (matchesKey(data, 'escape') || data === 'q' || data === 'Q') {
        // Quit without clearing save
        this.dispose();
        this.onClose();
        return;
      }
      // Any other key resumes
      this.paused = false;
      this.startGame();
      return;
    }

    // ESC to pause and save
    if (matchesKey(data, 'escape')) {
      this.dispose();
      this.onSave(this.state);
      this.onClose();
      return;
    }

    // Q to quit without saving (clears saved state)
    if (data === 'q' || data === 'Q') {
      this.dispose();
      this.onSave(null); // Clear saved state
      this.onClose();
      return;
    }

    // Arrow keys or WASD
    if (matchesKey(data, 'up') || data === 'w' || data === 'W') {
      if (this.state.direction !== 'down') this.state.nextDirection = 'up';
    } else if (matchesKey(data, 'down') || data === 's' || data === 'S') {
      if (this.state.direction !== 'up') this.state.nextDirection = 'down';
    } else if (matchesKey(data, 'right') || data === 'd' || data === 'D') {
      if (this.state.direction !== 'left') this.state.nextDirection = 'right';
    } else if (matchesKey(data, 'left') || data === 'a' || data === 'A') {
      if (this.state.direction !== 'right') this.state.nextDirection = 'left';
    }

    // Restart on game over
    if (this.state.gameOver && (data === 'r' || data === 'R' || data === ' ')) {
      const highScore = this.state.highScore;
      this.state = createInitialState();
      this.state.highScore = highScore;
      this.onSave(null); // Clear saved state on restart
      this.version++;
      this.tui.requestRender();
    }
  }

  invalidate(): void {
    this.cachedWidth = 0;
  }

  render(width: number): string[] {
    if (width === this.cachedWidth && this.cachedVersion === this.version) {
      return this.cachedLines;
    }

    const lines: string[] = [];

    // Each game cell is 2 chars wide to appear square (terminal cells are ~2:1 aspect)
    const cellWidth = 2;
    const effectiveWidth = Math.min(GAME_WIDTH, Math.floor((width - 4) / cellWidth));
    const effectiveHeight = GAME_HEIGHT;

    // Colors
    const dim = (s: string) => `\x1b[2m${s}\x1b[22m`;
    const green = (s: string) => `\x1b[32m${s}\x1b[0m`;
    const red = (s: string) => `\x1b[31m${s}\x1b[0m`;
    const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`;
    const bold = (s: string) => `\x1b[1m${s}\x1b[22m`;

    const boxWidth = effectiveWidth * cellWidth;

    // Helper to pad content inside box
    const boxLine = (content: string) => {
      const contentLen = visibleWidth(content);
      const padding = Math.max(0, boxWidth - contentLen);
      return dim(' │') + content + ' '.repeat(padding) + dim('│');
    };

    // Top border
    lines.push(this.padLine(dim(` ╭${'─'.repeat(boxWidth)}╮`), width));

    // Header with score
    const scoreText = `Score: ${bold(yellow(String(this.state.score)))}`;
    const highText = `High: ${bold(yellow(String(this.state.highScore)))}`;
    const title = `${bold(green('SNAKE'))} │ ${scoreText} │ ${highText}`;
    lines.push(this.padLine(boxLine(title), width));

    // Separator
    lines.push(this.padLine(dim(` ├${'─'.repeat(boxWidth)}┤`), width));

    // Game grid
    for (let y = 0; y < effectiveHeight; y++) {
      let row = '';
      for (let x = 0; x < effectiveWidth; x++) {
        const isHead = this.state.snake[0].x === x && this.state.snake[0].y === y;
        const isBody = this.state.snake.slice(1).some((s) => s.x === x && s.y === y);
        const isFood = this.state.food.x === x && this.state.food.y === y;

        if (isHead) {
          row += green('██'); // Snake head (2 chars)
        } else if (isBody) {
          row += green('▓▓'); // Snake body (2 chars)
        } else if (isFood) {
          row += red('◆ '); // Food (2 chars)
        } else {
          row += '  '; // Empty cell (2 spaces)
        }
      }
      lines.push(this.padLine(dim(' │') + row + dim('│'), width));
    }

    // Separator
    lines.push(this.padLine(dim(` ├${'─'.repeat(boxWidth)}┤`), width));

    // Footer
    let footer: string;
    if (this.paused) {
      footer = `${yellow(bold('PAUSED'))} Press any key to continue, ${bold('Q')} to quit`;
    } else if (this.state.gameOver) {
      footer = `${red(bold('GAME OVER!'))} Press ${bold('R')} to restart, ${bold('Q')} to quit`;
    } else {
      footer = `↑↓←→ or WASD to move, ${bold('ESC')} pause, ${bold('Q')} quit`;
    }
    lines.push(this.padLine(boxLine(footer), width));

    // Bottom border
    lines.push(this.padLine(dim(` ╰${'─'.repeat(boxWidth)}╯`), width));

    this.cachedLines = lines;
    this.cachedWidth = width;
    this.cachedVersion = this.version;

    return lines;
  }

  private padLine(line: string, width: number): string {
    // Calculate visible length (strip ANSI codes)
    const visibleLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
    const padding = Math.max(0, width - visibleLen);
    return line + ' '.repeat(padding);
  }

  dispose(): void {
    if (this.interval) {
      clearInterval(this.interval);
      this.interval = null;
    }
  }
}

const SNAKE_SAVE_TYPE = 'snake-save';

export default function (shortcut: ExtensionAPI) {
  shortcut.registerCommand('snake', {
    description: 'Play Snake!',

    handler: async (_args, ctx) => {
      if (!ctx.hasUI) {
        ctx.ui.notify('Snake requires interactive mode', 'error');
        return;
      }

      // Load saved state from session
      const entries = ctx.sessionManager.getEntries();
      let savedState: GameState | undefined;
      for (let i = entries.length - 1; i >= 0; i--) {
        const entry = entries[i];
        if (entry.type === 'custom' && entry.customType === SNAKE_SAVE_TYPE) {
          savedState = entry.data as GameState;
          break;
        }
      }

      await ctx.ui.custom((tui, _theme, _kb, done) => {
        return new SnakeComponent(
          tui,
          () => done(undefined),
          (state) => {
            // Save or clear state
            shortcut.appendEntry(SNAKE_SAVE_TYPE, state);
          },
          savedState
        );
      });
    }
  });
}
