"""Command to implement features from GitHub issues or plan files.

This unified command provides two modes:
- GitHub issue mode: erk implement 123 or erk implement <URL>
- Plan file mode: erk implement path/to/plan.md

Both modes create a worktree and invoke Claude for implementation.
"""

import re
import shlex
import shutil
from dataclasses import dataclass
from pathlib import Path
from typing import NamedTuple

import click
from erk_shared.impl_folder import create_impl_folder, save_issue_reference
from erk_shared.naming import (
    ensure_unique_worktree_name_with_date,
    format_branch_timestamp_suffix,
    sanitize_worktree_name,
    strip_plan_from_filename,
)
from erk_shared.output.output import user_output
from erk_shared.plan_store.types import PlanState

from erk.cli.activation import render_activation_script
from erk.cli.commands.completions import complete_plan_files
from erk.cli.commands.wt.create_cmd import add_worktree, run_post_worktree_setup
from erk.cli.config import LoadedConfig
from erk.cli.core import discover_repo_context, worktree_path_for
from erk.core.claude_executor import ClaudeExecutor
from erk.core.context import ErkContext
from erk.core.project_discovery import ProjectContext, discover_project
from erk.core.repo_discovery import ensure_erk_metadata_dir
from erk.core.worktree_utils import compute_relative_path_in_worktree


def _determine_base_branch(ctx: ErkContext, repo_root: Path) -> str:
    """Determine the base branch for new worktree creation.

    When Graphite is enabled and the user is on a non-trunk branch,
    stack on the current branch. Otherwise, use trunk.

    Args:
        ctx: Erk context
        repo_root: Repository root path

    Returns:
        Base branch name to use as ref for worktree creation
    """
    trunk_branch = ctx.git.detect_trunk_branch(repo_root)
    use_graphite = ctx.global_config.use_graphite if ctx.global_config else False

    if not use_graphite:
        return trunk_branch

    current_branch = ctx.git.get_current_branch(ctx.cwd)
    if current_branch and current_branch != trunk_branch:
        return current_branch

    return trunk_branch


def _build_claude_command(slash_command: str, dangerous: bool) -> str:
    """Build a Claude CLI invocation for interactive mode.

    Args:
        slash_command: The slash command to execute (e.g., "/erk:plan-implement")
        dangerous: Whether to skip permission prompts

    Returns:
        Complete Claude CLI command string
    """
    cmd = "claude --permission-mode acceptEdits"
    if dangerous:
        cmd += " --dangerously-skip-permissions"
    cmd += f' "{slash_command}"'
    return cmd


def _validate_flags(submit: bool, no_interactive: bool, script: bool) -> None:
    """Validate flag combinations and raise ClickException if invalid.

    Args:
        submit: Whether to auto-submit PR after implementation
        no_interactive: Whether to execute non-interactively
        script: Whether to output shell integration script

    Raises:
        click.ClickException: If flag combination is invalid
    """
    # --submit requires --no-interactive UNLESS using --script mode
    # Script mode generates shell code, so --submit is allowed
    if submit and not no_interactive and not script:
        raise click.ClickException(
            "--submit requires --no-interactive\n"
            "Automated workflows must run non-interactively\n"
            "(or use --script to generate shell integration code)"
        )

    if no_interactive and script:
        raise click.ClickException(
            "--no-interactive and --script are mutually exclusive\n"
            "--script generates shell integration code for manual execution\n"
            "--no-interactive executes commands programmatically"
        )


def _build_command_sequence(submit: bool) -> list[str]:
    """Build list of slash commands to execute.

    Args:
        submit: Whether to include full CI/PR workflow

    Returns:
        List of slash commands to execute in sequence
    """
    commands = ["/erk:plan-implement"]
    if submit:
        commands.extend(["/fast-ci", "/gt:pr-submit"])
    return commands


def _build_claude_args(slash_command: str, dangerous: bool) -> list[str]:
    """Build Claude command argument list for interactive script mode.

    Args:
        slash_command: The slash command to execute
        dangerous: Whether to skip permission prompts

    Returns:
        List of command arguments suitable for subprocess
    """
    args = ["claude", "--permission-mode", "acceptEdits"]
    if dangerous:
        args.append("--dangerously-skip-permissions")
    args.append(slash_command)
    return args


