// scripts/mines-batamote-llm-demo.mjs
//
// REAL agent + LLM + RAG demonstration on the Batamote dataset.
//
// Unlike scripts/mines-batamote-demo.mjs (which hand-seeds facts and calls the
// adapters directly), this driver proves the autonomous path:
//
//   1. Reconfigure the Mines agents onto the `anthropic` engine (server-native
//      governed tool_use loop) and grant the tool permissions they were missing.
//   2. Agent tick — mines-data-interpreter calls mines_extract_facts: the LLM
//      reads the 43-101 chunks (RAG corpus) and emits provenance-cited facts.
//      Every fact is validated against a real chunk + verbatim quote; uncited
//      facts are rejected. THIS is the LLM doing the extraction.
//   3. Deterministic valuation tools (no LLM math) — assumption_validate + run.
//   4. LLM authors the investor narrative prose (numbers are NOT authored here —
//      they are rendered by the report tool from cited DB rows).
//   5. Agent tick — mines-report-packager generates the investor presentation
//      into the dashboard space.
//   6. Deterministic mines_report_generate for the remaining report kinds, all
//      published into the Batamote dashboard space.
//
// Requires a working Anthropic key (vault via MADARX_MASTER_KEY, or
// ANTHROPIC_API_KEY in the environment).
//
// Run:
//   ANTHROPIC_API_KEY=sk-... node scripts/mines-batamote-llm-demo.mjs
//   # or, to decrypt the vault key:
//   MADARX_MASTER_KEY=<base64> node scripts/mines-batamote-llm-demo.mjs

process.env.MADARX_DISABLE_INPROC_DAEMON = '1';
// Give the agent ticks room to emit tool calls + narrative.
if (!process.env.MAX_TOKENS_PER_TICK) process.env.MAX_TOKENS_PER_TICK = '4096';

const USER = 'local-user';
const ORG = 'personal-org';
const MODEL = 'claude-sonnet-4-6';

const { getDb, nowIso, resetDbHandle } = await import('../lib/db.js');
const { resolveAnthropicApiKey } = await import('../lib/anthropic-key.js');
const { setAgentToolPermission } = await import('../lib/runtime.js');
const { runAgentTick } = await import('../lib/agent-runner.js');
const { chatWithClaude } = await import('../lib/claude.js');
const { retrieveChunks } = await import('../lib/mines/rag.js');
const { embedText } = await import('../lib/mines/embeddings.js');
const { createSpace, getSpace, listSpaces, moveArtifactToSpace } = await import('../lib/dashboard-spaces.js');
const { minesExtractFacts } = await import('../lib/tools/adapters/mines/extract-facts.js');
const { minesAssumptionValidate } = await import('../lib/tools/adapters/mines/assumption-validate.js');
const { minesValuationRun } = await import('../lib/tools/adapters/mines/valuation-run.js');
const { minesReportGenerate } = await import('../lib/tools/adapters/mines/report-generate.js');

const db = getDb();
const log = (step, msg) => console.log(`\n[${step}] ${msg}`);

// ── Preflight: LLM key ──────────────────────────────────────────────────────
if (!resolveAnthropicApiKey()) {
  console.error(`
FAILED: no Anthropic API key resolved.

This demo runs real LLM calls (fact extraction + narrative authoring). Provide a key:
  export ANTHROPIC_API_KEY=sk-ant-...        # simplest
or unlock the vault that already stores it:
  export MADARX_MASTER_KEY=<your-base64-key>

Then re-run: node scripts/mines-batamote-llm-demo.mjs
`);
  resetDbHandle();
  process.exit(1);
}

