//go:build ignore

// post-commit-producers-ast.go is the structural half of
// post-commit-producers.sh. It uses the Go parser rather than line-oriented
// matching so formatting changes cannot silently create an unowned autonomous
// message producer.
package main

import (
	"flag"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"io/fs"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

type siteKey struct {
	file string
	fn   string
}

type callSite struct {
	key           siteKey
	line          int
	call          *ast.CallExpr
	insideClosure bool
	assignedNames []string
}

type parsedFunction struct {
	key  siteKey
	decl *ast.FuncDecl
	file *ast.File
}

type checker struct {
	root      string
	fset      *token.FileSet
	functions []parsedFunction
	failures  []string
}

type appServiceProducer struct {
	contract             string
	producerID           string
	owner                string
	requiresConfigurator bool
}

var expectedAppServiceProducers = map[siteKey]appServiceProducer{
	{file: "internal/store/app/service_event_delivery_dispatcher.go", fn: "deliverCandidateGroup"}: {
		contract:   "appServiceMessageProviderEvent",
		producerID: "committedMessageProducerProviderEvent",
		owner:      "appServiceMessageCallerOwned",
	},
	{file: "internal/store/app/app_notification_service.go", fn: "CreateAppNotification"}: {
		contract:             "appServiceMessageAppNotification",
		producerID:           "committedMessageProducerAppNotification",
		owner:                "appServiceMessageStoreOwnedRecoverable",
		requiresConfigurator: true,
	},
	{file: "internal/store/app/app_runtime_delivery.go", fn: "appendAppRuntimeReplyEffect"}: {
		contract:   "appServiceMessageExternalAppRuntimeReply",
		producerID: "committedMessageProducerExternalAppRuntimeReply",
		owner:      "appServiceMessageStoreOwnedRecoverable",
	},
	{file: "internal/store/app/knowledge_qa_messages.go", fn: "AppendKnowledgeAssistantMessageTx"}: {
		contract:   "appServiceMessageKnowledgeQA",
		producerID: "committedMessageProducerKnowledgeQA",
		owner:      "appServiceMessageCallerOwned",
	},
}

var expectedMessageAppendCallers = map[siteKey]string{
	{file: "internal/store/app/channel_share_cards.go", fn: "CreateChannelShareCardForUser"}:                         "messageAppendCallerOwned",
	{file: "internal/store/app/channel_share_cards.go", fn: "ShareChannelShareCardForUser"}:                          "messageAppendCallerOwned",
	{file: "internal/store/app/app_messages.go", fn: "sendAppMessageForUser"}:                                        "messageAppendCallerOwned",
	{file: "internal/store/app/thread_share_cards.go", fn: "CreateThreadShareCardForUser"}:                           "messageAppendCallerOwned",
	{file: "internal/store/app/service_account_publisher.go", fn: "sendServiceAccountPublisherMessage"}:              "messageAppendCallerOwned",
	{file: "internal/store/app/app_service_messages.go", fn: "appendAppServiceMessageWithOptions"}:                   "messageAppendAppServiceContracted",
	{file: "internal/store/app/platform_service_account_broadcast.go", fn: "DeliverPlatformServiceAccountBroadcast"}: "messageAppendStoreOwnedRecoverable",
	{file: "internal/store/app/app_messages.go", fn: "appendWorkflowApprovalMessageTx"}:                              "messageAppendCallerOwned",
}

var expectedMessageAppendResultCallers = map[siteKey]string{
	{file: "internal/store/app/message_append.go", fn: "appendMessageToThread"}:        "lifecycle",
	{file: "internal/store/app/agent_messages.go", fn: "sendAgentThreadMessageResult"}: "messageAppendCallerOwned",
}

var expectedConversationWriterResultSites = map[siteKey]bool{
	{file: "internal/store/app/message_append.go", fn: "appendMessageToThreadResult"}:      false,
	{file: "internal/store/privateagent/service_ports.go", fn: "WriteConversationMessage"}: false,
}

// Close every layer below the exported privateagent conversation writer. A
// same-package producer must not bypass WriteConversationMessageResult and
// silently drop the SendMessageResult handoff by calling either raw seam.
var expectedPrivateConversationRawSeamCalls = map[string]map[siteKey]int{
	"writeConversationMessageResult": {
		{file: "internal/store/privateagent/service_ports.go", fn: "WriteConversationMessageResult"}: 1,
	},
	"writeScopeMessageToConversationResult": {
		{file: "internal/store/privateagent/conversation_write.go", fn: "writeConversationMessageResult"}: 1,
	},
}

// These are the complete production MessagePlan applier call sets. In
// particular, conversation_write.go is admitted only at the named lowest raw
// seam and exactly once, rather than by a file-level allowlist.
var expectedMessagePlanApplierCalls = map[string]map[siteKey]int{
	"ApplyMessagePlanTx": {
		{file: "internal/store/app/channel_member_events.go", fn: "appendChannelMemberEventMessage"}:          1,
		{file: "internal/store/app/service_event_task_delivery.go", fn: "appendServiceEventTaskDiscussionTx"}: 1,
		{file: "internal/store/message/workflow_scope.go", fn: "WriteWorkflowScopeMessageTx"}:                 1,
	},
	"ApplyMessagePlanTxWithSidecarWriter": {
		{file: "internal/store/privateagent/conversation_write.go", fn: "writeScopeMessageToConversationResult"}: 1,
		{file: "internal/store/taskboard/detail_mutation.go", fn: "anchorTaskDescriptionAttachments"}:            1,
		{file: "internal/store/taskboard/task_scope_message.go", fn: "appendTaskScopeMessage"}:                   1,
	},
}

// The exported appliers are the only owners of the lowercase physical
// application seam. Closing this final layer prevents a package-local writer
// from bypassing profile validation or sidecar-writer policy.
var expectedMessagePlanRawSeamCalls = map[siteKey]int{
	{file: "internal/store/message/scope_applier.go", fn: "ApplyMessagePlanTx"}:                  1,
	{file: "internal/store/message/scope_applier.go", fn: "ApplyMessagePlanTxWithSidecarWriter"}: 1,
}

var expectedAgentCreateRawSeamCalls = map[string]map[siteKey]int{
	"createAgentInWorkspace": {
		{file: "internal/store/app/agent_lifecycle.go", fn: "CreateAgentForUser"}:       1,
		{file: "internal/store/app/agent_lifecycle.go", fn: "CreateWorkspaceAgentInTx"}: 1,
	},
	"addAgentChannelMembershipsInTx": {
		{file: "internal/store/app/agent_lifecycle.go", fn: "createAgentInWorkspace"}: 1,
	},
	"emitCreatedAgentChannelMemberEvents": {
		{file: "internal/store/app/agent_lifecycle.go", fn: "CreateAgentForUser"}: 1,
	},
}

// All production agent-creation routes share CreateAgentForUser. Closing this
// call set keeps regular create, duplicate, and role materialization on the
// same post-commit channel-member projection contract.
var expectedCreateAgentForUserCalls = map[siteKey]int{
	{file: "internal/api/webagent/agent_services.go", fn: "DuplicateAgent"}:      1,
	{file: "internal/api/webagent/agent_services.go", fn: "CreateAgent"}:         1,
	{file: "internal/service/role/agents/service.go", fn: "CreateAgentFromRole"}: 1,
	{file: "internal/api/testkit/store_agent.go", fn: "CreateAgentForUser"}:      1,
	{file: "internal/store/testkit/store.go", fn: "CreateAgentForUser"}:          1,
}

var expectedProjectionOnlySites = map[siteKey]bool{
	{file: "internal/api/channel/api_channel_membership.go", fn: "emitChannelMemberEvent"}:     false,
	{file: "internal/service/channeladmin/service.go", fn: "emitChannelMemberMutationEffects"}: false,
	{file: "internal/store/app/agent_lifecycle.go", fn: "emitCreatedAgentChannelMemberEvents"}: false,
}

var expectedChannelExecMemberEffectSites = map[siteKey]bool{
	{file: "internal/service/channeladmin/service.go", fn: "mutateAgents"}: false,
}

// expectedCallerOwnedBoundaryCalls closes the outer delivery-returning API
// surface as well as the raw append seam. Without this inventory an autonomous
// producer could call an existing caller-owned method, discard its delivery,
// and reproduce the exact missing-first-fanout failure without adding a raw
// append call.
var expectedCallerOwnedBoundaryCalls = map[string]map[siteKey]int{
	"SendHumanMessage": {
		{file: "internal/api/im/messages_handler.go", fn: "Create"}:                                1,
		{file: "internal/api/im/rewind_handler.go", fn: "publishRewindNotice"}:                     1,
		{file: "internal/api/im/fanout/effect_writer_helpers.go", fn: "SendAndFanoutHumanMessage"}: 1,
	},
	"SendAppMessageForUserWithOptions": {
		{file: "internal/store/app/review_feedback.go", fn: "CreateReviewFeedbackForUser"}:                1,
		{file: "internal/service/approvalapp/team_role_feedback.go", fn: "postTeamRoleRejectionFeedback"}: 1,
	},
	"SendMessageForUserWithOptions": {
		{file: "internal/api/onboarding/routes.go", fn: "SendDesktopOnboardingMockMessageForUser"}:     1,
		{file: "internal/service/message/service.go", fn: "SendHumanMessage"}:                          1,
		{file: "internal/service/approvalapp/workflow_run_feedback.go", fn: "sendWorkflowRunFeedback"}: 1,
	},
	"SendMessageForUserWithType": {
		{file: "internal/service/message/service.go", fn: "SendHumanMessage"}:  2,
		{file: "internal/store/app/app_messages.go", fn: "SendMessageForUser"}: 1,
	},
	"SendMessageForUserWithMetadata": {
		{file: "internal/service/message/service.go", fn: "SendHumanMessage"}:                 1,
		{file: "internal/api/testkit/store_message.go", fn: "SendMessageForUserWithMetadata"}: 1,
		{file: "internal/api/task/store_adapter.go", fn: "SendMessageForUserWithMetadata"}:    1,
		{file: "internal/api/task/api_parity.go", fn: "CreateAnnouncement"}:                   2,
	},
	"SendMessageForUser": {
		{file: "internal/api/testkit/store_message.go", fn: "SendMessageForUser"}: 1,
	},
	"SendSystemMessageForUserWithMetadata": {
		{file: "internal/store/app/app_messages.go", fn: "SendSystemMessageForUser"}: 1,
	},
	"SendSystemMessageForUser": {},
	"SendAgentThreadMessage": {
		{file: "internal/api/onboarding/routes.go", fn: "SendDesktopOnboardingMockAgentReply"}: 1,
		{file: "internal/api/testkit/store_message.go", fn: "SendAgentMessage"}:                1,
		{file: "internal/store/testkit/store.go", fn: "SendAgentMessage"}:                      1,
		{file: "internal/api/agent/store_adapter.go", fn: "SendAgentMessage"}:                  1,
	},
	"SendAgentThreadMessageWithFreshness": {
		{file: "internal/api/agent/store_adapter.go", fn: "SendAgentMessageWithFreshness"}: 1,
	},
	"SendAgentMessage": {
		{file: "internal/api/agent/message_flow.go", fn: "sendMessage"}: 1,
	},
	"SendAgentMessageWithFreshness": {
		{file: "internal/api/agent/message_flow.go", fn: "sendMessageWithFreshness"}: 1,
	},
	"CreateChannelShareCardForUser": {
		{file: "internal/api/channel/api_channel_share_cards.go", fn: "Create"}: 1,
	},
	"ShareChannelShareCardForUser": {
		{file: "internal/api/channel/api_channel_share_cards.go", fn: "Share"}: 1,
	},
	"CreateThreadShareCardForUser": {
		{file: "internal/api/channel/api_threads.go", fn: "CreateShareCard"}: 1,
	},
	"CreateReviewFeedbackForUser": {
		{file: "internal/api/channel/api_review_feedback.go", fn: "SubmitReviewFeedback"}: 1,
	},
	"sendMessageForUserTargetAware": {
		{file: "internal/store/app/app_messages.go", fn: "SendMessageForUserWithOptions"}:  1,
		{file: "internal/store/app/app_messages.go", fn: "SendMessageForUserWithMetadata"}: 1,
		{file: "internal/store/app/app_messages.go", fn: "SendMessageForUserWithType"}:     1,
	},
	"sendServiceAccountPublisherMessage": {
		{file: "internal/store/app/service_account_publisher.go", fn: "sendMessageForUserTargetAware"}: 1,
	},
}

var expectedPrepareSites = map[siteKey]bool{
	// false means the call is in the named function body itself. The function is
	// invoked only while its caller owns an open transaction.
	{file: "internal/store/app/committed_message_fanout.go", fn: "finalizeAppServiceMessageTx"}: false,
	// The platform broadcast prepares its token in the transaction callback.
	{file: "internal/store/app/platform_service_account_broadcast.go", fn: "DeliverPlatformServiceAccountBroadcast"}: true,
	// External App runtime Task replies prepare the recoverable token in the
	// Task-specific reply append, which only runs inside the caller's open tx.
	{file: "internal/store/app/app_runtime_task_reply.go", fn: "appendAppRuntimeTaskReplyEffect"}: false,
}

var expectedEmitSites = map[siteKey]bool{
	{file: "internal/store/app/app_notification_service.go", fn: "CreateAppNotification"}:                            false,
	{file: "internal/store/app/app_runtime_delivery.go", fn: "ReplyAppRuntimeDelivery"}:                              false,
	{file: "internal/store/app/platform_service_account_broadcast.go", fn: "DeliverPlatformServiceAccountBroadcast"}: false,
}

var expectedSetterSites = map[siteKey]bool{
	{file: "internal/server/wire.go", fn: "wireRouterDeps"}:                                         false,
	{file: "internal/api/testkit/runtime.go", fn: "startRuntimeWithOrganizationProviderAndWorkers"}: false,
	{file: "cmd/agentteam-e2e-server/main.go", fn: "main"}:                                               false,
}

var expectedEncodeSites = map[siteKey]bool{
	{file: "internal/store/app/committed_message_fanout.go", fn: "preparePostCommitMessage"}: false,
}

var expectedAppServiceFinalizeSites = map[siteKey]bool{
	{file: "internal/store/app/app_service_messages.go", fn: "appendAppServiceMessageWithOptions"}: false,
}

func main() {
	root := flag.String("root", ".", "repository root")
	flag.Parse()

	absRoot, err := filepath.Abs(*root)
	if err != nil {
		fmt.Fprintf(os.Stderr, "resolve repository root: %v\n", err)
		os.Exit(2)
	}
	c := &checker{root: absRoot, fset: token.NewFileSet()}
	c.loadProductionGo()
	c.checkAuthoritativeDeclarations()
	c.checkAppServiceContractRegistry()
	c.checkRecoverableProducerRegistry()
	c.checkMessageAppendLifecycle()
	c.checkMessageAppendResultLifecycle()
	c.checkControlledCalls("WriteConversationMessageResult", expectedConversationWriterResultSites)
	c.checkControlledCalls("WriteConversationMessage", map[siteKey]bool{})
	for callName, expected := range expectedPrivateConversationRawSeamCalls {
		c.checkControlledCallCounts(callName, expected)
	}
	for callName, expected := range expectedMessagePlanApplierCalls {
		c.checkControlledCallCounts(callName, expected)
	}
	c.checkControlledCallCounts("applyMessagePlanTx", expectedMessagePlanRawSeamCalls)
	c.checkPrivateConversationRawSeamLifecycle()
	c.checkControlledCallCounts("CreateAgentForUser", expectedCreateAgentForUserCalls)
	c.checkControlledCallCounts("CreateWorkspaceAgentInTx", map[siteKey]int{})
	for callName, expected := range expectedAgentCreateRawSeamCalls {
		c.checkControlledCallCounts(callName, expected)
	}
	for callName, expected := range expectedCallerOwnedBoundaryCalls {
		c.checkControlledCallCounts(callName, expected)
	}
	c.checkAgentCreateMemberEventLifecycle()
	c.checkControlledCalls("EmitCommittedMessageProjectionOnly", expectedProjectionOnlySites)
	c.checkControlledCalls("emitChannelMemberMutationEffects", expectedChannelExecMemberEffectSites)
	c.checkChannelExecMemberEventLifecycle()
	c.checkAppServiceFinalization()
	c.checkControlledCalls("finalizeAppServiceMessageTx", expectedAppServiceFinalizeSites)
	c.checkControlledCalls("preparePostCommitMessage", expectedPrepareSites)
	c.checkPrepareProducerArguments()
	c.checkControlledCalls("encodeRecoverablePostCommitDelivery", expectedEncodeSites)
	c.checkControlledCalls("emitPostCommitMessage", expectedEmitSites)
	c.checkControlledCalls("SetCommittedMessageFanout", expectedSetterSites)
	c.checkRecoverablePolicyWrites()
	c.checkHandoffTokenConstruction()

	if len(c.failures) != 0 {
		sort.Strings(c.failures)
		for _, failure := range c.failures {
			fmt.Fprintf(os.Stderr, "FAIL: %s\n", failure)
		}
		fmt.Fprintf(os.Stderr, "\npost-commit producer AST guardrail failed with %d issue(s).\n", len(c.failures))
		os.Exit(1)
	}
	fmt.Println("post-commit producer AST guardrail passed.")
}

func (c *checker) fail(format string, args ...any) {
	c.failures = append(c.failures, fmt.Sprintf(format, args...))
}

func (c *checker) loadProductionGo() {
	roots := []string{"internal", "cmd", "desktop"}
	for _, relativeRoot := range roots {
		walkRoot := filepath.Join(c.root, relativeRoot)
		if _, err := os.Stat(walkRoot); err != nil {
			if !os.IsNotExist(err) {
				c.fail("inspect %s: %v", relativeRoot, err)
			}
			continue
		}
		err := filepath.WalkDir(walkRoot, func(path string, entry fs.DirEntry, walkErr error) error {
			if walkErr != nil {
				return walkErr
			}
			if entry.IsDir() {
				switch entry.Name() {
				case ".git", "node_modules", "vendor":
					return filepath.SkipDir
				}
				return nil
			}
			if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
				return nil
			}
			parsed, err := parser.ParseFile(c.fset, path, nil, parser.SkipObjectResolution)
			if err != nil {
				return fmt.Errorf("parse %s: %w", path, err)
			}
			rel, err := filepath.Rel(c.root, path)
			if err != nil {
				return err
			}
			rel = filepath.ToSlash(rel)
			for _, declaration := range parsed.Decls {
				fn, ok := declaration.(*ast.FuncDecl)
				if !ok || fn.Body == nil {
					continue
				}
				c.functions = append(c.functions, parsedFunction{
					key:  siteKey{file: rel, fn: fn.Name.Name},
					decl: fn,
					file: parsed,
				})
			}
			return nil
		})
		if err != nil {
			c.fail("walk production Go under %s: %v", relativeRoot, err)
		}
	}
}