def _execute_interactive_mode(
    ctx: ErkContext,
    repo_root: Path,
    worktree_path: Path,
    dangerous: bool,
    executor: ClaudeExecutor,
) -> None:
    """Execute implementation in interactive mode using executor.

    Args:
        ctx: Erk context for accessing git and current working directory
        repo_root: Path to repository root for listing worktrees
        worktree_path: Path to worktree directory
        dangerous: Whether to skip permission prompts
        executor: Claude CLI executor for process replacement

    Raises:
        click.ClickException: If Claude CLI not found

    Note:
        This function never returns in production - the process is replaced by Claude
    """
    click.echo("Entering interactive implementation mode...", err=True)
    try:
        executor.execute_interactive(
            worktree_path,
            dangerous,
            "/erk:plan-implement",
            compute_relative_path_in_worktree(ctx.git.list_worktrees(repo_root), ctx.cwd),
        )
    except RuntimeError as e:
        raise click.ClickException(str(e)) from e


def _execute_non_interactive_mode(
    worktree_path: Path,
    commands: list[str],
    dangerous: bool,
    verbose: bool,
    executor: ClaudeExecutor,
) -> None:
    """Execute commands via Claude CLI executor with rich output formatting.

    Args:
        worktree_path: Path to worktree directory
        commands: List of slash commands to execute
        dangerous: Whether to skip permission prompts
        verbose: Whether to show raw output (True) or filtered output (False)
        executor: Claude CLI executor for command execution

    Raises:
        click.ClickException: If Claude CLI not found or command fails
    """
    import time

    from rich.console import Console

    from erk.cli.output import format_implement_summary, stream_command_with_feedback
    from erk.core.claude_executor import CommandResult

    # Verify Claude is available
    if not executor.is_claude_available():
        raise click.ClickException(
            "Claude CLI not found\nInstall from: https://claude.com/download"
        )

    console = Console()
    total_start = time.time()
    all_results: list[CommandResult] = []

    for cmd in commands:
        if verbose:
            # Verbose mode - simple output, no spinner
            click.echo(f"Running {cmd}...", err=True)
            result = executor.execute_command(cmd, worktree_path, dangerous, verbose=True)
        else:
            # Filtered mode - streaming with live print-based feedback
            result = stream_command_with_feedback(
                executor=executor,
                command=cmd,
                worktree_path=worktree_path,
                dangerous=dangerous,
                console=console,
            )

        all_results.append(result)

        # Stop on first failure
        if not result.success:
            break

    # Show final summary (unless verbose mode)
    if not verbose:
        total_duration = time.time() - total_start
        summary = format_implement_summary(all_results, total_duration)
        console.print(summary)

    # Raise exception if any command failed
    if not all(r.success for r in all_results):
        raise click.ClickException("One or more commands failed")


def _build_activation_script_with_commands(
    worktree_path: Path, commands: list[str], dangerous: bool
) -> str:
    """Build activation script with Claude commands.

    Args:
        worktree_path: Path to worktree
        commands: List of slash commands to include
        dangerous: Whether to skip permission prompts

    Returns:
        Complete activation script with commands
    """
    # Get base activation script (cd + venv + env)
    script = render_activation_script(
        worktree_path=worktree_path,
        final_message="",  # We'll add commands instead
        comment="implement activation",
    )

    # Add Claude commands
    shell_commands = []
    for cmd in commands:
        cmd_args = _build_claude_args(cmd, dangerous)
        # Build shell command string
        shell_cmd = " ".join(shlex.quote(arg) for arg in cmd_args)
        shell_commands.append(shell_cmd)

    # Chain commands with && so they only run if previous command succeeded
    script += " && \\\n".join(shell_commands) + "\n"

    return script


class TargetInfo(NamedTuple):
    """Information about detected target type.

    Attributes:
        target_type: Type of target - "issue_number", "issue_url", or "file_path"
        issue_number: Extracted issue number for GitHub targets, None for file paths
    """

    target_type: str
    issue_number: str | None


@dataclass(frozen=True)
class PlanSource:
    """Source information for creating a worktree with plan.

    Attributes:
        plan_content: The plan content as a string
        base_name: Base name for generating worktree name
        dry_run_description: Description to show in dry-run mode
    """

    plan_content: str
    base_name: str
    dry_run_description: str


@dataclass(frozen=True)
class ForceDeleteOptions:
    """Options for force-deleting existing worktree/branch.

    Attributes:
        worktree: Whether to delete existing worktree with same name
        branch: Whether to delete existing branch with same name
    """

    worktree: bool = False
    branch: bool = False

    @classmethod
    def from_force_flag(cls, force: bool) -> "ForceDeleteOptions":
        """Create options from --force flag value."""
        return cls(worktree=force, branch=force)

    @property
    def any_enabled(self) -> bool:
        """Return True if any force delete option is enabled."""
        return self.worktree or self.branch


@dataclass(frozen=True)
class WorktreeCreationResult:
    """Result of creating a worktree with plan content.

    Attributes:
        worktree_path: Path to the created worktree root
        impl_dir: Path to the .impl/ directory (may be inside a project subdirectory)
    """

    worktree_path: Path
    impl_dir: Path


