/**
 * DeleteWorkspaceOperation - Orchestrates workspace deletion.
 *
 * Steps:
 * 1. Dispatch workspace:resolve — resolves workspacePath to projectPath + workspaceName
 * 2. Dispatch project:resolve — resolves projectPath to projectId
 * 3. "shutdown" hook — ViewModule (switch + destroy view), AgentModule (kill terminals, stop server, clear MCP/TUI)
 * 4. "release" hook — WindowsLockModule (detect CWD + kill) [Windows-only]
 * 5. If blockingPids provided (retry): "flush" hook — kill provided PIDs
 * 6. "delete" hook — WorktreeModule (remove git worktree), CodeServerModule (delete .code-workspace file)
 *
 * If delete fails (and not force):
 * 7. "detect" — Full blocking process detection (RM + CWD + handles)
 * 8. Emit progress with blockers, emit workspace:delete-failed, return
 *
 * On retry, the UI dispatches a new intent with blockingPids from the previous failure.
 * The flush hook kills those PIDs before re-attempting delete.
 *
 * Each handler returns a typed result; the operation merges results and tracks errors.
 * On success (or force=true), emits a workspace:deleted domain event for state cleanup.
 * On failure, emits workspace:delete-failed to reset idempotency for retry.
 *
 * No provider dependencies - hook handlers do the actual work.
 */

import type { Intent, DomainEvent } from "./lib/types";
import type { Operation, OperationContext, HookContext } from "./lib/operation";
import type {
  ProjectId,
  WorkspaceName,
  DeletionProgress,
  DeletionOperation,
  DeletionOperationId,
  DeletionOperationStatus,
  BlockingProcess,
} from "../shared/api/types";
import type { WorkspacePath } from "../shared/ipc";
import { INTENT_SWITCH_WORKSPACE, type SwitchWorkspaceIntent } from "./switch-workspace";
import { INTENT_RESOLVE_WORKSPACE, type ResolveWorkspaceIntent } from "./resolve-workspace";
import { INTENT_RESOLVE_PROJECT, type ResolveProjectIntent } from "./resolve-project";
import { INTENT_GET_ACTIVE_WORKSPACE, type GetActiveWorkspaceIntent } from "./get-active-workspace";

// =============================================================================
// Intent Types
// =============================================================================

export interface DeleteWorkspacePayload {
  readonly workspacePath: string;
  readonly keepBranch: boolean;
  readonly force: boolean;
  /** Whether to remove the git worktree. true = full pipeline, false = shutdown only (runtime teardown). */
  readonly removeWorktree: boolean;
  readonly skipSwitch?: boolean;
  /** If true, skip preflight checks for uncommitted changes and unmerged commits. */
  readonly ignoreWarnings?: boolean;
  /** PIDs from a previous failed attempt. When present, flush hook kills these before delete. */
  readonly blockingPids?: readonly number[];
}

export interface DeleteWorkspaceIntent extends Intent<{ started: true }> {
  readonly type: "workspace:delete";
  readonly payload: DeleteWorkspacePayload;
}

export const INTENT_DELETE_WORKSPACE = "workspace:delete" as const;

// =============================================================================
// Event Types
// =============================================================================

export interface WorkspaceDeletedPayload {
  readonly projectId: ProjectId;
  readonly workspaceName: WorkspaceName;
  readonly workspacePath: string;
  readonly projectPath: string;
}

export interface WorkspaceDeletedEvent extends DomainEvent {
  readonly type: "workspace:deleted";
  readonly payload: WorkspaceDeletedPayload;
}

export const EVENT_WORKSPACE_DELETED = "workspace:deleted" as const;

export const EVENT_WORKSPACE_DELETE_FAILED = "workspace:delete-failed" as const;

export interface WorkspaceDeleteFailedPayload {
  readonly workspacePath: string;
}

export interface WorkspaceDeleteFailedEvent extends DomainEvent {
  readonly type: typeof EVENT_WORKSPACE_DELETE_FAILED;
  readonly payload: WorkspaceDeleteFailedPayload;
}

