package runtime

import (
	"github.com/tidbcloud/AgentTeam/internal/knowledgeqa/protocol"

	"context"
	"errors"
	"sort"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/store/content"
	"github.com/tidbcloud/AgentTeam/internal/store/kb"
	"github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
	"gorm.io/gorm"
)

type WorkspaceAccessVerifier interface {
	HasWorkspaceAccess(context.Context, string, string) (bool, error)
}

const (
	CitationOpenModeSource   = "source"
	CitationOpenModeEvidence = "evidence"
)

type KnowledgeRootStore interface {
	ListOfficialKnowledgeBases(context.Context, string, int, int) ([]kb.KnowledgeBase, error)
	ListKnowledgeBasesByIDs(context.Context, string, []string) ([]kb.KnowledgeBase, error)
	GetKnowledgeBase(context.Context, string, string) (kb.KnowledgeBase, error)
	ListKnowledgeBaseGrantsForSubject(context.Context, string, string, string, []string) ([]kb.KnowledgeBaseGrant, error)
	ListSourcesForKnowledgeBases(context.Context, string, []string) ([]kb.Source, error)
	ListSourcesByIDs(context.Context, string, []string) ([]kb.Source, error)
	GetSource(context.Context, string, string, string) (kb.Source, error)
	ListCurrentSourceRevisions(context.Context, string, []string) ([]kb.SourceRevision, error)
	ListSourceMirrorsBySourceIDs(context.Context, string, []string) ([]kb.SourceMirror, error)
}

// ResolveCitationRoot rechecks current requester authorization for a retained
// citation, while keeping the cited revision separate from the current ready
// revision. The returned repository reference remains server-only.
func (catalog Catalog) ResolveCitationRoot(ctx context.Context, principal protocol.Principal, citation CitationMetadata) (AuthorizedRootCandidate, string, string, error) {
	principal = normalizePrincipal(principal)
	if err := validatePrincipal(principal); err != nil {
		return AuthorizedRootCandidate{}, "", "", err
	}
	citation.KnowledgeBaseID = strings.TrimSpace(citation.KnowledgeBaseID)
	citation.SourceID = strings.TrimSpace(citation.SourceID)
	citation.RootKind = strings.TrimSpace(citation.RootKind)
	citation.RepoRole = strings.TrimSpace(citation.RepoRole)
	if citation.KnowledgeBaseID == "" || !validRootKindRole(citation.RootKind, citation.RepoRole) {
		return AuthorizedRootCandidate{}, "", "", invalidRequest("Knowledge citation metadata is invalid")
	}
	if citation.RootKind == RootKindLexicon && citation.SourceID != "" {
		return AuthorizedRootCandidate{}, "", "", invalidRequest("Knowledge citation metadata is invalid")
	}
	if citation.RootKind != RootKindLexicon && citation.SourceID == "" {
		return AuthorizedRootCandidate{}, "", "", invalidRequest("Knowledge citation metadata is invalid")
	}
	if catalog.Knowledge == nil {
		return AuthorizedRootCandidate{}, "", "", repositoryFailure("Knowledge root catalog is unavailable", nil)
	}
	if catalog.WorkspaceAccess == nil {
		return AuthorizedRootCandidate{}, "", "", repositoryFailure("Knowledge root catalog is unavailable", nil)
	}
	hasWorkspaceAccess, err := catalog.WorkspaceAccess.HasWorkspaceAccess(ctx, principal.WorkspaceID, principal.RequesterID)
	if err != nil {
		return AuthorizedRootCandidate{}, "", "", err
	}
	if !hasWorkspaceAccess {
		return AuthorizedRootCandidate{}, CitationStatusForbidden, "", nil
	}
	base, err := catalog.Knowledge.GetKnowledgeBase(ctx, principal.WorkspaceID, citation.KnowledgeBaseID)
	if err != nil || base.DeletedAt != nil {
		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			return AuthorizedRootCandidate{}, "", "", err
		}
		return AuthorizedRootCandidate{}, CitationStatusSourceDeleted, "", nil
	}
	if !base.IsActive {
		return AuthorizedRootCandidate{}, CitationStatusForbidden, "", nil
	}
	var citationSource kb.Source
	if citation.SourceID != "" {
		citationSource, err = catalog.Knowledge.GetSource(ctx, principal.WorkspaceID, citation.KnowledgeBaseID, citation.SourceID)
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return AuthorizedRootCandidate{}, CitationStatusSourceDeleted, "", nil
			}
			return AuthorizedRootCandidate{}, "", "", err
		}
	}
	productAccess, err := catalog.citationProductAccess(ctx, principal, base, citation)
	if err != nil {
		return AuthorizedRootCandidate{}, "", "", err
	}
	if !productAccess.allowed {
		return AuthorizedRootCandidate{}, CitationStatusForbidden, "", nil
	}
	if citation.SourceID != "" {
		nativeRoot, native, err := catalog.resolveNativeCitationRoot(ctx, principal.WorkspaceID, citationSource, citation)
		if err != nil {
			return AuthorizedRootCandidate{}, "", "", err
		}
		if native {
			if nativeRoot.RepositoryRef == "" {
				return AuthorizedRootCandidate{}, CitationStatusRevisionUnavailable, "", nil
			}
			openMode, err := catalog.nativeCitationOpenMode(ctx, principal, base, citation.SourceID, citation.RepoRole, productAccess)
			if err != nil {
				return AuthorizedRootCandidate{}, "", "", err
			}
			if openMode == "" {
				return AuthorizedRootCandidate{}, CitationStatusForbidden, "", nil
			}
			return nativeRoot, CitationStatusAvailable, openMode, nil
		}
		repositories, err := catalog.Repositories.ListForResourceBatch(
			ctx,
			principal.WorkspaceID,
			repos.ResourceTypeKBSource,
			[]string{citation.SourceID},
		)
		if err != nil {
			return AuthorizedRootCandidate{}, "", "", err
		}
		for _, repository := range repositories {
			if repository.WorkspaceID != principal.WorkspaceID ||
				repository.ResourceType != repos.ResourceTypeKBSource ||
				repository.ResourceID != citation.SourceID ||
				repository.RepoRole != citation.RepoRole {
				continue
			}
			repositoryRef, err := NewAGSRepositoryRef(
				repository.RepoFullName,
				citation.RootKind,
				citation.RepoRole,
			)
			if err != nil {
				continue
			}
			openMode, err := catalog.citationOpenMode(
				ctx, principal, base, citation, productAccess, repository.RepoFullName,
			)
			if err != nil {
				return AuthorizedRootCandidate{}, "", "", err
			}
			if openMode == "" {
				return AuthorizedRootCandidate{}, CitationStatusForbidden, "", nil
			}
			// The current open mode has already been authorized independently of
			// revision readiness. OpenCitation probes the exact retained revision
			// and reports revision_unavailable when it can no longer be read.
			return AuthorizedRootCandidate{
				KnowledgeBaseID: citation.KnowledgeBaseID,
				SourceID:        citation.SourceID,
				RootKind:        citation.RootKind,
				RepoRole:        citation.RepoRole,
				Revision:        strings.TrimSpace(citation.Revision),
				RepositoryRef:   repositoryRef,
				DefaultRef:      strings.TrimSpace(repository.DefaultRef),
			}, CitationStatusAvailable, openMode, nil
		}
		return AuthorizedRootCandidate{}, CitationStatusRevisionUnavailable, "", nil
	}
	openMode, err := catalog.citationOpenMode(ctx, principal, base, citation, productAccess, "")
	if err != nil {
		return AuthorizedRootCandidate{}, "", "", err
	}
	if openMode == "" {
		return AuthorizedRootCandidate{}, CitationStatusForbidden, "", nil
	}
	roots, err := catalog.ResolveAuthorizedRoots(ctx, ScopeRequest{
		Principal: principal, KnowledgeBaseIDs: []string{citation.KnowledgeBaseID}, SourceIDs: optionalCitationSource(citation.SourceID),
	})
	if err != nil {
		return AuthorizedRootCandidate{}, "", "", err
	}
	for _, root := range roots {
		if root.KnowledgeBaseID == citation.KnowledgeBaseID && root.SourceID == citation.SourceID &&
			root.RootKind == citation.RootKind && root.RepoRole == citation.RepoRole {
			return root, CitationStatusAvailable, openMode, nil
		}
	}
	// Authorization was established independently of ready-root availability,
	// so an absent current repository view is an unavailable revision rather
	// than a false permission denial.
	return AuthorizedRootCandidate{}, CitationStatusRevisionUnavailable, "", nil
}