def get_impl_location(worktree_path: Path, project: ProjectContext | None) -> Path:
    """Determine where to create .impl/ folder based on project context.

    In monorepos, .impl/ should be placed at the project root (where .erk/project.toml
    lives) rather than the worktree root. This ensures Claude starts in the correct
    context for the project.

    Args:
        worktree_path: Path to the worktree root
        project: Project context if running from within a project, None otherwise

    Returns:
        Path where .impl/ folder should be created (NOT including the .impl/ suffix)
    """
    if project is not None:
        return worktree_path / project.path_from_repo
    return worktree_path


def _detect_target_type(target: str) -> TargetInfo:
    """Detect whether target is an issue number, issue URL, or file path.

    Args:
        target: User-provided target argument

    Returns:
        TargetInfo with target type and extracted issue number (if applicable)
    """
    # Check if starts with # followed by digits (issue number)
    if target.startswith("#") and target[1:].isdigit():
        return TargetInfo(target_type="issue_number", issue_number=target[1:])

    # Check if GitHub issue URL
    github_issue_pattern = r"github\.com/[^/]+/[^/]+/issues/(\d+)"
    match = re.search(github_issue_pattern, target)
    if match:
        issue_number = match.group(1)
        return TargetInfo(target_type="issue_url", issue_number=issue_number)

    # Check if plain digits (issue number without # prefix)
    if target.isdigit():
        return TargetInfo(target_type="issue_number", issue_number=target)

    # Otherwise, treat as file path
    return TargetInfo(target_type="file_path", issue_number=None)


@dataclass(frozen=True)
class IssuePlanSource:
    """Extended plan source with issue-specific metadata.

    Attributes:
        plan_source: The base PlanSource with content and metadata
        branch_name: The development branch name for this issue
        already_existed: Whether the branch already existed
    """

    plan_source: PlanSource
    branch_name: str
    already_existed: bool


def _prepare_plan_source_from_issue(
    ctx: ErkContext, repo_root: Path, issue_number: str, base_branch: str
) -> IssuePlanSource:
    """Prepare plan source from GitHub issue.

    Creates a branch for the issue and fetches plan content.

    Args:
        ctx: Erk context
        repo_root: Repository root path
        issue_number: GitHub issue number
        base_branch: Base branch for creating the development branch

    Returns:
        IssuePlanSource with plan content, metadata, and branch name

    Raises:
        SystemExit: If issue not found or doesn't have erk-plan label
    """
    # Output fetching diagnostic
    ctx.feedback.info("Fetching issue from GitHub...")

    # Fetch plan from GitHub
    try:
        plan = ctx.plan_store.get_plan(repo_root, issue_number)
    except RuntimeError as e:
        ctx.feedback.error(f"Error: {e}")
        raise SystemExit(1) from e

    # Output issue title
    ctx.feedback.info(f"Issue: {plan.title}")

    # Validate issue has erk-plan label
    if "erk-plan" not in plan.labels:
        user_output(
            click.style("Error: ", fg="red")
            + f"Issue #{issue_number} does not have the 'erk-plan' label.\n"
            + "Only issues tagged with 'erk-plan' can be used for implementation.\n\n"
            + f"To add the label, visit: {plan.url}"
        )
        raise SystemExit(1)

    # Validate issue is open
    if plan.state != PlanState.OPEN:
        user_output(
            click.style("Warning: ", fg="yellow")
            + f"Issue #{issue_number} is {plan.state.value}. "
            + "Proceeding anyway..."
        )

    # Create branch name: P prefix + issue number + sanitized title + timestamp
    # Apply P prefix AFTER sanitization since sanitize_worktree_name lowercases input
    # Truncate total to 31 chars before adding timestamp suffix
    prefix = f"P{issue_number}-"
    sanitized_title = sanitize_worktree_name(plan.title)
    base_branch_name = (prefix + sanitized_title)[:31].rstrip("-")
    timestamp_suffix = format_branch_timestamp_suffix(ctx.time.now())
    branch_name = base_branch_name + timestamp_suffix

    # Create branch directly via git
    ctx.git.create_branch(repo_root, branch_name, base_branch)
    ctx.feedback.info(f"Created branch: {branch_name}")

    # Use the branch name as the base name for the worktree
    base_name = sanitize_worktree_name(branch_name)

    dry_run_desc = f"Would create worktree from issue #{issue_number}\n  Title: {plan.title}"

    plan_source = PlanSource(
        plan_content=plan.body,
        base_name=base_name,
        dry_run_description=dry_run_desc,
    )

    return IssuePlanSource(
        plan_source=plan_source,
        branch_name=branch_name,
        already_existed=False,  # Always new branch since we create it directly
    )


