package rpc

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/steveyegge/beads/internal/storage/sqlite"
	"github.com/steveyegge/beads/internal/types"
	"github.com/steveyegge/beads/internal/util"
	"github.com/steveyegge/beads/internal/utils"
)

// parseTimeRPC parses time strings in multiple formats (RFC3339, YYYY-MM-DD, etc.)
// Matches the parseTimeFlag behavior in cmd/bd/list.go for CLI parity
func parseTimeRPC(s string) (time.Time, error) {
	// Try RFC3339 first (ISO 8601 with timezone)
	if t, err := time.Parse(time.RFC3339, s); err == nil {
		return t, nil
	}
	
	// Try YYYY-MM-DD format (common user input)
	if t, err := time.Parse("2006-01-02", s); err == nil {
		return t, nil
	}
	
	// Try YYYY-MM-DD HH:MM:SS format
	if t, err := time.Parse("2006-01-02 15:04:05", s); err == nil {
		return t, nil
	}
	
	return time.Time{}, fmt.Errorf("unsupported date format: %q (use YYYY-MM-DD or RFC3339)", s)
}

func strValue(p *string) string {
	if p == nil {
		return ""
	}
	return *p
}

func updatesFromArgs(a UpdateArgs) map[string]interface{} {
	u := map[string]interface{}{}
	if a.Title != nil {
		u["title"] = *a.Title
	}
	if a.Description != nil {
		u["description"] = *a.Description
	}
	if a.Status != nil {
		u["status"] = *a.Status
	}
	if a.Priority != nil {
		u["priority"] = *a.Priority
	}
	if a.Design != nil {
		u["design"] = *a.Design
	}
	if a.AcceptanceCriteria != nil {
		u["acceptance_criteria"] = *a.AcceptanceCriteria
	}
	if a.Notes != nil {
		u["notes"] = *a.Notes
	}
	if a.Assignee != nil {
		u["assignee"] = *a.Assignee
	}
	if a.ExternalRef != nil {
		u["external_ref"] = *a.ExternalRef
	}
	if a.EstimatedMinutes != nil {
		u["estimated_minutes"] = *a.EstimatedMinutes
	}
	if a.IssueType != nil {
		u["issue_type"] = *a.IssueType
	}
	// Messaging fields
	if a.Sender != nil {
		u["sender"] = *a.Sender
	}
	if a.Ephemeral != nil {
		u["ephemeral"] = *a.Ephemeral
	}
	if a.RepliesTo != nil {
		u["replies_to"] = *a.RepliesTo
	}
	// Graph link fields
	if a.RelatesTo != nil {
		u["relates_to"] = *a.RelatesTo
	}
	if a.DuplicateOf != nil {
		u["duplicate_of"] = *a.DuplicateOf
	}
	if a.SupersededBy != nil {
		u["superseded_by"] = *a.SupersededBy
	}
	// Pinned field
	if a.Pinned != nil {
		u["pinned"] = *a.Pinned
	}
	// Agent slot fields
	if a.HookBead != nil {
		u["hook_bead"] = *a.HookBead
	}
	if a.RoleBead != nil {
		u["role_bead"] = *a.RoleBead
	}
	// Agent state fields
	if a.AgentState != nil {
		u["agent_state"] = *a.AgentState
	}
	if a.LastActivity != nil && *a.LastActivity {
		u["last_activity"] = time.Now()
	}
	return u
}

