package sfcrmappservice

import (
	"context"
	"net/http"
	"net/url"
	"slices"
	"sort"
	"strings"

	crmwire "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/wire"
	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	pagelayoutsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/pagelayoutresolutionv1"
	accesssemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordaccessv1"
	recordsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordviewv1"
)

type recordUIRoute struct {
	version   string
	recordIDs []string
}

type recordUIMetadataRoute struct {
	version  string
	objectID string
	layout   bool
}

type recordUIAuthority struct {
	view              catalogsemantic.AppMetadataViewV3
	layout            appPageLayout
	layoutReceipts    commonsemantic.Receipts
	listFields        map[string][]string
	listFieldReceipts commonsemantic.Receipts
	specs             map[string]searchObjectSpec
	spec              searchObjectSpec
	objectPermission  permissionsemantic.ObjectPermission
	permissions       permissionsemantic.Response
	recordTypes       recordTypeProjection
	revision          uint64
}

func matchRecordUIRoute(path string) (recordUIRoute, bool) {
	parts := strings.Split(strings.Trim(path, "/"), "/")
	if len(parts) != 6 || parts[0] != "services" || parts[1] != "data" ||
		parts[3] != "ui-api" || parts[4] != "record-ui" {
		return recordUIRoute{}, false
	}
	if _, supported := recordViewVersions[parts[2]]; !supported {
		return recordUIRoute{}, false
	}
	raw, err := url.PathUnescape(parts[5])
	if err != nil {
		return recordUIRoute{}, false
	}
	seen := make(map[string]struct{})
	recordIDs := make([]string, 0)
	for _, value := range strings.Split(raw, ",") {
		id := strings.TrimSpace(value)
		if id == "" {
			return recordUIRoute{}, false
		}
		if _, duplicate := seen[id]; duplicate {
			continue
		}
		seen[id] = struct{}{}
		recordIDs = append(recordIDs, id)
	}
	if len(recordIDs) == 0 {
		return recordUIRoute{}, false
	}
	return recordUIRoute{version: parts[2], recordIDs: recordIDs}, true
}

func matchRecordUIMetadataRoute(path string) (recordUIMetadataRoute, bool) {
	parts := strings.Split(strings.Trim(path, "/"), "/")
	if len(parts) != 6 || parts[0] != "services" || parts[1] != "data" ||
		parts[3] != "ui-api" || (parts[4] != "object-info" && parts[4] != "layout") {
		return recordUIMetadataRoute{}, false
	}
	if _, supported := recordViewVersions[parts[2]]; !supported {
		return recordUIMetadataRoute{}, false
	}
	objectID, err := url.PathUnescape(parts[5])
	if err != nil || !validSearchIdentifier(objectID) {
		return recordUIMetadataRoute{}, false
	}
	return recordUIMetadataRoute{
		version: parts[2], objectID: objectID, layout: parts[4] == "layout",
	}, true
}

func recordUIReadRecordTypeID(read recordsemantic.ReadResponse) (string, error) {
	for _, field := range read.Root.Fields {
		if !strings.EqualFold(field.Name, "RecordTypeId") {
			continue
		}
		value, err := recordViewValue(field.Value)
		if err != nil {
			return "", err
		}
		if value == nil {
			return "", nil
		}
		identifier, ok := value.(string)
		if !ok {
			return "", unavailablePageLayout()
		}
		identifier = strings.TrimSpace(identifier)
		if identifier == "" || identifier == masterRecordTypeID {
			return "", nil
		}
		return identifier, nil
	}
	return "", nil
}

