package sfcrmappservice

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/url"
	"sort"
	"strings"

	crmwire "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/wire"
	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
)

const (
	appPageLayoutComponentKind = "metadata.pack.page-layout.v1"
	appRecordTypeComponentKind = "metadata.pack.record-type.v1"
	masterRecordTypeID         = "012000000000000AAA"
)

type objectLayoutsRoute struct {
	version      string
	objectID     string
	recordTypeID string
}

type appPageLayout struct {
	ID                    string                 `json:"id"`
	ObjectAPIName         string                 `json:"objectApiName"`
	LayoutName            string                 `json:"layoutName"`
	Type                  string                 `json:"type,omitempty"`
	IsDefault             bool                   `json:"isDefault,omitempty"`
	CompactLayout         []string               `json:"compactLayout"`
	Tabs                  []string               `json:"tabs,omitempty"`
	Sections              []appPageLayoutSection `json:"sections"`
	RelatedLists          []appRelatedList       `json:"relatedLists"`
	RecordPanels          []appRecordPanel       `json:"recordPanels,omitempty"`
	HeaderActions         []appLayoutAction      `json:"headerActions"`
	QuickActions          []appLayoutAction      `json:"quickActions"`
	HeaderActionsAuthored bool                   `json:"headerActionsAuthored,omitempty"`
	QuickActionsAuthored  bool                   `json:"quickActionsAuthored,omitempty"`
	RecordStates          []appRecordStateRule   `json:"recordStates,omitempty"`
	PrimaryHeaderActions  []string               `json:"primaryHeaderActions,omitempty"`
}

type appPageLayoutSection struct {
	ID                  string                       `json:"id"`
	Label               string                       `json:"label"`
	Tab                 string                       `json:"tab,omitempty"`
	Type                string                       `json:"type,omitempty"`
	ComponentName       string                       `json:"componentName,omitempty"`
	ComponentConfig     map[string]any               `json:"componentConfig,omitempty"`
	Columns             int                          `json:"columns"`
	Fields              []string                     `json:"fields"`
	FieldLabels         map[string]string            `json:"fieldLabels,omitempty"`
	FieldBehaviors      map[string]map[string]string `json:"fieldBehaviors,omitempty"`
	VisibilityCondition string                       `json:"visibilityCondition,omitempty"`
}

type appRelatedList struct {
	ID              string                    `json:"id"`
	Label           string                    `json:"label"`
	SecondaryLabel  string                    `json:"secondaryLabel,omitempty"`
	ObjectAPIName   string                    `json:"objectApiName"`
	LookupField     string                    `json:"lookupField"`
	Fields          []string                  `json:"fields"`
	PrefillMappings map[string]string         `json:"prefillMappings,omitempty"`
	Resolution      *appRelatedListResolution `json:"resolution,omitempty"`
}

type appRelatedListResolution struct {
	Query  appRelatedListQueryTarget  `json:"query"`
	Detail appRelatedListDetailTarget `json:"detail"`
	List   appRelatedListListTarget   `json:"list"`
	Create appRelatedListCreateTarget `json:"create"`
}

type appRelatedListQueryTarget struct {
	ObjectAPIName      string `json:"objectApiName"`
	LookupField        string `json:"lookupField"`
	ParentFieldAPIName string `json:"parentFieldApiName,omitempty"`
}

type appRelatedListDetailTarget struct {
	ObjectAPIName string `json:"objectApiName"`
}

type appRelatedListListTarget struct {
	ObjectAPIName      string `json:"objectApiName"`
	FilterField        string `json:"filterField"`
	ParentFieldAPIName string `json:"parentFieldApiName,omitempty"`
}

type appRelatedListCreateTarget struct {
	Kind            string            `json:"kind"`
	ObjectAPIName   string            `json:"objectApiName,omitempty"`
	PrefillMappings map[string]string `json:"prefillMappings,omitempty"`
}

type appLayoutAction struct {
	Name                string         `json:"name"`
	Label               string         `json:"label"`
	Type                string         `json:"type"`
	Icon                string         `json:"icon,omitempty"`
	TargetObject        string         `json:"targetObject,omitempty"`
	Config              map[string]any `json:"config,omitempty"`
	VisibilityCondition string         `json:"visibilityCondition,omitempty"`
	Component           string         `json:"component,omitempty"`
}

type appRecordStateBanner struct {
	Severity string   `json:"severity"`
	Title    string   `json:"title"`
	Body     string   `json:"body,omitempty"`
	LinkIDs  []string `json:"linkIds,omitempty"`
}

type appRecordStateLink struct {
	ID                    string `json:"id"`
	Label                 string `json:"label"`
	ReferenceFieldAPIName string `json:"referenceFieldApiName"`
	ObjectAPIName         string `json:"objectApiName,omitempty"`
	Placement             string `json:"placement,omitempty"`
}

type appRecordStateActionOverride struct {
	ActionKey string `json:"actionKey"`
	Mode      string `json:"mode"`
	Reason    string `json:"reason,omitempty"`
}

type appRecordStateReadOnly struct {
	Enabled bool   `json:"enabled"`
	Reason  string `json:"reason,omitempty"`
}

type appRecordStateRule struct {
	ID              string                         `json:"id"`
	When            string                         `json:"when"`
	Priority        int                            `json:"priority,omitempty"`
	Banner          *appRecordStateBanner          `json:"banner,omitempty"`
	Links           []appRecordStateLink           `json:"links,omitempty"`
	ActionOverrides []appRecordStateActionOverride `json:"actionOverrides,omitempty"`
	ReadOnly        *appRecordStateReadOnly        `json:"readOnly,omitempty"`
}

// appRecordPanel is the AppService-owned wire projection of an app-authored
// record-page panel. Config remains opaque to sfcrm: the frontend renderer
// identified by RendererID owns its schema and interpretation.
type appRecordPanel struct {
	ID                  string         `json:"id"`
	RendererID          string         `json:"rendererId"`
	Config              map[string]any `json:"config,omitempty"`
	Position            string         `json:"position,omitempty"`
	TabID               string         `json:"tabId,omitempty"`
	TabLabel            string         `json:"tabLabel,omitempty"`
	Order               int            `json:"order,omitempty"`
	VisibilityCondition string         `json:"visibilityCondition,omitempty"`
}

type appRecordTypeDocument struct {
	RecordTypes []appRecordTypeGroup `json:"recordTypes"`
}

type appRecordTypeGroup struct {
	ObjectAPIName string                `json:"objectApiName"`
	Types         []appRecordTypeChoice `json:"types"`
}

type appRecordTypeChoice struct {
	Name string `json:"name"`
}

type effectiveRecordTypeDocument struct {
	ObjectAPIName string                          `json:"objectApiName"`
	RecordTypes   []effectiveRecordTypeDefinition `json:"recordTypes"`
}

