import type {
  Item,
  ResearchEvidenceCard,
  ResearchRun,
  ResearchSubquestion,
  ThreadId,
  Turn,
  TurnId,
  VerdictRecord,
} from "@best-agent/protocol";
import { createRandomId } from "@best-agent/foundation";
import type { AgentProfile } from "../agent/profile-registry.js";
import { collectChildTaskEffectiveVerdicts } from "../core/workflow-graph.js";

type DeepResearchSubquestionPlan = {
  profile: AgentProfile;
  prompt: string;
  subquestionId: string;
  title: string;
};

type ResearchExecutorDeps = {
  bindResearchRoot(turnId: TurnId, graphId: string): Promise<void>;
  buildSubquestionPlans(prompt: string): DeepResearchSubquestionPlan[];
  completeRun(input: {
    evidence: ResearchEvidenceCard[];
    failureMessages: string[];
    finalizedSubquestions: ResearchSubquestion[];
    graphId: string;
    prompt: string;
    rootVerdictStatus?: VerdictRecord["status"];
    rootVerdictSummary?: string;
    run: ResearchRun;
    turn: Turn;
  }): Promise<string>;
  createRun(input: {
    objective: string;
    subquestionCount: number;
    threadId: ThreadId;
    turnId: TurnId;
  }): Promise<ResearchRun>;
  executeSubquestion(
    turn: Turn,
    hostProfile: AgentProfile,
    activeSkillIds: string[],
    runId: string,
    plan: DeepResearchSubquestionPlan,
    persistSubquestion: (subquestion: ResearchSubquestion) => Promise<void>,
  ): Promise<{ evidence: ResearchEvidenceCard[]; subquestion: ResearchSubquestion }>;
  failRun(
    turn: Turn,
    run: ResearchRun,
    input: {
      errorCode?: string;
      failureKind: import("@best-agent/protocol").ToolFailureKind;
      graphId: string;
      summary: string;
    },
  ): Promise<never>;
  getEffectiveVerdicts(threadId: ThreadId, nodeIds: string[]): Promise<Map<string, VerdictRecord>>;
  pushItems(items: Item[]): Promise<void>;
  startRunLifecycle(turn: Turn, run: ResearchRun, subquestionCount: number): void;
  classifyFailure(error: unknown): {
    errorCode?: string;
    failureKind: import("@best-agent/protocol").ToolFailureKind;
  };
  persistSubquestions(
    threadId: ThreadId,
    runId: string,
    subquestions: ResearchSubquestion[],
  ): Promise<void>;
  verdictClosureMode: "heuristic" | "kernel";
};

export class ResearchExecutor {
  readonly #deps: ResearchExecutorDeps;

  constructor(deps: ResearchExecutorDeps) {
    this.#deps = deps;
  }