func projectRecordUIRecord(
	objectID string,
	read recordsemantic.ReadResponse,
	access RecordAccess,
	recordTypes recordTypeProjection,
) (map[string]any, error) {
	displays := make(map[string]recordsemantic.ReferenceDisplay, len(read.ReferenceDisplays))
	for _, display := range read.ReferenceDisplays {
		displays[strings.ToLower(display.SourceFieldID)] = display
	}
	fields := make(map[string]any, len(read.Root.Fields)+len(read.ReferenceDisplays))
	for _, field := range read.Root.Fields {
		value, err := recordViewValue(field.Value)
		if err != nil {
			return nil, err
		}
		var displayValue any
		if display, found := displays[strings.ToLower(field.Name)]; found && display.DisplayName != "" {
			displayValue = display.DisplayName
		}
		fields[field.Name] = map[string]any{"displayValue": displayValue, "value": value}
	}
	for _, display := range read.ReferenceDisplays {
		fields[display.RelationshipID] = projectRecordUIRelationship(
			display.TargetObjectID, display.TargetRecordID, display.DisplayName,
		)
	}
	recordTypeID := masterRecordTypeID
	if field, found := fields["RecordTypeId"].(map[string]any); found {
		if value, ok := field["value"].(string); ok && value != "" {
			recordTypeID = value
			if recordType, resolved := recordTypes.entry(recordTypeID); resolved {
				projectRecordTypeDisplay(fields, "RecordTypeId", recordType)
			}
		}
	}
	result := map[string]any{
		"apiName": objectID,
		"id":      read.RecordID,
		"fields":  fields,
		"recordAccess": map[string]any{
			"hasReadAccess": access.HasReadAccess, "hasEditAccess": access.HasEditAccess,
			"hasDeleteAccess": access.HasDeleteAccess, "isLocked": access.IsLocked,
		},
		"recordTypeId": recordTypeID,
	}
	for _, field := range []string{"LastModifiedDate", "SystemModstamp"} {
		entry, found := fields[field].(map[string]any)
		if !found {
			continue
		}
		value, ok := entry["value"].(string)
		if !ok || value == "" {
			continue
		}
		if field == "LastModifiedDate" {
			result["lastModifiedDate"] = value
		} else {
			result["systemModstamp"] = value
		}
	}
	return result, nil
}

func projectRecordUIRelationship(objectID string, recordID string, displayName string) map[string]any {
	return map[string]any{
		"displayValue": displayName,
		"value": map[string]any{
			"apiName": objectID,
			"id":      recordID,
			"fields": map[string]any{
				"Name": map[string]any{"displayValue": nil, "value": displayName},
			},
		},
	}
}

func projectRecordTypeDisplay(fields map[string]any, fieldName string, recordType projectedRecordType) {
	field, found := fields[fieldName].(map[string]any)
	if !found {
		return
	}
	value, valueIsString := field["value"].(string)
	if !valueIsString || value != recordType.RecordTypeID {
		return
	}
	field["displayValue"] = recordType.Name
	fields["RecordType"] = projectRecordUIRelationship(
		"RecordType", recordType.RecordTypeID, recordType.Name,
	)
}

func validateRecordUIReadAccess(read recordsemantic.ReadResponse) error {
	if read.Access == nil {
		return &commonsemantic.Failure{Category: commonsemantic.FailureUnavailable}
	}
	if !read.Access.Read {
		return &commonsemantic.Failure{Category: commonsemantic.FailureForbidden}
	}
	return nil
}

func recordUIRelationshipName(field searchFieldSpec) string {
	if len(field.ReferenceTo) == 0 {
		return ""
	}
	if field.RelationshipName != "" {
		return field.RelationshipName
	}
	if !strings.HasSuffix(field.APIName, "Id") || field.APIName == "Id" {
		return ""
	}
	return strings.TrimSuffix(field.APIName, "Id")
}

