package cli

import (
	"fmt"
	"strings"

	"github.com/charmbracelet/x/ansi"

	"reasonix/internal/agent"
	"reasonix/internal/control"
	"reasonix/internal/provider"
)

func (m *chatTUI) showBranchTree() {
	branches, err := m.ctrl.Branches()
	m.followSessionLease()
	if err != nil {
		m.notice("tree: " + err.Error())
		return
	}
	current := agent.BranchID(m.ctrl.SessionPath())
	tree := renderBranchTree(control.FormatBranchTree(branches, current))
	m.commitLine(ansi.Hardwrap(tree, max(m.width, 20), false))
}

func renderBranchTree(tree string) string {
	lines := strings.Split(tree, "\n")
	for i, line := range lines {
		lines[i] = renderBranchTreeLine(line)
	}
	return strings.Join(lines, "\n")
}

func renderBranchTreeLine(line string) string {
	if line == "branches:" {
		return accent(line)
	}
	joint := strings.LastIndex(line, "├─ ")
	if alt := strings.LastIndex(line, "└─ "); alt > joint {
		joint = alt
	}
	if joint < 0 {
		return line
	}
	treePrefix := line[:joint+len("├─ ")]
	parts := strings.SplitN(line[joint+len("├─ "):], "  ", 3)
	if len(parts) < 3 {
		return line
	}
	id, title, meta := parts[0], parts[1], parts[2]

	turns := meta
	current := ""
	if before, after, ok := strings.Cut(meta, "  "); ok {
		turns = before
		if strings.TrimSpace(after) == "current" {
			current = "  " + accent("current")
		} else if strings.TrimSpace(after) != "" {
			current = "  " + after
		}
	}
	return dim(treePrefix) + dim(id) + "  " + title + "  " + dim(turns) + current
}

func (m *chatTUI) runBranchCommand(input string) {
	cmd := strings.Fields(input)[0]
	args := strings.TrimSpace(strings.TrimPrefix(input, cmd))

	// /branch 3 optional-name branches from displayed turn 3. Plain /branch
	// branches from the current tip.
	if n, name, fromTurn, err := control.ParseBranchTarget(args); err != nil {
		m.notice(err.Error())
		return
	} else if fromTurn {
		if _, err := m.ctrl.ForkNamed(n-1, name); err != nil {
			m.followSessionLease()
			return
		}
		m.followSessionLease()
		m.replayActiveBranch(fmt.Sprintf("branched from turn %d", n))
		return
	} else {
		if _, err := m.ctrl.Branch(name); err != nil {
			m.followSessionLease()
			return
		}
		m.followSessionLease()
	}
	m.showBranchTree()
}

func (m *chatTUI) runSwitchCommand(input string) {
	ref := strings.TrimSpace(strings.TrimPrefix(input, strings.Fields(input)[0]))
	if ref == "" {
		m.notice("usage: /switch <branch id|name>")
		return
	}
	// Move the session lease before the controller binds the target branch for
	// writing; a branch held by another runtime is refused here. Resolution
	// failures fall through to SwitchBranch, which reports them as before.
	if m.leases != nil {
		if branches, err := m.ctrl.Branches(); err == nil {
			m.followSessionLease()
			if match, err := control.ResolveBranchRef(branches, ref); err == nil {
				if err := m.rebindSessionLease(match.Path); err != nil {
					m.notice("switch: " + sessionLeaseHeldNotice(err))
					return
				}
			}
		} else {
			m.followSessionLease()
		}
	}
	if _, err := m.ctrl.SwitchBranch(ref); err != nil {
		// The switch failed after the lease already moved; re-point it at the
		// session the controller still owns.
		m.restoreSessionLease()
		return
	}
	m.replayActiveBranch("switched branch")
}

func (m *chatTUI) replayActiveBranch(title string) {
	m.finalizeStreamed()
	m.pending.Reset()
	m.reasoning.Reset()
	m.todoArgs = ""
	m.chooser = nil
	m.pendingApproval = nil
	m.bubblePending = false
	m.turnDiscarded = false
	m.planMode = false
	m.ctrl.SetPlanMode(false)
	m.sessionSwitch = true

	// Discard the previous session's transcript so the viewport only shows the
	// newly loaded session. Without this the transcript accumulates across
	// every /resume / /switch / /rewind / /branch, bloating memory and causing
	// the scroll position to be preserved at a stale offset inside the merged
	// content (#4584).
	m.clearTranscriptDisplay()
	m.transcriptDirty = true
	m.forceGotoBottom = true

	m.commitLine("")
	if title != "" {
		m.commitLine(dim("  -- " + title + " --"))
	}
	m.commitTranscriptSource(transcriptSource{
		kind:    transcriptSourceReplayBundle,
		history: append([]provider.Message(nil), m.ctrl.History()...),
	})
}
