#!/usr/bin/env bun
/**
 * FailureCapture.ts - Full Context Failure Analysis System
 *
 * PURPOSE:
 * Creates comprehensive context dumps for low-sentiment events (ratings 1-3)
 * to enable retroactive learning system analysis.
 *
 * USAGE:
 *   bun FailureCapture.ts <transcript_path> <rating> <sentiment_summary> [detailed_context]
 *
 * Or as a module:
 *   import { captureFailure } from './FailureCapture'
 *   await captureFailure({ transcriptPath, rating, sentimentSummary, detailedContext })
 *
 * OUTPUT:
 * Creates a directory under MEMORY/LEARNING/FAILURES/<YYYY-MM>/ with:
 * - CONTEXT.md - Human-readable analysis with metadata
 * - transcript.jsonl - Raw conversation transcript
 * - sentiment.json - Sentiment analysis details
 * - tool-calls.json - Extracted tool invocations
 *
 * NAMING:
 * Directory named: YYYY-MM-DD-HHMMSS_eight-word-description
 * The 8-word description is generated by fast inference.
 */

import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'fs';
import { join, basename } from 'path';
import { inference } from './Inference';

const PAI_DIR = process.env.PAI_DIR || join(process.env.HOME!, '.claude');

interface FailureCaptureInput {
  transcriptPath: string;
  rating: number;
  sentimentSummary: string;
  detailedContext?: string;
  sessionId?: string;
}

interface TranscriptEntry {
  type: string;
  message?: {
    role?: string;
    content?: unknown;
  };
  timestamp?: string;
  [key: string]: unknown;
}

interface ToolCall {
  name: string;
  input: unknown;
  output?: string;
  timestamp?: string;
}

/**
 * Extract text content from Claude's content format
 */
function contentToText(content: unknown): string {
  if (typeof content === 'string') return content;
  if (Array.isArray(content)) {
    return content
      .map(c => {
        if (typeof c === 'string') return c;
        if (c?.text) return c.text;
        if (c?.content) return contentToText(c.content);
        return '';
      })
      .join('\n')
      .trim();
  }
  return '';
}

/**
 * Parse transcript and extract all relevant data
 */
function parseTranscript(transcriptPath: string): {
  entries: TranscriptEntry[];
  toolCalls: ToolCall[];
  conversations: { role: string; content: string; timestamp?: string }[];
} {
  const entries: TranscriptEntry[] = [];
  const toolCalls: ToolCall[] = [];
  const conversations: { role: string; content: string; timestamp?: string }[] = [];

  try {
    const content = readFileSync(transcriptPath, 'utf-8');
    const lines = content.trim().split('\n');

    for (const line of lines) {
      if (!line.trim()) continue;
      try {
        const entry = JSON.parse(line) as TranscriptEntry;
        entries.push(entry);

        // Extract conversations
        if (entry.type === 'user' && entry.message?.content) {
          const text = contentToText(entry.message.content);
          if (text) {
            conversations.push({
              role: 'user',
              content: text,
              timestamp: entry.timestamp,
            });
          }
        }

        if (entry.type === 'assistant' && entry.message?.content) {
          const text = contentToText(entry.message.content);
          if (text) {
            conversations.push({
              role: 'assistant',
              content: text,
              timestamp: entry.timestamp,
            });
          }

          // Extract tool calls
          if (Array.isArray(entry.message.content)) {
            for (const block of entry.message.content as any[]) {
              if (block.type === 'tool_use') {
                toolCalls.push({
                  name: block.name,
                  input: block.input,
                  timestamp: entry.timestamp,
                });
              }
            }
          }
        }

        // Capture tool results
        if (entry.type === 'tool_result' || entry.type === 'tool_output') {
          const lastToolCall = toolCalls[toolCalls.length - 1];
          if (lastToolCall && !lastToolCall.output) {
            lastToolCall.output = contentToText((entry as any).content || (entry as any).output);
          }
        }
      } catch {
        // Skip malformed lines
      }
    }
  } catch (err) {
    console.error(`[FailureCapture] Error parsing transcript: ${err}`);
  }

  return { entries, toolCalls, conversations };
}

/**
 * Generate 8-word description using fast inference
 */