type effectiveRecordTypeDefinition struct {
	RecordTypeID  string `json:"recordTypeId"`
	Name          string `json:"name"`
	DeveloperName string `json:"developerName"`
}

type projectedRecordType struct {
	ResourceType  string
	RecordTypeID  string
	Name          string
	DeveloperName string
	Available     bool
	Master        bool
	IsDefault     bool
}

type recordTypeProjection struct {
	entries                []projectedRecordType
	defaultRecordTypeID    string
	hasConcreteRecordTypes bool
	availableConcreteCount int
}

func authoredRecordTypesFromView(
	view catalogsemantic.AppMetadataViewV3,
) (map[string]map[string]struct{}, error) {
	var document *appRecordTypeDocument
	for _, component := range view.AppComponents {
		if component.ComponentKind != appRecordTypeComponentKind {
			continue
		}
		if document != nil || component.ComponentFormatVersion != 1 {
			return nil, unavailableSearch()
		}
		var decoded appRecordTypeDocument
		decoder := json.NewDecoder(bytes.NewReader(component.CanonicalBehavior))
		if err := decoder.Decode(&decoded); err != nil {
			return nil, unavailableSearch()
		}
		var trailing any
		if err := decoder.Decode(&trailing); err != io.EOF {
			return nil, unavailableSearch()
		}
		document = &decoded
	}
	result := make(map[string]map[string]struct{})
	if document == nil {
		return result, nil
	}
	for _, group := range document.RecordTypes {
		objectKey := strings.ToLower(group.ObjectAPIName)
		if !validSearchIdentifier(group.ObjectAPIName) || len(group.Types) == 0 || result[objectKey] != nil {
			return nil, unavailableSearch()
		}
		result[objectKey] = make(map[string]struct{}, len(group.Types))
		for _, choice := range group.Types {
			if choice.Name == "" || choice.Name != strings.TrimSpace(choice.Name) {
				return nil, unavailableSearch()
			}
			if _, duplicate := result[objectKey][choice.Name]; duplicate {
				return nil, unavailableSearch()
			}
			result[objectKey][choice.Name] = struct{}{}
		}
	}
	return result, nil
}

func effectiveRecordTypesFromView(
	view catalogsemantic.AppMetadataViewV3,
) (map[string][]effectiveRecordTypeDefinition, error) {
	result := make(map[string][]effectiveRecordTypeDefinition)
	seenIDs := make(map[string]struct{})
	for _, component := range view.EffectiveComponents {
		if component.ComponentKind != catalogsemantic.EffectiveRecordTypeComponentKind {
			continue
		}
		if component.ComponentFormatVersion != 1 {
			return nil, unavailableSearch()
		}
		var document effectiveRecordTypeDocument
		decoder := json.NewDecoder(bytes.NewReader(component.CanonicalBehavior))
		decoder.DisallowUnknownFields()
		if err := decoder.Decode(&document); err != nil {
			return nil, unavailableSearch()
		}
		var trailing any
		if err := decoder.Decode(&trailing); err != io.EOF {
			return nil, unavailableSearch()
		}
		objectKey := strings.ToLower(document.ObjectAPIName)
		if !validSearchIdentifier(document.ObjectAPIName) ||
			component.BusinessKey != document.ObjectAPIName ||
			len(document.RecordTypes) == 0 || result[objectKey] != nil {
			return nil, unavailableSearch()
		}
		seenDeveloperNames := make(map[string]struct{}, len(document.RecordTypes))
		for _, definition := range document.RecordTypes {
			id := strings.TrimSpace(definition.RecordTypeID)
			name := strings.TrimSpace(definition.Name)
			developerName := strings.TrimSpace(definition.DeveloperName)
			if id == "" || name == "" || developerName == "" ||
				id != definition.RecordTypeID || name != definition.Name ||
				developerName != definition.DeveloperName || id == masterRecordTypeID {
				return nil, unavailableSearch()
			}
			developerKey := strings.ToLower(developerName)
			if _, duplicate := seenIDs[id]; duplicate {
				return nil, unavailableSearch()
			}
			if _, duplicate := seenDeveloperNames[developerKey]; duplicate {
				return nil, unavailableSearch()
			}
			seenIDs[id] = struct{}{}
			seenDeveloperNames[developerKey] = struct{}{}
		}
		result[objectKey] = document.RecordTypes
	}
	return result, nil
}

func projectRecordTypes(
	view catalogsemantic.AppMetadataViewV3,
	permissions permissionsemantic.Response,
	objectID string,
) (recordTypeProjection, error) {
	authoredByObject, err := authoredRecordTypesFromView(view)
	if err != nil {
		return recordTypeProjection{}, err
	}
	effectiveByObject, err := effectiveRecordTypesFromView(view)
	if err != nil {
		return recordTypeProjection{}, err
	}
	authored := authoredByObject[strings.ToLower(objectID)]
	definitions := effectiveByObject[strings.ToLower(objectID)]
	projection := recordTypeProjection{}
	seenAuthored := make(map[string]struct{}, len(authored))
	permissionsByID := make(map[string]permissionsemantic.RecordTypePermission, len(definitions))
	for _, entry := range permissions.RecordTypePermissions {
		if !strings.EqualFold(entry.ResourceType, objectID) {
			continue
		}
		if entry.ResourceType != objectID || strings.TrimSpace(entry.RecordTypeID) == "" ||
			entry.RecordTypeID == masterRecordTypeID {
			return recordTypeProjection{}, unavailableSearch()
		}
		if _, duplicate := permissionsByID[entry.RecordTypeID]; duplicate {
			return recordTypeProjection{}, unavailableSearch()
		}
		permissionsByID[entry.RecordTypeID] = entry
	}
	if len(definitions) == 0 {
		if len(permissionsByID) != 0 || len(authored) != 0 {
			return recordTypeProjection{}, unavailableSearch()
		}
		projection.entries = []projectedRecordType{{
			ResourceType: objectID, RecordTypeID: masterRecordTypeID,
			Name: "Master", DeveloperName: "Master", Available: true,
			Master: true, IsDefault: true,
		}}
		projection.defaultRecordTypeID = masterRecordTypeID
		return projection, nil
	}
	projection.hasConcreteRecordTypes = true
	defaultCount := 0
	for _, definition := range definitions {
		permission, found := permissionsByID[definition.RecordTypeID]
		if !found {
			return recordTypeProjection{}, unavailableSearch()
		}
		delete(permissionsByID, definition.RecordTypeID)
		if _, expected := authored[definition.Name]; expected {
			if _, duplicate := seenAuthored[definition.Name]; duplicate {
				return recordTypeProjection{}, unavailableSearch()
			}
			seenAuthored[definition.Name] = struct{}{}
		}
		if permission.Available {
			projection.availableConcreteCount++
		}
		if permission.IsDefault {
			if !permission.Available {
				return recordTypeProjection{}, unavailableSearch()
			}
			defaultCount++
			projection.defaultRecordTypeID = definition.RecordTypeID
		}
		projection.entries = append(projection.entries, projectedRecordType{
			ResourceType: objectID, RecordTypeID: definition.RecordTypeID,
			Name: definition.Name, DeveloperName: definition.DeveloperName,
			Available: permission.Available, IsDefault: permission.IsDefault,
		})
	}
	expectedDefaults := 0
	if projection.availableConcreteCount != 0 {
		expectedDefaults = 1
	}
	if len(permissionsByID) != 0 || len(seenAuthored) != len(authored) ||
		defaultCount != expectedDefaults {
		return recordTypeProjection{}, unavailableSearch()
	}
	return projection, nil
}

