// Multi-Agent Narrator - Same LLM, different prompts, sequential execution

const axios = require('axios');

const LM_STUDIO_URL = 'http://localhost:1234';

// ============ AGENT PROMPTS ============

const CONTEXT_MANAGER_PROMPT = `You are the CONTEXT MANAGER for an RPG.
Your job is to provide relevant context for the player's action.

Given the full game state, identify:
1. What location is the player in?
2. What characters are present?
3. What items does the player have?
4. What just happened (recent messages)?
5. Any important story facts?

Return a concise context summary (max 200 words) that the narrator will use.`;

const REASONER_PROMPT = `You are the REASONER/VALIDATOR for an RPG.
Your job is to ensure narration quality and consistency.

CRITICAL RULES TO ENFORCE:
1. NO TIME SKIPS - Never jump forward in time
2. NO PLAYER AGENCY - Never make decisions for the player
3. NO CONTRADICTIONS - Stay consistent with context

You will review narration and flag any violations.
Be strict but fair. Only flag real issues.`;

const NARRATOR_PROMPT = `You are the NARRATOR for a text-based RPG.

CRITICAL RULES - NEVER VIOLATE:
1. NEVER skip time (no "Hours pass...", "The next day...", "After traveling...")
2. NEVER make decisions for player (no "You walk to...", "You decide to...", "You say...")
3. NEVER contradict established context (locations, characters, events)
4. ALWAYS stay in present moment
5. ALWAYS end with opportunity for player action
6. Keep responses 200-400 words

IMPORTANT: The player has LEFT the Threshold of Reincarnation. They are now in their new world. Do NOT put them back at the Goddess's realm unless they explicitly use magic/time travel/divine intervention.

Describe the WORLD, not player actions.
End with: "What do you do?"`;

// ============ MULTI-AGENT SYSTEM ============

class MultiAgentNarrator {
  constructor() {
    this.model = 'local-model';
    this.maxRetries = 2;
  }

