import type { LocalTaskComment } from '../db/repository.js';

/** Comment kinds that represent a direct user instruction (vs. agent progress/result/ask rows). */
export const USER_INSTRUCTION_KINDS = ['comment', 'request', 'interjection'] as const;

export interface LocalContextInput {
  /** All comments for the task, oldest-first (as returned by listLocalTaskComments). */
  comments: LocalTaskComment[];
  /** The job's original instruction (task body). */
  jobInstruction: string;
  /** Filenames under input/ (display only). */
  inputFiles: string[];
  /** Filenames under output/ (display only). */
  outputFiles: string[];
}

function isUserInstruction(c: LocalTaskComment): boolean {
  return c.author === 'user' && (USER_INSTRUCTION_KINDS as readonly string[]).includes(c.kind);
}

/**
 * Build the conversation-context block injected into a (re)started local-task job.
 *
 * Two behaviors matter when a task is cancelled mid-loop and re-run:
 *  - Interjections (kind='interjection', sent while the task was running) count as
 *    user instructions, so the LATEST one becomes the "current instruction" — not just
 *    the original task body. Without this, a resumed agent loses what the user actually
 *    asked for during the loop.
 *  - The "recent conversation" window is the last 10 comments, but a busy loop floods the
 *    task with agent `progress` rows that would otherwise push the user's message out of
 *    that window. We therefore guarantee the last few USER messages are always shown.
 *
 * Returns the context body (without the time-context prefix, which the caller prepends).
 */
export function buildLocalConversationContext(input: LocalContextInput): string {
  const { comments, jobInstruction, inputFiles, outputFiles } = input;
  const contextParts: string[] = [];

  // The active instruction is the LATEST user message (comment/request/interjection),
  // skipping agent progress/result/ask rows. The original task body stays as reference.
  const latestUserComment = [...comments].reverse().find(isUserInstruction);
  const currentInstructionBody =
    latestUserComment && latestUserComment.body.trim() !== jobInstruction.trim()
      ? latestUserComment.body
      : jobInstruction;
  const hasFollowUp = currentInstructionBody !== jobInstruction;

  // Recent conversation: last 10 overall, but never drop the last 5 user messages —
  // otherwise a flood of agent progress rows hides what the user said.
  const recentOverall = comments.slice(-10);
  const recentUserMsgs = comments.filter(isUserInstruction).slice(-5);
  const displayMap = new Map<number, LocalTaskComment>();
  for (const c of [...recentOverall, ...recentUserMsgs]) displayMap.set(c.id, c);
  const display = [...displayMap.values()].sort((a, b) => a.id - b.id);

  if (display.length > 0) {
    contextParts.push('## これまでのやり取り');
    for (const comment of display) {
      const truncated = comment.body.length > 500 ? comment.body.slice(0, 500) + '...' : comment.body;
      contextParts.push(`[${comment.author}/${comment.kind}] ${truncated}`);
    }
    contextParts.push('');
  }

  contextParts.push('## ワークスペース状況');
  if (inputFiles.length > 0) contextParts.push(`input/: ${inputFiles.join(', ')}`);
  if (outputFiles.length > 0) contextParts.push(`output/: ${outputFiles.join(', ')}`);
  contextParts.push('');

  if (hasFollowUp) {
    contextParts.push('## オリジナルタスク (参考、対応済みの可能性あり)');
    contextParts.push(jobInstruction);
    contextParts.push('');
    contextParts.push('## 現在のユーザー指示 (これに対応する)');
    contextParts.push(currentInstructionBody);
  } else {
    contextParts.push('## タスク');
    contextParts.push(jobInstruction);
  }

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