/**
 * LegacyOpenDevTeamAdmissionPolicy — WP-07.
 *
 * Isolates open-dev-team dispatch conditions out of generic Switchboard Core.
 * Composes existing gates; does not reinvent eligibility logic.
 *
 * Role-specific branches (e.g. QA) belong here only — never in generic
 * Switchboard hosts/stores/binding/shadow surfaces.
 */

import {
  canDispatchQA,
  qaGateToDispatchSkipReason,
  type QaDispatchGateResult,
} from "../../pm/qaDispatchGate.ts";
import type { ExecutionGateContext } from "../../pm/taskDispatchContext.ts";
import {
  evaluateDispatchEligibility,
  normalizeWorkerRole,
  resolveExplicitDispatchHoldReason,
  type DispatchGateReportRef,
  type DispatchGateTaskRef,
  type ExplicitDispatchGateInput,
  type ExplicitDispatchHoldReason,
} from "../../pm/taskDispatchGate.ts";
import type {
  WorkAdmissionDecision,
  WorkAdmissionDecisionKind,
  WorkAdmissionInput,
  WorkAdmissionPolicy,
} from "./WorkAdmissionPolicy.ts";

export interface LegacyProductGateSnapshot {
  allowed: boolean;
  reason?: string;
  detail?: string;
  findings?: string[];
  code?: string;
}

export interface LegacyDependencyGateSnapshot {
  allowed: boolean;
  reason?: string;
  dependencyTaskIds?: string[];
}

export interface LegacyOpenDevTeamAdmissionInput extends WorkAdmissionInput {
  target: DispatchGateTaskRef;
  threadTasks: DispatchGateTaskRef[];
  reports: DispatchGateReportRef[];
  /** Required when target recipient is QA (artifact / meta checks). */
  executionContext?: ExecutionGateContext;
  /** Precomputed ProductDeliveryRuntimeGate; omit = treat as already allowed. */
  productGate?: LegacyProductGateSnapshot;
  /** Precomputed TaskDependencyGate; omit = treat as already allowed. */
  dependencyGate?: LegacyDependencyGateSnapshot;
  /**
   * When true, evaluate explicit-hold (trusted route / manual import).
   * Mirrors InboxWatcher / hold registration; default false for dispatchTask path.
   */
  checkExplicitHold?: boolean;
  /** Extra fields for resolveExplicitDispatchHoldReason when checkExplicitHold. */
  explicitHoldInput?: ExplicitDispatchGateInput;
  /**
   * Mirror TaskDispatcher: when eligibility says waiting_dependency but the
   * task already carries explicit depends_on (TaskDependencyGate authority),
   * do not block again.
   */
  honorExplicitDependsOnBypass?: boolean;
}

export type LegacyGateStage =
  | "product"
  | "dependency"
  | "explicit_hold"
  | "qa"
  | "eligibility";

/** Legacy-shaped verdict used for old/new comparison tests. */
export interface LegacyOpenDevTeamGateVerdict {
  allowed: boolean;
  stage: LegacyGateStage;
  reason: string;
  detail?: string;
  waitingOn?: string;
  executionState?: string;
  qaReason?: string;
  eligibilityReason?: string;
  holdReason?: ExplicitDispatchHoldReason;
}

const REJECT_REASONS = new Set([
  "already_done",
  "already_active",
  "cancelled",
  "superseded",
  "root_task_closed",
]);

const HOLD_REASONS = new Set([
  "waiting_dependency",
  "dependency_pending",
  "task_not_dispatched",
  "lifecycle_split",
  "execution_blocked",
  "product_brief_required",
  "dev_report_pending",
  "artifact_missing",
  "untrusted_source",
  "missing_provenance",
  "manual_import",
  "child_subtask",
]);

/**
 * Pure composition of open-dev-team gates (same order as TaskDispatcher
 * business gates after TaskSpecAdmission).
 */
export function evaluateLegacyOpenDevTeamGates(
  input: LegacyOpenDevTeamAdmissionInput,
): LegacyOpenDevTeamGateVerdict {
  const product = input.productGate;
  if (product && !product.allowed) {
    const reason = String(product.reason ?? product.code ?? "product_blocked");
    return {
      allowed: false,
      stage: "product",
      reason,
      detail: product.detail ?? product.findings?.join(","),
    };
  }

  const dependency = input.dependencyGate;
  if (dependency && !dependency.allowed) {
    return {
      allowed: false,
      stage: "dependency",
      reason: "dependency_pending",
      detail: dependency.reason,
      waitingOn: dependency.dependencyTaskIds?.[0],
    };
  }

  if (input.checkExplicitHold) {
    const holdInput: ExplicitDispatchGateInput = {
      sender: input.target.sender,
      recipient: input.target.recipient,
      filename: input.target.filename,
      parent: input.target.parent,
      parentTaskId: input.target.parentTaskId,
      reworkOf: input.target.reworkOf,
      ...input.explicitHoldInput,
    };
    const holdReason = resolveExplicitDispatchHoldReason(holdInput);
    if (holdReason) {
      return {
        allowed: false,
        stage: "explicit_hold",
        reason: "task_not_dispatched",
        detail: `explicit hold: ${holdReason}`,
        holdReason,
      };
    }
  }

  const workerRole = normalizeWorkerRole(input.target.recipient);
  if (workerRole === "QA") {
    const ctx = input.executionContext;
    if (!ctx) {
      return {
        allowed: false,
        stage: "qa",
        reason: "execution_blocked",
        detail: "executionContext required for QA admission",
        qaReason: "artifact_missing",
      };
    }
    const qaGate: QaDispatchGateResult = canDispatchQA(
      input.target,
      ctx,
      input.threadTasks,
    );
    if (!qaGate.allowed) {
      const skipReason = qaGateToDispatchSkipReason(qaGate.reason);
      return {
        allowed: false,
        stage: "qa",
        reason: skipReason,
        detail: qaGate.detail,
        waitingOn: qaGate.waiting_on,
        executionState: qaGate.execution_state,
        qaReason: qaGate.reason,
      };
    }
    return {
      allowed: true,
      stage: "qa",
      reason: "allowed",
      executionState: qaGate.execution_state,
    };
  }

  const gate = evaluateDispatchEligibility(
    input.target,
    input.threadTasks,
    input.reports,
  );
  const explicitDeps = input.target.dependsOn ?? [];
  const bypassWaiting =
    (input.honorExplicitDependsOnBypass ?? true) &&
    !gate.allowed &&
    gate.reason === "waiting_dependency" &&
    explicitDeps.length > 0;

  if (!gate.allowed && !bypassWaiting) {
    return {
      allowed: false,
      stage: "eligibility",
      reason: gate.reason,
      detail: gate.detail,
      waitingOn: gate.waitingOn,
      eligibilityReason: gate.reason,
    };
  }

  return {
    allowed: true,
    stage: "eligibility",
    reason: "allowed",
    eligibilityReason: bypassWaiting ? "waiting_dependency_bypassed" : gate.reason,
  };
}