func (s *Server) handleCreate(req *Request) Response {
	var createArgs CreateArgs
	if err := json.Unmarshal(req.Args, &createArgs); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid create args: %v", err),
		}
	}

	// Check for conflicting flags
	if createArgs.ID != "" && createArgs.Parent != "" {
		return Response{
			Success: false,
			Error:   "cannot specify both ID and Parent",
		}
	}

	// Warn if creating an issue without a description (unless it's a test issue)
	if createArgs.Description == "" && !strings.Contains(strings.ToLower(createArgs.Title), "test") {
		// Log warning to daemon logs (stderr goes to daemon logs)
		fmt.Fprintf(os.Stderr, "[WARNING] Creating issue '%s' without description. Issues without descriptions lack context for future work.\n", createArgs.Title)
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}
	ctx := s.reqCtx(req)

	// If parent is specified, generate child ID
	issueID := createArgs.ID
	if createArgs.Parent != "" {
		childID, err := store.GetNextChildID(ctx, createArgs.Parent)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to generate child ID: %v", err),
			}
		}
		issueID = childID
	}

	var design, acceptance, assignee, externalRef *string
	if createArgs.Design != "" {
		design = &createArgs.Design
	}
	if createArgs.AcceptanceCriteria != "" {
		acceptance = &createArgs.AcceptanceCriteria
	}
	if createArgs.Assignee != "" {
		assignee = &createArgs.Assignee
	}
	if createArgs.ExternalRef != "" {
		externalRef = &createArgs.ExternalRef
	}

	issue := &types.Issue{
		ID:                 issueID,
		Title:              createArgs.Title,
		Description:        createArgs.Description,
		IssueType:          types.IssueType(createArgs.IssueType),
		Priority:           createArgs.Priority,
		Design:             strValue(design),
		AcceptanceCriteria: strValue(acceptance),
		Assignee:           strValue(assignee),
		ExternalRef:        externalRef,
		EstimatedMinutes:   createArgs.EstimatedMinutes,
		Status:             types.StatusOpen,
		// Messaging fields
		Sender:    createArgs.Sender,
		Ephemeral: createArgs.Ephemeral,
		// NOTE: RepliesTo now handled via replies-to dependency (Decision 004)
		// ID generation
		IDPrefix:  createArgs.IDPrefix,
		CreatedBy: createArgs.CreatedBy,
	}
	
	// Check if any dependencies are discovered-from type
	// If so, inherit source_repo from the parent issue
	var discoveredFromParentID string
	for _, depSpec := range createArgs.Dependencies {
		depSpec = strings.TrimSpace(depSpec)
		if depSpec == "" {
			continue
		}
		
		var depType types.DependencyType
		var dependsOnID string
		
		if strings.Contains(depSpec, ":") {
			parts := strings.SplitN(depSpec, ":", 2)
			if len(parts) == 2 {
				depType = types.DependencyType(strings.TrimSpace(parts[0]))
				dependsOnID = strings.TrimSpace(parts[1])
				
				if depType == types.DepDiscoveredFrom {
					discoveredFromParentID = dependsOnID
					break
				}
			}
		}
	}
	
	// If we found a discovered-from dependency, inherit source_repo from parent
	if discoveredFromParentID != "" {
		parentIssue, err := store.GetIssue(ctx, discoveredFromParentID)
		if err == nil && parentIssue.SourceRepo != "" {
			issue.SourceRepo = parentIssue.SourceRepo
		}
		// If error getting parent or parent has no source_repo, continue with default
	}
	
	if err := store.CreateIssue(ctx, issue, s.reqActor(req)); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to create issue: %v", err),
		}
	}

	// If parent was specified, add parent-child dependency
	if createArgs.Parent != "" {
		dep := &types.Dependency{
			IssueID:     issue.ID,
			DependsOnID: createArgs.Parent,
			Type:        types.DepParentChild,
		}
		if err := store.AddDependency(ctx, dep, s.reqActor(req)); err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to add parent-child dependency %s -> %s: %v", issue.ID, createArgs.Parent, err),
			}
		}
	}

	// If RepliesTo was specified, add replies-to dependency (Decision 004)
	if createArgs.RepliesTo != "" {
		dep := &types.Dependency{
			IssueID:     issue.ID,
			DependsOnID: createArgs.RepliesTo,
			Type:        types.DepRepliesTo,
			ThreadID:    createArgs.RepliesTo, // Use parent ID as thread root
		}
		if err := store.AddDependency(ctx, dep, s.reqActor(req)); err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to add replies-to dependency %s -> %s: %v", issue.ID, createArgs.RepliesTo, err),
			}
		}
	}

	// Add labels if specified
	for _, label := range createArgs.Labels {
		if err := store.AddLabel(ctx, issue.ID, label, s.reqActor(req)); err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to add label %s: %v", label, err),
			}
		}
	}

	// Add dependencies if specified
	for _, depSpec := range createArgs.Dependencies {
		depSpec = strings.TrimSpace(depSpec)
		if depSpec == "" {
			continue
		}

		var depType types.DependencyType
		var dependsOnID string

		if strings.Contains(depSpec, ":") {
			parts := strings.SplitN(depSpec, ":", 2)
			if len(parts) != 2 {
				return Response{
					Success: false,
					Error:   fmt.Sprintf("invalid dependency format '%s', expected 'type:id' or 'id'", depSpec),
				}
			}
			depType = types.DependencyType(strings.TrimSpace(parts[0]))
			dependsOnID = strings.TrimSpace(parts[1])
		} else {
			depType = types.DepBlocks
			dependsOnID = depSpec
		}

		if !depType.IsValid() {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid dependency type '%s' (valid: blocks, related, parent-child, discovered-from)", depType),
			}
		}

		dep := &types.Dependency{
			IssueID:     issue.ID,
			DependsOnID: dependsOnID,
			Type:        depType,
		}
		if err := store.AddDependency(ctx, dep, s.reqActor(req)); err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to add dependency %s -> %s: %v", issue.ID, dependsOnID, err),
			}
		}
	}

	// Add waits-for dependency if specified
	if createArgs.WaitsFor != "" {
		// Validate gate type
		gate := createArgs.WaitsForGate
		if gate == "" {
			gate = types.WaitsForAllChildren
		}
		if gate != types.WaitsForAllChildren && gate != types.WaitsForAnyChildren {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid waits_for_gate value '%s' (valid: all-children, any-children)", gate),
			}
		}

		// Create metadata JSON
		meta := types.WaitsForMeta{
			Gate: gate,
		}
		metaJSON, err := json.Marshal(meta)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to serialize waits-for metadata: %v", err),
			}
		}

		dep := &types.Dependency{
			IssueID:     issue.ID,
			DependsOnID: createArgs.WaitsFor,
			Type:        types.DepWaitsFor,
			Metadata:    string(metaJSON),
		}
		if err := store.AddDependency(ctx, dep, s.reqActor(req)); err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to add waits-for dependency %s -> %s: %v", issue.ID, createArgs.WaitsFor, err),
			}
		}
	}

	// Emit mutation event for event-driven daemon
	s.emitMutation(MutationCreate, issue.ID, issue.Title, issue.Assignee)

	data, _ := json.Marshal(issue)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleUpdate(req *Request) Response {
	var updateArgs UpdateArgs
	if err := json.Unmarshal(req.Args, &updateArgs); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid update args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	ctx := s.reqCtx(req)

	// Check if issue is a template (beads-1ra): templates are read-only
	issue, err := store.GetIssue(ctx, updateArgs.ID)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get issue: %v", err),
		}
	}
	if issue == nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("issue %s not found", updateArgs.ID),
		}
	}
	if issue.IsTemplate {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("cannot update template %s: templates are read-only; use 'bd molecule instantiate' to create a work item", updateArgs.ID),
		}
	}

	updates := updatesFromArgs(updateArgs)
	actor := s.reqActor(req)

	// Apply regular field updates if any
	if len(updates) > 0 {
		if err := store.UpdateIssue(ctx, updateArgs.ID, updates, actor); err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to update issue: %v", err),
			}
		}
	}

	// Handle label operations
	// Set labels (replaces all existing labels)
	if len(updateArgs.SetLabels) > 0 {
		// Get current labels
		currentLabels, err := store.GetLabels(ctx, updateArgs.ID)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to get current labels: %v", err),
			}
		}
		// Remove all current labels
		for _, label := range currentLabels {
			if err := store.RemoveLabel(ctx, updateArgs.ID, label, actor); err != nil {
				return Response{
					Success: false,
					Error:   fmt.Sprintf("failed to remove label %s: %v", label, err),
				}
			}
		}
		// Add new labels
		for _, label := range updateArgs.SetLabels {
			if err := store.AddLabel(ctx, updateArgs.ID, label, actor); err != nil {
				return Response{
					Success: false,
					Error:   fmt.Sprintf("failed to set label %s: %v", label, err),
				}
			}
		}
	}

	// Add labels
	for _, label := range updateArgs.AddLabels {
		if err := store.AddLabel(ctx, updateArgs.ID, label, actor); err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to add label %s: %v", label, err),
			}
		}
	}

	// Remove labels
	for _, label := range updateArgs.RemoveLabels {
		if err := store.RemoveLabel(ctx, updateArgs.ID, label, actor); err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to remove label %s: %v", label, err),
			}
		}
	}

	// Handle reparenting
	if updateArgs.Parent != nil {
		newParentID := *updateArgs.Parent

		// Validate new parent exists (unless empty string to remove parent)
		if newParentID != "" {
			newParent, err := store.GetIssue(ctx, newParentID)
			if err != nil {
				return Response{
					Success: false,
					Error:   fmt.Sprintf("failed to get new parent: %v", err),
				}
			}
			if newParent == nil {
				return Response{
					Success: false,
					Error:   fmt.Sprintf("parent issue %s not found", newParentID),
				}
			}
		}

		// Find and remove existing parent-child dependency
		deps, err := store.GetDependencyRecords(ctx, updateArgs.ID)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to get dependencies: %v", err),
			}
		}
		for _, dep := range deps {
			if dep.Type == types.DepParentChild {
				if err := store.RemoveDependency(ctx, updateArgs.ID, dep.DependsOnID, actor); err != nil {
					return Response{
						Success: false,
						Error:   fmt.Sprintf("failed to remove old parent dependency: %v", err),
					}
				}
				break // Only one parent-child dependency expected
			}
		}

		// Add new parent-child dependency (if not removing parent)
		if newParentID != "" {
			newDep := &types.Dependency{
				IssueID:     updateArgs.ID,
				DependsOnID: newParentID,
				Type:        types.DepParentChild,
			}
			if err := store.AddDependency(ctx, newDep, actor); err != nil {
				return Response{
					Success: false,
					Error:   fmt.Sprintf("failed to add parent dependency: %v", err),
				}
			}
		}
	}

	// Emit mutation event for event-driven daemon (only if any updates or label/parent operations were performed)
	if len(updates) > 0 || len(updateArgs.SetLabels) > 0 || len(updateArgs.AddLabels) > 0 || len(updateArgs.RemoveLabels) > 0 || updateArgs.Parent != nil {
		// Check if this was a status change - emit rich MutationStatus event
		if updateArgs.Status != nil && *updateArgs.Status != string(issue.Status) {
			s.emitRichMutation(MutationEvent{
				Type:      MutationStatus,
				IssueID:   updateArgs.ID,
				Title:     issue.Title,
				Assignee:  issue.Assignee,
				OldStatus: string(issue.Status),
				NewStatus: *updateArgs.Status,
			})
		} else {
			s.emitMutation(MutationUpdate, updateArgs.ID, issue.Title, issue.Assignee)
		}
	}

	updatedIssue, getErr := store.GetIssue(ctx, updateArgs.ID)
	if getErr != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get updated issue: %v", getErr),
		}
	}

	data, _ := json.Marshal(updatedIssue)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleClose(req *Request) Response {
	var closeArgs CloseArgs
	if err := json.Unmarshal(req.Args, &closeArgs); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid close args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	ctx := s.reqCtx(req)

	// Check if issue is a template (beads-1ra): templates are read-only
	issue, err := store.GetIssue(ctx, closeArgs.ID)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get issue: %v", err),
		}
	}
	if issue != nil && issue.IsTemplate {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("cannot close template %s: templates are read-only", closeArgs.ID),
		}
	}

	// Capture old status for rich mutation event
	oldStatus := ""
	if issue != nil {
		oldStatus = string(issue.Status)
	}

	if err := store.CloseIssue(ctx, closeArgs.ID, closeArgs.Reason, s.reqActor(req)); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to close issue: %v", err),
		}
	}

	// Emit rich status change event for event-driven daemon
	s.emitRichMutation(MutationEvent{
		Type:      MutationStatus,
		IssueID:   closeArgs.ID,
		Title:     issue.Title,
		Assignee:  issue.Assignee,
		OldStatus: oldStatus,
		NewStatus: "closed",
	})

	closedIssue, _ := store.GetIssue(ctx, closeArgs.ID)

	// If SuggestNext is requested, find newly unblocked issues (GH#679)
	if closeArgs.SuggestNext {
		unblocked, err := store.GetNewlyUnblockedByClose(ctx, closeArgs.ID)
		if err != nil {
			// Non-fatal: still return the closed issue
			unblocked = nil
		}
		result := CloseResult{
			Closed:    closedIssue,
			Unblocked: unblocked,
		}
		data, _ := json.Marshal(result)
		return Response{
			Success: true,
			Data:    data,
		}
	}

	// Backward compatible: just return the closed issue
	data, _ := json.Marshal(closedIssue)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleDelete(req *Request) Response {
	var deleteArgs DeleteArgs
	if err := json.Unmarshal(req.Args, &deleteArgs); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid delete args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	// Validate that we have issue IDs to delete
	if len(deleteArgs.IDs) == 0 {
		return Response{
			Success: false,
			Error:   "no issue IDs provided for deletion",
		}
	}

	// DryRun mode: just return what would be deleted
	if deleteArgs.DryRun {
		data, _ := json.Marshal(map[string]interface{}{
			"dry_run":     true,
			"issue_count": len(deleteArgs.IDs),
			"issues":      deleteArgs.IDs,
		})
		return Response{
			Success: true,
			Data:    data,
		}
	}

	ctx := s.reqCtx(req)
	deletedCount := 0
	errors := make([]string, 0)

	// Delete each issue
	for _, issueID := range deleteArgs.IDs {
		// Verify issue exists before deleting
		issue, err := store.GetIssue(ctx, issueID)
		if err != nil {
			errors = append(errors, fmt.Sprintf("%s: %v", issueID, err))
			continue
		}
		if issue == nil {
			errors = append(errors, fmt.Sprintf("%s: not found", issueID))
			continue
		}

		// Check if issue is a template (beads-1ra): templates are read-only
		if issue.IsTemplate {
			errors = append(errors, fmt.Sprintf("%s: cannot delete template (templates are read-only)", issueID))
			continue
		}

		// Create tombstone instead of hard delete
		// This preserves deletion history and prevents resurrection during sync
		type tombstoner interface {
			CreateTombstone(ctx context.Context, id string, actor string, reason string) error
		}
		if t, ok := store.(tombstoner); ok {
			reason := deleteArgs.Reason
			if reason == "" {
				reason = "deleted via daemon"
			}
			if err := t.CreateTombstone(ctx, issueID, "daemon", reason); err != nil {
				errors = append(errors, fmt.Sprintf("%s: %v", issueID, err))
				continue
			}
		} else {
			// Fallback to hard delete if CreateTombstone not available
			if err := store.DeleteIssue(ctx, issueID); err != nil {
				errors = append(errors, fmt.Sprintf("%s: %v", issueID, err))
				continue
			}
		}

		// Emit mutation event for event-driven daemon
		s.emitMutation(MutationDelete, issueID, issue.Title, issue.Assignee)
		deletedCount++
	}

	// Build response
	result := map[string]interface{}{
		"deleted_count": deletedCount,
		"total_count":   len(deleteArgs.IDs),
	}

	if len(errors) > 0 {
		result["errors"] = errors
		if deletedCount == 0 {
			// All deletes failed
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to delete all issues: %v", errors),
			}
		}
		// Partial success
		result["partial_success"] = true
	}

	data, _ := json.Marshal(result)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleList(req *Request) Response {
	var listArgs ListArgs
	if err := json.Unmarshal(req.Args, &listArgs); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid list args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	filter := types.IssueFilter{
		Limit: listArgs.Limit,
	}
	
	// Normalize status: treat "" or "all" as unset (no filter)
	if listArgs.Status != "" && listArgs.Status != "all" {
		status := types.Status(listArgs.Status)
		filter.Status = &status
	}
	
	if listArgs.IssueType != "" {
		issueType := types.IssueType(listArgs.IssueType)
		filter.IssueType = &issueType
	}
	if listArgs.Assignee != "" {
		filter.Assignee = &listArgs.Assignee
	}
	if listArgs.Priority != nil {
		filter.Priority = listArgs.Priority
	}
	
	// Normalize and apply label filters
	labels := util.NormalizeLabels(listArgs.Labels)
	labelsAny := util.NormalizeLabels(listArgs.LabelsAny)
	// Support both old single Label and new Labels array (backward compat)
	if len(labels) > 0 {
		filter.Labels = labels
	} else if listArgs.Label != "" {
		filter.Labels = []string{strings.TrimSpace(listArgs.Label)}
	}
	if len(labelsAny) > 0 {
		filter.LabelsAny = labelsAny
	}
	if len(listArgs.IDs) > 0 {
		ids := util.NormalizeLabels(listArgs.IDs)
		if len(ids) > 0 {
			filter.IDs = ids
		}
	}
	
	// Pattern matching
	filter.TitleContains = listArgs.TitleContains
	filter.DescriptionContains = listArgs.DescriptionContains
	filter.NotesContains = listArgs.NotesContains
	
	// Date ranges - use parseTimeRPC helper for flexible formats
	if listArgs.CreatedAfter != "" {
		t, err := parseTimeRPC(listArgs.CreatedAfter)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --created-after date: %v", err),
			}
		}
		filter.CreatedAfter = &t
	}
	if listArgs.CreatedBefore != "" {
		t, err := parseTimeRPC(listArgs.CreatedBefore)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --created-before date: %v", err),
			}
		}
		filter.CreatedBefore = &t
	}
	if listArgs.UpdatedAfter != "" {
		t, err := parseTimeRPC(listArgs.UpdatedAfter)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --updated-after date: %v", err),
			}
		}
		filter.UpdatedAfter = &t
	}
	if listArgs.UpdatedBefore != "" {
		t, err := parseTimeRPC(listArgs.UpdatedBefore)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --updated-before date: %v", err),
			}
		}
		filter.UpdatedBefore = &t
	}
	if listArgs.ClosedAfter != "" {
		t, err := parseTimeRPC(listArgs.ClosedAfter)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --closed-after date: %v", err),
			}
		}
		filter.ClosedAfter = &t
	}
	if listArgs.ClosedBefore != "" {
		t, err := parseTimeRPC(listArgs.ClosedBefore)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --closed-before date: %v", err),
			}
		}
		filter.ClosedBefore = &t
	}
	
	// Empty/null checks
	filter.EmptyDescription = listArgs.EmptyDescription
	filter.NoAssignee = listArgs.NoAssignee
	filter.NoLabels = listArgs.NoLabels
	
	// Priority range
	filter.PriorityMin = listArgs.PriorityMin
	filter.PriorityMax = listArgs.PriorityMax

	// Pinned filtering
	filter.Pinned = listArgs.Pinned

	// Template filtering: exclude templates by default
	if !listArgs.IncludeTemplates {
		isTemplate := false
		filter.IsTemplate = &isTemplate
	}

	// Parent filtering
	if listArgs.ParentID != "" {
		filter.ParentID = &listArgs.ParentID
	}

	// Ephemeral filtering
	filter.Ephemeral = listArgs.Ephemeral

	// Guard against excessive ID lists to avoid SQLite parameter limits
	const maxIDs = 1000
	if len(filter.IDs) > maxIDs {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("--id flag supports at most %d issue IDs, got %d", maxIDs, len(filter.IDs)),
		}
	}

	ctx := s.reqCtx(req)
	issues, err := store.SearchIssues(ctx, listArgs.Query, filter)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to list issues: %v", err),
		}
	}

	// Populate labels for each issue
	for _, issue := range issues {
		labels, _ := store.GetLabels(ctx, issue.ID)
		issue.Labels = labels
	}

	// Get dependency counts in bulk (single query instead of N queries)
	issueIDs := make([]string, len(issues))
	for i, issue := range issues {
		issueIDs[i] = issue.ID
	}
	depCounts, _ := store.GetDependencyCounts(ctx, issueIDs)

	// Build response with counts
	issuesWithCounts := make([]*types.IssueWithCounts, len(issues))
	for i, issue := range issues {
		counts := depCounts[issue.ID]
		if counts == nil {
			counts = &types.DependencyCounts{DependencyCount: 0, DependentCount: 0}
		}
		issuesWithCounts[i] = &types.IssueWithCounts{
			Issue:           issue,
			DependencyCount: counts.DependencyCount,
			DependentCount:  counts.DependentCount,
		}
	}

	data, _ := json.Marshal(issuesWithCounts)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleCount(req *Request) Response {
	var countArgs CountArgs
	if err := json.Unmarshal(req.Args, &countArgs); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid count args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	filter := types.IssueFilter{}

	// Normalize status: treat "" or "all" as unset (no filter)
	if countArgs.Status != "" && countArgs.Status != "all" {
		status := types.Status(countArgs.Status)
		filter.Status = &status
	}

	if countArgs.IssueType != "" {
		issueType := types.IssueType(countArgs.IssueType)
		filter.IssueType = &issueType
	}
	if countArgs.Assignee != "" {
		filter.Assignee = &countArgs.Assignee
	}
	if countArgs.Priority != nil {
		filter.Priority = countArgs.Priority
	}

	// Normalize and apply label filters
	labels := util.NormalizeLabels(countArgs.Labels)
	labelsAny := util.NormalizeLabels(countArgs.LabelsAny)
	if len(labels) > 0 {
		filter.Labels = labels
	}
	if len(labelsAny) > 0 {
		filter.LabelsAny = labelsAny
	}
	if len(countArgs.IDs) > 0 {
		ids := util.NormalizeLabels(countArgs.IDs)
		if len(ids) > 0 {
			filter.IDs = ids
		}
	}

	// Pattern matching
	filter.TitleContains = countArgs.TitleContains
	filter.DescriptionContains = countArgs.DescriptionContains
	filter.NotesContains = countArgs.NotesContains

	// Date ranges - use parseTimeRPC helper for flexible formats
	if countArgs.CreatedAfter != "" {
		t, err := parseTimeRPC(countArgs.CreatedAfter)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --created-after date: %v", err),
			}
		}
		filter.CreatedAfter = &t
	}
	if countArgs.CreatedBefore != "" {
		t, err := parseTimeRPC(countArgs.CreatedBefore)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --created-before date: %v", err),
			}
		}
		filter.CreatedBefore = &t
	}
	if countArgs.UpdatedAfter != "" {
		t, err := parseTimeRPC(countArgs.UpdatedAfter)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --updated-after date: %v", err),
			}
		}
		filter.UpdatedAfter = &t
	}
	if countArgs.UpdatedBefore != "" {
		t, err := parseTimeRPC(countArgs.UpdatedBefore)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --updated-before date: %v", err),
			}
		}
		filter.UpdatedBefore = &t
	}
	if countArgs.ClosedAfter != "" {
		t, err := parseTimeRPC(countArgs.ClosedAfter)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --closed-after date: %v", err),
			}
		}
		filter.ClosedAfter = &t
	}
	if countArgs.ClosedBefore != "" {
		t, err := parseTimeRPC(countArgs.ClosedBefore)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid --closed-before date: %v", err),
			}
		}
		filter.ClosedBefore = &t
	}

	// Empty/null checks
	filter.EmptyDescription = countArgs.EmptyDescription
	filter.NoAssignee = countArgs.NoAssignee
	filter.NoLabels = countArgs.NoLabels

	// Priority range
	filter.PriorityMin = countArgs.PriorityMin
	filter.PriorityMax = countArgs.PriorityMax

	ctx := s.reqCtx(req)
	issues, err := store.SearchIssues(ctx, countArgs.Query, filter)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to count issues: %v", err),
		}
	}

	// If no grouping, just return the count
	if countArgs.GroupBy == "" {
		type CountResult struct {
			Count int `json:"count"`
		}
		data, _ := json.Marshal(CountResult{Count: len(issues)})
		return Response{
			Success: true,
			Data:    data,
		}
	}

	// Group by the specified field
	type GroupCount struct {
		Group string `json:"group"`
		Count int    `json:"count"`
	}

	counts := make(map[string]int)

	// For label grouping, fetch all labels in one query to avoid N+1
	var labelsMap map[string][]string
	if countArgs.GroupBy == "label" {
		issueIDs := make([]string, len(issues))
		for i, issue := range issues {
			issueIDs[i] = issue.ID
		}
		var err error
		labelsMap, err = store.GetLabelsForIssues(ctx, issueIDs)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to get labels: %v", err),
			}
		}
	}

	for _, issue := range issues {
		var groupKey string
		switch countArgs.GroupBy {
		case "status":
			groupKey = string(issue.Status)
		case "priority":
			groupKey = fmt.Sprintf("P%d", issue.Priority)
		case "type":
			groupKey = string(issue.IssueType)
		case "assignee":
			if issue.Assignee == "" {
				groupKey = "(unassigned)"
			} else {
				groupKey = issue.Assignee
			}
		case "label":
			// For labels, count each label separately
			labels := labelsMap[issue.ID]
			if len(labels) > 0 {
				for _, label := range labels {
					counts[label]++
				}
				continue
			} else {
				groupKey = "(no labels)"
			}
		default:
			return Response{
				Success: false,
				Error:   fmt.Sprintf("invalid group_by value: %s (must be one of: status, priority, type, assignee, label)", countArgs.GroupBy),
			}
		}
		counts[groupKey]++
	}

	// Convert map to sorted slice
	groups := make([]GroupCount, 0, len(counts))
	for group, count := range counts {
		groups = append(groups, GroupCount{Group: group, Count: count})
	}

	type GroupedCountResult struct {
		Total  int          `json:"total"`
		Groups []GroupCount `json:"groups"`
	}

	result := GroupedCountResult{
		Total:  len(issues),
		Groups: groups,
	}

	data, _ := json.Marshal(result)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleResolveID(req *Request) Response {
	var args ResolveIDArgs
	if err := json.Unmarshal(req.Args, &args); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid resolve_id args: %v", err),
		}
	}

	if s.storage == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	ctx := s.reqCtx(req)
	resolvedID, err := utils.ResolvePartialID(ctx, s.storage, args.ID)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to resolve ID: %v", err),
		}
	}

	data, _ := json.Marshal(resolvedID)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleShow(req *Request) Response {
	var showArgs ShowArgs
	if err := json.Unmarshal(req.Args, &showArgs); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid show args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	ctx := s.reqCtx(req)
	issue, err := store.GetIssue(ctx, showArgs.ID)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get issue: %v", err),
		}
	}
	if issue == nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("issue not found: %s", showArgs.ID),
		}
	}

	// Populate labels, dependencies (with metadata), and dependents (with metadata)
	labels, _ := store.GetLabels(ctx, issue.ID)
	
	// Get dependencies and dependents with metadata (including dependency type)
	var deps []*types.IssueWithDependencyMetadata
	var dependents []*types.IssueWithDependencyMetadata
	if sqliteStore, ok := store.(*sqlite.SQLiteStorage); ok {
		deps, _ = sqliteStore.GetDependenciesWithMetadata(ctx, issue.ID)
		dependents, _ = sqliteStore.GetDependentsWithMetadata(ctx, issue.ID)
	} else {
		// Fallback for non-SQLite storage (won't have dependency type metadata)
		regularDeps, _ := store.GetDependencies(ctx, issue.ID)
		for _, d := range regularDeps {
			deps = append(deps, &types.IssueWithDependencyMetadata{
				Issue:          *d,
				DependencyType: types.DepBlocks, // default
			})
		}
		regularDependents, _ := store.GetDependents(ctx, issue.ID)
		for _, d := range regularDependents {
			dependents = append(dependents, &types.IssueWithDependencyMetadata{
				Issue:          *d,
				DependencyType: types.DepBlocks, // default
			})
		}
	}

	// Fetch comments
	comments, _ := store.GetIssueComments(ctx, issue.ID)

	// Create detailed response with related data
	type IssueDetails struct {
		*types.Issue
		Labels       []string                              `json:"labels,omitempty"`
		Dependencies []*types.IssueWithDependencyMetadata `json:"dependencies,omitempty"`
		Dependents   []*types.IssueWithDependencyMetadata `json:"dependents,omitempty"`
		Comments     []*types.Comment                      `json:"comments,omitempty"`
	}

	details := &IssueDetails{
		Issue:        issue,
		Labels:       labels,
		Dependencies: deps,
		Dependents:   dependents,
		Comments:     comments,
	}

	data, _ := json.Marshal(details)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleReady(req *Request) Response {
	var readyArgs ReadyArgs
	if err := json.Unmarshal(req.Args, &readyArgs); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid ready args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	wf := types.WorkFilter{
		Status:     types.StatusOpen,
		Type:       readyArgs.Type,
		Priority:   readyArgs.Priority,
		Unassigned: readyArgs.Unassigned,
		Limit:      readyArgs.Limit,
		SortPolicy: types.SortPolicy(readyArgs.SortPolicy),
		Labels:     util.NormalizeLabels(readyArgs.Labels),
		LabelsAny:  util.NormalizeLabels(readyArgs.LabelsAny),
	}
	if readyArgs.Assignee != "" && !readyArgs.Unassigned {
		wf.Assignee = &readyArgs.Assignee
	}
	if readyArgs.ParentID != "" {
		wf.ParentID = &readyArgs.ParentID
	}

	ctx := s.reqCtx(req)
	issues, err := store.GetReadyWork(ctx, wf)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get ready work: %v", err),
		}
	}

	data, _ := json.Marshal(issues)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleBlocked(req *Request) Response {
	var blockedArgs BlockedArgs
	if err := json.Unmarshal(req.Args, &blockedArgs); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid blocked args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	var wf types.WorkFilter
	if blockedArgs.ParentID != "" {
		wf.ParentID = &blockedArgs.ParentID
	}

	ctx := s.reqCtx(req)
	blocked, err := store.GetBlockedIssues(ctx, wf)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get blocked issues: %v", err),
		}
	}

	data, _ := json.Marshal(blocked)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleStale(req *Request) Response {
	var staleArgs StaleArgs
	if err := json.Unmarshal(req.Args, &staleArgs); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid stale args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	filter := types.StaleFilter{
		Days:   staleArgs.Days,
		Status: staleArgs.Status,
		Limit:  staleArgs.Limit,
	}

	ctx := s.reqCtx(req)
	issues, err := store.GetStaleIssues(ctx, filter)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get stale issues: %v", err),
		}
	}

	data, _ := json.Marshal(issues)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleStats(req *Request) Response {
	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	ctx := s.reqCtx(req)
	stats, err := store.GetStatistics(ctx)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get statistics: %v", err),
		}
	}

	data, _ := json.Marshal(stats)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleEpicStatus(req *Request) Response {
	var epicArgs EpicStatusArgs
	if err := json.Unmarshal(req.Args, &epicArgs); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid epic status args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)",
		}
	}

	ctx := s.reqCtx(req)
	epics, err := store.GetEpicsEligibleForClosure(ctx)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get epic status: %v", err),
		}
	}

	if epicArgs.EligibleOnly {
		filtered := []*types.EpicStatus{}
		for _, epic := range epics {
			if epic.EligibleForClose {
				filtered = append(filtered, epic)
			}
		}
		epics = filtered
	}

	data, err := json.Marshal(epics)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to marshal epics: %v", err),
		}
	}

	return Response{
		Success: true,
		Data:    data,
	}
}