async function generateDescription(
  sentimentSummary: string,
  conversations: { role: string; content: string }[],
  toolCalls: ToolCall[]
): Promise<string> {
  // Get last few exchanges for context
  const recentConvos = conversations.slice(-6).map(c =>
    `${c.role.toUpperCase()}: ${c.content.slice(0, 200)}`
  ).join('\n');

  const recentTools = toolCalls.slice(-5).map(t => t.name).join(', ');

  const systemPrompt = `Generate a SHORT, SPECIFIC description of what went wrong in this AI assistant interaction.

REQUIREMENTS:
- EXACTLY 8 words (count them!)
- Use lowercase with hyphens between words (kebab-case)
- Be specific about the actual failure, not generic
- Focus on what the assistant did wrong or what frustrated the user

EXAMPLES OF GOOD DESCRIPTIONS:
- "assistant-deleted-users-file-without-asking-permission-first"
- "ignored-explicit-python-prohibition-and-used-it-anyway"
- "claimed-task-complete-when-build-was-still-failing"
- "overwrote-working-code-with-broken-implementation-silently"
- "asked-clarifying-question-instead-of-just-doing-task"

EXAMPLES OF BAD DESCRIPTIONS:
- "user-was-frustrated-with-assistant-response-quality" (too generic)
- "error" (too short, not descriptive)
- "the-assistant-made-a-mistake-on-this-task" (still too generic)

OUTPUT: Return ONLY the 8-word description, nothing else.`;

  const userPrompt = `SENTIMENT: ${sentimentSummary}

RECENT CONVERSATION:
${recentConvos}

TOOLS USED: ${recentTools || 'none'}

Generate the 8-word description:`;

  try {
    const result = await inference({
      systemPrompt,
      userPrompt,
      level: 'fast',
      timeout: 10000,
    });

    if (result.success && result.output) {
      // Clean and validate
      let desc = result.output.trim().toLowerCase();
      desc = desc.replace(/[^a-z0-9\s-]/g, '');
      desc = desc.replace(/\s+/g, '-');

      // Ensure it's roughly 8 words
      const words = desc.split('-').filter(w => w.length > 0);
      if (words.length > 10) {
        desc = words.slice(0, 8).join('-');
      } else if (words.length < 5) {
        desc = `low-rating-failure-${words.join('-')}`;
      }

      return desc;
    }
  } catch (err) {
    console.error(`[FailureCapture] Inference error: ${err}`);
  }

  // Fallback: derive from sentiment summary
  const fallback = sentimentSummary
    .toLowerCase()
    .replace(/[^a-z0-9\s]/g, '')
    .split(/\s+/)
    .slice(0, 8)
    .join('-');

  return fallback || 'unspecified-failure-needs-manual-review';
}

/**
 * Get PST timestamp components
 */
function getPSTComponents(): {
  year: string;
  month: string;
  day: string;
  hours: string;
  minutes: string;
  seconds: string;
} {
  const now = new Date();
  const pst = new Date(now.toLocaleString('en-US', { timeZone: 'America/Los_Angeles' }));

  return {
    year: pst.getFullYear().toString(),
    month: String(pst.getMonth() + 1).padStart(2, '0'),
    day: String(pst.getDate()).padStart(2, '0'),
    hours: String(pst.getHours()).padStart(2, '0'),
    minutes: String(pst.getMinutes()).padStart(2, '0'),
    seconds: String(pst.getSeconds()).padStart(2, '0'),
  };
}

/**
 * Create the full failure capture
 */