func (projection recordTypeProjection) entry(recordTypeID string) (projectedRecordType, bool) {
	for _, entry := range projection.entries {
		if entry.RecordTypeID == recordTypeID {
			return entry, true
		}
	}
	return projectedRecordType{}, false
}

func recordTypeInfo(
	version string,
	objectID string,
	entry projectedRecordType,
) map[string]any {
	sobjectsBase := "/services/data/" + version + "/sobjects/" + objectID
	return map[string]any{
		"active": true, "available": entry.Available,
		"defaultRecordTypeMapping": entry.IsDefault,
		"developerName":            entry.DeveloperName, "master": entry.Master,
		"name": entry.Name, "recordTypeId": entry.RecordTypeID,
		"urls": map[string]string{
			"layout": sobjectsBase + "/describe/layouts/" + url.PathEscape(entry.RecordTypeID),
		},
	}
}

func recordTypeMapping(
	layoutID string,
	entry projectedRecordType,
) map[string]any {
	return map[string]any{
		"active": true, "available": entry.Available,
		"defaultRecordTypeMapping": entry.IsDefault,
		"developerName":            entry.DeveloperName, "layoutId": layoutID,
		"master": entry.Master, "name": entry.Name,
		"recordTypeId": entry.RecordTypeID, "urls": map[string]any{},
	}
}

func matchObjectLayoutsRoute(path string) (objectLayoutsRoute, bool) {
	parts := strings.Split(strings.Trim(path, "/"), "/")
	if (len(parts) != 7 && len(parts) != 8) ||
		parts[0] != "services" || parts[1] != "data" || parts[3] != "sobjects" ||
		parts[5] != "describe" || parts[6] != "layouts" ||
		!validSearchIdentifier(parts[4]) {
		return objectLayoutsRoute{}, false
	}
	if _, supported := recordViewVersions[parts[2]]; !supported {
		return objectLayoutsRoute{}, false
	}
	route := objectLayoutsRoute{version: parts[2], objectID: parts[4]}
	if len(parts) == 8 {
		recordTypeID, err := url.PathUnescape(parts[7])
		if err != nil || !validPageLayoutRecordTypeID(recordTypeID, false) {
			return objectLayoutsRoute{}, false
		}
		route.recordTypeID = recordTypeID
	}
	return route, true
}

func decodeAppPageLayout(source []byte) (appPageLayout, error) {
	var layout appPageLayout
	decoder := json.NewDecoder(bytes.NewReader(source))
	if err := decoder.Decode(&layout); err != nil {
		return appPageLayout{}, err
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		return appPageLayout{}, unavailableSearch()
	}
	if !validSearchIdentifier(layout.ObjectAPIName) ||
		strings.TrimSpace(layout.LayoutName) == "" {
		return appPageLayout{}, unavailableSearch()
	}
	if layout.ID == "" {
		layout.ID = layout.ObjectAPIName + "-default-layout"
	}
	return layout, nil
}

func layoutFieldSpec(spec searchObjectSpec, fieldName string) (searchFieldSpec, bool) {
	for _, field := range spec.Fields {
		if strings.EqualFold(field.APIName, fieldName) {
			return field, true
		}
	}
	return searchFieldSpec{}, false
}

func readableObjectFieldNames(
	spec searchObjectSpec,
	permissions map[string]permissionsemantic.FieldPermission,
	fieldNames []string,
) []string {
	result := make([]string, 0, len(fieldNames))
	for _, fieldName := range fieldNames {
		permission, readable := permissions[strings.ToLower(fieldName)]
		if _, exists := layoutFieldSpec(spec, fieldName); exists && readable && permission.CanRead {
			result = append(result, fieldName)
		}
	}
	return result
}

type readableLayoutFieldPath struct {
	APIName         string
	Field           searchFieldSpec
	RootField       searchFieldSpec
	Label           string
	LookupExpansion bool
}

// pageLayoutAuthorityIndex is a request-local acceleration of the immutable
// Catalog specs and one effective-permission response. It owns no metadata:
// every value remains derived from those two authorities, and the index is
// shared by authorization and wire projection so complete layout semantics do
// not reintroduce a per-field linear scan or another Runtime read.
type pageLayoutAuthorityIndex struct {
	specs            map[string]searchObjectSpec
	fields           map[string]map[string]searchFieldSpec
	fieldPermissions map[string]map[string]permissionsemantic.FieldPermission
	readableObjects  map[string]bool
}

func newPageLayoutAuthorityIndex(
	specs map[string]searchObjectSpec,
	permissions permissionsemantic.Response,
) pageLayoutAuthorityIndex {
	index := pageLayoutAuthorityIndex{
		specs: specs, fields: make(map[string]map[string]searchFieldSpec, len(specs)),
		fieldPermissions: make(map[string]map[string]permissionsemantic.FieldPermission, len(specs)),
		readableObjects:  make(map[string]bool, len(specs)),
	}
	for _, spec := range specs {
		objectKey := strings.ToLower(spec.APIName)
		index.fields[objectKey] = make(map[string]searchFieldSpec, len(spec.Fields))
		for _, field := range spec.Fields {
			index.fields[objectKey][strings.ToLower(field.APIName)] = field
		}
		index.fieldPermissions[objectKey] = fieldPermissionsForDescribe(permissions, spec.APIName)
		_, index.readableObjects[objectKey] = objectPermissionForDescribe(permissions, spec.APIName)
	}
	return index
}

func (index pageLayoutAuthorityIndex) field(
	objectName string,
	fieldName string,
) (searchFieldSpec, permissionsemantic.FieldPermission, bool) {
	objectKey := strings.ToLower(objectName)
	field, exists := index.fields[objectKey][strings.ToLower(fieldName)]
	permission, permitted := index.fieldPermissions[objectKey][strings.ToLower(fieldName)]
	return field, permission, exists && permitted
}

