package main

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

	"github.com/spf13/cobra"
	"github.com/steveyegge/beads/internal/config"
	"github.com/steveyegge/beads/internal/debug"
	"github.com/steveyegge/beads/internal/hooks"
	"github.com/steveyegge/beads/internal/routing"
	"github.com/steveyegge/beads/internal/rpc"
	"github.com/steveyegge/beads/internal/storage"
	"github.com/steveyegge/beads/internal/storage/sqlite"
	"github.com/steveyegge/beads/internal/timeparsing"
	"github.com/steveyegge/beads/internal/types"
	"github.com/steveyegge/beads/internal/ui"
	"github.com/steveyegge/beads/internal/validation"
)

var createCmd = &cobra.Command{
	Use:     "create [title]",
	GroupID: "issues",
	Aliases: []string{"new"},
	Short:   "Create a new issue (or multiple issues from markdown file)",
	Args:    cobra.MinimumNArgs(0), // Changed to allow no args when using -f
	Run: func(cmd *cobra.Command, args []string) {
		CheckReadonly("create")
		file, _ := cmd.Flags().GetString("file")

		// If file flag is provided, parse markdown and create multiple issues
		if file != "" {
			if len(args) > 0 {
				FatalError("cannot specify both title and --file flag")
			}
			// --dry-run not supported with --file (would need to parse and preview multiple issues)
			dryRun, _ := cmd.Flags().GetBool("dry-run")
			if dryRun {
				FatalError("--dry-run is not supported with --file flag")
			}
			createIssuesFromMarkdown(cmd, file)
			return
		}

		// Original single-issue creation logic
		// Get title from flag or positional argument
		titleFlag, _ := cmd.Flags().GetString("title")
		var title string

		if len(args) > 0 && titleFlag != "" {
			// Both provided - check if they match
			if args[0] != titleFlag {
				FatalError("cannot specify different titles as both positional argument and --title flag\n  Positional: %q\n  --title:    %q", args[0], titleFlag)
			}
			title = args[0] // They're the same, use either
		} else if len(args) > 0 {
			title = args[0]
		} else if titleFlag != "" {
			title = titleFlag
		} else {
			FatalError("title required (or use --file to create from markdown)")
		}

		// Get silent flag
		silent, _ := cmd.Flags().GetBool("silent")

		// Warn if creating a test issue in production database (unless silent mode)
		if strings.HasPrefix(strings.ToLower(title), "test") && !silent && !debug.IsQuiet() {
			fmt.Fprintf(os.Stderr, "%s Creating issue with 'Test' prefix in production database.\n", ui.RenderWarn("⚠"))
			fmt.Fprintf(os.Stderr, "  For testing, consider using: BEADS_DB=/tmp/test.db ./bd create \"Test issue\"\n")
		}

		// Get field values
		description, _ := getDescriptionFlag(cmd)

		// Check if description is required by config
		if description == "" && !strings.Contains(strings.ToLower(title), "test") {
			if config.GetBool("create.require-description") {
				FatalError("description is required (set create.require-description: false in config.yaml to disable)")
			}
			// Warn if creating an issue without a description (unless silent mode)
			if !silent && !debug.IsQuiet() {
				fmt.Fprintf(os.Stderr, "%s Creating issue without description.\n", ui.RenderWarn("⚠"))
				fmt.Fprintf(os.Stderr, "  Issues without descriptions lack context for future work.\n")
				fmt.Fprintf(os.Stderr, "  Consider adding --description=\"Why this issue exists and what needs to be done\"\n")
			}
		}

		design, _ := cmd.Flags().GetString("design")
		acceptance, _ := cmd.Flags().GetString("acceptance")
		notes, _ := cmd.Flags().GetString("notes")

		// Parse priority (supports both "1" and "P1" formats)
		priorityStr, _ := cmd.Flags().GetString("priority")
		priority, err := validation.ValidatePriority(priorityStr)
		if err != nil {
			FatalError("%v", err)
		}

		issueType, _ := cmd.Flags().GetString("type")
		assignee, _ := cmd.Flags().GetString("assignee")

		labels, _ := cmd.Flags().GetStringSlice("labels")
		labelAlias, _ := cmd.Flags().GetStringSlice("label")
		if len(labelAlias) > 0 {
			labels = append(labels, labelAlias...)
		}

		explicitID, _ := cmd.Flags().GetString("id")
		parentID, _ := cmd.Flags().GetString("parent")
		externalRef, _ := cmd.Flags().GetString("external-ref")
		deps, _ := cmd.Flags().GetStringSlice("deps")
		waitsFor, _ := cmd.Flags().GetString("waits-for")
		waitsForGate, _ := cmd.Flags().GetString("waits-for-gate")
		forceCreate, _ := cmd.Flags().GetBool("force")
		repoOverride, _ := cmd.Flags().GetString("repo")
		rigOverride, _ := cmd.Flags().GetString("rig")
		prefixOverride, _ := cmd.Flags().GetString("prefix")
		wisp, _ := cmd.Flags().GetBool("ephemeral")
		molTypeStr, _ := cmd.Flags().GetString("mol-type")
		var molType types.MolType
		if molTypeStr != "" {
			molType = types.MolType(molTypeStr)
			if !molType.IsValid() {
				FatalError("invalid mol-type %q (must be swarm, patrol, or work)", molTypeStr)
			}
		}

		// Agent-specific flags
		roleType, _ := cmd.Flags().GetString("role-type")
		agentRig, _ := cmd.Flags().GetString("agent-rig")

		// Validate agent-specific flags require --type=agent
		if (roleType != "" || agentRig != "") && issueType != "agent" {
			FatalError("--role-type and --agent-rig flags require --type=agent")
		}

		// Event-specific flags
		eventCategory, _ := cmd.Flags().GetString("event-category")
		eventActor, _ := cmd.Flags().GetString("event-actor")
		eventTarget, _ := cmd.Flags().GetString("event-target")
		eventPayload, _ := cmd.Flags().GetString("event-payload")

		// Validate event-specific flags require --type=event
		if (eventCategory != "" || eventActor != "" || eventTarget != "" || eventPayload != "") && issueType != "event" {
			FatalError("--event-category, --event-actor, --event-target, and --event-payload flags require --type=event")
		}

		// Parse --due flag (GH#820)
		// Uses layered parsing: compact duration → NLP → date-only → RFC3339
		var dueAt *time.Time
		dueStr, _ := cmd.Flags().GetString("due")
		if dueStr != "" {
			t, err := timeparsing.ParseRelativeTime(dueStr, time.Now())
			if err != nil {
				FatalError("invalid --due format %q. Examples: +6h, tomorrow, next monday, 2025-01-15", dueStr)
			}
			dueAt = &t
		}

		// Parse --defer flag (GH#820)
		var deferUntil *time.Time
		deferStr, _ := cmd.Flags().GetString("defer")
		if deferStr != "" {
			t, err := timeparsing.ParseRelativeTime(deferStr, time.Now())
			if err != nil {
				FatalError("invalid --defer format %q. Examples: +1h, tomorrow, next monday, 2025-01-15", deferStr)
			}
			// Warn if defer date is in the past (user probably meant future)
			if t.Before(time.Now()) && !silent && !debug.IsQuiet() {
				fmt.Fprintf(os.Stderr, "%s Defer date %q is in the past. Issue will appear in bd ready immediately.\n",
					ui.RenderWarn("!"), t.Format("2006-01-02 15:04"))
				fmt.Fprintf(os.Stderr, "  Did you mean a future date? Use --defer=+1h or --defer=tomorrow\n")
			}
			deferUntil = &t
		}

		// Handle --dry-run flag (before --rig to ensure it works with cross-rig creation)
		dryRun, _ := cmd.Flags().GetBool("dry-run")
		if dryRun {
			// Build preview issue
			var externalRefPtr *string
			if externalRef != "" {
				externalRefPtr = &externalRef
			}
			previewIssue := &types.Issue{
				Title:              title,
				Description:        description,
				Design:             design,
				AcceptanceCriteria: acceptance,
				Notes:              notes,
				Status:             types.StatusOpen,
				Priority:           priority,
				IssueType:          types.IssueType(issueType),
				Assignee:           assignee,
				ExternalRef:        externalRefPtr,
				Ephemeral:          wisp,
				CreatedBy:          getActorWithGit(),
				Owner:              getOwner(),
				MolType:            molType,
				RoleType:           roleType,
				Rig:                agentRig,
				DueAt:              dueAt,
				DeferUntil:         deferUntil,
				// Event fields
				EventKind: eventCategory,
				Actor:     eventActor,
				Target:    eventTarget,
				Payload:   eventPayload,
			}
			if explicitID != "" {
				previewIssue.ID = explicitID
			}

			if jsonOutput {
				outputJSON(previewIssue)
			} else {
				idDisplay := previewIssue.ID
				if idDisplay == "" {
					idDisplay = "(will be generated)"
				}
				fmt.Printf("%s [DRY RUN] Would create issue:\n", ui.RenderWarn("⚠"))
				fmt.Printf("  ID: %s\n", idDisplay)
				fmt.Printf("  Title: %s\n", previewIssue.Title)
				fmt.Printf("  Type: %s\n", previewIssue.IssueType)
				fmt.Printf("  Priority: P%d\n", previewIssue.Priority)
				fmt.Printf("  Status: %s\n", previewIssue.Status)
				if previewIssue.Assignee != "" {
					fmt.Printf("  Assignee: %s\n", previewIssue.Assignee)
				}
				if previewIssue.Description != "" {
					fmt.Printf("  Description: %s\n", previewIssue.Description)
				}
				if len(labels) > 0 {
					fmt.Printf("  Labels: %s\n", strings.Join(labels, ", "))
				}
				if len(deps) > 0 {
					fmt.Printf("  Dependencies: %s\n", strings.Join(deps, ", "))
				}
				if rigOverride != "" || prefixOverride != "" {
					rig := rigOverride
					if rig == "" {
						rig = prefixOverride
					}
					fmt.Printf("  Target rig: %s\n", rig)
				}
				if eventCategory != "" {
					fmt.Printf("  Event category: %s\n", eventCategory)
				}
			}
			return
		}

		// Handle --rig or --prefix flag: create issue in a different rig
		// Both flags use the same forgiving lookup (accepts rig names or prefixes)
		targetRig := rigOverride
		if prefixOverride != "" {
			if targetRig != "" {
				FatalError("cannot specify both --rig and --prefix flags")
			}
			targetRig = prefixOverride
		}
		if targetRig != "" {
			createInRig(cmd, targetRig, title, description, issueType, priority, design, acceptance, notes, assignee, labels, externalRef, wisp)
			return
		}

		// Get estimate if provided
		var estimatedMinutes *int
		if cmd.Flags().Changed("estimate") {
			est, _ := cmd.Flags().GetInt("estimate")
			if est < 0 {
				FatalError("estimate must be a non-negative number of minutes")
			}
			estimatedMinutes = &est
		}

		// Validate template based on --validate flag or config
		validateTemplate, _ := cmd.Flags().GetBool("validate")
		if validateTemplate {
			// Explicit --validate flag: fail on error
			if err := validation.ValidateTemplate(types.IssueType(issueType), description); err != nil {
				FatalError("%v", err)
			}
		} else {
			// Check validation.on-create config (bd-t7jq)
			validationMode := config.GetString("validation.on-create")
			if validationMode == "error" || validationMode == "warn" {
				if err := validation.ValidateTemplate(types.IssueType(issueType), description); err != nil {
					if validationMode == "error" {
						FatalError("%v", err)
					} else {
						// warn mode: print warning but proceed
						fmt.Fprintf(os.Stderr, "%s %v\n", ui.RenderWarn("⚠"), err)
					}
				}
			}
		}

		// Use global jsonOutput set by PersistentPreRun

		// Determine target repository using routing logic
		repoPath := "." // default to current directory
		if cmd.Flags().Changed("repo") {
			// Explicit --repo flag overrides auto-routing
			repoPath = repoOverride
		} else {
			// Auto-routing based on user role
			userRole, err := routing.DetectUserRole(".")
			if err != nil {
				debug.Logf("Warning: failed to detect user role: %v\n", err)
			}

			// Build routing config with backward compatibility for legacy contributor.* keys
			routingMode := config.GetString("routing.mode")
			contributorRepo := config.GetString("routing.contributor")

			// NFR-001: Backward compatibility - fall back to legacy contributor.* keys
			if routingMode == "" {
				if config.GetString("contributor.auto_route") == "true" {
					routingMode = "auto"
				}
			}
			if contributorRepo == "" {
				contributorRepo = config.GetString("contributor.planning_repo")
			}

			routingConfig := &routing.RoutingConfig{
				Mode:             routingMode,
				DefaultRepo:      config.GetString("routing.default"),
				MaintainerRepo:   config.GetString("routing.maintainer"),
				ContributorRepo:  contributorRepo,
				ExplicitOverride: repoOverride,
			}

			repoPath = routing.DetermineTargetRepo(routingConfig, userRole, ".")
		}

		// Switch to target repo for multi-repo support (bd-6x6g)
		// When routing to a different repo, we bypass daemon mode and use direct storage
		var targetStore storage.Storage
		var routedToTarget bool
		if repoPath != "." {
			targetBeadsDir := routing.ExpandPath(repoPath)
			debug.Logf("DEBUG: Routing to target repo: %s\n", targetBeadsDir)

			// Ensure target beads directory exists with prefix inheritance
			if err := ensureBeadsDirForPath(rootCtx, targetBeadsDir, store); err != nil {
				FatalError("failed to initialize target repo: %v", err)
			}

			// Open new store for target repo
			targetDBPath := filepath.Join(targetBeadsDir, ".beads", "beads.db")
			var err error
			targetStore, err = sqlite.New(rootCtx, targetDBPath)
			if err != nil {
				FatalError("failed to open target store: %v", err)
			}
			defer func() {
				if err := targetStore.Close(); err != nil {
					fmt.Fprintf(os.Stderr, "warning: failed to close target store: %v\n", err)
				}
			}()

			// Replace store for remainder of create operation
			// This also bypasses daemon mode since daemon owns the current repo's store
			store = targetStore
			daemonClient = nil // Bypass daemon for routed issues (T013)
			routedToTarget = true
		}
		_ = routedToTarget // Used for logging context

		// Check for conflicting flags
		if explicitID != "" && parentID != "" {
			FatalError("cannot specify both --id and --parent flags")
		}

		// If parent is specified, generate child ID
		// In daemon mode, the parent will be sent to the RPC handler
		// In direct mode, we generate the child ID here
		if parentID != "" && daemonClient == nil {
			ctx := rootCtx
			// Validate parent exists before generating child ID
			parentIssue, err := store.GetIssue(ctx, parentID)
			if err != nil {
				FatalError("failed to check parent issue: %v", err)
			}
			if parentIssue == nil {
				FatalError("parent issue %s not found", parentID)
			}
			childID, err := store.GetNextChildID(ctx, parentID)
			if err != nil {
				FatalError("%v", err)
			}
			explicitID = childID // Set as explicit ID for the rest of the flow
		}

		// Validate explicit ID format if provided
		if explicitID != "" {
			requestedPrefix, err := validation.ValidateIDFormat(explicitID)
			if err != nil {
				FatalError("%v", err)
			}

			// Validate prefix matches database prefix
			ctx := rootCtx

			// Get database prefix and allowed prefixes from config
			var dbPrefix, allowedPrefixes string
			if daemonClient != nil {
				// Daemon mode - use RPC to get config
				configResp, err := daemonClient.GetConfig(&rpc.GetConfigArgs{Key: "issue_prefix"})
				if err == nil {
					dbPrefix = configResp.Value
				}
				// Also get allowed_prefixes for multi-prefix support (e.g., Gas Town)
				allowedResp, err := daemonClient.GetConfig(&rpc.GetConfigArgs{Key: "allowed_prefixes"})
				if err == nil {
					allowedPrefixes = allowedResp.Value
				}
				// If error, continue without validation (non-fatal)
			} else {
				// Direct mode - check config
				dbPrefix, _ = store.GetConfig(ctx, "issue_prefix")
				allowedPrefixes, _ = store.GetConfig(ctx, "allowed_prefixes")
			}

			if err := validation.ValidatePrefixWithAllowed(requestedPrefix, dbPrefix, allowedPrefixes, forceCreate); err != nil {
				FatalError("%v", err)
			}

			// Validate agent ID pattern if type is agent
			if issueType == "agent" {
				if err := validation.ValidateAgentID(explicitID); err != nil {
					FatalError("invalid agent ID: %v", err)
				}
			}
		}

		var externalRefPtr *string
		if externalRef != "" {
			externalRefPtr = &externalRef
		}

		// If daemon is running, use RPC
		if daemonClient != nil {
			createArgs := &rpc.CreateArgs{
				ID:                 explicitID,
				Parent:             parentID,
				Title:              title,
				Description:        description,
				IssueType:          issueType,
				Priority:           priority,
				Design:             design,
				AcceptanceCriteria: acceptance,
				Notes:              notes,
				Assignee:           assignee,
				ExternalRef:        externalRef,
				EstimatedMinutes:   estimatedMinutes,
				Labels:             labels,
				Dependencies:       deps,
				WaitsFor:           waitsFor,
				WaitsForGate:       waitsForGate,
				Ephemeral:          wisp,
				CreatedBy:          getActorWithGit(),
				Owner:              getOwner(),
				MolType:            string(molType),
				RoleType:           roleType,
				Rig:                agentRig,
				EventCategory:      eventCategory,
				EventActor:         eventActor,
				EventTarget:        eventTarget,
				EventPayload:       eventPayload,
				DueAt:              formatTimeForRPC(dueAt),
				DeferUntil:         formatTimeForRPC(deferUntil),
			}

			resp, err := daemonClient.Create(createArgs)
			if err != nil {
				FatalError("%v", err)
			}

			// Parse response to get issue for hook
			var issue types.Issue
			if err := json.Unmarshal(resp.Data, &issue); err != nil {
				FatalError("parsing response: %v", err)
			}

			// Run create hook
			if hookRunner != nil {
				hookRunner.Run(hooks.EventCreate, &issue)
			}

			if jsonOutput {
				fmt.Println(string(resp.Data))
			} else if silent {
				fmt.Println(issue.ID)
			} else {
				fmt.Printf("%s Created issue: %s\n", ui.RenderPass("✓"), issue.ID)
				fmt.Printf("  Title: %s\n", issue.Title)
				fmt.Printf("  Priority: P%d\n", issue.Priority)
				fmt.Printf("  Status: %s\n", issue.Status)
			}

			// Track as last touched issue
			SetLastTouchedID(issue.ID)
			return
		}

		// Direct mode
		issue := &types.Issue{
			ID:                 explicitID, // Set explicit ID if provided (empty string if not)
			Title:              title,
			Description:        description,
			Design:             design,
			AcceptanceCriteria: acceptance,
			Notes:              notes,
			Status:             types.StatusOpen,
			Priority:           priority,
			IssueType:          types.IssueType(issueType),
			Assignee:           assignee,
			ExternalRef:        externalRefPtr,
			EstimatedMinutes:   estimatedMinutes,
			Ephemeral:          wisp,
			CreatedBy:          getActorWithGit(),
			Owner:              getOwner(),
			MolType:            molType,
			RoleType:           roleType,
			Rig:                agentRig,
			EventKind:          eventCategory,
			Actor:              eventActor,
			Target:             eventTarget,
			Payload:            eventPayload,
			DueAt:              dueAt,
			DeferUntil:         deferUntil,
		}

		ctx := rootCtx

		// Check if any dependencies are discovered-from type
		// If so, inherit source_repo from the parent issue
		var discoveredFromParentID string
		for _, depSpec := range deps {
			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 && dependsOnID != "" {
						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, actor); err != nil {
			FatalError("%v", err)
		}

		// If parent was specified, add parent-child dependency
		if parentID != "" {
			dep := &types.Dependency{
				IssueID:     issue.ID,
				DependsOnID: parentID,
				Type:        types.DepParentChild,
			}
			if err := store.AddDependency(ctx, dep, actor); err != nil {
				WarnError("failed to add parent-child dependency %s -> %s: %v", issue.ID, parentID, err)
			}
		}

		// Add labels if specified
		for _, label := range labels {
			if err := store.AddLabel(ctx, issue.ID, label, actor); err != nil {
				WarnError("failed to add label %s: %v", label, err)
			}
		}

		// Auto-add role_type/rig labels for agent beads (enables filtering queries)
		// Check for gt:agent label to identify agent beads (Gas Town separation)
		hasAgentLabel := false
		for _, l := range labels {
			if l == "gt:agent" {
				hasAgentLabel = true
				break
			}
		}
		if hasAgentLabel {
			if issue.RoleType != "" {
				agentLabel := "role_type:" + issue.RoleType
				if err := store.AddLabel(ctx, issue.ID, agentLabel, actor); err != nil {
					WarnError("failed to add role_type label: %v", err)
				}
			}
			if issue.Rig != "" {
				rigLabel := "rig:" + issue.Rig
				if err := store.AddLabel(ctx, issue.ID, rigLabel, actor); err != nil {
					WarnError("failed to add rig label: %v", err)
				}
			}
		}

		// Add dependencies if specified (format: type:id or just id for default "blocks" type)
		for _, depSpec := range deps {
			// Skip empty specs (e.g., from trailing commas)
			depSpec = strings.TrimSpace(depSpec)
			if depSpec == "" {
				continue
			}

			var depType types.DependencyType
			var dependsOnID string

			// Parse format: "type:id" or just "id" (defaults to "blocks")
			if strings.Contains(depSpec, ":") {
				parts := strings.SplitN(depSpec, ":", 2)
				if len(parts) != 2 {
					WarnError("invalid dependency format '%s', expected 'type:id' or 'id'", depSpec)
					continue
				}
				depType = types.DependencyType(strings.TrimSpace(parts[0]))
				dependsOnID = strings.TrimSpace(parts[1])
			} else {
				// Default to "blocks" if no type specified
				depType = types.DepBlocks
				dependsOnID = depSpec
			}

			// Validate dependency type
			if !depType.IsValid() {
				WarnError("invalid dependency type '%s' (valid: blocks, related, parent-child, discovered-from)", depType)
				continue
			}

			// Add the dependency
			dep := &types.Dependency{
				IssueID:     issue.ID,
				DependsOnID: dependsOnID,
				Type:        depType,
			}
			if err := store.AddDependency(ctx, dep, actor); err != nil {
				WarnError("failed to add dependency %s -> %s: %v", issue.ID, dependsOnID, err)
			}
		}

		// Add waits-for dependency if specified
		if waitsFor != "" {
			// Validate gate type
			gate := waitsForGate
			if gate == "" {
				gate = types.WaitsForAllChildren
			}
			if gate != types.WaitsForAllChildren && gate != types.WaitsForAnyChildren {
				FatalError("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 {
				FatalError("failed to serialize waits-for metadata: %v", err)
			}

			dep := &types.Dependency{
				IssueID:     issue.ID,
				DependsOnID: waitsFor,
				Type:        types.DepWaitsFor,
				Metadata:    string(metaJSON),
			}
			if err := store.AddDependency(ctx, dep, actor); err != nil {
				WarnError("failed to add waits-for dependency %s -> %s: %v", issue.ID, waitsFor, err)
			}
		}

		// Schedule auto-flush
		markDirtyAndScheduleFlush()

		// Run create hook
		if hookRunner != nil {
			hookRunner.Run(hooks.EventCreate, issue)
		}

		if jsonOutput {
			outputJSON(issue)
		} else if silent {
			fmt.Println(issue.ID)
		} else {
			fmt.Printf("%s Created issue: %s\n", ui.RenderPass("✓"), issue.ID)
			fmt.Printf("  Title: %s\n", issue.Title)
			fmt.Printf("  Priority: P%d\n", issue.Priority)
			fmt.Printf("  Status: %s\n", issue.Status)

			// Show tip after successful create (direct mode only)
			maybeShowTip(store)
		}

		// Track as last touched issue
		SetLastTouchedID(issue.ID)
	},
}

func init() {
	createCmd.Flags().StringP("file", "f", "", "Create multiple issues from markdown file")
	createCmd.Flags().String("title", "", "Issue title (alternative to positional argument)")
	createCmd.Flags().Bool("silent", false, "Output only the issue ID (for scripting)")
	createCmd.Flags().Bool("dry-run", false, "Preview what would be created without actually creating")
	registerPriorityFlag(createCmd, "2")
	createCmd.Flags().StringP("type", "t", "task", "Issue type (bug|feature|task|epic|chore|merge-request|molecule|gate|agent|role|rig|convoy|event)")
	registerCommonIssueFlags(createCmd)
	createCmd.Flags().StringSliceP("labels", "l", []string{}, "Labels (comma-separated)")
	createCmd.Flags().StringSlice("label", []string{}, "Alias for --labels")
	_ = createCmd.Flags().MarkHidden("label") // Only fails if flag missing (caught in tests)
	createCmd.Flags().String("id", "", "Explicit issue ID (e.g., 'bd-42' for partitioning)")
	createCmd.Flags().String("parent", "", "Parent issue ID for hierarchical child (e.g., 'bd-a3f8e9')")
	createCmd.Flags().StringSlice("deps", []string{}, "Dependencies in format 'type:id' or 'id' (e.g., 'discovered-from:bd-20,blocks:bd-15' or 'bd-20')")
	createCmd.Flags().String("waits-for", "", "Spawner issue ID to wait for (creates waits-for dependency for fanout gate)")
	createCmd.Flags().String("waits-for-gate", "all-children", "Gate type: all-children (wait for all) or any-children (wait for first)")
	createCmd.Flags().Bool("force", false, "Force creation even if prefix doesn't match database prefix")
	createCmd.Flags().String("repo", "", "Target repository for issue (overrides auto-routing)")
	createCmd.Flags().String("rig", "", "Create issue in a different rig (e.g., --rig beads)")
	createCmd.Flags().String("prefix", "", "Create issue in rig by prefix (e.g., --prefix bd- or --prefix bd or --prefix beads)")
	createCmd.Flags().IntP("estimate", "e", 0, "Time estimate in minutes (e.g., 60 for 1 hour)")
	createCmd.Flags().Bool("ephemeral", false, "Create as ephemeral (ephemeral, not exported to JSONL)")
	createCmd.Flags().String("mol-type", "", "Molecule type: swarm (multi-polecat), patrol (recurring ops), work (default)")
	createCmd.Flags().Bool("validate", false, "Validate description contains required sections for issue type")
	// Agent-specific flags (only valid when --type=agent)
	createCmd.Flags().String("role-type", "", "Agent role type: polecat|crew|witness|refinery|mayor|deacon (requires --type=agent)")
	createCmd.Flags().String("agent-rig", "", "Agent's rig name (requires --type=agent)")
	// Event-specific flags (only valid when --type=event)
	createCmd.Flags().String("event-category", "", "Event category (e.g., patrol.muted, agent.started) (requires --type=event)")
	createCmd.Flags().String("event-actor", "", "Entity URI who caused this event (requires --type=event)")
	createCmd.Flags().String("event-target", "", "Entity URI or bead ID affected (requires --type=event)")
	createCmd.Flags().String("event-payload", "", "Event-specific JSON data (requires --type=event)")
	// Time-based scheduling flags (GH#820)
	// Examples:
	//   --due=+6h           Due in 6 hours
	//   --due=tomorrow      Due tomorrow
	//   --due="next monday" Due next Monday
	//   --due=2025-01-15    Due on specific date
	//   --defer=+1h         Hidden from bd ready for 1 hour
	//   --defer=tomorrow    Hidden until tomorrow
	createCmd.Flags().String("due", "", "Due date/time. Formats: +6h, +1d, +2w, tomorrow, next monday, 2025-01-15")
	createCmd.Flags().String("defer", "", "Defer until date (issue hidden from bd ready until then). Same formats as --due")
	// Note: --json flag is defined as a persistent flag in main.go, not here
	rootCmd.AddCommand(createCmd)
}

// createInRig creates an issue in a different rig using --rig flag.
// This bypasses the normal daemon/direct flow and directly creates in the target rig.
func createInRig(cmd *cobra.Command, rigName, title, description, issueType string, priority int, design, acceptance, notes, assignee string, labels []string, externalRef string, wisp bool) {
	ctx := rootCtx

	// Find the town-level beads directory (where routes.jsonl lives)
	townBeadsDir, err := findTownBeadsDir()
	if err != nil {
		FatalError("cannot use --rig: %v", err)
	}

	// Resolve the target rig's beads directory and prefix
	targetBeadsDir, targetPrefix, err := routing.ResolveBeadsDirForRig(rigName, townBeadsDir)
	if err != nil {
		FatalError("%v", err)
	}

	// Open storage for the target rig
	targetDBPath := filepath.Join(targetBeadsDir, "beads.db")
	targetStore, err := sqlite.New(ctx, targetDBPath)
	if err != nil {
		FatalError("failed to open rig %q database: %v", rigName, err)
	}
	defer func() {
		if err := targetStore.Close(); err != nil {
			fmt.Fprintf(os.Stderr, "warning: failed to close rig database: %v\n", err)
		}
	}()

	// Prepare prefix override from routes.jsonl for cross-rig creation
	// Strip trailing hyphen - database stores prefix without it (e.g., "aops" not "aops-")
	var prefixOverride string
	if targetPrefix != "" {
		prefixOverride = strings.TrimSuffix(targetPrefix, "-")
	}

	var externalRefPtr *string
	if externalRef != "" {
		externalRefPtr = &externalRef
	}

	// Extract event-specific flags (bd-xwvo fix)
	eventCategory, _ := cmd.Flags().GetString("event-category")
	eventActor, _ := cmd.Flags().GetString("event-actor")
	eventTarget, _ := cmd.Flags().GetString("event-target")
	eventPayload, _ := cmd.Flags().GetString("event-payload")

	// Extract molecule/agent flags (bd-xwvo fix)
	molTypeStr, _ := cmd.Flags().GetString("mol-type")
	var molType types.MolType
	if molTypeStr != "" {
		molType = types.MolType(molTypeStr)
	}
	roleType, _ := cmd.Flags().GetString("role-type")
	agentRig, _ := cmd.Flags().GetString("agent-rig")

	// Extract time-based scheduling flags (bd-xwvo fix)
	var dueAt *time.Time
	dueStr, _ := cmd.Flags().GetString("due")
	if dueStr != "" {
		t, err := timeparsing.ParseRelativeTime(dueStr, time.Now())
		if err != nil {
			FatalError("invalid --due format %q", dueStr)
		}
		dueAt = &t
	}

	var deferUntil *time.Time
	deferStr, _ := cmd.Flags().GetString("defer")
	if deferStr != "" {
		t, err := timeparsing.ParseRelativeTime(deferStr, time.Now())
		if err != nil {
			FatalError("invalid --defer format %q", deferStr)
		}
		deferUntil = &t
	}

	// Create issue without ID - CreateIssue will generate one with the correct prefix
	issue := &types.Issue{
		Title:              title,
		Description:        description,
		Design:             design,
		AcceptanceCriteria: acceptance,
		Notes:              notes,
		Status:             types.StatusOpen,
		Priority:           priority,
		IssueType:          types.IssueType(issueType),
		Assignee:           assignee,
		ExternalRef:        externalRefPtr,
		Ephemeral:          wisp,
		CreatedBy:          getActorWithGit(),
		Owner:              getOwner(),
		// Event fields (bd-xwvo fix)
		EventKind: eventCategory,
		Actor:     eventActor,
		Target:    eventTarget,
		Payload:   eventPayload,
		// Molecule/agent fields (bd-xwvo fix)
		MolType:  molType,
		RoleType: roleType,
		Rig:      agentRig,
		// Time scheduling fields (bd-xwvo fix)
		DueAt:      dueAt,
		DeferUntil: deferUntil,
		// Cross-rig routing: use route prefix instead of database config
		PrefixOverride: prefixOverride,
	}

	if err := targetStore.CreateIssue(ctx, issue, actor); err != nil {
		FatalError("failed to create issue in rig %q: %v", rigName, err)
	}

	// Add labels if specified
	for _, label := range labels {
		if err := targetStore.AddLabel(ctx, issue.ID, label, actor); err != nil {
			WarnError("failed to add label %s: %v", label, err)
		}
	}

	// Get silent flag
	silent, _ := cmd.Flags().GetBool("silent")

	if jsonOutput {
		outputJSON(issue)
	} else if silent {
		fmt.Println(issue.ID)
	} else {
		fmt.Printf("%s Created issue in rig %q: %s\n", ui.RenderPass("✓"), rigName, issue.ID)
		fmt.Printf("  Title: %s\n", issue.Title)
		fmt.Printf("  Priority: P%d\n", issue.Priority)
		fmt.Printf("  Status: %s\n", issue.Status)
	}
}

// findTownBeadsDir finds the town-level .beads directory (where routes.jsonl lives).
// It walks up from the current directory looking for a .beads directory with routes.jsonl.
func findTownBeadsDir() (string, error) {
	// Start from current directory and walk up
	dir, err := os.Getwd()
	if err != nil {
		return "", err
	}

	for {
		beadsDir := filepath.Join(dir, ".beads")
		routesFile := filepath.Join(beadsDir, routing.RoutesFileName)

		// Check if this .beads directory has routes.jsonl
		if _, err := os.Stat(routesFile); err == nil {
			return beadsDir, nil
		}

		// Move up one directory
		parent := filepath.Dir(dir)
		if parent == dir {
			// Reached filesystem root
			break
		}
		dir = parent
	}

	return "", fmt.Errorf("no routes.jsonl found in any parent .beads directory")
}

// formatTimeForRPC converts a *time.Time to RFC3339 string for daemon RPC calls.
// Returns empty string if t is nil, allowing the daemon to distinguish "not set" from "set to zero".
func formatTimeForRPC(t *time.Time) string {
	if t == nil {
		return ""
	}
	return t.Format(time.RFC3339)
}

// ensureBeadsDirForPath ensures a beads directory exists at the target path.
// If the .beads directory doesn't exist, it creates it and initializes with
// the same prefix as the source store (T010, T012: prefix inheritance).
func ensureBeadsDirForPath(ctx context.Context, targetPath string, sourceStore storage.Storage) error {
	beadsDir := filepath.Join(targetPath, ".beads")
	dbPath := filepath.Join(beadsDir, "beads.db")

	// Check if beads directory already exists
	if _, err := os.Stat(beadsDir); err == nil {
		// Directory exists, check if database exists
		if _, err := os.Stat(dbPath); err == nil {
			// Database exists, nothing to do
			return nil
		}
	}

	// Create .beads directory
	if err := os.MkdirAll(beadsDir, 0750); err != nil {
		return fmt.Errorf("cannot create .beads directory: %w", err)
	}

	// Create issues.jsonl if it doesn't exist
	jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
	if _, err := os.Stat(jsonlPath); os.IsNotExist(err) {
		// #nosec G306 -- planning repo JSONL must be shareable across collaborators
		if err := os.WriteFile(jsonlPath, []byte{}, 0644); err != nil {
			return fmt.Errorf("failed to create issues.jsonl: %w", err)
		}
	}

	// Initialize database - it will be created when sqlite.New is called
	// But we need to set the prefix if source store has one (T012: prefix inheritance)
	if sourceStore != nil {
		sourcePrefix, err := sourceStore.GetConfig(ctx, "issue_prefix")
		if err == nil && sourcePrefix != "" {
			// Open target store temporarily to set prefix
			tempStore, err := sqlite.New(ctx, dbPath)
			if err != nil {
				return fmt.Errorf("failed to initialize target database: %w", err)
			}
			if err := tempStore.SetConfig(ctx, "issue_prefix", sourcePrefix); err != nil {
				_ = tempStore.Close()
				return fmt.Errorf("failed to set prefix in target store: %w", err)
			}
			if err := tempStore.Close(); err != nil {
				return fmt.Errorf("failed to close target store: %w", err)
			}
		}
	}

	return nil
}
