/**
 * RecoveryPlanner — WP-11.
 *
 * Chooses hot / warm / cold / needs_human recovery based on Session & Host health.
 * Phase-1: no automatic cross-Host migration.
 */

import type { InheritanceTier } from "./ContextProjectionBuilder.js";
import type { WorkFactBundle } from "./WorkFactReader.js";
import {
  ContextProjectionBuilder,
  type ProjectionBuildResult,
} from "./ContextProjectionBuilder.js";

export type SessionHealth = "healthy" | "recoverable" | "lost";
export type HostHealth = "available" | "unavailable";

export type RecoveryAction =
  | "resume_hot"
  | "resume_warm"
  | "cold_same_host"
  | "cold_needs_host_choice"
  | "failed";

export interface RecoveryPlanInput {
  facts: WorkFactBundle;
  workerId: string;
  runId: string;
  sessionHealth: SessionHealth;
  hostHealth: HostHealth;
  /** Alternate Host available for future migration (phase-1: never auto-used). */
  alternateHostAvailable?: boolean;
  persistDir?: string;
  notes?: string[];
}

export interface RecoveryPlan {
  tier: InheritanceTier | "none";
  action: RecoveryAction;
  reason: string;
  canHandoff: boolean;
  projection?: ProjectionBuildResult;
  failure?: string;
}

export class RecoveryPlanner {
  readonly #builder: ContextProjectionBuilder;

  constructor(builder: ContextProjectionBuilder = new ContextProjectionBuilder()) {
    this.#builder = builder;
  }

  /**
   * Decide recovery tier, then build the matching projection pack.
   * Cold with insufficient materials → failed (explicit, not silent invent).
   */
  async plan(input: RecoveryPlanInput): Promise<RecoveryPlan> {
    const decision = this.decide(input);

    if (decision.action === "failed" || decision.tier === "none") {
      return decision;
    }

    const projection = await this.#builder.build({
      facts: input.facts,
      workerId: input.workerId,
      runId: input.runId,
      tier: decision.tier as InheritanceTier,
      persistDir: input.persistDir,
      notes: input.notes,
    });

    if (decision.tier === "cold" && !projection.sufficient) {
      return {
        tier: "cold",
        action: "failed",
        reason: decision.reason,
        canHandoff: false,
        projection,
        failure: `cold handoff materials insufficient: ${projection.sufficiencyGaps.join("; ") || "UNKNOWN gaps"}`,
      };
    }

    return {
      ...decision,
      canHandoff: projection.sufficient || decision.tier !== "cold",
      projection,
    };
  }

  /** Pure decision without writing files (for unit checks). */
  decide(input: RecoveryPlanInput): RecoveryPlan {
    const { sessionHealth, hostHealth, alternateHostAvailable } = input;

    if (sessionHealth === "healthy" && hostHealth === "available") {
      return {
        tier: "hot",
        action: "resume_hot",
        reason: "Session healthy on available Host — delta facts only",
        canHandoff: true,
      };
    }

    if (sessionHealth === "recoverable" && hostHealth === "available") {
      return {
        tier: "warm",
        action: "resume_warm",
        reason: "Session recoverable — resume with state summary",
        canHandoff: true,
      };
    }

    if (sessionHealth === "lost" && hostHealth === "available") {
      return {
        tier: "cold",
        action: "cold_same_host",
        reason: "Session lost — cold pack on same Host, new Session",
        canHandoff: true,
      };
    }

    // Host unavailable
    if (hostHealth === "unavailable") {
      if (alternateHostAvailable) {
        // Phase-1: do not auto-migrate; wait for human Host choice.
        return {
          tier: "cold",
          action: "cold_needs_host_choice",
          reason:
            "Host unavailable; alternate Host exists but phase-1 does not auto-migrate — needs_human",
          canHandoff: false,
          failure: "needs_human: choose replacement Host before cold handoff",
        };
      }
      return {
        tier: "none",
        action: "failed",
        reason: "Host unavailable and no alternate Host",
        canHandoff: false,
        failure: "needs_human / failed: no replacement Host",
      };
    }

    return {
      tier: "none",
      action: "failed",
      reason: "UNKNOWN recovery combination",
      canHandoff: false,
      failure: "UNKNOWN",
    };
  }
}
