import type { ProviderTurnActionPolicy } from "@best-agent/protocol";
import type { ConversationMessage } from "../../provider/provider-conversation.js";
import {
  formatPromptDeliverableRepairObservation,
  type RuntimeObservationDeliverableFailure,
} from "./deliverable-observation.js";
import { buildRuntimeObservation } from "./runtime-observation-builder.js";
import { formatAgentVisibleRuntimeError } from "../../shared/utils/agent-error.js";

export type RuntimeObservationCode =
  | "CONTROL_PAYLOAD_LEAK"
  | "PROMPT_DELIVERABLE_MISSING"
  | "PROVIDER_NO_ACTION"
  | "STRUCTURED_ACTION_REQUIRED"
  | "TEXTUAL_TOOL_CALL"
  | "FINAL_ANSWER_UNSUPPORTED";

export type TerminalOutputInvalidKind =
  | "textual-tool-call"
  | "phase-or-plan-payload"
  | "provider-internal-payload"
  | "generic-control-payload";

export type TerminalOutputInvalidReasonCode =
  | "runtime-decision-json"
  | "provider-native-tool-call-json"
  | "provider-internal-payload"
  | "structured-control-payload"
  | "textual-tool-call"
  | "tool-call-marker";

export type RuntimeObservation = {
  code: RuntimeObservationCode;
  message: string;
  severity: "warning" | "error";
};

export function appendRuntimeObservation(
  messages: ConversationMessage[],
  observation: RuntimeObservation,
): ConversationMessage[] {
  return [
    ...messages,
    {
      content: formatRuntimeObservation(observation),
      code: observation.code,
      role: "runtime",
      severity: observation.severity,
    },
  ];
}

export function formatRuntimeObservation(observation: RuntimeObservation): string {
  return [
    `Runtime observation [${observation.severity.toUpperCase()} ${observation.code}]:`,
    observation.message,
  ].join("\n");
}

export function textualToolCallObservation(input: {
  actionPolicy: ProviderTurnActionPolicy;
  description?: string;
  toolKnown?: boolean;
  toolName?: string;
}): RuntimeObservation {
  const subject = input.toolName
    ? input.toolKnown === false
      ? `${input.toolName} tool call, but that tool is not available`
      : `${input.toolName} tool call`
    : (input.description ?? "a textual tool-call/control payload");
  return buildRuntimeObservation({
    code: "TEXTUAL_TOOL_CALL",
    severity: "error",
    lines: formatAgentVisibleRuntimeError({
      code: "TEXTUAL_TOOL_CALL",
      message: input.toolName
        ? `Provider described a ${subject} in text instead of emitting a native decision`
        : `Provider exposed ${subject}`,
    }),
  });
}

export function structuredActionRequiredObservation(input: {
  actionPolicy: ProviderTurnActionPolicy;
  responseText: string;
}): RuntimeObservation {
  const excerpt = formatResponseExcerpt(input.responseText);
  return buildRuntimeObservation({
    code: "STRUCTURED_ACTION_REQUIRED",
    severity: "error",
    lines: formatAgentVisibleRuntimeError({
      code: "STRUCTURED_ACTION_REQUIRED",
      message: "Direct prose is not valid for this structured runtime turn",
      details: [
        `Allowed runtime actions: ${input.actionPolicy.allowedActions.join(", ")}`,
        ...(excerpt ? [`Provider response excerpt: ${excerpt}`] : []),
      ],
    }),
  });
}

export function terminalOutputInvalidObservation(input: {
  actionPolicy: ProviderTurnActionPolicy;
  deliverableFailures?: RuntimeObservationDeliverableFailure[];
  invalidKind: TerminalOutputInvalidKind;
  leakedToolKnown?: boolean;
  leakedToolName?: string;
  reasonCode?: TerminalOutputInvalidReasonCode;
  toolNames?: readonly string[];
}): RuntimeObservation {
  const runtimeJsonContentHint =
    input.reasonCode === "runtime-decision-json"
      ? "If the user explicitly requested this JSON as content, return it in a fenced code block or write it to the requested file."
      : undefined;
  const deliverableLines =
    input.deliverableFailures && input.deliverableFailures.length > 0
      ? ["", formatPromptDeliverableRepairObservation(input.deliverableFailures)]
      : [];
  if (input.reasonCode === "provider-native-tool-call-json") {
    return buildRuntimeObservation({
      code: "CONTROL_PAYLOAD_LEAK",
      severity: "error",
      lines: [
        "Your previous visible text looked like provider-native tool-call JSON.",
        "Reason code: provider-native-tool-call-json.",
        `Allowed runtime actions: ${input.actionPolicy.allowedActions.join(", ")}.`,
        ...deliverableLines,
      ],
    });
  }
  if (input.invalidKind === "textual-tool-call") {
    const observation = textualToolCallObservation({
      actionPolicy: input.actionPolicy,
      description:
        input.reasonCode === "runtime-decision-json"
          ? "a runtime control JSON payload"
          : "a textual tool-call/control payload",
      toolKnown: input.leakedToolKnown,
      toolName: input.leakedToolName,
    });
    return {
      ...observation,
      message: buildRuntimeObservation({
        code: observation.code,
        severity: observation.severity,
        lines: [
          observation.message,
          input.reasonCode ? `Reason code: ${input.reasonCode}.` : undefined,
          runtimeJsonContentHint,
          ...deliverableLines,
        ],
      }).message,
    };
  }

  return buildRuntimeObservation({
    code: "CONTROL_PAYLOAD_LEAK",
    severity: "error",
    lines: [
      "Your previous visible text looked like internal control payload.",
      input.reasonCode ? `Reason code: ${input.reasonCode}.` : undefined,
      runtimeJsonContentHint,
      `Allowed runtime actions: ${input.actionPolicy.allowedActions.join(", ")}.`,
      ...deliverableLines,
    ],
  });
}

function formatResponseExcerpt(text: string): string | undefined {
  const compact = text.replace(/\s+/gu, " ").trim();
  if (!compact) {
    return undefined;
  }
  return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
}
