package tui

import (
	"fmt"
	"strings"

	tea "github.com/charmbracelet/bubbletea"

	"github.com/andreagrandi/mcp-wire/internal/catalog"
	"github.com/andreagrandi/mcp-wire/internal/service"
	targetpkg "github.com/andreagrandi/mcp-wire/internal/target"
)

// Callbacks provides functions that generate output for display in the TUI
// and configuration flags that control wizard behavior.
type Callbacks struct {
	LoadCatalog           func(source string) (*catalog.Catalog, error)
	RegistrySyncStatus    func() string
	RefreshRegistryEntry  func(catalog.Entry) catalog.Entry
	CatalogEntryToService func(catalog.Entry) (service.Service, bool)
	AllTargets            func() []targetpkg.Target
	RegistryEnabled       bool

	// Credential resolution.
	ResolveCredential func(envName string) (value, source string, found bool)
	StoreCredential   func(envName, value string) error

	// Apply operations.
	InstallTarget    func(svc service.Service, env map[string]string, t targetpkg.Target, scope targetpkg.ConfigScope) error
	UninstallTarget  func(name string, t targetpkg.Target, scope targetpkg.ConfigScope) error
	ServiceUsesOAuth func(svc service.Service) bool
	OAuthManualHint  func(t targetpkg.Target) string

	// Credential cleanup (post-uninstall).
	RemoveStoredCredentials func(envNames []string) (int, error)

	// Installed service listing (for uninstall flow).
	ListInstalledServices func(t targetpkg.Target, scope targetpkg.ConfigScope) ([]string, error)

	// URL opening.
	OpenURL func(url string) error
}

// WizardState holds the accumulated selections across wizard screens.
type WizardState struct {
	Action      string                // "install" or "uninstall"
	Source      string                // "curated", "registry", "all"
	Entry       catalog.Entry         // selected service
	Targets     []targetpkg.Target    // selected targets
	Scope       targetpkg.ConfigScope // "user" or "project"
	Service     service.Service       // resolved service definition
	ResolvedEnv map[string]string     // resolved credential values
}

// WizardModel is the root Bubble Tea model for the full-screen TUI.
type WizardModel struct {
	theme     Theme
	screen    Screen
	callbacks Callbacks
	version   string
	state     WizardState
	steps     []BreadcrumbStep
	width     int
	height    int
}

// NewWizardModel creates a new root model starting at the main menu.
func NewWizardModel(cb Callbacks, version string) WizardModel {
	theme := NewTheme()
	return WizardModel{
		theme:     theme,
		screen:    NewMenuScreen(theme),
		callbacks: cb,
		version:   version,
	}
}

func (m WizardModel) Init() tea.Cmd {
	return m.screen.Init()
}

func (m WizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		m.width = msg.Width
		m.height = msg.Height
		// Forward to screen so it can adjust (e.g. scroll bounds).

	case tea.KeyMsg:
		if msg.String() == "ctrl+c" {
			return m, tea.Quit
		}

	case menuSelectMsg:
		return m.handleMenuSelect(msg)

	case sourceSelectMsg:
		return m.handleSourceSelect(msg)

	case serviceSelectMsg:
		return m.handleServiceSelect(msg)

	case trustConfirmMsg:
		return m.handleTrustConfirm(msg)

	case targetSelectMsg:
		return m.handleTargetSelect(msg)

	case scopeSelectMsg:
		return m.handleScopeSelect(msg)

	case reviewConfirmMsg:
		return m.handleReviewConfirm(msg)

	case credentialDoneMsg:
		return m.handleCredentialDone(msg)

	case applyPostActionMsg:
		return m.handleApplyPostAction(msg)

	case BackMsg:
		return m.handleBack()
	}

	var cmd tea.Cmd
	m.screen, cmd = m.screen.Update(msg)
	return m, cmd
}

func (m WizardModel) View() string {
	// Title bar.
	titleLabel := "mcp-wire"
	if m.version != "" {
		titleLabel += " v" + m.version
	}

	titleText := m.theme.Title.Render(titleLabel)
	breadcrumb := RenderBreadcrumb(m.theme, m.steps)

	var titleBar string
	if breadcrumb != "" {
		titleBar = titleText + "  " + breadcrumb
	} else {
		titleBar = titleText
	}

	// Separator line.
	sepWidth := m.width
	if sepWidth <= 0 {
		sepWidth = 40
	}

	separator := m.theme.Separator.Render(strings.Repeat("\u2500", sepWidth))

	// Content area.
	content := m.screen.View()
	contentHeight := m.contentHeight()
	content = padToHeight(content, contentHeight)

	// Status bar.
	statusBar := RenderStatusBar(m.theme, m.screen.StatusHints(), m.width)

	return titleBar + "\n" + separator + "\n" + content + "\n" + statusBar
}

