package cmd

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/spf13/cobra"
	"github.com/steveyegge/gastown/internal/beads"
	"github.com/steveyegge/gastown/internal/config"
	"github.com/steveyegge/gastown/internal/style"
	"github.com/steveyegge/gastown/internal/workspace"
)

// Note: Agent field parsing is now in internal/beads/fields.go (AgentFields, ParseAgentFieldsFromDescription)

// buildAgentBeadID constructs the agent bead ID from an agent identity.
// Uses canonical naming: prefix-rig-role-name
// Town-level agents use hq- prefix; rig-level agents use rig's prefix.
// Examples:
//   - "mayor" -> "hq-mayor"
//   - "deacon" -> "hq-deacon"
//   - "gastown/witness" -> "gt-gastown-witness"
//   - "gastown/refinery" -> "gt-gastown-refinery"
//   - "gastown/nux" (polecat) -> "gt-gastown-polecat-nux"
//   - "gastown/crew/max" -> "gt-gastown-crew-max"
//
// If role is unknown, it tries to infer from the identity string.
// townRoot is needed to look up the rig's configured prefix.
func buildAgentBeadID(identity string, role Role, townRoot string) string {
	parts := strings.Split(identity, "/")

	// Helper to get prefix for a rig
	getPrefix := func(rig string) string {
		return config.GetRigPrefix(townRoot, rig)
	}

	// If role is unknown or empty, try to infer from identity
	if role == RoleUnknown || role == Role("") {
		switch {
		case identity == "mayor":
			return beads.MayorBeadIDTown()
		case identity == "deacon":
			return beads.DeaconBeadIDTown()
		case len(parts) == 2 && parts[1] == "witness":
			return beads.WitnessBeadIDWithPrefix(getPrefix(parts[0]), parts[0])
		case len(parts) == 2 && parts[1] == "refinery":
			return beads.RefineryBeadIDWithPrefix(getPrefix(parts[0]), parts[0])
		case len(parts) == 2:
			// Assume rig/name is a polecat
			return beads.PolecatBeadIDWithPrefix(getPrefix(parts[0]), parts[0], parts[1])
		case len(parts) == 3 && parts[1] == "crew":
			// rig/crew/name - crew member
			return beads.CrewBeadIDWithPrefix(getPrefix(parts[0]), parts[0], parts[2])
		case len(parts) == 3 && parts[1] == "polecats":
			// rig/polecats/name - explicit polecat
			return beads.PolecatBeadIDWithPrefix(getPrefix(parts[0]), parts[0], parts[2])
		default:
			return ""
		}
	}

	switch role {
	case RoleMayor:
		return beads.MayorBeadIDTown()
	case RoleDeacon:
		return beads.DeaconBeadIDTown()
	case RoleWitness:
		if len(parts) >= 1 {
			return beads.WitnessBeadIDWithPrefix(getPrefix(parts[0]), parts[0])
		}
		return ""
	case RoleRefinery:
		if len(parts) >= 1 {
			return beads.RefineryBeadIDWithPrefix(getPrefix(parts[0]), parts[0])
		}
		return ""
	case RolePolecat:
		// Handle both 2-part (rig/name) and 3-part (rig/polecats/name) formats
		if len(parts) == 3 && parts[1] == "polecats" {
			return beads.PolecatBeadIDWithPrefix(getPrefix(parts[0]), parts[0], parts[2])
		}
		if len(parts) >= 2 {
			return beads.PolecatBeadIDWithPrefix(getPrefix(parts[0]), parts[0], parts[1])
		}
		return ""
	case RoleCrew:
		if len(parts) >= 3 && parts[1] == "crew" {
			return beads.CrewBeadIDWithPrefix(getPrefix(parts[0]), parts[0], parts[2])
		}
		return ""
	default:
		return ""
	}
}