func projectRecordUIObjectInfo(
	spec searchObjectSpec,
	objectPermission permissionsemantic.ObjectPermission,
	permissions permissionsemantic.Response,
	recordTypes recordTypeProjection,
	orderedListFields []string,
) map[string]any {
	fieldPermissions := fieldPermissionsForDescribe(permissions, spec.APIName)
	fields := make(map[string]any)
	for _, field := range spec.Fields {
		permission, readable := fieldPermissions[strings.ToLower(field.APIName)]
		if !readable || !permission.CanRead {
			continue
		}
		references := make([]map[string]any, 0, len(field.ReferenceTo))
		for _, target := range field.ReferenceTo {
			if _, readable := objectPermissionForDescribe(permissions, target); !readable {
				continue
			}
			references = append(references, map[string]any{
				"apiName": target, "nameFields": []string{"Name"},
			})
		}
		compoundFieldName := ""
		compoundComponentRole := ""
		if parentName := strings.TrimSpace(field.CompoundFieldName); parentName != "" {
			if parentPermission, found := fieldPermissions[strings.ToLower(parentName)]; found && parentPermission.CanRead {
				compoundFieldName = parentName
				compoundComponentRole = strings.TrimSpace(field.CompoundComponentRole)
			}
		}
		relationshipName := ""
		if len(references) != 0 {
			relationshipName = recordUIRelationshipName(field)
		}
		fields[field.APIName] = map[string]any{
			"apiName": field.APIName, "label": field.Label,
			"dataType": describeFieldType(field), "required": field.Required,
			"createable": objectPermission.CanCreate && permission.CanCreate && field.Createable,
			"updateable": objectPermission.CanEdit && permission.CanEdit && field.Updateable,
			"reference":  len(references) != 0, "referenceToInfos": references,
			"relationshipName": relationshipName, "nameField": field.IsNameField,
			"inlineHelpText": field.InlineHelpText, "length": describeFieldLength(field, describeFieldType(field)),
			"compoundFieldName": compoundFieldName, "compoundComponentRole": compoundComponentRole,
		}
	}
	plural := spec.PluralLabel
	if plural == "" {
		plural = spec.Label
	}
	searchable := spec.Searchable == nil || *spec.Searchable
	recordTypeInfos := make(map[string]any)
	for _, entry := range recordTypes.entries {
		recordTypeInfos[entry.RecordTypeID] = map[string]any{
			"name": entry.Name, "recordTypeId": entry.RecordTypeID,
			"available": entry.Available, "defaultRecordTypeMapping": entry.IsDefault,
			"master": entry.Master,
		}
	}
	result := map[string]any{
		"apiName": spec.APIName, "label": spec.Label, "labelPlural": plural,
		"keyPrefix": spec.KeyPrefix, "layoutable": true, "queryable": true,
		"createable": objectPermission.CanCreate, "updateable": objectPermission.CanEdit,
		"deletable": objectPermission.CanDelete, "retrieveable": true,
		"searchable": searchable, "triggerable": !strings.HasPrefix(spec.APIName, "_System_"),
		"undeletable": true, "fields": fields, "dependentFields": map[string]any{},
		"defaultRecordTypeId": recordTypes.defaultRecordTypeID,
		"recordTypeInfos":     recordTypeInfos,
	}
	if orderedListFields != nil {
		result["listFields"] = readableObjectFieldNames(
			spec, fieldPermissions, orderedListFields,
		)
	}
	if spec.PresentationFactsV5 && spec.DisplayNameField != "" {
		if readableSearchDisplayNameField(spec, permissions) {
			result["displayNameField"] = spec.DisplayNameField
		}
	}
	return result
}

