package mcp

import (
	"context"

	"github.com/google/uuid"
	mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"

	"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
	"github.com/yan1h/agent-coding-workflow/internal/infra/git"
	"github.com/yan1h/agent-coding-workflow/internal/project"
	"github.com/yan1h/agent-coding-workflow/internal/workspace"
)

// registerWorkspaceTools 注册 workspace / worktree / git 操作工具。
//
// 范围边界（spec 决策）：git 凭证（UpsertCredential / GetCredentialMeta）不经
// MCP 暴露——秘密读写仅走 Web 后台。
//
// 项目 scope 校验策略：workspace 级工具先 Get workspace 再按 ProjectID 做
// CheckProjectFromCtx；worktree 级工具要求同时给 workspace_id，并通过
// Worktrees.List 校验 worktree 归属，防止用 scope 外 worktree 绕过限定。
func registerWorkspaceTools(srv *mcpsdk.Server, deps ServerDeps) {
	registerGetWorkspace(srv, deps)
	registerCreateWorkspace(srv, deps)
	registerUpdateWorkspace(srv, deps)
	registerDeleteWorkspace(srv, deps)
	registerSyncWorkspace(srv, deps)
	registerListBranches(srv, deps)
	registerListWorktrees(srv, deps)
	registerCreateWorktree(srv, deps)
	registerDeleteWorktree(srv, deps)
	registerAcquireWorktree(srv, deps)
	registerReleaseWorktree(srv, deps)
	registerGitStatus(srv, deps)
	registerGitCommit(srv, deps)
	registerGitPush(srv, deps)
	registerGitDiff(srv, deps)
}

// ===== 共用 DTO / helper =====

type workspaceDetail struct {
	ID            string `json:"id"`
	ProjectID     string `json:"project_id"`
	Slug          string `json:"slug"`
	Name          string `json:"name"`
	Description   string `json:"description,omitempty"`
	GitRemoteURL  string `json:"git_remote_url"`
	DefaultBranch string `json:"default_branch"`
	SyncStatus    string `json:"sync_status"`
	LastSyncedAt  string `json:"last_synced_at,omitempty"`
	LastSyncError string `json:"last_sync_error,omitempty"`
	CreatedAt     string `json:"created_at"`
}

func workspaceDetailFrom(w *workspace.Workspace) workspaceDetail {
	d := workspaceDetail{
		ID: w.ID.String(), ProjectID: w.ProjectID.String(),
		Slug: w.Slug, Name: w.Name, Description: w.Description,
		GitRemoteURL: w.GitRemoteURL, DefaultBranch: w.DefaultBranch,
		SyncStatus: string(w.SyncStatus), LastSyncError: w.LastSyncError,
		CreatedAt: w.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
	}
	if w.LastSyncedAt != nil {
		d.LastSyncedAt = w.LastSyncedAt.Format("2006-01-02T15:04:05Z07:00")
	}
	return d
}

type worktreeDetail struct {
	ID           string `json:"id"`
	WorkspaceID  string `json:"workspace_id"`
	Branch       string `json:"branch"`
	Path         string `json:"path"`
	Status       string `json:"status"`
	ActiveHolder string `json:"active_holder,omitempty"`
	CreatedAt    string `json:"created_at"`
}

func worktreeDetailFrom(wt *workspace.Worktree) worktreeDetail {
	return worktreeDetail{
		ID: wt.ID.String(), WorkspaceID: wt.WorkspaceID.String(),
		Branch: wt.Branch, Path: wt.Path, Status: string(wt.Status),
		ActiveHolder: wt.ActiveHolder,
		CreatedAt:    wt.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
	}
}