export async function captureFailure(input: FailureCaptureInput): Promise<string | null> {
  const { transcriptPath, rating, sentimentSummary, detailedContext, sessionId } = input;

  // Only capture ratings 1-3
  if (rating > 3) {
    console.error(`[FailureCapture] Rating ${rating} is above threshold (1-3), skipping`);
    return null;
  }

  if (!existsSync(transcriptPath)) {
    console.error(`[FailureCapture] Transcript not found: ${transcriptPath}`);
    return null;
  }

  // Parse transcript
  const { entries, toolCalls, conversations } = parseTranscript(transcriptPath);

  // Generate description
  const description = await generateDescription(sentimentSummary, conversations, toolCalls);

  // Create directory structure
  const { year, month, day, hours, minutes, seconds } = getPSTComponents();
  const timestamp = `${year}-${month}-${day}-${hours}${minutes}${seconds}`;
  const dirName = `${timestamp}_${description}`;
  const yearMonth = `${year}-${month}`;

  const failuresDir = join(PAI_DIR, 'MEMORY', 'LEARNING', 'FAILURES', yearMonth);
  const failureDir = join(failuresDir, dirName);

  if (!existsSync(failuresDir)) {
    mkdirSync(failuresDir, { recursive: true });
  }
  mkdirSync(failureDir, { recursive: true });

  // 1. Copy raw transcript
  const transcriptDest = join(failureDir, 'transcript.jsonl');
  copyFileSync(transcriptPath, transcriptDest);

  // 2. Write sentiment.json
  const sentimentData = {
    rating,
    summary: sentimentSummary,
    detailed_context: detailedContext || '',
    session_id: sessionId || '',
    captured_at: new Date().toISOString(),
    transcript_source: basename(transcriptPath),
  };
  writeFileSync(
    join(failureDir, 'sentiment.json'),
    JSON.stringify(sentimentData, null, 2),
    'utf-8'
  );

  // 3. Write tool-calls.json
  writeFileSync(
    join(failureDir, 'tool-calls.json'),
    JSON.stringify(toolCalls, null, 2),
    'utf-8'
  );

  // 4. Write CONTEXT.md
  const contextMd = `---
capture_type: FAILURE_ANALYSIS
timestamp: ${year}-${month}-${day} ${hours}:${minutes}:${seconds} PST
rating: ${rating}
description: ${description}
session_id: ${sessionId || 'unknown'}
---

# Failure Analysis: ${description.replace(/-/g, ' ')}

**Date:** ${year}-${month}-${day}
**Rating:** ${rating}/10
**Summary:** ${sentimentSummary}

---

## What Happened

${detailedContext || 'No detailed analysis available. Review the transcript for context.'}

---

## Conversation Summary

${conversations.slice(-10).map(c => `**${c.role.toUpperCase()}:** ${c.content.slice(0, 500)}${c.content.length > 500 ? '...' : ''}`).join('\n\n')}

---

## Tool Calls (${toolCalls.length} total)

${toolCalls.length > 0 ? toolCalls.slice(-10).map(t => `- **${t.name}**: ${JSON.stringify(t.input).slice(0, 200)}...`).join('\n') : 'No tool calls recorded'}

---

## Files in This Capture

| File | Description |
|------|-------------|
| \`CONTEXT.md\` | This analysis document |
| \`transcript.jsonl\` | Full raw conversation (${entries.length} entries) |
| \`sentiment.json\` | Sentiment analysis metadata |
| \`tool-calls.json\` | Extracted tool invocations (${toolCalls.length} calls) |

---

## Learning System Notes

This failure has been captured for retroactive analysis. The learning system should:

1. Review the full transcript for root cause
2. Identify systemic issues that contributed
3. Determine if this failure type has occurred before
4. Propose improvements to prevent recurrence

**Action Required:** This capture needs manual review to extract learnings.
`;

  writeFileSync(join(failureDir, 'CONTEXT.md'), contextMd, 'utf-8');

  console.error(`[FailureCapture] Created failure capture at: ${failureDir}`);
  return failureDir;
}

/**
 * Migrate existing low-rating learning files to FAILURES structure
 */
