/**
 * Resume Strategy Engine - Determines optimal resume approach
 * Supports native resume, prompt concatenation, and hybrid modes
 */

import type { ConversationTurn, ConversationRecord, NativeSessionMapping } from './cli-history-store.js';

/**
 * Emit user warning for silent fallback scenarios
 */
function warnUser(message: string): void {
  console.warn(`[ccw] ${message}`);
}

// Strategy types
export type ResumeStrategy = 'native' | 'prompt-concat' | 'hybrid';

// Resume decision result
export interface ResumeDecision {
  strategy: ResumeStrategy;
  nativeSessionId?: string;      // Native UUID for native/hybrid modes
  isLatest?: boolean;            // Use latest/--last flag
  contextTurns?: ConversationTurn[];  // Turns to include as context prefix
  primaryConversationId?: string; // Primary conversation for append
}

// Resume strategy options
export interface ResumeStrategyOptions {
  tool: string;
  resumeIds: string[];           // CCW IDs to resume from
  customId?: string;             // New custom ID (fork scenario)
  forceNative?: boolean;         // Force native resume
  forcePromptConcat?: boolean;   // Force prompt concatenation

  // Lookup functions (dependency injection)
  getNativeSessionId: (ccwId: string) => string | null;
  getConversation: (ccwId: string) => ConversationRecord | null;
  getConversationTool: (ccwId: string) => string | null;
}

/**
 * Determine the optimal resume strategy based on scenario
 *
 * Scenarios:
 * 1. Single append (no customId) → native if mapping exists
 * 2. Fork (customId provided) → prompt-concat (new conversation)
 * 3. Merge multiple → hybrid (primary native + others as context)
 * 4. Cross-tool → prompt-concat (tools differ)
 * 5. resume=true (latest) → native with isLatest flag
 */
export function determineResumeStrategy(options: ResumeStrategyOptions): ResumeDecision {
  const {
    tool,
    resumeIds,
    customId,
    forceNative,
    forcePromptConcat,
    getNativeSessionId,
    getConversation,
    getConversationTool
  } = options;

  // Force prompt concatenation
  if (forcePromptConcat) {
    return buildPromptConcatDecision(resumeIds, getConversation);
  }

  // No resume IDs - new conversation
  if (resumeIds.length === 0) {
    return { strategy: 'prompt-concat' };
  }

  // Scenario 5: resume=true (latest) - use native latest
  // This is handled before this function is called, but included for completeness

  // Scenario 2: Fork (customId provided) → always prompt-concat
  if (customId) {
    return buildPromptConcatDecision(resumeIds, getConversation);
  }

  // Scenario 4: Check for cross-tool resume
  const crossTool = resumeIds.some(id => {
    const convTool = getConversationTool(id);
    return convTool && convTool !== tool;
  });

  if (crossTool) {
    warnUser('Cross-tool resume: using prompt concatenation (different tool)');
    return buildPromptConcatDecision(resumeIds, getConversation);
  }

  // Scenario 1: Single append
  if (resumeIds.length === 1) {
    const nativeId = getNativeSessionId(resumeIds[0]);

    if (nativeId || forceNative) {
      return {
        strategy: 'native',
        nativeSessionId: nativeId || undefined,
        primaryConversationId: resumeIds[0]
      };
    }

    // No native mapping, fall back to prompt-concat
    warnUser('No native session mapping found, using prompt concatenation');
    return buildPromptConcatDecision(resumeIds, getConversation);
  }

  // Scenario 3: Merge multiple conversations → hybrid mode
  return buildHybridDecision(resumeIds, tool, getNativeSessionId, getConversation);
}

/**
 * Build prompt-concat decision with all turns loaded
 */
function buildPromptConcatDecision(
  resumeIds: string[],
  getConversation: (ccwId: string) => ConversationRecord | null
): ResumeDecision {
  const allTurns: ConversationTurn[] = [];
  let hasMissingConversation = false;

  for (const id of resumeIds) {
    const conversation = getConversation(id);
    if (conversation) {
      // Add source ID to each turn for tracking
      const turnsWithSource = conversation.turns.map(turn => ({
        ...turn,
        _sourceId: id
      }));
      allTurns.push(...turnsWithSource as ConversationTurn[]);
    } else {
      hasMissingConversation = true;
    }
  }

  // Warn if any conversation was not found
  if (hasMissingConversation) {
    warnUser('One or more resume IDs not found, using prompt concatenation (new session created)');
  }

  // Sort by timestamp
  allTurns.sort((a, b) =>
    new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
  );

  return {
    strategy: 'prompt-concat',
    contextTurns: allTurns,
    primaryConversationId: resumeIds[0]
  };
}

/**
 * Build hybrid decision: primary uses native, others as context prefix
 */
