#!/usr/bin/env node

const fs = require('fs');
const path = require('path');

const repoRoot = path.resolve(__dirname, '..');
const rulesPath = path.join(repoRoot, 'database', 'rules', 'rules-database.json');
const db = JSON.parse(fs.readFileSync(rulesPath, 'utf8'));
const rules = db.rules || [];

const scenarios = [
  {
    name: 'Brother Kael Dodges a Warrior',
    tableMoment: 'A Tyranid Warrior hits Brother Kael in melee. The player asks whether he can spend his Reaction to avoid the hit.',
    query: 'dodge reaction',
    mustFind: ['Dodge'],
    formatChecks: [
      { title: 'Dodge', category: 'actions', source: 'CR', minConfidence: 0.9 }
    ],
    worksIf: 'Dodge appears before broad combat pages and has a CR page citation.'
  },
  {
    name: 'Devastator Uses Full Auto',
    tableMoment: 'Brother Aeldan fires a heavy bolter on full auto and needs the additional-hit rule.',
    query: 'full auto burst additional hits',
    mustFind: ['Full Auto Burst'],
    formatChecks: [
      { title: 'Full Auto Burst', category: 'actions', source: 'CR', minConfidence: 0.9 }
    ],
    worksIf: 'The card distinguishes Full Auto from Semi-Auto and links to Weapon Jams.'
  },
  {
    name: 'GM Runs a Horde',
    tableMoment: 'The GM turns thirty Hormagaunts into one Horde and needs magnitude/damage handling.',
    query: 'horde magnitude damage',
    mustFind: ['Hordes'],
    formatChecks: [
      { title: 'Hordes', category: 'hordes', source: 'CR', minConfidence: 0.9 }
    ],
    worksIf: 'Hordes is found as its own card instead of buried in adversary text.'
  },
  {
    name: 'Kill-team Takes Blast Damage in Squad Mode',
    tableMoment: 'A blast weapon hits a Battle-Brother in Squad Mode. The GM checks if the team loses Cohesion.',
    query: 'cohesion damage blast command test',
    mustFind: ['Cohesion Damage'],
    formatChecks: [
      { title: 'Cohesion Damage', category: 'cohesion', source: 'CR', minConfidence: 0.9 }
    ],
    worksIf: 'The result points to Cohesion Damage, not only the general Cohesion card.'
  },
  {
    name: 'Librarian Triggers Perils',
    tableMoment: 'The Librarian pushes a psychic power and rolls badly. The player asks for the Perils table.',
    query: 'perils of the warp psychic backlash',
    mustFind: ['Perils of the Warp'],
    formatChecks: [
      { title: 'Perils of the Warp', category: 'psychic', source: 'CR', minConfidence: 0.9 }
    ],
    worksIf: 'The Perils table is directly discoverable from common player language.'
  },
  {
    name: 'Fear Test in the Genatorium',
    tableMoment: 'A daemonhost manifests in a ruined genatorium. The GM needs the Fear test modifier.',
    query: 'fear test modifier',
    mustFind: ['Fear'],
    formatChecks: [
      { title: 'Fear', category: 'conditions', source: 'CR', minConfidence: 0.9 }
    ],
    worksIf: 'Fear outranks generic test difficulty and trait references.'
  },
  {
    name: 'Mission Prep Requisition',
    tableMoment: 'Before extraction, players pool Requisition for a lascannon and ask how Renown gates it.',
    query: 'requisition renown pooling',
    mustFind: ['Requisition', 'Renown'],
    formatChecks: [
      {
        title: 'Requisition',
        category: 'requisition',
        source: 'CR',
        minConfidence: 0.9,
        sourceMethod: 'pdf+curated',
        requiredSections: ['Quick Use', 'Pooling', 'Renown Gate', 'Availability to Requisition', 'Mid-play Ruling']
      },
      {
        title: 'Renown',
        category: 'renown',
        source: 'CR',
        minConfidence: 0.9,
        sourceMethod: 'pdf+curated',
        requiredSections: ['Quick Use', 'Renown Ranks', 'Mid-play Ruling']
      }
    ],
    worksIf: 'The answer is formatted as a play aid rather than raw PDF prose.'
  }
];