func resolveReadableLayoutFieldPath(
	baseObject string,
	fieldPath string,
	index pageLayoutAuthorityIndex,
) (readableLayoutFieldPath, bool) {
	references, recognized := resolveLayoutFieldReferences(
		baseObject, fieldPath, index.specs,
	)
	if !recognized || len(references) == 0 {
		return readableLayoutFieldPath{}, false
	}
	var rootField searchFieldSpec
	var terminalField searchFieldSpec
	lookupExpansion := false
	for _, reference := range references {
		if reference.ForceHidden {
			return readableLayoutFieldPath{}, false
		}
		objectKey := strings.ToLower(reference.ObjectAPIName)
		if !strings.EqualFold(reference.ObjectAPIName, baseObject) &&
			!index.readableObjects[objectKey] {
			return readableLayoutFieldPath{}, false
		}
		field, permission, exists := index.field(
			reference.ObjectAPIName, reference.FieldAPIName,
		)
		if !exists || !permission.CanRead {
			return readableLayoutFieldPath{}, false
		}
		if rootField.APIName == "" {
			rootField = field
		}
		if reference.Terminal {
			terminalField = field
		}
		lookupExpansion = lookupExpansion || !reference.Terminal ||
			!strings.EqualFold(reference.ObjectAPIName, baseObject)
	}
	if terminalField.APIName == "" {
		return readableLayoutFieldPath{}, false
	}
	label := terminalField.Label
	if lookupExpansion {
		label = rootField.Label + " → " + terminalField.APIName
	}
	return readableLayoutFieldPath{
		APIName: fieldPath, Field: terminalField, RootField: rootField, Label: label,
		LookupExpansion: lookupExpansion,
	}, true
}

func layoutFieldPathResourceTypes(
	baseObject string,
	fieldPath string,
	specs map[string]searchObjectSpec,
) []string {
	references, recognized := resolveLayoutFieldReferences(baseObject, fieldPath, specs)
	if !recognized {
		return nil
	}
	var resources []string
	for _, reference := range references {
		if !reference.ForceHidden && !strings.EqualFold(reference.ObjectAPIName, baseObject) {
			resources = append(resources, reference.ObjectAPIName)
		}
	}
	return dedupLayoutObjectNames(resources)
}

func recordStateLinkTargetObject(
	baseObject string,
	link appRecordStateLink,
	specs map[string]searchObjectSpec,
) (string, bool) {
	if target := strings.TrimSpace(link.ObjectAPIName); target != "" {
		_, found := specs[strings.ToLower(target)]
		return target, found
	}
	base, found := specs[strings.ToLower(baseObject)]
	if !found {
		return "", false
	}
	field, found := layoutFieldSpec(base, link.ReferenceFieldAPIName)
	if !found || len(field.ReferenceTo) != 1 {
		return "", false
	}
	return field.ReferenceTo[0], true
}

// pageLayoutPermissionSelection derives the complete authority resource set
// from the same immutable layout and object-spec view that authorization will
// project. L1 owns this metadata assembly; Runtime sees only neutral Catalog
// resource identities and permission sections.
func pageLayoutPermissionSelection(
	layout appPageLayout,
	specs map[string]searchObjectSpec,
) (*permissionsemantic.Selection, error) {
	resources := make(map[string]string, 1+len(layout.RelatedLists))
	addResource := func(objectName string) {
		if parent, virtual := virtualHistoryParent(objectName, specs); virtual {
			objectName = parent.APIName
		}
		if spec, ok := specs[strings.ToLower(strings.TrimSpace(objectName))]; ok &&
			describeAuthorityIsCurrent(spec) {
			resources[strings.ToLower(spec.APIName)] = spec.APIName
		}
	}
	addTextResources := func(text string) {
		for _, resourceType := range layoutTextResourceTypes(
			text, layout.ObjectAPIName, specs,
		) {
			addResource(resourceType)
		}
	}
	addPayloadResources := func(payload any) {
		if payload == nil {
			return
		}
		encoded, err := json.Marshal(payload)
		if err != nil {
			return
		}
		addTextResources(string(encoded))
		for _, resourceType := range payloadResourceTypes(payload, specs) {
			addResource(resourceType)
		}
	}
	if root, ok := specs[strings.ToLower(layout.ObjectAPIName)]; ok &&
		describeAuthorityIsCurrent(root) {
		resources[strings.ToLower(root.APIName)] = root.APIName
	}
	for _, related := range layout.RelatedLists {
		addResource(related.ObjectAPIName)
		if related.Resolution != nil {
			addResource(related.Resolution.Query.ObjectAPIName)
			addResource(related.Resolution.Detail.ObjectAPIName)
			addResource(related.Resolution.List.ObjectAPIName)
			addResource(related.Resolution.Create.ObjectAPIName)
		}
	}
	for _, section := range layout.Sections {
		for _, fieldPath := range section.Fields {
			for _, resourceType := range layoutFieldPathResourceTypes(
				layout.ObjectAPIName, fieldPath, specs,
			) {
				addResource(resourceType)
			}
		}
		for _, label := range section.FieldLabels {
			addTextResources(label)
		}
		addTextResources(section.VisibilityCondition)
		addPayloadResources(section.ComponentConfig)
	}
	for _, related := range layout.RelatedLists {
		for _, sourceFieldPath := range related.PrefillMappings {
			for _, resourceType := range layoutFieldPathResourceTypes(
				layout.ObjectAPIName, sourceFieldPath, specs,
			) {
				addResource(resourceType)
			}
		}
		if related.Resolution != nil {
			for _, sourceFieldPath := range related.Resolution.Create.PrefillMappings {
				for _, resourceType := range layoutFieldPathResourceTypes(
					layout.ObjectAPIName, sourceFieldPath, specs,
				) {
					addResource(resourceType)
				}
			}
		}
	}
	for _, panel := range layout.RecordPanels {
		addTextResources(panel.VisibilityCondition)
		addPayloadResources(panel.Config)
	}
	for _, action := range append(
		append([]appLayoutAction(nil), layout.HeaderActions...),
		layout.QuickActions...,
	) {
		objectKeys, _, ok := quickActionReferences(
			layout.ObjectAPIName, effectiveActionFromLayout(action), specs,
		)
		if !ok {
			continue
		}
		for objectKey := range objectKeys {
			addResource(objectKey)
		}
		addTextResources(action.Label)
		addTextResources(action.VisibilityCondition)
		addPayloadResources(action.Config)
	}
	for _, rule := range layout.RecordStates {
		for _, text := range appRecordStateRuleTexts(rule) {
			addTextResources(text)
		}
		for _, link := range rule.Links {
			if target, found := recordStateLinkTargetObject(
				layout.ObjectAPIName, link, specs,
			); found {
				addResource(target)
			}
			addTextResources(link.Label)
		}
	}
	if len(resources) == 0 || len(resources) > permissionsemantic.MaxSelectedResourceTypes {
		return nil, unavailableSearch()
	}
	resourceTypes := make([]string, 0, len(resources))
	for _, resourceType := range resources {
		resourceTypes = append(resourceTypes, resourceType)
	}
	sort.Strings(resourceTypes)
	return &permissionsemantic.Selection{
		Sections: []permissionsemantic.Section{
			permissionsemantic.SectionObjectPermissions,
			permissionsemantic.SectionFieldPermissions,
			permissionsemantic.SectionRecordTypePermissions,
		},
		ResourceTypes: resourceTypes,
	}, nil
}

