import type { Message } from '../../llm/openai-compat.js';
import { logger } from '../../logger.js';
import { estimateTokensFromText } from './token-estimate.js';

export const SUMMARY_MARKER_PREFIX = '# 会話履歴の要約（システム生成）';
const TOOL_OUTPUT_MAX_CHARS_DEFAULT = 2_000;

export interface SummarizeHistoryOptions {
  tailTurns?: number;            // default 2
  preserveRecentBudget?: number;  // tokens, default 8000
  toolOutputMaxChars?: number;   // default 2000
  runIsolatedLlm: (messages: Message[]) => Promise<string>;
}

export interface SummarizeHistoryResult {
  summarized: boolean;
  freedChars: number;
  summary?: string;
  reason?: string; // populated when summarized=false to explain why
}

interface TurnRange {
  assistantIndex: number;
  toolEnd: number; // exclusive end index
}

/**
 * Walk messages and group each assistant message with its consecutive tool
 * result messages into a "turn". Messages before the first assistant message
 * are considered preamble (system prompt + original user task).
 */
export function splitIntoTurns(messages: Message[]): {
  preambleEnd: number;
  turns: TurnRange[];
} {
  let preambleEnd = 0;
  // Preamble = leading non-assistant messages (system, user). The first
  // assistant message ends the preamble.
  while (preambleEnd < messages.length && messages[preambleEnd]!.role !== 'assistant') {
    preambleEnd++;
  }
  const turns: TurnRange[] = [];
  let i = preambleEnd;
  while (i < messages.length) {
    if (messages[i]!.role !== 'assistant') {
      // Loose user/tool messages outside an assistant turn — fold into the
      // previous turn if any, otherwise extend the preamble.
      if (turns.length > 0) {
        turns[turns.length - 1]!.toolEnd = i + 1;
      } else {
        preambleEnd = i + 1;
      }
      i++;
      continue;
    }
    const start = i;
    let end = i + 1;
    while (end < messages.length && messages[end]!.role !== 'assistant') {
      end++;
    }
    turns.push({ assistantIndex: start, toolEnd: end });
    i = end;
  }
  return { preambleEnd, turns };
}

function messageToSummaryText(message: Message, toolOutputMaxChars: number): string {
  const role = message.role;
  if (role === 'tool') {
    const raw = typeof message.content === 'string' ? message.content : JSON.stringify(message.content ?? '');
    const trimmed = raw.length > toolOutputMaxChars
      ? `${raw.slice(0, toolOutputMaxChars)}\n[... truncated ${raw.length - toolOutputMaxChars} chars ...]`
      : raw;
    return `[tool result] ${trimmed}`;
  }
  if (role === 'assistant') {
    const text = typeof message.content === 'string' ? message.content : '';
    const calls = (message.tool_calls ?? [])
      .map((c) => `${c.function.name}(${c.function.arguments.slice(0, 200)})`)
      .join(', ');
    if (text && calls) return `[assistant] ${text}\n  -> calls: ${calls}`;
    if (calls) return `[assistant] -> calls: ${calls}`;
    return `[assistant] ${text}`;
  }
  if (role === 'user') {
    const content = typeof message.content === 'string' ? message.content : JSON.stringify(message.content);
    return `[user] ${content}`;
  }
  return `[${role}] ${typeof message.content === 'string' ? message.content : ''}`;
}

function findExistingSummary(messages: Message[]): string | null {
  for (const message of messages) {
    if (message.role !== 'user') continue;
    const content = typeof message.content === 'string' ? message.content : '';
    if (content.startsWith(SUMMARY_MARKER_PREFIX)) return content;
  }
  return null;
}

export function buildSummaryPrompt(
  middleMessages: Message[],
  previousSummary: string | null,
  toolOutputMaxChars: number,
): Message[] {
  const transcript = middleMessages.map((m) => messageToSummaryText(m, toolOutputMaxChars)).join('\n\n');
  const directive = previousSummary
    ? [
      'あなたはエージェント会話のアンカー要約を維持・更新する役割です。',
      '以下は前回のアンカー要約 (新しい transcript で矛盾しない部分は引き続き有効) と、新しい transcript の断片です。',
      '要約を更新してください: まだ有効な詳細は保持、陳腐化した詳細は削除、新しい事実・決定をマージしてください。',
      '出力は指定テンプレートに沿った要約のみ。前置きや説明は一切付けないこと。',
    ].join('\n')
    : [
      'エージェント会話のアンカー要約を作成してください。古いメッセージがコンテキストから drop された後でも、エージェントが作業を継続できるための要約です。',
      '出力は指定テンプレートに沿った要約のみ。前置きや説明は一切付けないこと。',
    ].join('\n');

  const template = [
    SUMMARY_MARKER_PREFIX,
    '',
    '## ゴール',
    '{元タスクの目的を 1-2 文で}',
    '',
    '## ここまでの進捗',
    '- Done: {完了した具体的アイテム}',
    '- In Progress: {現在進行中}',
    '- Blocked: {ブロック中、未解決の問題}',
    '',
    '## 重要な決定',
    '- {主要な判断とその理由}',
    '',
    '## 次にやるべきこと',
    '{次に直接続けるべき具体的アクション}',
    '',
    '## 重要なコンテキスト',
    '- {忘れてはならない事実・制約・パスなど}',
    '',
    '## 関連ファイル',
    '- {触れたファイル、1行サマリ付き}',
  ].join('\n');

  const sections = [
    directive,
    '',
    '## テンプレート',
    template,
    '',
  ];
  if (previousSummary) {
    sections.push('## 前回の要約');
    sections.push(previousSummary);
    sections.push('');
  }
  sections.push('## 取り込む新しい transcript 断片');
  sections.push(transcript);

  return [
    {
      role: 'system',
      content: 'あなたは自律エージェント向けの要約アシスタントです。 目的・決定・ファイルパス・未完の作業を保ったまま、簡潔で忠実な Markdown 要約を生成してください。',
    },
    { role: 'user', content: sections.join('\n') },
  ];
}