export const EVENT_WORKSPACE_DELETION_PROGRESS = "workspace:deletion-progress" as const;

export interface WorkspaceDeletionProgressEvent extends DomainEvent {
  readonly type: typeof EVENT_WORKSPACE_DELETION_PROGRESS;
  readonly payload: DeletionProgress;
}

// =============================================================================
// Hook Result Types (returned by handlers via collect())
// =============================================================================

export const DELETE_WORKSPACE_OPERATION_ID = "delete-workspace";

/**
 * Per-handler result for the "preflight" hook point.
 * Checks workspace for uncommitted changes and unmerged commits before deletion.
 */
export interface PreflightHookResult {
  readonly isDirty?: boolean;
  readonly unmergedCommits?: number;
  readonly error?: string;
}

/**
 * Per-handler result for the "shutdown" hook point.
 * ViewModule provides wasActive; AgentModule may provide error.
 */
export interface ShutdownHookResult {
  readonly wasActive?: boolean;
  readonly serverName?: string;
  readonly error?: string;
}

/**
 * Per-handler result for the "release" hook point.
 * CWD-only scan: finds and kills processes with CWD under workspace.
 */
export interface ReleaseHookResult {
  readonly error?: string;
}

/**
 * Per-handler result for the "delete" hook point.
 */
export interface DeleteHookResult {
  readonly error?: string;
}

/**
 * Per-handler result for the "detect" hook point.
 * Full blocking process detection after delete failure.
 */
export interface DetectHookResult {
  readonly blockingProcesses?: readonly BlockingProcess[];
  readonly error?: string;
}

/**
 * Per-handler result for the "flush" hook point.
 * Kills blocking processes by PID.
 */
export interface FlushHookResult {
  readonly error?: string;
}

/** Input for shutdown/release/delete/detect hooks (enriched with both resolved paths). */
export interface DeletePipelineHookInput extends HookContext {
  readonly projectPath: string;
  readonly workspacePath: string;
  readonly active: boolean;
}

/** Input for flush hook (enriched with PIDs to kill). */
export interface FlushHookInput extends DeletePipelineHookInput {
  readonly blockingPids: readonly number[];
}

// =============================================================================
// Merged Result Types (internal to operation)
// =============================================================================

interface MergedShutdown {
  readonly wasActive: boolean;
  readonly serverName: string | undefined;
  readonly errors: readonly string[];
}

interface MergedRelease {
  readonly errors: readonly string[];
}

interface MergedDelete {
  readonly errors: readonly string[];
}

interface MergedDetect {
  readonly blockingProcesses?: readonly BlockingProcess[];
  readonly errors: readonly string[];
}

interface MergedFlush {
  readonly errors: readonly string[];
}

// =============================================================================
// Merge Functions
// =============================================================================

function mergeShutdown(
  results: readonly ShutdownHookResult[],
  collectErrors: readonly Error[]
): MergedShutdown {
  let wasActive = false;
  let serverName: string | undefined;
  const errors: string[] = [];

  for (const e of collectErrors) errors.push(e.message);
  for (const r of results) {
    if (r.wasActive) wasActive = true;
    if (r.serverName && !serverName) serverName = r.serverName;
    if (r.error) errors.push(r.error);
  }

  return { wasActive, serverName, errors };
}

function mergeRelease(
  results: readonly ReleaseHookResult[],
  collectErrors: readonly Error[]
): MergedRelease {
  const errors: string[] = [];

  for (const e of collectErrors) errors.push(e.message);
  for (const r of results) {
    if (r.error) errors.push(r.error);
  }

  return { errors };
}

function mergeDelete(
  results: readonly DeleteHookResult[],
  collectErrors: readonly Error[]
): MergedDelete {
  const errors: string[] = [];

  for (const e of collectErrors) errors.push(e.message);
  for (const r of results) {
    if (r.error) errors.push(r.error);
  }

  return { errors };
}