// MoleculeProgressInfo contains progress information for a molecule instance.
type MoleculeProgressInfo struct {
	RootID       string   `json:"root_id"`
	RootTitle    string   `json:"root_title"`
	MoleculeID   string   `json:"molecule_id,omitempty"`
	TotalSteps   int      `json:"total_steps"`
	DoneSteps    int      `json:"done_steps"`
	InProgress   int      `json:"in_progress_steps"`
	ReadySteps   []string `json:"ready_steps"`
	BlockedSteps []string `json:"blocked_steps"`
	Percent      int      `json:"percent_complete"`
	Complete     bool     `json:"complete"`
}

// MoleculeStatusInfo contains status information for an agent's work.
type MoleculeStatusInfo struct {
	Target           string                `json:"target"`
	Role             string                `json:"role"`
	AgentBeadID      string                `json:"agent_bead_id,omitempty"` // The agent bead if found
	HasWork          bool                  `json:"has_work"`
	PinnedBead       *beads.Issue          `json:"pinned_bead,omitempty"`
	AttachedMolecule string                `json:"attached_molecule,omitempty"`
	AttachedAt       string                `json:"attached_at,omitempty"`
	AttachedArgs     string                `json:"attached_args,omitempty"`
	IsWisp           bool                  `json:"is_wisp"`
	Progress         *MoleculeProgressInfo `json:"progress,omitempty"`
	NextAction       string                `json:"next_action,omitempty"`
}

// MoleculeCurrentInfo contains info about what an agent should be working on.
type MoleculeCurrentInfo struct {
	Identity      string `json:"identity"`
	HandoffID     string `json:"handoff_id,omitempty"`
	HandoffTitle  string `json:"handoff_title,omitempty"`
	MoleculeID    string `json:"molecule_id,omitempty"`
	MoleculeTitle string `json:"molecule_title,omitempty"`
	StepsComplete int    `json:"steps_complete"`
	StepsTotal    int    `json:"steps_total"`
	CurrentStepID string `json:"current_step_id,omitempty"`
	CurrentStep   string `json:"current_step,omitempty"`
	Status        string `json:"status"` // "working", "naked", "complete", "blocked"
}

func runMoleculeProgress(cmd *cobra.Command, args []string) error {
	rootID := args[0]

	workDir, err := findLocalBeadsDir()
	if err != nil {
		return fmt.Errorf("not in a beads workspace: %w", err)
	}

	b := beads.New(workDir)

	// Get the root issue
	root, err := b.Show(rootID)
	if err != nil {
		return fmt.Errorf("getting root issue: %w", err)
	}

	// Find all children of the root issue
	children, err := b.List(beads.ListOptions{
		Parent:   rootID,
		Status:   "all",
		Priority: -1,
	})
	if err != nil {
		return fmt.Errorf("listing children: %w", err)
	}

	if len(children) == 0 {
		return fmt.Errorf("no steps found for %s (not a molecule root?)", rootID)
	}

	// Build progress info
	progress := MoleculeProgressInfo{
		RootID:    rootID,
		RootTitle: root.Title,
	}

	// Try to find molecule ID from first child's description
	for _, child := range children {
		if molID := extractMoleculeID(child.Description); molID != "" {
			progress.MoleculeID = molID
			break
		}
	}

	// Build set of closed issue IDs for dependency checking
	closedIDs := make(map[string]bool)
	for _, child := range children {
		if child.Status == "closed" {
			closedIDs[child.ID] = true
		}
	}

	// Categorize steps
	for _, child := range children {
		progress.TotalSteps++

		switch child.Status {
		case "closed":
			progress.DoneSteps++
		case "in_progress":
			progress.InProgress++
		case "open":
			// Check if all dependencies are closed
			allDepsClosed := true
			for _, depID := range child.DependsOn {
				if !closedIDs[depID] {
					allDepsClosed = false
					break
				}
			}

			if len(child.DependsOn) == 0 || allDepsClosed {
				progress.ReadySteps = append(progress.ReadySteps, child.ID)
			} else {
				progress.BlockedSteps = append(progress.BlockedSteps, child.ID)
			}
		}
	}

	// Calculate completion percentage
	if progress.TotalSteps > 0 {
		progress.Percent = (progress.DoneSteps * 100) / progress.TotalSteps
	}
	progress.Complete = progress.DoneSteps == progress.TotalSteps

	// JSON output
	if moleculeJSON {
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "  ")
		return enc.Encode(progress)
	}

	// Human-readable output
	fmt.Printf("\n%s %s\n\n", style.Bold.Render("🧬 Molecule Progress:"), root.Title)
	fmt.Printf("  Root: %s\n", rootID)
	if progress.MoleculeID != "" {
		fmt.Printf("  Molecule: %s\n", progress.MoleculeID)
	}
	fmt.Println()

	// Progress bar
	barWidth := 20
	filled := (progress.Percent * barWidth) / 100
	bar := strings.Repeat("█", filled) + strings.Repeat("░", barWidth-filled)
	fmt.Printf("  [%s] %d%% (%d/%d)\n\n", bar, progress.Percent, progress.DoneSteps, progress.TotalSteps)

	// Step status
	fmt.Printf("  Done:        %d\n", progress.DoneSteps)
	fmt.Printf("  In Progress: %d\n", progress.InProgress)
	fmt.Printf("  Ready:       %d", len(progress.ReadySteps))
	if len(progress.ReadySteps) > 0 {
		fmt.Printf(" (%s)", strings.Join(progress.ReadySteps, ", "))
	}
	fmt.Println()
	fmt.Printf("  Blocked:     %d\n", len(progress.BlockedSteps))

	if progress.Complete {
		fmt.Printf("\n  %s\n", style.Bold.Render("✓ Molecule complete!"))
	}

	return nil
}

