package promptcompiler

import (
	"strings"
	"time"

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

// BlockType classifies the semantic role of a prompt block.
type BlockType string

const (
	BlockInstruction BlockType = "instruction"
	BlockTurnContext BlockType = "turn_context"
	BlockNotice      BlockType = "notice"
	BlockReference   BlockType = "reference"
)

// Durability describes how long a prompt block should remain relevant.
type Durability string

const (
	DurabilityPersistent        Durability = "persistent"
	DurabilityTurnOnly          Durability = "turn_only"
	DurabilityUntilAcknowledged Durability = "until_acknowledged"
	DurabilityCompactable       Durability = "compactable"
)

// BlockLayer identifies the producer layer for a prompt block.
type BlockLayer string

const (
	LayerPlatform        BlockLayer = "platform"
	LayerChannel         BlockLayer = "channel"
	LayerWorkspaceDoc    BlockLayer = "workspace_doc"
	LayerAgentConfig     BlockLayer = "agent_config"
	LayerEvent           BlockLayer = "event"
	LayerRetrieval       BlockLayer = "retrieval"
	LayerSystemNotice    BlockLayer = "system_notice"
	LayerRuntimeGuidance BlockLayer = "runtime_guidance"
)

// OutputSlot selects the runtime instruction slot that receives a block.
type OutputSlot string

const (
	SlotBase           OutputSlot = "base"
	SlotDeveloper      OutputSlot = "developer"
	SlotTurn           OutputSlot = "turn"
	SlotReference      OutputSlot = "reference"
	SlotRuntimeCarrier OutputSlot = "runtime"
)

// SourceKind classifies the origin of a prompt source reference.
type SourceKind string

const (
	SourceKindMarkdownTemplate SourceKind = "markdown_template"
	SourceKindWorkspaceDoc     SourceKind = "workspace_doc"
	SourceKindGenerated        SourceKind = "generated"
	SourceKindEventPayload     SourceKind = "event_payload"
	SourceKindRetrieval        SourceKind = "retrieval"
	SourceKindNotice           SourceKind = "notice"
	SourceKindAgentConfig      SourceKind = "agent_config"
)

// PromptSourceRef records where a prompt block came from.
type PromptSourceRef struct {
	ID          string     `json:"id"`
	SourceKind  SourceKind `json:"sourceKind"`
	Path        string     `json:"path"`
	Version     string     `json:"version"`
	Owner       string     `json:"owner"`
	Description string     `json:"description"`
}

// PromptBlock is one ordered, sourced unit of prompt content.
type PromptBlock struct {
	ID            string          `json:"id"`
	Title         string          `json:"title"`
	Type          BlockType       `json:"type"`
	Layer         BlockLayer      `json:"layer"`
	Slot          OutputSlot      `json:"slot"`
	Durability    Durability      `json:"durability"`
	Priority      int             `json:"priority"`
	Source        PromptSourceRef `json:"source"`
	text          string          `json:"-"`
	TokenEstimate int             `json:"tokenEstimate"`
	Tags          []string        `json:"tags,omitempty"`
	DebugLabel    string          `json:"debugLabel"`
	registryKey   string
}

// CompileEventType identifies the lifecycle event being compiled.
type CompileEventType string

const (
	EventThreadStart            CompileEventType = "thread_start"
	EventThreadResume           CompileEventType = "thread_resume"
	EventFreshStart             CompileEventType = "fresh_start"
	EventWake                   CompileEventType = "wake"
	EventResume                 CompileEventType = "resume"
	EventDeferredContinuation   CompileEventType = "deferred_continuation"
	EventRoutedMessageDelivery  CompileEventType = "routed_message_delivery"
	EventReactionDelivery       CompileEventType = "reaction_delivery"
	EventFinalVisibleReplyCheck CompileEventType = "final_visible_reply_check"
)

// PromptBundle is the prompt-block payload carried by thread and turn events.
type PromptBundle struct {
	SchemaVersion string           `json:"schemaVersion"`
	EventID       string           `json:"eventId"`
	EventType     CompileEventType `json:"eventType"`
	TurnID        string           `json:"turnId,omitempty"`
	AgentID       string           `json:"agentId"`
	Blocks        []PromptBlock    `json:"blocks"`
	ReplyContext  *ReplyContext    `json:"replyContext,omitempty"`
	Fingerprint   string           `json:"fingerprint"`
}

// ReplyContext carries advisory message-to-target context into final rendering.
// It is intentionally excluded from prompt fingerprints.
type ReplyContext struct {
	Target string `json:"target"`
}

// DeliverySourceKind distinguishes routed messages from reaction signals.
type DeliverySourceKind string

const (
	DeliverySourceRoutedMessage DeliverySourceKind = "routed_message"
	DeliverySourceReaction      DeliverySourceKind = "reaction_signal"
)

// ConversationBindingRef identifies the AgentTeam conversation scope for delivery.
type ConversationBindingRef struct {
	Target    string `json:"target"`
	ThreadRef string `json:"threadRef,omitempty"`
	ScopeKind string `json:"surfaceKind"`
}

// DeliveryTransportBinding records runtime delivery and wake correlation fields.
type DeliveryTransportBinding struct {
	DeliveryMode     string `json:"deliveryMode"`
	RuntimeSessionID string `json:"runtimeSessionId,omitempty"`
	ThreadID         string `json:"threadId,omitempty"`
	ActiveTurnID     string `json:"activeTurnId,omitempty"`
	WakeToken        string `json:"wakeToken,omitempty"`
	Attempt          int    `json:"attempt,omitempty"`
	DeferredBatchID  string `json:"deferredBatchId,omitempty"`
}

// DeliveryCorrelation links delivery input back to message and wake identifiers.
type DeliveryCorrelation struct {
	AgentID         string `json:"agentId"`
	MessageID       string `json:"messageId,omitempty"`
	DeliveryID      string `json:"deliveryId,omitempty"`
	SourceMessageID string `json:"sourceMessageId,omitempty"`
	TargetKey       string `json:"targetKey,omitempty"`
	TargetSeq       int64  `json:"targetSeq,omitempty"`
	WakeToken       string `json:"wakeToken,omitempty"`
}

// DeliveryReplayContext carries deferred-delivery and replay provenance.
type DeliveryReplayContext struct {
	ReplayState       string    `json:"replayState,omitempty"`
	DeferredReason    string    `json:"deferredReason,omitempty"`
	AcceptedAt        time.Time `json:"acceptedAt,omitempty"`
	OriginalOrderKey  string    `json:"originalOrderKey,omitempty"`
	PriorAttemptCount int       `json:"priorAttemptCount,omitempty"`
}

// DeliveryEvidenceClass labels the reason a delivery envelope was emitted.
type DeliveryEvidenceClass string

const (
	EvidenceDeferredWhileBusy  DeliveryEvidenceClass = "deferred_while_busy"
	EvidenceReplayStarted      DeliveryEvidenceClass = "replay_started"
	EvidenceWakeFailed         DeliveryEvidenceClass = "wake_failed"
	EvidenceRuntimeStartFailed DeliveryEvidenceClass = "runtime_start_failed"
	EvidenceRuntimeExited      DeliveryEvidenceClass = "runtime_exited_unexpectedly"
	EvidenceVisibleReplySent   DeliveryEvidenceClass = "visible_reply_sent"
	EvidenceCompletedNoReply   DeliveryEvidenceClass = "completed_without_visible_reply"
)

// DeliveryEnvelope is the structured runtime input for one delivered message.
type DeliveryEnvelope struct {
	SourceKind          DeliverySourceKind            `json:"sourceKind"`
	Binding             ConversationBindingRef        `json:"binding"`
	Transport           DeliveryTransportBinding      `json:"transport"`
	Correlation         DeliveryCorrelation           `json:"correlation"`
	Replay              DeliveryReplayContext         `json:"replay"`
	EvidenceClass       DeliveryEvidenceClass         `json:"evidenceClass,omitempty"`
	AuthorMentionName   string                        `json:"authorMentionName,omitempty"`
	AuthorDisplayName   string                        `json:"authorDisplayName,omitempty"`
	MessageType         string                        `json:"messageType,omitempty"`
	Timestamp           string                        `json:"-"`
	PayloadText         string                        `json:"payloadText,omitempty"`
	Attachments         []types.MessageAttachment     `json:"attachments,omitempty"`
	ResolvedAttachments []types.RuntimeAttachment     `json:"resolvedAttachments,omitempty"`
	AttachmentFailure   *AttachmentPreparationFailure `json:"attachmentFailure,omitempty"`
	Metadata            map[string]any                `json:"metadata,omitempty"`
	WorkflowContext     DeliveryWorkflowContext       `json:"workflowContext,omitempty,omitzero"`
	ActiveTaskTarget    *types.ActiveTaskTarget       `json:"activeTaskTarget,omitempty"`
	TeamContexts        []types.TeamContext           `json:"teamContexts,omitempty"`
	Fingerprint         string                        `json:"fingerprint"`
	// PromptFacts is the normalized delivery authoring projection. It is
	// compiler-local and never accepts rendered semantic text from transport.
	PromptFacts        MessagePromptData          `json:"-"`
	IdentityNoticeData IdentityNoticeTemplateData `json:"-"`
	// ReplyUnavailable is daemon-internal render state derived from trusted
	// delivery facts. It is never serialized into runtime input.
	ReplyUnavailable bool `json:"-"`
}

type DeliveryWorkflowContext struct {
	Available bool   `json:"available,omitempty"`
	RunID     string `json:"runId,omitempty"`
	StepID    string `json:"stepId,omitempty"`
}

func (context DeliveryWorkflowContext) IsZero() bool {
	return !context.Available
}

// CompileEventKind selects the shape of runtime input to build.
type CompileEventKind string

const (
	EventThreadContract   CompileEventKind = "thread_contract"
	EventTurnContext      CompileEventKind = "turn_context"
	EventDeliveryEnvelope CompileEventKind = "delivery_envelope"
)

// RuntimeInputEnvelope is the validated top-level runtime prompt payload.
type RuntimeInputEnvelope struct {
	SchemaVersion string            `json:"schemaVersion"`
	EventID       string            `json:"eventId"`
	EventType     CompileEventType  `json:"eventType"`
	TurnID        string            `json:"turnId,omitempty"`
	AgentID       string            `json:"agentId"`
	EventKind     CompileEventKind  `json:"eventKind"`
	Bundle        *PromptBundle     `json:"bundle,omitempty"`
	Delivery      *DeliveryEnvelope `json:"delivery,omitempty"`
	Fingerprint   string            `json:"fingerprint"`
	// TaskPayloadPresent distinguishes an envelope without a task payload from
	// one whose payload identity is unavailable.
	TaskPayloadPresent bool `json:"-"`
	// TaskPayloadDigest is a deterministic digest of the model-visible
	// task/scope payload. It is retained only for local Token attribution and
	// never serialized in runtime envelopes.
	TaskPayloadDigest string `json:"-"`
}

// PromptSlots is the typed runtime carrier produced by the compiler. The
// legacy Slots field on RenderedPromptPayload is retained only for golden-file
// compatibility; production boundaries should use these named fields.
type PromptSlots struct {
	BaseInstructions           string
	DeveloperInstructions      string
	SystemPrompt               string
	Prompt                     string
	FreshPrompt                string
	ResumePrompt               string
	RuntimeCarrier             string
	ContractCarrierFile        string
	TurnInput                  string
	DeliveryText               string
	KimiRestrictedSystemPrompt string
	KimiInitialPrompt          string
	DeliveryEnvelope           *DeliveryEnvelope
}

// RenderedPromptPayload holds runtime-specific slots produced from an envelope.
type RenderedPromptPayload struct {
	Runtime string `json:"runtime"`
	// Slots is a compatibility projection for old golden readers. New code must
	// use PromptSlots or SlotText; semantic prompt data is never parsed from it.
	Slots             map[string]any `json:"surfaces"`
	PromptSlots       PromptSlots    `json:"-"`
	RenderOrder       []string       `json:"renderOrder,omitempty"`
	AttributionBlocks []PromptBlock  `json:"-"`
}

// CompiledPrompt is the sealed compiler product handed to runtime carriers.
// Its representation contains unexported fields, so callers outside this
// package cannot construct or replace a compiled prompt with a map or prose.
// Only Compiler methods create a valid value.
type CompiledPrompt struct {
	envelope   RuntimeInputEnvelope
	rendered   RenderedPromptPayload
	launch     *launchPromptCarrier
	components []CompiledPromptComponent
	sealed     struct{}
}

// launchPromptCarrier is the complete runtime launch product. It is private so
// a caller cannot manufacture a carrier by assigning individual semantic
// surfaces; only CompileLaunchPrompt can produce one.
type launchPromptCarrier struct {
	Prompt                     string
	FreshPrompt                string
	ResumePrompt               string
	BaseInstructions           string
	DeveloperInstructions      string
	SystemPrompt               string
	RuntimeCarrier             string
	ContractCarrierFile        string
	KimiRestrictedSystemPrompt string
	KimiInitialPrompt          string
}

// CompiledPromptComponent is one compiler-owned component used to build an
// atomic launch carrier. It is exposed for attribution only.
type CompiledPromptComponent struct {
	Stage     string
	Freshness string
	Envelope  RuntimeInputEnvelope
	Rendered  RenderedPromptPayload
}

// Valid reports whether this value was produced by a compiler and still has a
// valid envelope/carrier pair.
func (p CompiledPrompt) Valid() bool {
	return strings.TrimSpace(p.envelope.SchemaVersion) != "" &&
		strings.TrimSpace(p.envelope.Fingerprint) != "" &&
		p.rendered.Runtime != ""
}

// HasLaunchCarrier reports whether this compiler product contains the complete
// atomic launch surface set required by runtime adapters.
func (p CompiledPrompt) HasLaunchCarrier() bool {
	return p.Valid() && p.launch != nil
}

// Envelope returns the validated domain envelope. Runtime adapters should not
// mutate it; it is exposed for attribution and transport serialization only.
func (p CompiledPrompt) Envelope() RuntimeInputEnvelope { return p.envelope }

// Rendered returns a detached typed runtime projection. Mutating the returned
// compatibility map cannot mutate the sealed compiler carrier.
func (p CompiledPrompt) Rendered() RenderedPromptPayload {
	rendered := p.rendered
	return cloneRenderedPromptPayload(rendered)
}

// Runtime returns the runtime name selected by the compiler.
func (p CompiledPrompt) Runtime() string { return p.rendered.Runtime }

// SlotText returns a named text surface without map parsing.
func (p CompiledPrompt) SlotText(slot string) string {
	if p.launch != nil {
		switch strings.TrimSpace(slot) {
		case "prompt":
			return p.launch.Prompt
		case "freshPrompt":
			return p.launch.FreshPrompt
		case "resumePrompt":
			return p.launch.ResumePrompt
		case "baseInstructions":
			return p.launch.BaseInstructions
		case "developerInstructions":
			return p.launch.DeveloperInstructions
		case "systemPrompt":
			return p.launch.SystemPrompt
		case "runtimeCarrier":
			return p.launch.RuntimeCarrier
		case "contractCarrierFile":
			return p.launch.ContractCarrierFile
		case "kimiRestrictedSystemPrompt":
			return p.launch.KimiRestrictedSystemPrompt
		case "kimiInitialPrompt":
			return p.launch.KimiInitialPrompt
		}
	}
	switch strings.TrimSpace(slot) {
	case "baseInstructions":
		return p.rendered.PromptSlots.BaseInstructions
	case "developerInstructions":
		return p.rendered.PromptSlots.DeveloperInstructions
	case "systemPrompt":
		return p.rendered.PromptSlots.SystemPrompt
	case "prompt":
		return p.rendered.PromptSlots.Prompt
	case "freshPrompt":
		return p.rendered.PromptSlots.FreshPrompt
	case "resumePrompt":
		return p.rendered.PromptSlots.ResumePrompt
	case "runtimeCarrier":
		return p.rendered.PromptSlots.RuntimeCarrier
	case "contractCarrierFile":
		return p.rendered.PromptSlots.ContractCarrierFile
	case "kimiRestrictedSystemPrompt":
		return p.rendered.PromptSlots.KimiRestrictedSystemPrompt
	case "kimiInitialPrompt":
		return p.rendered.PromptSlots.KimiInitialPrompt
	case "turnInput":
		return p.rendered.PromptSlots.TurnInput
	case "deliveryText":
		return p.rendered.PromptSlots.DeliveryText
	default:
		return ""
	}
}

// InitialPrompt returns the compiler-owned first user turn for a fresh or
// resumed session. The mode-specific surface wins when nonempty; the
// canonical prompt surface is the only fallback. Runtime adapters must use
// this sealed selection instead of choosing or reconstructing surfaces.
func (p CompiledPrompt) InitialPrompt(resumed bool) string {
	if !p.Valid() || p.launch == nil {
		return ""
	}
	if resumed && strings.TrimSpace(p.launch.ResumePrompt) != "" {
		return p.launch.ResumePrompt
	}
	if !resumed && strings.TrimSpace(p.launch.FreshPrompt) != "" {
		return p.launch.FreshPrompt
	}
	return p.launch.Prompt
}

// LaunchSurface returns a named launch surface selected atomically by the
// compiler. It never falls back to caller-provided strings.
func (p CompiledPrompt) LaunchSurface(slot string) string {
	if !p.Valid() || p.launch == nil {
		return ""
	}
	switch strings.TrimSpace(slot) {
	case "prompt":
		return p.launch.Prompt
	case "freshPrompt":
		return p.launch.FreshPrompt
	case "resumePrompt":
		return p.launch.ResumePrompt
	case "baseInstructions":
		return p.launch.BaseInstructions
	case "developerInstructions":
		return p.launch.DeveloperInstructions
	case "systemPrompt":
		return p.launch.SystemPrompt
	case "runtimeCarrier":
		return p.launch.RuntimeCarrier
	case "contractCarrierFile":
		return p.launch.ContractCarrierFile
	case "kimiRestrictedSystemPrompt":
		return p.launch.KimiRestrictedSystemPrompt
	case "kimiInitialPrompt":
		return p.launch.KimiInitialPrompt
	default:
		return ""
	}
}

// LaunchComponents returns immutable-by-convention copies of the compiler
// components used to make this launch carrier. It exists for attribution and
// metrics; runtime adapters must use LaunchSurface instead.
func (p CompiledPrompt) LaunchComponents() []CompiledPromptComponent {
	out := make([]CompiledPromptComponent, len(p.components))
	for index, component := range p.components {
		out[index] = component
		out[index].Rendered = cloneRenderedPromptPayload(component.Rendered)
	}
	return out
}