func projectRecordUILayout(
	layout appPageLayout,
	spec searchObjectSpec,
	authorityIndex pageLayoutAuthorityIndex,
	objectPermission permissionsemantic.ObjectPermission,
	permissions permissionsemantic.Response,
	mode string,
) (map[string]any, error) {
	// Historical object-search metadata does not carry this Records-owned
	// requirement set. Keep View/Edit readable; never substitute raw Required
	// for the missing Create input contract.
	if mode == "Create" && spec.CreateRequiredFields == nil {
		return nil, &commonsemantic.Failure{Category: commonsemantic.FailureUnavailable}
	}
	var createRequiredFields map[string]struct{}
	if mode == "Create" {
		createRequiredFields = make(map[string]struct{}, len(*spec.CreateRequiredFields))
		for _, fieldName := range *spec.CreateRequiredFields {
			createRequiredFields[fieldName] = struct{}{}
		}
	}
	fieldPermissions := fieldPermissionsForDescribe(permissions, spec.APIName)
	sections := make([]map[string]any, 0, len(layout.Sections))
	for _, section := range layout.Sections {
		columns := section.Columns
		if columns < 1 {
			columns = 1
		}
		items := make([]map[string]any, 0, len(section.Fields))
		visibleFieldLabels := make(map[string]string)
		for _, fieldName := range section.Fields {
			resolved, readable := resolveReadableLayoutFieldPath(
				spec.APIName, fieldName, authorityIndex,
			)
			if !readable {
				continue
			}
			field := resolved.Field
			permission := fieldPermissions[strings.ToLower(fieldName)]
			if !resolved.LookupExpansion && !permission.CanRead {
				continue
			}
			rootPermission := fieldPermissions[strings.ToLower(resolved.RootField.APIName)]
			behavior := authoredLayoutFieldBehavior(section.FieldBehaviors, fieldName, mode)
			_, createRequired := createRequiredFields[field.APIName]
			if behavior == "hidden" && !(mode == "Create" && createRequired && !resolved.LookupExpansion) {
				continue
			}
			label := resolved.Label
			if customLabel := section.FieldLabels[fieldName]; customLabel != "" {
				label = customLabel
				visibleFieldLabels[fieldName] = customLabel
			}
			required := field.Required && !resolved.LookupExpansion
			if behavior == "required" && !resolved.LookupExpansion {
				switch mode {
				case "Create":
					required = required || (objectPermission.CanCreate && permission.CanCreate && field.Createable)
				case "Edit":
					required = required || (objectPermission.CanEdit && permission.CanEdit && field.Updateable)
				}
			}
			component := map[string]any{"componentType": "Field", "apiName": resolved.APIName}
			if resolved.LookupExpansion {
				component["details"] = map[string]any{
					"lookupExpansion": true,
					"parentField":     strings.Split(resolved.APIName, ".")[0],
					"displayField":    field.APIName,
					"editable":        strings.EqualFold(field.APIName, "Name"),
				}
			}
			editableExpansionForNew := resolved.LookupExpansion && strings.EqualFold(field.APIName, "Name") &&
				objectPermission.CanCreate && rootPermission.CanCreate && resolved.RootField.Createable
			editableExpansionForUpdate := resolved.LookupExpansion && strings.EqualFold(field.APIName, "Name") &&
				objectPermission.CanEdit && rootPermission.CanEdit && resolved.RootField.Updateable
			if resolved.LookupExpansion {
				component["details"].(map[string]any)["editable"] =
					editableExpansionForNew || editableExpansionForUpdate
			}
			items = append(items, map[string]any{
				"label":            label,
				"layoutComponents": []map[string]any{component},
				"required":         required,
				"editableForNew": editableExpansionForNew ||
					(objectPermission.CanCreate && permission.CanCreate && field.Createable),
				"editableForUpdate": editableExpansionForUpdate ||
					(objectPermission.CanEdit && permission.CanEdit && field.Updateable),
			})
		}
		rows := make([]map[string]any, 0, (len(items)+columns-1)/columns)
		for start := 0; start < len(items); start += columns {
			end := start + columns
			if end > len(items) {
				end = len(items)
			}
			rows = append(rows, map[string]any{"layoutItems": items[start:end]})
		}
		projected := map[string]any{
			"id": section.ID, "heading": section.Label, "columns": columns,
			"layoutRows": rows, "useHeading": true, "collapsible": true, "isCollapsed": false,
		}
		if section.Tab != "" {
			projected["tab"] = section.Tab
		}
		if section.Type != "" {
			projected["type"] = section.Type
		}
		if section.ComponentName != "" {
			projected["componentName"] = section.ComponentName
		}
		if section.ComponentConfig != nil {
			projected["componentConfig"] = section.ComponentConfig
		}
		if len(visibleFieldLabels) > 0 {
			projected["fieldLabels"] = visibleFieldLabels
		}
		if section.VisibilityCondition != "" {
			projected["visibilityCondition"] = section.VisibilityCondition
		}
		sections = append(sections, projected)
	}
	relatedLists := make([]map[string]any, 0, len(layout.RelatedLists))
	for _, related := range layout.RelatedLists {
		projected := map[string]any{
			"id": related.ID, "label": related.Label, "secondaryLabel": related.SecondaryLabel,
			"objectApiName": related.ObjectAPIName, "lookupField": related.LookupField,
			"fields": append([]string(nil), related.Fields...),
		}
		if related.PrefillMappings != nil {
			projected["prefillMappings"] = related.PrefillMappings
		}
		if related.Resolution != nil {
			projected["resolution"] = related.Resolution
		}
		relatedLists = append(relatedLists, projected)
	}
	result := map[string]any{
		"id": layout.ID, "layoutName": layout.LayoutName, "objectApiName": spec.APIName,
		"layoutType": "Full", "mode": mode, "sections": sections,
		"compactLayout": append([]string(nil), layout.CompactLayout...),
		"tabs":          append([]string(nil), layout.Tabs...), "relatedLists": relatedLists,
		"headerActions": append([]appLayoutAction{}, layout.HeaderActions...),
		"quickActions":  append([]appLayoutAction{}, layout.QuickActions...),
	}
	if len(layout.RecordPanels) > 0 {
		result["recordPanels"] = append([]appRecordPanel(nil), layout.RecordPanels...)
	}
	if layout.HeaderActionsAuthored || len(layout.HeaderActions) > 0 {
		result["headerActionsAuthored"] = true
	}
	if layout.QuickActionsAuthored || len(layout.QuickActions) > 0 {
		result["quickActionsAuthored"] = true
	}
	if len(layout.RecordStates) > 0 {
		result["recordStates"] = append([]appRecordStateRule(nil), layout.RecordStates...)
	}
	if len(layout.PrimaryHeaderActions) > 0 {
		result["primaryHeaderActions"] = append([]string(nil), layout.PrimaryHeaderActions...)
	}
	return result, nil
}