// ResolveSharedCitationRoot resolves an exact citation snapshot after the AgentTeam
// API has verified that the requester received the containing share. It never
// grants repository navigation: callers may use the returned root only for the
// original bounded evidence read. Deleted sources and missing repository views
// still fail closed.
func (catalog Catalog) ResolveSharedCitationRoot(ctx context.Context, principal protocol.Principal, citation CitationMetadata) (AuthorizedRootCandidate, string, error) {
	principal = normalizePrincipal(principal)
	if err := validatePrincipal(principal); err != nil {
		return AuthorizedRootCandidate{}, "", err
	}
	citation.KnowledgeBaseID = strings.TrimSpace(citation.KnowledgeBaseID)
	citation.SourceID = strings.TrimSpace(citation.SourceID)
	citation.RootKind = strings.TrimSpace(citation.RootKind)
	citation.RepoRole = strings.TrimSpace(citation.RepoRole)
	if citation.KnowledgeBaseID == "" || !validRootKindRole(citation.RootKind, citation.RepoRole) ||
		(citation.RootKind == RootKindLexicon && citation.SourceID != "") ||
		(citation.RootKind != RootKindLexicon && citation.SourceID == "") {
		return AuthorizedRootCandidate{}, "", invalidRequest("Knowledge citation metadata is invalid")
	}
	if catalog.Knowledge == nil || catalog.Repositories == nil {
		return AuthorizedRootCandidate{}, "", repositoryFailure("Knowledge root catalog is unavailable", nil)
	}
	base, err := catalog.Knowledge.GetKnowledgeBase(ctx, principal.WorkspaceID, citation.KnowledgeBaseID)
	if err != nil || base.DeletedAt != nil {
		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			return AuthorizedRootCandidate{}, "", err
		}
		return AuthorizedRootCandidate{}, CitationStatusSourceDeleted, nil
	}
	if citation.RootKind == RootKindLexicon {
		if catalog.Lexicons == nil {
			return AuthorizedRootCandidate{}, CitationStatusRevisionUnavailable, nil
		}
		roots, err := catalog.Lexicons.ListReadyLexiconRoots(ctx, principal.WorkspaceID, []string{citation.KnowledgeBaseID})
		if err != nil {
			return AuthorizedRootCandidate{}, "", err
		}
		for _, root := range roots {
			if strings.TrimSpace(root.KnowledgeBaseID) != citation.KnowledgeBaseID {
				continue
			}
			repositoryRef, err := NewAGSRepositoryRef(root.RepositoryFullName, citation.RootKind, citation.RepoRole)
			if err != nil {
				continue
			}
			return AuthorizedRootCandidate{
				KnowledgeBaseID: citation.KnowledgeBaseID,
				RootKind:        citation.RootKind, RepoRole: citation.RepoRole,
				Revision: strings.TrimSpace(citation.Revision), RepositoryRef: repositoryRef,
			}, CitationStatusAvailable, nil
		}
		return AuthorizedRootCandidate{}, CitationStatusRevisionUnavailable, nil
	}
	source, err := catalog.Knowledge.GetSource(ctx, principal.WorkspaceID, citation.KnowledgeBaseID, citation.SourceID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return AuthorizedRootCandidate{}, CitationStatusSourceDeleted, nil
		}
		return AuthorizedRootCandidate{}, "", err
	}
	nativeRoot, native, err := catalog.resolveNativeCitationRoot(ctx, principal.WorkspaceID, source, citation)
	if err != nil {
		return AuthorizedRootCandidate{}, "", err
	}
	if native {
		if nativeRoot.RepositoryRef == "" {
			return AuthorizedRootCandidate{}, CitationStatusRevisionUnavailable, nil
		}
		return nativeRoot, CitationStatusAvailable, nil
	}
	repositories, err := catalog.Repositories.ListForResourceBatch(
		ctx, principal.WorkspaceID, repos.ResourceTypeKBSource, []string{citation.SourceID},
	)
	if err != nil {
		return AuthorizedRootCandidate{}, "", err
	}
	for _, repository := range repositories {
		if repository.WorkspaceID != principal.WorkspaceID ||
			repository.ResourceType != repos.ResourceTypeKBSource ||
			repository.ResourceID != citation.SourceID || repository.RepoRole != citation.RepoRole {
			continue
		}
		repositoryRef, err := NewAGSRepositoryRef(repository.RepoFullName, citation.RootKind, citation.RepoRole)
		if err != nil {
			continue
		}
		return AuthorizedRootCandidate{
			KnowledgeBaseID: citation.KnowledgeBaseID, SourceID: citation.SourceID,
			RootKind: citation.RootKind, RepoRole: citation.RepoRole,
			Revision: strings.TrimSpace(citation.Revision), RepositoryRef: repositoryRef,
			DefaultRef: strings.TrimSpace(repository.DefaultRef),
		}, CitationStatusAvailable, nil
	}
	return AuthorizedRootCandidate{}, CitationStatusRevisionUnavailable, nil
}