func (c *checker) checkAuthoritativeDeclarations() {
	expectedFunctions := map[string]string{
		"appendMessageToThread":                 "internal/store/app/message_append.go",
		"appendMessageToThreadResult":           "internal/store/app/message_append.go",
		"writeConversationMessageResult":        "internal/store/privateagent/conversation_write.go",
		"writeScopeMessageToConversationResult": "internal/store/privateagent/conversation_write.go",
		"applyMessagePlanTx":                    "internal/store/message/scope_applier.go",
		"appendAppServiceMessageWithOptions":    "internal/store/app/app_service_messages.go",
		"preparePostCommitMessage":              "internal/store/app/committed_message_fanout.go",
		"encodeRecoverablePostCommitDelivery":   "internal/store/app/committed_message_fanout.go",
		"finalizeAppServiceMessageTx":           "internal/store/app/committed_message_fanout.go",
		"emitPostCommitMessage":                 "internal/store/app/committed_message_fanout.go",
		"SetCommittedMessageFanout":             "internal/store/app/committed_message_fanout.go",
		"ownsRecoverablePostCommitFanout":       "internal/store/app/committed_message_fanout.go",
	}
	seen := make(map[string]int, len(expectedFunctions))
	for _, fn := range c.functions {
		expectedFile, tracked := expectedFunctions[fn.key.fn]
		if !tracked {
			continue
		}
		if fn.key.file != expectedFile {
			c.fail("canonical function %s moved or was duplicated in %s", fn.key.fn, fn.key.file)
			continue
		}
		seen[fn.key.fn]++
		if (fn.key.fn == "appendMessageToThread" || fn.key.fn == "appendMessageToThreadResult") && !functionLastParameterHasType(fn.decl, "messageAppendLifecycle") {
			c.fail("%s must require an explicit messageAppendLifecycle parameter", formatKey(fn.key))
		}
		if (fn.key.fn == "writeConversationMessageResult" || fn.key.fn == "writeScopeMessageToConversationResult") && !functionReturnsNamedType(fn.decl, "SendMessageResult") {
			c.fail("%s must return the complete SendMessageResult handoff", formatKey(fn.key))
		}
		if fn.key.fn == "applyMessagePlanTx" && !functionReturnsNamedType(fn.decl, "MessageApplyResult") {
			c.fail("%s must return the complete MessageApplyResult", formatKey(fn.key))
		}
		if fn.key.fn == "appendAppServiceMessageWithOptions" && !functionReturnsNamedType(fn.decl, "MessageDelivery") {
			c.fail("%s must return the finalized MessageDelivery", formatKey(fn.key))
		}
		if fn.key.fn == "appendAppServiceMessageWithOptions" && functionCalls(fn.decl, "persistScopeFanoutPayloads") {
			c.fail("%s must route durable payload policy through finalizeAppServiceMessageTx", formatKey(fn.key))
		}
		if fn.key.fn == "appendAppServiceMessageWithOptions" && !functionCalls(fn.decl, "finalizeAppServiceMessageTx") {
			c.fail("%s must finalize every successful append before returning", formatKey(fn.key))
		}
		if fn.key.fn == "preparePostCommitMessage" {
			if !functionCalls(fn.decl, "encodeRecoverablePostCommitDelivery") {
				c.fail("%s must encode the recoverable delivery before persistence", formatKey(fn.key))
			}
			if !functionCalls(fn.decl, "SetPayloadForMessage") {
				c.fail("%s must persist the canonical recoverable delivery in its transaction", formatKey(fn.key))
			}
		}
		if fn.key.fn == "finalizeAppServiceMessageTx" {
			if !functionCalls(fn.decl, "persistScopeFanoutPayloads") {
				c.fail("%s must persist caller-owned deliveries before returning them", formatKey(fn.key))
			}
			if !functionCalls(fn.decl, "preparePostCommitMessage") {
				c.fail("%s must route store-owned deliveries through preparePostCommitMessage", formatKey(fn.key))
			}
		}
	}
	for name := range expectedFunctions {
		if seen[name] != 1 {
			c.fail("canonical function %s must be declared exactly once (found %d)", name, seen[name])
		}
	}

	for _, expected := range []struct {
		file string
		name string
	}{
		{file: "internal/store/app/message_append.go", name: "messageAppendLifecycle"},
		{file: "internal/store/app/app_service_messages.go", name: "appServiceMessageDeliveryConfigurator"},
		{file: "internal/store/app/committed_message_fanout.go", name: "postCommitMessage"},
		{file: "internal/store/app/committed_message_fanout.go", name: "appServiceMessagePostCommitOwner"},
	} {
		count := c.countTypeDeclaration(expected.file, expected.name)
		if count != 1 {
			c.fail("canonical type %s must be declared exactly once in %s (found %d)", expected.name, expected.file, count)
		}
	}
}

