//! Worktree manager implementation.

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;

use tokio::process::Command;
use tokio::sync::Mutex;
use uuid::Uuid;

/// Errors that can occur during worktree operations.
#[derive(Debug, thiserror::Error)]
pub enum WorktreeError {
    #[error("Git command failed: {0}")]
    GitCommand(String),

    #[error("Repository not found: {0}")]
    RepoNotFound(String),

    #[error("Failed to create directory: {0}")]
    CreateDir(#[from] std::io::Error),

    #[error("Invalid repository path: {0}")]
    InvalidPath(String),

    #[error("Worktree already exists: {0}")]
    AlreadyExists(String),

    #[error("Clone failed: {0}")]
    CloneFailed(String),

    #[error("Merge in progress")]
    MergeInProgress,

    #[error("No merge in progress")]
    NoMergeInProgress,

    #[error("Merge has conflicts: {0}")]
    MergeConflicts(String),
}

/// Strategy for resolving a merge conflict.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConflictResolution {
    /// Use our version (the branch being merged into).
    Ours,
    /// Use their version (the branch being merged).
    Theirs,
}

/// State of an in-progress merge.
#[derive(Debug, Clone)]
pub struct MergeState {
    /// The branch being merged.
    pub source_branch: String,
    /// Files with unresolved conflicts.
    pub conflicted_files: Vec<String>,
    /// Whether a merge is currently in progress.
    pub in_progress: bool,
}

/// Information about a task branch.
#[derive(Debug, Clone)]
pub struct TaskBranchInfo {
    /// Full branch name.
    pub name: String,
    /// Task ID extracted from branch name (if parseable).
    pub task_id: Option<Uuid>,
    /// Whether this branch has been merged into the current branch.
    pub is_merged: bool,
    /// Short SHA of the last commit.
    pub last_commit: String,
    /// Subject line of the last commit.
    pub last_commit_message: String,
}

/// Information about a created worktree.
#[derive(Debug, Clone)]
pub struct WorktreeInfo {
    /// Path to the worktree directory.
    pub path: PathBuf,
    /// Git branch name for this worktree.
    pub branch: String,
    /// Source repository path.
    pub source_repo: PathBuf,
}

/// Manages git worktrees for task isolation.
pub struct WorktreeManager {
    /// Base directory for all worktrees (~/.makima/worktrees).
    base_dir: PathBuf,
    /// Base directory for cloned repos (~/.makima/repos).
    repos_dir: PathBuf,
    /// Branch prefix for task branches.
    branch_prefix: String,
}

/// Per-worktree locks to prevent concurrent creation issues.
static WORKTREE_LOCKS: LazyLock<Mutex<HashMap<String, std::sync::Arc<tokio::sync::Mutex<()>>>>> =
    LazyLock::new(|| Mutex::new(HashMap::new()));

impl WorktreeManager {
    /// Create a new WorktreeManager with the given base directory.
    pub fn new(base_dir: PathBuf) -> Self {
        let repos_dir = base_dir.parent()
            .map(|p| p.join("repos"))
            .unwrap_or_else(|| base_dir.join("repos"));

        Self {
            base_dir,
            repos_dir,
            branch_prefix: "makima/task-".to_string(),
        }
    }

    /// Get the default worktree base directory (~/.makima/worktrees).
    pub fn default_base_dir() -> PathBuf {
        dirs::home_dir()
            .unwrap_or_else(|| PathBuf::from("."))
            .join(".makima")
            .join("worktrees")
    }

    /// Get the base directory for worktrees.
    pub fn base_dir(&self) -> &Path {
        &self.base_dir
    }

    /// Detect the default branch of a repository.
    /// Tries to find HEAD's target, falling back to common branch names.
    /// Works for both regular and bare repositories.
    pub async fn detect_default_branch(&self, repo_path: &Path) -> Result<String, WorktreeError> {
        tracing::debug!("Detecting default branch for repo: {}", repo_path.display());

        // First, try to read HEAD directly (works for bare repos)
        // In bare repos, HEAD is a symbolic ref to the default branch
        let output = Command::new("git")
            .args(["symbolic-ref", "HEAD", "--short"])
            .current_dir(repo_path)
            .output()
            .await?;

        if output.status.success() {
            let branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
            if !branch.is_empty() {
                tracing::debug!("Detected default branch from HEAD: {}", branch);
                return Ok(branch);
            }
        } else {
            let stderr = String::from_utf8_lossy(&output.stderr);
            tracing::debug!("symbolic-ref HEAD failed: {}", stderr.trim());
        }

        // Try to get the branch that origin/HEAD points to (for regular clones)
        let output = Command::new("git")
            .args(["symbolic-ref", "refs/remotes/origin/HEAD", "--short"])
            .current_dir(repo_path)
            .output()
            .await?;

        if output.status.success() {
            let branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
            // Remove "origin/" prefix if present
            let branch = branch.strip_prefix("origin/").unwrap_or(&branch).to_string();
            if !branch.is_empty() {
                tracing::debug!("Detected default branch from origin/HEAD: {}", branch);
                return Ok(branch);
            }
        }

        // Try common branch names in refs/heads (works for bare and regular repos)
        for branch in ["main", "master", "develop", "trunk"] {
            let output = Command::new("git")
                .args(["rev-parse", "--verify", &format!("refs/heads/{}", branch)])
                .current_dir(repo_path)
                .output()
                .await?;

            if output.status.success() {
                tracing::debug!("Detected default branch from refs/heads: {}", branch);
                return Ok(branch.to_string());
            }
        }

        // Fall back to getting the current branch (for regular repos)
        let output = Command::new("git")
            .args(["rev-parse", "--abbrev-ref", "HEAD"])
            .current_dir(repo_path)
            .output()
            .await?;

        if output.status.success() {
            let branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
            if !branch.is_empty() && branch != "HEAD" {
                tracing::debug!("Detected default branch from rev-parse: {}", branch);
                return Ok(branch);
            }
        }

        // Final fallback: list all branches and pick the first one
        let output = Command::new("git")
            .args(["for-each-ref", "--format=%(refname:short)", "refs/heads/", "--count=1"])
            .current_dir(repo_path)
            .output()
            .await?;

        if output.status.success() {
            let branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
            if !branch.is_empty() {
                tracing::warn!("Using first available branch as fallback: {}", branch);
                return Ok(branch);
            }
        }

        // Log what branches exist for debugging
        let output = Command::new("git")
            .args(["for-each-ref", "--format=%(refname)", "refs/"])
            .current_dir(repo_path)
            .output()
            .await?;

        let available_refs = String::from_utf8_lossy(&output.stdout);
        tracing::error!(
            "Could not detect default branch. Available refs:\n{}",
            available_refs
        );

        Err(WorktreeError::GitCommand(
            format!("Could not detect default branch. Check if the repository at {} has any branches.", repo_path.display()),
        ))
    }