func authoredLayoutFieldBehavior(
	behaviors map[string]map[string]string,
	fieldName string,
	mode string,
) string {
	var modeKey string
	switch mode {
	case "Create":
		modeKey = "create"
	case "Edit":
		modeKey = "edit"
	case "View":
		modeKey = "detail"
	default:
		return ""
	}
	value := behaviors[fieldName][modeKey]
	if value != "hidden" && value != "required" {
		return ""
	}
	if mode == "View" && value == "required" {
		return ""
	}
	return value
}

func (service *Service) readRecordUIAuthority(
	ctx context.Context,
	bearer string,
	objectID string,
	recordTypeID string,
) (recordUIAuthority, error) {
	authority, err := service.readRecordUICatalogAuthority(ctx, objectID)
	if err != nil {
		return recordUIAuthority{}, err
	}
	authority.layout, authority.layoutReceipts, err = service.resolvePageLayout(
		ctx, bearer, authority.view, authority.spec.APIName, recordTypeID,
	)
	if err != nil {
		return recordUIAuthority{}, err
	}
	authority, err = service.readRecordUIListFields(ctx, bearer, authority)
	if err != nil {
		return recordUIAuthority{}, err
	}
	selection, err := recordUILayoutAuthoritySelection(authority)
	if err != nil {
		return recordUIAuthority{}, err
	}
	return service.completeRecordUIAuthority(ctx, bearer, authority, selection)
}

func (service *Service) readRecordUIListFields(
	ctx context.Context,
	bearer string,
	authority recordUIAuthority,
) (recordUIAuthority, error) {
	objectIDs := pageLayoutListFieldObjectIDs(authority.spec.APIName, authority.specs, authority.layout)
	fields, receipts, err := service.readCurrentObjectListFields(
		ctx, bearer, authority.view, objectIDs,
	)
	if err != nil {
		return recordUIAuthority{}, err
	}
	authority.listFields = fields
	authority.listFieldReceipts = receipts
	return authority, nil
}

func recordUILayoutAuthoritySelection(
	authority recordUIAuthority,
) (*permissionsemantic.Selection, error) {
	objectInfoSelection, err := recordUIObjectInfoPermissionSelection(authority.spec, authority.specs)
	if err != nil {
		return nil, err
	}
	if authority.layout.ID == "" {
		return nil, unavailablePageLayout()
	}
	layoutSelection, err := pageLayoutPermissionSelection(authority.layout, authority.specs)
	if err != nil {
		return nil, err
	}
	return boundedRecordUIPermissionSelection(append(
		append([]string(nil), objectInfoSelection.ResourceTypes...),
		layoutSelection.ResourceTypes...,
	))
}

// recordUIAuthoritySelection asks for the entire L1 projection authority in
// one bounded permission read. RecordView authority is deliberately root-only;
// it cannot authorize layout-related child resources or action permission
// guards that L1 assembled from Catalog metadata.
func recordUIAuthoritySelection(
	authority recordUIAuthority,
) (*permissionsemantic.Selection, error) {
	selection, err := recordUILayoutAuthoritySelection(authority)
	if err != nil {
		return nil, err
	}
	selection.Sections = []permissionsemantic.Section{
		permissionsemantic.SectionSystemPermissions,
		permissionsemantic.SectionCustomPermissions,
		permissionsemantic.SectionObjectPermissions,
		permissionsemantic.SectionFieldPermissions,
		permissionsemantic.SectionRecordTypePermissions,
	}
	return selection, nil
}

func boundedRecordUIPermissionSelection(resourceTypes []string) (*permissionsemantic.Selection, error) {
	resources := make(map[string]string, len(resourceTypes))
	for _, resourceType := range resourceTypes {
		resourceType = strings.TrimSpace(resourceType)
		if resourceType == "" {
			continue
		}
		resources[strings.ToLower(resourceType)] = resourceType
	}
	if len(resources) == 0 || len(resources) > permissionsemantic.MaxSelectedResourceTypes {
		return nil, unavailableSearch()
	}
	selected := make([]string, 0, len(resources))
	for _, resourceType := range resources {
		selected = append(selected, resourceType)
	}
	sort.Strings(selected)
	return &permissionsemantic.Selection{
		Sections: []permissionsemantic.Section{
			permissionsemantic.SectionObjectPermissions,
			permissionsemantic.SectionFieldPermissions,
			permissionsemantic.SectionRecordTypePermissions,
		},
		ResourceTypes: selected,
	}, nil
}