function fields(rule) {
  return [
    rule.title,
    rule.summary,
    rule.content,
    rule.category,
    ...(rule.tags || []),
    ...(rule.aliases || [])
  ].map(v => String(v || '').toLowerCase());
}

function score(rule, query) {
  const term = query.toLowerCase();
  const tokens = term.split(/\s+/).filter(Boolean);
  const title = String(rule.title || '').toLowerCase();
  const haystacks = fields(rule);
  return (title === term ? 100 : 0) +
    (title.includes(term) ? 45 : 0) +
    Number(rule.midgamePriority || 0) +
    tokens.reduce((sum, token) => sum + haystacks.reduce((hits, value) => hits + (value.includes(token) ? 1 : 0), 0), 0);
}

function search(query, limit = 5) {
  const term = query.toLowerCase();
  const tokens = term.split(/\s+/).filter(Boolean);
  return rules
    .filter(rule => {
      const haystacks = fields(rule);
      return haystacks.some(value => value.includes(term)) ||
        tokens.every(token => haystacks.some(value => value.includes(token)));
    })
    .sort((a, b) => score(b, query) - score(a, query))
    .slice(0, limit);
}

function assertRuleFormat(check) {
  const rule = rules.find(r => r.title === check.title);
  const errors = [];

  if (!rule) return [`Missing rule card: ${check.title}`];
  if (rule.category !== check.category) errors.push(`${check.title}: category ${rule.category} !== ${check.category}`);
  if (rule.sourceAbbr !== check.source) errors.push(`${check.title}: source ${rule.sourceAbbr} !== ${check.source}`);
  if (!rule.page && rule.page !== 0) errors.push(`${check.title}: missing page`);
  if (!rule.sourceMethod) errors.push(`${check.title}: missing sourceMethod`);
  if (check.sourceMethod && rule.sourceMethod !== check.sourceMethod) errors.push(`${check.title}: sourceMethod ${rule.sourceMethod} !== ${check.sourceMethod}`);
  if (Number(rule.confidence || 0) < check.minConfidence) errors.push(`${check.title}: confidence ${rule.confidence} < ${check.minConfidence}`);
  if (!rule.summary) errors.push(`${check.title}: missing summary`);
  if (!rule.content || rule.content.length < 80) errors.push(`${check.title}: content too short`);
  if (!Array.isArray(rule.relatedRules)) errors.push(`${check.title}: relatedRules is not an array`);

  for (const section of check.requiredSections || []) {
    if (!rule.content.includes(section)) errors.push(`${check.title}: missing section "${section}"`);
  }

  return errors;
}

let failures = 0;
console.log('In-game rules format and browse verification\n');

for (const scenario of scenarios) {
  const results = search(scenario.query);
  const titles = results.map(r => r.title);
  const lookupOk = scenario.mustFind.every(title => titles.slice(0, 4).includes(title));
  const formatErrors = scenario.formatChecks.flatMap(assertRuleFormat);
  const ok = lookupOk && formatErrors.length === 0;
  if (!ok) failures += 1;

  console.log(`${ok ? 'PASS' : 'FAIL'} ${scenario.name}`);
  console.log(`  Moment: ${scenario.tableMoment}`);
  console.log(`  Query: ${scenario.query}`);
  console.log(`  Top results: ${titles.slice(0, 4).join(' | ') || '(none)'}`);
  console.log(`  Works if: ${scenario.worksIf}`);
  for (const error of formatErrors) console.log(`  Format error: ${error}`);
  if (!lookupOk) console.log(`  Lookup error: expected ${scenario.mustFind.join(', ')} in top 4 results`);
  console.log('');
}

if (failures) {
  console.error(`${failures} scenario(s) failed.`);
  process.exit(1);
}

console.log('All invented in-game scenarios passed lookup and format checks.');