    /// Ensure the source repository exists locally and is up-to-date.
    /// If repo_source is a URL, clone it. If it's a path, verify it exists.
    /// For both cases, fetch latest changes from remote if available.
    pub async fn ensure_repo(&self, repo_source: &str) -> Result<PathBuf, WorktreeError> {
        // Check if it's a URL (simple heuristic)
        if repo_source.starts_with("http://")
            || repo_source.starts_with("https://")
            || repo_source.starts_with("git@")
            || repo_source.starts_with("ssh://")
        {
            self.clone_or_fetch_repo(repo_source).await
        } else {
            // Treat as local path - expand tilde if present
            let path = expand_tilde(repo_source);
            if !path.exists() {
                return Err(WorktreeError::RepoNotFound(repo_source.to_string()));
            }
            // Verify it's a git repo
            let git_dir = path.join(".git");
            if !git_dir.exists() {
                return Err(WorktreeError::InvalidPath(format!(
                    "{} is not a git repository",
                    repo_source
                )));
            }

            // Check if 'origin' remote exists
            let origin_check = Command::new("git")
                .args(["remote", "get-url", "origin"])
                .current_dir(&path)
                .output()
                .await;

            let has_origin = origin_check
                .map(|o| o.status.success())
                .unwrap_or(false);

            if has_origin {
                // Fetch from origin specifically to get the latest changes
                tracing::info!("Fetching latest from origin for local repo: {}", repo_source);
                let fetch_output = Command::new("git")
                    .args(["fetch", "origin"])
                    .current_dir(&path)
                    .output()
                    .await?;

                if !fetch_output.status.success() {
                    let stderr = String::from_utf8_lossy(&fetch_output.stderr);
                    tracing::warn!("Git fetch from origin failed (continuing anyway): {}", stderr);
                } else {
                    tracing::info!("Successfully fetched latest changes from origin for {}", repo_source);
                }
            } else {
                tracing::debug!("Local repo has no origin remote: {}", repo_source);
            }

            // Fetch from all remotes (includes any other remotes besides origin)
            let output = Command::new("git")
                .args(["fetch", "--all", "--prune"])
                .current_dir(&path)
                .output()
                .await?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                // Don't fail - repo might not have a remote configured
                tracing::debug!("Git fetch --all for local repo (may not have remote): {}", stderr);
            }

            Ok(path)
        }
    }

    /// Clone a repository or fetch if already cloned.
    async fn clone_or_fetch_repo(&self, url: &str) -> Result<PathBuf, WorktreeError> {
        // Extract repo name from URL
        let repo_name = extract_repo_name(url);
        let repo_path = self.repos_dir.join(&repo_name);

        // Create repos directory if needed
        tokio::fs::create_dir_all(&self.repos_dir).await?;

        if repo_path.exists() {
            // Verify this is actually a git repository before trying to fetch
            let is_git_repo = Command::new("git")
                .args(["rev-parse", "--is-bare-repository"])
                .current_dir(&repo_path)
                .output()
                .await
                .map(|o| o.status.success())
                .unwrap_or(false);

            if !is_git_repo {
                // Directory exists but is not a git repository - remove and re-clone
                tracing::warn!(
                    "Directory {} exists but is not a git repository, removing and re-cloning",
                    repo_path.display()
                );
                tokio::fs::remove_dir_all(&repo_path).await?;

                // Fall through to clone below
            } else {
                // Fetch latest changes
                tracing::info!("Fetching updates for existing repo: {}", repo_name);
                let output = Command::new("git")
                    .args(["fetch", "--all", "--prune"])
                    .current_dir(&repo_path)
                    .output()
                    .await?;

                if !output.status.success() {
                    let stderr = String::from_utf8_lossy(&output.stderr);
                    tracing::warn!("Git fetch warning: {}", stderr);
                    // Don't fail on fetch errors, repo might still be usable
                }

                return Ok(repo_path);
            }
        }

        // Clone the repository
        tracing::info!("Cloning repository: {} -> {}", url, repo_path.display());
        let output = Command::new("git")
            .args(["clone", "--bare", url])
            .arg(&repo_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::CloneFailed(stderr.to_string()));
        }

        Ok(repo_path)
    }

    /// Create a worktree for a task.
    ///
    /// This creates a unique directory with a git worktree checked out to a new branch.
    pub async fn create_worktree(
        &self,
        source_repo: &Path,
        task_id: Uuid,
        task_name: &str,
        base_branch: &str,
    ) -> Result<WorktreeInfo, WorktreeError> {
        // Generate unique directory name and branch
        let dir_name = format!("{}-{}", short_uuid(task_id), sanitize_name(task_name));
        let worktree_path = self.base_dir.join(&dir_name);
        // Branch name: makima/{task-name-with-dashes}-{short-id}
        let branch_name = format!("{}{}-{}", self.branch_prefix, sanitize_name(task_name), short_uuid(task_id));

        // Acquire lock for this worktree path
        let lock = {
            let mut locks = WORKTREE_LOCKS.lock().await;
            locks
                .entry(worktree_path.to_string_lossy().to_string())
                .or_insert_with(|| std::sync::Arc::new(tokio::sync::Mutex::new(())))
                .clone()
        };
        let _guard = lock.lock().await;

        // Check if worktree already exists - reuse it if so
        if worktree_path.exists() {
            tracing::info!(
                task_id = %task_id,
                worktree_path = %worktree_path.display(),
                "Worktree already exists, reusing"
            );

            // Verify it's a valid git directory
            let git_dir = worktree_path.join(".git");
            if git_dir.exists() {
                // Get the current branch name
                let output = Command::new("git")
                    .args(["rev-parse", "--abbrev-ref", "HEAD"])
                    .current_dir(&worktree_path)
                    .output()
                    .await?;

                let current_branch = if output.status.success() {
                    String::from_utf8_lossy(&output.stdout).trim().to_string()
                } else {
                    branch_name.clone()
                };

                return Ok(WorktreeInfo {
                    path: worktree_path,
                    branch: current_branch,
                    source_repo: source_repo.to_path_buf(),
                });
            } else {
                // Directory exists but isn't a git worktree - remove and recreate
                tracing::warn!(
                    task_id = %task_id,
                    worktree_path = %worktree_path.display(),
                    "Directory exists but is not a git worktree, removing"
                );
                tokio::fs::remove_dir_all(&worktree_path).await?;
            }
        }

        // Create base directory
        tokio::fs::create_dir_all(&self.base_dir).await?;

        // Prune stale worktree entries first (handles "missing but registered" errors)
        let _ = Command::new("git")
            .args(["worktree", "prune"])
            .current_dir(source_repo)
            .output()
            .await;

        // Check if the branch already exists (e.g., from a previous run of the same task)
        let branch_exists = Command::new("git")
            .args(["rev-parse", "--verify", &format!("refs/heads/{}", branch_name)])
            .current_dir(source_repo)
            .output()
            .await
            .map(|o| o.status.success())
            .unwrap_or(false);

        if branch_exists {
            tracing::info!(
                task_id = %task_id,
                worktree_path = %worktree_path.display(),
                branch = %branch_name,
                "Branch already exists, creating worktree from existing branch"
            );

            // Use existing branch - try without force first, then with force
            let output = Command::new("git")
                .args(["worktree", "add", "-f"])
                .arg(&worktree_path)
                .arg(&branch_name)
                .current_dir(source_repo)
                .output()
                .await?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                return Err(WorktreeError::GitCommand(format!(
                    "Failed to create worktree from existing branch: {}",
                    stderr
                )));
            }
        } else {
            tracing::info!(
                task_id = %task_id,
                worktree_path = %worktree_path.display(),
                branch = %branch_name,
                base_branch = %base_branch,
                "Creating worktree with new branch"
            );

            // Create the worktree with a new branch based on the local base_branch
            let output = Command::new("git")
                .args([
                    "worktree",
                    "add",
                    "-b",
                    &branch_name,
                ])
                .arg(&worktree_path)
                .arg(base_branch)
                .current_dir(source_repo)
                .output()
                .await?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                return Err(WorktreeError::GitCommand(format!(
                    "Failed to create worktree: {}",
                    stderr
                )));
            }
        }

        tracing::info!(
            task_id = %task_id,
            worktree_path = %worktree_path.display(),
            "Worktree created successfully"
        );

        Ok(WorktreeInfo {
            path: worktree_path,
            branch: branch_name,
            source_repo: source_repo.to_path_buf(),
        })
    }

    /// Create a worktree for a task by copying from another task's worktree.
    ///
    /// This allows sequential subtasks where one continues from another's work,
    /// including uncommitted changes.
    pub async fn create_worktree_from_task(
        &self,
        source_worktree: &Path,
        task_id: Uuid,
        task_name: &str,
    ) -> Result<WorktreeInfo, WorktreeError> {
        // Verify source worktree exists
        if !source_worktree.exists() {
            return Err(WorktreeError::RepoNotFound(format!(
                "Source worktree not found: {}",
                source_worktree.display()
            )));
        }

        // Get the source repo from the source worktree
        let source_repo = self.get_worktree_source(source_worktree).await?;

        // Get the base branch from source worktree's current HEAD
        let output = Command::new("git")
            .args(["rev-parse", "HEAD"])
            .current_dir(source_worktree)
            .output()
            .await?;

        if !output.status.success() {
            return Err(WorktreeError::GitCommand(
                "Failed to get source worktree HEAD".to_string(),
            ));
        }
        let source_commit = String::from_utf8_lossy(&output.stdout).trim().to_string();

        // Generate unique directory name and branch for new worktree
        let dir_name = format!("{}-{}", short_uuid(task_id), sanitize_name(task_name));
        let worktree_path = self.base_dir.join(&dir_name);
        let branch_name = format!("{}{}", self.branch_prefix, task_id);

        // Acquire lock for this worktree path
        let lock = {
            let mut locks = WORKTREE_LOCKS.lock().await;
            locks
                .entry(worktree_path.to_string_lossy().to_string())
                .or_insert_with(|| std::sync::Arc::new(tokio::sync::Mutex::new(())))
                .clone()
        };
        let _guard = lock.lock().await;

        // Remove existing worktree if present
        if worktree_path.exists() {
            tracing::info!(
                task_id = %task_id,
                worktree_path = %worktree_path.display(),
                "Removing existing worktree before creating from source"
            );
            tokio::fs::remove_dir_all(&worktree_path).await?;
        }

        // Create base directory
        tokio::fs::create_dir_all(&self.base_dir).await?;

        // Prune stale worktree entries first (handles "missing but registered" errors)
        let _ = Command::new("git")
            .args(["worktree", "prune"])
            .current_dir(&source_repo)
            .output()
            .await;

        // Check if the branch already exists (e.g., from a previous run of the same task)
        let branch_exists = Command::new("git")
            .args(["rev-parse", "--verify", &format!("refs/heads/{}", branch_name)])
            .current_dir(&source_repo)
            .output()
            .await
            .map(|o| o.status.success())
            .unwrap_or(false);

        if branch_exists {
            tracing::info!(
                task_id = %task_id,
                source_worktree = %source_worktree.display(),
                worktree_path = %worktree_path.display(),
                branch = %branch_name,
                "Branch already exists, creating worktree from existing branch"
            );

            // Use existing branch with force flag
            let output = Command::new("git")
                .args(["worktree", "add", "-f"])
                .arg(&worktree_path)
                .arg(&branch_name)
                .current_dir(&source_repo)
                .output()
                .await?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                return Err(WorktreeError::GitCommand(format!(
                    "Failed to create worktree from existing branch: {}",
                    stderr
                )));
            }
        } else {
            tracing::info!(
                task_id = %task_id,
                source_worktree = %source_worktree.display(),
                worktree_path = %worktree_path.display(),
                branch = %branch_name,
                source_commit = %source_commit,
                "Creating worktree from source task with new branch"
            );

            // Create a new worktree based on the source commit
            let output = Command::new("git")
                .args([
                    "worktree",
                    "add",
                    "-b",
                    &branch_name,
                ])
                .arg(&worktree_path)
                .arg(&source_commit)
                .current_dir(&source_repo)
                .output()
                .await?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                return Err(WorktreeError::GitCommand(format!(
                    "Failed to create worktree: {}",
                    stderr
                )));
            }
        }

        // Now copy uncommitted changes from source worktree
        // Use rsync to copy all files except .git
        let output = Command::new("rsync")
            .args([
                "-a",
                "--exclude", ".git",
                "--exclude", ".makima",
                &format!("{}/", source_worktree.display()),
                &format!("{}/", worktree_path.display()),
            ])
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            tracing::warn!(
                task_id = %task_id,
                "rsync warning (continuing anyway): {}",
                stderr
            );
        }

        tracing::info!(
            task_id = %task_id,
            worktree_path = %worktree_path.display(),
            "Worktree created from source task successfully"
        );

        Ok(WorktreeInfo {
            path: worktree_path,
            branch: branch_name,
            source_repo: source_repo.to_path_buf(),
        })
    }

    /// Find and create a worktree from a previously pushed branch for a task.
    ///
    /// Searches local and remote branches for ones matching the task's UUID,
    /// then creates a new worktree based on that branch.
    pub async fn create_worktree_from_task_branch(
        &self,
        source_repo: &Path,
        from_task_id: Uuid,
        new_task_id: Uuid,
        new_task_name: &str,
    ) -> Result<WorktreeInfo, WorktreeError> {
        let from_short_id = short_uuid(from_task_id);
        let from_full_id = from_task_id.to_string();

        tracing::info!(
            from_task_id = %from_task_id,
            new_task_id = %new_task_id,
            "Searching for pushed branch to continue from"
        );

        // Fetch latest from remote first
        let _ = Command::new("git")
            .args(["fetch", "--all", "--prune"])
            .current_dir(source_repo)
            .output()
            .await;

        // Search for branches matching the task ID (both local and remote)
        let output = Command::new("git")
            .args(["branch", "-a", "--format=%(refname:short)"])
            .current_dir(source_repo)
            .output()
            .await?;

        if !output.status.success() {
            return Err(WorktreeError::GitCommand(
                "Failed to list branches".to_string(),
            ));
        }

        let branches_output = String::from_utf8_lossy(&output.stdout);
        let mut found_branch: Option<String> = None;

        for line in branches_output.lines() {
            let b = line.trim();
            if b.is_empty() {
                continue;
            }
            // Match branches containing the full UUID or short UUID
            if b.contains(&from_full_id) || b.contains(&from_short_id) {
                // Prefer local branches, but accept remote
                if let Some(remote_branch) = b.strip_prefix("origin/") {
                    if found_branch.is_none() {
                        found_branch = Some(remote_branch.to_string());
                    }
                } else {
                    found_branch = Some(b.to_string());
                }
            }
        }

        let from_branch = found_branch.ok_or_else(|| {
            WorktreeError::GitCommand(format!(
                "No branch found for task {} in repository",
                from_short_id
            ))
        })?;

        tracing::info!(
            from_task_id = %from_task_id,
            from_branch = %from_branch,
            "Found pushed branch, creating worktree from it"
        );

        // Create a worktree using the found branch as the base
        self.create_worktree(source_repo, new_task_id, new_task_name, &from_branch)
            .await
    }

    /// Remove a worktree and optionally its branch.
    pub async fn remove_worktree(
        &self,
        worktree_path: &Path,
        delete_branch: bool,
    ) -> Result<(), WorktreeError> {
        if !worktree_path.exists() {
            return Ok(()); // Already gone
        }

        // Get the branch name before removing
        let branch_name = if delete_branch {
            self.get_worktree_branch(worktree_path).await.ok()
        } else {
            None
        };

        // Find the source repo from worktree
        let source_repo = self.get_worktree_source(worktree_path).await?;

        tracing::info!(
            worktree_path = %worktree_path.display(),
            delete_branch = delete_branch,
            "Removing worktree"
        );

        // Remove the worktree
        let output = Command::new("git")
            .args(["worktree", "remove", "--force"])
            .arg(worktree_path)
            .current_dir(&source_repo)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            // Try force removal of directory if git worktree remove fails
            if worktree_path.exists() {
                tokio::fs::remove_dir_all(worktree_path).await?;
            }
            tracing::warn!("Git worktree remove warning: {}", stderr);
        }

        // Prune worktree references
        let _ = Command::new("git")
            .args(["worktree", "prune"])
            .current_dir(&source_repo)
            .output()
            .await;

        // Delete the branch if requested
        if let Some(branch) = branch_name {
            let output = Command::new("git")
                .args(["branch", "-D", &branch])
                .current_dir(&source_repo)
                .output()
                .await?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                tracing::warn!("Failed to delete branch {}: {}", branch, stderr);
            }
        }

        Ok(())
    }

    /// Get the branch name of a worktree.
    async fn get_worktree_branch(&self, worktree_path: &Path) -> Result<String, WorktreeError> {
        let output = Command::new("git")
            .args(["rev-parse", "--abbrev-ref", "HEAD"])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to get branch: {}",
                stderr
            )));
        }

        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
    }

    /// Get the source repository path for a worktree.
    async fn get_worktree_source(&self, worktree_path: &Path) -> Result<PathBuf, WorktreeError> {
        // Read the .git file in the worktree which contains the path to the main repo
        let git_file = worktree_path.join(".git");

        if git_file.is_file() {
            let content = tokio::fs::read_to_string(&git_file).await?;
            // Format: "gitdir: /path/to/repo/.git/worktrees/name"
            if let Some(gitdir) = content.strip_prefix("gitdir: ") {
                let gitdir = gitdir.trim();
                // Navigate from worktrees/name back to the main repo
                let path = PathBuf::from(gitdir);
                if let Some(worktrees_dir) = path.parent() {
                    if let Some(git_dir) = worktrees_dir.parent() {
                        if let Some(repo_dir) = git_dir.parent() {
                            return Ok(repo_dir.to_path_buf());
                        }
                    }
                }
            }
        }

        // Fallback: try to find it in our repos directory
        Err(WorktreeError::InvalidPath(format!(
            "Could not determine source repo for worktree: {}",
            worktree_path.display()
        )))
    }

    /// List all worktrees in the base directory.
    pub async fn list_worktrees(&self) -> Result<Vec<PathBuf>, WorktreeError> {
        let mut worktrees = Vec::new();

        if !self.base_dir.exists() {
            return Ok(worktrees);
        }

        let mut entries = tokio::fs::read_dir(&self.base_dir).await?;
        while let Some(entry) = entries.next_entry().await? {
            let path = entry.path();
            if path.is_dir() && path.join(".git").exists() {
                worktrees.push(path);
            }
        }

        Ok(worktrees)
    }

    /// Initialize a new git repository for a task.
    ///
    /// This creates a fresh git repo (not a worktree) for tasks that don't need
    /// an existing codebase. Use this when `repository_url` is `new://` or `new://project-name`.
    pub async fn init_new_repo(
        &self,
        task_id: Uuid,
        repo_source: &str,
    ) -> Result<WorktreeInfo, WorktreeError> {
        let project_name = extract_new_repo_name(repo_source);
        let dir_name = match project_name {
            Some(name) => format!("{}-{}", short_uuid(task_id), sanitize_name(name)),
            None => format!("{}-new", short_uuid(task_id)),
        };
        let repo_path = self.repos_dir.join(&dir_name);

        tracing::info!(
            task_id = %task_id,
            path = %repo_path.display(),
            project_name = ?project_name,
            "Initializing new git repository"
        );

        // Create directory
        tokio::fs::create_dir_all(&repo_path).await?;

        // git init
        let output = Command::new("git")
            .args(["init"])
            .current_dir(&repo_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to init repository: {}",
                stderr
            )));
        }

        // Configure git user (needed for commits)
        let _ = Command::new("git")
            .args(["config", "user.email", "makima@localhost"])
            .current_dir(&repo_path)
            .output()
            .await;
        let _ = Command::new("git")
            .args(["config", "user.name", "Makima"])
            .current_dir(&repo_path)
            .output()
            .await;

        // Initial commit (required for worktrees to work later if needed)
        let output = Command::new("git")
            .args(["commit", "--allow-empty", "-m", "Initial commit"])
            .current_dir(&repo_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to create initial commit: {}",
                stderr
            )));
        }

        tracing::info!(
            task_id = %task_id,
            path = %repo_path.display(),
            "New git repository initialized"
        );

        Ok(WorktreeInfo {
            path: repo_path.clone(),
            branch: "main".to_string(),
            source_repo: repo_path,
        })
    }

    // ========== Merge Operations ==========

    /// List all task branches in a repository.
    ///
    /// Returns branches matching the pattern `makima/task-*`.
    pub async fn list_task_branches(
        &self,
        repo_path: &Path,
    ) -> Result<Vec<TaskBranchInfo>, WorktreeError> {
        // Get all branches matching our prefix
        let output = Command::new("git")
            .args([
                "branch",
                "--list",
                &format!("{}*", self.branch_prefix),
                "--format=%(refname:short)|%(objectname:short)|%(subject)",
            ])
            .current_dir(repo_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to list branches: {}",
                stderr
            )));
        }

        // Get list of merged branches
        let merged_output = Command::new("git")
            .args(["branch", "--merged", "HEAD", "--format=%(refname:short)"])
            .current_dir(repo_path)
            .output()
            .await?;

        let merged_branches: std::collections::HashSet<String> = if merged_output.status.success() {
            String::from_utf8_lossy(&merged_output.stdout)
                .lines()
                .map(|s| s.trim().to_string())
                .collect()
        } else {
            std::collections::HashSet::new()
        };

        let stdout = String::from_utf8_lossy(&output.stdout);
        let mut branches = Vec::new();

        for line in stdout.lines() {
            let parts: Vec<&str> = line.split('|').collect();
            if parts.len() >= 3 {
                let name = parts[0].trim().to_string();
                let last_commit = parts[1].trim().to_string();
                let last_commit_message = parts[2].trim().to_string();

                // Try to extract task ID from branch name
                let task_id = name
                    .strip_prefix(&self.branch_prefix)
                    .and_then(|s| Uuid::parse_str(s).ok());

                let is_merged = merged_branches.contains(&name);

                branches.push(TaskBranchInfo {
                    name,
                    task_id,
                    is_merged,
                    last_commit,
                    last_commit_message,
                });
            }
        }

        Ok(branches)
    }

    /// Start a merge of a branch into the current worktree.
    ///
    /// Uses `--no-commit` to allow conflict resolution before committing.
    /// Returns Ok(None) if merge succeeds without conflicts, or Ok(Some(files))
    /// with the list of conflicted files.
    pub async fn merge_branch(
        &self,
        worktree_path: &Path,
        source_branch: &str,
    ) -> Result<Option<Vec<String>>, WorktreeError> {
        // Check if there's already a merge in progress
        if self.is_merge_in_progress(worktree_path).await? {
            return Err(WorktreeError::MergeInProgress);
        }

        tracing::info!(
            worktree = %worktree_path.display(),
            source_branch = %source_branch,
            "Starting merge"
        );

        // Attempt the merge with --no-commit --no-ff
        let output = Command::new("git")
            .args(["merge", "--no-commit", "--no-ff", source_branch])
            .current_dir(worktree_path)
            .output()
            .await?;

        if output.status.success() {
            tracing::info!("Merge completed without conflicts");
            return Ok(None);
        }

        // Check if there are conflicts
        let conflicts = self.get_conflicted_files(worktree_path).await?;
        if !conflicts.is_empty() {
            tracing::info!(
                conflicts = ?conflicts,
                "Merge has conflicts"
            );
            return Ok(Some(conflicts));
        }

        // Other error
        let stderr = String::from_utf8_lossy(&output.stderr);
        Err(WorktreeError::GitCommand(format!(
            "Merge failed: {}",
            stderr
        )))
    }

    /// Check if a merge is currently in progress.
    pub async fn is_merge_in_progress(&self, worktree_path: &Path) -> Result<bool, WorktreeError> {
        // Check for MERGE_HEAD file
        let merge_head = worktree_path.join(".git").join("MERGE_HEAD");
        if merge_head.exists() {
            return Ok(true);
        }

        // Also check in .git file (for worktrees)
        let git_file = worktree_path.join(".git");
        if git_file.is_file() {
            if let Ok(content) = tokio::fs::read_to_string(&git_file).await {
                if let Some(gitdir) = content.strip_prefix("gitdir: ") {
                    let gitdir = PathBuf::from(gitdir.trim());
                    let merge_head = gitdir.join("MERGE_HEAD");
                    if merge_head.exists() {
                        return Ok(true);
                    }
                }
            }
        }

        Ok(false)
    }

    /// Get the list of files with unresolved conflicts.
    pub async fn get_conflicted_files(
        &self,
        worktree_path: &Path,
    ) -> Result<Vec<String>, WorktreeError> {
        let output = Command::new("git")
            .args(["diff", "--name-only", "--diff-filter=U"])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            // No conflicts or not in merge state
            return Ok(Vec::new());
        }

        let stdout = String::from_utf8_lossy(&output.stdout);
        let files: Vec<String> = stdout
            .lines()
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty())
            .collect();

        Ok(files)
    }

    /// Get the current merge state.
    pub async fn get_merge_state(
        &self,
        worktree_path: &Path,
    ) -> Result<MergeState, WorktreeError> {
        let in_progress = self.is_merge_in_progress(worktree_path).await?;

        if !in_progress {
            return Ok(MergeState {
                source_branch: String::new(),
                conflicted_files: Vec::new(),
                in_progress: false,
            });
        }

        // Get the branch being merged from MERGE_HEAD
        let source_branch = self.get_merge_source_branch(worktree_path).await?;
        let conflicted_files = self.get_conflicted_files(worktree_path).await?;

        Ok(MergeState {
            source_branch,
            conflicted_files,
            in_progress: true,
        })
    }

    /// Get the branch name being merged (from MERGE_HEAD).
    async fn get_merge_source_branch(&self, worktree_path: &Path) -> Result<String, WorktreeError> {
        // Get MERGE_HEAD commit
        let output = Command::new("git")
            .args(["rev-parse", "MERGE_HEAD"])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            return Ok("unknown".to_string());
        }

        let commit = String::from_utf8_lossy(&output.stdout).trim().to_string();

        // Try to find branch name for this commit
        let output = Command::new("git")
            .args(["name-rev", "--name-only", &commit])
            .current_dir(worktree_path)
            .output()
            .await?;

        if output.status.success() {
            let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
            // Clean up the name (remove ~N suffixes, etc.)
            let name = name.split('~').next().unwrap_or(&name);
            let name = name.split('^').next().unwrap_or(name);
            return Ok(name.to_string());
        }

        Ok(commit[..8.min(commit.len())].to_string())
    }

    /// Resolve a conflict in a specific file.
    pub async fn resolve_conflict(
        &self,
        worktree_path: &Path,
        file_path: &str,
        resolution: ConflictResolution,
    ) -> Result<(), WorktreeError> {
        if !self.is_merge_in_progress(worktree_path).await? {
            return Err(WorktreeError::NoMergeInProgress);
        }

        let strategy = match resolution {
            ConflictResolution::Ours => "--ours",
            ConflictResolution::Theirs => "--theirs",
        };

        tracing::info!(
            worktree = %worktree_path.display(),
            file = %file_path,
            strategy = %strategy,
            "Resolving conflict"
        );

        // Checkout the chosen version
        let output = Command::new("git")
            .args(["checkout", strategy, "--", file_path])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to resolve conflict: {}",
                stderr
            )));
        }

        // Stage the resolved file
        let output = Command::new("git")
            .args(["add", file_path])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to stage resolved file: {}",
                stderr
            )));
        }

        Ok(())
    }

    /// Abort the current merge.
    pub async fn abort_merge(&self, worktree_path: &Path) -> Result<(), WorktreeError> {
        if !self.is_merge_in_progress(worktree_path).await? {
            return Err(WorktreeError::NoMergeInProgress);
        }

        tracing::info!(
            worktree = %worktree_path.display(),
            "Aborting merge"
        );

        let output = Command::new("git")
            .args(["merge", "--abort"])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to abort merge: {}",
                stderr
            )));
        }

        Ok(())
    }

    /// Commit the current merge.
    pub async fn commit_merge(
        &self,
        worktree_path: &Path,
        message: &str,
    ) -> Result<String, WorktreeError> {
        // Check for remaining conflicts
        let conflicts = self.get_conflicted_files(worktree_path).await?;
        if !conflicts.is_empty() {
            return Err(WorktreeError::MergeConflicts(conflicts.join(", ")));
        }

        tracing::info!(
            worktree = %worktree_path.display(),
            message = %message,
            "Committing merge"
        );

        let output = Command::new("git")
            .args(["commit", "-m", message])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to commit merge: {}",
                stderr
            )));
        }

        // Get the new commit SHA
        let output = Command::new("git")
            .args(["rev-parse", "HEAD"])
            .current_dir(worktree_path)
            .output()
            .await?;

        if output.status.success() {
            let sha = String::from_utf8_lossy(&output.stdout).trim().to_string();
            return Ok(sha);
        }

        Ok("unknown".to_string())
    }

    // ========== Completion Action Operations ==========

    /// Push task branch from worktree to an external target repository.
    ///
    /// This stages and commits any uncommitted changes, then pushes to the target repo.
    pub async fn push_to_target_repo(
        &self,
        worktree_path: &Path,
        target_repo: &Path,
        branch_name: &str,
        task_name: &str,
    ) -> Result<(), WorktreeError> {
        tracing::info!(
            worktree = %worktree_path.display(),
            target_repo = %target_repo.display(),
            branch = %branch_name,
            "Pushing branch to target repository"
        );

        // First, stage all changes (including new files)
        let output = Command::new("git")
            .args(["add", "-A"])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to stage changes: {}",
                stderr
            )));
        }

        // Check if there are staged changes to commit
        let output = Command::new("git")
            .args(["diff", "--cached", "--quiet"])
            .current_dir(worktree_path)
            .output()
            .await?;

        // Exit code 1 means there are staged changes
        if !output.status.success() {
            tracing::info!("Committing staged changes before push");

            let commit_message = format!("feat: {}", task_name);
            let output = Command::new("git")
                .args(["commit", "-m", &commit_message])
                .current_dir(worktree_path)
                .output()
                .await?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                return Err(WorktreeError::GitCommand(format!(
                    "Failed to commit changes: {}",
                    stderr
                )));
            }
        }

        // Ensure there are commits to push
        let output = Command::new("git")
            .args(["log", "--oneline", "-1"])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            return Err(WorktreeError::GitCommand(
                "No commits in worktree".to_string(),
            ));
        }

        // Add target repo as a remote in the worktree (if not already)
        let remote_name = "target";
        let target_path_str = target_repo.to_string_lossy();

        // Remove existing remote if any (ignore errors)
        let _ = Command::new("git")
            .args(["remote", "remove", remote_name])
            .current_dir(worktree_path)
            .output()
            .await;

        // Add the target as a remote
        let output = Command::new("git")
            .args(["remote", "add", remote_name, &target_path_str])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to add remote: {}",
                stderr
            )));
        }

        // Push the branch to the target
        let output = Command::new("git")
            .args(["push", "-u", remote_name, &format!("HEAD:{}", branch_name)])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to push to target: {}",
                stderr
            )));
        }

        tracing::info!(
            branch = %branch_name,
            target_repo = %target_repo.display(),
            "Branch pushed successfully"
        );

        // Detach HEAD in the worktree to release the branch
        // This allows the branch to be checked out in the target repo
        let output = Command::new("git")
            .args(["checkout", "--detach", "HEAD"])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            // Non-fatal: log but don't fail the push
            let stderr = String::from_utf8_lossy(&output.stderr);
            tracing::warn!(
                "Failed to detach HEAD in worktree (branch may not be checkable in target): {}",
                stderr
            );
        } else {
            tracing::info!("Detached HEAD in worktree to release branch");
        }

        Ok(())
    }

    /// Push a worktree branch to origin (the upstream GitHub remote).
    /// Simpler than push_to_target_repo — just pushes to origin.
    pub async fn push_branch_to_origin(
        &self,
        worktree_path: &Path,
        branch_name: &str,
        task_name: &str,
    ) -> Result<(), WorktreeError> {
        tracing::info!(
            worktree = %worktree_path.display(),
            branch = %branch_name,
            "Pushing branch to origin"
        );

        // Stage all changes
        let output = Command::new("git")
            .args(["add", "-A"])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to stage changes: {}",
                stderr
            )));
        }

        // Check if there are staged changes to commit
        let output = Command::new("git")
            .args(["diff", "--cached", "--quiet"])
            .current_dir(worktree_path)
            .output()
            .await?;

        // Exit code 1 means there are staged changes
        if !output.status.success() {
            tracing::info!("Committing staged changes before push to origin");

            let commit_message = format!("feat: {}", task_name);
            let output = Command::new("git")
                .args(["commit", "-m", &commit_message])
                .current_dir(worktree_path)
                .output()
                .await?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                return Err(WorktreeError::GitCommand(format!(
                    "Failed to commit changes: {}",
                    stderr
                )));
            }
        }

        // Ensure there are commits to push
        let output = Command::new("git")
            .args(["log", "--oneline", "-1"])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            return Err(WorktreeError::GitCommand(
                "No commits in worktree".to_string(),
            ));
        }

        // Push to origin
        let output = Command::new("git")
            .args(["push", "-u", "origin", &format!("HEAD:{}", branch_name)])
            .current_dir(worktree_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to push to origin: {}",
                stderr
            )));
        }

        tracing::info!(
            branch = %branch_name,
            "Branch pushed to origin successfully"
        );

        Ok(())
    }

    /// Merge a branch into the target branch in the target repository.
    ///
    /// This pushes the branch first (if needed), then performs a merge in the target repo.
    pub async fn merge_to_target(
        &self,
        worktree_path: &Path,
        target_repo: &Path,
        source_branch: &str,
        target_branch: &str,
        task_name: &str,
    ) -> Result<String, WorktreeError> {
        tracing::info!(
            worktree = %worktree_path.display(),
            target_repo = %target_repo.display(),
            source_branch = %source_branch,
            target_branch = %target_branch,
            "Merging branch to target"
        );

        // First, push the branch to target repo
        self.push_to_target_repo(worktree_path, target_repo, source_branch, task_name)
            .await?;

        // In target repo, checkout the target branch
        let output = Command::new("git")
            .args(["checkout", target_branch])
            .current_dir(target_repo)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to checkout target branch: {}",
                stderr
            )));
        }

        // Pull latest changes first
        let _ = Command::new("git")
            .args(["pull", "--ff-only"])
            .current_dir(target_repo)
            .output()
            .await;

        // Merge the source branch
        let merge_message = format!("feat: {}", task_name);
        let output = Command::new("git")
            .args(["merge", "--no-ff", source_branch, "-m", &merge_message])
            .current_dir(target_repo)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);

            // Check if it's a conflict
            let conflicts = self.get_conflicted_files(target_repo).await?;
            if !conflicts.is_empty() {
                // Abort the merge
                let _ = Command::new("git")
                    .args(["merge", "--abort"])
                    .current_dir(target_repo)
                    .output()
                    .await;

                return Err(WorktreeError::MergeConflicts(format!(
                    "Merge conflicts in: {}. Consider creating a PR instead.",
                    conflicts.join(", ")
                )));
            }

            return Err(WorktreeError::GitCommand(format!(
                "Failed to merge: {}",
                stderr
            )));
        }

        // Get the merge commit SHA
        let output = Command::new("git")
            .args(["rev-parse", "HEAD"])
            .current_dir(target_repo)
            .output()
            .await?;

        let commit_sha = if output.status.success() {
            String::from_utf8_lossy(&output.stdout).trim().to_string()
        } else {
            "unknown".to_string()
        };

        tracing::info!(
            commit_sha = %commit_sha,
            "Merge completed successfully"
        );

        Ok(commit_sha)
    }

    /// Create a GitHub pull request using the gh CLI.
    ///
    /// This pushes the branch first, then creates a PR.
    /// If target_repo is None, uses the worktree's origin remote directly (for repos already cloned from remote).
    pub async fn create_pull_request(
        &self,
        worktree_path: &Path,
        target_repo: Option<&Path>,
        source_branch: &str,
        target_branch: &str,
        title: &str,
        body: &str,
    ) -> Result<String, WorktreeError> {
        tracing::info!(
            worktree = %worktree_path.display(),
            target_repo = ?target_repo.map(|p| p.display().to_string()),
            source_branch = %source_branch,
            target_branch = %target_branch,
            title = %title,
            "Creating pull request"
        );

        // First, push the branch to the target repo's remote
        // For PRs, we need to push to origin (the GitHub remote)

        // Get the worktree's current branch
        let output = Command::new("git")
            .args(["rev-parse", "--abbrev-ref", "HEAD"])
            .current_dir(worktree_path)
            .output()
            .await?;

        let current_branch = if output.status.success() {
            String::from_utf8_lossy(&output.stdout).trim().to_string()
        } else {
            source_branch.to_string()
        };

        // Get the origin URL - either from target_repo or from worktree directly
        let (origin_url, gh_working_dir) = if let Some(target_repo) = target_repo {
            // Use target_repo's origin
            let output = Command::new("git")
                .args(["remote", "get-url", "origin"])
                .current_dir(target_repo)
                .output()
                .await?;

            if !output.status.success() {
                return Err(WorktreeError::GitCommand(
                    "Target repository has no origin remote configured".to_string(),
                ));
            }

            let url = String::from_utf8_lossy(&output.stdout).trim().to_string();
            (url, target_repo.to_path_buf())
        } else {
            // Check if worktree has an origin remote directly
            let output = Command::new("git")
                .args(["remote", "get-url", "origin"])
                .current_dir(worktree_path)
                .output()
                .await?;

            if !output.status.success() {
                return Err(WorktreeError::GitCommand(
                    "Repository has no origin remote configured. Either set target_repo_path or ensure the worktree was cloned from a remote repository.".to_string(),
                ));
            }

            let url = String::from_utf8_lossy(&output.stdout).trim().to_string();
            (url, worktree_path.to_path_buf())
        };

        // Push the branch from worktree to the remote
        // First add the remote to worktree (if not using worktree's origin directly)
        if target_repo.is_some() {
            let _ = Command::new("git")
                .args(["remote", "remove", "pr-origin"])
                .current_dir(worktree_path)
                .output()
                .await;

            let output = Command::new("git")
                .args(["remote", "add", "pr-origin", &origin_url])
                .current_dir(worktree_path)
                .output()
                .await?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                return Err(WorktreeError::GitCommand(format!(
                    "Failed to add remote: {}",
                    stderr
                )));
            }

            // Push to the remote
            let output = Command::new("git")
                .args(["push", "-u", "pr-origin", &format!("{}:{}", current_branch, source_branch)])
                .current_dir(worktree_path)
                .output()
                .await?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                return Err(WorktreeError::GitCommand(format!(
                    "Failed to push branch: {}",
                    stderr
                )));
            }
        } else {
            // Push directly to origin
            let output = Command::new("git")
                .args(["push", "-u", "origin", &format!("{}:{}", current_branch, source_branch)])
                .current_dir(worktree_path)
                .output()
                .await?;

            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                return Err(WorktreeError::GitCommand(format!(
                    "Failed to push branch: {}",
                    stderr
                )));
            }
        }

        // Create PR using gh CLI
        let output = Command::new("gh")
            .args([
                "pr",
                "create",
                "--title", title,
                "--body", body,
                "--head", source_branch,
                "--base", target_branch,
            ])
            .current_dir(&gh_working_dir)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::GitCommand(format!(
                "Failed to create PR: {}",
                stderr
            )));
        }

        // The gh CLI outputs the PR URL
        let pr_url = String::from_utf8_lossy(&output.stdout).trim().to_string();

        tracing::info!(
            pr_url = %pr_url,
            "Pull request created successfully"
        );

        Ok(pr_url)
    }

    /// Clone/copy the worktree contents to a target directory.
    ///
    /// This creates a new git repository at the target path with the same contents
    /// as the worktree. Returns (success, message).
    pub async fn clone_worktree_to_directory(
        &self,
        worktree_path: &Path,
        target_dir: &Path,
    ) -> Result<String, WorktreeError> {
        tracing::info!(
            worktree = %worktree_path.display(),
            target = %target_dir.display(),
            "Cloning worktree to target directory"
        );

        // Check if target directory already exists
        if target_dir.exists() {
            return Err(WorktreeError::AlreadyExists(format!(
                "Target directory already exists: {}",
                target_dir.display()
            )));
        }

        // Get parent directory to ensure it exists
        if let Some(parent) = target_dir.parent() {
            if !parent.exists() {
                tokio::fs::create_dir_all(parent).await?;
            }
        }

        // Use git clone --local to efficiently copy the repository
        // This is more efficient than cp -r for git repos
        let output = Command::new("git")
            .args([
                "clone",
                "--local",
                "--no-hardlinks",
                &worktree_path.to_string_lossy(),
                &target_dir.to_string_lossy(),
            ])
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::CloneFailed(format!(
                "Failed to clone worktree: {}",
                stderr
            )));
        }

        // Remove the 'origin' remote that points back to the worktree
        let _ = Command::new("git")
            .args(["remote", "remove", "origin"])
            .current_dir(target_dir)
            .output()
            .await;

        tracing::info!(
            target = %target_dir.display(),
            "Worktree cloned successfully"
        );

        Ok(format!("Cloned to {}", target_dir.display()))
    }

    /// Check if a target directory exists.
    pub async fn target_directory_exists(&self, target_dir: &Path) -> bool {
        target_dir.exists()
    }

    /// Restore a worktree from a stored patch.
    ///
    /// This is used for task recovery when the local worktree has been lost.
    /// 1. Clone/fetch the source repo to get the base commit
    /// 2. Create a new worktree at the base commit
    /// 3. Apply the patch to restore the task's state
    pub async fn restore_from_patch(
        &self,
        source_repo: &str,
        task_id: Uuid,
        task_name: &str,
        base_commit_sha: &str,
        patch_data: &[u8],
    ) -> Result<WorktreeInfo, WorktreeError> {
        use crate::daemon::storage;

        // Generate directory and branch names
        let dir_name = format!("{}-{}", short_uuid(task_id), sanitize_name(task_name));
        let worktree_path = self.base_dir.join(&dir_name);
        let branch_name = format!(
            "{}{}-{}",
            self.branch_prefix,
            sanitize_name(task_name),
            short_uuid(task_id)
        );

        // Ensure base directory exists
        tokio::fs::create_dir_all(&self.base_dir).await?;

        // Remove existing worktree if present (we're restoring from scratch)
        if worktree_path.exists() {
            tracing::info!(
                task_id = %task_id,
                worktree_path = %worktree_path.display(),
                "Removing existing worktree before restore"
            );
            tokio::fs::remove_dir_all(&worktree_path).await?;
        }

        // Clone the source repo if needed
        let repo_path = self.ensure_repo(source_repo).await?;

        // Create worktree at the base commit
        // First, we need to make sure the base commit is available
        let fetch_output = Command::new("git")
            .args(["fetch", "--all"])
            .current_dir(&repo_path)
            .output()
            .await?;

        if !fetch_output.status.success() {
            tracing::warn!(
                task_id = %task_id,
                stderr = %String::from_utf8_lossy(&fetch_output.stderr),
                "git fetch failed, continuing anyway"
            );
        }

        // Create the worktree from the base commit
        let output = Command::new("git")
            .args([
                "worktree",
                "add",
                "-b",
                &branch_name,
                worktree_path.to_str().ok_or_else(|| {
                    WorktreeError::InvalidPath("Invalid worktree path".to_string())
                })?,
                base_commit_sha,
            ])
            .current_dir(&repo_path)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            // If branch already exists, try without -b flag
            if stderr.contains("already exists") {
                // Remove the branch and try again
                let _ = Command::new("git")
                    .args(["branch", "-D", &branch_name])
                    .current_dir(&repo_path)
                    .output()
                    .await;

                let retry_output = Command::new("git")
                    .args([
                        "worktree",
                        "add",
                        "-b",
                        &branch_name,
                        worktree_path.to_str().unwrap(),
                        base_commit_sha,
                    ])
                    .current_dir(&repo_path)
                    .output()
                    .await?;

                if !retry_output.status.success() {
                    return Err(WorktreeError::GitCommand(format!(
                        "Failed to create worktree after retry: {}",
                        String::from_utf8_lossy(&retry_output.stderr)
                    )));
                }
            } else {
                return Err(WorktreeError::GitCommand(format!(
                    "Failed to create worktree: {}",
                    stderr
                )));
            }
        }

        // Apply the patch to restore the task's state
        if let Err(e) = storage::apply_patch(&worktree_path, patch_data).await {
            tracing::error!(
                task_id = %task_id,
                error = %e,
                "Failed to apply patch, worktree is at base commit"
            );
            // Don't fail - the worktree is usable, just at the base commit
        } else {
            tracing::info!(
                task_id = %task_id,
                worktree_path = %worktree_path.display(),
                "Successfully restored worktree from patch"
            );
        }

        Ok(WorktreeInfo {
            path: worktree_path,
            branch: branch_name,
            source_repo: repo_path,
        })
    }
}