function mergeDetect(
  results: readonly DetectHookResult[],
  collectErrors: readonly Error[]
): MergedDetect {
  let blockingProcesses: readonly BlockingProcess[] | undefined;
  const errors: string[] = [];

  for (const e of collectErrors) errors.push(e.message);
  for (const r of results) {
    if (r.blockingProcesses !== undefined) blockingProcesses = r.blockingProcesses;
    if (r.error) errors.push(r.error);
  }

  return {
    ...(blockingProcesses !== undefined && { blockingProcesses }),
    errors,
  };
}

function mergeFlush(
  results: readonly FlushHookResult[],
  collectErrors: readonly Error[]
): MergedFlush {
  const errors: string[] = [];

  for (const e of collectErrors) errors.push(e.message);
  for (const r of results) {
    if (r.error) errors.push(r.error);
  }

  return { errors };
}

// =============================================================================
// Emit function type (for threading ctx.emit through private methods)
// =============================================================================

type EmitFn = (event: DomainEvent) => Promise<void>;

// =============================================================================
// Pipeline State (for progress emission)
// =============================================================================

interface PipelineState {
  readonly shutdown?: MergedShutdown;
  readonly release?: MergedRelease;
  readonly del?: MergedDelete;
  readonly detect?: MergedDetect;
  readonly flush?: MergedFlush;
}

// =============================================================================
// Operation
// =============================================================================

/** Resolved identity from dispatch, needed for events and progress. */
interface ResolvedIdentity {
  readonly projectId: ProjectId;
  readonly workspaceName: WorkspaceName;
  readonly projectPath: string;
}

/** Return value of runPipeline, carrying resolved identity for emitEvent. */
interface PipelineResult {
  readonly hasErrors: boolean;
  readonly identity: ResolvedIdentity;
}

export class DeleteWorkspaceOperation implements Operation<
  DeleteWorkspaceIntent,
  { started: true }
