'use strict';
const db = require('../db/db');
const { awardXP } = require('./gamificationController');

/* ── Crossword generator (improved) ──────────────────────────────────── */
// Algorithm based on MichaelWehar/crossword-layout-generator scoring with additions:
//  1. Enumerate candidates from already-placed words (only perpendicular directions)
//  2. Weighted score: connections 70% + center 15% + orientation balance 10% + length 5%
//  3. Two-pass: retry skipped words after full first pass
//  4. Random pick from top-3 candidates per attempt for variety
//  5. 120 attempts, keeping best result by placed-word count
const CW_GRID      = 20;
const CW_MAX_WORDS = 10;
const CW_ATTEMPTS  = 120;

function _shuffle(arr) {
  const a = [...arr];
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

function _cwPlace(grid, word, row, col, dir) {
  for (let i = 0; i < word.length; i++) {
    if (dir === 'across') grid[row][col + i] = word[i];
    else                  grid[row + i][col] = word[i];
  }
}

// Returns number of valid intersections (≥0) if placement is valid, -1 if invalid
function _cwCheck(grid, word, row, col, dir, N) {
  if (row < 0 || col < 0) return -1;
  const endR = dir === 'down'   ? row + word.length - 1 : row;
  const endC = dir === 'across' ? col + word.length - 1 : col;
  if (endR >= N || endC >= N) return -1;

  // No letter immediately before/after word in its own direction
  if (dir === 'across') {
    if (col > 0      && grid[row][col - 1])      return -1;
    if (endC < N - 1 && grid[row][endC + 1])     return -1;
  } else {
    if (row > 0      && grid[row - 1][col])      return -1;
    if (endR < N - 1 && grid[endR + 1][col])     return -1;
  }

  let intersections = 0;
  for (let i = 0; i < word.length; i++) {
    const r = dir === 'across' ? row     : row + i;
    const c = dir === 'across' ? col + i : col;
    const ex = grid[r][c];
    if (ex !== null) {
      if (ex !== word[i]) return -1;   // letter conflict
      intersections++;
    } else {
      // Empty cell — perpendicular neighbours must be empty
      if (dir === 'across') {
        if (r > 0     && grid[r - 1][c]) return -1;
        if (r < N - 1 && grid[r + 1][c]) return -1;
      } else {
        if (c > 0     && grid[r][c - 1]) return -1;
        if (c < N - 1 && grid[r][c + 1]) return -1;
      }
    }
  }
  return intersections;
}

// Find all valid placements by iterating over already-placed words.
// Only tries the direction PERPENDICULAR to each placed word — guaranteed crossing.
function _cwFindPlacements(grid, word, placed, N, center) {
  const seen       = new Set();
  const candidates = [];
  const acrossCount = placed.filter(p => p.dir === 'across').length;
  const downCount   = placed.length - acrossCount;

  for (const pw of placed) {
    const newDir = pw.dir === 'across' ? 'down' : 'across';
    for (let wi = 0; wi < word.length; wi++) {
      for (let pi = 0; pi < pw.word.length; pi++) {
        if (word[wi] !== pw.word[pi]) continue;

        // Intersection cell on the grid
        const ir = pw.dir === 'across' ? pw.row       : pw.row + pi;
        const ic = pw.dir === 'across' ? pw.col + pi  : pw.col;

        // Start of new word so that letter wi lands at (ir, ic)
        const r = newDir === 'across' ? ir       : ir - wi;
        const c = newDir === 'across' ? ic - wi  : ic;

        const key = `${r},${c},${newDir}`;
        if (seen.has(key)) continue;
        seen.add(key);

        const intersections = _cwCheck(grid, word, r, c, newDir, N);
        if (intersections < 1) continue;

        // Weighted score (MichaelWehar 70/15/10/5 split)
        const maxDist = center * Math.SQRT2;
        const dist    = Math.hypot(r - center, c - center);
        const conn    = intersections / (word.length / 2);
        const cen     = 1 - dist / maxDist;
        const oBal    = newDir === 'down'
          ? (acrossCount >= downCount   ? 0.1 : 0)
          : (downCount   >= acrossCount ? 0.1 : 0);
        const len     = word.length / N;
        const score   = conn * 0.7 + cen * 0.15 + oBal * 0.1 + len * 0.05;

        candidates.push({ row: r, col: c, dir: newDir, intersections, score });
      }
    }
  }
  return candidates;
}

function _buildAttempt(words, N) {
  const grid   = Array.from({ length: N }, () => Array(N).fill(null));
  const placed = [];
  const center = Math.floor(N / 2);

  // First word: placed across at center
  const first = words[0];
  const r0 = center;
  const c0 = Math.max(1, center - Math.floor(first.word.length / 2));
  if (c0 + first.word.length > N - 1) return { placed, grid };
  _cwPlace(grid, first.word, r0, c0, 'across');
  placed.push({ ...first, row: r0, col: c0, dir: 'across' });

  const skipped = [];
  for (const pass of [words.slice(1), skipped]) {
    for (const w of pass) {
      if (placed.length >= CW_MAX_WORDS) break;
      const cands = _cwFindPlacements(grid, w.word, placed, N, center);
      if (!cands.length) {
        if (pass !== skipped) skipped.push(w);
        continue;
      }
      cands.sort((a, b) => b.score - a.score);
      // Pick randomly from top 3 for variety across attempts
      const pick = cands[Math.floor(Math.random() * Math.min(3, cands.length))];
      _cwPlace(grid, w.word, pick.row, pick.col, pick.dir);
      placed.push({ ...w, row: pick.row, col: pick.col, dir: pick.dir });
    }
  }

  return { placed, grid };
}

function buildCrossword(wordList) {
  const N = CW_GRID;
  let best = null;

  // Baseline: longest words first
  const sorted  = [...wordList].sort((a, b) => b.word.length - a.word.length);
  best = _buildAttempt(sorted, N);

  for (let a = 1; a < CW_ATTEMPTS; a++) {
    if (best.placed.length >= CW_MAX_WORDS) break;
    const shuffled = _shuffle(wordList);
    // Ensure a long word is near the front (better first placement)
    shuffled.sort((x, y) => (y.word.length >= 6 ? 1 : 0) - (x.word.length >= 6 ? 1 : 0));
    const attempt = _buildAttempt(shuffled, N);
    if (attempt.placed.length > best.placed.length) best = attempt;
  }

  if (!best || best.placed.length < 3) return null;

  // Compact: trim empty rows/cols
  const { placed, grid } = best;
  let minR = N, maxR = 0, minC = N, maxC = 0;
  for (let r = 0; r < N; r++)
    for (let c = 0; c < N; c++)
      if (grid[r][c]) {
        minR = Math.min(minR, r); maxR = Math.max(maxR, r);
        minC = Math.min(minC, c); maxC = Math.max(maxC, c);
      }

  const trimmed = [];
  for (let r = minR; r <= maxR; r++) trimmed.push(grid[r].slice(minC, maxC + 1));

  const words = placed.map(p => ({
    word: p.word, clue: p.clue,
    subjectName: p.subjectName,
    row: p.row - minR, col: p.col - minC, dir: p.dir,
  }));

  // Number words in reading order (top→bottom, left→right)
  words.sort((a, b) => a.row !== b.row ? a.row - b.row : a.col - b.col);
  const starts = new Map();
  let num = 1;
  for (const w of words) {
    const key = `${w.row},${w.col}`;
    if (!starts.has(key)) starts.set(key, num++);
    w.num = starts.get(key);
  }

  return { grid: trimmed, words, across: words.filter(w => w.dir === 'across'), down: words.filter(w => w.dir === 'down') };
}

/* ── GET /api/games/hangman/word?subject_slug=bio ─────────────────────── */
function hangmanWord(req, res) {
  const { subject_slug } = req.query;

  let row;
  if (subject_slug) {
    const subj = db.prepare('SELECT id FROM subjects WHERE slug = ?').get(subject_slug);
    if (!subj) return res.status(404).json({ error: 'Subject not found' });
    row = db.prepare(`
      SELECT t.id, t.name, s.name AS subject_name, s.slug AS subject_slug
      FROM topics t
      JOIN subjects s ON s.id = t.subject_id
      WHERE t.subject_id = ? AND length(t.name) >= 4
      ORDER BY RANDOM() LIMIT 1
    `).get(subj.id);
  } else {
    row = db.prepare(`
      SELECT t.id, t.name, s.name AS subject_name, s.slug AS subject_slug
      FROM topics t
      JOIN subjects s ON s.id = t.subject_id
      WHERE length(t.name) >= 4
      ORDER BY RANDOM() LIMIT 1
    `).get();
  }

  if (!row) return res.status(404).json({ error: 'No topics found' });

  res.json({
    topicId:     row.id,
    word:        row.name.toUpperCase(),
    hint:        row.subject_name,
    subjectSlug: row.subject_slug,
  });
}

/* ── POST /api/games/hangman/complete ─────────────────────────────────── */
function hangmanComplete(req, res) {
  const { won, errors } = req.body;
  if (typeof won !== 'boolean') return res.status(400).json({ error: 'won required' });

  let xpGain = 0;
  if (won) {
    // 15 XP perfect, -2 per error, min 5
    xpGain = Math.max(5, 15 - (Number(errors) || 0) * 2);
  }

  if (xpGain > 0) {
    try { awardXP(req.user.id, xpGain, 'hangman_win'); } catch (e) { console.error('[games] hangman XP:', e.message); }
  }

  res.json({ ok: true, xp: xpGain });
}

/* ── GET /api/games/crossword/generate?subject_slug= ──────────────────── */
function crosswordGenerate(req, res) {
  const { subject_slug } = req.query;

  let rows;
  const base = `
    SELECT t.id, t.name, s.name AS subject_name,
           (SELECT q.text FROM questions q WHERE q.topic_id = t.id ORDER BY RANDOM() LIMIT 1) AS clue
    FROM topics t
    JOIN subjects s ON s.id = t.subject_id
    WHERE length(t.name) BETWEEN 4 AND 12
      AND t.name NOT LIKE '% %'
      AND t.name NOT GLOB '*[0-9]*'
  `;

  if (subject_slug) {
    const subj = db.prepare('SELECT id FROM subjects WHERE slug = ?').get(subject_slug);
    if (!subj) return res.status(404).json({ error: 'Subject not found' });
    rows = db.prepare(base + ' AND t.subject_id = ? ORDER BY RANDOM() LIMIT 50').all(subj.id);
  } else {
    rows = db.prepare(base + ' ORDER BY RANDOM() LIMIT 50').all();
  }

  if (rows.length < 3) return res.status(404).json({ error: 'Not enough topics for a crossword' });

  const wordList = rows
    .filter(r => /^[А-яЁёA-Za-z]+$/.test(r.name))  // only letters, no numbers/symbols
    .map(r => ({
      word: r.name.toUpperCase(),
      clue: r.clue || r.subject_name,
      subjectName: r.subject_name,
    }));

  const crossword = buildCrossword(wordList);
  if (!crossword) return res.status(404).json({ error: 'Could not build crossword' });

  res.json(crossword);
}

/* ── POST /api/games/crossword/complete ───────────────────────────────── */
function crosswordComplete(req, res) {
  const { completed, hintsUsed } = req.body;
  if (typeof completed !== 'boolean') return res.status(400).json({ error: 'completed required' });

  let xpGain = 0;
  if (completed) {
    xpGain = Math.max(5, 20 - (Number(hintsUsed) || 0) * 3);
  }

  if (xpGain > 0) {
    try { awardXP(req.user.id, xpGain, 'crossword_win'); } catch (e) { console.error('[games] crossword XP:', e.message); }
  }

  res.json({ ok: true, xp: xpGain });
}

module.exports = { hangmanWord, hangmanComplete, crosswordGenerate, crosswordComplete };