func effectiveActionFromLayout(action appLayoutAction) effectiveObjectActionSpec {
	config := make(map[string]any, len(action.Config)+1)
	for key, value := range action.Config {
		config[key] = value
	}
	if action.VisibilityCondition != "" {
		config["visibilityCondition"] = action.VisibilityCondition
	}
	return effectiveObjectActionSpec{
		Name: action.Name, Label: action.Label, Type: action.Type,
		Icon: action.Icon, TargetObject: action.TargetObject, Config: config,
	}
}

func textReferencesUnreadableField(
	text string,
	spec searchObjectSpec,
	permissions map[string]permissionsemantic.FieldPermission,
) bool {
	if strings.TrimSpace(text) == "" {
		return false
	}
	identifiers := make(map[string]struct{})
	for _, identifier := range actionConfigIdentifierPattern.FindAllString(text, -1) {
		identifiers[strings.ToLower(identifier)] = struct{}{}
	}
	for _, field := range spec.Fields {
		if _, referenced := identifiers[strings.ToLower(field.APIName)]; !referenced {
			continue
		}
		permission, found := permissions[strings.ToLower(field.APIName)]
		if !found || !permission.CanRead {
			return true
		}
	}
	return false
}

func payloadResourceTypes(
	payload any,
	specs map[string]searchObjectSpec,
) []string {
	if payload == nil {
		return nil
	}
	encoded, err := json.Marshal(payload)
	if err != nil {
		return nil
	}
	identifiers := make(map[string]struct{})
	for _, identifier := range actionConfigIdentifierPattern.FindAllString(string(encoded), -1) {
		identifiers[strings.ToLower(identifier)] = struct{}{}
	}
	var result []string
	for _, spec := range specs {
		if _, referenced := identifiers[strings.ToLower(spec.APIName)]; referenced {
			result = append(result, spec.APIName)
		}
	}
	return result
}

func payloadReferencesUnreadableFields(
	payload any,
	baseObject string,
	index pageLayoutAuthorityIndex,
	objectHints ...string,
) bool {
	if payload == nil {
		return false
	}
	encoded, err := json.Marshal(payload)
	if err != nil {
		return true
	}
	return layoutTextReferencesUnreadableFields(
		string(encoded), baseObject, index,
		append(objectHints, payloadResourceTypes(payload, index.specs)...)...,
	)
}

func filterLayoutBehaviorMapByReadableField(
	source map[string]map[string]string,
	readable func(string) bool,
) map[string]map[string]string {
	if len(source) == 0 {
		return nil
	}
	result := make(map[string]map[string]string, len(source))
	for fieldName, behaviors := range source {
		if readable(fieldName) {
			result[fieldName] = behaviors
		}
	}
	if len(result) == 0 {
		return nil
	}
	return result
}

// authorizePageLayout applies caller-effective object and field authority to
// one immutable Catalog layout before any Salesforce wire projection uses it.
// Keeping this as the single projection boundary prevents record-ui and
// describe/layouts from drifting into different disclosure behavior.
func authorizePageLayout(
	layout appPageLayout,
	specs map[string]searchObjectSpec,
	permissions permissionsemantic.Response,
	currentListFields map[string][]string,
) (appPageLayout, error) {
	return authorizePageLayoutWithIndex(
		layout, specs, permissions, newPageLayoutAuthorityIndex(specs, permissions), currentListFields,
	)
}

// relatedListObjectAuthority adapts the existing Describe history schema for
// layout projection without inserting virtual objects into the physical Catalog
// or asking Runtime for virtual object/field permissions.
func relatedListObjectAuthority(objectName string, specs map[string]searchObjectSpec, permissions permissionsemantic.Response) (searchObjectSpec, map[string]permissionsemantic.FieldPermission, bool) {
	if spec, physical := specs[strings.ToLower(objectName)]; physical {
		_, readable := objectPermissionForDescribe(permissions, spec.APIName)
		return spec, fieldPermissionsForDescribe(permissions, spec.APIName), readable && describeAuthorityIsCurrent(spec)
	}
	parent, virtual := virtualHistoryParent(objectName, specs)
	if !virtual {
		return searchObjectSpec{}, nil, false
	}
	if _, readable := objectPermissionForDescribe(permissions, parent.APIName); !readable {
		return searchObjectSpec{}, nil, false
	}
	spec := searchObjectSpec{APIName: objectName}
	fields := make(map[string]permissionsemantic.FieldPermission)
	for _, field := range historyObjectFields(objectName, parent, permissions) {
		name := field["name"].(string)
		spec.Fields = append(spec.Fields, searchFieldSpec{
			APIName: name, Label: field["label"].(string), Type: field["type"].(string),
			ReferenceTo: field["referenceTo"].([]string),
		})
		fields[strings.ToLower(name)] = permissionsemantic.FieldPermission{
			ResourceType: objectName, FieldName: name, CanRead: field["readable"].(bool),
		}
	}
	return spec, fields, true
}