// extractMoleculeID extracts the molecule ID from an issue's description.
func extractMoleculeID(description string) string {
	lines := strings.Split(description, "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "instantiated_from:") {
			return strings.TrimSpace(strings.TrimPrefix(line, "instantiated_from:"))
		}
	}
	return ""
}

func runMoleculeStatus(cmd *cobra.Command, args []string) error {
	cwd, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("getting current directory: %w", err)
	}

	// Find town root
	townRoot, err := workspace.FindFromCwd()
	if err != nil {
		return fmt.Errorf("finding workspace: %w", err)
	}
	if townRoot == "" {
		return fmt.Errorf("not in a Gas Town workspace")
	}

	// Determine target agent
	var target string
	var roleCtx RoleContext

	if len(args) > 0 {
		// Explicit target provided
		target = args[0]
	} else {
		// Use cwd-based detection for status display
		// This ensures we show the hook for the agent whose directory we're in,
		// not the agent from the GT_ROLE env var (which might be different if
		// we cd'd into another rig's crew/polecat directory)
		roleCtx = detectRole(cwd, townRoot)
		target = buildAgentIdentity(roleCtx)
		if target == "" {
			return fmt.Errorf("cannot determine agent identity (role: %s)", roleCtx.Role)
		}
	}

	// Find beads directory
	workDir, err := findLocalBeadsDir()
	if err != nil {
		return fmt.Errorf("not in a beads workspace: %w", err)
	}

	b := beads.New(workDir)

	// Build status info
	status := MoleculeStatusInfo{
		Target: target,
		Role:   string(roleCtx.Role),
	}

	// Try to find agent bead and read hook slot
	// This is the preferred method - agent beads have a hook_bead field
	agentBeadID := buildAgentBeadID(target, roleCtx.Role, townRoot)
	var hookBead *beads.Issue

	if agentBeadID != "" {
		// Try to fetch the agent bead
		agentBead, err := b.Show(agentBeadID)
		if err == nil && agentBead != nil && agentBead.Type == "agent" {
			status.AgentBeadID = agentBeadID

			// Read hook_bead from the agent bead's database field (not description!)
			// The hook_bead column is updated by `bd slot set` in UpdateAgentState.
			// IMPORTANT: Don't use ParseAgentFieldsFromDescription - the description
			// field may contain stale data, causing the wrong issue to be hooked.
			if agentBead.HookBead != "" {
				// Fetch the bead on the hook
				hookBead, err = b.Show(agentBead.HookBead)
				if err != nil {
					// Hook bead referenced but not found - report error but continue
					hookBead = nil
				}
			}
		}
		// If agent bead not found or not an agent type, fall through to legacy approach
	}

	// If we found a hook bead via agent bead, use it
	if hookBead != nil {
		status.HasWork = true
		status.PinnedBead = hookBead

		// Check for attached molecule
		attachment := beads.ParseAttachmentFields(hookBead)
		if attachment != nil {
			status.AttachedMolecule = attachment.AttachedMolecule
			status.AttachedAt = attachment.AttachedAt
			status.AttachedArgs = attachment.AttachedArgs

			// Check if it's a wisp
			status.IsWisp = strings.Contains(hookBead.Description, "wisp: true") ||
				strings.Contains(hookBead.Description, "is_wisp: true")

			// Get progress if there's an attached molecule
			if attachment.AttachedMolecule != "" {
				progress, _ := getMoleculeProgressInfo(b, attachment.AttachedMolecule)
				status.Progress = progress
				status.NextAction = determineNextAction(status)
			}
		}
	} else {
		// FALLBACK: Query for hooked beads (work on agent's hook)
		// First try status=hooked (work that's been slung but not yet claimed)
		hookedBeads, err := b.List(beads.ListOptions{
			Status:   beads.StatusHooked,
			Assignee: target,
			Priority: -1,
		})
		if err != nil {
			return fmt.Errorf("listing hooked beads: %w", err)
		}

		// If no hooked beads found, also check in_progress beads assigned to this agent.
		// This handles the case where work was claimed (status changed to in_progress)
		// but the session was interrupted before completion. The hook should persist.
		if len(hookedBeads) == 0 {
			inProgressBeads, err := b.List(beads.ListOptions{
				Status:   "in_progress",
				Assignee: target,
				Priority: -1,
			})
			if err == nil && len(inProgressBeads) > 0 {
				// Use the first in_progress bead (should typically be only one)
				hookedBeads = inProgressBeads
			}
		}

		// For town-level roles (mayor, deacon), scan all rigs if nothing found locally
		if len(hookedBeads) == 0 && isTownLevelRole(target) {
			hookedBeads = scanAllRigsForHookedBeads(townRoot, target)
		}

		status.HasWork = len(hookedBeads) > 0

		if len(hookedBeads) > 0 {
			// Take the first hooked bead
			status.PinnedBead = hookedBeads[0]

			// Check for attached molecule
			attachment := beads.ParseAttachmentFields(hookedBeads[0])
			if attachment != nil {
				status.AttachedMolecule = attachment.AttachedMolecule
				status.AttachedAt = attachment.AttachedAt
				status.AttachedArgs = attachment.AttachedArgs

				// Check if it's a wisp
				status.IsWisp = strings.Contains(hookedBeads[0].Description, "wisp: true") ||
					strings.Contains(hookedBeads[0].Description, "is_wisp: true")

				// Get progress if there's an attached molecule
				if attachment.AttachedMolecule != "" {
					progress, _ := getMoleculeProgressInfo(b, attachment.AttachedMolecule)
					status.Progress = progress
					status.NextAction = determineNextAction(status)
				}
			}
		}
	}

	// Determine next action if no work is slung
	if !status.HasWork {
		status.NextAction = "Check inbox for work assignments: gt mail inbox"
	} else if status.AttachedMolecule == "" {
		status.NextAction = "Attach a molecule to start work: gt mol attach <bead-id> <molecule-id>"
	}

	// JSON output
	if moleculeJSON {
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "  ")
		return enc.Encode(status)
	}

	// Human-readable output
	return outputMoleculeStatus(status)
}