func (m WizardModel) contentHeight() int {
	return contentHeightFromTerminal(m.height)
}

func (m WizardModel) handleMenuSelect(msg menuSelectMsg) (tea.Model, tea.Cmd) {
	switch msg.item {
	case "Exit":
		return m, tea.Quit

	case "Install service":
		return m.startWizard("install")

	case "Uninstall service":
		return m.startUninstallWizard()
	}

	return m, nil
}

func (m WizardModel) startWizard(action string) (tea.Model, tea.Cmd) {
	m.state = WizardState{Action: action}

	if m.callbacks.RegistryEnabled {
		m.screen = NewSourceScreen(m.theme)
		m.steps = []BreadcrumbStep{
			{Label: "Source", Active: true, Visible: true},
		}
		return m, m.screen.Init()
	}

	// Registry disabled — default to curated, skip source screen.
	m.state.Source = "curated"
	return m.showServiceScreen()
}

func (m WizardModel) startUninstallWizard() (tea.Model, tea.Cmd) {
	m.state = WizardState{Action: "uninstall"}
	return m.showUninstallTargetScreen()
}

func (m WizardModel) showUninstallTargetScreen() (tea.Model, tea.Cmd) {
	m.steps = []BreadcrumbStep{
		{Label: "Targets", Active: true, Visible: true},
	}

	var allTargets []targetpkg.Target
	if m.callbacks.AllTargets != nil {
		allTargets = m.callbacks.AllTargets()
	}

	m.screen = NewTargetScreen(m.theme, allTargets, m.state.Targets)
	return m, m.screen.Init()
}

func (m WizardModel) showInstalledServiceScreen() (tea.Model, tea.Cmd) {
	steps := []BreadcrumbStep{
		{Label: "Targets", Value: targetSummary(m.state.Targets), Completed: true, Visible: true},
		{Label: "Service", Active: true, Visible: true},
	}
	m.steps = steps

	cat := m.buildInstalledCatalog()
	m.screen = NewServiceScreen(
		m.theme, "curated", m.contentHeight(),
		func(_ string) (*catalog.Catalog, error) { return cat, nil },
		nil,
	)
	return m, m.screen.Init()
}

func (m WizardModel) buildInstalledCatalog() *catalog.Catalog {
	seen := make(map[string]bool)
	var entries []catalog.Entry

	for _, t := range m.state.Targets {
		var names []string
		if m.callbacks.ListInstalledServices != nil {
			names, _ = m.callbacks.ListInstalledServices(t, targetpkg.ConfigScopeEffective)
		}
		for _, name := range names {
			key := strings.ToLower(name)
			if seen[key] {
				continue
			}
			seen[key] = true
			entries = append(entries, catalog.Entry{
				Source: catalog.SourceCurated,
				Name:   name,
			})
		}
	}

	return catalog.Merge(entries, nil)
}

func (m WizardModel) handleSourceSelect(msg sourceSelectMsg) (tea.Model, tea.Cmd) {
	m.state.Source = msg.source
	return m.showServiceScreen()
}

func (m WizardModel) showServiceScreen() (tea.Model, tea.Cmd) {
	var steps []BreadcrumbStep
	if m.callbacks.RegistryEnabled {
		steps = append(steps, BreadcrumbStep{
			Label: "Source", Value: sourceValueLabel(m.state.Source),
			Completed: true, Visible: true,
		})
	}
	steps = append(steps, BreadcrumbStep{
		Label: "Service", Active: true, Visible: true,
	})
	m.steps = steps
	m.screen = NewServiceScreen(
		m.theme, m.state.Source, m.contentHeight(),
		m.callbacks.LoadCatalog, m.callbacks.RegistrySyncStatus,
	)
	return m, m.screen.Init()
}