func authorizePageLayoutWithIndex(
	layout appPageLayout,
	specs map[string]searchObjectSpec,
	permissions permissionsemantic.Response,
	authorityIndex pageLayoutAuthorityIndex,
	currentListFields map[string][]string,
) (appPageLayout, error) {
	parent, found := specs[strings.ToLower(layout.ObjectAPIName)]
	if !found || !describeAuthorityIsCurrent(parent) {
		return appPageLayout{}, unavailableSearch()
	}
	if _, readable := objectPermissionForDescribe(permissions, parent.APIName); !readable {
		return appPageLayout{}, &permissionsemantic.Failure{Category: permissionsemantic.FailureForbidden}
	}
	readableParentField := func(fieldName string) bool {
		_, readable := resolveReadableLayoutFieldPath(
			parent.APIName, fieldName, authorityIndex,
		)
		return readable
	}
	recordPanelAuthorized := func(panel appRecordPanel) bool {
		return !layoutTextReferencesUnreadableFields(
			panel.VisibilityCondition, parent.APIName, authorityIndex,
		) && !payloadReferencesUnreadableFields(
			panel.Config, parent.APIName, authorityIndex,
		)
	}
	layoutActionAuthorized := func(action appLayoutAction) bool {
		objectHints := []string(nil)
		if targetName := quickActionTargetObject(effectiveActionFromLayout(action)); targetName != "" {
			objectHints = append(objectHints, targetName)
		}
		if layoutTextReferencesUnreadableFields(
			action.Label, parent.APIName, authorityIndex, objectHints...,
		) || layoutTextReferencesUnreadableFields(
			action.VisibilityCondition, parent.APIName, authorityIndex, objectHints...,
		) || payloadReferencesUnreadableFields(
			action.Config, parent.APIName, authorityIndex, objectHints...,
		) {
			return false
		}
		if targetName := quickActionTargetObject(effectiveActionFromLayout(action)); targetName != "" {
			target, found := specs[strings.ToLower(targetName)]
			if !found || !describeAuthorityIsCurrent(target) {
				return false
			}
		}
		return pageLayoutActionAuthorized(
			layout.ObjectAPIName, effectiveActionFromLayout(action), specs, permissions,
		)
	}
	recordStateAuthorized := func(rule appRecordStateRule) bool {
		for _, text := range appRecordStateRuleTexts(rule) {
			if layoutTextReferencesUnreadableFields(
				text, parent.APIName, authorityIndex,
			) {
				return false
			}
		}
		return true
	}

	authorized := layout
	authorized.CompactLayout = nil
	for _, fieldName := range layout.CompactLayout {
		if readableParentField(fieldName) {
			authorized.CompactLayout = append(authorized.CompactLayout, fieldName)
		}
	}
	authorized.Sections = make([]appPageLayoutSection, 0, len(layout.Sections))
	for _, section := range layout.Sections {
		if layoutTextReferencesUnreadableFields(
			section.VisibilityCondition, parent.APIName, authorityIndex,
		) ||
			payloadReferencesUnreadableFields(
				section.ComponentConfig, parent.APIName, authorityIndex,
			) {
			continue
		}
		projected := section
		projected.Fields = nil
		for _, fieldName := range section.Fields {
			if readableParentField(fieldName) {
				projected.Fields = append(projected.Fields, fieldName)
			}
		}
		projected.FieldLabels = nil
		for fieldName, label := range section.FieldLabels {
			if !readableParentField(fieldName) || layoutTextReferencesUnreadableFields(
				label, parent.APIName, authorityIndex,
				layoutFieldPathObjectHints(parent.APIName, fieldName, specs)...,
			) {
				continue
			}
			if projected.FieldLabels == nil {
				projected.FieldLabels = make(map[string]string)
			}
			projected.FieldLabels[fieldName] = label
		}
		projected.FieldBehaviors = filterLayoutBehaviorMapByReadableField(
			section.FieldBehaviors, readableParentField,
		)
		authorized.Sections = append(authorized.Sections, projected)
	}
	authorized.RecordPanels = make([]appRecordPanel, 0, len(layout.RecordPanels))
	for _, panel := range layout.RecordPanels {
		if recordPanelAuthorized(panel) {
			authorized.RecordPanels = append(authorized.RecordPanels, panel)
		}
	}
	authorized.RelatedLists = make([]appRelatedList, 0, len(layout.RelatedLists))
	for _, related := range layout.RelatedLists {
		if _, _, readable := relatedListObjectAuthority(related.ObjectAPIName, specs, permissions); !readable {
			continue
		}
		queryObjectName := related.ObjectAPIName
		queryLookupField := related.LookupField
		if related.Resolution != nil {
			queryObjectName = related.Resolution.Query.ObjectAPIName
			queryLookupField = related.Resolution.Query.LookupField
		}
		queryChild, queryFields, readable := relatedListObjectAuthority(queryObjectName, specs, permissions)
		if !readable {
			continue
		}
		lookupPermission, lookupReadable := queryFields[strings.ToLower(queryLookupField)]
		if _, exists := layoutFieldSpec(queryChild, queryLookupField); !exists ||
			!lookupReadable || !lookupPermission.CanRead {
			continue
		}
		if related.Resolution != nil {
			if (related.Resolution.Query.ParentFieldAPIName != "" &&
				!readableParentField(related.Resolution.Query.ParentFieldAPIName)) ||
				(related.Resolution.List.ParentFieldAPIName != "" &&
					!readableParentField(related.Resolution.List.ParentFieldAPIName)) {
				continue
			}
			resolutionAuthorized := true
			for _, required := range []string{
				related.Resolution.Detail.ObjectAPIName,
				related.Resolution.List.ObjectAPIName,
			} {
				if _, _, readable := relatedListObjectAuthority(required, specs, permissions); !readable {
					resolutionAuthorized = false
					break
				}
			}
			if !resolutionAuthorized {
				continue
			}
			listTarget, listFields, _ := relatedListObjectAuthority(related.Resolution.List.ObjectAPIName, specs, permissions)
			filterPermission, permitted := listFields[strings.ToLower(related.Resolution.List.FilterField)]
			if _, exists := layoutFieldSpec(listTarget, related.Resolution.List.FilterField); !exists ||
				!permitted || !filterPermission.CanRead {
				continue
			}
		}
		projected := related
		projected.Fields = nil
		projected.PrefillMappings = nil
		configuredFields := related.Fields
		_, virtualHistory := virtualHistoryParent(queryChild.APIName, specs)
		// History has no stored ListFields. Preserve empty columns for the
		// existing Describe-driven display fallback, not the parent's columns.
		if len(configuredFields) == 0 && !virtualHistory {
			var current bool
			configuredFields, current = currentListFields[strings.ToLower(queryChild.APIName)]
			if !current {
				return appPageLayout{}, unavailablePageLayout()
			}
		}
		projected.Fields = readableObjectFieldNames(queryChild, queryFields, configuredFields)
		if len(configuredFields) != 0 && len(projected.Fields) == 0 {
			continue
		}
		createObject := queryChild
		createMappings := related.PrefillMappings
		if projected.Resolution != nil {
			createMappings = projected.Resolution.Create.PrefillMappings
			if projected.Resolution.Create.Kind == "standard" {
				var createFound bool
				createObject, createFound = specs[strings.ToLower(projected.Resolution.Create.ObjectAPIName)]
				if !createFound || !describeAuthorityIsCurrent(createObject) {
					projected.Resolution.Create = appRelatedListCreateTarget{Kind: "none"}
					createMappings = nil
				}
			} else {
				createMappings = nil
			}
			projected.Resolution.Create.PrefillMappings = nil
		}
		createPermissions := fieldPermissionsForDescribe(permissions, createObject.APIName)
		createObjectPermission, createReadable := objectPermissionForDescribe(permissions, createObject.APIName)
		if createReadable && createObjectPermission.CanCreate {
			for targetField, parentField := range createMappings {
				target, targetExists := layoutFieldSpec(createObject, targetField)
				targetPermission, targetPermitted := createPermissions[strings.ToLower(targetField)]
				if targetExists && targetPermitted && targetPermission.CanRead &&
					targetPermission.CanCreate && target.Createable && readableParentField(parentField) {
					if projected.Resolution != nil {
						if projected.Resolution.Create.PrefillMappings == nil {
							projected.Resolution.Create.PrefillMappings = make(map[string]string)
						}
						projected.Resolution.Create.PrefillMappings[targetField] = parentField
					} else {
						if projected.PrefillMappings == nil {
							projected.PrefillMappings = make(map[string]string)
						}
						projected.PrefillMappings[targetField] = parentField
					}
				}
			}
		} else if projected.Resolution != nil && projected.Resolution.Create.Kind == "standard" {
			projected.Resolution.Create = appRelatedListCreateTarget{Kind: "none"}
		}
		authorized.RelatedLists = append(authorized.RelatedLists, projected)
	}
	authorized.HeaderActions = make([]appLayoutAction, 0, len(layout.HeaderActions))
	for _, action := range layout.HeaderActions {
		if layoutActionAuthorized(action) {
			authorized.HeaderActions = append(authorized.HeaderActions, action)
		}
	}
	authorized.QuickActions = make([]appLayoutAction, 0, len(layout.QuickActions))
	for _, action := range layout.QuickActions {
		if layoutActionAuthorized(action) {
			authorized.QuickActions = append(authorized.QuickActions, action)
		}
	}
	authorized.RecordStates = make([]appRecordStateRule, 0, len(layout.RecordStates))
	for _, rule := range layout.RecordStates {
		if !recordStateAuthorized(rule) {
			continue
		}
		projected := rule
		projected.Links = nil
		for _, link := range rule.Links {
			if !readableParentField(link.ReferenceFieldAPIName) {
				continue
			}
			targetName, found := recordStateLinkTargetObject(
				layout.ObjectAPIName, link, specs,
			)
			if !found {
				continue
			}
			target := specs[strings.ToLower(targetName)]
			if !describeAuthorityIsCurrent(target) ||
				!authorityIndex.readableObjects[strings.ToLower(target.APIName)] ||
				layoutTextReferencesUnreadableFields(
					link.Label, parent.APIName, authorityIndex, target.APIName,
				) {
				continue
			}
			projected.Links = append(projected.Links, link)
		}
		authorized.RecordStates = append(authorized.RecordStates, projected)
	}
	return authorized, nil
}