type citationProductAuthorization struct {
	grants    []kb.KnowledgeBaseGrant
	inherited bool
	allowed   bool
}

func (catalog Catalog) citationProductAccess(
	ctx context.Context,
	principal protocol.Principal,
	base kb.KnowledgeBase,
	citation CitationMetadata,
) (citationProductAuthorization, error) {
	grants, err := catalog.Knowledge.ListKnowledgeBaseGrantsForSubject(
		ctx, principal.WorkspaceID, "user", principal.RequesterID, []string{citation.KnowledgeBaseID},
	)
	if err != nil {
		return citationProductAuthorization{}, err
	}
	if len(grants) == 0 {
		return citationProductAuthorization{
			inherited: true,
			allowed:   defaultKnowledgeRoleHasProductAccess(base),
		}, nil
	}
	for _, grant := range grants {
		if knowledgeGrantHasProductAccess(grant) {
			return citationProductAuthorization{grants: grants, allowed: true}, nil
		}
	}
	return citationProductAuthorization{grants: grants}, nil
}

// citationOpenMode distinguishes live repository browsing authority from
// hosted evidence authority. An all-members repository default does not count
// as direct access because it does not materialize AGS repository grants.
func (catalog Catalog) citationOpenMode(
	ctx context.Context,
	principal protocol.Principal,
	base kb.KnowledgeBase,
	citation CitationMetadata,
	productAccess citationProductAuthorization,
	repositoryFullName string,
) (string, error) {
	if citation.RootKind == RootKindLexicon {
		if productAccess.inherited && repositoryProfileCanBrowse(base.DefaultRepositoryAccessProfile) {
			return CitationOpenModeEvidence, nil
		}
		for _, grant := range productAccess.grants {
			if knowledgeGrantHasProductAccess(grant) && repositoryProfileCanBrowse(grant.RepositoryAccessProfile) {
				return CitationOpenModeEvidence, nil
			}
		}
		if base.AllowKnowledgeQAWithoutSourceAccess {
			return CitationOpenModeEvidence, nil
		}
		return "", nil
	}
	directAccess, err := catalog.hasDirectCitationRepositoryAccess(
		ctx, principal, repositoryFullName,
	)
	if err != nil {
		return "", err
	}
	if directAccess {
		return CitationOpenModeSource, nil
	}
	if base.AllowKnowledgeQAWithoutSourceAccess ||
		(productAccess.inherited && repositoryProfileCanBrowse(base.DefaultRepositoryAccessProfile)) {
		return CitationOpenModeEvidence, nil
	}
	return "", nil
}

func (catalog Catalog) hasDirectCitationRepositoryAccess(
	ctx context.Context,
	principal protocol.Principal,
	repositoryFullName string,
) (bool, error) {
	rows, err := catalog.Repositories.ListActiveAccessGrantsForRepositorySubject(
		ctx, principal.WorkspaceID, strings.TrimSpace(repositoryFullName), "user", principal.RequesterID,
	)
	if err != nil {
		return false, err
	}
	repositoryFullName = strings.TrimSpace(repositoryFullName)
	for _, row := range rows {
		if row.WorkspaceID != principal.WorkspaceID || strings.TrimSpace(row.RepoFullName) != repositoryFullName ||
			row.SubjectType != "user" || row.SubjectID != principal.RequesterID ||
			!row.Materialized || strings.TrimSpace(row.Status) != repos.AccessStatusCurrent {
			continue
		}
		switch strings.TrimSpace(row.Permission) {
		case repos.AccessPermissionRead,
			repos.AccessPermissionWrite,
			repos.AccessPermissionAdmin:
			return true, nil
		}
	}
	return false, nil
}