func (m WizardModel) handleServiceSelect(msg serviceSelectMsg) (tea.Model, tea.Cmd) {
	m.state.Entry = msg.entry

	// Uninstall: targets already selected, skip trust, go to scope or review.
	if m.state.Action == "uninstall" {
		if anyTargetSupportsProjectScope(m.state.Targets) {
			return m.showUninstallScopeScreen()
		}
		m.state.Scope = targetpkg.ConfigScopeUser
		return m.showReviewScreen()
	}

	if registryEntryNeedsConfirmation(msg.entry) {
		return m.showTrustScreen()
	}

	return m.showTargetScreen()
}

func (m WizardModel) handleTrustConfirm(msg trustConfirmMsg) (tea.Model, tea.Cmd) {
	if !msg.confirmed {
		// Back to service selection.
		return m.showServiceScreen()
	}

	// Refresh the entry with latest details.
	if m.callbacks.RefreshRegistryEntry != nil {
		m.state.Entry = m.callbacks.RefreshRegistryEntry(m.state.Entry)
	}

	return m.showTargetScreen()
}

func (m WizardModel) showTrustScreen() (tea.Model, tea.Cmd) {
	var steps []BreadcrumbStep
	if m.callbacks.RegistryEnabled {
		steps = append(steps, BreadcrumbStep{
			Label: "Source", Value: sourceValueLabel(m.state.Source),
			Completed: true, Visible: true,
		})
	}
	steps = append(steps, BreadcrumbStep{
		Label: "Service", Value: m.state.Entry.Name,
		Completed: true, Visible: true,
	})
	steps = append(steps, BreadcrumbStep{
		Label: "Trust", Active: true, Visible: true,
	})
	m.steps = steps
	m.screen = NewTrustScreen(m.theme, m.state.Entry)
	return m, m.screen.Init()
}

func (m WizardModel) showTargetScreen() (tea.Model, tea.Cmd) {
	var steps []BreadcrumbStep
	if m.callbacks.RegistryEnabled {
		steps = append(steps, BreadcrumbStep{
			Label: "Source", Value: sourceValueLabel(m.state.Source),
			Completed: true, Visible: true,
		})
	}
	steps = append(steps, BreadcrumbStep{
		Label: "Service", Value: m.state.Entry.Name,
		Completed: true, Visible: true,
	})
	steps = append(steps, BreadcrumbStep{
		Label: "Targets", Active: true, Visible: true,
	})
	m.steps = steps

	var allTargets []targetpkg.Target
	if m.callbacks.AllTargets != nil {
		allTargets = m.callbacks.AllTargets()
	}

	m.screen = NewTargetScreen(m.theme, allTargets, m.state.Targets)
	return m, m.screen.Init()
}

func (m WizardModel) handleTargetSelect(msg targetSelectMsg) (tea.Model, tea.Cmd) {
	m.state.Targets = msg.targets

	// Uninstall: targets selected first, now show installed services.
	if m.state.Action == "uninstall" {
		return m.showInstalledServiceScreen()
	}

	if anyTargetSupportsProjectScope(msg.targets) {
		return m.showScopeScreen()
	}

	// No scope selection needed — default to user scope.
	m.state.Scope = targetpkg.ConfigScopeUser
	return m.showReviewScreen()
}

func (m WizardModel) showScopeScreen() (tea.Model, tea.Cmd) {
	var steps []BreadcrumbStep
	if m.callbacks.RegistryEnabled {
		steps = append(steps, BreadcrumbStep{
			Label: "Source", Value: sourceValueLabel(m.state.Source),
			Completed: true, Visible: true,
		})
	}
	steps = append(steps, BreadcrumbStep{
		Label: "Service", Value: m.state.Entry.Name,
		Completed: true, Visible: true,
	})
	steps = append(steps, BreadcrumbStep{
		Label: "Targets", Value: targetSummary(m.state.Targets),
		Completed: true, Visible: true,
	})
	steps = append(steps, BreadcrumbStep{
		Label: "Scope", Active: true, Visible: true,
	})
	m.steps = steps
	m.screen = NewScopeScreen(m.theme, scopedTargetNames(m.state.Targets))
	return m, m.screen.Init()
}

func (m WizardModel) showUninstallScopeScreen() (tea.Model, tea.Cmd) {
	steps := []BreadcrumbStep{
		{Label: "Targets", Value: targetSummary(m.state.Targets), Completed: true, Visible: true},
		{Label: "Service", Value: m.state.Entry.Name, Completed: true, Visible: true},
		{Label: "Scope", Active: true, Visible: true},
	}
	m.steps = steps
	m.screen = NewScopeScreen(m.theme, scopedTargetNames(m.state.Targets))
	return m, m.screen.Init()
}