func recordUIObjectInfoPermissionSelection(
	spec searchObjectSpec,
	specs map[string]searchObjectSpec,
) (*permissionsemantic.Selection, error) {
	resources := []string{spec.APIName}
	for _, field := range spec.Fields {
		for _, referenced := range field.ReferenceTo {
			target, found := specs[strings.ToLower(referenced)]
			if !found || !describeAuthorityIsCurrent(target) {
				continue
			}
			resources = append(resources, target.APIName)
		}
	}
	return boundedRecordUIPermissionSelection(resources)
}

func (service *Service) readRecordUICatalogAuthority(
	ctx context.Context,
	objectID string,
) (recordUIAuthority, error) {
	view, specs, err := readObjectSpecs(ctx, service.appMetadataV3)
	if err != nil {
		return recordUIAuthority{}, err
	}
	spec, found := specs[strings.ToLower(objectID)]
	if !found || !describeAuthorityIsCurrent(spec) {
		return recordUIAuthority{}, &commonsemantic.Failure{Category: commonsemantic.FailureMissing}
	}
	revision := view.Receipts.CatalogRevision
	return recordUIAuthority{view: view, specs: specs, spec: spec, revision: revision}, nil
}

func (service *Service) completeRecordUIAuthority(
	ctx context.Context,
	bearer string,
	authority recordUIAuthority,
	selection *permissionsemantic.Selection,
) (recordUIAuthority, error) {
	permissionRequest := permissionsemantic.Request{
		MinCatalogRevision: &authority.revision,
		Selection:          selection,
	}
	permissions, err := service.permissionMetadata.readEffective(
		ctx, bearer, permissionRequest,
	)
	if err != nil {
		return recordUIAuthority{}, err
	}
	if err := permissionsemantic.ValidateResponseForRequest(permissionRequest, permissions); err != nil {
		return recordUIAuthority{}, err
	}
	if err := validateSearchMetadataAuthority(
		ctx, service.reconcileAppMetadataV3, authority.view, permissions.Receipts,
	); err != nil {
		return recordUIAuthority{}, err
	}
	if authority.layout.ID != "" &&
		!sameAppMetadataAuthority(authority.layoutReceipts, permissions.Receipts) {
		return recordUIAuthority{}, unavailablePageLayout()
	}
	if authority.listFields != nil &&
		!sameAppMetadataAuthority(authority.listFieldReceipts, permissions.Receipts) {
		return recordUIAuthority{}, unavailablePageLayout()
	}
	objectPermission, readable := objectPermissionForDescribe(permissions, authority.spec.APIName)
	if !readable {
		return recordUIAuthority{}, &commonsemantic.Failure{Category: commonsemantic.FailureForbidden}
	}
	recordTypes, err := projectRecordTypes(authority.view, permissions, authority.spec.APIName)
	if err != nil {
		return recordUIAuthority{}, err
	}
	authority.objectPermission = objectPermission
	authority.permissions = permissions
	authority.recordTypes = recordTypes
	return authority, nil
}

func (authority recordUIAuthority) authorizedLayout() (
	appPageLayout,
	pageLayoutAuthorityIndex,
	error,
) {
	if authority.layout.ID == "" {
		return appPageLayout{}, pageLayoutAuthorityIndex{}, unavailablePageLayout()
	}
	index := newPageLayoutAuthorityIndex(authority.specs, authority.permissions)
	layout, err := authorizePageLayoutWithIndex(
		authority.layout, authority.specs, authority.permissions, index, authority.listFields,
	)
	return layout, index, err
}