function buildHybridDecision(
  resumeIds: string[],
  tool: string,
  getNativeSessionId: (ccwId: string) => string | null,
  getConversation: (ccwId: string) => ConversationRecord | null
): ResumeDecision {
  // Find the first ID with native session mapping
  let primaryId: string | null = null;
  let nativeId: string | null = null;

  for (const id of resumeIds) {
    const native = getNativeSessionId(id);
    if (native) {
      primaryId = id;
      nativeId = native;
      break;
    }
  }

  // If no native mapping found, use first as primary
  if (!primaryId) {
    primaryId = resumeIds[0];
  }

  // Collect context turns from non-primary conversations
  const contextTurns: ConversationTurn[] = [];

  for (const id of resumeIds) {
    if (id === primaryId && nativeId) {
      // Skip primary if using native - its context is handled natively
      continue;
    }

    const conversation = getConversation(id);
    if (conversation) {
      const turnsWithSource = conversation.turns.map(turn => ({
        ...turn,
        _sourceId: id
      }));
      contextTurns.push(...turnsWithSource as ConversationTurn[]);
    }
  }

  // Sort context turns by timestamp
  contextTurns.sort((a, b) =>
    new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
  );

  // If we have native ID, use hybrid; otherwise fall back to prompt-concat
  if (nativeId) {
    return {
      strategy: 'hybrid',
      nativeSessionId: nativeId,
      contextTurns: contextTurns.length > 0 ? contextTurns : undefined,
      primaryConversationId: primaryId
    };
  }

  // No native mapping, use full prompt-concat
  return buildPromptConcatDecision(resumeIds, getConversation);
}

/**
 * Build context prefix for hybrid mode
 * Formats non-primary conversation turns as context
 */
export function buildContextPrefix(
  contextTurns: ConversationTurn[],
  format: 'plain' | 'yaml' | 'json' = 'plain'
): string {
  if (!contextTurns || contextTurns.length === 0) {
    return '';
  }

  const maxOutputLength = 4096; // Truncate long outputs

  switch (format) {
    case 'yaml':
      return buildYamlContext(contextTurns, maxOutputLength);
    case 'json':
      return buildJsonContext(contextTurns, maxOutputLength);
    default:
      return buildPlainContext(contextTurns, maxOutputLength);
  }
}

function buildPlainContext(turns: ConversationTurn[], maxLength: number): string {
  const lines: string[] = [
    '=== MERGED CONTEXT FROM OTHER CONVERSATIONS ===',
    ''
  ];

  for (const turn of turns) {
    const sourceId = (turn as any)._sourceId || 'unknown';
    lines.push(`--- Turn ${turn.turn} [${sourceId}] ---`);
    lines.push(`USER:`);
    lines.push(turn.prompt);
    lines.push('');
    lines.push(`ASSISTANT:`);
    const output = turn.output.stdout || '';
    lines.push(output.length > maxLength ? output.substring(0, maxLength) + '\n[truncated]' : output);
    lines.push('');
  }

  lines.push('=== END MERGED CONTEXT ===');
  lines.push('');

  return lines.join('\n');
}

function buildYamlContext(turns: ConversationTurn[], maxLength: number): string {
  const lines: string[] = [
    'merged_context:',
    '  source: "other_conversations"',
    '  turns:'
  ];

  for (const turn of turns) {
    const sourceId = (turn as any)._sourceId || 'unknown';
    const output = turn.output.stdout || '';
    const truncatedOutput = output.length > maxLength
      ? output.substring(0, maxLength) + '\n[truncated]'
      : output;

    lines.push(`    - turn: ${turn.turn}`);
    lines.push(`      source: "${sourceId}"`);
    lines.push(`      user: |`);
    lines.push(turn.prompt.split('\n').map(l => `        ${l}`).join('\n'));
    lines.push(`      assistant: |`);
    lines.push(truncatedOutput.split('\n').map(l => `        ${l}`).join('\n'));
  }

  lines.push('');
  return lines.join('\n');
}

function buildJsonContext(turns: ConversationTurn[], maxLength: number): string {
  const context = {
    merged_context: {
      source: 'other_conversations',
      turns: turns.map(turn => {
        const output = turn.output.stdout || '';
        return {
          turn: turn.turn,
          source: (turn as any)._sourceId || 'unknown',
          user: turn.prompt,
          assistant: output.length > maxLength
            ? output.substring(0, maxLength) + '\n[truncated]'
            : output
        };
      })
    }
  };

  return JSON.stringify(context, null, 2) + '\n\n';
}

/**
 * Check if a resume scenario requires native resume
 */
export function shouldUseNativeResume(
  tool: string,
  resumeIds: string[],
  customId: string | undefined,
  getNativeSessionId: (ccwId: string) => string | null,
  getConversationTool: (ccwId: string) => string | null
): boolean {
  // Fork always uses prompt-concat
  if (customId) return false;

  // No resume IDs
  if (resumeIds.length === 0) return false;

  // Cross-tool not supported natively
  const crossTool = resumeIds.some(id => {
    const convTool = getConversationTool(id);
    return convTool && convTool !== tool;
  });
  if (crossTool) return false;

  // Single resume with native mapping
  if (resumeIds.length === 1) {
    return !!getNativeSessionId(resumeIds[0]);
  }

  // Merge: at least one needs native mapping for hybrid
  return resumeIds.some(id => !!getNativeSessionId(id));
}

/**
 * Get resume mode description for logging
 */
export function getResumeModeDescription(decision: ResumeDecision): string {
  switch (decision.strategy) {
    case 'native':
      return `Native resume (session: ${decision.nativeSessionId || 'latest'})`;
    case 'hybrid':
      const contextCount = decision.contextTurns?.length || 0;
      return `Hybrid (native + ${contextCount} context turns)`;
    case 'prompt-concat':
      const turnCount = decision.contextTurns?.length || 0;
      return `Prompt concat (${turnCount} turns)`;
    default:
      return 'Unknown';
  }
}
