package promptcompiler

import (
	"fmt"
	"sort"
	"strings"

	"github.com/tidbcloud/AgentTeam/desktop/prompts"
)

// PromptInputType identifies the domain value used to render a registered
// prompt block. Keeping this as a closed vocabulary prevents a block from
// silently switching from typed domain data to an unstructured map.
type PromptInputType string

const (
	PromptInputIdentity                 PromptInputType = "identity"
	PromptInputAgentConfig              PromptInputType = "agent_config"
	PromptInputSkillPlan                PromptInputType = "skill_plan"
	PromptInputMessage                  PromptInputType = "message"
	PromptInputTask                     PromptInputType = "task"
	PromptInputTeam                     PromptInputType = "team"
	PromptInputWorkflow                 PromptInputType = "workflow"
	PromptInputActiveTask               PromptInputType = "active_task"
	PromptInputAttachment               PromptInputType = "attachment"
	PromptInputRecovery                 PromptInputType = "recovery"
	PromptInputVisibleReplyCheck        PromptInputType = "visible_reply_check"
	PromptInputExecGuidance             PromptInputType = "exec_guidance"
	PromptInputExtensionCatalogGuidance PromptInputType = "extension_catalog_guidance"
	PromptInputWorkspaceDocument        PromptInputType = "workspace_document"
	PromptInputRenderer                 PromptInputType = "renderer"
	PromptInputRuntimePolicy            PromptInputType = "runtime_policy"
)

// PromptBlockSpec is the executable registry entry for one AgentTeam-authored
// prompt block. Every semantic block has one owner, one template, one output
// slot, one ordering priority, one durability policy, and one token budget.
// PromptBlockInput is sealed to promptcompiler: external packages cannot
// implement the unexported method and therefore cannot submit arbitrary prose
// to a registered constructor.
type PromptBlockInput interface {
	promptInputType() PromptInputType
	promptInputName() string
	renderPromptBlock(PromptBlockSpec) (string, error)
}

// PromptBlockRenderer is the registry-owned template renderer for one typed
// domain input. It receives the spec so the declared template path cannot be
// swapped by the caller.
type PromptBlockRenderer func(PromptBlockSpec, PromptBlockInput) (string, error)

// PromptBlockConstructor is the executable typed registry constructor.
type PromptBlockConstructor func(PromptBlockSpec, PromptBlockInput) (PromptBlock, error)

type PromptBlockSpec struct {
	ID           string
	Title        string
	Type         BlockType
	Layer        BlockLayer
	InputType    PromptInputType
	ExpectedType string
	TemplatePath string
	Slot         OutputSlot
	Priority     int
	Durability   Durability
	BudgetTokens int
	Owner        string
	Description  string
	DebugLabel   string
	SourceKind   SourceKind
	Renderer     PromptBlockRenderer
	Constructor  PromptBlockConstructor
}

// PromptBlockRegistry is immutable after construction. The compiler owns the
// registry and is the only production path that may materialize registered
// blocks.
type PromptBlockRegistry struct {
	entries    map[string]PromptBlockSpec
	duplicates []string
}

// DefaultPromptBlockRegistry returns the canonical registry. The returned
// value has no mutating methods so callers can only inspect it or validate it.
func DefaultPromptBlockRegistry() PromptBlockRegistry {
	entries := make(map[string]PromptBlockSpec, len(defaultPromptBlockSpecs))
	duplicates := make([]string, 0)
	for _, spec := range defaultPromptBlockSpecs {
		if _, exists := entries[spec.ID]; exists {
			duplicates = append(duplicates, spec.ID)
		}
		entries[spec.ID] = spec
	}
	return PromptBlockRegistry{entries: entries, duplicates: duplicates}
}

// RegisteredPromptBlocks returns a sorted copy of the canonical registry.
func RegisteredPromptBlocks() []PromptBlockSpec {
	entries := append([]PromptBlockSpec(nil), defaultPromptBlockSpecs...)
	sort.Slice(entries, func(i, j int) bool { return entries[i].ID < entries[j].ID })
	return entries
}

// Lookup returns the immutable specification for id.
func (r PromptBlockRegistry) Lookup(id string) (PromptBlockSpec, bool) {
	spec, ok := r.entries[strings.TrimSpace(id)]
	return spec, ok
}