func (m WizardModel) handleScopeSelect(msg scopeSelectMsg) (tea.Model, tea.Cmd) {
	m.state.Scope = msg.scope
	return m.showReviewScreen()
}

func (m WizardModel) showReviewScreen() (tea.Model, tea.Cmd) {
	m.steps = m.reviewBreadcrumbs()
	m.screen = NewReviewScreen(m.theme, m.state, m.callbacks.RegistryEnabled)
	return m, m.screen.Init()
}

func (m WizardModel) handleReviewConfirm(msg reviewConfirmMsg) (tea.Model, tea.Cmd) {
	if !msg.confirmed {
		return m.reviewGoBack()
	}

	// Convert catalog entry to service.
	svc, ok := m.convertEntryToService()
	if !ok {
		content := "Cannot resolve service definition.\n" +
			"No supported install method found for " + m.state.Entry.Name + ".\n"
		m.screen = NewOutputScreen(m.theme, content, m.contentHeight())
		return m, m.screen.Init()
	}
	m.state.Service = svc

	// For uninstall, skip credentials and go directly to apply.
	if m.state.Action == "uninstall" {
		m.state.ResolvedEnv = nil
		return m.showApplyScreen()
	}

	// Resolve credentials from existing sources.
	resolvedEnv, unresolvedVars := m.resolveExistingCredentials(svc)
	m.state.ResolvedEnv = resolvedEnv

	if len(unresolvedVars) > 0 {
		return m.showCredentialScreen(unresolvedVars)
	}

	return m.showApplyScreen()
}

// convertEntryToService converts the selected catalog entry to a service.Service.
func (m WizardModel) convertEntryToService() (service.Service, bool) {
	if m.callbacks.CatalogEntryToService != nil {
		svc, ok := m.callbacks.CatalogEntryToService(m.state.Entry)
		if ok {
			return svc, true
		}
	} else if m.state.Entry.Curated != nil {
		// Curated entry with direct service reference.
		return *m.state.Entry.Curated, true
	}
	// Name-only entry (e.g. installed service not in catalog) — for uninstall
	// we only need the name to remove it.
	if m.state.Action == "uninstall" && m.state.Entry.Name != "" {
		return service.Service{Name: m.state.Entry.Name}, true
	}
	return service.Service{}, false
}

// resolveExistingCredentials checks which env vars are already resolved and
// returns the resolved values and the list of unresolved required vars.
func (m WizardModel) resolveExistingCredentials(svc service.Service) (map[string]string, []service.EnvVar) {
	resolvedEnv := make(map[string]string)
	var unresolvedVars []service.EnvVar

	for _, ev := range svc.Env {
		name := strings.TrimSpace(ev.Name)
		if name == "" {
			continue
		}

		if m.callbacks.ResolveCredential != nil {
			value, _, found := m.callbacks.ResolveCredential(name)
			if found {
				resolvedEnv[name] = value
				continue
			}
		}

		if ev.Required {
			unresolvedVars = append(unresolvedVars, ev)
		} else if defaultVal := strings.TrimSpace(ev.Default); defaultVal != "" {
			resolvedEnv[name] = defaultVal
		}
	}

	return resolvedEnv, unresolvedVars
}

func (m WizardModel) showCredentialScreen(unresolvedVars []service.EnvVar) (tea.Model, tea.Cmd) {
	steps := m.reviewBreadcrumbs()
	steps = append(steps, BreadcrumbStep{
		Label: "Credentials", Active: true, Visible: true,
	})
	m.steps = steps

	m.screen = NewCredentialScreen(
		m.theme,
		unresolvedVars,
		m.state.ResolvedEnv,
		m.callbacks.StoreCredential,
		m.callbacks.OpenURL,
	)
	return m, m.screen.Init()
}

func (m WizardModel) handleCredentialDone(msg credentialDoneMsg) (tea.Model, tea.Cmd) {
	m.state.ResolvedEnv = msg.resolvedEnv

	// Apply registry substitutions to the service.
	applySubstitutions(&m.state.Service, m.state.ResolvedEnv)

	return m.showApplyScreen()
}

