import type { ToolContext } from '../tools/index.js';
import { logger } from '../../logger.js';
import type { MovementResult } from './types.js';
import type { DispatchOutcome } from './tool-dispatcher.js';

/**
 * agent-loop/pending-states.ts — the post-dispatch "park / wait" decision
 * branches of executeMovement, extracted into named predicates so each
 * control-flow exit is individually testable.
 *
 * Each builder returns a terminal MovementResult when its condition holds, or
 * null to let the loop continue. The caller wraps the non-null result in
 * finishMovement() and returns. Builders that consume a one-shot toolCtx flag
 * (pendingToolApproval / pendingSubtaskWait) clear it as a side effect, matching
 * the original inline behaviour.
 */

type WaitingHumanDispatch = Extract<DispatchOutcome, { status: 'waiting_human' }>;

/**
 * InteractiveBrowse parked the movement for a human login (waiting_human
 * dispatch outcome). The caller keeps the `dispatch.status === 'waiting_human'`
 * guard inline so the union narrows for the rest of the loop; this builder just
 * shapes the result.
 */
export function buildBrowserWaitResult(
  dispatch: WaitingHumanDispatch,
  toolsUsed: string[],
  movementName: string,
): MovementResult {
  logger.info(`[agent-loop] movement=${movementName} InteractiveBrowse waiting_human: sessionId=${dispatch.sessionId}`);
  return {
    next: 'WAITING_HUMAN_BROWSER',
    output: dispatch.output,
    toolsUsed,
    waitReason: dispatch.waitReason,
    browserSessionId: dispatch.sessionId,
  };
}

/**
 * Tool-request mechanism: RequestTool set a pause flag → end the movement as
 * waiting_human so a user can approve/deny the requested tool inline. On resume
 * the granted tool is in the effective set and the movement re-runs from the
 * start (resumeMovement). Clears toolCtx.pendingToolApproval.
 */
export function buildToolApprovalWaitResult(
  toolCtx: ToolContext,
  toolsUsed: string[],
  movementName: string,
): MovementResult | null {
  if (!toolCtx.pendingToolApproval) return null;
  const pending = toolCtx.pendingToolApproval;
  toolCtx.pendingToolApproval = null;
  logger.info(`[agent-loop] movement=${movementName} waiting_human for tool approval: ${pending.toolName}`);
  return {
    next: 'WAITING_HUMAN_TOOL_REQUEST',
    output: `ツール "${pending.toolName}" の利用承認を待っています。`,
    toolsUsed,
    waitReason: 'tool_request',
  };
}

/**
 * Package-request mechanism: RequestPackage set a pause flag → end the movement
 * as waiting_human so a user can approve/deny the requested Python package
 * inline. On approve the wheel is installed into the space overlay and the job
 * resumes; the movement re-runs from the start (resumeMovement) with the
 * package importable. Clears toolCtx.pendingPackageApproval. Mirrors
 * buildToolApprovalWaitResult.
 */
export function buildPackageApprovalWaitResult(
  toolCtx: ToolContext,
  toolsUsed: string[],
  movementName: string,
): MovementResult | null {
  if (!toolCtx.pendingPackageApproval) return null;
  const pending = toolCtx.pendingPackageApproval;
  toolCtx.pendingPackageApproval = null;
  logger.info(`[agent-loop] movement=${movementName} waiting_human for package approval: ${pending.spec}`);
  return {
    next: 'WAITING_HUMAN_PACKAGE_REQUEST',
    output: `Python パッケージ "${pending.spec}" のインストール承認を待っています。`,
    toolsUsed,
    waitReason: 'package_request',
  };
}

/**
 * WaitSubTask set a park flag → end the movement as WAIT_SUBTASKS so the worker
 * parks this job (releasing the worker) until the spawned children finish, then
 * resumes the SAME movement (resumeMovement) with subtasks/*\/result.md present.
 * Park signal, NOT an in-tool block (worker-release is load-bearing vs
 * deadlock). Clears toolCtx.pendingSubtaskWait.
 */
export function buildSubtaskWaitResult(
  toolCtx: ToolContext,
  toolsUsed: string[],
  movementName: string,
): MovementResult | null {
  if (!toolCtx.pendingSubtaskWait) return null;
  toolCtx.pendingSubtaskWait = false;
  logger.info(`[agent-loop] movement=${movementName} WaitSubTask → WAIT_SUBTASKS (resume same movement)`);
  return {
    next: 'WAIT_SUBTASKS',
    output: 'サブタスクの完了を待機しています。',
    toolsUsed,
    resumeMovement: movementName,
  };
}

/**
 * Auto-park guard: the agent tried to complete *successfully* while it still
 * has unwaited pending children. Convert to WAIT_SUBTASKS instead of a false
 * success, resuming the SAME movement so it can read subtasks/*\/result.md and
 * genuinely finish. Only the success path is parked here; abort /
 * needs_user_input with pending children is handled at the worker terminal
 * chokepoint (cancelPendingSubtasks).
 *
 * Fail-open: a DB hiccup in the pending-children check must NOT turn a genuine
 * completion into a job failure, so hasPendingSubtasks() errors default to
 * "no pending" and let the real complete go through.
 */
export async function buildCompleteAutoParkResult(
  winnerArgs: { status: string; result?: string },
  toolCtx: ToolContext,
  toolsUsed: string[],
  movementName: string,
): Promise<MovementResult | null> {
  if (
    winnerArgs.status === 'success' &&
    toolCtx.hasPendingSubtasks &&
    (await toolCtx.hasPendingSubtasks().catch(() => false))
  ) {
    logger.info(`[agent-loop] movement=${movementName} complete(success) with pending subtasks → auto-park WAIT_SUBTASKS`);
    return {
      next: 'WAIT_SUBTASKS',
      output: winnerArgs.result ?? 'サブタスクの完了を待機しています。',
      toolsUsed,
      resumeMovement: movementName,
    };
  }
  return null;
}