// Validate verifies uniqueness and completeness of the canonical registry and
// that every declared template is embedded in desktop/prompts.
func (r PromptBlockRegistry) Validate() error {
	if len(r.entries) == 0 {
		return fmt.Errorf("prompt block registry is empty")
	}
	if len(r.duplicates) > 0 {
		return fmt.Errorf("prompt block registry contains duplicate ids: %s", strings.Join(r.duplicates, ", "))
	}
	for id, spec := range r.entries {
		if strings.TrimSpace(id) == "" || strings.TrimSpace(spec.ID) == "" || id != spec.ID {
			return fmt.Errorf("prompt block registry contains an invalid id %q", id)
		}
		if spec.InputType == "" {
			return fmt.Errorf("prompt block %q is missing typed input", id)
		}
		if strings.TrimSpace(spec.Title) == "" || spec.Type == "" || spec.Layer == "" {
			return fmt.Errorf("prompt block %q is missing registry-owned title/type/layer metadata", id)
		}
		if strings.TrimSpace(spec.ExpectedType) == "" {
			return fmt.Errorf("prompt block %q is missing expected concrete input type", id)
		}
		if strings.TrimSpace(spec.TemplatePath) == "" {
			return fmt.Errorf("prompt block %q is missing template", id)
		}
		if strings.TrimSpace(spec.Owner) == "" || strings.TrimSpace(spec.Description) == "" || strings.TrimSpace(spec.DebugLabel) == "" || spec.SourceKind == "" {
			return fmt.Errorf("prompt block %q is missing source/debug metadata", id)
		}
		if spec.Slot == "" {
			return fmt.Errorf("prompt block %q is missing slot", id)
		}
		if spec.Priority <= 0 {
			return fmt.Errorf("prompt block %q is missing priority", id)
		}
		if spec.Durability == "" {
			return fmt.Errorf("prompt block %q is missing durability", id)
		}
		if spec.BudgetTokens <= 0 {
			return fmt.Errorf("prompt block %q is missing budget", id)
		}
		if spec.Renderer == nil {
			return fmt.Errorf("prompt block %q is missing typed template renderer", id)
		}
		if spec.Constructor == nil {
			return fmt.Errorf("prompt block %q is missing typed constructor", id)
		}
		fixture, err := promptBlockFixture(spec)
		if err != nil {
			return fmt.Errorf("prompt block %q has no concrete typed fixture: %w", id, err)
		}
		if _, err := spec.Constructor(spec, fixture); err != nil {
			return fmt.Errorf("prompt block %q typed constructor is not executable: %w", id, err)
		}
		if err := prompts.Validate(spec.TemplatePath); err != nil {
			return fmt.Errorf("prompt block %q template %q is invalid: %w", id, spec.TemplatePath, err)
		}
	}
	return nil
}

// Construct renders one registered block from a compiler-sealed typed input.
func (r PromptBlockRegistry) Construct(id string, input PromptBlockInput) (PromptBlock, error) {
	spec, ok := r.Lookup(id)
	if !ok {
		return PromptBlock{}, fmt.Errorf("prompt block %q is not registered", strings.TrimSpace(id))
	}
	if spec.Constructor == nil {
		return PromptBlock{}, fmt.Errorf("prompt block %q has no constructor", id)
	}
	return spec.Constructor(spec, input)
}

func registeredBlockIdentityError(block PromptBlock) error {
	spec, ok := DefaultPromptBlockRegistry().Lookup(block.ID)
	if !ok {
		return fmt.Errorf("prompt block %q is not registered", block.ID)
	}
	if block.registryKey != spec.ID {
		return fmt.Errorf("prompt block %q was not materialized by its registered constructor", block.ID)
	}
	if block.Title != spec.Title || block.Type != spec.Type || block.Layer != spec.Layer || block.Slot != spec.Slot || block.Priority != spec.Priority || block.Durability != spec.Durability || block.Source.ID != spec.ID || block.Source.SourceKind != spec.SourceKind || block.Source.Path != "desktop/prompts/"+spec.TemplatePath || block.Source.Version != specTemplateVersion(spec) || block.Source.Owner != spec.Owner || block.Source.Description != spec.Description {
		return fmt.Errorf("prompt block %q has altered registry metadata or provenance", block.ID)
	}
	return nil
}

// ConstructAttribution creates a compiler-owned attribution projection for a
// rendered runtime payload. It carries no semantic text, but still obtains all
// identity, metadata, and provenance from the registered ID.
func (r PromptBlockRegistry) ConstructAttribution(id string, input AttributionTemplateData) (PromptBlock, error) {
	spec, ok := r.Lookup(id)
	if !ok {
		return PromptBlock{}, fmt.Errorf("prompt block %q is not registered", strings.TrimSpace(id))
	}
	return PromptBlock{ID: spec.ID, Title: spec.Title, Type: spec.Type, Layer: spec.Layer, Slot: spec.Slot, Durability: spec.Durability, Priority: spec.Priority, TokenEstimate: input.TokenEstimate, DebugLabel: spec.DebugLabel, registryKey: spec.ID, Source: PromptSourceRef{ID: spec.ID, SourceKind: spec.SourceKind, Path: "desktop/prompts/" + spec.TemplatePath, Version: specTemplateVersion(spec), Owner: spec.Owner, Description: spec.Description}}, nil
}

// ValidatePromptBlockRegistry is the build/test-time guard used by CI.
func ValidatePromptBlockRegistry() error {
	registry := DefaultPromptBlockRegistry()
	return registry.Validate()
}

// blockBudget is intentionally generous for dynamic event payloads; the
// compiler's policy remains the final per-launch ceiling.
const blockBudget = 16384

func blockSpec(id string, input PromptInputType, templatePath string, slot OutputSlot, priority int, durability Durability) PromptBlockSpec {
	metadata := promptBlockMetadata(id)
	spec := PromptBlockSpec{
		ID: id, Title: metadata.Title, Type: metadata.Type, Layer: metadata.Layer,
		InputType: input, ExpectedType: expectedPromptInputType(id), TemplatePath: templatePath,
		Slot: slot, Priority: priority, Durability: durability, BudgetTokens: blockBudget,
		Owner: metadata.Owner, Description: metadata.Description, DebugLabel: metadata.DebugLabel, SourceKind: metadata.SourceKind,
	}
	spec.Renderer = registeredPromptTemplateRenderer
	spec.Constructor = registeredPromptBlockConstructor
	return spec
}