// scopedWorkspace 解析 workspace_id、取 workspace 并做项目 scope 校验。
// 返回的 workspace.Caller 供后续 service 调用复用。
func scopedWorkspace(ctx context.Context, deps ServerDeps, c project.Caller, workspaceID string) (*workspace.Workspace, workspace.Caller, error) {
	wsID, err := uuid.Parse(workspaceID)
	if err != nil {
		return nil, workspace.Caller{}, errs.Wrap(err, errs.CodeInvalidInput, "workspace_id parse")
	}
	wc := workspaceCallerFrom(c)
	w, err := deps.Workspaces.Get(ctx, wc, wsID)
	if err != nil {
		return nil, wc, err
	}
	if err := CheckProjectFromCtx(ctx, w.ProjectID); err != nil {
		return nil, wc, err
	}
	return w, wc, nil
}

// worktreeInWorkspace 校验 worktree 归属于给定 workspace 并返回该 worktree。
func worktreeInWorkspace(ctx context.Context, deps ServerDeps, wc workspace.Caller, wsID uuid.UUID, worktreeID string) (*workspace.Worktree, error) {
	wtID, err := uuid.Parse(worktreeID)
	if err != nil {
		return nil, errs.Wrap(err, errs.CodeInvalidInput, "worktree_id parse")
	}
	wts, err := deps.Worktrees.List(ctx, wc, wsID)
	if err != nil {
		return nil, err
	}
	for _, wt := range wts {
		if wt.ID == wtID {
			return wt, nil
		}
	}
	return nil, errs.New(errs.CodeNotFound, "worktree not found in workspace")
}

// ===== get_workspace =====

type getWorkspaceIn struct {
	WorkspaceID string `json:"workspace_id" jsonschema:"workspace UUID"`
}

func registerGetWorkspace(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "get_workspace", Description: "Get a single workspace by ID."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in getWorkspaceIn) (*mcpsdk.CallToolResult, workspaceDetail, error) {
			if err := CheckToolFromCtx(ctx, "get_workspace"); err != nil {
				return nil, workspaceDetail{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, workspaceDetail{}, err
			}
			w, _, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, workspaceDetail{}, err
			}
			return nil, workspaceDetailFrom(w), nil
		})
}

// ===== create_workspace =====

type createWorkspaceIn struct {
	ProjectSlug   string `json:"project_slug" jsonschema:"project slug"`
	Slug          string `json:"slug" jsonschema:"workspace slug (non-empty)"`
	Name          string `json:"name" jsonschema:"display name (non-empty)"`
	Description   string `json:"description,omitempty"`
	GitRemoteURL  string `json:"git_remote_url" jsonschema:"git remote URL to clone"`
	DefaultBranch string `json:"default_branch,omitempty" jsonschema:"default branch (service default when empty)"`
}
type workspaceCreateOut struct {
	OK        bool            `json:"ok"`
	Workspace workspaceDetail `json:"workspace"`
}

func registerCreateWorkspace(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "create_workspace", Description: "Create a workspace in a project. Git credentials cannot be set via MCP; private repos need credentials configured in the web console."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in createWorkspaceIn) (*mcpsdk.CallToolResult, workspaceCreateOut, error) {
			if err := CheckToolFromCtx(ctx, "create_workspace"); err != nil {
				return nil, workspaceCreateOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, workspaceCreateOut{}, err
			}
			p, err := deps.Projects.Get(ctx, c, in.ProjectSlug)
			if err != nil {
				return nil, workspaceCreateOut{}, err
			}
			if err := CheckProjectFromCtx(ctx, p.ID); err != nil {
				return nil, workspaceCreateOut{}, err
			}
			w, err := deps.Workspaces.Create(ctx, workspaceCallerFrom(c), in.ProjectSlug, workspace.CreateWorkspaceInput{
				Slug: in.Slug, Name: in.Name, Description: in.Description,
				GitRemoteURL: in.GitRemoteURL, DefaultBranch: in.DefaultBranch,
				Credential: workspace.UpsertCredentialInput{Kind: workspace.CredKindNone},
			})
			if err != nil {
				return nil, workspaceCreateOut{}, err
			}
			return nil, workspaceCreateOut{OK: true, Workspace: workspaceDetailFrom(w)}, nil
		})
}

