package app

import (
	"github.com/correx/tui-go/internal/protocol"
	"github.com/correx/tui-go/internal/ws"
)

// DisplayState is the top-level screen the UI renders.
type DisplayState int

const (
	StateIdle DisplayState = iota
	StateInSession
	StateApproval
)

// InputMode toggles what a submitted line targets.
type InputMode int

const (
	ModeRouter InputMode = iota // chat / steering input
	ModeFilter                  // session-list filter
	ModeIntent                  // freeform request for a workflow being started
)

// EditMode is the vim-style modality: Normal = bare-key commands, Insert = typing.
type EditMode int

const (
	ModeNormal EditMode = iota
	ModeInsert
)

// ChatMode selects how a turn is sent to the router.
const (
	ChatModeChat     = "CHAT"
	ChatModeSteering = "STEERING"
)

// OverlayKind is the active modal (immediate-mode: drawn on top when set).
type OverlayKind int

const (
	OverlayNone OverlayKind = iota
	OverlayPalette
	OverlayEventInspector
	OverlayDiff
	OverlayToolPalette
	OverlayModels
)

// RouterEntry is one line in a session's conversation transcript.
type RouterEntry struct {
	Role    string // user | router | tool | narration | narration_llm
	Content string
	Metrics *TurnMetrics
}

// TurnMetrics carries optional latency + token cost for a ROUTER chat turn.
type TurnMetrics struct {
	LatencyMs   int64
	TotalTokens int
}

// EventEntry is a row in the event stream.
type EventEntry struct {
	Time   string
	Type   string
	Detail string
}

// ToolStatus mirrors the Kotlin ToolDisplayStatus.
type ToolStatus int

const (
	ToolStarted ToolStatus = iota
	ToolCompleted
	ToolFailed
	ToolRejected
)

type ToolRecord struct {
	Name   string
	Tier   int
	Status ToolStatus
}

// ManifestTool is a declared (not-yet-run) tool from a stage manifest.
type ManifestTool struct {
	Name string
	Tier int
}

// Approval is a pending approval gate for a session.
type Approval struct {
	RequestID string
	SessionID string
	Tier      string
	Risk      string
	ToolName  string
	Preview   string
	// Rationale holds the plane-2 verified preconditions ("[PATH_OUTSIDE_WORKSPACE] …")
	// that justify the gate — shown in the approval band instead of an opaque tier.
	Rationale []string
}

// Session is the UI's view of one server session.
type Session struct {
	ID            string
	Status        string
	WorkflowID    string
	Name          string
	LastEventAt   int64
	CurrentStage  string
	WorkspaceRoot string // bound cwd, from session.workspace_bound
	LastOutput    string
	LastResponse  string
	Tools         []ToolRecord
	ToolsByStage  map[string][]ManifestTool
	Events        []EventEntry
	Pending       *Approval
	Active        bool // an inference/tool is in flight (drives the spinner)
}

// Workflow is a launchable workflow advertised by the server.
type Workflow struct {
	ID          string
	Description string
}

// Model is the whole TUI state. View is a pure function of it (immediate-mode),
// which is what keeps overlays from desyncing.
type Model struct {
	width, height int
	client        *ws.Client
	theme         Theme
	quitting      bool

	// connection
	connected    bool
	reconnecting bool

	// sessions
	sessions   []Session
	selectedID string
	filter     string
	workflows  []Workflow
	wfIndex    int // -1 = not in workflow picker
	wfVisible  bool
	wfPendingID   string // workflow chosen, awaiting an intent line before StartSession
	wfPendingName string
	bgUpdates  int

	// input
	editMode     EditMode
	inputMode    InputMode
	inputBuffer  string
	inputCursor  int
	history      map[string][]string
	historyIndex int
	savedBuffer  string

	// flow flags
	sessionEntered       bool
	approvalDismissed    bool
	pendingWorkflowFocus bool

	// router transcript
	routerMessages  map[string][]RouterEntry
	routerConnected bool
	chatMode        string

	// provider
	currentModel string
	providerType string // LOCAL | REMOTE

	// managed-model swap (nil resource fields = unavailable)
	availableModels []string
	modelsIndex     int
	gpuUsedMB       *int64
	gpuTotalMB      *int64
	gpuUtil         *int
	ramMB           *int64

	// diff / overlay
	overlay          OverlayKind
	overlayEventIdx  int
	diffScrollOffset int
	eventStripShown  bool

	// command palette
	paletteFilter string
	paletteIndex  int

	// animation
	frame int // tick counter; drives spinner + caret blink

	// snapshot phase
	snapshotPhase bool
	pendingEvents []protocol.ServerMessage

	// approval steering input buffer
	steerBuffer string
	steering    bool
}

// NewModel builds the initial idle state.
func NewModel(client *ws.Client) Model {
	return Model{
		client:          client,
		theme:           NewTheme(SoftBlue),
		wfIndex:         -1,
		inputMode:       ModeRouter,
		history:         map[string][]string{},
		historyIndex:    -1,
		routerMessages:  map[string][]RouterEntry{},
		chatMode:        ChatModeChat,
		providerType:    "LOCAL",
		snapshotPhase:   true,
		eventStripShown: true,
	}
}

// displayState derives the active screen. A session must be *entered*
// (sessionEntered) before its in-session or approval surfaces show — otherwise
// merely moving the list cursor onto a session with a pending gate would yank
// you into the approval, and `l` back-to-list couldn't escape it.
func (m Model) displayState() DisplayState {
	if m.selectedID == "" || !m.sessionEntered {
		return StateIdle
	}
	if m.approvalDismissed {
		return StateInSession
	}
	if s := m.session(m.selectedID); s != nil && s.Pending != nil {
		return StateApproval
	}
	return StateInSession
}

func (m Model) session(id string) *Session {
	for i := range m.sessions {
		if m.sessions[i].ID == id {
			return &m.sessions[i]
		}
	}
	return nil
}

// ensureSession returns the session with id, creating an ACTIVE entry if absent.
// Session existence is derived from the event stream (any event for an unknown
// session vivifies it) rather than a dedicated control frame.
func (m *Model) ensureSession(id string) *Session {
	if s := m.session(id); s != nil {
		return s
	}
	m.sessions = append(m.sessions, Session{
		ID: id, Status: "ACTIVE", LastEventAt: nowMillis(),
	})
	return &m.sessions[len(m.sessions)-1]
}

// filteredSessions applies the workflow-id filter.
func (m Model) filteredSessions() []Session {
	if m.filter == "" {
		return m.sessions
	}
	out := make([]Session, 0, len(m.sessions))
	for _, s := range m.sessions {
		if containsFold(s.WorkflowID, m.filter) {
			out = append(out, s)
		}
	}
	return out
}