type promptBlockMetadataValues struct {
	Title       string
	Type        BlockType
	Layer       BlockLayer
	Owner       string
	Description string
	DebugLabel  string
	SourceKind  SourceKind
}

func promptBlockMetadata(id string) promptBlockMetadataValues {
	meta := promptBlockMetadataValues{Title: id, Type: BlockTurnContext, Layer: LayerEvent, Owner: "promptcompiler", Description: "Registered compiler-owned prompt block.", DebugLabel: id, SourceKind: SourceKindMarkdownTemplate}
	if strings.HasPrefix(id, "platform.") || strings.HasPrefix(id, "agent_config.") || strings.HasPrefix(id, "workspace.") {
		meta.Type, meta.Layer = BlockInstruction, LayerPlatform
	}
	if strings.HasPrefix(id, "platform.agentteam.") {
		meta.Owner = "platform:agentteam"
	} else if strings.HasPrefix(id, "platform.") {
		meta.Owner = "platform"
	} else if strings.HasPrefix(id, "agent_config.") {
		meta.Owner = "agent"
	} else if strings.HasPrefix(id, "workspace.") {
		meta.Owner = "identity"
	} else if strings.HasPrefix(id, "event.") {
		meta.Owner = "event"
	} else if strings.HasPrefix(id, "notice.") {
		meta.Owner = "system"
	} else if strings.HasPrefix(id, "runtime.") || strings.HasPrefix(id, "agentteam.") {
		meta.Owner = "agentteam"
	}
	if strings.HasPrefix(id, "workspace.") {
		meta.Layer, meta.SourceKind = LayerWorkspaceDoc, SourceKindWorkspaceDoc
	}
	if strings.HasPrefix(id, "agent_config.") {
		meta.SourceKind = SourceKindAgentConfig
	}
	if strings.HasPrefix(id, "notice.") {
		meta.Type, meta.Layer = BlockNotice, LayerSystemNotice
		if id != "notice.identity" {
			meta.Type = BlockInstruction
		}
		if id == "notice.identity" {
			meta.SourceKind = SourceKindNotice
		}
	}
	if id == "platform.agentteam.reference" || id == "platform.agentteam.external_skill_reference" {
		meta.Type, meta.Layer = BlockReference, LayerPlatform
	}
	if id == "platform.agentteam.visible_reply_contract" {
		meta.Type, meta.Layer, meta.SourceKind, meta.Owner = BlockTurnContext, LayerRuntimeGuidance, SourceKindGenerated, "execution"
	}
	if id == "platform.agentteam.exec" || id == "platform.agentteam.extension" {
		meta.Layer, meta.SourceKind, meta.Owner = LayerRuntimeGuidance, SourceKindGenerated, "execution"
	}
	if strings.HasPrefix(id, "runtime.") || strings.HasPrefix(id, "agentteam.") {
		meta.Layer, meta.SourceKind = LayerRuntimeGuidance, SourceKindGenerated
	}
	titles := map[string]string{
		"platform.identity": "Agent identity", "agent_config.initial_role": "Initial role", "platform.who_you_are": "Who you are", "platform.cooperation": "Cooperation",
		"platform.agentteam.startup": "Startup sequence", "platform.agentteam.core": "AgentTeam core rules", "platform.agentteam.memory_search": "Memory search", "platform.agentteam.external_skill_core": "Skill-provided core guidance", "platform.agentteam.task_boards": "Task boards", "platform.agentteam.review_context": "Review context",
		"platform.agentteam.exec": "AgentTeam Exec actions", "platform.agentteam.extension": "AgentTeam Extension actions", "platform.agentteam.external_skill_reference": "Skill-provided reference", "platform.workspace_memory": "Workspace and memory", "platform.agent_identity": "Agent identity",
		"workspace.identity": "System identity", "agent_config.agents_md": "AGENTS.md", "notice.identity": "Identity notice", "event.current_message": "Current message", "event.wake_context": "Wake context", "event.resume_context": "Resume context", "event.deferred_continuation": "Deferred continuation", "platform.agentteam.visible_reply_contract": "Participant delivery contract",
		"event.attachment_manifest": "Runtime attachment manifest", "event.attachment_preparation_failure": "Attachment preparation failure", "runtime_attachment_manifest": "Runtime attachment manifest", "runtime_attachment_preparation_failure": "Runtime attachment preparation failure",
		"agentteam.context_compaction_continuation": "Context compaction continuation", "event.context_recovery_restart": "Context recovery restart", "agentteam.output_truncation_continuation": "Output truncation continuation", "event.runtime_retry": "Runtime retry", "agentteam.final_visible_reply_check": "Final visible reply check",
	}
	if title := titles[id]; title != "" {
		meta.Title, meta.DebugLabel = title, strings.ToLower(title)
	}
	return meta
}