func (c *checker) checkMessageAppendLifecycle() {
	calls := c.callsNamed("appendMessageToThread")
	grouped := groupCalls(calls)
	for _, site := range calls {
		expectedLifecycle, reviewed := expectedMessageAppendCallers[site.key]
		if !reviewed {
			c.fail("unreviewed appendMessageToThread production caller at %s", formatCall(site))
			continue
		}
		if len(site.call.Args) != 9 {
			c.fail("%s appendMessageToThread must pass one explicit lifecycle policy", formatCall(site))
			continue
		}
		actualLifecycle := expressionName(site.call.Args[8])
		if actualLifecycle != expectedLifecycle {
			c.fail("%s must use append lifecycle %s, got %s", formatCall(site), expectedLifecycle, actualLifecycle)
		}
	}
	for key, expectedLifecycle := range expectedMessageAppendCallers {
		if len(grouped[key]) != 1 {
			c.fail("raw message append inventory entry %s (%s) must contain exactly one appendMessageToThread call (found %d)", formatKey(key), expectedLifecycle, len(grouped[key]))
		}
	}
}

func (c *checker) checkMessageAppendResultLifecycle() {
	calls := c.callsNamed("appendMessageToThreadResult")
	grouped := groupCalls(calls)
	for _, site := range calls {
		expectedLifecycle, reviewed := expectedMessageAppendResultCallers[site.key]
		if !reviewed {
			c.fail("unreviewed appendMessageToThreadResult production caller at %s", formatCall(site))
			continue
		}
		if len(site.call.Args) != 9 {
			c.fail("%s appendMessageToThreadResult must pass one explicit lifecycle policy", formatCall(site))
			continue
		}
		actualLifecycle := expressionName(site.call.Args[8])
		if actualLifecycle != expectedLifecycle {
			c.fail("%s must use append-result lifecycle %s, got %s", formatCall(site), expectedLifecycle, actualLifecycle)
		}
	}
	for key, expectedLifecycle := range expectedMessageAppendResultCallers {
		if len(grouped[key]) != 1 {
			c.fail("raw message append-result inventory entry %s (%s) must contain exactly one appendMessageToThreadResult call (found %d)", formatKey(key), expectedLifecycle, len(grouped[key]))
		}
	}
}

