/**
 * Session export utilities for Hermit.
 *
 * Provides formatters to export session data as plain text, Markdown, or JSON,
 * and a download trigger for browser-based file saving.
 */

import type { Chunk, SessionDetail } from '@renderer/types/data';
import type { ContentBlock } from '@shared/types';

// =============================================================================
// Types
// =============================================================================

export type ExportFormat = 'markdown' | 'json' | 'plaintext';

interface ExtractOptions {
  includeThinking?: boolean;
}

// =============================================================================
// Helpers (not exported)
// =============================================================================

function formatNumber(n: number): string {
  return n.toLocaleString('en-US');
}

function formatCost(cost?: number): string {
  if (cost == null) return 'N/A';
  return `$${cost.toFixed(2)}`;
}

function formatTimestamp(date: Date): string {
  return date
    .toISOString()
    .replace('T', ' ')
    .replace(/\.\d{3}Z$/, ' UTC');
}

function formatDurationForExport(ms: number): string {
  if (ms < 1000) return `${ms}ms`;
  const secs = Math.floor(ms / 1000);
  const mins = Math.floor(secs / 60);
  const remainSecs = secs % 60;
  if (mins === 0) return `${secs}s`;
  return `${mins}m ${remainSecs}s`;
}

function truncate(text: string, maxLen: number): string {
  if (text.length <= maxLen) return text;
  return text.slice(0, maxLen) + '...';
}

// =============================================================================
// extractTextFromContent
// =============================================================================

/**
 * Extract readable text from message content (string or ContentBlock[]).
 *
 * @param content - String content or array of ContentBlocks
 * @param options - Options controlling extraction behavior
 * @returns Extracted text with newlines between blocks
 */