func (m WizardModel) showApplyScreen() (tea.Model, tea.Cmd) {
	steps := m.reviewBreadcrumbs()
	steps = append(steps, BreadcrumbStep{
		Label: "Apply", Active: true, Visible: true,
	})
	m.steps = steps

	m.screen = NewApplyScreen(
		m.theme,
		m.state,
		m.state.Service,
		m.state.ResolvedEnv,
		ApplyCallbacks{
			InstallTarget:           m.callbacks.InstallTarget,
			UninstallTarget:         m.callbacks.UninstallTarget,
			ServiceUsesOAuth:        m.callbacks.ServiceUsesOAuth,
			OAuthManualHint:         m.callbacks.OAuthManualHint,
			RemoveStoredCredentials: m.callbacks.RemoveStoredCredentials,
		},
	)
	return m, m.screen.Init()
}

func (m WizardModel) handleApplyPostAction(msg applyPostActionMsg) (tea.Model, tea.Cmd) {
	switch msg.action {
	case "another":
		if m.state.Action == "uninstall" {
			return m.startUninstallWizard()
		}
		return m.startWizard(m.state.Action)
	case "exit":
		return m, tea.Quit
	default:
		// "menu" or unknown — return to menu.
		m.screen = NewMenuScreen(m.theme)
		m.state = WizardState{}
		m.steps = nil
		return m, m.screen.Init()
	}
}

// reviewGoBack navigates back from the review screen to the previous step.
func (m WizardModel) reviewGoBack() (tea.Model, tea.Cmd) {
	if anyTargetSupportsProjectScope(m.state.Targets) {
		m.state.Scope = ""
		if m.state.Action == "uninstall" {
			return m.showUninstallScopeScreen()
		}
		return m.showScopeScreen()
	}

	if m.state.Action == "uninstall" {
		return m.showInstalledServiceScreen()
	}

	return m.showTargetScreen()
}

// applySubstitutions replaces {VAR} placeholders in the service URL, headers,
// and args with resolved credential values.
func applySubstitutions(svc *service.Service, resolvedEnv map[string]string) {
	for name, value := range resolvedEnv {
		placeholder := "{" + name + "}"
		svc.URL = strings.ReplaceAll(svc.URL, placeholder, value)
		for hdr, tmpl := range svc.Headers {
			svc.Headers[hdr] = strings.ReplaceAll(tmpl, placeholder, value)
		}
		for i, arg := range svc.Args {
			svc.Args[i] = strings.ReplaceAll(arg, placeholder, value)
		}
	}
}

// reviewBreadcrumbs builds the breadcrumb steps for the review screen.
func (m WizardModel) reviewBreadcrumbs() []BreadcrumbStep {
	var steps []BreadcrumbStep

	if m.state.Action == "uninstall" {
		// Uninstall order: Targets → Service → (Scope).
		steps = append(steps, BreadcrumbStep{
			Label: "Targets", Value: targetSummary(m.state.Targets),
			Completed: true, Visible: true,
		})
		steps = append(steps, BreadcrumbStep{
			Label: "Service", Value: m.state.Entry.Name,
			Completed: true, Visible: true,
		})
		if m.state.Scope == targetpkg.ConfigScopeProject {
			steps = append(steps, BreadcrumbStep{
				Label: "Scope", Value: "project",
				Completed: true, Visible: true,
			})
		}
		return steps
	}

	// Install order: (Source) → Service → Targets → (Scope).
	if m.callbacks.RegistryEnabled {
		steps = append(steps, BreadcrumbStep{
			Label: "Source", Value: sourceValueLabel(m.state.Source),
			Completed: true, Visible: true,
		})
	}
	steps = append(steps, BreadcrumbStep{
		Label: "Service", Value: m.state.Entry.Name,
		Completed: true, Visible: true,
	})
	steps = append(steps, BreadcrumbStep{
		Label: "Targets", Value: targetSummary(m.state.Targets),
		Completed: true, Visible: true,
	})
	if m.state.Scope == targetpkg.ConfigScopeProject {
		steps = append(steps, BreadcrumbStep{
			Label: "Scope", Value: "project",
			Completed: true, Visible: true,
		})
	}
	return steps
}

// targetSummary returns a short label for the selected targets.
func targetSummary(targets []targetpkg.Target) string {
	if len(targets) == 0 {
		return ""
	}
	if len(targets) == 1 {
		return targets[0].Name()
	}
	return targets[0].Name() + " +" + fmt.Sprintf("%d", len(targets)-1)
}