> {
  readonly id = DELETE_WORKSPACE_OPERATION_ID;

  async execute(ctx: OperationContext<DeleteWorkspaceIntent>): Promise<{ started: true }> {
    const { payload } = ctx.intent;

    const emitEvent = (identity: ResolvedIdentity): void => {
      const event: WorkspaceDeletedEvent = {
        type: EVENT_WORKSPACE_DELETED,
        payload: {
          projectId: identity.projectId,
          workspaceName: identity.workspaceName,
          workspacePath: payload.workspacePath,
          projectPath: identity.projectPath,
        },
      };
      ctx.emit(event);
    };

    if (payload.force) {
      let identity: ResolvedIdentity | undefined;
      try {
        const result = await this.runPipeline(ctx, ctx.emit);
        identity = result.identity;
      } finally {
        // Force mode: always emit workspace:deleted for state cleanup (if identity resolved)
        if (identity) {
          emitEvent(identity);
        }
      }
    } else {
      try {
        const result = await this.runPipeline(ctx, ctx.emit);

        if (result.hasErrors) {
          // Emit delete-failed to reset idempotency, allowing retry dispatch
          const failedEvent: WorkspaceDeleteFailedEvent = {
            type: EVENT_WORKSPACE_DELETE_FAILED,
            payload: { workspacePath: payload.workspacePath },
          };
          ctx.emit(failedEvent);
        } else {
          emitEvent(result.identity);
        }
      } catch (error) {
        // Preflight or unexpected error — emit delete-failed for idempotency reset, then propagate
        const failedEvent: WorkspaceDeleteFailedEvent = {
          type: EVENT_WORKSPACE_DELETE_FAILED,
          payload: { workspacePath: payload.workspacePath },
        };
        ctx.emit(failedEvent);
        throw error;
      }
    }

    await this.autoSwitchIfBecameActive(ctx, payload.workspacePath);
    return { started: true };
  }

  private async runPipeline(
    ctx: OperationContext<DeleteWorkspaceIntent>,
    emit: EmitFn
  ): Promise<PipelineResult> {
    const { payload } = ctx.intent;

    // --- Resolve (workspacePath → projectPath + workspaceName) via dispatch ---
    const { projectPath, workspaceName, active } = await ctx.dispatch({
      type: INTENT_RESOLVE_WORKSPACE,
      payload: { workspacePath: payload.workspacePath },
    } as ResolveWorkspaceIntent);

    // --- Resolve Project (projectPath → projectId) via dispatch ---
    const { projectId } = await ctx.dispatch({
      type: INTENT_RESOLVE_PROJECT,
      payload: { projectPath },
    } as ResolveProjectIntent);

    const identity: ResolvedIdentity = { projectId, workspaceName, projectPath };

    // Build enriched context for downstream hooks
    const pipelineCtx: DeletePipelineHookInput = {
      intent: ctx.intent,
      projectPath,
      workspacePath: payload.workspacePath,
      active,
    };

    // Safety net: catch unexpected errors after identity resolution to ensure
    // the UI always receives a terminal progress event (completed: true).
    // Without this, an unexpected throw after the first progress emission
    // leaves the UI permanently stuck on "Removing workspace".
    try {
      return await this.runPipelineBody(ctx, emit, identity, pipelineCtx);
    } catch (error) {
      // Preflight errors must propagate (no progress events emitted yet)
      if (error instanceof Error && error.message.startsWith("Preflight check failed:"))
        throw error;
      this.emitPipelineProgress(emit, identity, payload, {}, true, true);
      return { hasErrors: true, identity };
    }
  }

  private async runPipelineBody(
    ctx: OperationContext<DeleteWorkspaceIntent>,
    emit: EmitFn,
    identity: ResolvedIdentity,
    pipelineCtx: DeletePipelineHookInput
  ): Promise<PipelineResult> {
    const { payload } = ctx.intent;

    // --- Preflight (dirty/unmerged check) ---
    if (payload.removeWorktree && !payload.force && !payload.ignoreWarnings) {
      const { results: preflightResults, errors: preflightCollectErrors } =
        await ctx.hooks.collect<PreflightHookResult>("preflight", pipelineCtx);

      let isDirty = false;
      let unmergedCommits = 0;
      for (const e of preflightCollectErrors) throw e;
      for (const r of preflightResults) {
        if (r.isDirty) isDirty = true;
        if (r.unmergedCommits !== undefined && r.unmergedCommits > unmergedCommits)
          unmergedCommits = r.unmergedCommits;
        if (r.error) throw new Error(r.error);
      }

      if (isDirty || unmergedCommits > 0) {
        const messages: string[] = [];
        if (isDirty) messages.push("Workspace has uncommitted changes");
        if (unmergedCommits > 0)
          messages.push(
            `Workspace has ${unmergedCommits} unmerged commit${unmergedCommits === 1 ? "" : "s"}`
          );
        throw new Error(`Preflight check failed: ${messages.join("; ")}`);
      }
    }

    // --- Shutdown ---
    this.emitPipelineProgress(emit, identity, payload, {}, false, false, "kill-terminals");
    const { results: shutdownResults, errors: shutdownCollectErrors } =
      await ctx.hooks.collect<ShutdownHookResult>("shutdown", pipelineCtx);
    const shutdown = mergeShutdown(shutdownResults, shutdownCollectErrors);
    this.emitPipelineProgress(
      emit,
      identity,
      payload,
      { shutdown },
      false,
      false,
      "cleanup-workspace"
    );

    // Dispatch workspace:switch(auto) if deleted workspace was the active one.
    // Auto-select mode finds the best candidate via find-candidates hook.
    if (shutdown.wasActive && !payload.skipSwitch) {
      try {
        const switchIntent: SwitchWorkspaceIntent = {
          type: INTENT_SWITCH_WORKSPACE,
          payload: { auto: true, currentPath: payload.workspacePath, focus: true },
        };
        await ctx.dispatch(switchIntent);
      } catch {
        // Best-effort: switch failure doesn't fail the deletion
      }
    }

    const shutdownFailed = shutdown.errors.length > 0;
    if (shutdownFailed && !payload.force) {
      this.emitPipelineProgress(emit, identity, payload, { shutdown }, true, true);
      return { hasErrors: true, identity };
    }

    // When removeWorktree is false, skip "release" and "delete" hooks (runtime teardown only)
    if (!payload.removeWorktree) {
      this.emitPipelineProgress(emit, identity, payload, { shutdown }, true, false);
      return { hasErrors: false, identity };
    }

    // --- Release (CWD scan + kill) ---
    const { results: releaseResults, errors: releaseCollectErrors } =
      await ctx.hooks.collect<ReleaseHookResult>("release", pipelineCtx);
    const release = mergeRelease(releaseResults, releaseCollectErrors);
    this.emitPipelineProgress(
      emit,
      identity,
      payload,
      { shutdown, release },
      false,
      false,
      "cleanup-workspace"
    );

    // --- Flush (kill provided PIDs from previous attempt) ---
    let flush: MergedFlush | undefined;
    if (payload.blockingPids && payload.blockingPids.length > 0) {
      this.emitPipelineProgress(
        emit,
        identity,
        payload,
        { shutdown, release },
        false,
        false,
        "killing-blockers"
      );
      const flushCtx: FlushHookInput = {
        ...pipelineCtx,
        blockingPids: payload.blockingPids,
      };
      const { results: flushResults, errors: flushCollectErrors } =
        await ctx.hooks.collect<FlushHookResult>("flush", flushCtx);
      flush = mergeFlush(flushResults, flushCollectErrors);
    }

    // --- Delete ---
    const { results: deleteResults, errors: deleteCollectErrors } =
      await ctx.hooks.collect<DeleteHookResult>("delete", pipelineCtx);
    const del = mergeDelete(deleteResults, deleteCollectErrors);

    const deleteFailed = del.errors.length > 0;
    if (!deleteFailed) {
      // Success
      this.emitPipelineProgress(
        emit,
        identity,
        payload,
        { shutdown, release, del, ...(flush && { flush }) },
        true,
        false
      );
      return { hasErrors: false, identity };
    }

    // Delete failed — if force mode, emit and return
    if (payload.force) {
      const hasErrors = shutdownFailed || deleteFailed;
      this.emitPipelineProgress(
        emit,
        identity,
        payload,
        { shutdown, release, del },
        true,
        hasErrors
      );
      return { hasErrors, identity };
    }

    // --- Detect blockers (full scan after failure) ---
    this.emitPipelineProgress(
      emit,
      identity,
      payload,
      { shutdown, release, del },
      false,
      false,
      "detecting-blockers"
    );
    const { results: detectResults, errors: detectCollectErrors } =
      await ctx.hooks.collect<DetectHookResult>("detect", pipelineCtx);
    const detect = mergeDetect(detectResults, detectCollectErrors);

    // Emit progress with blockers and return failure
    this.emitPipelineProgress(
      emit,
      identity,
      payload,
      { shutdown, release, del, detect },
      true,
      true
    );
    return { hasErrors: true, identity };
  }

  /**
   * If the user navigated to the workspace after the initial switch-away,
   * we must switch again before emitting workspace:deleted.
   */
  private async autoSwitchIfBecameActive(
    ctx: OperationContext<DeleteWorkspaceIntent>,
    workspacePath: string
  ): Promise<void> {
    try {
      const activeRef = await ctx.dispatch({
        type: INTENT_GET_ACTIVE_WORKSPACE,
        payload: {},
      } as GetActiveWorkspaceIntent);
      if (activeRef?.path === workspacePath) {
        await ctx.dispatch({
          type: INTENT_SWITCH_WORKSPACE,
          payload: { auto: true, currentPath: workspacePath, focus: true },
        } as SwitchWorkspaceIntent);
      }
    } catch {
      // Best-effort
    }
  }

  /**
   * Build DeletionOperation[] from pipeline state and emit progress.
   */
  private emitPipelineProgress(
    emit: EmitFn,
    identity: ResolvedIdentity,
    payload: DeleteWorkspacePayload,
    state: PipelineState,
    completed = false,
    hasErrors = false,
    currentStep?: DeletionOperationId
  ): void {
    const operations: DeletionOperation[] = [];

    const applyCurrentStep = (
      id: DeletionOperationId,
      status: DeletionOperationStatus
    ): DeletionOperationStatus => (currentStep === id ? "in-progress" : status);

    // Shutdown operations (always present)
    const shutdownStatus = this.hookPointStatus(state.shutdown);
    const shutdownError =
      state.shutdown && state.shutdown.errors.length > 0
        ? state.shutdown.errors.join("; ")
        : undefined;

    operations.push({
      id: "kill-terminals",
      label: "Terminating processes",
      status: applyCurrentStep("kill-terminals", shutdownStatus),
    });
    operations.push({
      id: "stop-server",
      label: `Stopping ${state.shutdown?.serverName ?? "agent"} server`,
      status: applyCurrentStep("stop-server", shutdownStatus),
      ...(shutdownError && { error: shutdownError }),
    });
    operations.push({
      id: "cleanup-vscode",
      label: "Closing VS Code view",
      status: applyCurrentStep("cleanup-vscode", shutdownStatus),
      ...(shutdownError && { error: shutdownError }),
    });

    // Delete operation (always present, runs before detect/flush in pipeline)
    const deleteStatus = this.hookPointStatus(state.del);
    const deleteError =
      state.del && state.del.errors.length > 0 ? state.del.errors.join("; ") : undefined;

    operations.push({
      id: "cleanup-workspace",
      label: "Removing workspace",
      status: applyCurrentStep("cleanup-workspace", deleteStatus),
      ...(deleteError && { error: deleteError }),
    });

    // Detection operation (from detect hook, shown after delete failure)
    if (state.detect?.blockingProcesses !== undefined) {
      const blockersFound = state.detect.blockingProcesses.length > 0;
      operations.push({
        id: "detecting-blockers",
        label: "Detecting blocking processes...",
        status: applyCurrentStep("detecting-blockers", blockersFound ? "error" : "done"),
        ...(blockersFound && {
          error: `Blocked by ${state.detect.blockingProcesses.length} process(es)`,
        }),
      });
    } else if (currentStep === "detecting-blockers") {
      operations.push({
        id: "detecting-blockers",
        label: "Detecting blocking processes...",
        status: "in-progress",
      });
    }

    // Flush operation (from flush hook, shown when killing blockers)
    if (state.flush) {
      const flushError = state.flush.errors.length > 0 ? state.flush.errors[0] : undefined;
      operations.push({
        id: "killing-blockers",
        label: "Killing blocking processes...",
        status: applyCurrentStep("killing-blockers", flushError ? "error" : "done"),
        ...(flushError && { error: flushError }),
      });
    } else if (currentStep === "killing-blockers") {
      operations.push({
        id: "killing-blockers",
        label: "Killing blocking processes...",
        status: "in-progress",
      });
    }

    // Build blocking processes from detect results
    const blockingProcesses =
      state.detect?.blockingProcesses && state.detect.blockingProcesses.length > 0
        ? state.detect.blockingProcesses
        : undefined;

    const progressEvent: WorkspaceDeletionProgressEvent = {
      type: EVENT_WORKSPACE_DELETION_PROGRESS,
      payload: {
        workspacePath: payload.workspacePath as WorkspacePath,
        workspaceName: identity.workspaceName,
        projectId: identity.projectId,
        keepBranch: payload.keepBranch,
        operations,
        completed,
        hasErrors,
        ...(blockingProcesses !== undefined && { blockingProcesses }),
      },
    };
    emit(progressEvent);
  }

  private hookPointStatus(
    merged: { readonly errors: readonly string[] } | undefined
  ): "pending" | "done" | "error" {
    if (!merged) return "pending";
    return merged.errors.length > 0 ? "error" : "done";
  }
}
