/**
 * Message Formatter for Structured Context Window
 *
 * Uses HTML <details> elements for collapsible sections.
 * This format is understood by LLMs and renders natively in browsers.
 */

import { FEEDBACK_KEY } from '../../../lib/shared/schemas/approvals';
import { getString, isRecord } from '../../../lib/utils/type-guards';

/**
 * Format compact timestamp (YYYY-MM-DD HH:MM:SS UTC)
 */
function shortTime(timestamp: number): string {
  const iso = new Date(timestamp).toISOString();
  return iso.slice(0, 10) + ' ' + iso.slice(11, 19) + 'Z';
}

/**
 * Escape HTML special characters to prevent structure breakage and XSS
 */
function escapeHtml(value: string): string {
  return value
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

/**
 * Wrap content in a collapsible <details> section.
 * Used for all context window sections for unified formatting.
 */
export function wrapInDetails(
  summary: string,
  content: string,
  open = false,
): string {
  const openAttr = open ? ' open' : '';
  return `<details${openAttr}>
<summary>${escapeHtml(summary)}</summary>

${escapeHtml(content)}

</details>`;
}

/**
 * Safely stringify a value, handling circular refs, BigInt, and other edge cases
 */
function safeStringify(value: unknown): string {
  if (typeof value === 'string') return value;
  try {
    return JSON.stringify(value, (_, v) =>
      typeof v === 'bigint' ? v.toString() : v,
    );
  } catch {
    return String(value);
  }
}

export type ToolOutputAge = 'recent' | 'mid' | 'old';

const TOOL_OUTPUT_LIMITS: Record<ToolOutputAge, number> = {
  recent: 8000,
  mid: 2000,
  old: 300,
};

/**
 * Summarize output (truncate long values)
 * Default limit increased to 8000 chars to preserve meaningful tool results
 * like web research reports, RAG results, and document analysis.
 */
function summarize(output: unknown, max = 8000): string {
  if (output === null || output === undefined) return '-';
  const str = safeStringify(output);
  return str.length > max ? str.slice(0, max) + '...' : str;
}

function summarizeForHistory(output: unknown, age: ToolOutputAge): string {
  return summarize(output, TOOL_OUTPUT_LIMITS[age]);
}

/**
 * Format a user message
 */
export function formatUserMessage(content: string, timestamp: number): string {
  return `User[${shortTime(timestamp)}]: ${content}`;
}

/**
 * Format an assistant message
 */
export function formatAssistantMessage(
  content: string,
  timestamp: number,
): string {
  return `Assistant[${shortTime(timestamp)}]: ${content}`;
}

/**
 * Format a tool call with its input and output (legacy)
 * Uses log-style format to prevent AI from mimicking this as an action instruction
 */
export function formatToolCall(
  toolName: string,
  _input: unknown,
  output: unknown,
  _timestamp: number,
  status: 'success' | 'error' = 'success',
): string {
  const s = status === 'success' ? '✓' : '✗';
  return `[Tool Result] ${toolName} (${s}): ${summarize(output)}`;
}

/**
 * Format a tool call summary
 * Uses log-style format to prevent AI from mimicking this as an action instruction.
 * When `age` is provided, applies tiered truncation (errors always get full output).
 */
export function formatToolCallSummary(
  toolName: string,
  output: unknown,
  _timestamp: number,
  status: 'success' | 'error' = 'success',
  age?: ToolOutputAge,
): string {
  const s = status === 'success' ? '✓' : '✗';
  const summary =
    age && status !== 'error'
      ? summarizeForHistory(output, age)
      : summarize(output);
  return `[Tool Result] ${toolName} (${s}): ${summary}`;
}

/**
 * Format a human input request
 */
export function formatHumanInputRequest(
  id: string,
  question: string,
  fields: Array<{
    label: string;
    type: string;
    options?: Array<{ label: string }>;
  }>,
  context?: string,
  timestamp?: number,
): string {
  const t = timestamp ? `[${shortTime(timestamp)}]` : '';
  const fieldSummaries = fields.map((f) => {
    const opts = f.options?.length
      ? ` [${f.options.map((o) => o.label).join(', ')}]`
      : '';
    return `${f.label} (${f.type})${opts}`;
  });
  const fieldsStr =
    fieldSummaries.length > 0 ? ` Fields: ${fieldSummaries.join('; ')}` : '';
  return `HumanInput${t}: ${question}${fieldsStr}`;
}

/**
 * Format a human response
 */
export function formatHumanResponse(
  id: string,
  response: string | string[],
  user: string,
  timestamp: number,
): string {
  let r: string;
  if (Array.isArray(response)) {
    r = response.join(', ');
  } else {
    try {
      const parsed: unknown = JSON.parse(response);
      if (isRecord(parsed)) {
        const feedbackVal = getString(parsed, FEEDBACK_KEY);
        if (feedbackVal) {
          r = `[FEEDBACK] ${feedbackVal}`;
        } else {
          r = Object.entries(parsed)
            .map(([key, val]) =>
              Array.isArray(val)
                ? `${key}: ${val.join(', ')}`
                : `${key}: ${String(val)}`,
            )
            .join('; ');
        }
      } else {
        r = response;
      }
    } catch (e) {
      console.error('Failed to parse human response JSON:', e);
      r = response;
    }
  }
  return `HumanResponse[${shortTime(timestamp)}] by ${user}: ${r}`;
}

/**
 * Format context summary (collapsible)
 */
export function formatContextSummary(summary: string): string {
  return wrapInDetails('📝 Summary', summary);
}

/**
 * Format knowledge base (collapsible)
 */
export function formatKnowledgeBase(content: string): string {
  return wrapInDetails('📚 Knowledge', content);
}

/**
 * Format web search context (collapsible)
 */
export function formatWebContext(content: string): string {
  return wrapInDetails('🌐 Web Search', content);
}

/**
 * Format the artifacts available in the current thread (collapsible).
 * The body is *not* HTML-escaped because it is a structured XML-like
 * block the LLM is expected to read verbatim — escaping would force the
 * model to mentally un-escape every character of HTML/code content.
 * `buildArtifactsContext` is responsible for defusing the only delimiters
 * (`</artifact>`, `</details>`) that could break out of these wrappers.
 */
export function formatArtifactsContext(content: string): string {
  return `<details>
<summary>🧩 Artifacts</summary>

${content}

</details>`;
}

/**
 * Format task description (just prefixed user message for sub-agents)
 */
export function formatTaskDescription(description: string): string {
  return `User: ${description}`;
}

/**
 * Format additional context (collapsible)
 */
export function formatAdditionalContext(key: string, value: string): string {
  return wrapInDetails(`📎 ${key}`, value);
}

/**
 * Format parent thread reference (collapsible)
 */
export function formatParentThread(parentThreadId: string): string {
  return wrapInDetails('🔗 Parent', parentThreadId);
}

/**
 * Format conversation history section (collapsible)
 */
export function formatHistorySection(historyContent: string): string {
  return wrapInDetails('💬 History', historyContent);
}

/**
 * Format current user request section (expanded by default, distinct from history)
 */
export function formatCurrentRequestSection(content: string): string {
  return wrapInDetails('📩 Current Request', content, true);
}

/**
 * Format AI response section (expanded by default)
 */
export function formatCurrentTurnSection(content: string): string {
  return wrapInDetails('✨ AI Response', content, true);
}

/**
 * Format a system message
 */
export function formatSystemMessage(
  content: string,
  timestamp: number,
): string {
  return `System[${shortTime(timestamp)}]: ${content}`;
}

/**
 * Tool call info for current turn
 */
export interface CurrentTurnToolCall {
  toolName: string;
  status: string;
}

/**
 * Format the current turn (tool calls and assistant output only)
 * User input is already in message history, so we don't duplicate it here.
 */
export function formatCurrentTurn(params: {
  assistantOutput: string;
  toolCalls?: CurrentTurnToolCall[];
  timestamp: number;
}): string {
  const { assistantOutput, toolCalls, timestamp } = params;
  const parts: string[] = [];
  const t = shortTime(timestamp);

  if (toolCalls?.length) {
    parts.push(
      `Tools: ${toolCalls.map((tc) => `${tc.toolName}(${tc.status})`).join(', ')}`,
    );
  }
  if (assistantOutput) parts.push(`Assistant[${t}]: ${assistantOutput}`);

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