// anyTargetSupportsProjectScope checks if any of the given targets support
// the project config scope.
func anyTargetSupportsProjectScope(targets []targetpkg.Target) bool {
	for _, t := range targets {
		st, ok := t.(targetpkg.ScopedTarget)
		if !ok {
			continue
		}
		for _, s := range st.SupportedScopes() {
			if s == targetpkg.ConfigScopeProject {
				return true
			}
		}
	}
	return false
}

// scopedTargetNames returns a comma-separated list of target names that
// support project scope, for display in the scope selection heading.
func scopedTargetNames(targets []targetpkg.Target) string {
	var names []string
	for _, t := range targets {
		st, ok := t.(targetpkg.ScopedTarget)
		if !ok {
			continue
		}
		for _, s := range st.SupportedScopes() {
			if s == targetpkg.ConfigScopeProject {
				names = append(names, t.Name())
				break
			}
		}
	}
	return strings.Join(names, ", ")
}

func (m WizardModel) handleBack() (tea.Model, tea.Cmd) {
	switch m.screen.(type) {
	case *ApplyScreen:
		// Cannot go back from apply (operations may have started).
		return m, nil

	case *CredentialScreen:
		// Back from credentials goes to review, clearing resolved state.
		m.state.ResolvedEnv = nil
		m.state.Service = service.Service{}
		return m.showReviewScreen()

	case *ReviewScreen:
		return m.reviewGoBack()

	case *ScopeScreen:
		if m.state.Action == "uninstall" {
			// Uninstall: back from scope goes to service selection.
			return m.showInstalledServiceScreen()
		}
		// Back from scope goes to target selection, preserving selections.
		return m.showTargetScreen()

	case *TargetScreen:
		if m.state.Action == "uninstall" {
			// Uninstall: target is the first screen, back goes to menu.
			m.screen = NewMenuScreen(m.theme)
			m.state = WizardState{}
			m.steps = nil
			return m, m.screen.Init()
		}
		// Back from target goes to service selection.
		m.state.Targets = nil
		m.state.Scope = ""
		m.state.Entry = catalog.Entry{}
		return m.showServiceScreen()

	case *TrustScreen:
		// Back from trust goes to service selection.
		m.state.Entry = catalog.Entry{}
		return m.showServiceScreen()

	case *ServiceScreen:
		if m.state.Action == "uninstall" {
			// Uninstall: back from service goes to target selection.
			m.state.Entry = catalog.Entry{}
			return m.showUninstallTargetScreen()
		}
		if m.callbacks.RegistryEnabled {
			// Back to source selection.
			m.screen = NewSourceScreen(m.theme)
			m.state.Source = ""
			m.state.Entry = catalog.Entry{}
			m.steps = []BreadcrumbStep{
				{Label: "Source", Active: true, Visible: true},
			}
			return m, m.screen.Init()
		}
	}

	// Default: return to menu.
	m.screen = NewMenuScreen(m.theme)
	m.state = WizardState{}
	m.steps = nil
	return m, m.screen.Init()
}

// sourceValueLabel returns a display label for a source value.
func sourceValueLabel(source string) string {
	labels := map[string]string{
		"curated":  "Curated services",
		"registry": "Registry services (community)",
		"all":      "Both (curated + registry)",
	}
	if l, ok := labels[source]; ok {
		return l
	}
	return source
}

// contentHeightFromTerminal calculates the content area height from the
// terminal height, subtracting the chrome lines (title + separator + status bar).
func contentHeightFromTerminal(termHeight int) int {
	if termHeight <= 0 {
		return ContentHeight
	}

	h := termHeight - ChromeLines
	if h < 1 {
		h = 1
	}

	return h
}

// padToHeight pads or truncates content to exactly targetHeight lines.
func padToHeight(content string, targetHeight int) string {
	content = strings.TrimRight(content, "\n")

	lines := strings.Split(content, "\n")
	for len(lines) < targetHeight {
		lines = append(lines, "")
	}

	if len(lines) > targetHeight {
		lines = lines[:targetHeight]
	}

	return strings.Join(lines, "\n")
}

// Run starts the full-screen TUI.
func Run(cb Callbacks, version string) error {
	p := tea.NewProgram(NewWizardModel(cb, version), tea.WithAltScreen())
	_, err := p.Run()
	return err
}