// buildAgentIdentity constructs the agent identity string from role context.
// Format matches session.AgentIdentity.Address() for consistency.
func buildAgentIdentity(ctx RoleContext) string {
	switch ctx.Role {
	case RoleMayor:
		return "mayor"
	case RoleDeacon:
		return "deacon"
	case RoleWitness:
		return ctx.Rig + "/witness"
	case RoleRefinery:
		return ctx.Rig + "/refinery"
	case RolePolecat:
		return ctx.Rig + "/polecats/" + ctx.Polecat
	case RoleCrew:
		return ctx.Rig + "/crew/" + ctx.Polecat
	default:
		return ""
	}
}

// getMoleculeProgressInfo gets progress info for a molecule instance.
func getMoleculeProgressInfo(b *beads.Beads, moleculeRootID string) (*MoleculeProgressInfo, error) {
	// Get the molecule root issue
	root, err := b.Show(moleculeRootID)
	if err != nil {
		return nil, fmt.Errorf("getting molecule root: %w", err)
	}

	// Find all children of the root issue
	children, err := b.List(beads.ListOptions{
		Parent:   moleculeRootID,
		Status:   "all",
		Priority: -1,
	})
	if err != nil {
		return nil, fmt.Errorf("listing children: %w", err)
	}

	if len(children) == 0 {
		// No children - might be a simple issue, not a molecule
		return nil, nil
	}

	// Build progress info
	progress := &MoleculeProgressInfo{
		RootID:    moleculeRootID,
		RootTitle: root.Title,
	}

	// Try to find molecule ID from first child's description
	for _, child := range children {
		if molID := extractMoleculeID(child.Description); molID != "" {
			progress.MoleculeID = molID
			break
		}
	}

	// Build set of closed issue IDs for dependency checking
	closedIDs := make(map[string]bool)
	for _, child := range children {
		if child.Status == "closed" {
			closedIDs[child.ID] = true
		}
	}

	// Categorize steps
	for _, child := range children {
		progress.TotalSteps++

		switch child.Status {
		case "closed":
			progress.DoneSteps++
		case "in_progress":
			progress.InProgress++
		case "open":
			// Check if all dependencies are closed
			allDepsClosed := true
			for _, depID := range child.DependsOn {
				if !closedIDs[depID] {
					allDepsClosed = false
					break
				}
			}

			if len(child.DependsOn) == 0 || allDepsClosed {
				progress.ReadySteps = append(progress.ReadySteps, child.ID)
			} else {
				progress.BlockedSteps = append(progress.BlockedSteps, child.ID)
			}
		}
	}

	// Calculate completion percentage
	if progress.TotalSteps > 0 {
		progress.Percent = (progress.DoneSteps * 100) / progress.TotalSteps
	}
	progress.Complete = progress.DoneSteps == progress.TotalSteps

	return progress, nil
}