func (c *checker) checkPrivateConversationRawSeamLifecycle() {
	outerKey := siteKey{file: "internal/store/privateagent/conversation_write.go", fn: "writeConversationMessageResult"}
	lowerKey := siteKey{file: "internal/store/privateagent/conversation_write.go", fn: "writeScopeMessageToConversationResult"}

	outer := c.functionByKey(outerKey)
	if outer == nil {
		c.fail("missing canonical privateagent raw writer %s", formatKey(outerKey))
	} else if !functionReturnsCallNamed(outer.decl, "writeScopeMessageToConversationResult") {
		c.fail("%s must forward the complete lower writer result", formatKey(outerKey))
	}

	lower := c.functionByKey(lowerKey)
	if lower == nil {
		c.fail("missing canonical privateagent scope writer %s", formatKey(lowerKey))
		return
	}
	applySites := groupCalls(c.callsNamed("ApplyMessagePlanTxWithSidecarWriter"))[lowerKey]
	if len(applySites) != 1 {
		c.fail("%s must contain exactly one ApplyMessagePlanTxWithSidecarWriter call (found %d)", formatKey(lowerKey), len(applySites))
		return
	}
	if len(applySites[0].assignedNames) < 2 || applySites[0].assignedNames[0] == "_" {
		c.fail("%s must retain the canonical MessageApplyResult", formatCall(applySites[0]))
	}
}

