/**
 * Retry actions for failed tasks.
 *
 * Uses the existing worktree (clone) for conversation and direct re-execution.
 * The worktree is preserved after initial execution, so no clone creation is needed.
 */

import * as fs from 'node:fs';
import type { TaskListItem } from '../../../infra/task/index.js';
import { TaskRunner } from '../../../infra/task/index.js';
import { loadPieceByIdentifier, resolvePieceConfigValue, getPieceDescription } from '../../../infra/config/index.js';
import { selectPiece } from '../../pieceSelection/index.js';
import { selectOptionWithDefault } from '../../../shared/prompt/index.js';
import { getLabel } from '../../../shared/i18n/index.js';
import { info, header, blankLine, status, warn } from '../../../shared/ui/index.js';
import { createLogger } from '../../../shared/utils/index.js';
import type { PieceConfig } from '../../../core/models/index.js';
import {
  findRunForTask,
  loadRunSessionContext,
  getRunPaths,
  formatRunSessionForPrompt,
  runRetryMode,
  findPreviousOrderContent,
  type RetryContext,
  type RetryFailureInfo,
  type RetryRunInfo,
} from '../../interactive/index.js';
import { executeAndCompleteTask } from '../execute/taskExecution.js';
import {
  appendRetryNote,
  DEPRECATED_PROVIDER_CONFIG_WARNING,
  hasDeprecatedProviderConfig,
} from './requeueHelpers.js';

const log = createLogger('list-tasks');

function displayFailureInfo(task: TaskListItem): void {
  header(`Failed Task: ${task.name}`);
  info(`  Failed at: ${task.createdAt}`);

  if (task.failure) {
    blankLine();
    if (task.failure.movement) {
      status('Failed at', task.failure.movement, 'red');
    }
    status('Error', task.failure.error, 'red');
    if (task.failure.last_message) {
      status('Last message', task.failure.last_message);
    }
  }

  blankLine();
}

async function selectStartMovement(
  pieceConfig: PieceConfig,
  defaultMovement: string | null,
): Promise<string | null> {
  const movements = pieceConfig.movements.map((m) => m.name);

  const defaultIdx = defaultMovement
    ? movements.indexOf(defaultMovement)
    : 0;
  const effectiveDefault = defaultIdx >= 0 ? movements[defaultIdx] : movements[0];

  const options = movements.map((name) => ({
    label: name,
    value: name,
    description: name === pieceConfig.initialMovement ? 'Initial movement' : undefined,
  }));

  return await selectOptionWithDefault<string>('Start from movement:', options, effectiveDefault ?? movements[0]!);
}

function buildRetryFailureInfo(task: TaskListItem): RetryFailureInfo {
  return {
    taskName: task.name,
    taskContent: task.content,
    createdAt: task.createdAt,
    failedMovement: task.failure?.movement ?? '',
    error: task.failure?.error ?? '',
    lastMessage: task.failure?.last_message ?? '',
    retryNote: task.data?.retry_note ?? '',
  };
}

function buildRetryRunInfo(
  runsBaseDir: string,
  slug: string,
): RetryRunInfo {
  const paths = getRunPaths(runsBaseDir, slug);
  const sessionContext = loadRunSessionContext(runsBaseDir, slug);
  const formatted = formatRunSessionForPrompt(sessionContext);
  return {
    logsDir: paths.logsDir,
    reportsDir: paths.reportsDir,
    task: formatted.runTask,
    piece: formatted.runPiece,
    status: formatted.runStatus,
    movementLogs: formatted.runMovementLogs,
    reports: formatted.runReports,
  };
}

function resolveWorktreePath(task: TaskListItem): string {
  if (!task.worktreePath) {
    throw new Error(`Worktree path is not set for task: ${task.name}`);
  }
  if (!fs.existsSync(task.worktreePath)) {
    throw new Error(`Worktree directory does not exist: ${task.worktreePath}`);
  }
  return task.worktreePath;
}