func optionalCitationSource(sourceID string) []string {
	if strings.TrimSpace(sourceID) == "" {
		return nil
	}
	return []string{strings.TrimSpace(sourceID)}
}

type KnowledgeRootRepositoryStore interface {
	ListForResourceBatch(context.Context, string, string, []string) ([]repos.ResourceRepository, error)
	ListAccessGrantsByIntentIDs(context.Context, string, string, []string) ([]repos.ResourceRepositoryAccessGrant, error)
	ListActiveAccessGrantsForRepositorySubject(context.Context, string, string, string, string) ([]repos.ResourceRepositoryAccessGrant, error)
}

type KnowledgeContentAuthority interface {
	GetAuthority(context.Context, string, string, string) (content.Authority, bool, error)
	ListAuthoritiesForBindings(context.Context, string, string, []string) (map[string]content.Authority, error)
}

type nativeKnowledgeRootStore interface {
	ListSourceContentMappingsForRevisions(context.Context, string, []string) ([]kb.SourceContentMapping, error)
	GetSourceContentMappingByVersion(context.Context, string, string, string) (kb.SourceContentMapping, error)
}

type nativeKnowledgeAccessStore interface {
	ListKnowledgeBaseSourceRepositoryAccessOverridesForSources(context.Context, string, []string, []string) ([]kb.KnowledgeBaseSourceRepositoryAccessOverride, error)
}

type ReadyLexiconRoot struct {
	KnowledgeBaseID    string
	RepositoryFullName string
	Revision           string
	FreshnessStatus    string
}

// LexiconRootSource is optional until a deployment enables Knowledge Entry
// publication. It must return only ready immutable lexicon revisions for the
// supplied, already-authorized KB IDs.
type LexiconRootSource interface {
	ListReadyLexiconRoots(context.Context, string, []string) ([]ReadyLexiconRoot, error)
}

// Catalog expands a user-visible KB/Source scope into layered server-only
// immutable roots. Missing authority is explicit legacy ownership; native
// authority emits only the mapped ContentVersion and never an AGS fallback. It
// rechecks current workspace membership, KB grants, and either direct source
// access or the narrow manager-enabled Q&A policy on every turn.
type Catalog struct {
	Knowledge        KnowledgeRootStore
	Repositories     KnowledgeRootRepositoryStore
	WorkspaceAccess  WorkspaceAccessVerifier
	Lexicons         LexiconRootSource
	ContentAuthority KnowledgeContentAuthority
}

const knowledgeRootCatalogPageSize = 500

func (catalog Catalog) ResolveAuthorizedRoots(ctx context.Context, request ScopeRequest) ([]AuthorizedRootCandidate, error) {
	resolution, err := catalog.ResolveAuthorizedScope(ctx, request)
	return resolution.Roots, err
}