func (c *checker) checkAgentCreateMemberEventLifecycle() {
	const lifecycleFile = "internal/store/app/agent_lifecycle.go"
	addMembershipsKey := siteKey{file: lifecycleFile, fn: "addAgentChannelMembershipsInTx"}
	createInWorkspaceKey := siteKey{file: lifecycleFile, fn: "createAgentInWorkspace"}
	createForUserKey := siteKey{file: lifecycleFile, fn: "CreateAgentForUser"}
	createInTxKey := siteKey{file: lifecycleFile, fn: "CreateWorkspaceAgentInTx"}
	emitMemberEventsKey := siteKey{file: lifecycleFile, fn: "emitCreatedAgentChannelMemberEvents"}

	addMemberships := c.functionByKey(addMembershipsKey)
	if addMemberships == nil {
		c.fail("missing canonical agent membership helper %s", formatKey(addMembershipsKey))
	} else {
		if !functionReturnsSliceOfNamedType(addMemberships.decl, "ChannelMemberEventResult") {
			c.fail("%s must return every transaction-bound ChannelMemberEventResult", formatKey(addMembershipsKey))
		}
		appendSites := groupCalls(c.callsNamed("appendChannelMemberEventMessage"))[addMembershipsKey]
		if len(appendSites) != 1 {
			c.fail("%s must contain exactly one appendChannelMemberEventMessage call (found %d)", formatKey(addMembershipsKey), len(appendSites))
		} else {
			site := appendSites[0]
			if len(site.assignedNames) < 2 || site.assignedNames[0] == "_" {
				c.fail("%s must retain the ChannelMemberEventResult instead of discarding it", formatCall(site))
			} else if countAppendOfName(addMemberships.decl, site.assignedNames[0]) != 1 {
				c.fail("%s must append its retained ChannelMemberEventResult exactly once to the returned handoff slice", formatKey(addMembershipsKey))
			}
		}
	}

	createInWorkspace := c.functionByKey(createInWorkspaceKey)
	if createInWorkspace == nil {
		c.fail("missing canonical agent create helper %s", formatKey(createInWorkspaceKey))
	} else {
		if !functionReturnsSliceOfNamedType(createInWorkspace.decl, "ChannelMemberEventResult") {
			c.fail("%s must return transaction-bound ChannelMemberEventResult handoffs", formatKey(createInWorkspaceKey))
		}
		membershipSites := groupCalls(c.callsNamed("addAgentChannelMembershipsInTx"))[createInWorkspaceKey]
		if len(membershipSites) != 1 {
			c.fail("%s must call addAgentChannelMembershipsInTx exactly once (found %d)", formatKey(createInWorkspaceKey), len(membershipSites))
		} else if len(membershipSites[0].assignedNames) < 2 || membershipSites[0].assignedNames[0] == "_" {
			c.fail("%s must retain the initial membership projection handoffs", formatCall(membershipSites[0]))
		}
		if !functionReturnsNameAtIndex(createInWorkspace.decl, "memberEvents", 1) {
			c.fail("%s must return memberEvents to its transaction owner", formatKey(createInWorkspaceKey))
		}
	}

	createForUser := c.functionByKey(createForUserKey)
	if createForUser == nil {
		c.fail("missing canonical agent create entrypoint %s", formatKey(createForUserKey))
	} else {
		createSites := groupCalls(c.callsNamed("createAgentInWorkspace"))[createForUserKey]
		if len(createSites) != 1 {
			c.fail("%s must call createAgentInWorkspace exactly once (found %d)", formatKey(createForUserKey), len(createSites))
		} else if len(createSites[0].assignedNames) < 3 || createSites[0].assignedNames[1] == "_" {
			c.fail("%s must retain the channel-member projection handoffs", formatCall(createSites[0]))
		}
		emitSites := groupCalls(c.callsNamed("emitCreatedAgentChannelMemberEvents"))[createForUserKey]
		commitSites := groupCalls(c.callsNamed("Commit"))[createForUserKey]
		if len(commitSites) != 1 {
			c.fail("%s must commit exactly once before projecting initial memberships (found %d)", formatKey(createForUserKey), len(commitSites))
		}
		if len(emitSites) != 1 {
			c.fail("%s must emit initial membership projections exactly once after commit (found %d)", formatKey(createForUserKey), len(emitSites))
		} else {
			emit := emitSites[0]
			if len(emit.call.Args) != 3 || expressionName(emit.call.Args[2]) != "memberEvents" {
				c.fail("%s must emit the retained memberEvents handoff", formatCall(emit))
			}
			if len(commitSites) == 1 && emit.call.Pos() < commitSites[0].call.Pos() {
				c.fail("%s must run only after the create-agent transaction commits", formatCall(emit))
			}
		}
	}

	createInTx := c.functionByKey(createInTxKey)
	if createInTx == nil {
		c.fail("missing caller-owned agent create entrypoint %s", formatKey(createInTxKey))
	} else {
		if !functionReturnsSliceOfNamedType(createInTx.decl, "ChannelMemberEventResult") {
			c.fail("%s must expose transaction-bound ChannelMemberEventResult handoffs to its caller", formatKey(createInTxKey))
		}
		if !functionReturnsCallNamed(createInTx.decl, "createAgentInWorkspace") {
			c.fail("%s must forward the complete createAgentInWorkspace result", formatKey(createInTxKey))
		}
	}

	emitMemberEvents := c.functionByKey(emitMemberEventsKey)
	if emitMemberEvents == nil {
		c.fail("missing canonical initial membership projection helper %s", formatKey(emitMemberEventsKey))
	} else {
		for _, ordinaryCall := range []string{"DeliverCommittedMessage", "EmitCommittedHumanMessage"} {
			if functionCalls(emitMemberEvents.decl, ordinaryCall) {
				c.fail("%s must not call ordinary %s fanout", formatKey(emitMemberEventsKey), ordinaryCall)
			}
		}
	}
}

func (c *checker) checkChannelExecMemberEventLifecycle() {
	key := siteKey{file: "internal/service/channeladmin/service.go", fn: "mutateAgents"}
	fn := c.functionByKey(key)
	if fn == nil {
		c.fail("missing canonical Channel Exec member mutation entrypoint %s", formatKey(key))
		return
	}

	txSites := groupCalls(c.callsNamed("WithTx"))[key]
	emitSites := groupCalls(c.callsNamed("emitChannelMemberMutationEffects"))[key]
	if len(txSites) != 1 {
		c.fail("%s must contain exactly one transaction boundary (found %d)", formatKey(key), len(txSites))
		return
	}
	if len(emitSites) != 1 {
		c.fail("%s must emit captured member effects exactly once (found %d)", formatKey(key), len(emitSites))
		return
	}
	emit := emitSites[0]
	if len(emit.call.Args) != 5 || expressionName(emit.call.Args[4]) != "mutationEffects" {
		c.fail("%s must emit the retained mutationEffects handoff", formatCall(emit))
	}

	// The helper must follow a top-level `if err != nil { ...; return }`
	// guard after WithTx. Merely appearing outside the transaction closure is
	// insufficient: publishing before the transaction error is checked would
	// still leak a projection for a rolled-back mutation.
	tx := txSites[0]
	hasCommitSuccessGuard := false
	for _, statement := range fn.decl.Body.List {
		guard, ok := statement.(*ast.IfStmt)
		if !ok || !isIdentifierNotNil(guard.Cond, "err") {
			continue
		}
		if guard.Pos() < tx.call.End() || guard.End() > emit.call.Pos() || len(guard.Body.List) == 0 {
			continue
		}
		if _, ok := guard.Body.List[len(guard.Body.List)-1].(*ast.ReturnStmt); ok {
			hasCommitSuccessGuard = true
			break
		}
	}
	if !hasCommitSuccessGuard {
		c.fail("%s must run only after WithTx succeeds and its error guard returns on rollback", formatCall(emit))
	}
}