def _prepare_plan_source_from_file(ctx: ErkContext, plan_file: Path) -> PlanSource:
    """Prepare plan source from file.

    Args:
        ctx: Erk context
        plan_file: Path to plan file

    Returns:
        PlanSource with plan content and metadata

    Raises:
        SystemExit: If plan file doesn't exist
    """
    # Validate plan file exists
    if not plan_file.exists():
        ctx.feedback.error(f"Error: Plan file not found: {plan_file}")
        raise SystemExit(1)

    # Output reading diagnostic
    ctx.feedback.info("Reading plan file...")

    # Read plan content
    plan_content = plan_file.read_text(encoding="utf-8")

    # Extract title from plan content for display
    title = plan_file.stem
    for line in plan_content.split("\n"):
        stripped = line.strip()
        if stripped.startswith("#"):
            # Extract title from first heading
            title = stripped.lstrip("#").strip()
            break

    # Output plan title
    ctx.feedback.info(f"Plan: {title}")

    # Derive base name from filename
    plan_stem = plan_file.stem
    cleaned_stem = strip_plan_from_filename(plan_stem)
    base_name = sanitize_worktree_name(cleaned_stem)

    dry_run_desc = (
        f"Would create worktree from plan file: {plan_file}\n"
        f"  Plan file would be deleted: {plan_file}"
    )

    return PlanSource(
        plan_content=plan_content,
        base_name=base_name,
        dry_run_description=dry_run_desc,
    )


def _show_conflict_error(
    ctx: ErkContext,
    *,
    branch: str,
    wt_path: Path,
    wt_exists: bool,
    branch_exists: bool,
    worktree_name_provided: bool,
) -> None:
    """Show error message when branch or worktree already exists.

    Args:
        ctx: Erk context
        branch: Branch name that conflicts
        wt_path: Worktree path that conflicts
        wt_exists: Whether worktree directory exists
        branch_exists: Whether branch exists
        worktree_name_provided: Whether user explicitly provided --worktree-name

    Raises:
        SystemExit: Always exits with code 1
    """
    # Build error message based on what exists
    if wt_exists and branch_exists:
        conflict_msg = f"Branch '{branch}' and worktree '{wt_path.name}' already exist."
    elif branch_exists:
        conflict_msg = f"Branch '{branch}' already exists."
    else:
        conflict_msg = f"Worktree '{wt_path.name}' already exists."

    # Suggest using --force or --worktree-name
    if worktree_name_provided:
        suggestion = "Use -f to delete the existing resources, or choose a different name."
    else:
        suggestion = (
            "Use -f to delete the existing resources, "
            "or --worktree-name to choose a different name."
        )

    ctx.feedback.error(f"Error: {conflict_msg}\n{suggestion}")
    raise SystemExit(1)


def _handle_force_delete(
    ctx: ErkContext,
    *,
    repo_root: Path,
    wt_path: Path,
    branch: str,
    wt_exists: bool,
    branch_exists: bool,
    force_delete: ForceDeleteOptions,
    dry_run: bool,
) -> None:
    """Handle --force flag by prompting for confirmation and deleting existing resources.

    Args:
        ctx: Erk context
        repo_root: Repository root path
        wt_path: Path to existing worktree
        branch: Branch name
        wt_exists: Whether worktree directory exists
        branch_exists: Whether branch exists
        force_delete: Options for which resources to force-delete
        dry_run: Whether in dry-run mode

    Raises:
        SystemExit: If user declines confirmation
    """
    # Build list of what will be deleted
    deletions: list[str] = []
    if wt_exists and force_delete.worktree:
        deletions.append(f"worktree: {click.style(str(wt_path), fg='cyan')}")
    if branch_exists and force_delete.branch:
        deletions.append(f"branch: {click.style(branch, fg='yellow')}")

    if not deletions:
        return

    # Display what will be deleted
    user_output(click.style("⚠️  The following will be deleted:", fg="yellow", bold=True))
    for item in deletions:
        user_output(f"  • {item}")

    # Prompt for confirmation
    if not dry_run:
        prompt_text = click.style("Proceed with deletion?", fg="yellow", bold=True)
        if not click.confirm(f"\n{prompt_text}", default=False, err=True):
            user_output(click.style("⭕ Aborted.", fg="red", bold=True))
            raise SystemExit(1)

    # Perform deletions
    if wt_exists and force_delete.worktree:
        if dry_run:
            user_output(f"[DRY RUN] Would delete worktree: {wt_path}")
        else:
            ctx.feedback.info(f"Deleting existing worktree: {wt_path.name}...")
            _delete_worktree_for_force(ctx, repo_root, wt_path)
            ctx.feedback.success(f"✓ Deleted worktree: {wt_path.name}")

    if branch_exists and force_delete.branch:
        if dry_run:
            user_output(f"[DRY RUN] Would delete branch: {branch}")
        else:
            ctx.feedback.info(f"Deleting existing branch: {branch}...")
            _delete_branch_for_force(ctx, repo_root, branch)
            ctx.feedback.success(f"✓ Deleted branch: {branch}")