export function mapLegacyGateVerdictToAdmission(
  verdict: LegacyOpenDevTeamGateVerdict,
): WorkAdmissionDecision {
  if (verdict.allowed) {
    return {
      decision: "allow",
      reason: "allowed",
      evidence: [`stage:${verdict.stage}`],
      extensions: {
        stage: verdict.stage,
        legacy_allowed: true,
        eligibilityReason: verdict.eligibilityReason,
        executionState: verdict.executionState,
      },
    };
  }

  const decision = classifyAdmissionDecision(verdict.reason, verdict);
  return {
    decision,
    reason: verdict.reason,
    evidence: [
      `stage:${verdict.stage}`,
      ...(verdict.detail ? [`detail:${verdict.detail}`] : []),
      ...(verdict.waitingOn ? [`waiting_on:${verdict.waitingOn}`] : []),
    ],
    extensions: {
      stage: verdict.stage,
      legacy_allowed: false,
      detail: verdict.detail,
      waitingOn: verdict.waitingOn,
      executionState: verdict.executionState,
      qaReason: verdict.qaReason,
      eligibilityReason: verdict.eligibilityReason,
      holdReason: verdict.holdReason,
    },
  };
}

function classifyAdmissionDecision(
  reason: string,
  verdict: LegacyOpenDevTeamGateVerdict,
): WorkAdmissionDecisionKind {
  const normalized = String(reason ?? "").trim().toLowerCase();
  if (normalized === "lifecycle_split") return "needs_human";

  if (verdict.stage === "product") {
    if (normalized === "cancelled" || normalized === "root_task_closed") {
      return "reject";
    }
    return "hold";
  }

  if (verdict.qaReason === "cancelled" || verdict.qaReason === "superseded") {
    return "reject";
  }
  if (verdict.qaReason === "already_done") return "reject";
  if (
    verdict.qaReason === "dev_report_pending" ||
    verdict.qaReason === "artifact_missing" ||
    verdict.qaReason === "not_qa_task"
  ) {
    return "hold";
  }

  if (REJECT_REASONS.has(normalized)) return "reject";
  if (HOLD_REASONS.has(normalized)) return "hold";
  return "hold";
}

/**
 * Compare-helper: run the same pure gates the legacy dispatcher uses for a
 * non-product/non-dependency slice (QA or eligibility), returning the legacy
 * {allowed, reason} shape for matrix checks.
 */
export function evaluateLegacyDispatcherSlice(
  input: LegacyOpenDevTeamAdmissionInput,
): { allowed: boolean; reason: string; detail?: string } {
  const workerRole = normalizeWorkerRole(input.target.recipient);
  if (workerRole === "QA") {
    if (!input.executionContext) {
      return {
        allowed: false,
        reason: "execution_blocked",
        detail: "executionContext required for QA admission",
      };
    }
    const qa = canDispatchQA(
      input.target,
      input.executionContext,
      input.threadTasks,
    );
    if (!qa.allowed) {
      return {
        allowed: qa.allowed,
        reason: qaGateToDispatchSkipReason(qa.reason),
        detail: qa.detail,
      };
    }
    return { allowed: true, reason: "allowed" };
  }
  const gate = evaluateDispatchEligibility(
    input.target,
    input.threadTasks,
    input.reports,
  );
  const explicitDeps = input.target.dependsOn ?? [];
  if (
    !gate.allowed &&
    gate.reason === "waiting_dependency" &&
    explicitDeps.length > 0 &&
    (input.honorExplicitDependsOnBypass ?? true)
  ) {
    return { allowed: true, reason: "allowed" };
  }
  return {
    allowed: gate.allowed,
    reason: gate.allowed ? "allowed" : gate.reason,
    detail: gate.detail,
  };
}

export class LegacyOpenDevTeamAdmissionPolicy
  implements WorkAdmissionPolicy<LegacyOpenDevTeamAdmissionInput>
{
  async inspect(
    input: LegacyOpenDevTeamAdmissionInput,
  ): Promise<WorkAdmissionDecision> {
    return mapLegacyGateVerdictToAdmission(
      evaluateLegacyOpenDevTeamGates(input),
    );
  }
}