func (c *checker) countTypeDeclaration(filePath, typeName string) int {
	path := filepath.Join(c.root, filepath.FromSlash(filePath))
	parsed, err := parser.ParseFile(c.fset, path, nil, parser.SkipObjectResolution)
	if err != nil {
		c.fail("parse canonical file %s: %v", filePath, err)
		return 0
	}
	count := 0
	for _, declaration := range parsed.Decls {
		general, ok := declaration.(*ast.GenDecl)
		if !ok || general.Tok != token.TYPE {
			continue
		}
		for _, spec := range general.Specs {
			typeSpec, ok := spec.(*ast.TypeSpec)
			if ok && typeSpec.Name.Name == typeName {
				count++
			}
		}
	}
	return count
}

func (c *checker) checkAppServiceContractRegistry() {
	expectedByContract := make(map[string]appServiceProducer, len(expectedAppServiceProducers))
	for _, producer := range expectedAppServiceProducers {
		expectedByContract[producer.contract] = producer
	}

	var descriptor *parsedFunction
	for i := range c.functions {
		fn := &c.functions[i]
		if fn.key == (siteKey{file: "internal/store/app/committed_message_fanout.go", fn: "descriptor"}) &&
			receiverTypeName(fn.decl) == "appServiceMessageContract" {
			if descriptor != nil {
				c.fail("appServiceMessageContract.descriptor must be declared exactly once")
				continue
			}
			descriptor = fn
		}
	}
	if descriptor == nil {
		c.fail("missing canonical appServiceMessageContract.descriptor registry")
		return
	}

	seen := make(map[string]int, len(expectedByContract))
	ast.Inspect(descriptor.decl.Body, func(node ast.Node) bool {
		clause, ok := node.(*ast.CaseClause)
		if !ok || len(clause.List) == 0 {
			return true
		}
		for _, caseExpression := range clause.List {
			contract := expressionName(caseExpression)
			expected, reviewed := expectedByContract[contract]
			if !reviewed {
				c.fail("unreviewed app-service producer contract %s in %s", contract, formatKey(descriptor.key))
				continue
			}
			seen[contract]++
			returns := returnStatements(clause.Body)
			if len(returns) != 1 || len(returns[0].Results) != 3 {
				c.fail("contract %s must return exactly producer ID, post-commit owner, and true", contract)
				continue
			}
			actualProducerID := expressionName(returns[0].Results[0])
			actualOwner := expressionName(returns[0].Results[1])
			if actualProducerID != expected.producerID || actualOwner != expected.owner || !isTrue(returns[0].Results[2]) {
				c.fail(
					"contract %s must map to (%s, %s, true), got (%s, %s, %s)",
					contract,
					expected.producerID,
					expected.owner,
					actualProducerID,
					actualOwner,
					expressionName(returns[0].Results[2]),
				)
			}
		}
		return true
	})
	for contract := range expectedByContract {
		if seen[contract] != 1 {
			c.fail("app-service producer contract %s must have exactly one descriptor case (found %d)", contract, seen[contract])
		}
	}
}

func (c *checker) checkRecoverableProducerRegistry() {
	expected := map[string]bool{
		"committedMessageProducerAppNotification":                 true,
		"committedMessageProducerExternalAppRuntimeReply":         true,
		"committedMessageProducerPlatformServiceAccountBroadcast": true,
	}
	var registry *parsedFunction
	for i := range c.functions {
		fn := &c.functions[i]
		if fn.key == (siteKey{file: "internal/store/app/committed_message_fanout.go", fn: "ownsRecoverablePostCommitFanout"}) &&
			receiverTypeName(fn.decl) == "committedMessageProducerID" {
			if registry != nil {
				c.fail("committedMessageProducerID.ownsRecoverablePostCommitFanout must be declared exactly once")
				continue
			}
			registry = fn
		}
	}
	if registry == nil {
		c.fail("missing canonical committedMessageProducerID.ownsRecoverablePostCommitFanout registry")
		return
	}

	seen := make(map[string]int, len(expected))
	defaultReturnsFalse := false
	ast.Inspect(registry.decl.Body, func(node ast.Node) bool {
		clause, ok := node.(*ast.CaseClause)
		if !ok {
			return true
		}
		returns := returnStatements(clause.Body)
		if len(clause.List) == 0 {
			defaultReturnsFalse = len(returns) == 1 && len(returns[0].Results) == 1 && isFalse(returns[0].Results[0])
			return true
		}
		if len(returns) != 1 || len(returns[0].Results) != 1 || !isTrue(returns[0].Results[0]) {
			c.fail("recoverable producer registry case must return the literal true in %s", formatKey(registry.key))
		}
		for _, caseExpression := range clause.List {
			producerID := expressionName(caseExpression)
			if !expected[producerID] {
				c.fail("unreviewed recoverable post-commit producer %s in %s", producerID, formatKey(registry.key))
				continue
			}
			seen[producerID]++
		}
		return true
	})
	for producerID := range expected {
		if seen[producerID] != 1 {
			c.fail("recoverable producer %s must have exactly one registry case (found %d)", producerID, seen[producerID])
		}
	}
	if !defaultReturnsFalse {
		c.fail("recoverable producer registry must fail closed with default false")
	}
}

func (c *checker) checkPrepareProducerArguments() {
	expected := map[siteKey]string{
		{file: "internal/store/app/committed_message_fanout.go", fn: "finalizeAppServiceMessageTx"}:                      "producerID",
		{file: "internal/store/app/platform_service_account_broadcast.go", fn: "DeliverPlatformServiceAccountBroadcast"}: "committedMessageProducerPlatformServiceAccountBroadcast",
		{file: "internal/store/app/app_runtime_task_reply.go", fn: "appendAppRuntimeTaskReplyEffect"}:                    "committedMessageProducerExternalAppRuntimeReply",
	}
	for _, site := range c.callsNamed("preparePostCommitMessage") {
		producerID, reviewed := expected[site.key]
		if !reviewed {
			continue
		}
		if len(site.call.Args) != 5 {
			c.fail("%s preparePostCommitMessage must pass one explicit producer ID", formatCall(site))
			continue
		}
		actual := expressionName(site.call.Args[3])
		if actual != producerID {
			c.fail("%s must prepare as producer %s, got %s", formatCall(site), producerID, actual)
		}
	}
}

