package agent

import (
	"strings"
	"sync"

	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

type workflowCompactionState uint8

const (
	workflowCompactionInactive workflowCompactionState = iota
	workflowCompactionAwaiting
	workflowCompactionCompleted
	workflowCompactionUnsuccessful
)

// WorkflowCompactionGate tracks only proactive idle-compaction eligibility for
// the exact Workflow Step visible to one Process generation. It does not grant
// callback authority, block message delivery, or change Process idle state.
type WorkflowCompactionGate struct {
	mu        sync.Mutex
	messageID string
	runID     string
	stepID    string
	state     workflowCompactionState
}

// NewWorkflowCompactionGate creates an inactive Process-generation gate.
func NewWorkflowCompactionGate() *WorkflowCompactionGate {
	return &WorkflowCompactionGate{}
}

// ObserveRuntimeMessage records one message after it becomes model-visible.
// A Workflow callback starts or replaces Step ownership. Ordinary work retains
// an awaiting or successfully completed Step, but starts a fresh boundary after
// an unsuccessfully terminated Step.
func (g *WorkflowCompactionGate) ObserveRuntimeMessage(msg types.InboxMessage) {
	messageID := strings.TrimSpace(msg.MessageID)
	if g == nil || messageID == "" {
		return
	}
	workflow := msg.ResolvedWorkflowContext()

	g.mu.Lock()
	defer g.mu.Unlock()
	if workflow.Valid() {
		g.observeWorkflowLocked(messageID, workflow)
		return
	}
	if g.state == workflowCompactionUnsuccessful {
		g.clearLocked()
	}
}

// observeWorkflowRuntimeMessage records only valid Workflow callback context.
// Recovered or late correlated acknowledgement must restore an awaiting Step,
// but an old ordinary message is not a new independent turn boundary.
func (g *WorkflowCompactionGate) observeWorkflowRuntimeMessage(msg types.InboxMessage) {
	messageID := strings.TrimSpace(msg.MessageID)
	if g == nil || messageID == "" {
		return
	}
	workflow := msg.ResolvedWorkflowContext()
	if !workflow.Valid() {
		return
	}

	g.mu.Lock()
	defer g.mu.Unlock()
	g.observeWorkflowLocked(messageID, workflow)
}

func (g *WorkflowCompactionGate) observeWorkflowLocked(messageID string, workflow types.WorkflowContext) {
	// Correlated delivery records Workflow visibility before its ownership
	// fsync, then reaches the ordinary acknowledgement hook after commit. Treat
	// both observations as one acknowledgement so a terminal Exec result between them is
	// not reset to awaiting.
	if g.messageID == messageID && g.runID == workflow.RunID && g.stepID == workflow.EffectID {
		return
	}
	g.messageID = messageID
	g.runID = workflow.RunID
	g.stepID = workflow.EffectID
	g.state = workflowCompactionAwaiting
}

// WorkflowStepCompleted records Server-confirmed complete success, including a
// same-result idempotent replay, for the exact active Step.
func (g *WorkflowCompactionGate) WorkflowStepCompleted(runID, stepID string) {
	if g == nil {
		return
	}
	runID = strings.TrimSpace(runID)
	stepID = strings.TrimSpace(stepID)
	if runID == "" || stepID == "" {
		return
	}

	g.mu.Lock()
	defer g.mu.Unlock()
	if g.runID == runID && g.stepID == stepID && g.state != workflowCompactionInactive {
		g.state = workflowCompactionCompleted
	}
}

// WorkflowStepUnsuccessful records an unsuccessful or externally resolved
// terminal outcome for the exact active Step. A previously confirmed complete
// remains authoritative if concurrent terminal calls finish out of order.
func (g *WorkflowCompactionGate) WorkflowStepUnsuccessful(runID, stepID string) {
	if g == nil {
		return
	}
	runID = strings.TrimSpace(runID)
	stepID = strings.TrimSpace(stepID)
	if runID == "" || stepID == "" {
		return
	}

	g.mu.Lock()
	defer g.mu.Unlock()
	if g.runID == runID && g.stepID == stepID &&
		g.state != workflowCompactionInactive &&
		g.state != workflowCompactionCompleted {
		g.state = workflowCompactionUnsuccessful
	}
}

// WorkflowRunCanceled records a confirmed cancellation for the active Step
// only when it belongs to the exact Run.
func (g *WorkflowCompactionGate) WorkflowRunCanceled(runID string) {
	if g == nil {
		return
	}
	runID = strings.TrimSpace(runID)
	if runID == "" {
		return
	}

	g.mu.Lock()
	defer g.mu.Unlock()
	if g.runID == runID &&
		g.state != workflowCompactionInactive &&
		g.state != workflowCompactionCompleted {
		g.state = workflowCompactionUnsuccessful
	}
}

// allowProactiveCompactionCheck is consumed only at a quiescent proactive
// compaction checkpoint, after final-check and delivery have had priority.
func (g *WorkflowCompactionGate) allowProactiveCompactionCheck() bool {
	if g == nil {
		return true
	}

	g.mu.Lock()
	defer g.mu.Unlock()
	switch g.state {
	case workflowCompactionAwaiting:
		return false
	case workflowCompactionCompleted:
		g.clearLocked()
		return true
	case workflowCompactionUnsuccessful:
		g.clearLocked()
		return false
	default:
		return true
	}
}

func (g *WorkflowCompactionGate) clearLocked() {
	g.messageID = ""
	g.runID = ""
	g.stepID = ""
	g.state = workflowCompactionInactive
}