export async function migrateExistingFailures(): Promise<{ migrated: number; errors: string[] }> {
  const results = { migrated: 0, errors: [] as string[] };

  // Read ratings.jsonl and find all 1-3 ratings
  const ratingsFile = join(PAI_DIR, 'MEMORY', 'LEARNING', 'SIGNALS', 'ratings.jsonl');

  if (!existsSync(ratingsFile)) {
    results.errors.push('ratings.jsonl not found');
    return results;
  }

  const content = readFileSync(ratingsFile, 'utf-8');
  const lines = content.trim().split('\n');

  // Get ratings from last month
  const oneMonthAgo = new Date();
  oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);

  for (const line of lines) {
    if (!line.trim()) continue;
    try {
      const entry = JSON.parse(line);
      if (entry.rating >= 1 && entry.rating <= 3) {
        const entryDate = new Date(entry.timestamp);
        if (entryDate >= oneMonthAgo) {
          // Create a failure capture from this entry
          // Note: We don't have the full transcript, so we create a minimal capture
          const { year, month, day, hours, minutes, seconds } = (() => {
            const d = entryDate;
            return {
              year: d.getFullYear().toString(),
              month: String(d.getMonth() + 1).padStart(2, '0'),
              day: String(d.getDate()).padStart(2, '0'),
              hours: String(d.getHours()).padStart(2, '0'),
              minutes: String(d.getMinutes()).padStart(2, '0'),
              seconds: String(d.getSeconds()).padStart(2, '0'),
            };
          })();

          const desc = (entry.sentiment_summary || 'historical-low-rating-from-migration')
            .toLowerCase()
            .replace(/[^a-z0-9\s]/g, '')
            .split(/\s+/)
            .slice(0, 8)
            .join('-');

          const timestamp = `${year}-${month}-${day}-${hours}${minutes}${seconds}`;
          const dirName = `${timestamp}_${desc}`;
          const yearMonth = `${year}-${month}`;

          const failuresDir = join(PAI_DIR, 'MEMORY', 'LEARNING', 'FAILURES', yearMonth);
          const failureDir = join(failuresDir, dirName);

          // Skip if already exists
          if (existsSync(failureDir)) {
            continue;
          }

          if (!existsSync(failuresDir)) {
            mkdirSync(failuresDir, { recursive: true });
          }
          mkdirSync(failureDir, { recursive: true });

          // Write minimal context
          const sentimentData = {
            rating: entry.rating,
            summary: entry.sentiment_summary || 'No summary available',
            source: entry.source || 'migration',
            confidence: entry.confidence || 0,
            session_id: entry.session_id || 'unknown',
            original_timestamp: entry.timestamp,
            migrated_at: new Date().toISOString(),
          };

          writeFileSync(
            join(failureDir, 'sentiment.json'),
            JSON.stringify(sentimentData, null, 2),
            'utf-8'
          );

          const contextMd = `---
capture_type: FAILURE_ANALYSIS_MIGRATED
original_timestamp: ${entry.timestamp}
rating: ${entry.rating}
source: migration
---

# Migrated Failure: ${desc.replace(/-/g, ' ')}

**Original Date:** ${entry.timestamp}
**Rating:** ${entry.rating}/10
**Summary:** ${entry.sentiment_summary || 'No summary available'}

---

## Migration Note

This failure was migrated from historical ratings data. The original transcript
is not available, but the sentiment analysis was preserved.

**Session ID:** ${entry.session_id || 'unknown'}
**Source:** ${entry.source || 'unknown'}
**Confidence:** ${entry.confidence || 'unknown'}

---

## Learning System Notes

Review the ALGORITHM and SYSTEM learning directories for corresponding
learning files that may contain more context about this failure.

**Action Required:** Manual review needed to correlate with existing learning files.
`;

          writeFileSync(join(failureDir, 'CONTEXT.md'), contextMd, 'utf-8');

          results.migrated++;
        }
      }
    } catch (err) {
      results.errors.push(`Error processing line: ${err}`);
    }
  }

  return results;
}

// CLI
if (import.meta.main) {
  const args = process.argv.slice(2);

  if (args[0] === '--migrate') {
    console.log('[FailureCapture] Starting migration of existing low ratings...');
    migrateExistingFailures().then(results => {
      console.log(`[FailureCapture] Migration complete: ${results.migrated} failures migrated`);
      if (results.errors.length > 0) {
        console.error(`[FailureCapture] Errors: ${results.errors.join(', ')}`);
      }
    });
  } else if (args.length >= 3) {
    const [transcriptPath, rating, sentimentSummary, detailedContext] = args;
    captureFailure({
      transcriptPath,
      rating: parseInt(rating, 10),
      sentimentSummary,
      detailedContext,
    }).then(dir => {
      if (dir) {
        console.log(dir);
      } else {
        process.exit(1);
      }
    });
  } else {
    console.log(`Usage:
  bun FailureCapture.ts <transcript_path> <rating> <sentiment_summary> [detailed_context]
  bun FailureCapture.ts --migrate   # Migrate existing low ratings from ratings.jsonl
`);
    process.exit(1);
  }
}