func (c *checker) checkAppServiceFinalization() {
	appendSites := c.callsNamed("appendAppServiceMessageWithOptions")
	appendByKey := groupCalls(appendSites)

	for _, site := range appendSites {
		expected, ok := expectedAppServiceProducers[site.key]
		if !ok {
			c.fail("unreviewed appendAppServiceMessageWithOptions production caller at %s", formatCall(site))
			continue
		}
		if len(site.call.Args) != 10 {
			c.fail("%s must pass one explicit App-service contract and configurator", formatCall(site))
			continue
		}
		actualContract := expressionName(site.call.Args[8])
		if actualContract != expected.contract {
			c.fail("%s must use explicit producer contract %s (owner %s), got %s", formatCall(site), expected.contract, expected.owner, actualContract)
		}
		_, configuratorIsFunction := site.call.Args[9].(*ast.FuncLit)
		if expected.requiresConfigurator && !configuratorIsFunction {
			c.fail("%s must configure its producer-specific projection before canonical finalization", formatCall(site))
		}
		if !expected.requiresConfigurator && expressionName(site.call.Args[9]) != "nil" {
			c.fail("%s must pass nil instead of an unreviewed delivery configurator", formatCall(site))
		}
		if len(site.assignedNames) < 2 {
			c.fail("%s must explicitly receive the finalized delivery and post-commit token", formatCall(site))
			continue
		}
		if expected.owner == "appServiceMessageStoreOwnedRecoverable" && site.assignedNames[1] == "_" {
			c.fail("%s store-owned producer must retain its post-commit token", formatCall(site))
		}
		if expected.owner == "appServiceMessageCallerOwned" && site.assignedNames[1] != "_" {
			c.fail("%s caller-owned producer must not retain a Store-owned post-commit token", formatCall(site))
		}
	}

	for key, producer := range expectedAppServiceProducers {
		if len(appendByKey[key]) != 1 {
			c.fail("inventory entry %s (%s, owner %s) must contain exactly one appendAppServiceMessageWithOptions call (found %d)", formatKey(key), producer.contract, producer.owner, len(appendByKey[key]))
		}
	}
}

func (c *checker) checkControlledCalls(name string, expected map[siteKey]bool) {
	actual := c.callsNamed(name)
	grouped := groupCalls(actual)
	for _, site := range actual {
		expectedClosure, ok := expected[site.key]
		if !ok {
			c.fail("unreviewed %s production caller at %s", name, formatCall(site))
			continue
		}
		if site.insideClosure != expectedClosure {
			location := "outside"
			if site.insideClosure {
				location = "inside"
			}
			want := "outside"
			if expectedClosure {
				want = "inside"
			}
			c.fail("%s is %s a function literal; inventory requires it %s the transaction callback", formatCall(site), location, want)
		}
	}
	for key := range expected {
		if len(grouped[key]) != 1 {
			c.fail("inventory entry %s must contain exactly one %s call (found %d)", formatKey(key), name, len(grouped[key]))
		}
	}
}

func (c *checker) checkControlledCallCounts(name string, expected map[siteKey]int) {
	actual := c.callsNamed(name)
	grouped := groupCalls(actual)
	for _, site := range actual {
		if _, reviewed := expected[site.key]; !reviewed {
			c.fail("unreviewed controlled production call to %s at %s", name, formatCall(site))
		}
	}
	for key, expectedCount := range expected {
		if len(grouped[key]) != expectedCount {
			c.fail("controlled production call %s inventory entry %s must contain %d call(s), found %d", name, formatKey(key), expectedCount, len(grouped[key]))
		}
	}
}

func (c *checker) checkRecoverablePolicyWrites() {
	expected := siteKey{file: "internal/store/app/committed_message_fanout.go", fn: "encodeRecoverablePostCommitDelivery"}
	writes := 0
	for _, fn := range c.functions {
		ast.Inspect(fn.decl.Body, func(node ast.Node) bool {
			switch typed := node.(type) {
			case *ast.AssignStmt:
				for index, lhs := range typed.Lhs {
					if selectorName(lhs) != "RecoverableStreamProjection" {
						continue
					}
					writes++
					if fn.key != expected {
						c.fail("RecoverableStreamProjection may only be written by encodeRecoverablePostCommitDelivery, found %s:%d:%s", fn.key.file, c.fset.Position(lhs.Pos()).Line, fn.key.fn)
					}
					if typed.Tok != token.ASSIGN || index >= len(typed.Rhs) || !isTrue(typed.Rhs[index]) {
						c.fail("RecoverableStreamProjection must be assigned the literal true at %s:%d:%s", fn.key.file, c.fset.Position(lhs.Pos()).Line, fn.key.fn)
					}
				}
			case *ast.KeyValueExpr:
				if expressionName(typed.Key) != "RecoverableStreamProjection" {
					return true
				}
				writes++
				if fn.key != expected {
					c.fail("RecoverableStreamProjection composite write may only be in encodeRecoverablePostCommitDelivery, found %s:%d:%s", fn.key.file, c.fset.Position(typed.Pos()).Line, fn.key.fn)
				}
				if !isTrue(typed.Value) {
					c.fail("RecoverableStreamProjection composite write must use the literal true at %s:%d:%s", fn.key.file, c.fset.Position(typed.Pos()).Line, fn.key.fn)
				}
			}
			return true
		})
	}
	if writes != 1 {
		c.fail("RecoverableStreamProjection must have exactly one production write in the canonical helper (found %d)", writes)
	}
}

func (c *checker) checkHandoffTokenConstruction() {
	c.checkNonEmptyCompositeConstruction(
		"postCommitMessage",
		siteKey{file: "internal/store/app/committed_message_fanout.go", fn: "preparePostCommitMessage"},
	)
}

func (c *checker) checkNonEmptyCompositeConstruction(typeName string, expected siteKey) {
	constructions := 0
	for _, fn := range c.functions {
		ast.Inspect(fn.decl.Body, func(node ast.Node) bool {
			literal, ok := node.(*ast.CompositeLit)
			if !ok || expressionName(literal.Type) != typeName || len(literal.Elts) == 0 {
				return true
			}
			constructions++
			if fn.key != expected {
				c.fail("non-empty %s token may only be constructed by %s, found %s:%d:%s", typeName, formatKey(expected), fn.key.file, c.fset.Position(literal.Pos()).Line, fn.key.fn)
			}
			return true
		})
	}
	if constructions != 1 {
		c.fail("%s must have exactly one non-empty production construction in %s (found %d)", typeName, formatKey(expected), constructions)
	}
}

func (c *checker) functionByKey(key siteKey) *parsedFunction {
	var found *parsedFunction
	for i := range c.functions {
		if c.functions[i].key != key {
			continue
		}
		if found != nil {
			c.fail("canonical function %s must be declared exactly once", formatKey(key))
			return found
		}
		found = &c.functions[i]
	}
	return found
}