// ===== update_workspace =====

type updateWorkspaceIn struct {
	WorkspaceID   string `json:"workspace_id" jsonschema:"workspace UUID"`
	Name          string `json:"name,omitempty"`
	Description   string `json:"description,omitempty"`
	DefaultBranch string `json:"default_branch,omitempty"`
}

func registerUpdateWorkspace(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "update_workspace", Description: "Update a workspace (name/description/default_branch)."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in updateWorkspaceIn) (*mcpsdk.CallToolResult, workspaceCreateOut, error) {
			if err := CheckToolFromCtx(ctx, "update_workspace"); err != nil {
				return nil, workspaceCreateOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, workspaceCreateOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, workspaceCreateOut{}, err
			}
			input := workspace.UpdateWorkspaceInput{}
			if in.Name != "" {
				input.Name = &in.Name
			}
			if in.Description != "" {
				input.Description = &in.Description
			}
			if in.DefaultBranch != "" {
				input.DefaultBranch = &in.DefaultBranch
			}
			w, err = deps.Workspaces.Update(ctx, wc, w.ID, input)
			if err != nil {
				return nil, workspaceCreateOut{}, err
			}
			return nil, workspaceCreateOut{OK: true, Workspace: workspaceDetailFrom(w)}, nil
		})
}

// ===== delete_workspace =====

type deleteWorkspaceIn struct {
	WorkspaceID string `json:"workspace_id" jsonschema:"workspace UUID"`
}

func registerDeleteWorkspace(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "delete_workspace", Description: "Delete a workspace and its local clone. Irreversible."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in deleteWorkspaceIn) (*mcpsdk.CallToolResult, okOut, error) {
			if err := CheckToolFromCtx(ctx, "delete_workspace"); err != nil {
				return nil, okOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, okOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, okOut{}, err
			}
			if err := deps.Workspaces.Delete(ctx, wc, w.ID); err != nil {
				return nil, okOut{}, err
			}
			return nil, okOut{OK: true}, nil
		})
}

// ===== sync_workspace =====

func registerSyncWorkspace(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "sync_workspace", Description: "Trigger a git clone/fetch sync of the workspace."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in getWorkspaceIn) (*mcpsdk.CallToolResult, workspaceCreateOut, error) {
			if err := CheckToolFromCtx(ctx, "sync_workspace"); err != nil {
				return nil, workspaceCreateOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, workspaceCreateOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, workspaceCreateOut{}, err
			}
			w, err = deps.Workspaces.Sync(ctx, wc, w.ID)
			if err != nil {
				return nil, workspaceCreateOut{}, err
			}
			return nil, workspaceCreateOut{OK: true, Workspace: workspaceDetailFrom(w)}, nil
		})
}

// ===== list_branches =====

type listBranchesOut struct {
	Branches []string `json:"branches"`
}

func registerListBranches(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "list_branches", Description: "List git branches of a workspace."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in getWorkspaceIn) (*mcpsdk.CallToolResult, listBranchesOut, error) {
			if err := CheckToolFromCtx(ctx, "list_branches"); err != nil {
				return nil, listBranchesOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, listBranchesOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, listBranchesOut{}, err
			}
			bs, err := deps.Workspaces.ListBranches(ctx, wc, w.ID)
			if err != nil {
				return nil, listBranchesOut{}, err
			}
			return nil, listBranchesOut{Branches: bs}, nil
		})
}

// ===== list_worktrees =====

type listWorktreesOut struct {
	Worktrees []worktreeDetail `json:"worktrees"`
}

func registerListWorktrees(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "list_worktrees", Description: "List worktrees of a workspace."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in getWorkspaceIn) (*mcpsdk.CallToolResult, listWorktreesOut, error) {
			if err := CheckToolFromCtx(ctx, "list_worktrees"); err != nil {
				return nil, listWorktreesOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, listWorktreesOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, listWorktreesOut{}, err
			}
			wts, err := deps.Worktrees.List(ctx, wc, w.ID)
			if err != nil {
				return nil, listWorktreesOut{}, err
			}
			out := listWorktreesOut{Worktrees: make([]worktreeDetail, 0, len(wts))}
			for _, wt := range wts {
				out.Worktrees = append(out.Worktrees, worktreeDetailFrom(wt))
			}
			return nil, out, nil
		})
}

