// Silver Q&A agent. Uses Anthropic Messages API with inline retrieval over
// the lesson transcripts + FAQ, plus four function tools.
//
// Stub mode (no ANTHROPIC_API_KEY or SILVER_AGENT_STUB=1): returns deterministic
// stub answers so the dev UI flow is exercisable end-to-end without API calls.
//
// Why inline retrieval (not a vector store):
//   - Anthropic doesn't ship a managed vector store; we'd have to run our own.
//   - The Silver corpus is tiny: 30 lesson transcripts (~150 words each, stub)
//     + FAQ + templates ≈ 10–20K tokens once real transcripts land.
//   - Claude's 200K context window swallows the whole corpus easily.
//   - Prompt caching (cache_control on the system block) keeps cost flat —
//     after the first hit the corpus reads as cached input tokens.

import { existsSync, readFileSync, readdirSync } from 'fs';
import { join } from 'path';
import { sendText } from './whatsappAdapter.js';
import { templateById } from './silverStore.js';

const MODEL = process.env.SILVER_MODEL || 'claude-haiku-4-5';
const ANTHROPIC_VERSION = '2023-06-01';

function loadSystemPrompt(scriptDir) {
  const p = join(scriptDir, 'training', 'silver.txt');
  const base = existsSync(p) ? readFileSync(p, 'utf8') : [
    'You are EcoBe Silver — a calm, no-fluff coach inside a WhatsApp chat.',
    'Tone: practical, warm, brief. Short paragraphs. No emoji spam (max 1–2 per reply).',
    'Ground every claim in the lesson transcripts and FAQ included below. If unsure, say so and offer to escalate.',
    'Never invent links, prices, or guarantees. The course is execution-based; results require effort.',
    'Always answer in the user\'s language if obvious; otherwise English.',
  ].join('\n');
  // Append explicit tool-use guidance — every user message reaches you,
  // so you decide both what to say AND what action (if any) to take.
  return base + `

# Tool use — when to call which

You are the WHOLE intent classifier for this conversation. Every reply
from the user comes to you. Decide what to do:

- **sendLesson({ day })** — when the user asks for a specific lesson by
  day number ("day 3", "the day-4 video"). Day must be 1–30.
- **sendNextLesson()** — when the user asks for the *next* lesson, the
  *first* lesson, "the video", "another one", "what's today", etc.
  Picks the oldest incomplete lesson automatically. Use this for
  vague "send me the video" requests.
- **replayLastLesson()** — when the user asks for the most recent
  lesson again ("send again", "resend", "I missed it", "one more time").
- **markLessonComplete()** — when the user implies they finished the
  current lesson without using the literal word "yes" — e.g. "watched
  it", "ok done", "saw the video", "finito". Only call this when there
  IS a current lesson awaiting confirmation.
- **sendTemplate({ templateId })** — when the user asks for a template
  by name. IDs: career-plan-v1, cv-v1-template, linkedin-checklist,
  outreach-scripts, application-tracker, interview-prep.
- **recordTaskProof({ taskId, status, userReply, proofUrls?, notes? })**
  — when the user shares a link/count/proof for a tracked task, OR
  when there's an open task probe and they're answering it.
  status ∈ claimed | partial | not-done | unknown.
- **escalateToHuman({ reason })** — when the user explicitly asks for
  a human ("coach", "real person"), is frustrated, or you can't answer
  with the corpus.
- **getUserProgress()** — when the user asks "what day am I on", "what
  have I done", "show my progress".

Default behavior: if the user is just chatting, asking a course
question, or reflecting — answer directly with text grounded in the
corpus, no tool needed. If the message implies an action, call the
right tool AND optionally include short text confirming what you did.

Multiple tools per turn are fine (e.g. sendLesson + brief encouragement).`;
}

// Build the full corpus block (transcripts + FAQ + template metadata) once
// and cache it across calls. Re-read on first call per process; the file
// system call is cheap and we want hot-reload during dev.
let _corpusCache = null;
function loadCorpus(scriptDir) {
  if (_corpusCache) return _corpusCache;
  const parts = [];
  const trDir = join(scriptDir, 'data', 'silver', 'transcripts');
  if (existsSync(trDir)) {
    for (const f of readdirSync(trDir)) {
      if (!f.endsWith('.txt')) continue;
      const id = f.replace(/\.txt$/, '');
      parts.push(`### ${id}\n` + readFileSync(join(trDir, f), 'utf8').trim());
    }
  }
  const faqPath = join(scriptDir, 'data', 'silver', 'faq.md');
  if (existsSync(faqPath)) parts.push('### FAQ\n' + readFileSync(faqPath, 'utf8').trim());
  _corpusCache = parts.join('\n\n');
  return _corpusCache;
}
export function _resetCorpusCache() { _corpusCache = null; }

