/**
 * reconcileAgentTaskState — unified Agent / session / TASK reconcile (7 states).
 *
 * Triggered checks (Panel / Runtime) call this before swap-AI, wake, dispatch,
 * recover, startup, task detail, queue refresh, session ended, PM summary.
 */

import type { AgentRegistry } from "../registry/AgentRegistry.ts";
import type { SessionManager } from "../session/SessionManager.ts";
import type { SessionStore } from "../session/SessionStore.ts";
import type { DispatchTaskStateView } from "../scheduler/TaskDispatcher.ts";
import type { LedgerReportRecord, LedgerTaskRecord } from "../ledger/types.ts";
import { resolveTaskCurrentBucket } from "./taskCurrentBucket.ts";
import {
  buildCanonicalTaskRuntimeSnapshot,
  type CanonicalTaskRuntimeConflict,
  type CanonicalTaskRuntimeEvidence,
  type CanonicalTaskRuntimeSnapshot,
  type CanonicalTaskRuntimeState,
} from "./resolveCanonicalTaskRuntimeState.ts";

export type AgentTaskReconcileState =
  | CanonicalTaskRuntimeState
  | "idle"
  | "unknown";

export interface ReconcileAgentTaskStateOpts {
  projectRoot: string;
  agentId: string;
  taskId?: string | null;
  registry: AgentRegistry;
  sessionManager: SessionManager;
  sessionStore?: Pick<SessionStore, "listAll">;
  dispatcher?: {
    getDispatchTaskState(taskId: string): Promise<DispatchTaskStateView>;
  };
  tasks: LedgerTaskRecord[];
  reports: LedgerReportRecord[];
  /** In-memory PmQueueGuard signals when available. */
  nudgeCount?: number;
  workerFailed?: boolean;
}

export interface ReconcileAgentTaskStateResult {
  state: AgentTaskReconcileState;
  task_id: string | null;
  role: string | null;
  agent_id: string;
  session_id: string | null;
  reason_code: string;
  reason_text: string;
  admin_hint: string;
  suggested_action: "wait" | "recover" | "review_report" | "inspect_conflict" | "none";
  queue_state: string;
  last_activity_at: string | null;
  phase?: string;
  evidence?: CanonicalTaskRuntimeEvidence[];
  conflicts?: CanonicalTaskRuntimeConflict[];
  canonical?: CanonicalTaskRuntimeSnapshot;
}

function normalizeTaskId(id: string): string {
  return id.replace(/\.md$/i, "").trim();
}

function taskForAgent(
  tasks: LedgerTaskRecord[],
  role: string,
  focusTaskId?: string | null,
): LedgerTaskRecord | null {
  const normRole = role.toUpperCase();
  if (focusTaskId) {
    const norm = normalizeTaskId(focusTaskId);
    const hit =
      tasks.find((t) => normalizeTaskId(t.task_id) === norm) ??
      tasks.find((t) => normalizeTaskId(String(t.filename ?? "")) === norm);
    if (hit) return hit;
  }
  const open = tasks.filter((t) => {
    if (String(t.recipient ?? "").toUpperCase() !== normRole) return false;
    const bucket = resolveTaskCurrentBucket(t);
    return bucket === "active" || bucket === "review" || bucket === "inbox";
  });
  open.sort((a, b) => String(b.updated_at ?? "").localeCompare(String(a.updated_at ?? "")));
  return open[0] ?? null;
}

function canonicalAdminHint(snapshot: CanonicalTaskRuntimeSnapshot): string {
  const bindings = [
    `任务：${snapshot.task_id}`,
    snapshot.agent_id ? `Agent：${snapshot.agent_id}` : "",
    snapshot.session_id ? `Session：${snapshot.session_id}` : "",
    snapshot.lease_id ? `Lease：${snapshot.lease_id}` : "",
    `状态：${snapshot.state}`,
    `原因：${snapshot.reason_code}`,
    snapshot.summary,
  ].filter(Boolean);
  if (snapshot.state === "projection_conflict") {
    bindings.push("建议：保持现场并检查 evidence/conflicts；禁止自动 wake、restart 或 release");
  }
  return bindings.join("\n");
}