  // Make a single LLM call with retry
  async callLLM(systemPrompt, userPrompt, maxTokens = 500) {
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        console.log(`[LLM] Call attempt ${attempt}/${this.maxRetries}`);
        
        const response = await axios.post(`${LM_STUDIO_URL}/v1/chat/completions`, {
          model: this.model,
          messages: [
            { role: 'system', content: systemPrompt },
            { role: 'user', content: userPrompt }
          ],
          temperature: 0.7,
          max_tokens: maxTokens,
          stream: false
        }, {
          timeout: 90000,
          headers: { 'Content-Type': 'application/json' }
        });

        return {
          success: true,
          content: response.data.choices[0].message.content
        };
      } catch (error) {
        console.error(`[LLM] Attempt ${attempt} failed:`, error.message);
        if (attempt === this.maxRetries) {
          return { success: false, error: error.message };
        }
        // Wait before retry
        await new Promise(resolve => setTimeout(resolve, 1000));
      }
    }
  }

  // Main entry point
  async processPlayerInput(context, playerInput) {
    console.log('\n[MULTI-AGENT] Starting sequential agent chain...');
    
    // Agent 1: Context Manager
    console.log('[MULTI-AGENT] Agent 1: Context Manager...');
    const contextResult = await this.contextManagerAgent(context, playerInput);
    if (!contextResult.success) {
      console.warn('[MULTI-AGENT] Context Manager failed, using raw context');
    }
    const contextSummary = contextResult.content || this.buildSimpleContextSummary(context);
    console.log('[MULTI-AGENT] Context summary prepared');

    // Agent 2: Reasoner (pre-narration constraints)
    console.log('[MULTI-AGENT] Agent 2: Reasoner (constraints)...');
    const constraintsResult = await this.reasonerPreAgent(contextSummary, playerInput);
    const constraints = constraintsResult.success ? constraintsResult.content : '';
    console.log('[MULTI-AGENT] Constraints generated');

    // Agent 3: Narrator
    console.log('[MULTI-AGENT] Agent 3: Narrator...');
    const narrationResult = await this.narratorAgent(contextSummary, playerInput, constraints);
    if (!narrationResult.success) {
      console.error('[MULTI-AGENT] Narrator failed, using fallback');
      return this.createFallbackResponse(context, playerInput);
    }
    const narration = narrationResult.content;
    console.log('[MULTI-AGENT] Narration generated');

    // Agent 4: Reasoner (validation)
    console.log('[MULTI-AGENT] Agent 4: Reasoner (validation)...');
    const validation = await this.reasonerValidateAgent(narration, playerInput, contextSummary);
    
    if (!validation.approved && validation.issues.length > 0) {
      console.log('[MULTI-AGENT] Issues found, requesting revision...');
      // Agent 5: Narrator (revision)
      console.log('[MULTI-AGENT] Agent 5: Narrator (revision)...');
      const revisionResult = await this.narratorReviseAgent(
        contextSummary, playerInput, narration, validation.issues
      );
      if (revisionResult.success) {
        console.log('[MULTI-AGENT] Revision complete');
        return {
          response: revisionResult.content,
          success: true,
          iterations: 2
        };
      }
    }

    console.log('[MULTI-AGENT] Complete!');
    return {
      response: narration,
      success: true,
      iterations: 1
    };
  }

  // Agent 1: Context Manager
  async contextManagerAgent(context, playerInput) {
    const prompt = `Here is the current game state:

CURRENT LOCATION: ${context.currentLocation || 'Unknown'}
LOCATIONS: ${JSON.stringify(context.locations || [])}
CHARACTERS: ${JSON.stringify(context.characters || [])}
INVENTORY: ${JSON.stringify(context.inventory || [])}
JOURNAL SUMMARY: ${context.journal?.summary || 'None'}
STORY STATE: ${JSON.stringify(context.storyState || {})}
RECENT MESSAGES: ${JSON.stringify(context.recentMessages || [])}

Player input: "${playerInput}"

Provide a concise context summary for the narrator (max 200 words).
Focus on what's immediately relevant to the player's action.
IMPORTANT: Player is currently at ${context.currentLocation || 'unknown location'}, NOT at the Threshold of Reincarnation.`;

    return await this.callLLM(CONTEXT_MANAGER_PROMPT, prompt, 300);
  }

  // Agent 2: Reasoner (pre-narration)
  async reasonerPreAgent(contextSummary, playerInput) {
    const prompt = `Context: ${contextSummary}
Player input: "${playerInput}"

What constraints should the narrator follow?
What should they focus on?
What should they avoid?

List 3-5 specific constraints.`;

    return await this.callLLM(REASONER_PROMPT, prompt, 200);
  }

  // Agent 3: Narrator
  async narratorAgent(contextSummary, playerInput, constraints) {
    const prompt = `CONTEXT:
${contextSummary}

CONSTRAINTS:
${constraints || 'No specific constraints'}

PLAYER INPUT:
"${playerInput}"

Write your narration now (200-400 words). Remember the critical rules.`;

    return await this.callLLM(NARRATOR_PROMPT, prompt, 600);
  }

  // Agent 4: Reasoner (validation)
  async reasonerValidateAgent(narration, playerInput, contextSummary) {
    // Do programmatic validation first (fast, reliable)
    const programmaticIssues = this.programmaticCheck(narration);
    
    if (programmaticIssues.length === 0) {
      return { approved: true, issues: [] };
    }

    // If programmatic found issues, also ask LLM
    const prompt = `Review this narration for issues:

PLAYER INPUT: "${playerInput}"
CONTEXT: ${contextSummary}
NARRATION: ${narration.substring(0, 500)}

Check for:
1. Time skips (hours pass, you travel to, etc.)
2. Player agency violations (you decide, you walk, etc.)
3. Context contradictions

List any issues found.`;

    const llmResult = await this.callLLM(REASONER_PROMPT, prompt, 300);
    
    return {
      approved: programmaticIssues.length === 0,
      issues: programmaticIssues
    };
  }

  // Agent 5: Narrator (revision)
  async narratorReviseAgent(contextSummary, playerInput, originalNarration, issues) {
    const prompt = `Your previous narration had these issues:
${issues.map((i, idx) => `${idx + 1}. ${i.type}: ${i.description}`).join('\n')}

ORIGINAL:
${originalNarration}

Revise the narration to fix these issues.
Keep the same content but remove problematic phrases.
Stay in present moment, no player actions.`;

    return await this.callLLM(NARRATOR_PROMPT, prompt, 600);
  }

  // Programmatic validation
  programmaticCheck(narration) {
    const issues = [];
    const lower = narration.toLowerCase();

    const timeSkipPhrases = [
      'hours pass', 'hours later', 'the next day', 'after traveling',
      'you arrive at', 'you reach', 'you finally', 'you make your way',
      'you travel to', 'you head to', 'you walk to', 'you move to'
    ];

    for (const phrase of timeSkipPhrases) {
      if (lower.includes(phrase)) {
        issues.push({
          type: 'time_skip',
          description: `Found: "${phrase}"`,
          fix: 'Remove time skip, stay in present'
        });
        break;
      }
    }

    const agencyPhrases = [
      'you decide to', 'you choose to', 'you say "', 'you think "',
      'you approach', 'you enter', 'you leave', 'you pick up'
    ];

    for (const phrase of agencyPhrases) {
      if (lower.includes(phrase) && !lower.includes('do you')) {
        issues.push({
          type: 'player_agency',
          description: `Found: "${phrase}"`,
          fix: 'Describe world, not player action'
        });
        break;
      }
    }

    return issues;
  }

  buildSimpleContextSummary(context) {
    const parts = [];
    if (context.currentLocation) parts.push(`Location: ${context.currentLocation}`);
    if (context.characters?.length > 0) parts.push(`Characters: ${context.characters.map(c => c.name).join(', ')}`);
    if (context.inventory?.length > 0) parts.push(`Inventory: ${context.inventory.map(i => i.name).join(', ')}`);
    return parts.join('\n') || 'No context available';
  }

  createFallbackResponse(context, playerInput) {
    const location = context.currentLocation || 'this place';
    return `*[The world awaits your action...]*

You find yourself in **${location}**. ${context.characters?.length > 0 ? `${context.characters.map(c => c.name).join(', ')} ${context.characters.length === 1 ? 'is' : 'are'} present.` : 'You are alone.'}

What do you do?`;
  }
}

module.exports = MultiAgentNarrator;
