import type { Effect } from "effect";
import type { HarnessPipeline, KernelStateLike } from "@reactive-agents/core";
import type { ControllerDecision } from "../types.js";
import type { EntropyScore } from "../types.js";

export type InterventionMode = "dispatch" | "advisory" | "off";

export type KernelStatePatch =
  | { kind: "early-stop"; reason: string }
  | { kind: "set-temperature"; temperature: number }
  | { kind: "request-strategy-switch"; to: string; reason: string }
  | { kind: "inject-tool-guidance"; text: string }
  | { kind: "compress-messages"; targetTokens: number }
  | { kind: "inject-skill-content"; skillId: string; content: string }
  | { kind: "append-system-nudge"; text: string };

export interface InterventionCost {
  readonly tokensEstimated: number;
  readonly latencyMsEstimated: number;
}

export interface InterventionOutcome {
  readonly applied: boolean;
  readonly patches: readonly KernelStatePatch[];
  readonly cost: InterventionCost;
  readonly reason: string;
  readonly telemetry: Record<string, unknown>;
}

export type InterventionError = {
  readonly _tag: "InterventionFailed";
  readonly message: string;
};

export interface InterventionContext {
  readonly iteration: number;
  readonly entropyScore: EntropyScore;
  readonly recentDecisions: readonly ControllerDecision[];
  readonly budget: {
    readonly tokensSpentOnInterventions: number;
    readonly interventionsFiredThisRun: number;
  };
  /**
   * Model-adaptive entropy floor derived from calibration data + tier fallback.
   * When present, overrides `config.suppression.minEntropyComposite` so the
   * dispatcher threshold scales to the model's actual entropy range rather than
   * the hardcoded Anthropic-calibrated default (0.55).
   *
   * Computed in reactive-observer.ts via calibratedMinEntropy():
   *   - calibrated (≥20 samples): highEntropyThreshold × 0.6
   *   - local tier fallback:      0.12  (Ollama — no logprobs, floor ~0.15)
   *   - frontier tier fallback:   0.45  (Anthropic/OpenAI — with logprobs)
   *   - unknown fallback:         0.25
   */
  readonly adaptiveMinEntropy?: number;
  /**
   * Compose harness pipeline, when registered on the agent. Used by the
   * dispatcher (HS-112) to bridge applied RI decisions into Compose tag
   * emissions so external observers can tap previously-dead tag namespaces
   * (`control.strategy-evaluated`, `lifecycle.failure`, `nudge.healing-failure`).
   * Absent when no `.withHarness()` block was registered, in which case the
   * bridge is a zero-cost no-op.
   */
  readonly harnessPipeline?: HarnessPipeline;
}

export interface InterventionHandler<
  TDecision extends ControllerDecision["decision"] = ControllerDecision["decision"]
> {
  readonly type: TDecision;
  readonly description: string;
  readonly defaultMode: InterventionMode;
  readonly execute: (
    decision: Extract<ControllerDecision, { decision: TDecision }>,
    state: Readonly<KernelStateLike>,
    context: InterventionContext
  ) => Effect.Effect<InterventionOutcome, InterventionError, never>;
}

/**
 * Erases the per-decision generic from a typed handler so it can be stored in
 * a heterogeneous registry typed as `InterventionHandler[]`. TypeScript's
 * function-parameter contravariance prevents `InterventionHandler<"early-stop">`
 * from assigning to `InterventionHandler<full-union>` directly; runtime dispatch
 * is safe because handlers are keyed and retrieved by their `type` string.
 *
 * Single named-cast site for the registry pattern — see
 * `handlers/index.ts` and `dispatcher-compose-bridge.test.ts`. Resolves HS-29.
 */
export function asInterventionHandler<
  T extends ControllerDecision["decision"]
>(handler: InterventionHandler<T>): InterventionHandler {
  return handler as unknown as InterventionHandler;
}

export interface InterventionSuppressionConfig {
  readonly minEntropyComposite: number;
  readonly minIteration: number;
  readonly maxFiresPerRun: number;
  readonly maxInterventionTokenBudget: number;
}

export interface InterventionConfig {
  readonly modes: Partial<Record<ControllerDecision["decision"], InterventionMode>>;
  readonly suppression: InterventionSuppressionConfig;
}

export const defaultInterventionConfig: InterventionConfig = {
  modes: {
    // Only decisions with registered handlers (in defaultInterventionRegistry)
    // appear here. Advisory-only modes that never dispatched were removed in
    // P0 cleanup (see controller-service.ts header comment).
    "early-stop": "dispatch",
    "temp-adjust": "dispatch",
    "switch-strategy": "dispatch",
    "skill-activate": "dispatch",
    "tool-inject": "dispatch",
    "tool-failure-redirect": "dispatch",
    "compress": "dispatch",
    "stall-detect": "dispatch",
    "harness-harm": "dispatch",
  },
  suppression: {
    minEntropyComposite: 0.55,
    minIteration: 2,
    maxFiresPerRun: 5,
    maxInterventionTokenBudget: 1500,
  },
};