// ── Agent setup: anthropic engine + tool permissions ────────────────────────
const AGENT_TOOLS = {
  'mines-data-interpreter': ['mines_project_read', 'mines_document_extract', 'mines_extract_facts', 'mines_assumption_validate'],
  'mines-report-packager': ['mines_project_read', 'mines_report_generate'],
  'mines-valuation-analyst': ['mines_project_read', 'mines_assumption_validate', 'mines_valuation_run', 'mines_sensitivity_run'],
  'mines-analyst': ['mines_project_read', 'mines_extract_facts', 'mines_report_generate'],
};

function configureAgents() {
  const upd = db.prepare("UPDATE agents SET engine = 'anthropic', model = ? WHERE id = ?");
  for (const [agentId, tools] of Object.entries(AGENT_TOOLS)) {
    if (!db.prepare('SELECT 1 FROM agents WHERE id = ?').get(agentId)) { console.log(`  ! ${agentId} missing — skip`); continue; }
    upd.run(MODEL, agentId);
    for (const t of tools) setAgentToolPermission(agentId, t, 'auto');
    console.log(`  • ${agentId}: engine=anthropic model=${MODEL} tools=[${tools.join(', ')}]`);
  }
}

function ensureBatamoteSpace(project) {
  const existing = listSpaces().spaces.find((s) => s.name === 'Batamote');
  const space = existing || createSpace({ name: 'Batamote' });
  db.prepare('UPDATE mines_projects SET default_space_id = ?, updated_at = ? WHERE id = ?').run(space.id, nowIso(), project.id);
  return space;
}

// ── LLM narrative authoring (prose only — never numbers) ────────────────────
const NARRATIVE_TOOL = {
  name: 'emit_investor_narrative',
  description: 'Emit the prose narrative slots for an investor presentation. Call exactly once.',
  input_schema: {
    type: 'object',
    properties: {
      hero_headline: { type: 'string', description: 'One punchy line, < 12 words.' },
      hero_subhead: { type: 'string', description: '1–2 sentences framing the opportunity.' },
      opportunity: { type: 'array', items: { type: 'string' }, description: '2–3 short paragraphs on why this ground.' },
      findings_lead: { type: 'string' },
      upside_lead: { type: 'string' },
      risk_lead: { type: 'string', description: 'Lead with the risk honestly.' },
      the_ask: { type: 'string' },
    },
    required: ['hero_headline', 'hero_subhead', 'opportunity', 'findings_lead', 'upside_lead', 'risk_lead', 'the_ask'],
  },
};

async function authorNarrative(project) {
  const facts = db.prepare('SELECT fact_type, value_text FROM mines_extracted_facts WHERE project_id = ? ORDER BY fact_type').all(project.id);
  let ragText = '';
  try {
    const { chunks } = await retrieveChunks(project.id, `${project.name} geology mineralization targets copper porphyry exploration`, 5);
    ragText = chunks.map((c) => `- (p${c.page ?? '?'}) ${String(c.text || '').replace(/\s+/g, ' ').slice(0, 300)}`).join('\n');
  } catch { /* RAG optional */ }

  const system = [
    'You are an investment-memo copywriter for a mining advisory. Write crisp, credible prose for a non-technical investor.',
    'Rules:',
    '1. NEVER state a numeric figure (grade, tonnage, value) — those are rendered separately from cited database rows. Write qualitative prose only.',
    '2. Lead with the risk. Never present a conceptual exploration target as a mineral resource.',
    '3. Ground every claim in the supplied facts. No outside knowledge, no invented geology.',
    '4. Emit via emit_investor_narrative exactly once.',
  ].join('\n');
  const user = `PROJECT: ${project.name} (${project.commodity || 'Cu'}, ${project.asset_stage || 'exploration'}).\n\nEXTRACTED FACTS:\n${facts.map((f) => `- ${f.fact_type}: ${f.value_text}`).join('\n') || '(none)'}\n\nSOURCE EXCERPTS:\n${ragText || '(none)'}\n\nWrite the investor narrative now.`;

  try {
    const res = await chatWithClaude([{ role: 'user', content: user }], system, [NARRATIVE_TOOL],
      { max_tokens: 2048, temperature: 0.3, tool_choice: { type: 'tool', name: 'emit_investor_narrative' } });
    const tu = (res?.content || []).find((b) => b?.type === 'tool_use' && b.name === 'emit_investor_narrative');
    return tu?.input && typeof tu.input === 'object' ? tu.input : {};
  } catch (err) {
    log('narrative', `LLM narrative failed (${err.message}) — falling back to data-driven copy`);
    return {};
  }
}