// Gate handlers

func (s *Server) handleGateCreate(req *Request) Response {
	var args GateCreateArgs
	if err := json.Unmarshal(req.Args, &args); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid gate create args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available",
		}
	}

	ctx := s.reqCtx(req)
	now := time.Now()

	// Create gate issue
	gate := &types.Issue{
		Title:     args.Title,
		IssueType: types.TypeGate,
		Status:    types.StatusOpen,
		Priority:  1, // Gates are typically high priority
		Assignee:  "deacon/",
		Ephemeral:      true, // Gates are wisps (ephemeral)
		AwaitType: args.AwaitType,
		AwaitID:   args.AwaitID,
		Timeout:   args.Timeout,
		Waiters:   args.Waiters,
		CreatedAt: now,
		UpdatedAt: now,
	}
	gate.ContentHash = gate.ComputeContentHash()

	if err := store.CreateIssue(ctx, gate, s.reqActor(req)); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to create gate: %v", err),
		}
	}

	// Emit mutation event
	s.emitMutation(MutationCreate, gate.ID, gate.Title, gate.Assignee)

	data, _ := json.Marshal(GateCreateResult{ID: gate.ID})
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleGateList(req *Request) Response {
	var args GateListArgs
	if err := json.Unmarshal(req.Args, &args); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid gate list args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available",
		}
	}

	ctx := s.reqCtx(req)

	// Build filter for gates
	gateType := types.TypeGate
	filter := types.IssueFilter{
		IssueType: &gateType,
	}
	if !args.All {
		openStatus := types.StatusOpen
		filter.Status = &openStatus
	}

	gates, err := store.SearchIssues(ctx, "", filter)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to list gates: %v", err),
		}
	}

	data, _ := json.Marshal(gates)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleGateShow(req *Request) Response {
	var args GateShowArgs
	if err := json.Unmarshal(req.Args, &args); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid gate show args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available",
		}
	}

	ctx := s.reqCtx(req)

	// Resolve partial ID
	gateID, err := utils.ResolvePartialID(ctx, store, args.ID)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to resolve gate ID: %v", err),
		}
	}

	gate, err := store.GetIssue(ctx, gateID)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get gate: %v", err),
		}
	}
	if gate == nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("gate %s not found", gateID),
		}
	}
	if gate.IssueType != types.TypeGate {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("%s is not a gate (type: %s)", gateID, gate.IssueType),
		}
	}

	data, _ := json.Marshal(gate)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleGateClose(req *Request) Response {
	var args GateCloseArgs
	if err := json.Unmarshal(req.Args, &args); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid gate close args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available",
		}
	}

	ctx := s.reqCtx(req)

	// Resolve partial ID
	gateID, err := utils.ResolvePartialID(ctx, store, args.ID)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to resolve gate ID: %v", err),
		}
	}

	// Verify it's a gate
	gate, err := store.GetIssue(ctx, gateID)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get gate: %v", err),
		}
	}
	if gate == nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("gate %s not found", gateID),
		}
	}
	if gate.IssueType != types.TypeGate {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("%s is not a gate (type: %s)", gateID, gate.IssueType),
		}
	}

	reason := args.Reason
	if reason == "" {
		reason = "Gate closed"
	}

	oldStatus := string(gate.Status)

	if err := store.CloseIssue(ctx, gateID, reason, s.reqActor(req)); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to close gate: %v", err),
		}
	}

	// Emit rich status change event
	s.emitRichMutation(MutationEvent{
		Type:      MutationStatus,
		IssueID:   gateID,
		OldStatus: oldStatus,
		NewStatus: "closed",
	})

	closedGate, _ := store.GetIssue(ctx, gateID)
	data, _ := json.Marshal(closedGate)
	return Response{
		Success: true,
		Data:    data,
	}
}