// ===== create_worktree =====

type createWorktreeIn struct {
	WorkspaceID string `json:"workspace_id" jsonschema:"workspace UUID"`
	Branch      string `json:"branch" jsonschema:"branch name for the new worktree"`
	BaseBranch  string `json:"base_branch,omitempty" jsonschema:"base branch (defaults to workspace default branch)"`
}
type worktreeCreateOut struct {
	OK       bool           `json:"ok"`
	Worktree worktreeDetail `json:"worktree"`
}

func registerCreateWorktree(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "create_worktree", Description: "Create a worktree on a branch."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in createWorktreeIn) (*mcpsdk.CallToolResult, worktreeCreateOut, error) {
			if err := CheckToolFromCtx(ctx, "create_worktree"); err != nil {
				return nil, worktreeCreateOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, worktreeCreateOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, worktreeCreateOut{}, err
			}
			wt, err := deps.Worktrees.Create(ctx, wc, w.ID, workspace.CreateWorktreeInput{
				Branch: in.Branch, BaseBranch: in.BaseBranch,
			})
			if err != nil {
				return nil, worktreeCreateOut{}, err
			}
			return nil, worktreeCreateOut{OK: true, Worktree: worktreeDetailFrom(wt)}, nil
		})
}

// ===== delete/acquire/release_worktree =====

type worktreeTargetIn struct {
	WorkspaceID string `json:"workspace_id" jsonschema:"workspace UUID"`
	WorktreeID  string `json:"worktree_id" jsonschema:"worktree UUID"`
}

func registerDeleteWorktree(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "delete_worktree", Description: "Delete a worktree. Irreversible."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in worktreeTargetIn) (*mcpsdk.CallToolResult, okOut, error) {
			if err := CheckToolFromCtx(ctx, "delete_worktree"); err != nil {
				return nil, okOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, okOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, okOut{}, err
			}
			wt, err := worktreeInWorkspace(ctx, deps, wc, w.ID, in.WorktreeID)
			if err != nil {
				return nil, okOut{}, err
			}
			if err := deps.Worktrees.Delete(ctx, wc, wt.ID); err != nil {
				return nil, okOut{}, err
			}
			return nil, okOut{OK: true}, nil
		})
}

func registerAcquireWorktree(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "acquire_worktree", Description: "Acquire (lock) a worktree for exclusive use."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in worktreeTargetIn) (*mcpsdk.CallToolResult, worktreeCreateOut, error) {
			if err := CheckToolFromCtx(ctx, "acquire_worktree"); err != nil {
				return nil, worktreeCreateOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, worktreeCreateOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, worktreeCreateOut{}, err
			}
			wt, err := worktreeInWorkspace(ctx, deps, wc, w.ID, in.WorktreeID)
			if err != nil {
				return nil, worktreeCreateOut{}, err
			}
			wt, err = deps.Worktrees.Acquire(ctx, wc, wt.ID)
			if err != nil {
				return nil, worktreeCreateOut{}, err
			}
			return nil, worktreeCreateOut{OK: true, Worktree: worktreeDetailFrom(wt)}, nil
		})
}

func registerReleaseWorktree(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "release_worktree", Description: "Release a previously acquired worktree."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in worktreeTargetIn) (*mcpsdk.CallToolResult, worktreeCreateOut, error) {
			if err := CheckToolFromCtx(ctx, "release_worktree"); err != nil {
				return nil, worktreeCreateOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, worktreeCreateOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, worktreeCreateOut{}, err
			}
			wt, err := worktreeInWorkspace(ctx, deps, wc, w.ID, in.WorktreeID)
			if err != nil {
				return nil, worktreeCreateOut{}, err
			}
			wt, err = deps.Worktrees.Release(ctx, wc, wt.ID)
			if err != nil {
				return nil, worktreeCreateOut{}, err
			}
			return nil, worktreeCreateOut{OK: true, Worktree: worktreeDetailFrom(wt)}, nil
		})
}