export function extractTextFromContent(
  content: string | ContentBlock[],
  options?: ExtractOptions
): string {
  if (typeof content === 'string') {
    return content;
  }

  if (!Array.isArray(content) || content.length === 0) {
    return '';
  }

  const parts: string[] = [];

  for (const block of content) {
    switch (block.type) {
      case 'text':
        parts.push(block.text);
        break;
      case 'thinking':
        if (options?.includeThinking) {
          parts.push(block.thinking);
        }
        break;
      case 'tool_use':
        parts.push(`Tool: ${block.name}\nInput: ${JSON.stringify(block.input, null, 2)}`);
        break;
      case 'tool_result': {
        const resultContent = block.content;
        if (typeof resultContent === 'string') {
          parts.push(resultContent);
        } else if (Array.isArray(resultContent)) {
          // Recursively extract from nested content blocks
          const nested = extractTextFromContent(resultContent);
          if (nested) parts.push(nested);
        }
        break;
      }
      case 'image':
        parts.push('[Image]');
        break;
    }
  }

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

// =============================================================================
// Plain Text Chunk Formatters
// =============================================================================

function formatToolExecutionPlainText(exec: {
  toolCall: { name: string; input: Record<string, unknown> };
  result?: { content: string | unknown[]; isError: boolean };
}): string[] {
  const lines: string[] = [];
  lines.push(`  TOOL: ${exec.toolCall.name}`);
  lines.push(`  Input: ${JSON.stringify(exec.toolCall.input)}`);
  if (exec.result) {
    const prefix = exec.result.isError ? '  [ERROR] Result: ' : '  Result: ';
    const resultText =
      typeof exec.result.content === 'string'
        ? exec.result.content
        : JSON.stringify(exec.result.content);
    lines.push(`${prefix}${truncate(resultText, 500)}`);
  } else {
    lines.push('  [No result]');
  }
  return lines;
}

function formatChunkPlainText(chunk: Chunk): string[] {
  const lines: string[] = [];

  switch (chunk.chunkType) {
    case 'user': {
      lines.push(`USER: ${extractTextFromContent(chunk.userMessage.content)}`);
      break;
    }
    case 'ai': {
      // Render thinking blocks first, then text
      for (const response of chunk.responses) {
        if (Array.isArray(response.content)) {
          // Check for thinking blocks
          for (const block of response.content) {
            if (block.type === 'thinking') {
              lines.push(`THINKING: ${block.thinking}`);
            }
          }
          // Then text
          const text = extractTextFromContent(response.content);
          if (text) {
            lines.push(`ASSISTANT: ${text}`);
          }
        } else if (typeof response.content === 'string') {
          lines.push(`ASSISTANT: ${response.content}`);
        }
      }

      // Tool executions
      for (const exec of chunk.toolExecutions) {
        lines.push(...formatToolExecutionPlainText(exec));
      }
      break;
    }
    case 'system': {
      lines.push(`SYSTEM: ${chunk.commandOutput}`);
      break;
    }
    case 'compact': {
      lines.push('[Context compacted]');
      break;
    }
  }

  return lines;
}

// =============================================================================
// Markdown Chunk Formatters
// =============================================================================

function formatToolExecutionMarkdown(exec: {
  toolCall: { name: string; input: Record<string, unknown> };
  result?: { content: string | unknown[]; isError: boolean };
}): string[] {
  const lines: string[] = [];
  lines.push(`**Tool:** \`${exec.toolCall.name}\``);
  lines.push('');
  lines.push('```json');
  lines.push(JSON.stringify(exec.toolCall.input, null, 2));
  lines.push('```');
  lines.push('');

  if (exec.result) {
    if (exec.result.isError) {
      lines.push('**Error:**');
    } else {
      lines.push('**Result:**');
    }
    lines.push('');
    const resultText =
      typeof exec.result.content === 'string'
        ? exec.result.content
        : JSON.stringify(exec.result.content, null, 2);
    lines.push('```');
    lines.push(truncate(resultText, 2000));
    lines.push('```');
  }

  return lines;
}

function formatChunkMarkdown(chunk: Chunk, turnNum: number): string[] {
  const lines: string[] = [];

  switch (chunk.chunkType) {
    case 'user': {
      lines.push(`### User (Turn ${turnNum})`);
      lines.push('');
      lines.push(extractTextFromContent(chunk.userMessage.content));
      lines.push('');
      break;
    }
    case 'ai': {
      lines.push(`### Assistant (Turn ${turnNum})`);
      lines.push('');

      for (const response of chunk.responses) {
        if (Array.isArray(response.content)) {
          // Thinking blocks as blockquotes
          for (const block of response.content) {
            if (block.type === 'thinking') {
              lines.push('> *Thinking:*');
              for (const thinkLine of block.thinking.split('\n')) {
                lines.push(`> ${thinkLine}`);
              }
              lines.push('');
            }
          }
          // Text content
          const text = extractTextFromContent(response.content);
          if (text) {
            lines.push(text);
            lines.push('');
          }
        } else if (typeof response.content === 'string') {
          lines.push(response.content);
          lines.push('');
        }
      }

      // Tool executions
      for (const exec of chunk.toolExecutions) {
        lines.push(...formatToolExecutionMarkdown(exec));
        lines.push('');
      }
      break;
    }
    case 'system': {
      lines.push(`### System (Turn ${turnNum})`);
      lines.push('');
      lines.push(chunk.commandOutput);
      lines.push('');
      break;
    }
    case 'compact': {
      lines.push('---');
      lines.push('');
      lines.push('*Context compacted*');
      lines.push('');
      break;
    }
  }

  return lines;
}

// =============================================================================
// Export Functions
// =============================================================================

/**
 * Export session as plain text transcript.
 *
 * Produces a flat text format with clear labels (USER:, ASSISTANT:, TOOL:, etc.)
 * and separator lines between sections.
 */
export function exportAsPlainText(detail: SessionDetail): string {
  const { session, metrics, chunks } = detail;
  const lines: string[] = [];

  // Header
  lines.push('═'.repeat(60));
  lines.push('SESSION EXPORT');
  lines.push('═'.repeat(60));
  lines.push(`Session:  ${session.id}`);
  lines.push(`Project:  ${session.projectPath}`);
  if (session.gitBranch) {
    lines.push(`Branch:   ${session.gitBranch}`);
  }
  lines.push(`Date:     ${formatTimestamp(new Date(session.createdAt))}`);
  lines.push('');

  // Metrics
  lines.push('─'.repeat(40));
  lines.push('METRICS');
  lines.push('─'.repeat(40));
  lines.push(`Duration:       ${formatDurationForExport(metrics.durationMs)}`);
  lines.push(`Total Tokens:   ${formatNumber(metrics.totalTokens)}`);
  lines.push(`Input Tokens:   ${formatNumber(metrics.inputTokens)}`);
  lines.push(`Output Tokens:  ${formatNumber(metrics.outputTokens)}`);
  lines.push(`Cache Read:     ${formatNumber(metrics.cacheReadTokens)}`);
  lines.push(`Cache Created:  ${formatNumber(metrics.cacheCreationTokens)}`);
  lines.push(`Messages:       ${formatNumber(metrics.messageCount)}`);
  lines.push(`Cost:           ${formatCost(metrics.costUsd)}`);
  lines.push('');

  // Conversation
  lines.push('═'.repeat(60));
  lines.push('CONVERSATION');
  lines.push('═'.repeat(60));
  lines.push('');

  for (const chunk of chunks) {
    lines.push('─'.repeat(40));
    lines.push(...formatChunkPlainText(chunk));
    lines.push('');
  }

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

/**
 * Export session as structured Markdown.
 *
 * Produces Markdown with headings, tables, code blocks, and blockquotes
 * suitable for viewing in any Markdown renderer.
 */
export function exportAsMarkdown(detail: SessionDetail): string {
  const { session, metrics, chunks } = detail;
  const lines: string[] = [];

  // Title
  lines.push('# Session Export');
  lines.push('');

  // Property table
  lines.push('| Property | Value |');
  lines.push('|----------|-------|');
  lines.push(`| Session | \`${session.id}\` |`);
  lines.push(`| Project | \`${session.projectPath}\` |`);
  if (session.gitBranch) {
    lines.push(`| Branch | \`${session.gitBranch}\` |`);
  }
  lines.push(`| Date | ${formatTimestamp(new Date(session.createdAt))} |`);
  lines.push('');

  // Metrics table
  lines.push('## Metrics');
  lines.push('');
  lines.push('| Metric | Value |');
  lines.push('|--------|-------|');
  lines.push(`| Duration | ${formatDurationForExport(metrics.durationMs)} |`);
  lines.push(`| Total Tokens | ${formatNumber(metrics.totalTokens)} |`);
  lines.push(`| Input Tokens | ${formatNumber(metrics.inputTokens)} |`);
  lines.push(`| Output Tokens | ${formatNumber(metrics.outputTokens)} |`);
  lines.push(`| Cache Read | ${formatNumber(metrics.cacheReadTokens)} |`);
  lines.push(`| Cache Created | ${formatNumber(metrics.cacheCreationTokens)} |`);
  lines.push(`| Messages | ${formatNumber(metrics.messageCount)} |`);
  lines.push(`| Cost | ${formatCost(metrics.costUsd)} |`);
  lines.push('');

  // Conversation
  lines.push('## Conversation');
  lines.push('');

  let turnNum = 0;
  for (const chunk of chunks) {
    turnNum++;
    lines.push(...formatChunkMarkdown(chunk, turnNum));
  }

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

/**
 * Export session as pretty-printed JSON.
 */
export function exportAsJson(detail: SessionDetail): string {
  return JSON.stringify(detail, null, 2);
}

/**
 * Trigger a browser file download for the given session in the specified format.
 *
 * Creates a Blob, generates an object URL, and simulates an anchor click
 * to initiate the download.
 */
export function triggerDownload(detail: SessionDetail, format: ExportFormat): void {
  const formatters: Record<
    ExportFormat,
    { fn: (d: SessionDetail) => string; ext: string; mime: string }
  > = {
    markdown: { fn: exportAsMarkdown, ext: 'md', mime: 'text/markdown;charset=utf-8' },
    json: { fn: exportAsJson, ext: 'json', mime: 'application/json;charset=utf-8' },
    plaintext: { fn: exportAsPlainText, ext: 'txt', mime: 'text/plain;charset=utf-8' },
  };

  const { fn, ext, mime } = formatters[format];
  const content = fn(detail);
  const blob = new Blob([content], { type: mime });
  const url = URL.createObjectURL(blob);

  const anchor = document.createElement('a');
  anchor.href = url;
  anchor.download = `session-${detail.session.id}.${ext}`;
  document.body.appendChild(anchor);
  anchor.click();
  document.body.removeChild(anchor);
  URL.revokeObjectURL(url);
}