func (s *Server) handleGateWait(req *Request) Response {
	var args GateWaitArgs
	if err := json.Unmarshal(req.Args, &args); err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("invalid gate wait args: %v", err),
		}
	}

	store := s.storage
	if store == nil {
		return Response{
			Success: false,
			Error:   "storage not available",
		}
	}

	ctx := s.reqCtx(req)

	// Resolve partial ID
	gateID, err := utils.ResolvePartialID(ctx, store, args.ID)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to resolve gate ID: %v", err),
		}
	}

	// Get existing gate
	gate, err := store.GetIssue(ctx, gateID)
	if err != nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("failed to get gate: %v", err),
		}
	}
	if gate == nil {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("gate %s not found", gateID),
		}
	}
	if gate.IssueType != types.TypeGate {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("%s is not a gate (type: %s)", gateID, gate.IssueType),
		}
	}
	if gate.Status == types.StatusClosed {
		return Response{
			Success: false,
			Error:   fmt.Sprintf("gate %s is already closed", gateID),
		}
	}

	// Add new waiters (avoiding duplicates)
	waiterSet := make(map[string]bool)
	for _, w := range gate.Waiters {
		waiterSet[w] = true
	}
	newWaiters := []string{}
	for _, addr := range args.Waiters {
		if !waiterSet[addr] {
			newWaiters = append(newWaiters, addr)
			waiterSet[addr] = true
		}
	}

	addedCount := len(newWaiters)

	if addedCount > 0 {
		// Update waiters using SQLite directly
		sqliteStore, ok := store.(*sqlite.SQLiteStorage)
		if !ok {
			return Response{
				Success: false,
				Error:   "gate wait requires SQLite storage",
			}
		}

		allWaiters := append(gate.Waiters, newWaiters...)
		waitersJSON, _ := json.Marshal(allWaiters)

		// Use raw SQL to update the waiters field
		_, err = sqliteStore.UnderlyingDB().ExecContext(ctx, `UPDATE issues SET waiters = ?, updated_at = ? WHERE id = ?`,
			string(waitersJSON), time.Now(), gateID)
		if err != nil {
			return Response{
				Success: false,
				Error:   fmt.Sprintf("failed to add waiters: %v", err),
			}
		}

		// Emit mutation event
		s.emitMutation(MutationUpdate, gateID, gate.Title, gate.Assignee)
	}

	data, _ := json.Marshal(GateWaitResult{AddedCount: addedCount})
	return Response{
		Success: true,
		Data:    data,
	}
}
