import { getAgentOutputText, hasAgentOutputPayload } from './agent-output.js';
import {
  classifyClaudeAuthError,
  classifyRotationTrigger,
  detectClaudeProviderFailureMessage,
  isClaudeAuthError,
  isClaudeAuthExpiredMessage,
  isClaudeOrgAccessDeniedMessage,
  isClaudeUsageExhaustedMessage,
  type AgentTriggerReason,
} from './agent-error-detection.js';
import type { AgentOutput } from './agent-runner.js';
import { detectCodexRotationTrigger } from './codex-token-rotation.js';
import {
  shouldRetryFreshCodexSessionOnAgentFailure,
  shouldRetryFreshSessionOnAgentFailure,
} from './session-recovery.js';

export interface StreamedTriggerReason {
  reason: AgentTriggerReason;
  retryAfterMs?: number;
  message?: string;
}

export interface StreamedOutputState {
  sawOutput: boolean;
  sawVisibleOutput: boolean;
  sawSuccessNullResultWithoutOutput: boolean;
  streamedTriggerReason?: StreamedTriggerReason;
  retryableSessionFailureDetected?: boolean;
}

export interface EvaluateStreamedOutputOptions {
  agentType: 'claude-code' | 'codex';
  provider: string;
  suppressClaudeAuthErrorOutput?: boolean;
  trackSuccessNullResult?: boolean;
  shortCircuitTriggeredErrors?: boolean;
}

export interface EvaluateStreamedOutputResult {
  state: StreamedOutputState;
  shouldForwardOutput: boolean;
  newTrigger?: StreamedTriggerReason;
  suppressedAuthError?: boolean;
  suppressedRetryableSessionFailure?: boolean;
}

export function evaluateStreamedOutput(
  output: AgentOutput,
  state: StreamedOutputState,
  options: EvaluateStreamedOutputOptions,
): EvaluateStreamedOutputResult {
  const nextState: StreamedOutputState = { ...state };
  const primaryAgent = primaryAgentFor(options);
  const countsAsFinalOutput =
    output.phase === undefined || output.phase === 'final';
  const outputText = getAgentOutputText(output);

  const retryableSessionFailure = suppressRetryableSessionFailure(
    output,
    state,
    nextState,
    primaryAgent,
  );
  if (retryableSessionFailure) return retryableSessionFailure;

  if (
    primaryAgent === 'claude' &&
    output.status === 'success' &&
    !state.sawOutput &&
    typeof outputText === 'string'
  ) {
    const triggerReason = classifyClaudeSuccessTrigger(outputText);
    if (triggerReason) {
      const newTrigger = nextState.streamedTriggerReason
        ? undefined
        : { reason: triggerReason };
      nextState.streamedTriggerReason =
        nextState.streamedTriggerReason ?? newTrigger;
      return {
        state: nextState,
        shouldForwardOutput: false,
        newTrigger,
      };
    }

    if (
      options.suppressClaudeAuthErrorOutput &&
      isClaudeAuthError(outputText)
    ) {
      return {
        state: nextState,
        shouldForwardOutput: false,
        suppressedAuthError: true,
      };
    }
  }

  if (countsAsFinalOutput && hasAgentOutputPayload(output)) {
    nextState.sawOutput = true;
  } else if (
    options.trackSuccessNullResult &&
    primaryAgent === 'claude' &&
    output.status === 'success' &&
    !state.sawOutput
  ) {
    nextState.sawSuccessNullResultWithoutOutput = true;
  }

  if (
    primaryAgent === 'codex' &&
    typeof output.error === 'string' &&
    output.error.length > 0 &&
    !nextState.sawOutput &&
    !nextState.streamedTriggerReason
  ) {
    const trigger = detectCodexRotationTrigger(output.error);
    if (trigger.shouldRotate) {
      const newTrigger: StreamedTriggerReason = {
        reason: trigger.reason,
        message: output.error,
      };
      nextState.streamedTriggerReason = newTrigger;
      return {
        state: nextState,
        shouldForwardOutput: !options.shortCircuitTriggeredErrors,
        newTrigger,
      };
    }
  }

  if (
    output.status === 'error' &&
    !nextState.sawOutput &&
    !nextState.streamedTriggerReason
  ) {
    let newTrigger: StreamedTriggerReason | undefined;

    if (primaryAgent === 'claude') {
      const trigger = classifyRotationTrigger(output.error);
      if (trigger.shouldRetry) {
        newTrigger = {
          reason: trigger.reason,
          retryAfterMs: trigger.retryAfterMs,
        };
      }
    } else if (primaryAgent === 'codex') {
      const trigger = detectCodexRotationTrigger(output.error);
      if (trigger.shouldRotate) {
        newTrigger = { reason: trigger.reason, message: output.error };
      }
    }

    if (newTrigger) {
      nextState.streamedTriggerReason = newTrigger;
      return {
        state: nextState,
        shouldForwardOutput: !options.shortCircuitTriggeredErrors,
        newTrigger,
      };
    }
  }

  return {
    state: nextState,
    shouldForwardOutput: true,
  };
}

type PrimaryAgent = 'claude' | 'codex' | undefined;

function primaryAgentFor(options: EvaluateStreamedOutputOptions): PrimaryAgent {
  if (options.agentType === 'claude-code' && options.provider === 'claude') {
    return 'claude';
  }
  if (options.agentType === 'codex' && options.provider === 'codex') {
    return 'codex';
  }
  return undefined;
}

function suppressRetryableSessionFailure(
  output: AgentOutput,
  state: StreamedOutputState,
  nextState: StreamedOutputState,
  primaryAgent: PrimaryAgent,
): EvaluateStreamedOutputResult | undefined {
  if (state.sawOutput) return undefined;

  const shouldSuppress =
    primaryAgent === 'claude'
      ? shouldRetryFreshSessionOnAgentFailure(output)
      : primaryAgent === 'codex'
        ? shouldRetryFreshCodexSessionOnAgentFailure(output)
        : false;
  if (!shouldSuppress) return undefined;

  nextState.retryableSessionFailureDetected = true;
  return {
    state: nextState,
    shouldForwardOutput: false,
    suppressedRetryableSessionFailure: true,
  };
}

function classifyClaudeSuccessTrigger(
  outputText: string,
): AgentTriggerReason | undefined {
  if (isClaudeUsageExhaustedMessage(outputText)) return 'usage-exhausted';

  const authClassification = classifyClaudeAuthError(outputText);
  if (
    isClaudeOrgAccessDeniedMessage(outputText) ||
    authClassification.category === 'org-access-denied'
  ) {
    return 'org-access-denied';
  }
  if (
    isClaudeAuthExpiredMessage(outputText) ||
    authClassification.category === 'auth-expired'
  ) {
    return 'auth-expired';
  }
  return detectClaudeProviderFailureMessage(outputText) || undefined;
}