// determineNextAction suggests the next action based on status.
func determineNextAction(status MoleculeStatusInfo) string {
	if status.Progress == nil {
		return ""
	}

	if status.Progress.Complete {
		return "Molecule complete! Close the bead: bd close " + status.PinnedBead.ID
	}

	if status.Progress.InProgress > 0 {
		return "Continue working on in-progress steps"
	}

	if len(status.Progress.ReadySteps) > 0 {
		return fmt.Sprintf("Start next ready step: bd update %s --status=in_progress", status.Progress.ReadySteps[0])
	}

	if len(status.Progress.BlockedSteps) > 0 {
		return "All remaining steps are blocked - waiting on dependencies"
	}

	return ""
}

// outputMoleculeStatus outputs human-readable status.
func outputMoleculeStatus(status MoleculeStatusInfo) error {
	// Header with hook icon
	fmt.Printf("\n%s Hook Status: %s\n", style.Bold.Render("🪝"), status.Target)
	if status.Role != "" && status.Role != "unknown" {
		fmt.Printf("Role: %s\n", status.Role)
	}
	fmt.Println()

	if !status.HasWork {
		fmt.Printf("%s\n", style.Dim.Render("Nothing on hook - no work slung"))
		fmt.Printf("\n%s %s\n", style.Bold.Render("Next:"), status.NextAction)
		return nil
	}

	// Show hooked bead info
	if status.PinnedBead == nil {
		fmt.Printf("%s\n", style.Dim.Render("Work indicated but no bead found"))
		return nil
	}

	// AUTONOMOUS MODE banner - hooked work triggers autonomous execution
	fmt.Println(style.Bold.Render("🚀 AUTONOMOUS MODE - Work on hook triggers immediate execution"))
	fmt.Println()

	// Check if this is a mail bead - display mail-specific format
	if status.PinnedBead.Type == "message" {
		sender := extractMailSender(status.PinnedBead.Labels)
		fmt.Printf("%s %s (mail)\n", style.Bold.Render("🪝 Hook:"), status.PinnedBead.ID)
		if sender != "" {
			fmt.Printf("   From: %s\n", sender)
		}
		fmt.Printf("   Subject: %s\n", status.PinnedBead.Title)
		fmt.Printf("   Run: gt mail read %s\n", status.PinnedBead.ID)
		return nil
	}

	fmt.Printf("%s %s: %s\n", style.Bold.Render("🪝 Hooked:"), status.PinnedBead.ID, status.PinnedBead.Title)

	// Show attached molecule
	if status.AttachedMolecule != "" {
		molType := "Molecule"
		if status.IsWisp {
			molType = "Wisp"
		}
		fmt.Printf("%s %s: %s\n", style.Bold.Render("🧬 "+molType+":"), status.AttachedMolecule, "")
		if status.AttachedAt != "" {
			fmt.Printf("   Attached: %s\n", status.AttachedAt)
		}
		if status.AttachedArgs != "" {
			fmt.Printf("   %s %s\n", style.Bold.Render("Args:"), status.AttachedArgs)
		}
	} else {
		fmt.Printf("%s\n", style.Dim.Render("No molecule attached (hooked bead still triggers autonomous work)"))
	}

	// Show progress if available
	if status.Progress != nil {
		fmt.Println()

		// Progress bar
		barWidth := 20
		filled := (status.Progress.Percent * barWidth) / 100
		bar := strings.Repeat("█", filled) + strings.Repeat("░", barWidth-filled)
		fmt.Printf("Progress: [%s] %d%% (%d/%d steps)\n",
			bar, status.Progress.Percent, status.Progress.DoneSteps, status.Progress.TotalSteps)

		// Step breakdown
		fmt.Printf("  Done:        %d\n", status.Progress.DoneSteps)
		fmt.Printf("  In Progress: %d\n", status.Progress.InProgress)
		fmt.Printf("  Ready:       %d", len(status.Progress.ReadySteps))
		if len(status.Progress.ReadySteps) > 0 && len(status.Progress.ReadySteps) <= 3 {
			fmt.Printf(" (%s)", strings.Join(status.Progress.ReadySteps, ", "))
		}
		fmt.Println()
		fmt.Printf("  Blocked:     %d\n", len(status.Progress.BlockedSteps))

		if status.Progress.Complete {
			fmt.Printf("\n%s\n", style.Bold.Render("✓ Molecule complete!"))
		}
	}

	// Next action hint
	if status.NextAction != "" {
		fmt.Printf("\n%s %s\n", style.Bold.Render("Next:"), status.NextAction)
	}

	return nil
}