// ResolveAuthorizedScope expands the current requester's scope and reports
// readiness only for objects that passed the same authorization fence. The
// diagnostics let the hosted worker disclose partial ready-root coverage
// without revealing inaccessible object names or counts.
func (catalog Catalog) ResolveAuthorizedScope(ctx context.Context, request ScopeRequest) (AuthorizedScopeResolution, error) {
	request.Principal = normalizePrincipal(request.Principal)
	if err := validatePrincipal(request.Principal); err != nil {
		return AuthorizedScopeResolution{}, err
	}
	if catalog.Knowledge == nil || catalog.Repositories == nil || catalog.WorkspaceAccess == nil {
		return AuthorizedScopeResolution{}, repositoryFailure("Knowledge root catalog is unavailable", nil)
	}
	hasWorkspaceAccess, err := catalog.WorkspaceAccess.HasWorkspaceAccess(ctx, request.Principal.WorkspaceID, request.Principal.RequesterID)
	if err != nil {
		return AuthorizedScopeResolution{}, err
	}
	if !hasWorkspaceAccess {
		// Deliberately return an empty set instead of disclosing whether any
		// requested KB or Source exists in this Workspace.
		return AuthorizedScopeResolution{Roots: []AuthorizedRootCandidate{}}, nil
	}

	selectedKBIDs := normalizeUniqueStrings(request.KnowledgeBaseIDs)
	selectedSourceIDs := normalizeUniqueStrings(request.SourceIDs)
	selectedKBs := stringSet(selectedKBIDs)
	selectedSources := stringSet(selectedSourceIDs)
	bases, requestedSources, err := catalog.loadRequestedScope(
		ctx, request.Principal.WorkspaceID, selectedKBIDs, selectedSourceIDs,
	)
	if err != nil {
		return AuthorizedScopeResolution{}, err
	}
	activeBases := make([]kb.KnowledgeBase, 0, len(bases))
	baseIDs := make([]string, 0, len(bases))
	for _, base := range bases {
		if base.WorkspaceID != request.Principal.WorkspaceID || !base.IsActive || base.DeletedAt != nil {
			continue
		}
		if len(selectedKBs) > 0 {
			if _, ok := selectedKBs[base.ID]; !ok {
				continue
			}
		}
		activeBases = append(activeBases, base)
		baseIDs = append(baseIDs, base.ID)
	}
	if len(baseIDs) == 0 {
		return AuthorizedScopeResolution{Roots: []AuthorizedRootCandidate{}}, nil
	}

	grants, err := catalog.Knowledge.ListKnowledgeBaseGrantsForSubject(
		ctx,
		request.Principal.WorkspaceID,
		"user",
		request.Principal.RequesterID,
		baseIDs,
	)
	if err != nil {
		return AuthorizedScopeResolution{}, err
	}
	grantByKB := make(map[string]kb.KnowledgeBaseGrant, len(grants))
	explicitGrantByKB := make(map[string]struct{}, len(grants))
	grantIDs := make([]string, 0, len(grants))
	for _, grant := range grants {
		explicitGrantByKB[grant.KnowledgeBaseID] = struct{}{}
		if !knowledgeGrantHasProductAccess(grant) {
			continue
		}
		grantByKB[grant.KnowledgeBaseID] = grant
		grantIDs = append(grantIDs, grant.ID)
	}
	authorizedBases := make([]kb.KnowledgeBase, 0, len(activeBases))
	defaultAccessByKB := make(map[string]bool, len(activeBases))
	allowWithoutSourceAccessByKB := make(map[string]bool, len(activeBases))
	for _, base := range activeBases {
		if _, ok := grantByKB[base.ID]; ok {
			authorizedBases = append(authorizedBases, base)
			allowWithoutSourceAccessByKB[base.ID] = base.AllowKnowledgeQAWithoutSourceAccess
			continue
		}
		if _, hasExplicitGrant := explicitGrantByKB[base.ID]; hasExplicitGrant {
			continue
		}
		if defaultKnowledgeAccessCanBrowse(base) {
			authorizedBases = append(authorizedBases, base)
			defaultAccessByKB[base.ID] = true
			allowWithoutSourceAccessByKB[base.ID] = base.AllowKnowledgeQAWithoutSourceAccess
		}
	}
	if len(authorizedBases) == 0 {
		return AuthorizedScopeResolution{Roots: []AuthorizedRootCandidate{}}, nil
	}
	authorizedKBIDs := make([]string, 0, len(authorizedBases))
	for _, base := range authorizedBases {
		authorizedKBIDs = append(authorizedKBIDs, base.ID)
	}

	accessRows, err := catalog.Repositories.ListAccessGrantsByIntentIDs(
		ctx,
		request.Principal.WorkspaceID,
		repos.AccessIntentTypeKBGrant,
		grantIDs,
	)
	if err != nil {
		return AuthorizedScopeResolution{}, err
	}
	currentAccess := make(map[string]struct{}, len(accessRows))
	currentSourceAccess := make(map[string]struct{}, len(accessRows))
	for _, row := range accessRows {
		if row.SubjectType != "user" || row.SubjectID != request.Principal.RequesterID ||
			row.ResourceType != repos.ResourceTypeKBSource ||
			!row.Materialized || strings.TrimSpace(row.Status) != repos.AccessStatusCurrent {
			continue
		}
		currentAccess[repositoryAccessKey(row.IntentID, row.ResourceID, row.RepoRole)] = struct{}{}
		currentSourceAccess[row.ResourceID] = struct{}{}
	}

	candidates := make([]AuthorizedRootCandidate, 0)
	includeLexicons := len(selectedKBs) > 0 || len(selectedSources) == 0
	if includeLexicons && catalog.Lexicons != nil {
		lexicons, err := catalog.Lexicons.ListReadyLexiconRoots(ctx, request.Principal.WorkspaceID, authorizedKBIDs)
		if err != nil {
			return AuthorizedScopeResolution{}, err
		}
		authorizedKBSet := stringSet(authorizedKBIDs)
		for _, lexicon := range lexicons {
			if _, ok := authorizedKBSet[strings.TrimSpace(lexicon.KnowledgeBaseID)]; !ok {
				continue
			}
			repositoryRef, err := NewAGSRepositoryRef(lexicon.RepositoryFullName, RootKindLexicon, RepoRoleWiki)
			if err != nil || strings.TrimSpace(lexicon.Revision) == "" {
				continue
			}
			candidates = append(candidates, AuthorizedRootCandidate{
				KnowledgeBaseID: strings.TrimSpace(lexicon.KnowledgeBaseID),
				RootKind:        RootKindLexicon,
				RepoRole:        RepoRoleWiki,
				Revision:        strings.TrimSpace(lexicon.Revision),
				RepositoryRef:   repositoryRef,
				FreshnessStatus: strings.TrimSpace(lexicon.FreshnessStatus),
			})
		}
	}

	sources := requestedSources
	if len(selectedSourceIDs) == 0 {
		sources, err = catalog.Knowledge.ListSourcesForKnowledgeBases(ctx, request.Principal.WorkspaceID, authorizedKBIDs)
		if err != nil {
			return AuthorizedScopeResolution{}, err
		}
	}
	filteredSources := make([]kb.Source, 0, len(sources))
	sourceIDs := make([]string, 0, len(sources))
	knowledgeBaseBySource := make(map[string]string, len(sources))
	authorizedKBSet := stringSet(authorizedKBIDs)
	for _, source := range sources {
		if source.WorkspaceID != request.Principal.WorkspaceID || source.DeletedAt != nil {
			continue
		}
		if _, ok := authorizedKBSet[source.KnowledgeBaseID]; !ok {
			continue
		}
		if len(selectedSources) > 0 {
			if _, ok := selectedSources[source.ID]; !ok {
				continue
			}
		}
		filteredSources = append(filteredSources, source)
		sourceIDs = append(sourceIDs, source.ID)
		knowledgeBaseBySource[source.ID] = source.KnowledgeBaseID
	}
	if len(sourceIDs) == 0 {
		sortRootCandidates(candidates)
		return scopeResolution(authorizedKBIDs, nil, nil, candidates), nil
	}
	repositories, err := catalog.Repositories.ListForResourceBatch(
		ctx,
		request.Principal.WorkspaceID,
		repos.ResourceTypeKBSource,
		sourceIDs,
	)
	if err != nil {
		return AuthorizedScopeResolution{}, err
	}
	repositoriesBySource := make(map[string][]repos.ResourceRepository, len(sourceIDs))
	sourceIDSet := stringSet(sourceIDs)
	for _, repository := range repositories {
		if repository.WorkspaceID != request.Principal.WorkspaceID ||
			repository.ResourceType != repos.ResourceTypeKBSource {
			continue
		}
		repositoriesBySource[repository.ResourceID] = append(repositoriesBySource[repository.ResourceID], repository)
	}
	revisions, err := catalog.Knowledge.ListCurrentSourceRevisions(ctx, request.Principal.WorkspaceID, sourceIDs)
	if err != nil {
		return AuthorizedScopeResolution{}, err
	}
	revisionsBySource := make(map[string]kb.SourceRevision, len(revisions))
	revisionIDs := make([]string, 0, len(revisions))
	for _, revision := range revisions {
		if revision.WorkspaceID != request.Principal.WorkspaceID ||
			revision.KnowledgeBaseID != knowledgeBaseBySource[revision.SourceID] {
			continue
		}
		if _, ok := sourceIDSet[revision.SourceID]; !ok {
			continue
		}
		if _, exists := revisionsBySource[revision.SourceID]; !exists {
			revisionsBySource[revision.SourceID] = revision
			revisionIDs = append(revisionIDs, revision.ID)
		}
	}
	authorities, mappingsByRevision, err := catalog.nativeSourceState(ctx, request.Principal.WorkspaceID, sourceIDs, revisionIDs)
	if err != nil {
		return AuthorizedScopeResolution{}, err
	}
	overrideProfiles := make(map[string]string)
	hasNativeAuthority := false
	for _, authority := range authorities {
		if strings.TrimSpace(authority.State) == content.AuthorityStateNativeActive {
			hasNativeAuthority = true
			break
		}
	}
	if hasNativeAuthority && len(grantIDs) > 0 {
		nativeStore, ok := catalog.Knowledge.(nativeKnowledgeAccessStore)
		if !ok {
			return AuthorizedScopeResolution{}, repositoryFailure("native Knowledge access is unavailable", nil)
		}
		overrideRows, err := nativeStore.ListKnowledgeBaseSourceRepositoryAccessOverridesForSources(ctx, request.Principal.WorkspaceID, grantIDs, sourceIDs)
		if err != nil {
			return AuthorizedScopeResolution{}, err
		}
		overrideProfiles = make(map[string]string, len(overrideRows))
		for _, row := range overrideRows {
			grant, exists := grantByKB[row.KnowledgeBaseID]
			if !exists || grant.ID != row.GrantID || grant.SubjectType != row.SubjectType || grant.SubjectID != row.SubjectID {
				return AuthorizedScopeResolution{}, repositoryFailure("native Knowledge access override is inconsistent", nil)
			}
			overrideProfiles[repositoryAccessKey(row.GrantID, row.SourceID, row.RepoRole)] = kb.NormalizeKnowledgeBaseRepositoryAccessProfile(row.RepositoryAccessProfile)
		}
	}
	mirrors, err := catalog.Knowledge.ListSourceMirrorsBySourceIDs(ctx, request.Principal.WorkspaceID, sourceIDs)
	if err != nil {
		return AuthorizedScopeResolution{}, err
	}
	mirrorsBySource := make(map[string]kb.SourceMirror, len(mirrors))
	for _, mirror := range mirrors {
		if mirror.WorkspaceID != request.Principal.WorkspaceID ||
			mirror.KnowledgeBaseID != knowledgeBaseBySource[mirror.SourceID] {
			continue
		}
		if _, ok := sourceIDSet[mirror.SourceID]; !ok {
			continue
		}
		mirrorsBySource[mirror.SourceID] = mirror
	}

	authorizedSourceIDs := make([]string, 0, len(filteredSources))
	unavailableSourceIDs := make([]string, 0)
	partiallyAvailableSourceIDs := make([]string, 0)
	for _, source := range filteredSources {
		grant, hasGrant := grantByKB[source.KnowledgeBaseID]
		if !hasGrant && !defaultAccessByKB[source.KnowledgeBaseID] {
			continue
		}
		allowWithoutSourceAccess := allowWithoutSourceAccessByKB[source.KnowledgeBaseID]
		authority, authorityExists := authorities[source.ID]
		if authorityExists && strings.TrimSpace(authority.State) == content.AuthorityStateNativeActive {
			revision := revisionsBySource[source.ID]
			mapping := mappingsByRevision[revision.ID]
			if hasGrant && !allowWithoutSourceAccess {
				profile := kb.NormalizeKnowledgeBaseRepositoryAccessProfile(grant.RepositoryAccessProfile)
				if override, exists := overrideProfiles[repositoryAccessKey(grant.ID, source.ID, mapping.RepositoryRole)]; exists {
					profile = override
				}
				if !repositoryProfileCanBrowse(profile) {
					continue
				}
			}
			authorizedSourceIDs = append(authorizedSourceIDs, source.ID)
			candidate, candidateErr := nativeSourceRoot(source, revision, mapping)
			if candidateErr != nil {
				unavailableSourceIDs = append(unavailableSourceIDs, source.ID)
				continue
			}
			candidates = append(candidates, candidate)
			if mapping.SearchProjectionTruncated {
				partiallyAvailableSourceIDs = append(partiallyAvailableSourceIDs, source.ID)
			}
			continue
		}
		if authorityExists && strings.TrimSpace(authority.State) != content.AuthorityStateLegacyActive {
			return AuthorizedScopeResolution{}, repositoryFailure("Knowledge source authority is invalid", nil)
		}
		if hasGrant && !allowWithoutSourceAccess {
			if _, ok := currentSourceAccess[source.ID]; !ok {
				continue
			}
		}
		eligibleRepositories := make([]repos.ResourceRepository, 0, len(repositoriesBySource[source.ID]))
		for _, repository := range repositoriesBySource[source.ID] {
			if !validSourceRepositoryRole(repository.RepoRole) {
				continue
			}
			if hasGrant && !allowWithoutSourceAccess {
				if _, ok := currentAccess[repositoryAccessKey(grant.ID, source.ID, repository.RepoRole)]; !ok {
					continue
				}
			}
			eligibleRepositories = append(eligibleRepositories, repository)
		}
		authorizedSourceIDs = append(authorizedSourceIDs, source.ID)
		revision := revisionsBySource[source.ID]
		mirror := mirrorsBySource[source.ID]
		readyRoots := 0
		for _, repository := range eligibleRepositories {
			rootKind, rootRevision, freshness := readySourceRoot(source, revision, mirror, repository.RepoRole)
			if rootKind == "" || rootRevision == "" {
				continue
			}
			repositoryRef, err := NewAGSRepositoryRef(repository.RepoFullName, rootKind, repository.RepoRole)
			if err != nil {
				continue
			}
			candidates = append(candidates, AuthorizedRootCandidate{
				KnowledgeBaseID: source.KnowledgeBaseID,
				SourceID:        source.ID,
				SourceName:      strings.TrimSpace(source.Name),
				RootKind:        rootKind,
				RepoRole:        repository.RepoRole,
				Revision:        rootRevision,
				RepositoryRef:   repositoryRef,
				DefaultRef:      sourceRootDefaultRef(source, mirror, repository),
				FreshnessStatus: freshness,
			})
			readyRoots++
		}
		switch {
		case readyRoots == 0:
			unavailableSourceIDs = append(unavailableSourceIDs, source.ID)
		case readyRoots < len(eligibleRepositories):
			partiallyAvailableSourceIDs = append(partiallyAvailableSourceIDs, source.ID)
		}
	}
	sortRootCandidates(candidates)
	return scopeResolution(
		authorizedKBIDs,
		authorizedSourceIDs,
		append(unavailableSourceIDs, partiallyAvailableSourceIDs...),
		candidates,
	), nil
}

