package extension

import (
	"sort"

	"github.com/tidbcloud/AgentTeam/internal/extension/registry"
	"github.com/tidbcloud/AgentTeam/internal/extension/spec"
)

// projectAuthorizedRoot projects the registered Extensions into the Agent-visible
// root Catalog, filtered to ONLY the domains/actions the Agent is authorized
// for (stage 6 Agent Access). Each entry embeds the FULL Action descriptors, so
// one root GET is the complete catalog. It emits one extensionEntry PER
// authorized access entry (keyed by its Extension name) so multi-instance
// installations of the same Definition do not collapse. An access without an
// explicit Extension name is invalid and is not projected.
func projectAuthorizedRoot(extensions []registry.RegisteredExtension, authorized []AgentDomainAccess) rootResponse {
	entries := make([]extensionEntry, 0, len(authorized))
	for _, access := range authorized {
		if access.ExtensionName == "" {
			continue
		}
		ext, ok := definitionByIdentity(
			extensions,
			access.Domain,
			access.DefinitionVersion,
			access.DefinitionDigest,
		)
		if !ok {
			continue // selected Definition is not registered — fail closed
		}
		allowed := make(map[string]bool, len(access.Actions))
		for _, action := range access.Actions {
			allowed[action] = true
		}
		actions := make([]actionCatalog, 0, len(access.Actions))
		for _, action := range ext.Definition.Actions {
			if !allowed[action.Name] {
				continue
			}
			actions = append(actions, projectActionCatalog(action))
		}
		entries = append(entries, extensionEntry{
			ExtensionName: access.ExtensionName,
			DisplayName:   ext.Definition.Name,
			Actions:       actions,
		})
	}
	sort.Slice(entries, func(i, j int) bool { return entries[i].ExtensionName < entries[j].ExtensionName })
	return rootResponse{Extensions: entries}
}

// definitionByIdentity resolves the exact server-selected Definition used by
// invocation. Definition identity remains server-internal, but using it here
// keeps the content-only Catalog descriptors aligned with the authoritative
// execution contract when multiple versions of one domain are registered.
func definitionByIdentity(
	extensions []registry.RegisteredExtension,
	domain string,
	version int,
	digest string,
) (registry.RegisteredExtension, bool) {
	if domain == "" || version <= 0 || digest == "" {
		return registry.RegisteredExtension{}, false
	}
	for _, extension := range extensions {
		definition := extension.Definition
		if definition.Domain == domain &&
			definition.Version == version &&
			definition.Digest == digest {
			return extension, true
		}
	}
	return registry.RegisteredExtension{}, false
}

// projectActionCatalog projects one DefinitionAction into the full Agent-visible
// descriptor (schemas + example + safety class).
func projectActionCatalog(action registry.DefinitionAction) actionCatalog {
	return actionCatalog{
		Name:         action.Name,
		Summary:      action.Summary,
		Description:  action.Description,
		Safety:       projectSafety(action.Safety),
		InputSchema:  action.InputSchema,
		OutputSchema: action.OutputSchema,
		Example:      action.Example,
	}
}

func projectSafety(s spec.Safety) safety {
	return safety{Class: s.Class}
}

// effectiveDomains returns one Extension per domain — the highest Definition
// version registered for that domain — preserving first-seen domain order.
// It is used by registry-wide discovery helpers; authorized Catalog and invoke
// paths instead resolve the exact server-selected Definition identity.
func effectiveDomains(extensions []registry.RegisteredExtension) []registry.RegisteredExtension {
	best := make(map[string]registry.RegisteredExtension)
	order := make([]string, 0, len(extensions))
	for _, ext := range extensions {
		domain := ext.Definition.Domain
		if current, ok := best[domain]; !ok {
			order = append(order, domain)
			best[domain] = ext
			continue
		} else if ext.Definition.Version > current.Definition.Version {
			best[domain] = ext
		}
	}
	result := make([]registry.RegisteredExtension, 0, len(order))
	for _, domain := range order {
		result = append(result, best[domain])
	}
	return result
}

// latestDomain returns the effective registered Extension for the exact domain
// path identity, or ok=false if none is registered. Domain and action are path
// identities (spec §6.2): they are matched EXACTLY, with no normalization, so a
// URL-encoded trailing space (/github%20) is not the github identity and is
// rejected.
func latestDomain(extensions []registry.RegisteredExtension, domain string) (registry.RegisteredExtension, bool) {
	for _, ext := range effectiveDomains(extensions) {
		if ext.Definition.Domain == domain {
			return ext, true
		}
	}
	return registry.RegisteredExtension{}, false
}

// findAction returns the named DefinitionAction from ext, or ok=false. The
// action name is a path identity and is matched EXACTLY (spec §6.2): a
// URL-encoded trailing space (/create-issue%20) is not the create-issue
// identity and is rejected.
func findAction(ext registry.RegisteredExtension, action string) (registry.DefinitionAction, bool) {
	for _, candidate := range ext.Definition.Actions {
		if candidate.Name == action {
			return candidate, true
		}
	}
	return registry.DefinitionAction{}, false
}