export async function reconcileAgentTaskState(
  opts: ReconcileAgentTaskStateOpts,
): Promise<ReconcileAgentTaskStateResult> {
  const agentId = opts.agentId.trim();
  const record = await opts.registry.get(agentId);
  const role = String(record?.protocol.role ?? "").trim().toUpperCase() || null;
  const focusTaskId = normalizeTaskId(String(opts.taskId ?? ""));
  const focused = focusTaskId
    ? opts.tasks.find(
        (task) =>
          normalizeTaskId(task.task_id) === focusTaskId ||
          normalizeTaskId(String(task.filename ?? "")) === focusTaskId,
      ) ?? null
    : null;
  const worker = focused ?? (role ? taskForAgent(opts.tasks, role, null) : null);
  const taskId = worker ? normalizeTaskId(worker.task_id) : null;

  if (!taskId) {
    const activeSessions = await opts.sessionManager.listActive();
    const agentSession = activeSessions.find(
      (session) =>
        String(session.protocol.agent_id ?? session.agent_id ?? "") === agentId,
    );
    const agentStatus = String(record?.protocol.status ?? "").toLowerCase();
    const idle = !record || agentStatus === "idle";
    const agentRunning = Boolean(agentSession);
    return {
      state: idle ? "idle" : agentRunning ? "running" : "unknown",
      task_id: null,
      role,
      agent_id: agentId,
      session_id: String(agentSession?.protocol.session_id ?? "") || null,
      reason_code: !record ? "agent_not_found" : idle ? "no_open_task" : "agent_active_no_task",
      reason_text: !record
        ? `agent ${agentId} not in registry`
        : idle
          ? "no open task for agent"
          : "agent active without tracked task",
      admin_hint: !record
        ? `Agent ${agentId} 不在 Registry，且没有定位到任务`
        : idle
          ? "Agent 空闲，无待结算任务"
          : "Agent 存在活动投影，但没有定位到当前任务",
      suggested_action: idle ? "none" : "wait",
      queue_state: idle ? "none" : "running",
      last_activity_at: record?.protocol.last_active_at ?? null,
    };
  }
  const canonical = await buildCanonicalTaskRuntimeSnapshot({
    projectRoot: opts.projectRoot,
    taskId,
    role: String(worker?.recipient ?? role ?? "") || null,
    agentId,
    tasks: opts.tasks,
    reports: opts.reports,
    registry: opts.registry,
    sessionManager: opts.sessionManager,
    sessionStore: opts.sessionStore,
    dispatcher: opts.dispatcher,
  });
  return {
    state: canonical.state,
    task_id: taskId,
    role: canonical.role,
    agent_id: canonical.agent_id ?? agentId,
    session_id: canonical.session_id,
    reason_code: canonical.reason_code,
    reason_text: canonical.summary,
    admin_hint: canonicalAdminHint(canonical),
    suggested_action: canonical.suggested_action,
    queue_state: canonical.queue_state,
    last_activity_at: record?.protocol.last_active_at ?? null,
    phase: canonical.phase,
    evidence: canonical.evidence,
    conflicts: canonical.conflicts,
    canonical,
  };
}

/** PM summary gate — block close when thread has recoverable / running unsettled tasks. */
export async function findPmSummaryBlockers(
  opts: Omit<ReconcileAgentTaskStateOpts, "agentId" | "taskId"> & {
    threadKey?: string | null;
  },
): Promise<ReconcileAgentTaskStateResult[]> {
  const blockers: ReconcileAgentTaskStateResult[] = [];
  const seenTaskIds = new Set<string>();
  const agents = await opts.registry.list();
  const thread = String(opts.threadKey ?? "").trim();

  for (const agent of agents) {
    const role = String(agent.protocol.role ?? "").toUpperCase();
    if (!role || role === "ADMIN" || role === "EVAL") continue;

    const openTasks = opts.tasks.filter((t) => {
      if (String(t.recipient ?? "").toUpperCase() !== role) return false;
      if (thread) {
        const tk = String(t.thread_key ?? "").trim();
        if (tk && tk !== thread) return false;
      }
      const bucket = resolveTaskCurrentBucket(t);
      return bucket === "active" || bucket === "review" || bucket === "inbox";
    });

    for (const t of openTasks) {
      const canonicalTaskId = normalizeTaskId(t.task_id).toUpperCase();
      if (seenTaskIds.has(canonicalTaskId)) continue;
      seenTaskIds.add(canonicalTaskId);
      const r = await reconcileAgentTaskState({
        ...opts,
        agentId: agent.protocol.agent_id,
        taskId: t.task_id,
      });
      if (r.state !== "done") {
        blockers.push(r);
      }
    }
  }
  return blockers;
}