def _delete_worktree_for_force(ctx: ErkContext, repo_root: Path, wt_path: Path) -> None:
    """Delete a worktree directory for --force flag.

    This reuses patterns from wt/delete_cmd.py for worktree cleanup.
    """
    # Try git worktree remove first
    ctx.git.remove_worktree(repo_root, wt_path, force=True)

    # Manually delete directory if still exists (e.g., if git worktree remove didn't fully clean up)
    if ctx.git.path_exists(wt_path):
        shutil.rmtree(wt_path)

    # Prune worktree metadata
    ctx.git.prune_worktrees(repo_root)


def _delete_branch_for_force(ctx: ErkContext, repo_root: Path, branch: str) -> None:
    """Delete a branch for --force flag.

    Uses force delete (-D) since we've already confirmed with user.
    """
    use_graphite = ctx.global_config.use_graphite if ctx.global_config else False

    if use_graphite:
        ctx.git.delete_branch_with_graphite(repo_root, branch, force=True)
    else:
        ctx.git.delete_branch(repo_root, branch, force=True)


def _create_worktree_with_plan_content(
    ctx: ErkContext,
    *,
    plan_source: PlanSource,
    worktree_name: str | None,
    dry_run: bool,
    submit: bool,
    dangerous: bool,
    no_interactive: bool,
    linked_branch_name: str | None = None,
    base_branch: str,
    force_delete: ForceDeleteOptions | None = None,
) -> WorktreeCreationResult | None:
    """Create worktree with plan content.

    Args:
        ctx: Erk context
        plan_source: Plan source with content and metadata
        worktree_name: Optional custom worktree name
        dry_run: Whether to perform dry run
        submit: Whether to auto-submit PR after implementation
        dangerous: Whether to skip permission prompts
        no_interactive: Whether to execute non-interactively
        linked_branch_name: Optional branch name for issue-based worktrees
                           (when provided, use this branch instead of creating new)
        base_branch: Base branch to use as ref for worktree creation
        force_delete: Options for force-deleting existing worktree/branch

    Returns:
        WorktreeCreationResult with paths, or None if dry-run mode
    """
    if force_delete is None:
        force_delete = ForceDeleteOptions()
    # Discover repository context
    repo = discover_repo_context(ctx, ctx.cwd)
    ensure_erk_metadata_dir(repo)
    repo_root = repo.root

    # Detect project context from source directory (for monorepo support)
    # If running from within a project subdirectory, we'll create .impl/ there
    project: ProjectContext | None = discover_project(ctx.cwd, repo_root, ctx.git)

    # Determine branch name and worktree name
    # - linked_branch_name: use the issue-linked branch for the worktree
    # - worktree_name: user override for worktree directory name (not branch)
    # - base_name: fallback derived from plan file or issue title
    if linked_branch_name:
        # For issue mode: always use the branch created for this issue
        branch = linked_branch_name
        # But allow --worktree-name to override the directory name
        if worktree_name:
            name = sanitize_worktree_name(worktree_name)
        else:
            name = sanitize_worktree_name(linked_branch_name)
    elif worktree_name:
        name = sanitize_worktree_name(worktree_name)
        branch = name
    else:
        name = ensure_unique_worktree_name_with_date(
            plan_source.base_name, repo.worktrees_dir, ctx.git
        )
        branch = name

    # Calculate worktree path
    wt_path = worktree_path_for(repo.worktrees_dir, name)

    # For linked branches, we use the existing branch; for others, validate it doesn't exist
    use_existing_branch = linked_branch_name is not None

    if not use_existing_branch:
        # Check for existing worktree and branch
        local_branches = ctx.git.list_local_branches(repo_root)
        branch_exists = branch in local_branches
        wt_exists = ctx.git.path_exists(wt_path)

        # Handle conflicts with --force flag
        if branch_exists or wt_exists:
            if force_delete.any_enabled:
                # Prompt for confirmation and delete what exists
                _handle_force_delete(
                    ctx,
                    repo_root=repo_root,
                    wt_path=wt_path,
                    branch=branch,
                    wt_exists=wt_exists,
                    branch_exists=branch_exists,
                    force_delete=force_delete,
                    dry_run=dry_run,
                )
            else:
                # No --force flag - show error with suggestion
                _show_conflict_error(
                    ctx,
                    branch=branch,
                    wt_path=wt_path,
                    wt_exists=wt_exists,
                    branch_exists=branch_exists,
                    worktree_name_provided=worktree_name is not None,
                )

    # Handle dry-run mode
    if dry_run:
        dry_run_header = click.style("Dry-run mode:", fg="cyan", bold=True)
        user_output(dry_run_header + " No changes will be made\n")

        # Show execution mode
        mode = "non-interactive" if no_interactive else "interactive"
        user_output(f"Execution mode: {mode}\n")

        user_output(f"Would create worktree '{name}'")
        user_output(f"  {plan_source.dry_run_description}")

        # Show command sequence
        commands = _build_command_sequence(submit)
        user_output("\nCommand sequence:")
        for i, cmd in enumerate(commands, 1):
            cmd_args = _build_claude_args(cmd, dangerous)
            user_output(f"  {i}. {' '.join(cmd_args)}")

        return None

    # Create worktree
    ctx.feedback.info(f"Creating worktree '{name}'...")

    # Load local config
    config = (
        ctx.local_config
        if ctx.local_config is not None
        else LoadedConfig(env={}, post_create_commands=[], post_create_shell=None)
    )

    # Output worktree creation diagnostic
    if use_existing_branch:
        ctx.feedback.info(f"Using branch '{branch}'...")
    else:
        ctx.feedback.info(f"Creating branch '{branch}' from {base_branch}...")

    # Respect global use_graphite config (matching erk create behavior)
    use_graphite = ctx.global_config.use_graphite if ctx.global_config else False

    # Create worktree
    add_worktree(
        ctx,
        repo_root,
        wt_path,
        branch=branch,
        ref=base_branch,
        use_existing_branch=use_existing_branch,
        use_graphite=use_graphite,
        skip_remote_check=True,
    )

    ctx.feedback.success(f"✓ Created worktree: {name}")

    # Run post-worktree setup
    run_post_worktree_setup(ctx, config, wt_path, repo_root, name)

    # Create .impl/ folder with plan content
    # Use overwrite=True since new worktrees created from branches with existing
    # .impl/ folders inherit that folder, and we want to replace it with the new plan
    impl_location = get_impl_location(wt_path, project)

    ctx.feedback.info("Creating .impl/ folder with plan...")
    create_impl_folder(
        worktree_path=impl_location,
        plan_content=plan_source.plan_content,
        overwrite=True,
    )
    ctx.feedback.success("✓ Created .impl/ folder")

    return WorktreeCreationResult(
        worktree_path=wt_path,
        impl_dir=impl_location / ".impl",
    )