func runMoleculeCurrent(cmd *cobra.Command, args []string) error {
	cwd, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("getting current directory: %w", err)
	}

	// Find town root
	townRoot, err := workspace.FindFromCwd()
	if err != nil {
		return fmt.Errorf("finding workspace: %w", err)
	}
	if townRoot == "" {
		return fmt.Errorf("not in a Gas Town workspace")
	}

	// Determine target agent identity
	var target string
	var roleCtx RoleContext

	if len(args) > 0 {
		// Explicit target provided
		target = args[0]
	} else {
		// Use cwd-based detection for status display
		// This ensures we show the hook for the agent whose directory we're in,
		// not the agent from the GT_ROLE env var (which might be different if
		// we cd'd into another rig's crew/polecat directory)
		roleCtx = detectRole(cwd, townRoot)
		target = buildAgentIdentity(roleCtx)
		if target == "" {
			return fmt.Errorf("cannot determine agent identity (role: %s)", roleCtx.Role)
		}
	}

	// Find beads directory
	workDir, err := findLocalBeadsDir()
	if err != nil {
		return fmt.Errorf("not in a beads workspace: %w", err)
	}

	b := beads.New(workDir)

	// Extract role from target for handoff bead lookup
	parts := strings.Split(target, "/")
	role := parts[len(parts)-1]

	// Find handoff bead for this identity
	handoff, err := b.FindHandoffBead(role)
	if err != nil {
		return fmt.Errorf("finding handoff bead: %w", err)
	}

	// Build current info
	info := MoleculeCurrentInfo{
		Identity: target,
	}

	if handoff == nil {
		info.Status = "naked"
		return outputMoleculeCurrent(info)
	}

	info.HandoffID = handoff.ID
	info.HandoffTitle = handoff.Title

	// Check for attached molecule
	attachment := beads.ParseAttachmentFields(handoff)
	if attachment == nil || attachment.AttachedMolecule == "" {
		info.Status = "naked"
		return outputMoleculeCurrent(info)
	}

	info.MoleculeID = attachment.AttachedMolecule

	// Get the molecule root to find its title and children
	molRoot, err := b.Show(attachment.AttachedMolecule)
	if err != nil {
		// Molecule not found - might be a template ID, still report what we have
		info.Status = "working"
		return outputMoleculeCurrent(info)
	}

	info.MoleculeTitle = molRoot.Title

	// Find all children (steps) of the molecule root
	children, err := b.List(beads.ListOptions{
		Parent:   attachment.AttachedMolecule,
		Status:   "all",
		Priority: -1,
	})
	if err != nil {
		// No steps - just an issue, not a molecule instance
		info.Status = "working"
		return outputMoleculeCurrent(info)
	}

	info.StepsTotal = len(children)

	// Build set of closed issue IDs for dependency checking
	closedIDs := make(map[string]bool)
	var inProgressSteps []*beads.Issue
	var readySteps []*beads.Issue

	for _, child := range children {
		switch child.Status {
		case "closed":
			info.StepsComplete++
			closedIDs[child.ID] = true
		case "in_progress":
			inProgressSteps = append(inProgressSteps, child)
		}
	}

	// Find ready steps (open with all deps closed)
	for _, child := range children {
		if child.Status == "open" {
			allDepsClosed := true
			for _, depID := range child.DependsOn {
				if !closedIDs[depID] {
					allDepsClosed = false
					break
				}
			}
			if len(child.DependsOn) == 0 || allDepsClosed {
				readySteps = append(readySteps, child)
			}
		}
	}

	// Determine current step and status
	if info.StepsComplete == info.StepsTotal && info.StepsTotal > 0 {
		info.Status = "complete"
	} else if len(inProgressSteps) > 0 {
		// First in-progress step is the current one
		info.Status = "working"
		info.CurrentStepID = inProgressSteps[0].ID
		info.CurrentStep = inProgressSteps[0].Title
	} else if len(readySteps) > 0 {
		// First ready step is the next to work on
		info.Status = "working"
		info.CurrentStepID = readySteps[0].ID
		info.CurrentStep = readySteps[0].Title
	} else if info.StepsTotal > 0 {
		// Has steps but none ready or in-progress -> blocked
		info.Status = "blocked"
	} else {
		info.Status = "working"
	}

	return outputMoleculeCurrent(info)
}