func sourceRootDefaultRef(source kb.Source, mirror kb.SourceMirror, repository repos.ResourceRepository) string {
	switch strings.TrimSpace(repository.RepoRole) {
	case repos.RepoRoleMirror:
		return firstNonEmptyKnowledgeRef(mirror.DefaultRef, repository.DefaultRef, source.DefaultRef)
	case repos.RepoRolePrimary:
		return firstNonEmptyKnowledgeRef(repository.DefaultRef, source.DefaultRef)
	default:
		return ""
	}
}

func firstNonEmptyKnowledgeRef(values ...string) string {
	for _, value := range values {
		if value = strings.TrimSpace(value); value != "" {
			return value
		}
	}
	return ""
}

func scopeResolution(
	authorizedKnowledgeBaseIDs []string,
	authorizedSourceIDs []string,
	incompleteSourceIDs []string,
	roots []AuthorizedRootCandidate,
) AuthorizedScopeResolution {
	readyKnowledgeBases := make(map[string]struct{}, len(roots))
	readySources := make(map[string]struct{}, len(roots))
	for _, root := range roots {
		readyKnowledgeBases[root.KnowledgeBaseID] = struct{}{}
		if root.SourceID != "" {
			readySources[root.SourceID] = struct{}{}
		}
	}
	unavailableKnowledgeBases := 0
	for _, knowledgeBaseID := range authorizedKnowledgeBaseIDs {
		if _, ok := readyKnowledgeBases[knowledgeBaseID]; !ok {
			unavailableKnowledgeBases++
		}
	}
	unavailableSources := 0
	partiallyAvailableSources := 0
	for _, sourceID := range incompleteSourceIDs {
		if _, ok := readySources[sourceID]; ok {
			partiallyAvailableSources++
		} else {
			unavailableSources++
		}
	}
	return AuthorizedScopeResolution{
		Roots: roots,
		Diagnostics: ScopeDiagnostics{
			AuthorizedKnowledgeBaseCount:  len(authorizedKnowledgeBaseIDs),
			AuthorizedSourceCount:         len(authorizedSourceIDs),
			UnavailableKnowledgeBaseCount: unavailableKnowledgeBases,
			UnavailableSourceCount:        unavailableSources,
			PartiallyAvailableSourceCount: partiallyAvailableSources,
		},
	}
}