/**
 * Retry a failed task.
 *
 * Runs the retry conversation in the existing worktree, then directly
 * re-executes the task there (auto-commit + push + status update).
 *
 * @returns true if task was re-executed successfully, false if cancelled or failed
 */
export async function retryFailedTask(
  task: TaskListItem,
  projectDir: string,
): Promise<boolean> {
  if (task.kind !== 'failed') {
    throw new Error(`retryFailedTask requires failed task. received: ${task.kind}`);
  }

  const worktreePath = resolveWorktreePath(task);

  displayFailureInfo(task);

  const matchedSlug = findRunForTask(worktreePath, task.content);
  const runInfo = matchedSlug ? buildRetryRunInfo(worktreePath, matchedSlug) : null;

  let selectedPiece: string;
  if (runInfo?.piece) {
    const usePreviousLabel = getLabel('retry.usePreviousWorkflow');
    const changeWorkflowLabel = getLabel('retry.changeWorkflow');
    const choice = await selectOptionWithDefault(
      getLabel('retry.workflowPrompt'),
      [
        { label: `${runInfo.piece} - ${usePreviousLabel}`, value: 'use_previous' },
        { label: changeWorkflowLabel, value: 'change' },
      ],
      'use_previous',
    );

    if (choice === null) {
      info('Cancelled');
      return false;
    }

    if (choice === 'use_previous') {
      selectedPiece = runInfo.piece;
    } else {
      const selected = await selectPiece(projectDir);
      if (!selected) {
        info('Cancelled');
        return false;
      }
      selectedPiece = selected;
    }
  } else {
    const selected = await selectPiece(projectDir);
    if (!selected) {
      info('Cancelled');
      return false;
    }
    selectedPiece = selected;
  }

  const previewCount = resolvePieceConfigValue(projectDir, 'interactivePreviewMovements');
  const pieceConfig = loadPieceByIdentifier(selectedPiece, projectDir);

  if (!pieceConfig) {
    throw new Error(`Piece "${selectedPiece}" not found after selection.`);
  }

  const selectedMovement = await selectStartMovement(pieceConfig, task.failure?.movement ?? null);
  if (selectedMovement === null) {
    return false;
  }

  const pieceDesc = getPieceDescription(selectedPiece, projectDir, previewCount);
  const pieceContext = {
    name: pieceDesc.name,
    description: pieceDesc.description,
    pieceStructure: pieceDesc.pieceStructure,
    movementPreviews: pieceDesc.movementPreviews,
  };

  // Runs data lives in the worktree (written during previous execution)
  const previousOrderContent = findPreviousOrderContent(worktreePath, matchedSlug);
  if (hasDeprecatedProviderConfig(previousOrderContent)) {
    warn(DEPRECATED_PROVIDER_CONFIG_WARNING);
  }

  blankLine();
  const branchName = task.branch ?? task.name;
  const retryContext: RetryContext = {
    failure: buildRetryFailureInfo(task),
    branchName,
    pieceContext,
    run: runInfo,
    previousOrderContent,
  };

  const retryResult = await runRetryMode(worktreePath, retryContext, previousOrderContent);
  if (retryResult.action === 'cancel') {
    return false;
  }

  const startMovement = selectedMovement !== pieceConfig.initialMovement
    ? selectedMovement
    : undefined;
  const retryNote = appendRetryNote(task.data?.retry_note, retryResult.task);
  const runner = new TaskRunner(projectDir);

  if (retryResult.action === 'save_task') {
    runner.requeueTask(task.name, ['failed'], startMovement, retryNote);
    info(`Task "${task.name}" has been requeued.`);
    return true;
  }

  const taskInfo = runner.startReExecution(task.name, ['failed'], startMovement, retryNote);

  log.info('Starting re-execution of failed task', {
    name: task.name,
    worktreePath,
    startMovement,
  });

  return executeAndCompleteTask(taskInfo, runner, projectDir, selectedPiece);
}