// ===== git_status / git_commit / git_push =====
//
// 三个 git 工具统一用 workspace_id + 可选 worktree_id 定位目标：
// worktree_id 缺省 → 操作 main 工作区；给定 → 校验归属后操作该支线。

type gitTargetIn struct {
	WorkspaceID string `json:"workspace_id" jsonschema:"workspace UUID"`
	WorktreeID  string `json:"worktree_id,omitempty" jsonschema:"optional worktree UUID; omit to target the main worktree"`
}
type gitFileStatus struct {
	Path     string `json:"path"`
	Staged   string `json:"staged"`
	Unstaged string `json:"unstaged"`
}
type gitStatusOut struct {
	Files []gitFileStatus `json:"files"`
}

func registerGitStatus(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "git_status", Description: "Show git status of the main worktree or a branch worktree."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in gitTargetIn) (*mcpsdk.CallToolResult, gitStatusOut, error) {
			if err := CheckToolFromCtx(ctx, "git_status"); err != nil {
				return nil, gitStatusOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, gitStatusOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, gitStatusOut{}, err
			}
			files, err := gitStatusByTarget(ctx, deps, wc, w.ID, in.WorktreeID)
			if err != nil {
				return nil, gitStatusOut{}, err
			}
			out := gitStatusOut{Files: make([]gitFileStatus, 0, len(files))}
			for _, f := range files {
				out.Files = append(out.Files, gitFileStatus{
					Path: f.Path, Staged: string(f.IndexX), Unstaged: string(f.WorktreeY),
				})
			}
			return nil, out, nil
		})
}

func gitStatusByTarget(ctx context.Context, deps ServerDeps, wc workspace.Caller, wsID uuid.UUID, worktreeID string) ([]git.FileStatus, error) {
	if worktreeID == "" {
		return deps.GitOps.StatusOnMain(ctx, wc, wsID)
	}
	wt, err := worktreeInWorkspace(ctx, deps, wc, wsID, worktreeID)
	if err != nil {
		return nil, err
	}
	return deps.GitOps.StatusOnWorktree(ctx, wc, wt.ID)
}

type createCommitIn struct {
	WorkspaceID string `json:"workspace_id" jsonschema:"workspace UUID"`
	WorktreeID  string `json:"worktree_id,omitempty" jsonschema:"optional worktree UUID; omit to target the main worktree"`
	Message     string `json:"message" jsonschema:"commit message (non-empty)"`
	AddAll      *bool  `json:"add_all,omitempty" jsonschema:"stage all changes before commit (default true)"`
	Push        bool   `json:"push,omitempty" jsonschema:"push after commit (default false)"`
}
type gitCommitOut struct {
	OK         bool   `json:"ok"`
	CommitHash string `json:"commit_hash"`
}

func registerGitCommit(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "git_commit", Description: "Commit changes on the main worktree or a branch worktree."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in createCommitIn) (*mcpsdk.CallToolResult, gitCommitOut, error) {
			if err := CheckToolFromCtx(ctx, "git_commit"); err != nil {
				return nil, gitCommitOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, gitCommitOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, gitCommitOut{}, err
			}
			addAll := true
			if in.AddAll != nil {
				addAll = *in.AddAll
			}
			commit := workspace.CommitInput{Message: in.Message, AddAll: addAll, Push: in.Push}
			var hash string
			if in.WorktreeID == "" {
				hash, err = deps.GitOps.CommitOnMain(ctx, wc, w.ID, commit)
			} else {
				var wt *workspace.Worktree
				wt, err = worktreeInWorkspace(ctx, deps, wc, w.ID, in.WorktreeID)
				if err != nil {
					return nil, gitCommitOut{}, err
				}
				hash, err = deps.GitOps.CommitOnWorktree(ctx, wc, wt.ID, commit)
			}
			if err != nil {
				return nil, gitCommitOut{}, err
			}
			return nil, gitCommitOut{OK: true, CommitHash: hash}, nil
		})
}