/// Check if repo_source is a "new repo" request.
///
/// Accepts `new://` or `new://project-name` to create a fresh git repository.
pub fn is_new_repo_request(source: &str) -> bool {
    source == "new" || source == "new://" || source.starts_with("new://")
}

/// Extract optional project name from new:// URL.
fn extract_new_repo_name(source: &str) -> Option<&str> {
    source.strip_prefix("new://").filter(|s| !s.is_empty())
}

/// Extract repository name from URL.
fn extract_repo_name(url: &str) -> String {
    // Handle various URL formats:
    // https://github.com/user/repo.git -> repo
    // git@github.com:user/repo.git -> repo
    // https://github.com/user/repo -> repo

    let url = url.trim_end_matches('/');
    let url = url.trim_end_matches(".git");

    url.rsplit('/')
        .next()
        .or_else(|| url.rsplit(':').next())
        .unwrap_or("repo")
        .to_string()
}

/// Create a short UUID string for directory naming.
pub fn short_uuid(id: Uuid) -> String {
    id.to_string()[..8].to_string()
}

/// Expand tilde (~) in path to home directory.
pub fn expand_tilde(path: &str) -> PathBuf {
    if let Some(rest) = path.strip_prefix("~/") {
        if let Some(home) = dirs::home_dir() {
            return home.join(rest);
        }
    } else if path == "~" {
        if let Some(home) = dirs::home_dir() {
            return home;
        }
    }
    PathBuf::from(path)
}

/// Sanitize a name for use in directory/branch names.
pub fn sanitize_name(name: &str) -> String {
    name.chars()
        .map(|c| {
            if c.is_alphanumeric() || c == '-' || c == '_' {
                c.to_ascii_lowercase()
            } else {
                '-'
            }
        })
        .collect::<String>()
        .chars()
        .take(50) // Limit length
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use uuid::Uuid;

    #[test]
    fn test_extract_repo_name() {
        assert_eq!(
            extract_repo_name("https://github.com/user/repo.git"),
            "repo"
        );
        assert_eq!(
            extract_repo_name("https://github.com/user/repo"),
            "repo"
        );
        assert_eq!(
            extract_repo_name("git@github.com:user/repo.git"),
            "repo"
        );
    }

    #[test]
    fn test_sanitize_name() {
        assert_eq!(sanitize_name("Hello World!"), "hello-world-");
        assert_eq!(sanitize_name("test_name-123"), "test_name-123");
        assert_eq!(sanitize_name("A".repeat(100).as_str()).len(), 50);
    }

    #[test]
    fn test_short_uuid() {
        let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
        assert_eq!(short_uuid(id), "550e8400");
    }
}