function shouldStub() {
  return process.env.SILVER_AGENT_STUB === '1' || !process.env.ANTHROPIC_API_KEY;
}

// ── Stub fallback for offline dev ───────────────────────────────────────────

function extractUrls(text) {
  const re = /https?:\/\/[^\s)]+/g;
  return text.match(re) || [];
}

function classifyProof(userText) {
  const lower = userText.toLowerCase();
  if (/\b(no|not yet|haven['’]?t|skip|later|nope)\b/.test(lower)) return 'not-done';
  if (/\b(partial|halfway|in progress|started|kind of|sort of|wip)\b/.test(lower)) return 'partial';
  if (/\b(yes|done|finished|here|sent|submitted|applied|completed)\b/.test(lower)) return 'claimed';
  if (extractUrls(userText).length > 0) return 'claimed';
  if (/\d+/.test(userText)) return 'claimed';
  return 'unknown';
}

function stubAnswer({ scriptDir, state, lesson, pendingTask, userText }) {
  const lower = userText.toLowerCase();

  if (pendingTask) {
    const status = classifyProof(userText);
    const proofUrls = extractUrls(userText);
    const ack = status === 'claimed'
      ? `Got it — logged "${pendingTask.title}" as done${proofUrls.length ? ` (proof saved)` : ''}. 💪`
      : status === 'partial'
        ? `Logged as in progress. Keep at it — drop the link when it's ready.`
        : status === 'not-done'
          ? `Noted — not done yet. No judgment. Want me to send the relevant template again?`
          : `Logged your reply. If you have a doc or a count, share it and I'll log proof.`;
    return {
      text: ack,
      toolCalls: [{ name: 'recordTaskProof', args: { taskId: pendingTask.id, status, proofUrls } }],
      recordedTask: { taskId: pendingTask.id, status, userReply: userText, proofUrls, notes: '' },
      escalate: false,
    };
  }

  const tpl = (function () {
    if (/cv|resume/.test(lower)) return templateById(scriptDir, 'cv-v1-template');
    if (/career plan/.test(lower)) return templateById(scriptDir, 'career-plan-v1');
    if (/linkedin/.test(lower)) return templateById(scriptDir, 'linkedin-checklist');
    if (/outreach|cold message/.test(lower)) return templateById(scriptDir, 'outreach-scripts');
    if (/tracker|tracking/.test(lower)) return templateById(scriptDir, 'application-tracker');
    if (/interview/.test(lower)) return templateById(scriptDir, 'interview-prep');
    return null;
  })();
  if (tpl && /template|send|share|link|tracker/.test(lower)) {
    return {
      text: `Here's the *${tpl.title}*: ${tpl.url}`,
      toolCalls: [{ name: 'sendTemplate', args: { templateId: tpl.id } }],
      escalate: false,
    };
  }
  if (/car method/.test(lower)) {
    return {
      text: `CAR = *Context, Action, Result*. Each CV bullet describes the situation, what you did, and the measurable outcome. Used in CV v1.`,
      toolCalls: [],
      escalate: false,
    };
  }
  if (/how many app|per week|target/.test(lower)) {
    return {
      text: `Target is *10–20 applications per week* from Week 1. After Week 1, also send an outreach message after each application.`,
      toolCalls: [],
      escalate: false,
    };
  }
  if (/wrong|bad answer|not helpful|human|coach/.test(lower)) {
    return {
      text: `Let me get a coach to weigh in on this one.`,
      toolCalls: [{ name: 'escalateToHuman', args: { reason: 'user-dissatisfied' } }],
      escalate: true,
    };
  }
  // Stub heuristics for common lesson asks (so the demo doesn't fall back to
  // a generic message when ANTHROPIC_API_KEY isn't set).
  if (/\b(send|give|show)( me)?( the)?( first| next| another| today'?s)? (video|lesson)\b|\bsend day ?\d+\b|\bday ?\d+ video\b|\bstart now\b|\bnext video\b/i.test(lower)) {
    const dayMatch = lower.match(/day ?(\d+)/);
    const day = dayMatch ? Number(dayMatch[1]) : null;
    return {
      text: '',
      toolCalls: [{ name: day ? 'sendLesson' : 'sendNextLesson', args: day ? { day } : {} }],
      escalate: false,
      lessonAction: day ? { kind: 'send', day } : { kind: 'sendNext' },
    };
  }
  if (/\b(send again|resend|one more time|missed it|replay)\b/i.test(lower)) {
    return {
      text: '',
      toolCalls: [{ name: 'replayLastLesson', args: {} }],
      escalate: false,
      lessonAction: { kind: 'replay' },
    };
  }
  if (/\b(watched it|done|saw it|finito|finished)\b/i.test(lower)) {
    return {
      text: '',
      toolCalls: [{ name: 'markLessonComplete', args: {} }],
      escalate: false,
      lessonAction: { kind: 'markComplete' },
    };
  }
  const ctx = lesson ? `today's lesson (Day ${lesson.day}: ${lesson.title})` : 'the course';
  return {
    text: `Quick take on ${ctx}: I'd need to look at the transcript to answer that precisely. Once we've connected the knowledge base I can ground the answer in the exact lesson. For now: focus on execution — apply, send the outreach, log it. Want me to loop in a coach for a deeper answer?`,
    toolCalls: [],
    escalate: false,
  };
}

// ── Anthropic Messages API (real) ───────────────────────────────────────────

const TOOLS = [
  {
    name: 'sendLesson',
    description: 'Send a specific lesson video to the user. Use when user asks for a lesson by day number ("send me day 3", "the day 4 video").',
    input_schema: {
      type: 'object',
      properties: { day: { type: 'integer', minimum: 1, maximum: 30 } },
      required: ['day'],
    },
  },
  {
    name: 'sendNextLesson',
    description: 'Send the next incomplete lesson (or Day 1 if user has none yet). Use for vague "send me the video", "the first one", "another lesson", "what is today" requests.',
    input_schema: { type: 'object', properties: {} },
  },
  {
    name: 'replayLastLesson',
    description: 'Re-send the most recent lesson video. Use when user says "send again", "resend", "I missed it", "one more time".',
    input_schema: { type: 'object', properties: {} },
  },
  {
    name: 'markLessonComplete',
    description: 'Mark the current awaiting lesson as complete. Use when user implies completion without literal "yes" — "watched it", "done", "saw it", "finito". Only when state has an awaitingYesFor lesson.',
    input_schema: { type: 'object', properties: {} },
  },
  {
    name: 'sendTemplate',
    description: 'Send a course template to the user by id. IDs: career-plan-v1, cv-v1-template, linkedin-checklist, outreach-scripts, application-tracker, interview-prep.',
    input_schema: {
      type: 'object',
      properties: { templateId: { type: 'string' } },
      required: ['templateId'],
    },
  },
  {
    name: 'escalateToHuman',
    description: 'Escalate the conversation to a human coach. Call when the question is out of scope, user is frustrated, or explicitly asks for a coach.',
    input_schema: {
      type: 'object',
      properties: { reason: { type: 'string' } },
      required: ['reason'],
    },
  },
  {
    name: 'getUserProgress',
    description: "Read the user's current Silver progress (day number, completed lessons, modules done). Use when user asks 'what day am I on', 'show progress'.",
    input_schema: { type: 'object', properties: {} },
  },
  {
    name: 'recordTaskProof',
    description: 'Record proof/status for a Silver task. Call when user shares a link, count, screenshot reference, or confirms/denies completing a tracked task.',
    input_schema: {
      type: 'object',
      properties: {
        taskId: { type: 'string' },
        status: { type: 'string', enum: ['claimed', 'partial', 'not-done', 'unknown'] },
        userReply: { type: 'string' },
        proofUrls: { type: 'array', items: { type: 'string' } },
        notes: { type: 'string' },
      },
      required: ['taskId', 'status', 'userReply'],
    },
  },
];

async function liveAnswer({ scriptDir, state, lesson, pendingTask, userText }) {
  const baseSystem = loadSystemPrompt(scriptDir);
  const corpus = loadCorpus(scriptDir);

  const lessonContext = lesson
    ? `Current lesson context — Day ${lesson.day}, Week ${lesson.week}, Module ${lesson.module || 1}: ${lesson.title}.`
    : 'No specific lesson context.';
  const taskContext = pendingTask
    ? `OPEN TASK PROBE — bot just asked the user about task "${pendingTask.id}" (${pendingTask.title}). The user's next reply should be classified and recorded via recordTaskProof. Extract any URLs as proofUrls. Status: claimed if user says yes/done or shares a doc/count, partial if in-progress, not-done if no, unknown otherwise.`
    : 'No open task probe.';
  // Compact state summary so Claude can reason about user progress without
  // calling getUserProgress every turn. Kept short to control token cost.
  const completedLessons = Object.entries(state?.lessons || {}).filter(([, l]) => l.completedAt).map(([id]) => id);
  const awaitingLesson = state?.awaitingYesFor || null;
  const modulesDone = Object.entries(state?.modulesCompleted || {}).filter(([, v]) => v).map(([k]) => `M${k}`).join(',') || 'none';
  const stateSummary = `User state: slug=${state?.slug || '?'}, accessLevel=${state?.accessLevel || '?'}, completedLessons=${completedLessons.length}/30 [${completedLessons.slice(-3).join(',') || 'none'}], awaitingYesFor=${awaitingLesson || 'none'}, modulesCompleted=${modulesDone}, coachJoinedAt=${state?.coachJoinedAt ? 'yes' : 'no'}.`;
  const accessContext = stateSummary;

  // System block uses prompt caching so the heavy corpus is billed at cache
  // hit rate after the first request. Cache key is the entire array.
  const systemBlocks = [
    { type: 'text', text: baseSystem, cache_control: { type: 'ephemeral' } },
    { type: 'text', text: `# Silver course corpus (transcripts + FAQ)\n\n${corpus}`, cache_control: { type: 'ephemeral' } },
    { type: 'text', text: [lessonContext, taskContext, accessContext].filter(Boolean).join('\n') },
  ];

  const body = {
    model: MODEL,
    max_tokens: 800,
    system: systemBlocks,
    tools: TOOLS,
    messages: [
      { role: 'user', content: userText },
    ],
  };

  const res = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.ANTHROPIC_API_KEY,
      'anthropic-version': ANTHROPIC_VERSION,
      'content-type': 'application/json',
    },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const err = await res.text();
    console.error('[silverAgent] Anthropic API error', res.status, err);
    return { text: `I couldn't reach the answer engine just now. Try again in a moment, or ask for *a coach* if it's urgent.`, toolCalls: [], escalate: false };
  }
  const json = await res.json();
  return await reduceResponse(scriptDir, state, json);
}

async function reduceResponse(scriptDir, state, json) {
  let text = '';
  const toolCalls = [];
  let escalate = false;
  let recordedTask = null;
  // Marker for the caller (silverCourse) to apply lesson-state changes.
  // Kept on the result rather than mutating state here to keep this module
  // free of writeable state-flow concerns.
  let lessonAction = null;

  for (const block of json.content || []) {
    if (block.type === 'text' && block.text) {
      text += block.text;
    }
    if (block.type === 'tool_use') {
      const args = block.input || {};
      toolCalls.push({ name: block.name, args });
      if (block.name === 'sendTemplate') {
        const tpl = templateById(scriptDir, args.templateId);
        if (tpl) await sendText(state.groupJid, `Here's the *${tpl.title}*: ${tpl.url}`);
      }
      if (block.name === 'escalateToHuman') escalate = true;
      if (block.name === 'sendLesson')         lessonAction = { kind: 'send', day: Number(args.day) };
      if (block.name === 'sendNextLesson')     lessonAction = { kind: 'sendNext' };
      if (block.name === 'replayLastLesson')   lessonAction = { kind: 'replay' };
      if (block.name === 'markLessonComplete') lessonAction = { kind: 'markComplete' };
      if (block.name === 'recordTaskProof') {
        recordedTask = {
          taskId: args.taskId,
          status: args.status || 'unknown',
          userReply: args.userReply || '',
          proofUrls: args.proofUrls || [],
          notes: args.notes || '',
        };
      }
    }
  }
  // Allow tool-only responses (e.g. sendNextLesson) to skip the fallback
  // escalate. Only escalate when we have neither text nor any tool result.
  if (!text && toolCalls.length === 0) {
    text = `I'm not sure I have enough on that one yet. Want me to loop in a coach?`;
    escalate = true;
  }
  return { text, toolCalls, escalate, recordedTask, lessonAction };
}

// ── Public entrypoint ───────────────────────────────────────────────────────

export async function answerQuestion(scriptDir, args) {
  if (shouldStub()) return stubAnswer({ scriptDir, ...args });
  try {
    return await liveAnswer({ scriptDir, ...args });
  } catch (err) {
    console.error('[silverAgent] live error → falling back to stub', err);
    return stubAnswer({ scriptDir, ...args });
  }
}