// outputMoleculeCurrent outputs the current info in the appropriate format.
func outputMoleculeCurrent(info MoleculeCurrentInfo) error {
	if moleculeJSON {
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "  ")
		return enc.Encode(info)
	}

	// Human-readable output matching spec format
	fmt.Printf("Identity: %s\n", info.Identity)

	if info.HandoffID != "" {
		fmt.Printf("Handoff:  %s (%s)\n", info.HandoffID, info.HandoffTitle)
	} else {
		fmt.Printf("Handoff:  %s\n", style.Dim.Render("(none)"))
	}

	if info.MoleculeID != "" {
		if info.MoleculeTitle != "" {
			fmt.Printf("Molecule: %s (%s)\n", info.MoleculeID, info.MoleculeTitle)
		} else {
			fmt.Printf("Molecule: %s\n", info.MoleculeID)
		}
	} else {
		fmt.Printf("Molecule: %s\n", style.Dim.Render("(none attached)"))
	}

	if info.StepsTotal > 0 {
		fmt.Printf("Progress: %d/%d steps complete\n", info.StepsComplete, info.StepsTotal)
	}

	if info.CurrentStepID != "" {
		fmt.Printf("Current:  %s - %s\n", info.CurrentStepID, info.CurrentStep)
	} else if info.Status == "naked" {
		fmt.Printf("Status:   %s\n", style.Dim.Render("naked - awaiting work assignment"))
	} else if info.Status == "complete" {
		fmt.Printf("Status:   %s\n", style.Bold.Render("complete - molecule finished"))
	} else if info.Status == "blocked" {
		fmt.Printf("Status:   %s\n", style.Dim.Render("blocked - waiting on dependencies"))
	}

	return nil
}