func (service *Service) readRecordUI(
	ctx context.Context,
	bearer string,
	route recordUIRoute,
	objectID string,
) (map[string]any, error) {
	if service.authorizeRuntime != nil {
		capabilities := []string{
			permissionsemantic.CapabilityName,
			pagelayoutsemantic.CapabilityName,
			recordsemantic.CapabilityName,
		}
		slices.Sort(capabilities)
		delegated, err := service.authorizeRuntime(ctx, bearer, capabilities)
		if err != nil {
			return nil, err
		}
		ctx = delegated
	}
	authority, err := service.readRecordUICatalogAuthority(ctx, objectID)
	if err != nil {
		return nil, err
	}
	reads := make([]recordsemantic.ReadResponse, len(route.recordIDs))
	recordTypeID := ""
	for index, recordID := range route.recordIDs {
		read, readErr := service.recordView.Read(ctx, bearer, recordsemantic.ReadRequest{
			ObjectID:           authority.spec.APIName,
			Selector:           recordsemantic.Selector{Kind: recordsemantic.SelectorRecordID, RecordID: recordID},
			Projection:         recordsemantic.ProjectionEnrichedView,
			MinCatalogRevision: &authority.revision,
		})
		if readErr != nil {
			return nil, readErr
		}
		if validateErr := validateSearchMetadataAuthority(
			ctx, service.reconcileAppMetadataV3, authority.view, read.Receipts,
		); validateErr != nil {
			return nil, validateErr
		}
		if accessErr := validateRecordUIReadAccess(read); accessErr != nil {
			return nil, accessErr
		}
		currentRecordTypeID, typeErr := recordUIReadRecordTypeID(read)
		if typeErr != nil {
			return nil, typeErr
		}
		if index > 0 && currentRecordTypeID != recordTypeID {
			// The current L1 flat layout wire has no record->layout binding. Never
			// return one caller-effective layout for heterogeneous records.
			return nil, unavailablePageLayout()
		}
		recordTypeID = currentRecordTypeID
		reads[index] = read
	}
	authority.layout, authority.layoutReceipts, err = service.resolvePageLayout(
		ctx, bearer, authority.view, authority.spec.APIName, recordTypeID,
	)
	if err != nil {
		return nil, err
	}
	authority, err = service.readRecordUIListFields(ctx, bearer, authority)
	if err != nil {
		return nil, err
	}
	selection, err := recordUIAuthoritySelection(authority)
	if err != nil {
		return nil, err
	}
	authority, err = service.completeRecordUIAuthority(ctx, bearer, authority, selection)
	if err != nil {
		return nil, err
	}
	// Each branch was valid against the immutable Catalog view, but a
	// security-only refresh may have reconciled one branch to a newer epoch.
	// Never project record data and permission metadata from different
	// authorities into one public response.
	for _, read := range reads {
		if !sameAppMetadataAuthority(authority.permissions.Receipts, read.Receipts) {
			return nil, &commonsemantic.Failure{Category: commonsemantic.FailureUnavailable}
		}
	}
	layout, layoutAuthority, err := authority.authorizedLayout()
	if err != nil {
		return nil, err
	}
	records := make(map[string]any, len(route.recordIDs))
	for index, recordID := range route.recordIDs {
		read := reads[index]
		entry := RecordAccess{
			HasReadAccess: read.Access.Read, HasEditAccess: read.Access.Update,
			HasDeleteAccess: read.Access.Delete, IsLocked: read.Access.Locked,
		}
		decorateRecordViewReadAttachments(ctx, service.recordView, bearer, recordViewRoute{
			version: route.version, objectID: authority.spec.APIName, recordID: recordID,
		}, &read)
		projected, projectErr := projectRecordUIRecord(
			authority.spec.APIName, read, entry, authority.recordTypes,
		)
		if projectErr != nil {
			return nil, projectErr
		}
		records[recordID] = projected
	}
	projectedLayout, err := projectRecordUILayout(
		layout, authority.spec, layoutAuthority, authority.objectPermission, authority.permissions, "View",
	)
	if err != nil {
		return nil, err
	}
	response := map[string]any{
		"records": records,
		"layouts": map[string]any{authority.spec.APIName: map[string]any{layout.ID: projectedLayout}},
		"objectInfos": map[string]any{authority.spec.APIName: projectRecordUIObjectInfo(
			authority.spec, authority.objectPermission, authority.permissions, authority.recordTypes,
			authority.listFields[strings.ToLower(authority.spec.APIName)],
		)},
	}
	actionView, effective, err := standardActionViewFromV3(authority.view)
	if err != nil {
		return nil, err
	}
	hasActionComponent := false
	for _, component := range actionView.Components {
		if component.ComponentKind == invocableActionKind {
			hasActionComponent = true
			break
		}
	}
	if !hasActionComponent {
		return response, nil
	}
	definitions, err := standardActionDefinitionsFromViewProfile(actionView, !effective)
	if err != nil {
		return nil, err
	}
	response["standardActions"] = map[string]any{
		"actions": standardActionList(definitions, authority.permissions, authority.spec.APIName, route.version),
	}
	return response, nil
}