// ===== git_diff (read-only) =====

type gitDiffIn struct {
	WorkspaceID  string   `json:"workspace_id" jsonschema:"workspace UUID"`
	WorktreeID   string   `json:"worktree_id,omitempty" jsonschema:"optional worktree UUID; omit to target the main worktree"`
	Ref          string   `json:"ref,omitempty" jsonschema:"base ref; empty diffs working tree against HEAD"`
	Against      string   `json:"against,omitempty" jsonschema:"second ref; when set with ref produces ref..against"`
	Staged       bool     `json:"staged,omitempty" jsonschema:"diff the staged index (--cached) instead of the working tree"`
	Paths        []string `json:"paths,omitempty" jsonschema:"optional pathspecs to restrict the diff"`
	ContextLines int      `json:"context_lines,omitempty" jsonschema:"unified context lines (git default 3 when omitted)"`
}
type gitDiffOut struct {
	Diff      string `json:"diff"`
	Truncated bool   `json:"truncated"`
}

func registerGitDiff(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "git_diff", Description: "Show the unified git diff of the main worktree or a branch worktree. Returns raw diff text (capped at 1 MiB)."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in gitDiffIn) (*mcpsdk.CallToolResult, gitDiffOut, error) {
			if err := CheckToolFromCtx(ctx, "git_diff"); err != nil {
				return nil, gitDiffOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, gitDiffOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, gitDiffOut{}, err
			}
			opts := git.DiffOptions{
				Ref: in.Ref, Against: in.Against, Staged: in.Staged,
				Paths: in.Paths, ContextLines: in.ContextLines,
			}
			var res git.DiffResult
			if in.WorktreeID == "" {
				res, err = deps.GitOps.DiffOnMain(ctx, wc, w.ID, opts)
			} else {
				var wt *workspace.Worktree
				wt, err = worktreeInWorkspace(ctx, deps, wc, w.ID, in.WorktreeID)
				if err != nil {
					return nil, gitDiffOut{}, err
				}
				res, err = deps.GitOps.DiffOnWorktree(ctx, wc, wt.ID, opts)
			}
			if err != nil {
				return nil, gitDiffOut{}, err
			}
			return nil, gitDiffOut{Diff: res.Diff, Truncated: res.Truncated}, nil
		})
}

func registerGitPush(srv *mcpsdk.Server, deps ServerDeps) {
	mcpsdk.AddTool(srv,
		&mcpsdk.Tool{Name: "git_push", Description: "Push the main worktree or a branch worktree to the remote."},
		func(ctx context.Context, _ *mcpsdk.CallToolRequest, in gitTargetIn) (*mcpsdk.CallToolResult, okOut, error) {
			if err := CheckToolFromCtx(ctx, "git_push"); err != nil {
				return nil, okOut{}, err
			}
			c, err := deps.Caller.Resolve(ctx)
			if err != nil {
				return nil, okOut{}, err
			}
			w, wc, err := scopedWorkspace(ctx, deps, c, in.WorkspaceID)
			if err != nil {
				return nil, okOut{}, err
			}
			if in.WorktreeID == "" {
				err = deps.GitOps.PushOnMain(ctx, wc, w.ID)
			} else {
				var wt *workspace.Worktree
				wt, err = worktreeInWorkspace(ctx, deps, wc, w.ID, in.WorktreeID)
				if err != nil {
					return nil, okOut{}, err
				}
				err = deps.GitOps.PushOnWorktree(ctx, wc, wt.ID)
			}
			if err != nil {
				return nil, okOut{}, err
			}
			return nil, okOut{OK: true}, nil
		})
}