def _output_activation_instructions(
    ctx: ErkContext,
    *,
    wt_path: Path,
    branch: str,
    script: bool,
    submit: bool,
    dangerous: bool,
    target_description: str,
) -> None:
    """Output activation script or manual instructions.

    This is only called when in script mode (for manual shell integration).
    Interactive and non-interactive modes handle execution directly.

    Args:
        ctx: Erk context
        wt_path: Worktree path
        branch: Branch name
        script: Whether to output activation script
        submit: Whether to auto-submit PR after implementation
        dangerous: Whether to skip permission prompts
        target_description: Description of target for user messages
    """
    if script:
        # Build command sequence
        commands = _build_command_sequence(submit)

        # Generate activation script with commands
        full_script = _build_activation_script_with_commands(wt_path, commands, dangerous)

        comment_suffix = "implement, CI, and submit" if submit else "implement"
        result = ctx.script_writer.write_activation_script(
            full_script,
            command_name="implement",
            comment=f"activate {wt_path.name} and {comment_suffix}",
        )

        result.output_for_shell_integration()
    else:
        # Provide manual instructions
        user_output("\n" + click.style("Next steps:", fg="cyan", bold=True))
        user_output(f"  1. Change to worktree:  erk checkout {branch}")
        if submit:
            user_output("  2. Run implementation, CI, and submit PR:")
            user_output(f"     {_build_claude_command('/erk:plan-implement', dangerous)}")
            user_output(f"     {_build_claude_command('/fast-ci', dangerous)}")
            user_output(f"     {_build_claude_command('/gt:pr-submit', dangerous)}")
        else:
            claude_cmd = _build_claude_command("/erk:plan-implement", dangerous)
            user_output(f"  2. Run implementation:  {claude_cmd}")
        user_output("\n" + click.style("Shell integration not detected.", fg="yellow"))
        user_output("To activate environment and run commands, use:")
        script_flag = "--submit --script" if submit else "--script"
        user_output(f"  source <(erk implement {target_description} {script_flag})")