func recordUILayoutMode(request *http.Request) (string, bool) {
	switch strings.ToLower(strings.TrimSpace(request.URL.Query().Get("type"))) {
	case "", "view":
		return "View", true
	case "create":
		return "Create", true
	case "edit":
		return "Edit", true
	default:
		return "", false
	}
}

func (service *Service) serveRecordUIMetadata(writer http.ResponseWriter, request *http.Request) bool {
	route, matched := matchRecordUIMetadataRoute(request.URL.EscapedPath())
	if !matched || service.permissionMetadata == nil || service.appMetadataV3 == nil ||
		(route.layout && 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
	}
	mode := "View"
	recordTypeID := ""
	if route.layout {
		recordTypeID = request.URL.Query().Get("recordTypeId")
		if !validPageLayoutRecordTypeID(recordTypeID, true) {
			service.localRejected.Add(1)
			service.failures.Add(1)
			writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeInvalidRequest, "Invalid Record Type ID")
			return true
		}
		var valid bool
		mode, valid = recordUILayoutMode(request)
		if !valid {
			service.localRejected.Add(1)
			service.failures.Add(1)
			writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeInvalidRequest, "Invalid layout type")
			return true
		}
	}
	service.inFlight.Add(1)
	defer service.inFlight.Add(-1)
	ctx, cancel := context.WithTimeout(request.Context(), service.requestTimeout)
	defer cancel()
	authority, err := service.readRecordUICatalogAuthority(ctx, route.objectID)
	if err == nil {
		var selection *permissionsemantic.Selection
		if route.layout {
			authority.layout, authority.layoutReceipts, err = service.resolvePageLayout(
				ctx, bearer, authority.view, authority.spec.APIName, recordTypeID,
			)
			if err == nil {
				var layoutSelection *permissionsemantic.Selection
				layoutSelection, err = pageLayoutPermissionSelection(authority.layout, authority.specs)
				if err == nil {
					selection, err = boundedRecordUIPermissionSelection(layoutSelection.ResourceTypes)
				}
			}
		} else {
			selection, err = recordUIObjectInfoPermissionSelection(authority.spec, authority.specs)
		}
		if err == nil {
			authority, err = service.readRecordUIListFields(ctx, bearer, authority)
		}
		if err == nil {
			authority, err = service.completeRecordUIAuthority(ctx, bearer, authority, selection)
		}
		if err == nil && route.layout && recordTypeID != "" {
			recordType, found := authority.recordTypes.entry(recordTypeID)
			if !found || !recordType.Available {
				err = &commonsemantic.Failure{Category: commonsemantic.FailureMissing}
			}
		}
	}
	if err != nil {
		service.failures.Add(1)
		writeRuntimeFailure(writer, err)
		return true
	}
	var response map[string]any
	if route.layout {
		layout, layoutAuthority, layoutErr := authority.authorizedLayout()
		if layoutErr != nil {
			service.failures.Add(1)
			writeRuntimeFailure(writer, layoutErr)
			return true
		}
		response, err = projectRecordUILayout(
			layout, authority.spec, layoutAuthority, authority.objectPermission, authority.permissions, mode,
		)
		if err != nil {
			service.failures.Add(1)
			writeRuntimeFailure(writer, err)
			return true
		}
	} else {
		response = projectRecordUIObjectInfo(
			authority.spec, authority.objectPermission, authority.permissions, authority.recordTypes,
			authority.listFields[strings.ToLower(authority.spec.APIName)],
		)
	}
	service.successes.Add(1)
	writeJSON(writer, http.StatusOK, response)
	return true
}

func (service *Service) serveRecordUI(writer http.ResponseWriter, request *http.Request) bool {
	route, matched := matchRecordUIRoute(request.URL.EscapedPath())
	if !matched || service.recordView == nil || service.permissionMetadata == nil ||
		service.appMetadataV3 == 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
	}
	objectID := strings.TrimSpace(request.URL.Query().Get("type"))
	if !validSearchIdentifier(objectID) || len(route.recordIDs) > accesssemantic.MaxRecords {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeInvalidRequest, "Invalid record UI request")
		return true
	}
	service.inFlight.Add(1)
	defer service.inFlight.Add(-1)
	ctx, cancel := context.WithTimeout(request.Context(), service.requestTimeout)
	defer cancel()
	response, err := service.readRecordUI(ctx, bearer, route, objectID)
	if err != nil {
		service.failures.Add(1)
		writeRuntimeFailure(writer, err)
		return true
	}
	service.successes.Add(1)
	writeJSON(writer, http.StatusOK, response)
	return true
}