// loadRequestedScope batch-loads explicit selections. Workspace-wide paging is
// reserved for the explicit empty-selection meaning of "all current scope".
func (catalog Catalog) loadRequestedScope(ctx context.Context, workspaceID string, knowledgeBaseIDs, sourceIDs []string) ([]kb.KnowledgeBase, []kb.Source, error) {
	if len(knowledgeBaseIDs) == 0 && len(sourceIDs) == 0 {
		bases, err := catalog.listAllKnowledgeBases(ctx, workspaceID)
		return bases, nil, err
	}

	var sources []kb.Source
	var err error
	if len(sourceIDs) > 0 {
		sources, err = catalog.Knowledge.ListSourcesByIDs(ctx, workspaceID, sourceIDs)
		if err != nil {
			return nil, nil, err
		}
	}
	baseIDs := knowledgeBaseIDs
	if len(baseIDs) == 0 {
		baseIDs = make([]string, 0, len(sources))
		for _, source := range sources {
			baseIDs = append(baseIDs, source.KnowledgeBaseID)
		}
	}
	bases, err := catalog.Knowledge.ListKnowledgeBasesByIDs(ctx, workspaceID, baseIDs)
	if err != nil {
		return nil, nil, err
	}
	return bases, sources, nil
}

func validSourceRepositoryRole(repoRole string) bool {
	switch strings.TrimSpace(repoRole) {
	case repos.RepoRoleInsights,
		repos.RepoRolePrimary,
		repos.RepoRoleMirror:
		return true
	default:
		return false
	}
}