// getGitRootForMolStatus returns the git root for hook file lookup.
func getGitRootForMolStatus() (string, error) {
	cmd := exec.Command("git", "rev-parse", "--show-toplevel")
	out, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(out)), nil
}

// isTownLevelRole returns true if the agent ID is a town-level role.
// Town-level roles (Mayor, Deacon) operate from the town root and may have
// pinned beads in any rig's beads directory.
func isTownLevelRole(agentID string) bool {
	return agentID == "mayor" || agentID == "deacon"
}

// extractMailSender extracts the sender from mail bead labels.
// Mail beads have a "from:X" label containing the sender address.
func extractMailSender(labels []string) string {
	for _, label := range labels {
		if strings.HasPrefix(label, "from:") {
			return strings.TrimPrefix(label, "from:")
		}
	}
	return ""
}

// scanAllRigsForHookedBeads scans all registered rigs for hooked beads
// assigned to the target agent. Used for town-level roles that may have
// work hooked in any rig.
func scanAllRigsForHookedBeads(townRoot, target string) []*beads.Issue {
	// Load routes from town beads
	townBeadsDir := filepath.Join(townRoot, ".beads")
	routes, err := beads.LoadRoutes(townBeadsDir)
	if err != nil {
		return nil
	}

	// Scan each rig's beads directory
	for _, route := range routes {
		rigBeadsDir := filepath.Join(townRoot, route.Path)
		if _, err := os.Stat(rigBeadsDir); os.IsNotExist(err) {
			continue
		}

		b := beads.New(rigBeadsDir)

		// First check for hooked beads
		hookedBeads, err := b.List(beads.ListOptions{
			Status:   beads.StatusHooked,
			Assignee: target,
			Priority: -1,
		})
		if err != nil {
			continue
		}

		if len(hookedBeads) > 0 {
			return hookedBeads
		}

		// Also check for in_progress beads (work that was claimed but session interrupted)
		inProgressBeads, err := b.List(beads.ListOptions{
			Status:   "in_progress",
			Assignee: target,
			Priority: -1,
		})
		if err != nil {
			continue
		}

		if len(inProgressBeads) > 0 {
			return inProgressBeads
		}
	}

	return nil
}