def _implement_from_issue(
    ctx: ErkContext,
    *,
    issue_number: str,
    worktree_name: str | None,
    dry_run: bool,
    submit: bool,
    dangerous: bool,
    script: bool,
    no_interactive: bool,
    verbose: bool,
    executor: ClaudeExecutor,
) -> None:
    """Implement feature from GitHub issue.

    Args:
        ctx: Erk context
        issue_number: GitHub issue number
        worktree_name: Optional custom worktree name
        dry_run: Whether to perform dry run
        submit: Whether to auto-submit PR after implementation
        dangerous: Whether to skip permission prompts
        script: Whether to output activation script
        no_interactive: Whether to execute non-interactively
        verbose: Whether to show raw output or filtered output
        executor: Claude CLI executor for command execution
    """
    # Discover repo context for issue fetch
    repo = discover_repo_context(ctx, ctx.cwd)
    ensure_erk_metadata_dir(repo)

    # Determine base branch (respects worktree stacking)
    base_branch = _determine_base_branch(ctx, repo.root)

    # Prepare plan source from issue (creates branch via git)
    issue_plan_source = _prepare_plan_source_from_issue(
        ctx, repo.root, issue_number, base_branch=base_branch
    )

    # Create worktree with plan content, using the branch name
    result = _create_worktree_with_plan_content(
        ctx,
        plan_source=issue_plan_source.plan_source,
        worktree_name=worktree_name,
        dry_run=dry_run,
        submit=submit,
        dangerous=dangerous,
        no_interactive=no_interactive,
        linked_branch_name=issue_plan_source.branch_name,
        base_branch=base_branch,
    )

    # Early return for dry-run mode
    if result is None:
        return

    wt_path = result.worktree_path

    # Save issue reference for PR linking (issue-specific)
    # Use impl_dir from result to handle monorepo project-root placement
    ctx.feedback.info("Saving issue reference for PR linking...")
    plan = ctx.plan_store.get_plan(repo.root, issue_number)
    save_issue_reference(result.impl_dir, int(issue_number), plan.url)

    ctx.feedback.success(f"✓ Saved issue reference: {plan.url}")

    # Execute based on mode
    if script:
        # Script mode - output activation script
        branch = wt_path.name
        target_description = f"#{issue_number}"
        _output_activation_instructions(
            ctx,
            wt_path=wt_path,
            branch=branch,
            script=script,
            submit=submit,
            dangerous=dangerous,
            target_description=target_description,
        )
    elif no_interactive:
        # Non-interactive mode - execute via subprocess
        commands = _build_command_sequence(submit)
        _execute_non_interactive_mode(wt_path, commands, dangerous, verbose, executor)
    else:
        # Interactive mode - hand off to Claude (never returns)
        _execute_interactive_mode(ctx, repo.root, wt_path, dangerous, executor)


def _implement_from_file(
    ctx: ErkContext,
    *,
    plan_file: Path,
    worktree_name: str | None,
    dry_run: bool,
    submit: bool,
    dangerous: bool,
    script: bool,
    no_interactive: bool,
    verbose: bool,
    force_delete: ForceDeleteOptions,
    executor: ClaudeExecutor,
) -> None:
    """Implement feature from plan file.

    Args:
        ctx: Erk context
        plan_file: Path to plan file
        worktree_name: Optional custom worktree name
        dry_run: Whether to perform dry run
        submit: Whether to auto-submit PR after implementation
        dangerous: Whether to skip permission prompts
        script: Whether to output activation script
        no_interactive: Whether to execute non-interactively
        verbose: Whether to show raw output or filtered output
        force_delete: Options for force-deleting existing worktree/branch
        executor: Claude CLI executor for command execution
    """
    # Discover repo context
    repo = discover_repo_context(ctx, ctx.cwd)

    # Determine base branch (respects worktree stacking)
    base_branch = _determine_base_branch(ctx, repo.root)

    # Prepare plan source from file
    plan_source = _prepare_plan_source_from_file(ctx, plan_file)

    # Create worktree with plan content
    result = _create_worktree_with_plan_content(
        ctx,
        plan_source=plan_source,
        worktree_name=worktree_name,
        dry_run=dry_run,
        submit=submit,
        dangerous=dangerous,
        no_interactive=no_interactive,
        base_branch=base_branch,
        force_delete=force_delete,
    )

    # Early return for dry-run mode
    if result is None:
        return

    wt_path = result.worktree_path

    # Delete original plan file (move semantics, file-specific)
    ctx.feedback.info(f"Removing original plan file: {plan_file.name}...")
    plan_file.unlink()

    ctx.feedback.success("✓ Moved plan file to worktree")

    # Execute based on mode
    if script:
        # Script mode - output activation script
        branch = wt_path.name
        target_description = str(plan_file)
        _output_activation_instructions(
            ctx,
            wt_path=wt_path,
            branch=branch,
            script=script,
            submit=submit,
            dangerous=dangerous,
            target_description=target_description,
        )
    elif no_interactive:
        # Non-interactive mode - execute via subprocess
        commands = _build_command_sequence(submit)
        _execute_non_interactive_mode(wt_path, commands, dangerous, verbose, executor)
    else:
        # Interactive mode - hand off to Claude (never returns)
        _execute_interactive_mode(ctx, repo.root, wt_path, dangerous, executor)


