// Dialogue Parser Agent - Extracts character speech from narration

const axios = require('axios');

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

const DIALOGUE_PARSER_PROMPT = `You are a DIALOGUE PARSER for a text-based RPG.
Your job is to analyze narration text and identify:
1. Which characters speak (by exact name)
2. What their exact dialogue is
3. The narration segments between dialogue

RULES:
- Extract dialogue in quotation marks ("..." or '...')
- Identify the speaker from context (the character mentioned before/around the dialogue)
- Preserve narration segments that don't contain speech
- Handle multiple speakers in sequence
- Tag each segment as either "narration" or "dialogue"

OUTPUT FORMAT (valid JSON only):
{
  "segments": [
    {
      "type": "narration" | "dialogue",
      "text": "the exact text",
      "speaker": "character name" | null
    }
  ]
}

Example:
Input: "The guard steps forward. \"Halt! Who goes there?\" he demands. You see his hand on his sword."
Output:
{
  "segments": [
    {"type": "narration", "text": "The guard steps forward. ", "speaker": null},
    {"type": "dialogue", "text": "Halt! Who goes there?", "speaker": "guard"},
    {"type": "narration", "text": " he demands. You see his hand on his sword.", "speaker": null}
  ]
}`;

class DialogueParser {
  constructor() {
    this.model = 'local-model';
    this.maxRetries = 1;
  }

  async parse(narration, availableCharacters) {
    try {
      const characterList = availableCharacters.map(c => c.name).join(', ') || 'unknown characters';
      
      const response = await axios.post(`${LM_STUDIO_URL}/v1/chat/completions`, {
        model: this.model,
        messages: [
          { 
            role: 'system', 
            content: DIALOGUE_PARSER_PROMPT 
          },
          { 
            role: 'user', 
            content: `Available characters in scene: ${characterList}

Parse this narration:
${narration}

Return ONLY valid JSON.` 
          }
        ],
        temperature: 0.3,
        max_tokens: 2000,
        stream: false
      }, {
        timeout: 60000
      });

      const content = response.data.choices[0].message.content;
      
      // Extract JSON from response
      const jsonMatch = content.match(/\{[\s\S]*\}/);
      if (!jsonMatch) {
        console.log('[DialogueParser] No JSON found, using fallback');
        return this.fallbackParse(narration, availableCharacters);
      }

      const parsed = JSON.parse(jsonMatch[0]);
      
      // Validate and normalize speaker names
      parsed.segments = parsed.segments.map(segment => {
        if (segment.speaker) {
          segment.speaker = this.normalizeSpeakerName(segment.speaker, availableCharacters);
        }
        return segment;
      });

      console.log('[DialogueParser] Parsed successfully:', parsed.segments.length, 'segments');
      return parsed;

    } catch (error) {
      console.error('[DialogueParser] Error:', error.message);
      return this.fallbackParse(narration, availableCharacters);
    }
  }

  // Fallback parser using regex (when LLM fails)
  fallbackParse(narration, availableCharacters) {
    const segments = [];
    const normalizedChars = availableCharacters.map(c => ({
      original: c.name,
      normalized: this.normalizeName(c.name)
    }));

    // Split by quotation marks (both " and ")
    const parts = narration.split(/([""][^""]+[""])/g);
    
    let currentSegment = '';
    let inDialogue = false;
    let lastCharacterMentioned = null;

    for (let i = 0; i < parts.length; i++) {
      const part = parts[i].trim();
      if (!part) continue;

      if (part.startsWith('"') || part.startsWith('"')) {
        // This is dialogue
        const dialogueText = part.replace(/^[""]|[""]$/g, '');
        
        // Try to find speaker from context (previous narration)
        const speaker = this.findSpeakerInContext(currentSegment, normalizedChars) || lastCharacterMentioned;
        
        segments.push({
          type: 'dialogue',
          text: dialogueText,
          speaker: speaker
        });
        
        inDialogue = true;
        currentSegment = '';
      } else {
        // This is narration
        // Check if a character name is mentioned
        const mentionedChar = this.findCharacterMentioned(part, normalizedChars);
        if (mentionedChar) {
          lastCharacterMentioned = mentionedChar;
        }
        
        if (part) {
          segments.push({
            type: 'narration',
            text: part,
            speaker: null
          });
        }
        currentSegment = part;
        inDialogue = false;
      }
    }

    console.log('[DialogueParser] Fallback parse:', segments.length, 'segments');
    return { segments };
  }

  normalizeSpeakerName(name, availableCharacters) {
    const normalizedName = this.normalizeName(name);
    
    // Try exact match first
    const exactMatch = availableCharacters.find(c => 
      this.normalizeName(c.name) === normalizedName
    );
    
    if (exactMatch) return exactMatch.name;
    
    // Try partial match
    const partialMatch = availableCharacters.find(c => 
      this.normalizeName(c.name).includes(normalizedName) ||
      normalizedName.includes(this.normalizeName(c.name))
    );
    
    if (partialMatch) return partialMatch.name;
    
    // Return as-is if no match
    return name;
  }

  findSpeakerInContext(text, normalizedChars) {
    if (!text) return null;
    
    for (const char of normalizedChars) {
      if (this.normalizeName(text).includes(char.normalized)) {
        return char.original;
      }
    }
    return null;
  }

  findCharacterMentioned(text, normalizedChars) {
    for (const char of normalizedChars) {
      if (this.normalizeName(text).includes(char.normalized)) {
        return char.original;
      }
    }
    return null;
  }

  normalizeName(name) {
    return name.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '');
  }
}

module.exports = DialogueParser;