function factCount(project) {
  return db.prepare('SELECT COUNT(*) n FROM mines_extracted_facts WHERE project_id = ?').get(project.id).n;
}

async function main() {
  const project = db.prepare("SELECT * FROM mines_projects WHERE slug = 'batamote'").get();
  if (!project) { console.error('FAILED: no batamote project. Run scripts/mines-batamote-demo.mjs once to import the dataset.'); resetDbHandle(); process.exit(1); }
  log('project', `${project.name} (${project.id})`);

  log('agents', 'configuring engine + tool permissions');
  configureAgents();

  const space = ensureBatamoteSpace(project);
  log('space', `publishing into "${space.name}" (${space.id})`);

  // Clear prior artifacts in the space so a re-run yields a clean set.
  const cleared = db.prepare('DELETE FROM artifacts WHERE space_id = ?').run(space.id).changes;
  if (cleared) console.log(`    cleared ${cleared} prior artifacts in space`);

  // Target the authoritative NI 43-101 technical report explicitly.
  const reportDoc = db.prepare(
    "SELECT id, file_name FROM mines_source_documents WHERE project_id = ? AND file_name LIKE '%43-101%' ORDER BY id LIMIT 1"
  ).get(project.id);
  if (reportDoc) console.log(`    extraction source: ${reportDoc.file_name} (${reportDoc.id})`);

  // RAG mode probe.
  const sem = (await embedText('copper porphyry').catch(() => null)) ? 'semantic (embeddings)' : 'text-search (LIKE fallback — install @xenova/transformers for semantic)';
  log('rag', `retrieval mode: ${sem}`);

  // Prove LLM extraction: wipe the hand-seeded facts first.
  const wiped = db.prepare('DELETE FROM mines_extracted_facts WHERE project_id = ?').run(project.id).changes;
  log('reset', `cleared ${wiped} pre-existing facts — they will be re-derived by the LLM`);

  // ── STEP 1: agent-driven LLM extraction ──
  log('extract', 'mines-data-interpreter tick → mines_extract_facts (LLM reads chunks, emits cited facts)');
  try {
    const srcArg = reportDoc ? ` source_document_id="${reportDoc.id}",` : '';
    const run = await runAgentTick('mines-data-interpreter', {
      triggered_by: 'manual',
      acting_on_behalf_of_user_id: USER,
      instruction: `Extract every available structured fact for the Batamote project from its NI 43-101 technical report using the mines_extract_facts tool with arguments: project_id="${project.id}",${srcArg} char_budget=90000. Then give a brief data-quality summary of what was written and what is missing.`,
    });
    console.log(`    tick complete (run ${run?.id || 'n/a'}); facts now in DB: ${factCount(project)}`);
  } catch (err) {
    console.log(`    agent tick error: ${err.message}`);
  }

  // Guarantee facts exist even if the model didn't call the tool.
  if (factCount(project) === 0) {
    log('extract', 'agent did not persist facts — invoking mines_extract_facts directly (still the LLM)');
    const r = await minesExtractFacts(
      { project_id: project.id, source_document_id: reportDoc?.id, char_budget: 90000 },
      { agent_id: 'mines-data-interpreter', user_id: USER });
    console.log(`    direct extract: ${JSON.stringify({ written: r.facts_written, rejected: r.facts_rejected, types: r.fact_types })}`);
  }
  const types = db.prepare('SELECT fact_type FROM mines_extracted_facts WHERE project_id = ? ORDER BY fact_type').all(project.id).map((r) => r.fact_type);
  log('facts', `${types.length} LLM-extracted facts: ${types.join(', ')}`);

  // ── STEP 2: deterministic valuation ──
  const val = await minesValuationRun({ project_id: project.id }, { agent_id: 'mines-valuation-analyst', user_id: USER });
  const av = await minesAssumptionValidate({ project_id: project.id }, { agent_id: 'mines-valuation-analyst', user_id: USER });
  log('valuation', `tier=${val.tier} t2_blocked=${val.t2_blocked} | validate: recommended=${av.recommended_tier} t1_ready=${av.t1_ready}`);

  // ── STEP 3: LLM narrative ──
  log('narrative', 'LLM authoring investor prose (figures excluded — rendered from DB rows)');
  const narrative = await authorNarrative(project);
  console.log(`    narrative slots authored: ${Object.keys(narrative).join(', ') || '(none — data-driven fallback)'}`);

  // ── STEP 4: agent-driven investor presentation ──
  log('package', 'mines-report-packager tick → mines_report_generate (investor_presentation)');
  const before = new Set(db.prepare("SELECT id FROM artifacts WHERE space_id = ?").all(space.id).map((r) => r.id));
  try {
    await runAgentTick('mines-report-packager', {
      triggered_by: 'manual',
      acting_on_behalf_of_user_id: USER,
      instruction: `Generate an investor_presentation for the Batamote project using mines_report_generate with these exact arguments: project_id="${project.id}", report_kind="investor_presentation", space_id="${space.id}", narrative=${JSON.stringify(narrative)}. Then confirm the artifact id.`,
    });
  } catch (err) {
    console.log(`    packager tick error: ${err.message}`);
  }
  const madeInvestorDeck = db.prepare("SELECT COUNT(*) n FROM artifacts WHERE space_id = ? AND kind = 'mines_investor_presentation'").get(space.id).n > 0;

  // ── STEP 5: guarantee the full deliverable set in the space ──
  log('reports', 'generating the remaining report kinds into the space');
  const kinds = [
    { kind: 'data_quality_report' },
    { kind: 'interpretation_memo' },
    { kind: 'due_diligence_memo' },
  ];
  if (!madeInvestorDeck) kinds.push({ kind: 'investor_presentation', narrative });
  for (const k of kinds) {
    const r = await minesReportGenerate(
      { project_id: project.id, report_kind: k.kind, space_id: space.id, narrative: k.narrative },
      { agent_id: 'mines-report-packager', user_id: USER });
    console.log(`    ${k.kind}: ${r.artifact_id || r.status}`);
  }

  db.prepare('UPDATE mines_projects SET has_technical_report = 1, updated_at = ? WHERE id = ?').run(nowIso(), project.id);

  // Pin everything in the space (idempotent) and report.
  const artifacts = db.prepare("SELECT id, kind, title FROM artifacts WHERE space_id = ? ORDER BY created_at DESC").all(space.id);
  for (const a of artifacts) { try { moveArtifactToSpace(a.id, space.id); } catch {} }

  console.log('\n========== REAL LLM PIPELINE COMPLETE ==========');
  console.log(`Project: ${project.name} (${project.id})`);
  console.log(`Facts (LLM-extracted, cited): ${types.length}`);
  console.log(`Space: "${space.name}" (${space.id})`);
  console.log('\nArtifacts in space:');
  for (const a of artifacts) console.log(`  - [${a.kind}] ${a.title}  (${a.id})`);
  console.log(`\nView dashboard:  http://127.0.0.1:8787/   (open the "${space.name}" space)`);
  console.log(`View diligence:  http://127.0.0.1:8787/mines/${project.id}/results`);
}

main().then(() => { resetDbHandle(); process.exit(0); }).catch((err) => { console.error('FAILED:', err); resetDbHandle(); process.exit(1); });