func (c *checker) callsNamed(name string) []callSite {
	var result []callSite
	for _, fn := range c.functions {
		assignedByCall := make(map[*ast.CallExpr][]string)
		ast.Inspect(fn.decl.Body, func(node ast.Node) bool {
			assignment, ok := node.(*ast.AssignStmt)
			if !ok || len(assignment.Rhs) != 1 || len(assignment.Lhs) == 0 {
				return true
			}
			call, ok := assignment.Rhs[0].(*ast.CallExpr)
			if !ok {
				return true
			}
			names := make([]string, 0, len(assignment.Lhs))
			for _, lhs := range assignment.Lhs {
				if identifier, ok := lhs.(*ast.Ident); ok {
					names = append(names, identifier.Name)
				} else {
					names = append(names, expressionName(lhs))
				}
			}
			assignedByCall[call] = names
			return true
		})

		insideClosure := make(map[*ast.CallExpr]bool)
		ast.Inspect(fn.decl.Body, func(node ast.Node) bool {
			literal, ok := node.(*ast.FuncLit)
			if !ok {
				return true
			}
			ast.Inspect(literal.Body, func(inner ast.Node) bool {
				if call, ok := inner.(*ast.CallExpr); ok {
					insideClosure[call] = true
				}
				return true
			})
			return true
		})

		ast.Inspect(fn.decl.Body, func(node ast.Node) bool {
			call, ok := node.(*ast.CallExpr)
			if !ok || calledName(call) != name {
				return true
			}
			result = append(result, callSite{
				key:           fn.key,
				line:          c.fset.Position(call.Pos()).Line,
				call:          call,
				insideClosure: insideClosure[call],
				assignedNames: assignedByCall[call],
			})
			return true
		})
	}
	return result
}

func returnStatements(statements []ast.Stmt) []*ast.ReturnStmt {
	var returns []*ast.ReturnStmt
	for _, statement := range statements {
		ast.Inspect(statement, func(node ast.Node) bool {
			if returned, ok := node.(*ast.ReturnStmt); ok {
				returns = append(returns, returned)
			}
			return true
		})
	}
	return returns
}

func receiverTypeName(fn *ast.FuncDecl) string {
	if fn == nil || fn.Recv == nil || len(fn.Recv.List) != 1 {
		return ""
	}
	return expressionName(fn.Recv.List[0].Type)
}

func isTrue(expression ast.Expr) bool {
	identifier, ok := expression.(*ast.Ident)
	return ok && identifier.Name == "true"
}

func isFalse(expression ast.Expr) bool {
	identifier, ok := expression.(*ast.Ident)
	return ok && identifier.Name == "false"
}

func isIdentifierNotNil(expression ast.Expr, name string) bool {
	binary, ok := expression.(*ast.BinaryExpr)
	if !ok || binary.Op != token.NEQ {
		return false
	}
	left := expressionName(binary.X)
	right := expressionName(binary.Y)
	return (left == name && right == "nil") || (left == "nil" && right == name)
}

func groupCalls(calls []callSite) map[siteKey][]callSite {
	grouped := make(map[siteKey][]callSite)
	for _, call := range calls {
		grouped[call.key] = append(grouped[call.key], call)
	}
	return grouped
}

func functionReturnsNamedType(fn *ast.FuncDecl, typeName string) bool {
	if fn.Type.Results == nil {
		return false
	}
	for _, field := range fn.Type.Results.List {
		if expressionName(field.Type) == typeName {
			return true
		}
	}
	return false
}

func functionReturnsSliceOfNamedType(fn *ast.FuncDecl, typeName string) bool {
	if fn == nil || fn.Type == nil || fn.Type.Results == nil {
		return false
	}
	for _, field := range fn.Type.Results.List {
		array, ok := field.Type.(*ast.ArrayType)
		if ok && array.Len == nil && expressionName(array.Elt) == typeName {
			return true
		}
	}
	return false
}

func functionReturnsNameAtIndex(fn *ast.FuncDecl, name string, index int) bool {
	if fn == nil || fn.Body == nil || index < 0 {
		return false
	}
	found := false
	ast.Inspect(fn.Body, func(node ast.Node) bool {
		returned, ok := node.(*ast.ReturnStmt)
		if !ok || len(returned.Results) <= index {
			return true
		}
		if expressionName(returned.Results[index]) == name {
			found = true
			return false
		}
		return true
	})
	return found
}

func functionReturnsCallNamed(fn *ast.FuncDecl, name string) bool {
	if fn == nil || fn.Body == nil {
		return false
	}
	found := false
	ast.Inspect(fn.Body, func(node ast.Node) bool {
		returned, ok := node.(*ast.ReturnStmt)
		if !ok || len(returned.Results) != 1 {
			return true
		}
		call, ok := returned.Results[0].(*ast.CallExpr)
		if ok && calledName(call) == name {
			found = true
			return false
		}
		return true
	})
	return found
}

func countAppendOfName(fn *ast.FuncDecl, name string) int {
	if fn == nil || fn.Body == nil || strings.TrimSpace(name) == "" || name == "_" {
		return 0
	}
	count := 0
	ast.Inspect(fn.Body, func(node ast.Node) bool {
		call, ok := node.(*ast.CallExpr)
		if !ok || calledName(call) != "append" || len(call.Args) < 2 {
			return true
		}
		for _, argument := range call.Args[1:] {
			if expressionName(argument) == name {
				count++
			}
		}
		return true
	})
	return count
}

func functionLastParameterHasType(fn *ast.FuncDecl, typeName string) bool {
	if fn == nil || fn.Type == nil || fn.Type.Params == nil || len(fn.Type.Params.List) == 0 {
		return false
	}
	last := fn.Type.Params.List[len(fn.Type.Params.List)-1]
	return expressionName(last.Type) == typeName
}

func functionCalls(fn *ast.FuncDecl, callName string) bool {
	found := false
	ast.Inspect(fn.Body, func(node ast.Node) bool {
		if call, ok := node.(*ast.CallExpr); ok && calledName(call) == callName {
			found = true
		}
		return !found
	})
	return found
}

func calledName(call *ast.CallExpr) string {
	if call == nil {
		return ""
	}
	switch function := call.Fun.(type) {
	case *ast.Ident:
		return function.Name
	case *ast.SelectorExpr:
		return function.Sel.Name
	default:
		return ""
	}
}

func selectorName(expression ast.Expr) string {
	selector, ok := expression.(*ast.SelectorExpr)
	if !ok {
		return ""
	}
	return selector.Sel.Name
}

func expressionName(expression ast.Expr) string {
	switch typed := expression.(type) {
	case *ast.Ident:
		return typed.Name
	case *ast.SelectorExpr:
		return typed.Sel.Name
	case *ast.StarExpr:
		return expressionName(typed.X)
	case nil:
		return "<nil>"
	default:
		return fmt.Sprintf("%T", expression)
	}
}

func formatKey(key siteKey) string {
	return key.file + ":" + key.fn
}

func formatCall(site callSite) string {
	return fmt.Sprintf("%s:%d:%s", site.key.file, site.line, site.key.fn)
}