func promptBlockFixture(spec PromptBlockSpec) (PromptBlockInput, error) {
	switch spec.ExpectedType {
	case "IdentityTemplateData":
		return newIdentityPromptInput(IdentityTemplateData{AgentName: "fixture", ChannelDisplayName: "AgentTeam"}), nil
	case "InitialRoleTemplateData":
		return newInitialRolePromptInput(InitialRoleTemplateData{Description: "fixture role"}), nil
	case "EmptyTemplateData":
		return newEmptyPromptInput(spec.InputType), nil
	case "PlatformLoopTemplateData":
		return newPlatformLoopPromptInput(PlatformLoopTemplateData{ReviewAddTool: "review add", SendMessageTool: "message_send"}), nil
	case "AgentTeamCoreTemplateData":
		return newAgentTeamCorePromptInput(AgentTeamCoreTemplateData{SendMessageTool: "message_send"}), nil
	case "AgentTeamReferenceTemplateData":
		return newLoopReferencePromptInput(AgentTeamReferenceTemplateData{ReadHistoryTool: "history"}), nil
	case "MemorySearchGuidanceTemplateData":
		return newMemorySearchGuidancePromptInput(MemorySearchGuidanceTemplateData{SearchTool: "mcp__agentteam__memory_search"}), nil
	case "ThreadContextTemplateData":
		return newThreadContextPromptInput(ThreadContextTemplateData{Enabled: true, Target: "#fixture:abc123", ReadHistoryTool: "message_history_read"}), nil
	case "ExternalSkillPayloadTemplateData":
		return newExternalSkillCorePromptInput(ExternalSkillPayloadTemplateData{SkillText: "external fixture skill"}), nil
	case "ExecGuidanceTemplateData":
		return newExecGuidancePromptInput(ExecGuidanceTemplateData{Domains: []string{"agent"}, CommonActions: []ExecGuidanceAction{{Domain: "task", Name: "get", ExampleJSON: `{"number":1}`}}}), nil
	case "ExtensionCatalogGuidanceTemplateData":
		return newExtensionCatalogGuidancePromptInput(ExtensionCatalogGuidanceTemplateData{Actions: []ExtensionCatalogGuidanceAction{{ExtensionName: "github", ActionName: "list-issues"}}}), nil
	case "WorkspaceMemoryTemplateData":
		return newWorkspaceMemoryPromptInput(WorkspaceMemoryTemplateData{MemoryIndexPath: "MEMORY.md"}), nil
	case "WorkspaceIdentityTemplateData":
		return newWorkspaceIdentityPromptInput(WorkspaceIdentityTemplateData{Content: "fixture identity"}), nil
	case "WorkspaceDocumentTemplateData":
		return newWorkspaceDocumentPromptInput(WorkspaceDocumentTemplateData{Content: "fixture document"}), nil
	case "IdentityNoticeTemplateData":
		return newIdentityNoticePromptInput(IdentityNoticeTemplateData{CurrentName: "fixture"}), nil
	case "WorkspaceDocOversizeTemplateData":
		return newOversizePromptInput(WorkspaceDocOversizeTemplateData{DocTitle: "fixture", Path: "fixture.md", Constraints: []WorkspaceDocConstraint{{Actual: 2, Limit: 1, Unit: "bytes"}}}), nil
	case "MessageTemplateData":
		return newMessagePromptInput(MessageTemplateData{Target: "#fixture", MessageID: "msg", Timestamp: "-", Author: "@fixture", Content: "fixture"}), nil
	case "DeliveryMessageTemplateData":
		return newDeliveryMessagePromptInput(DeliveryMessageTemplateData{Body: "fixture", ReplyInstruction: "fixture"}), nil
	case "VisibleReplyContractTemplateData":
		return newVisibleReplyPromptInput(VisibleReplyContractTemplateData{SendMessageTool: "message_send"}), nil
	case "DeliveryReplyTemplateData":
		return newDeliveryReplyPromptInput(DeliveryReplyTemplateData{Target: "#fixture", SendCall: "message_send(...)"}), nil
	case "DeliveryReplayTemplateData":
		return newDeliveryReplayPromptInput(DeliveryReplayTemplateData{State: "replay"}), nil
	case "ReplyTargetTemplateData":
		return newReplyTargetPromptInput(ReplyTargetTemplateData{Target: "#fixture", SendCall: "message_send(...)"}), nil
	case "CurrentMessageTemplateData":
		return newCurrentMessagePromptInput(CurrentMessageTemplateData{Message: "fixture"}), nil
	case "WakeContextTemplateData":
		return newWakeContextPromptInput(WakeContextTemplateData{Message: "fixture", SendMessageTool: "message_send"}), nil
	case "ResumeContextTemplateData":
		return newResumeContextPromptInput(ResumeContextTemplateData{Message: "fixture", SendMessageTool: "message_send"}), nil
	case "UnreadSummaryTemplateData":
		return newUnreadSummaryPromptInput(UnreadSummaryTemplateData{Channels: []UnreadSummaryChannel{{Channel: "#fixture", Count: 1}}}), nil
	case "DeferredContinuationTemplateData":
		return newDeferredContinuationPromptInput(DeferredContinuationTemplateData{Message: "fixture", ReplyToolName: "message_send", ReplyAuthority: true}), nil
	case "ActiveTaskTemplateData":
		return newActiveTaskPromptInput(ActiveTaskTemplateData{TaskNumber: 1, Channel: "#fixture"}), nil
	case "ActiveTaskNormalRoutingTemplateData":
		return newActiveTaskNormalPromptInput(ActiveTaskNormalRoutingTemplateData{Target: "#fixture", SendMessageTool: "message_send"}), nil
	case "WorkflowTemplateData":
		return newWorkflowPromptInput(WorkflowTemplateData{Trusted: true, RunID: "run", StepID: "step"}), nil
	case "TeamPromptTemplateData":
		return newTeamPromptInput(TeamPromptTemplateData{Teams: []TeamPromptTemplateContext{{Label: "fixture"}}}), nil
	case "WorkflowTeamPromptData":
		return newWorkflowTeamPromptInput(WorkflowTeamPromptData{Teams: []WorkflowTeamPromptContext{{Label: "fixture"}}}), nil
	case "TaskReplyTemplateData":
		return newTaskReplyPromptInput(TaskReplyTemplateData{Tool: "message_send", TaskDiscussion: true}), nil
	case "TaskScopeReplyTemplateData":
		return newTaskScopeReplyPromptInput(TaskScopeReplyTemplateData{Tool: "message_send", TaskScopeCall: "message_send(...)"}), nil
	case "FinalVisibleReplyCheckTemplateData":
		return newFinalReplyCheckPromptInput(FinalVisibleReplyCheckTemplateData{SendMessageTool: "message_send", Wakes: []FinalVisibleReplyCheckTemplateWake{{Target: "#fixture", HasTarget: true}}}), nil
	case "RecoveryTemplateData":
		return newRecoveryPromptInput(RecoveryTemplateData{Text: "fixture"}), nil
	case "RecoveryCheckpointTemplateData":
		return newRecoveryCheckpointPromptInput(RecoveryCheckpointTemplateData{Kind: "fresh_start"}), nil
	case "RuntimeRetryTemplateData":
		return newRuntimeRetryPromptInput(RuntimeRetryTemplateData{Reason: "fixture", Attempt: 1, MaxAttempts: 2, Delay: "1s"}), nil
	case "KimiRestrictedSystemTemplateData":
		return newRuntimePolicyPromptInput(KimiRestrictedSystemTemplateData{}), nil
	case "WorkflowEditPromptData":
		return newWorkflowEditPromptInput(WorkflowEditPromptData{PlanID: "plan", TargetLabel: "node:fixture"}), nil
	case "TaskPromptTemplateData":
		return newTaskTemplatePromptInput(TaskPromptTemplateData{Label: "#t1", Discussion: true, ReplyTool: "message_send"}), nil
	case "TaskScopeContextTemplateData":
		return newTaskScopeContextPromptInput(TaskScopeContextTemplateData{Target: "#t1", ReplyInstruction: "fixture"}), nil
	case "ReactionPromptData":
		return newReactionPromptInput(ReactionPromptData{Emoji: "👍", TargetMessageID: "msg"}), nil
	case "MessageDetailsTemplateData":
		return newMessageDetailsPromptInput(MessageDetailsTemplateData{Details: "fixture"}), nil
	case "DeliveryHeaderTemplateData":
		return newDeliveryHeaderPromptInput(DeliveryHeaderTemplateData{Target: "#fixture", MessageID: "msg", Timestamp: "-", Author: "@fixture", Payload: "fixture"}), nil
	case "AttachmentTemplateData":
		return newAttachmentPromptInput(AttachmentTemplateData{Attachments: []AttachmentTemplateItem{{Number: 1, Label: "fixture"}}}), nil
	case "AttachmentDetailsTemplateData":
		return newAttachmentDetailsPromptInput(AttachmentDetailsTemplateData{Attachments: []MessageAttachmentDetailItem{{Filename: "fixture"}}}), nil
	case "ThreadRootTemplateData":
		return newThreadRootPromptInput(ThreadRootTemplateData{MessageID: "msg", Timestamp: "-", Author: "@fixture", Content: "fixture"}), nil
	default:
		return nil, fmt.Errorf("no fixture for expected concrete type %q", spec.ExpectedType)
	}
}

