import type { AgentRuntimeName } from './agent-runtime';

// Pure, side-effect-free decision for how a completed runtime turn should be
// surfaced. Kept out of worker.ts so it is unit-testable without loading the
// worker's Convex/Docker dependencies.
//
// Inputs:
// - assistantText: text already streamed to the user (may be partial).
// - recoveredText: text recovered from `TurnResult.finalMessage` (already
//   redacted+truncated by the caller), used only when nothing streamed.
// - error: `TurnResult.error` — non-empty when the runtime reported a hard
//   failure (nonzero exit / failure event).
// - runtime: runtime name, for the failure message prefix.
//
// Output:
// - text: the assistant text to persist (streamed text, else recovered text).
// - failure: when set, the caller must surface the turn as FAILED (throw). The
//   partial text in `text` is still preserved so the user can see it.
export const resolveTurnOutcome = (args: {
  assistantText: string;
  recoveredText?: string;
  error?: string;
  runtime: AgentRuntimeName;
}): { text: string; failure?: string } => {
  const { assistantText, recoveredText, error, runtime } = args;
  let text = assistantText;
  if (!text.trim() && recoveredText) {
    text = recoveredText;
  }
  if (!text.trim()) {
    return {
      text,
      failure: error
        ? `${runtime} failed:\n${error}`
        : 'Codex completed without producing an assistant response.',
    };
  }
  // A hard failure that streamed partial text must still be surfaced as failed;
  // the streamed text is preserved above so the user sees the truncated answer.
  if (error) {
    return { text, failure: `${runtime} failed:\n${error}` };
  }
  return { text };
};

// Pure decision for whether the `content` on an `assistant_completed` event
// should be folded into the accumulated assistant text.
//
// Some runtimes (Claude `-p --output-format stream-json`, OpenCode) emit the
// final answer TWICE: once as streamed `assistant_delta` chunks and again as
// the `content` on the terminal `assistant_completed`/`result` event. Appending
// both yields a doubled "<answer><answer>". So only fold in the completed
// content when nothing has streamed yet (a runtime that emits only a final
// result). When deltas already carried the answer, skip it. The truly-empty
// case is still covered by resolveTurnOutcome's recoveredText/finalMessage
// fallback, so nothing is lost.
export const shouldAppendCompletedContent = (
  currentText: string,
  completedContent?: string,
): boolean => Boolean(completedContent) && !currentText.trim();
