/**
 * Micro Learning generation service.
 *
 * Implements the generate-then-cache strategy:
 *   1. Check PocketBase for an existing published record (topic × type)
 *   2. If found → return it (cache hit)
 *   3. If not → call LLM, store result as published, return it
 *
 * Content is generated once per (topic, type) pair and shared across all users.
 */

import { pb } from './pb';
import { callLLM, cachedSystem } from './llm';
import {
  EMIT_CONCEPT_EXPLAINER_TOOL,
  EMIT_SCENARIO_QUIZ_TOOL,
  EMIT_FLASHCARD_SET_TOOL,
} from './llmTools';
import * as db from './db';

// ── Configuration per micro learning type ─────────────────────────────────────

const MICRO_LEARNING_TYPES = {
  concept_explainer: {
    tool: EMIT_CONCEPT_EXPLAINER_TOOL,
    tier: 'standard',
    maxTokens: 4096,
    instructions: `Generate a concept explainer with at least 3 sections.
Section 1: What the concept is — define it clearly.
Section 2: Why it matters — explain its importance in the workplace.
Section 3: Practical example — give a concrete, realistic scenario showing how it works in practice.
Use HTML formatting in the content fields (<p>, <ul>, <li>, <strong>).`,
  },
  scenario_quiz: {
    tool: EMIT_SCENARIO_QUIZ_TOOL,
    tier: 'standard',
    maxTokens: 4096,
    instructions: `Generate a scenario quiz with a realistic workplace situation.
The scenario should be specific and domain-relevant — something the employee might actually encounter.
Provide 3–4 answer options. Exactly one must be correct.
Each option needs a detailed explanation (2–3 sentences) that teaches why it is right or wrong.
The incorrect options should represent common mistakes or reasonable misreadings, not obviously wrong answers.`,
  },
  flashcard_set: {
    tool: EMIT_FLASHCARD_SET_TOOL,
    tier: 'fast',
    maxTokens: 2048,
    instructions: `Generate a flashcard set with 5–10 cards.
Mix three question types:
  - Definitions: "What is X?"
  - Applications: "How would you apply X in situation Y?"
  - Relationships: "How does X relate to Y?"
Keep answers concise — one or two sentences maximum.`,
  },
};

const SYSTEM_PROMPT = `You are an expert learning content writer for Respellion, an internal IT company.
You create micro learning content for employees based on knowledge topics from the company knowledge base.
Always write in clear, professional English.
Make the content practical and anchored to the workplace — avoid abstract theory without application.
Emit the content through the provided tool — do not return prose or raw JSON.`;

// ── Core API ──────────────────────────────────────────────────────────────────

/**
 * Get an existing micro learning or generate a new one.
 * Returns the PocketBase record (with .content parsed).
 */
export async function getOrGenerateMicroLearning(topicId, type) {
  const config = MICRO_LEARNING_TYPES[type];
  if (!config) throw new Error(`Unknown micro learning type: ${type}`);

  // 1. Check cache
  const existing = await findExisting(topicId, type);
  if (existing) {
    console.log(`[MicroLearning] Cache hit: ${topicId} / ${type}`);
    return existing;
  }

  // 2. Load topic metadata
  const topic = await loadTopic(topicId);
  if (!topic) throw new Error(`Topic not found: ${topicId}`);

  // 3. Generate
  console.log(`[MicroLearning] Generating: ${topicId} / ${type} (tier: ${config.tier})`);
  const content = await generateContent(topic, type, config);

  // 4. Store in PocketBase
  const record = await pb.collection('micro_learnings').create({
    topic_id: topicId,
    type: type,
    content: content,
    status: 'published',
  });

  console.log(`[MicroLearning] Stored: ${record.id}`);
  // Normalise in case PB returns the JSON field as a string on this PB version.
  return normalizeRecord(record);
}

/**
 * Delete an existing micro learning and regenerate it.
 * Used when a topic's content has changed and the cached version is stale.
 */
export async function regenerateMicroLearning(topicId, type) {
  const config = MICRO_LEARNING_TYPES[type];
  if (!config) throw new Error(`Unknown micro learning type: ${type}`);

  // Delete existing if present
  const existing = await findExisting(topicId, type);
  if (existing) {
    console.log(`[MicroLearning] Deleting stale record: ${existing.id}`);
    await pb.collection('micro_learnings').delete(existing.id);
  }

  // Generate fresh
  return getOrGenerateMicroLearning(topicId, type);
}

/**
 * Return the set of micro-learning types that already have a published
 * record for the given topic. Used by the selector UI to flag formats
 * that are ready to read instantly (no generation latency).
 */
export async function getExistingTypesForTopic(topicId) {
  try {
    const records = await pb.collection('micro_learnings').getFullList({
      filter: `topic_id = "${topicId}" && status = "published"`,
      fields: 'type',
    });
    return new Set(records.map((r) => r.type));
  } catch {
    return new Set();
  }
}

/**
 * Delete all cached micro learnings for a topic (all types).
 */
export async function deleteAllForTopic(topicId) {
  try {
    const records = await pb.collection('micro_learnings').getFullList({
      filter: `topic_id = "${topicId}"`,
    });
    for (const record of records) {
      await pb.collection('micro_learnings').delete(record.id);
    }
    console.log(`[MicroLearning] Deleted ${records.length} records for topic ${topicId}`);
    return records.length;
  } catch (err) {
    console.error('[MicroLearning] Error deleting records:', err);
    return 0;
  }
}

// ── Internal helpers ──────────────────────────────────────────────────────────

/**
 * Ensure the PocketBase record's `content` field is a parsed JS object.
 *
 * Older PocketBase versions (or misconfigured instances) may return a JSON
 * field as a raw string instead of a parsed object.  The PB JS SDK normally
 * handles this, but we have seen production deployments where `r.content` is
 * the serialised JSON string — which causes `content.sections?.map` to throw
 * "is not a function" because strings don't have a `.map` method.
 *
 * This guard is defensive and idempotent: if `content` is already a plain
 * object it is returned as-is; only strings are re-parsed.
 */
function normalizeRecord(record) {
  if (!record) return record;
  if (typeof record.content === 'string') {
    try {
      record.content = JSON.parse(record.content);
    } catch {
      // Malformed JSON — replace with an empty object so the UI can handle it.
      record.content = {};
    }
  }
  return record;
}

async function findExisting(topicId, type) {
  try {
    const records = await pb.collection('micro_learnings').getFullList({
      filter: `topic_id = "${topicId}" && type = "${type}" && status = "published"`,
    });
    return records.length > 0 ? normalizeRecord(records[0]) : null;
  } catch {
    return null;
  }
}

async function loadTopic(topicId) {
  try {
    const topics = await db.getTopics();
    return topics.find(t => t.id === topicId) || null;
  } catch {
    return null;
  }
}

async function generateContent(topic, type, config) {
  const prompt = `Generate a ${type.replace('_', ' ')} micro learning for the following topic:

Label: ${topic.label}
Type: ${topic.type}
Description: ${topic.description}

${config.instructions}`;

  const result = await callLLM({
    task: `micro_learning.${type}`,
    tier: config.tier,
    system: cachedSystem(SYSTEM_PROMPT),
    user: prompt,
    tools: [config.tool],
    toolChoice: { type: 'tool', name: config.tool.name },
    maxTokens: config.maxTokens,
  });

  const content = result.toolUses[0]?.input;
  if (!content) {
    throw new Error(`AI did not return content for ${type}. Please try again.`);
  }

  return content;
}