  async execute(input: {
    activeSkillIds: string[];
    hostProfile: AgentProfile;
    prompt: string;
    turn: Turn;
  }): Promise<string> {
    const plans = this.#deps.buildSubquestionPlans(input.prompt);
    const run = await this.#deps.createRun({
      objective: input.prompt,
      subquestionCount: plans.length,
      threadId: input.turn.threadId,
      turnId: input.turn.id,
    });
    const researchGraphId = `research:${run.runId}`;
    await this.#deps.bindResearchRoot(input.turn.id, researchGraphId);
    await this.#deps.pushItems([
      {
        id: createRandomId<"ItemId">("item"),
        threadId: input.turn.threadId,
        turnId: input.turn.id,
        createdAt: new Date().toISOString(),
        kind: "reasoning",
        status: "draft",
        summary: `The orchestrator profile started the deep-research workflow with ${plans.length} sub-questions.`,
      },
    ]);
    this.#deps.startRunLifecycle(input.turn, run, plans.length);

    const subquestions = new Map<string, ResearchSubquestion>();
    const persistSubquestion = async (subquestion: ResearchSubquestion) => {
      subquestions.set(subquestion.subquestionId, subquestion);
      await this.#deps.persistSubquestions(
        input.turn.threadId,
        run.runId,
        plans
          .map((plan) => subquestions.get(plan.subquestionId))
          .filter(Boolean) as ResearchSubquestion[],
      );
    };

    const settled = await Promise.allSettled(
      plans.map((plan) =>
        this.#deps.executeSubquestion(
          input.turn,
          input.hostProfile,
          input.activeSkillIds,
          run.runId,
          plan,
          persistSubquestion,
        ),
      ),
    );

    const evidence: ResearchEvidenceCard[] = [];
    const failureMessages: string[] = [];
    let firstFailure: unknown;
    for (const result of settled) {
      if (result.status === "fulfilled") {
        evidence.push(...result.value.evidence);
        continue;
      }
      firstFailure ??= result.reason;
      failureMessages.push(
        result.reason instanceof Error
          ? result.reason.message
          : "Unknown research sub-question failure",
      );
    }

    const finalizedSubquestions = plans
      .map((plan) => subquestions.get(plan.subquestionId))
      .filter(Boolean) as ResearchSubquestion[];
    const effectiveVerdicts = await this.#deps.getEffectiveVerdicts(
      input.turn.threadId,
      finalizedSubquestions.map((subquestion) => subquestion.childTaskId),
    );
    const effectiveSubquestionVerdicts = await collectChildTaskEffectiveVerdicts({
      childTaskIds: finalizedSubquestions.map((subquestion) => subquestion.childTaskId),
      effectiveVerdicts,
    });
    const subquestionVerdicts = new Map<string, VerdictRecord>();
    for (const subquestion of finalizedSubquestions) {
      const verdict = effectiveSubquestionVerdicts.get(subquestion.childTaskId);
      if (verdict) {
        subquestionVerdicts.set(subquestion.subquestionId, verdict);
      }
    }

    let rootVerdictStatus: VerdictRecord["status"] = "satisfied";
    let rootVerdictSummary = `Deep research completed with ${evidence.length} evidence cards and ${failureMessages.length} sub-question failures.`;
    const supportingSubquestions = finalizedSubquestions.filter(
      (subquestion) => subquestion.profileName !== "verify",
    );
    const satisfiedSupportingCount = supportingSubquestions.filter(
      (subquestion) => subquestionVerdicts.get(subquestion.subquestionId)?.status === "satisfied",
    ).length;
    const hasSatisfiedSupportingSubquestion =
      supportingSubquestions.length > 0 &&
      satisfiedSupportingCount / supportingSubquestions.length > 0.5;

    if (evidence.length === 0) {
      rootVerdictStatus = "unsatisfied";
      rootVerdictSummary = failureMessages[0] ?? "Deep research failed without evidence.";
    }
    if (!hasSatisfiedSupportingSubquestion) {
      rootVerdictStatus = "unsatisfied";
      rootVerdictSummary =
        satisfiedSupportingCount === 0
          ? "Deep research finished without any satisfied supporting sub-questions."
          : "Deep research finished without enough satisfied supporting sub-questions.";
    }

    if (evidence.length === 0 && this.#deps.verdictClosureMode === "kernel") {
      const failure = this.#deps.classifyFailure(firstFailure);
      await this.#deps.failRun(input.turn, run, {
        errorCode: failure.errorCode,
        failureKind: failure.failureKind,
        graphId: researchGraphId,
        summary: rootVerdictSummary,
      });
    }

    if (!hasSatisfiedSupportingSubquestion && this.#deps.verdictClosureMode === "kernel") {
      await this.#deps.failRun(input.turn, run, {
        failureKind: "execution-failed",
        graphId: researchGraphId,
        summary: rootVerdictSummary,
      });
    }

    const output = await this.#deps.completeRun({
      evidence,
      failureMessages,
      finalizedSubquestions,
      graphId: researchGraphId,
      prompt: input.prompt,
      rootVerdictStatus,
      rootVerdictSummary,
      run,
      turn: input.turn,
    });
    if (this.#deps.verdictClosureMode === "heuristic" && rootVerdictStatus !== "satisfied") {
      return [
        `Deep research root verdict unsatisfied: ${rootVerdictSummary}`,
        "Do not treat the following report as complete evidence.",
        "",
        output,
      ].join("\n");
    }
    return output;
  }
}
