/**
 * RunCompatibilityFacade — WP-09.
 *
 * Projects Host Session + Runs onto the legacy CodeFlowMu SessionRecord
 * shape used by UI / Transcript / old APIs.
 *
 * Explicit identity map:
 *   - workerId          → SessionRecord.agent_id / protocol.agent_id
 *   - Host Session      → host_session_ref (NOT protocol.session_id)
 *   - CodeFlowMu session → protocol.session_id (compatibility id)
 *   - Run               → protocol.runs[] + runtime_active_run_id
 */

import type { Session } from "@codeflowmu/protocol";

import type { HostSessionRef } from "../contracts/HostSession.ts";
import type { RunRecord, RunRecordState } from "../contracts/RunRecord.ts";
import type { SessionRecord } from "../../types/state.ts";
import { SessionStore } from "../../session/SessionStore.ts";
import {
  hostSessionKey,
  type RunCoordinator,
} from "./RunCoordinator.ts";

export const SESSION_PROJECTION_SCHEMA =
  "codeflowmu.session-projection/v1" as const;

export interface LegacySessionProjection {
  schema: typeof SESSION_PROJECTION_SCHEMA;
  /** CodeFlowMu execution-record id (≠ Host externalSessionId). */
  session_id: string;
  /** Worker / agent id. */
  agent_id: string;
  /** External Host Session reference. */
  host_session_ref: HostSessionRef;
  host_session_key: string;
  run_ids: string[];
  runtime_active_run_id?: string;
  task_id?: string;
  updated_at: string;
}

export interface RunCompatibilityFacadeOptions {
  coordinator: RunCoordinator;
  /** When set, enables SessionStore round-trip compatibility. */
  sessionStore?: SessionStore;
  now?: () => Date;
}

function mapRunState(
  state: RunRecordState,
): Session["runs"][number]["status"] {
  switch (state) {
    case "created":
    case "starting":
    case "running":
      return "running";
    case "completed":
      return "finished";
    case "cancelled":
      return "cancelled";
    case "failed":
    case "delivery_unknown":
      return "failed";
    default:
      return "failed";
  }
}

function codeflowSessionId(host: HostSessionRef, workerId: string): string {
  const safe = hostSessionKey(host)
    .replace(/[^a-zA-Z0-9-]+/g, "-")
    .toLowerCase();
  const agent = workerId.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
  return `session-${agent}-${safe}`.slice(0, 120);
}

export class RunCompatibilityFacade {
  readonly #coordinator: RunCoordinator;
  readonly #sessionStore: SessionStore | undefined;
  readonly #now: () => Date;

  constructor(opts: RunCompatibilityFacadeOptions) {
    this.#coordinator = opts.coordinator;
    this.#sessionStore = opts.sessionStore;
    this.#now = opts.now ?? (() => new Date());
  }

  get coordinator(): RunCoordinator {
    return this.#coordinator;
  }

  /**
   * Build a legacy projection for one Host Session + its Runs.
   */
  async project(
    workerId: string,
    host: HostSessionRef,
  ): Promise<LegacySessionProjection> {
    const runs = await this.#coordinator.listRunsForHostSession(host);
    const owned = runs.filter((r) => r.workerId === workerId);
    const active = owned.find(
      (r) =>
        r.state === "created" ||
        r.state === "starting" ||
        r.state === "running",
    );
    return {
      schema: SESSION_PROJECTION_SCHEMA,
      session_id: codeflowSessionId(host, workerId),
      agent_id: workerId,
      host_session_ref: { ...host },
      host_session_key: hostSessionKey(host),
      run_ids: owned.map((r) => r.runId),
      runtime_active_run_id: active?.runId,
      task_id: owned[0]?.taskId ?? active?.taskId,
      updated_at: this.#now().toISOString(),
    };
  }

  /**
   * Materialize a SessionRecord compatible with SessionStore read/write.
   * protocol.session_id is the CodeFlowMu id — not the Host external id.
   */
  async toSessionRecord(
    workerId: string,
    host: HostSessionRef,
  ): Promise<SessionRecord> {
    const projection = await this.project(workerId, host);
    const runs = await this.#coordinator.listRunsForHostSession(host);
    const owned = runs.filter((r) => r.workerId === workerId);
    const protocolRuns: Session["runs"] = owned.map((r) =>
      runToSessionRun(r),
    );
    const status: Session["status"] = projection.runtime_active_run_id
      ? "running"
      : owned.some((r) => r.state === "failed" || r.state === "delivery_unknown")
        ? "failed"
        : owned.some((r) => r.state === "cancelled")
          ? "cancelled"
          : "completed";

    return {
      protocol: {
        session_id: projection.session_id,
        agent_id: workerId,
        task_id: projection.task_id ?? "TASK-UNKNOWN",
        started_at:
          owned[0]?.startedAt ?? projection.updated_at,
        status,
        runs: protocolRuns,
      },
      session_id: projection.session_id,
      agent_id: workerId,
      runtime_active_run_id: projection.runtime_active_run_id,
      runtime_last_event_at: projection.updated_at,
    };
  }

  /** Persist SessionRecord via SessionStore (compatibility write). */
  async writeCompatibleSession(
    workerId: string,
    host: HostSessionRef,
  ): Promise<SessionRecord> {
    if (!this.#sessionStore) {
      throw new Error(
        "RunCompatibilityFacade: sessionStore required for writeCompatibleSession",
      );
    }
    const record = await this.toSessionRecord(workerId, host);
    await this.#sessionStore.save(record);
    return record;
  }

  /** Load SessionRecord and verify Host Session ≠ CodeFlowMu session_id. */
  async readCompatibleSession(
    sessionId: string,
  ): Promise<SessionRecord | null> {
    if (!this.#sessionStore) {
      throw new Error(
        "RunCompatibilityFacade: sessionStore required for readCompatibleSession",
      );
    }
    return this.#sessionStore.load(sessionId);
  }

  /**
   * Identity checklist helper for REPORT / QA:
   * answers "is this Worker / Host Session / SessionRecord / Run?"
   */
  classifyId(input: {
    value: string;
    workerId?: string;
    host?: HostSessionRef;
    runIds?: string[];
    sessionId?: string;
  }): "worker" | "host_session" | "session_record" | "run" | "unknown" {
    const v = String(input.value ?? "").trim();
    if (!v) return "unknown";
    if (input.workerId && v === input.workerId) return "worker";
    if (input.host && v === input.host.externalSessionId) return "host_session";
    if (input.host && v === hostSessionKey(input.host)) return "host_session";
    if (input.sessionId && v === input.sessionId) return "session_record";
    if (input.runIds?.includes(v)) return "run";
    if (v.startsWith("run-")) return "run";
    if (v.startsWith("session-")) return "session_record";
    if (v.startsWith("shadow:") || v.includes("::")) return "host_session";
    return "unknown";
  }
}

function runToSessionRun(
  run: RunRecord,
): Session["runs"][number] {
  return {
    run_id: run.runId,
    started_at: run.startedAt ?? new Date().toISOString(),
    ...(run.settledAt ? { ended_at: run.settledAt } : {}),
    status: mapRunState(run.state),
    tool_calls_count: 0,
  };
}