func expectedPromptInputType(id string) string {
	switch id {
	case "platform.identity":
		return "IdentityTemplateData"
	case "agent_config.initial_role":
		return "InitialRoleTemplateData"
	case "platform.who_you_are", "platform.cooperation", "platform.agentteam.task_boards", "platform.agent_identity", "event.agent_authored_wake_guidance":
		return "EmptyTemplateData"
	case "platform.agentteam.startup", "platform.agentteam.review_context":
		return "PlatformLoopTemplateData"
	case "platform.agentteam.core":
		return "AgentTeamCoreTemplateData"
	case "platform.agentteam.reference":
		return "AgentTeamReferenceTemplateData"
	case "platform.agentteam.memory_search":
		return "MemorySearchGuidanceTemplateData"
	case "event.thread_context_guidance", "event.thread_context_compact_guidance":
		return "ThreadContextTemplateData"
	case "platform.agentteam.external_skill_core", "platform.agentteam.external_skill_reference":
		return "ExternalSkillPayloadTemplateData"
	case "platform.agentteam.exec":
		return "ExecGuidanceTemplateData"
	case "platform.agentteam.extension":
		return "ExtensionCatalogGuidanceTemplateData"
	case "platform.workspace_memory":
		return "WorkspaceMemoryTemplateData"
	case "workspace.identity":
		return "WorkspaceIdentityTemplateData"
	case "agent_config.agents_md":
		return "WorkspaceDocumentTemplateData"
	case "notice.identity":
		return "IdentityNoticeTemplateData"
	case "notice.workspace.identity_oversize", "notice.agent_config.agents_md_oversize":
		return "WorkspaceDocOversizeTemplateData"
	case "event.message_header":
		return "MessageTemplateData"
	case "event.delivery_message":
		return "DeliveryMessageTemplateData"
	case "runtime.memory_resume_reminder":
		return "EmptyTemplateData"
	case "event.delivery_reply":
		return "DeliveryReplyTemplateData"
	case "event.delivery_replay":
		return "DeliveryReplayTemplateData"
	case "event.reply_target_context", "renderer.reply_target_context":
		return "ReplyTargetTemplateData"
	case "event.current_message":
		return "CurrentMessageTemplateData"
	case "event.wake_context":
		return "WakeContextTemplateData"
	case "event.resume_context":
		return "ResumeContextTemplateData"
	case "event.deferred_continuation":
		return "DeferredContinuationTemplateData"
	case "event.active_task_routing":
		return "ActiveTaskTemplateData"
	case "event.active_task_normal_routing":
		return "ActiveTaskNormalRoutingTemplateData"
	case "event.workflow_context":
		return "WorkflowTemplateData"
	case "event.team_context":
		return "TeamPromptTemplateData"
	case "event.workflow_team_context":
		return "WorkflowTeamPromptData"
	case "event.task_reply":
		return "TaskReplyTemplateData"
	case "event.task_scope_reply":
		return "TaskScopeReplyTemplateData"
	case "event.task_attributes":
		return "EmptyTemplateData"
	case "event.unread_summary":
		return "UnreadSummaryTemplateData"
	case "event.attachment_manifest", "event.attachment_preparation_failure", "runtime_attachment_manifest", "runtime_attachment_preparation_failure":
		return "AttachmentTemplateData"
	case "event.attachment_details":
		return "AttachmentDetailsTemplateData"
	case "event.thread_root":
		return "ThreadRootTemplateData"
	case "event.reaction_details":
		return "ReactionPromptData"
	case "event.message_details":
		return "MessageDetailsTemplateData"
	case "event.delivery_header":
		return "DeliveryHeaderTemplateData"
	case "platform.agentteam.visible_reply_contract":
		return "VisibleReplyContractTemplateData"
	case "agentteam.final_visible_reply_check":
		return "FinalVisibleReplyCheckTemplateData"
	case "agentteam.context_compaction_continuation", "agentteam.output_truncation_continuation", "runtime.opencode_subtask_summary", "event.context_recovery_restart":
		return "RecoveryTemplateData"
	case "event.recovery_checkpoint":
		return "RecoveryCheckpointTemplateData"
	case "event.runtime_retry":
		return "RuntimeRetryTemplateData"
	case "runtime.kimi_restricted_system":
		return "KimiRestrictedSystemTemplateData"
	case "event.workflow_edit":
		return "WorkflowEditPromptData"
	case "event.task_dispatch":
		return "TaskPromptTemplateData"
	case "event.task_scope_context":
		return "TaskScopeContextTemplateData"
	default:
		return ""
	}
}