/**
 * Compress old turns into a single anchored Markdown summary, preserving the
 * preamble (system + original task) and the most recent `tailTurns` turns.
 *
 * Mutates `messages` in place when summarized=true.
 *
 * Returns summarized=false when:
 *   - There are not enough turns to summarize (<= tailTurns total)
 *   - runIsolatedLlm throws (caller decides whether to ABORT or continue)
 */
export async function summarizeHistory(
  messages: Message[],
  opts: SummarizeHistoryOptions,
): Promise<SummarizeHistoryResult> {
  const tailTurns = opts.tailTurns ?? 2;
  const toolOutputMaxChars = opts.toolOutputMaxChars ?? TOOL_OUTPUT_MAX_CHARS_DEFAULT;

  const { preambleEnd, turns } = splitIntoTurns(messages);
  if (turns.length <= tailTurns) {
    return { summarized: false, freedChars: 0, reason: 'not enough turns to summarize' };
  }
  const cutoffTurn = turns[turns.length - tailTurns]!;
  const middleStart = preambleEnd;
  const middleEnd = cutoffTurn.assistantIndex;
  if (middleEnd <= middleStart) {
    return { summarized: false, freedChars: 0, reason: 'no middle turns to summarize' };
  }
  const middleMessages = messages.slice(middleStart, middleEnd);
  const previousSummary = findExistingSummary(messages.slice(0, middleEnd));
  const prompt = buildSummaryPrompt(middleMessages, previousSummary, toolOutputMaxChars);

  let summary: string;
  try {
    summary = await opts.runIsolatedLlm(prompt);
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    logger.warn(`[history-compactor] summary LLM call failed: ${message}`);
    return { summarized: false, freedChars: 0, reason: `LLM error: ${message}` };
  }

  const trimmed = summary.trim();
  if (!trimmed) {
    return { summarized: false, freedChars: 0, reason: 'LLM returned empty summary' };
  }

  // Ensure the marker is present so future calls can find and update it.
  const markedSummary = trimmed.startsWith(SUMMARY_MARKER_PREFIX)
    ? trimmed
    : `${SUMMARY_MARKER_PREFIX}\n\n${trimmed}`;

  const removedChars = middleMessages.reduce((acc, m) => {
    const content = typeof m.content === 'string' ? m.content : '';
    return acc + content.length;
  }, 0);

  const summaryMessage: Message = { role: 'user', content: markedSummary };
  messages.splice(middleStart, middleEnd - middleStart, summaryMessage);

  const freedChars = Math.max(0, removedChars - markedSummary.length);
  logger.info(`[history-compactor] summarized turns=${turns.length - tailTurns} removedChars=${removedChars} summaryChars=${markedSummary.length} freedChars=${freedChars}`);
  return { summarized: true, freedChars, summary: markedSummary };
}

/**
 * Last-resort: produce a minimal "where we are / what's next" summary from the
 * full message history, intended for use when guardPromptBeforeSend cannot
 * recover by other means and the agent needs to force-transition.
 *
 * Returns null on LLM failure so callers can fall back to a generic message.
 */
export async function summarizeForceTransition(
  messages: Message[],
  runIsolatedLlm: (messages: Message[]) => Promise<string>,
  toolOutputMaxChars: number = TOOL_OUTPUT_MAX_CHARS_DEFAULT,
): Promise<string | null> {
  if (messages.length === 0) return null;
  const transcript = messages
    .map((m) => messageToSummaryText(m, toolOutputMaxChars))
    .join('\n\n');
  const prompt: Message[] = [
    {
      role: 'system',
      content: 'あなたは triage アシスタントです。 切り詰められたエージェント transcript を受け取り、次のステップへ引き継ぐための短い Markdown 要約を生成してください。',
    },
    {
      role: 'user',
      content: [
        'The agent has run out of context budget. Produce a short Markdown summary so the next movement can pick up the work.',
        '',
        '## Output template (output ONLY this, no preamble)',
        '### Status',
        '{1-2 sentences on what was attempted and how far it got}',
        '',
        '### Done so far',
        '- {bullet list of concrete completed actions}',
        '',
        '### Not done / next step',
        '- {bullet list of specific outstanding work}',
        '',
        '### Files touched',
        '- {paths with one-line role}',
        '',
        '## Transcript',
        transcript,
      ].join('\n'),
    },
  ];
  try {
    const result = await runIsolatedLlm(prompt);
    const trimmed = result.trim();
    return trimmed.length > 0 ? trimmed : null;
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    logger.warn(`[history-compactor] force-transition summary failed: ${message}`);
    return null;
  }
}

// Re-export so tests / callers don't have to import from token-estimate just
// for the legacy estimateTokens signature.
export { estimateTokensFromText as estimateTokens };