// pageLayoutActionAuthorized keeps placement and execution authority on their
// respective owners. A Standard layout action is only a reference to either a
// host-owned record action or an invocable Standard Action. The host applies
// record CRUD, while Standard Action discovery and execution apply the
// definition's required permissions and input guards. Re-applying the generic
// quick-action CanEdit rule here would create a second, conflicting authority
// path and hide valid source-read/target-create actions such as createQuote.
// Non-standard metadata actions continue to use the quick-action authority
// rules because this layout response is their executable projection.
func pageLayoutActionAuthorized(
	baseObject string,
	action effectiveObjectActionSpec,
	specs map[string]searchObjectSpec,
	permissions permissionsemantic.Response,
) bool {
	if action.Type == "Standard" {
		return true
	}
	return quickActionAuthorized(baseObject, action, specs, permissions)
}

func projectPageLayout(
	layout appPageLayout,
	spec searchObjectSpec,
	objectPermission permissionsemantic.ObjectPermission,
	permissions permissionsemantic.Response,
) map[string]any {
	fieldPermissions := fieldPermissionsForDescribe(permissions, spec.APIName)
	detailSections := make([]map[string]any, 0, len(layout.Sections))
	sections := make([]map[string]any, 0, len(layout.Sections))
	for _, section := range layout.Sections {
		items := make([]map[string]any, 0, len(section.Fields))
		visibleFields := make([]string, 0, len(section.Fields))
		for _, fieldName := range section.Fields {
			field, found := layoutFieldSpec(spec, fieldName)
			permission, permitted := fieldPermissions[strings.ToLower(fieldName)]
			if !found || !permitted || !permission.CanRead {
				continue
			}
			visibleFields = append(visibleFields, field.APIName)
			items = append(items, map[string]any{
				"editableForNew":    objectPermission.CanCreate && permission.CanCreate && field.Createable,
				"editableForUpdate": objectPermission.CanEdit && permission.CanEdit && field.Updateable,
				"field":             field.APIName, "label": field.Label, "required": field.Required,
			})
		}
		detailSections = append(detailSections, map[string]any{
			"heading": section.Label, "columns": section.Columns,
			"rows": len(items), "useHeading": true,
			"layoutRows": []map[string]any{{"layoutItems": items}},
		})
		sections = append(sections, map[string]any{
			"id": section.ID, "label": section.Label,
			"columns": section.Columns, "fields": visibleFields,
		})
	}
	relatedLists := make([]map[string]any, 0, len(layout.RelatedLists))
	for _, related := range layout.RelatedLists {
		projected := map[string]any{
			"accessLevel": nil, "buttons": []any{}, "columns": []any{},
			"custom": false, "field": related.LookupField,
			"label": related.Label, "secondaryLabel": related.SecondaryLabel,
			"limitRows": 5, "name": related.Label,
			"sobject": related.ObjectAPIName, "sort": []any{},
		}
		if related.Resolution != nil {
			projected["resolution"] = related.Resolution
		}
		relatedLists = append(relatedLists, projected)
	}
	return map[string]any{
		"id": layout.ID, "name": layout.LayoutName,
		"detailLayoutSections": detailSections,
		"editLayoutSections":   detailSections,
		"relatedLists":         relatedLists, "sections": sections,
	}
}