func specTemplateVersion(spec PromptBlockSpec) string {
	if strings.TrimSpace(spec.TemplatePath) == "" {
		return ""
	}
	return prompts.Version(spec.TemplatePath)
}

func renderRegisteredPromptText(id string, input PromptBlockInput) string {
	if _, ok := DefaultPromptBlockRegistry().Lookup(id); !ok {
		panic(fmt.Errorf("prompt block %q is not registered", id))
	}
	block, err := DefaultPromptBlockRegistry().Construct(id, input)
	if err != nil {
		panic(err)
	}
	return block.text
}

func registeredPromptTemplateRenderer(spec PromptBlockSpec, input PromptBlockInput) (string, error) {
	if input == nil {
		return "", fmt.Errorf("prompt block %q received nil typed input", spec.ID)
	}
	return input.renderPromptBlock(spec)
}

func registeredPromptBlockConstructor(spec PromptBlockSpec, input PromptBlockInput) (PromptBlock, error) {
	if input == nil || input.promptInputType() != spec.InputType || input.promptInputName() != spec.ExpectedType {
		return PromptBlock{}, fmt.Errorf("prompt block %q received wrong typed input; expected %s", spec.ID, spec.ExpectedType)
	}
	if spec.Renderer == nil {
		return PromptBlock{}, fmt.Errorf("prompt block %q has no typed template renderer", spec.ID)
	}
	text, err := spec.Renderer(spec, input)
	if err != nil {
		return PromptBlock{}, err
	}
	text = strings.TrimSpace(text)
	var tags []string
	if tagged, ok := input.(interface{ promptBlockTags() []string }); ok {
		tags = tagged.promptBlockTags()
	}
	if text == "" && spec.ExpectedType != "AttributionTemplateData" {
		return PromptBlock{}, fmt.Errorf("prompt block %q rendered empty typed input", spec.ID)
	}
	return PromptBlock{
		ID: spec.ID, Title: spec.Title, Type: spec.Type, Layer: spec.Layer, Slot: spec.Slot,
		Durability: spec.Durability, Priority: spec.Priority, text: text, TokenEstimate: EstimateTokens(text),
		DebugLabel: spec.DebugLabel, Tags: tags, registryKey: spec.ID,
		Source: PromptSourceRef{ID: spec.ID, SourceKind: spec.SourceKind, Path: "desktop/prompts/" + spec.TemplatePath, Version: specTemplateVersion(spec), Owner: spec.Owner, Description: spec.Description},
	}, nil
}

