/**
 * Stage 4 — Opus deep reasoning (conditional).
 *
 * Triggered when the Sonnet output is uncertain, ambiguous, or describes a
 * complex situation. Opus addresses each ambiguity directly and proposes
 * targeted updates to the Sonnet analysis.
 *
 * Trigger conditions (any one):
 *   - triage.complexity_tier === "high"
 *   - sonnet.ambiguities_for_opus.length > 0
 *   - sonnet.confidence_score < 0.5
 *   - triage.status_matches_reality === false
 *
 * Spec: docs/wulf-pulse-ticket-analyzer-prompt.md → "Stage 4 — Deep Reasoning (Opus, conditional)"
 */

import {
  OpusResponse,
  type DeepAnalysisResponse,
  type PreprocessedTicket,
  type TriageResponse,
} from '@/lib/types/analyzer';
import { callLLMStage, type LLMCallResult } from '@/lib/services/llm/call';
import { OPUS, type ModelId } from '@/lib/services/llm/models';
import type Anthropic from '@anthropic-ai/sdk';

const STAGE4_MAX_TOKENS = 16_000;
const PAYLOAD_CHAR_CAP = 100_000;

const SYSTEM_PROMPT = `You are a principal-level MSP engineer doing a final review of a complex ticket. You will be given:
1. The full tagged ticket
2. The Sonnet-tier analysis
3. A list of specific ambiguities or open questions

Address each ambiguity directly with reasoning. Then produce updates ONLY for fields that should change.

Do not restate fields you are not changing. If the Sonnet analysis is correct as-is, return an empty updates object.

Respond ONLY with JSON. No prose, no code fences. Schema:
{
  "opus_notes": string,
  "updates": {
    // any subset of these — include only what should change
    "next_step"?: string,
    "next_step_rationale"?: string,
    "gaps"?: [{ "description": string, "severity": "low" | "medium" | "high", "evidence_timestamps": string[] }],
    "confidence_score"?: number,
    "needs_human_review"?: boolean,
    "human_review_reasons"?: string[],
    "post_resolution_analysis"?: string | null
  }
}`;

export interface DeepReasoningInput {
  pre: PreprocessedTicket;
  triage: TriageResponse;
  sonnet: DeepAnalysisResponse;
}

/**
 * Should Stage 4 run? Returns true if any of the spec's trigger conditions fire.
 */
export function shouldRunDeepReasoning(input: {
  triage: TriageResponse;
  sonnet: DeepAnalysisResponse;
}): boolean {
  if (input.triage.complexity_tier === 'high') return true;
  if (input.sonnet.ambiguities_for_opus.length > 0) return true;
  if (input.sonnet.confidence_score < 0.5) return true;
  if (input.triage.status_matches_reality === false) return true;
  return false;
}

export function buildDeepReasoningUserPayload(input: DeepReasoningInput): {
  payload: string;
  events_dropped: number;
} {
  const headerJson = JSON.stringify(input.pre.header, null, 2);
  const triageJson = JSON.stringify(input.triage, null, 2);
  const sonnetJson = JSON.stringify(input.sonnet, null, 2);
  const ambiguitiesList = input.sonnet.ambiguities_for_opus.length
    ? input.sonnet.ambiguities_for_opus.map((a, i) => `${i + 1}. ${a}`).join('\n')
    : '(no specific ambiguities; review the Sonnet analysis for any issues you would change)';

  const events = input.pre.events.slice();
  let droppedNotice = '';
  let eventsJson = JSON.stringify(events, null, 2);
  let droppedCount = 0;
  while (
    headerJson.length + triageJson.length + sonnetJson.length + eventsJson.length >
    PAYLOAD_CHAR_CAP
  ) {
    const oldestInternalIdx = events.findIndex(
      (e) => e.visibility === 'internal_only'
    );
    if (oldestInternalIdx === -1) break;
    events.splice(oldestInternalIdx, 1);
    droppedCount += 1;
    eventsJson = JSON.stringify(events, null, 2);
  }
  if (droppedCount > 0) {
    droppedNotice = `\n\nNOTE: ${droppedCount} oldest internal-only event(s) were dropped to fit the size cap.`;
  }

  const payload = [
    `=== TICKET HEADER ===`,
    headerJson,
    ``,
    `=== TRIAGE METADATA (Stage 1) ===`,
    triageJson,
    ``,
    `=== TAGGED EVENTS (chronological) ===`,
    eventsJson,
    ``,
    `=== SONNET-TIER ANALYSIS (Stage 3) ===`,
    sonnetJson,
    ``,
    `=== OPEN AMBIGUITIES TO RESOLVE ===`,
    ambiguitiesList,
    droppedNotice,
  ].join('\n');

  return { payload, events_dropped: droppedCount };
}

export interface DeepReasoningStageResult extends LLMCallResult<OpusResponse> {
  events_dropped: number;
}

export async function runDeepReasoningStage(
  input: DeepReasoningInput,
  injectedClient?: Anthropic,
  modelOverride?: ModelId
): Promise<DeepReasoningStageResult> {
  const { payload, events_dropped } = buildDeepReasoningUserPayload(input);
  const result = await callLLMStage({
    model: modelOverride ?? OPUS,
    system: SYSTEM_PROMPT,
    user: payload,
    schema: OpusResponse,
    maxTokens: STAGE4_MAX_TOKENS,
    client: injectedClient,
  });
  return { ...result, events_dropped };
}

/**
 * Apply Opus's `updates` object to a Sonnet analysis. Only fields explicitly
 * present in `updates` overwrite the corresponding Sonnet field. Returns a
 * shallow copy — does not mutate the input.
 */
export function applyOpusUpdates(
  sonnet: DeepAnalysisResponse,
  updates: OpusResponse['updates']
): DeepAnalysisResponse {
  return {
    ...sonnet,
    ...(updates.next_step !== undefined && { next_step: updates.next_step }),
    ...(updates.next_step_rationale !== undefined && {
      next_step_rationale: updates.next_step_rationale,
    }),
    ...(updates.gaps !== undefined && { gaps: updates.gaps }),
    ...(updates.confidence_score !== undefined && {
      confidence_score: updates.confidence_score,
    }),
    ...(updates.needs_human_review !== undefined && {
      needs_human_review: updates.needs_human_review,
    }),
    ...(updates.human_review_reasons !== undefined && {
      human_review_reasons: updates.human_review_reasons,
    }),
    ...(updates.post_resolution_analysis !== undefined && {
      post_resolution_analysis: updates.post_resolution_analysis,
    }),
  };
}

export const _STAGE4_INTERNALS = {
  SYSTEM_PROMPT,
  PAYLOAD_CHAR_CAP,
  STAGE4_MAX_TOKENS,
};