func (catalog Catalog) listAllKnowledgeBases(ctx context.Context, workspaceID string) ([]kb.KnowledgeBase, error) {
	result := make([]kb.KnowledgeBase, 0, knowledgeRootCatalogPageSize)
	for page := 1; ; page++ {
		items, err := catalog.Knowledge.ListOfficialKnowledgeBases(ctx, workspaceID, page, knowledgeRootCatalogPageSize)
		if err != nil {
			return nil, err
		}
		result = append(result, items...)
		if len(items) < knowledgeRootCatalogPageSize {
			return result, nil
		}
	}
}

func readySourceRoot(source kb.Source, revision kb.SourceRevision, mirror kb.SourceMirror, repoRole string) (rootKind, rootRevision, freshness string) {
	freshness = strings.TrimSpace(revision.FreshnessStatus)
	if freshness == "" {
		freshness = strings.TrimSpace(source.FreshnessStatus)
	}
	switch strings.TrimSpace(repoRole) {
	case repos.RepoRoleInsights:
		// PublishedWikiRevision is the immutable commit of the AGS companion
		// wiki repository that the local primitive will read.
		return RootKindInsights, strings.TrimSpace(revision.PublishedWikiRevision), freshness
	case repos.RepoRolePrimary:
		return RootKindPrimary, strings.TrimSpace(revision.SourceCommitSHA), freshness
	case repos.RepoRoleMirror:
		if mirror.LastSuccessAt == nil || strings.TrimSpace(mirror.LastMirroredCommit) == "" {
			return "", "", ""
		}
		mirrorFreshness := "stale"
		if strings.TrimSpace(mirror.SyncStatus) == kb.SourceMirrorSyncStatusSuccess {
			mirrorFreshness = "current"
		}
		return RootKindPrimary, strings.TrimSpace(mirror.LastMirroredCommit), mirrorFreshness
	default:
		return "", "", ""
	}
}

func knowledgeGrantHasProductAccess(grant kb.KnowledgeBaseGrant) bool {
	if strings.TrimSpace(grant.ID) == "" || strings.TrimSpace(grant.KnowledgeBaseID) == "" ||
		strings.TrimSpace(grant.Role) == "" || strings.TrimSpace(grant.Role) == "none" {
		return false
	}
	return true
}

func defaultKnowledgeAccessCanBrowse(base kb.KnowledgeBase) bool {
	if !defaultKnowledgeRoleHasProductAccess(base) {
		return false
	}
	return base.AllowKnowledgeQAWithoutSourceAccess || repositoryProfileCanBrowse(base.DefaultRepositoryAccessProfile)
}

func defaultKnowledgeRoleHasProductAccess(base kb.KnowledgeBase) bool {
	switch strings.TrimSpace(base.DefaultWorkspaceMemberRole) {
	case "view", "edit", "manage":
		return true
	default:
		return false
	}
}

func repositoryProfileCanBrowse(profile string) bool {
	switch kb.NormalizeKnowledgeBaseRepositoryAccessProfile(profile) {
	case kb.KnowledgeBaseRepositoryAccessProfileRead,
		kb.KnowledgeBaseRepositoryAccessProfileWrite,
		kb.KnowledgeBaseRepositoryAccessProfileManage:
		return true
	default:
		return false
	}
}

func repositoryAccessKey(intentID, sourceID, repoRole string) string {
	return strings.TrimSpace(intentID) + "\x00" + strings.TrimSpace(sourceID) + "\x00" + strings.TrimSpace(repoRole)
}

func stringSet(input []string) map[string]struct{} {
	result := make(map[string]struct{}, len(input))
	for _, value := range input {
		result[value] = struct{}{}
	}
	return result
}

func sortRootCandidates(candidates []AuthorizedRootCandidate) {
	rootOrder := func(rootKind string) int {
		switch rootKind {
		case RootKindLexicon:
			return 0
		case RootKindInsights:
			return 1
		case RootKindPrimary:
			return 2
		default:
			return 3
		}
	}
	sort.SliceStable(candidates, func(i, j int) bool {
		if candidates[i].KnowledgeBaseID != candidates[j].KnowledgeBaseID {
			return candidates[i].KnowledgeBaseID < candidates[j].KnowledgeBaseID
		}
		if rootOrder(candidates[i].RootKind) != rootOrder(candidates[j].RootKind) {
			return rootOrder(candidates[i].RootKind) < rootOrder(candidates[j].RootKind)
		}
		if candidates[i].SourceID != candidates[j].SourceID {
			return candidates[i].SourceID < candidates[j].SourceID
		}
		return candidates[i].RepoRole < candidates[j].RepoRole
	})
}