var defaultPromptBlockSpecs = []PromptBlockSpec{
	blockSpec("platform.identity", PromptInputIdentity, templatePlatformIdentity, SlotBase, 10, DurabilityPersistent),
	blockSpec("agent_config.initial_role", PromptInputAgentConfig, templateAgentInitialRole, SlotBase, 20, DurabilityPersistent),
	blockSpec("platform.who_you_are", PromptInputIdentity, templatePlatformWhoYouAre, SlotBase, 30, DurabilityPersistent),
	blockSpec("platform.cooperation", PromptInputIdentity, templatePlatformCooperation, SlotBase, 35, DurabilityPersistent),
	blockSpec("platform.agentteam.startup", PromptInputSkillPlan, templatePlatformAgentTeamStartup, SlotBase, 37, DurabilityPersistent),
	blockSpec("platform.agentteam.core", PromptInputSkillPlan, templateWireCore, SlotBase, 40, DurabilityPersistent),
	blockSpec("platform.agentteam.memory_search", PromptInputSkillPlan, templateMemorySearchGuidance, SlotBase, 41, DurabilityPersistent),
	blockSpec("platform.agentteam.external_skill_core", PromptInputSkillPlan, templateExternalSkillCore, SlotBase, 42, DurabilityPersistent),
	blockSpec("platform.agentteam.task_boards", PromptInputTask, templatePlatformAgentTeamTaskBoards, SlotBase, 45, DurabilityPersistent),
	blockSpec("platform.agentteam.review_context", PromptInputSkillPlan, templatePlatformAgentTeamReviewContext, SlotBase, 48, DurabilityPersistent),
	blockSpec("platform.agentteam.exec", PromptInputExecGuidance, templateExecGuidance, SlotBase, 49, DurabilityPersistent),
	blockSpec("platform.agentteam.extension", PromptInputExtensionCatalogGuidance, templateExtensionGuidance, SlotBase, 49, DurabilityPersistent),
	blockSpec("platform.workspace_memory", PromptInputWorkspaceDocument, templatePlatformWorkspaceMemory, SlotBase, 50, DurabilityPersistent),
	blockSpec("platform.agent_identity", PromptInputIdentity, templatePlatformAgentIdentity, SlotBase, 55, DurabilityPersistent),
	blockSpec("workspace.identity", PromptInputWorkspaceDocument, templateWorkspaceIdentity, SlotBase, 56, DurabilityPersistent),
	blockSpec("agent_config.agents_md", PromptInputWorkspaceDocument, templateAgentAgentsDoc, SlotDeveloper, 10, DurabilityPersistent),
	blockSpec("notice.identity", PromptInputIdentity, "notices/identity_notice.md", SlotTurn, 10, DurabilityUntilAcknowledged),
	blockSpec("notice.workspace.identity_oversize", PromptInputWorkspaceDocument, workspaceDocOversizeNoticeTemplate, SlotBase, 57, DurabilityPersistent),
	blockSpec("notice.agent_config.agents_md_oversize", PromptInputWorkspaceDocument, workspaceDocOversizeNoticeTemplate, SlotBase, 57, DurabilityPersistent),
	blockSpec("event.message_header", PromptInputMessage, templateMessageHeader, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.thread_context_guidance", PromptInputMessage, templateThreadContext, SlotTurn, 21, DurabilityTurnOnly),
	blockSpec("event.thread_context_compact_guidance", PromptInputMessage, templateThreadContextCompact, SlotTurn, 21, DurabilityTurnOnly),
	blockSpec("event.delivery_message", PromptInputMessage, templateEventDeliveryMessage, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("runtime.memory_resume_reminder", PromptInputRecovery, templateMemoryResumeReminder, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.delivery_reply", PromptInputMessage, templateDeliveryReply, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.delivery_replay", PromptInputRecovery, templateDeliveryReplay, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.reply_target_context", PromptInputRenderer, templateReplyTargetContext, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.current_message", PromptInputMessage, templateEventCurrentMessage, SlotTurn, 30, DurabilityTurnOnly),
	blockSpec("event.active_task_routing", PromptInputActiveTask, templateActiveTask, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.active_task_normal_routing", PromptInputActiveTask, templateActiveTaskNormalRouting, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.agent_authored_wake_guidance", PromptInputMessage, templateAgentAuthoredWakeGuidance, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.workflow_context", PromptInputWorkflow, templateWorkflowContext, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.team_context", PromptInputTeam, templateTeamContext, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.workflow_team_context", PromptInputTeam, templateWorkflowTeamContext, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.task_reply", PromptInputTask, templateTaskReply, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.task_scope_reply", PromptInputTask, templateTaskScopeReply, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.task_attributes", PromptInputTask, templateTaskAttributes, SlotTurn, 22, DurabilityTurnOnly),
	blockSpec("event.wake_context", PromptInputMessage, templateEventWakeMessage, SlotTurn, 20, DurabilityTurnOnly),
	blockSpec("event.resume_context", PromptInputRecovery, templateEventResumeIdle, SlotTurn, 20, DurabilityTurnOnly),
	blockSpec("event.unread_summary", PromptInputMessage, templateEventUnreadSummary, SlotTurn, 20, DurabilityTurnOnly),
	blockSpec("event.deferred_continuation", PromptInputRecovery, templateEventDeferredContinuation, SlotTurn, 30, DurabilityTurnOnly),
	blockSpec("platform.agentteam.visible_reply_contract", PromptInputVisibleReplyCheck, templateVisibleReplyContract, SlotTurn, 25, DurabilityTurnOnly),
	blockSpec("platform.agentteam.reference", PromptInputSkillPlan, templateWireReference, SlotReference, 20, DurabilityCompactable),
	blockSpec("platform.agentteam.external_skill_reference", PromptInputSkillPlan, templateExternalSkillReference, SlotReference, 21, DurabilityCompactable),
	blockSpec("renderer.reply_target_context", PromptInputRenderer, templateReplyTargetContext, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.attachment_manifest", PromptInputAttachment, templateAttachmentManifest, SlotTurn, 24, DurabilityTurnOnly),
	blockSpec("event.attachment_preparation_failure", PromptInputAttachment, templateAttachmentPreparationFailure, SlotTurn, 24, DurabilityTurnOnly),
	blockSpec("runtime_attachment_manifest", PromptInputAttachment, templateAttachmentManifest, SlotTurn, 24, DurabilityTurnOnly),
	blockSpec("runtime_attachment_preparation_failure", PromptInputAttachment, templateAttachmentPreparationFailure, SlotTurn, 24, DurabilityTurnOnly),
	blockSpec("agentteam.final_visible_reply_check", PromptInputVisibleReplyCheck, templateFinalVisibleReplyCheck, SlotTurn, 30, DurabilityTurnOnly),
	blockSpec("agentteam.context_compaction_continuation", PromptInputRecovery, templateContextCompactionContinuation, SlotTurn, 30, DurabilityTurnOnly),
	blockSpec("agentteam.output_truncation_continuation", PromptInputRecovery, templateOutputTruncationContinuation, SlotTurn, 30, DurabilityTurnOnly),
	blockSpec("event.recovery_checkpoint", PromptInputRecovery, templateRecoveryCheckpoint, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.runtime_retry", PromptInputRecovery, templateRuntimeRetry, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("runtime.kimi_restricted_system", PromptInputRuntimePolicy, templateKimiRestrictedSystem, SlotRuntimeCarrier, 1, DurabilityPersistent),
	blockSpec("runtime.opencode_subtask_summary", PromptInputRecovery, templateOpenCodeSubtaskSummary, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.context_recovery_restart", PromptInputRecovery, templateContextRecoveryRestart, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.workflow_edit", PromptInputWorkflow, templateWorkflowAgentEdit, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.task_dispatch", PromptInputTask, templateTaskDispatch, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.task_scope_context", PromptInputTask, templateTaskScopeContext, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.reaction_details", PromptInputMessage, templateReactionDetails, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.message_details", PromptInputMessage, templateMessageDetails, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.delivery_header", PromptInputMessage, templateDeliveryHeader, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.attachment_details", PromptInputAttachment, templateAttachmentDetails, SlotTurn, 1, DurabilityTurnOnly),
	blockSpec("event.thread_root", PromptInputMessage, templateThreadRoot, SlotTurn, 1, DurabilityTurnOnly),
}

const (
	templateWireCore                      = "wire/core.md"
	templateWireReference                 = "wire/reference.md"
	templateMemorySearchGuidance          = "platform/agentteam/memory_search.md"
	templateExternalSkillCore             = "wire/external_skill_core.md"
	templateExternalSkillReference        = "wire/external_skill_reference.md"
	templateExecGuidance                  = "events/exec_guidance.md"
	templateExtensionGuidance             = "events/extension_guidance.md"
	templateWorkspaceDocument             = "events/workspace_document.md"
	templateWorkspaceIdentity             = "events/workspace_identity.md"
	templateVisibleReplyContract          = "events/visible_reply_contract.md"
	templateAttachmentManifest            = "events/attachment_manifest.md"
	templateAttachmentPreparationFailure  = "events/attachment_preparation_failure.md"
	templateContextCompactionContinuation = "events/context_compaction_continuation.md"
	templateOutputTruncationContinuation  = "events/output_truncation_continuation.md"
	templateFinalVisibleReplyCheck        = "events/final_visible_reply_check.md"
	templateActiveTask                    = "events/active_task.md"
	templateActiveTaskNormalRouting       = "events/active_task_normal_routing.md"
	templateAgentAuthoredWakeGuidance     = "events/agent_authored_wake_guidance.md"
	templateWorkflowContext               = "events/workflow_context.md"
	templateTeamContext                   = "events/team_context.md"
	templateTaskReply                     = "events/task_reply.md"
	templateTaskScopeReply                = "events/task_scope_reply.md"
	templateTaskAttributes                = "events/task_attributes_instruction.md"
	templateMessageHeader                 = "events/message_header.md"
	templateThreadContext                 = "events/thread_context.md"
	templateThreadContextCompact          = "events/thread_context_compact.md"
	templateDeliveryReply                 = "events/delivery_reply.md"
	templateDeliveryReplay                = "events/delivery_replay.md"
	templateReplyTargetContext            = "events/reply_target_context.md"
	templateRecoveryCheckpoint            = "events/recovery_checkpoint.md"
	templateRuntimeRetry                  = "events/runtime_retry.md"
	templateKimiRestrictedSystem          = "events/kimi_restricted_system.md"
	templateOpenCodeSubtaskSummary        = "events/opencode_subtask_summary.md"
	templateContextRecoveryRestart        = "events/context_recovery_restart.md"
)