func (service *Service) readObjectLayouts(
	ctx context.Context,
	bearer string,
	route objectLayoutsRoute,
) (map[string]any, error) {
	view, specs, err := readObjectSpecs(ctx, service.appMetadataV3)
	if err != nil {
		return nil, err
	}
	spec, found := specs[strings.ToLower(route.objectID)]
	if !found || !describeAuthorityIsCurrent(spec) {
		return nil, &permissionsemantic.Failure{Category: permissionsemantic.FailureMissing}
	}
	recordTypeIDs := []string{route.recordTypeID}
	hasConcreteRecordTypes := false
	if route.recordTypeID == "" {
		effective, typeErr := effectiveRecordTypesFromView(view)
		if typeErr != nil {
			return nil, typeErr
		}
		definitions := effective[strings.ToLower(spec.APIName)]
		hasConcreteRecordTypes = len(definitions) != 0
		if len(definitions) == 0 {
			recordTypeIDs = []string{masterRecordTypeID}
		} else {
			recordTypeIDs = make([]string, 0, len(definitions))
			for _, definition := range definitions {
				recordTypeIDs = append(recordTypeIDs, definition.RecordTypeID)
			}
		}
	}
	resolverRecordTypeIDs := make([]string, 0, len(recordTypeIDs))
	for _, recordTypeID := range recordTypeIDs {
		if recordTypeID != masterRecordTypeID {
			resolverRecordTypeIDs = append(resolverRecordTypeIDs, recordTypeID)
		}
	}
	resolvedLayouts, layoutReceipts, err := service.resolvePageLayouts(
		ctx, bearer, view, spec.APIName, resolverRecordTypeIDs,
	)
	if err != nil {
		return nil, err
	}
	layoutsByRecordType := make(map[string]appPageLayout, len(recordTypeIDs))
	var resources []string
	for _, recordTypeID := range recordTypeIDs {
		resolverKey := recordTypeID
		if resolverKey == masterRecordTypeID {
			resolverKey = ""
		}
		layout, found := resolvedLayouts[resolverKey]
		if !found {
			return nil, unavailablePageLayout()
		}
		layoutsByRecordType[recordTypeID] = layout
		if hasConcreteRecordTypes && route.recordTypeID == "" {
			continue
		}
		selection, selectionErr := pageLayoutPermissionSelection(layout, specs)
		if selectionErr != nil {
			return nil, selectionErr
		}
		resources = append(resources, selection.ResourceTypes...)
	}
	resolvedForListFields := make([]appPageLayout, 0, len(layoutsByRecordType))
	for _, layout := range layoutsByRecordType {
		resolvedForListFields = append(resolvedForListFields, layout)
	}
	currentListFields, listFieldReceipts, err := service.readCurrentObjectListFields(
		ctx, bearer, view, pageLayoutListFieldObjectIDs(spec.APIName, specs, resolvedForListFields...),
	)
	if err != nil {
		return nil, err
	}
	var selection *permissionsemantic.Selection
	if hasConcreteRecordTypes && route.recordTypeID == "" {
		selection, err = recordUIObjectInfoPermissionSelection(spec, specs)
	} else {
		selection, err = boundedRecordUIPermissionSelection(resources)
	}
	if err != nil {
		return nil, err
	}
	revision := view.Receipts.CatalogRevision
	permissionRequest := permissionsemantic.Request{
		MinCatalogRevision: &revision,
		Selection:          selection,
	}
	permissions, err := service.permissionMetadata.readEffective(
		ctx, bearer, permissionRequest,
	)
	if err != nil {
		return nil, err
	}
	if err := permissionsemantic.ValidateResponseForRequest(
		permissionRequest, permissions,
	); err != nil {
		return nil, err
	}
	if err := validateSearchMetadataAuthority(
		ctx, service.reconcileAppMetadataV3, view, permissions.Receipts,
	); err != nil {
		return nil, err
	}
	if !sameAppMetadataAuthority(layoutReceipts, permissions.Receipts) ||
		!sameAppMetadataAuthority(listFieldReceipts, permissions.Receipts) {
		return nil, unavailablePageLayout()
	}
	objectPermission, readable := objectPermissionForDescribe(permissions, spec.APIName)
	if !readable {
		return nil, &permissionsemantic.Failure{Category: permissionsemantic.FailureForbidden}
	}
	recordTypes, err := projectRecordTypes(view, permissions, spec.APIName)
	if err != nil {
		return nil, err
	}
	if route.recordTypeID != "" {
		entry, found := recordTypes.entry(route.recordTypeID)
		if !found || !entry.Available {
			return nil, &permissionsemantic.Failure{Category: permissionsemantic.FailureMissing}
		}
		layout, authorizeErr := authorizePageLayout(
			layoutsByRecordType[route.recordTypeID], specs, permissions, currentListFields,
		)
		if authorizeErr != nil {
			return nil, authorizeErr
		}
		return projectPageLayout(layout, spec, objectPermission, permissions), nil
	}
	mappings := make([]map[string]any, 0, len(recordTypes.entries))
	projectedLayouts := make([]map[string]any, 0, len(recordTypes.entries))
	seenLayouts := make(map[string]struct{}, len(recordTypes.entries))
	for _, entry := range recordTypes.entries {
		layout, found := layoutsByRecordType[entry.RecordTypeID]
		if !found {
			return nil, unavailablePageLayout()
		}
		mappings = append(mappings, recordTypeMapping(layout.ID, entry))
		if hasConcreteRecordTypes || !entry.Available {
			continue
		}
		if _, seen := seenLayouts[layout.ID]; seen {
			continue
		}
		authorized, authorizeErr := authorizePageLayout(layout, specs, permissions, currentListFields)
		if authorizeErr != nil {
			return nil, authorizeErr
		}
		projectedLayouts = append(
			projectedLayouts,
			projectPageLayout(authorized, spec, objectPermission, permissions),
		)
		seenLayouts[layout.ID] = struct{}{}
	}
	var layoutsResponse any = projectedLayouts
	if hasConcreteRecordTypes {
		layoutsResponse = nil
	}
	return map[string]any{
		"layouts":                    layoutsResponse,
		"recordTypeMappings":         mappings,
		"recordTypeSelectorRequired": recordTypes.availableConcreteCount > 0,
	}, nil
}

func (service *Service) serveObjectLayouts(writer http.ResponseWriter, request *http.Request) bool {
	route, matched := matchObjectLayoutsRoute(request.URL.EscapedPath())
	if !matched || service.appMetadataV3 == nil || service.permissionMetadata == nil ||
		service.resolvePageLayoutRuntime == nil {
		return false
	}
	if request.Method != http.MethodGet {
		writer.Header().Set("Allow", http.MethodGet)
		writeCRMError(writer, http.StatusMethodNotAllowed, crmwire.SFCodeInvalidRequest, "Method not allowed")
		return true
	}
	service.requests.Add(1)
	if service.draining.Load() {
		service.drainRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusServiceUnavailable, crmwire.SFCodeUnknownException, "Service is draining")
		return true
	}
	bearer, ok := endUserBearer(request.Header.Values("Authorization"))
	if !ok {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusUnauthorized, crmwire.SFCodeInvalidSessionID, "Session expired or invalid")
		return true
	}
	service.inFlight.Add(1)
	defer service.inFlight.Add(-1)
	ctx, cancel := context.WithTimeout(request.Context(), service.requestTimeout)
	defer cancel()
	response, err := service.readObjectLayouts(ctx, bearer, route)
	if err != nil {
		service.failures.Add(1)
		writeSearchFailure(writer, err)
		return true
	}
	service.successes.Add(1)
	writeJSON(writer, http.StatusOK, response)
	return true
}