@click.command("implement")
@click.argument("target", shell_complete=complete_plan_files)
@click.option(
    "--worktree-name",
    type=str,
    default=None,
    help="Override worktree name (optional, auto-generated if not provided)",
)
@click.option(
    "--dry-run",
    is_flag=True,
    help="Print what would be executed without doing it",
)
@click.option(
    "--submit",
    is_flag=True,
    help="Automatically run CI validation and submit PR after implementation",
)
@click.option(
    "--dangerous",
    is_flag=True,
    default=False,
    help="Skip permission prompts by passing --dangerously-skip-permissions to Claude",
)
@click.option(
    "--no-interactive",
    is_flag=True,
    default=False,
    help="Execute commands via subprocess without user interaction",
)
@click.option(
    "--script",
    is_flag=True,
    hidden=True,
    help="Output activation script for shell integration",
)
@click.option(
    "--yolo",
    is_flag=True,
    default=False,
    help="Equivalent to --dangerous --submit --no-interactive (full automation)",
)
@click.option(
    "--verbose",
    is_flag=True,
    default=False,
    help="Show full Claude Code output (default: filtered)",
)
@click.option(
    "-f",
    "--force",
    is_flag=True,
    default=False,
    help="Delete existing branch/worktree with same name after confirmation.",
)
@click.pass_obj
def implement(
    ctx: ErkContext,
    target: str,
    worktree_name: str | None,
    dry_run: bool,
    submit: bool,
    dangerous: bool,
    no_interactive: bool,
    script: bool,
    yolo: bool,
    verbose: bool,
    force: bool,
) -> None:
    """Create worktree from GitHub issue or plan file and execute implementation.

    By default, runs in interactive mode where you can interact with Claude
    during implementation. Use --no-interactive for automated execution.

    TARGET can be:
    - GitHub issue number (e.g., #123 or 123)
    - GitHub issue URL (e.g., https://github.com/user/repo/issues/123)
    - Path to plan file (e.g., ./my-feature-plan.md)

    Note: Plain numbers (e.g., 809) are always interpreted as GitHub issues.
          For files with numeric names, use ./ prefix (e.g., ./809).

    For GitHub issues, the issue must have the 'erk-plan' label.

    Examples:

    \b
      # Interactive mode (default)
      erk implement 123

    \b
      # Interactive mode, skip permissions
      erk implement 123 --dangerous

    \b
      # Non-interactive mode (automated execution)
      erk implement 123 --no-interactive

    \b
      # Full CI/PR workflow (requires --no-interactive)
      erk implement 123 --no-interactive --submit

    \b
      # YOLO mode - full automation (dangerous + submit + no-interactive)
      erk implement 123 --yolo

    \b
      # Shell integration
      source <(erk implement 123 --script)

    \b
      # From plan file
      erk implement ./my-feature-plan.md
    """
    # Handle --yolo flag (shorthand for dangerous + submit + no-interactive)
    if yolo:
        dangerous = True
        submit = True
        no_interactive = True

    # Validate flag combinations
    _validate_flags(submit, no_interactive, script)

    # Create force delete options from --force flag (set at entry point for clarity)
    force_delete = ForceDeleteOptions.from_force_flag(force)

    # Detect target type
    target_info = _detect_target_type(target)

    # Output target detection diagnostic
    if target_info.target_type in ("issue_number", "issue_url"):
        ctx.feedback.info(f"Detected GitHub issue #{target_info.issue_number}")
    elif target_info.target_type == "file_path":
        ctx.feedback.info(f"Detected plan file: {target}")

    if target_info.target_type in ("issue_number", "issue_url"):
        # GitHub issue mode
        if target_info.issue_number is None:
            user_output(
                click.style("Error: ", fg="red") + "Failed to extract issue number from target"
            )
            raise SystemExit(1)

        _implement_from_issue(
            ctx,
            issue_number=target_info.issue_number,
            worktree_name=worktree_name,
            dry_run=dry_run,
            submit=submit,
            dangerous=dangerous,
            script=script,
            no_interactive=no_interactive,
            verbose=verbose,
            executor=ctx.claude_executor,
        )
    else:
        # Plan file mode
        plan_file = Path(target)
        _implement_from_file(
            ctx,
            plan_file=plan_file,
            worktree_name=worktree_name,
            dry_run=dry_run,
            submit=submit,
            dangerous=dangerous,
            script=script,
            no_interactive=no_interactive,
            verbose=verbose,
            force_delete=force_delete,
            executor=ctx.claude_executor,
        )
