package dialectsfcrm

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/nexuscrm/dialect-sf-crm/wire"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/dialect"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestUIAPIRecordUIProjectsInternalErrorsWithoutLeak(t *testing.T) {
	secretErr := uiAPITestInternalError("driver detail: SELECT * FROM Account")
	resp, err := UIAPIRecordUINeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"recordIds": "rec-1"},
	}, uiAPIDeps(&uiAPITestContracts{objectTypeErr: secretErr}, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusInternalServerError {
		t.Fatalf("status = %d, want 500", resp.Status)
	}
	body := mustJSON(t, resp.Body)
	if strings.Contains(body, "SELECT *") || strings.Contains(body, "driver detail") {
		t.Fatalf("internal detail leaked: %s", body)
	}
	assertBody(t, "ui api internal", resp.Body,
		mustJSON(t, wire.SFErrorResponse(wire.SFCodeInternalError, uiAPIInternalErrorMessage)),
		`[{"errorCode":"INTERNAL_ERROR","message":"An internal server error occurred. Contact your system administrator."}]`)
}

func TestUIAPIRecordUIAssemblesL1PayloadAndFileURLPrefix(t *testing.T) {
	stub := newUIAPITestContracts()
	decorator := &uiAPITestDecorator{}

	resp, err := uiAPIRecordUINeutralHandler(context.Background(), &dialect.Request{
		Version: "v62.0",
		Params:  map[string]string{"recordIds": "001-a"},
		Query:   map[string][]string{"type": {"Account"}},
	}, uiAPIDeps(stub, nil), decorator, nil)
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	if decorator.prefix != "/services/data/v62.0/files" {
		t.Fatalf("decorator prefix = %q", decorator.prefix)
	}
	body := mustJSON(t, resp.Body)
	for _, want := range []string{
		`"objectInfos":{"Account":`,
		`"apiName":"Account"`,
		`"FileRef":{"displayValue":null,"value":"/services/data/v62.0/files/file-1"}`,
		`"layouts":{"Account":`,
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("record-ui body missing %s: %s", want, body)
		}
	}
}

func TestUIAPIRecordUILookupsUseRetrieveManyPerTargetObject(t *testing.T) {
	stub := newUIAPITestContracts()
	stub.schemas = map[string]*models.ObjectMetadata{
		"User":       uiAPILookupTestSchema("User"),
		"Account":    uiAPILookupTestSchema("Account"),
		"RecordType": uiAPILookupTestSchema("RecordType"),
	}
	stub.recordTypes = []*models.RecordType{{ID: "012-business", Name: "Business"}}
	stub.records["005-owner"] = contract.Record{constants.FieldName: "Owner User"}
	stub.records["005-editor"] = contract.Record{constants.FieldName: "Editor User"}
	stub.records["001-parent"] = contract.Record{constants.FieldName: "Parent Account"}

	uiRecord := &uiAPIRecord{
		ApiName: "Opportunity",
		Fields: map[string]uiAPIFieldValue{
			"OwnerId":          {Value: "005-owner"},
			"CreatedById":      {Value: "005-owner"},
			"LastModifiedById": {Value: "005-editor"},
			"AccountId":        {Value: "001-parent"},
			"RecordTypeId":     {Value: "012-business"},
		},
	}
	lookups := []uiAPILookupTarget{
		{fieldName: "OwnerId", relName: "Owner", targetObj: "User", targetID: "005-owner"},
		{fieldName: "CreatedById", relName: "CreatedBy", targetObj: "User", targetID: "005-owner"},
		{fieldName: "LastModifiedById", relName: "LastModifiedBy", targetObj: "User", targetID: "005-editor"},
		{fieldName: "AccountId", relName: "Account", targetObj: "Account", targetID: "001-parent"},
		{fieldName: "RecordTypeId", relName: "RecordType", sourceObj: "Opportunity", targetObj: constants.ObjectRecordType, targetID: "012-business"},
	}

	service := &uiAPIService{records: stub, metadata: stub, catalog: stub}
	if err := service.resolveRecordLookups(context.Background(), lookups, uiRecord, &models.UserSession{ID: "005-user"}); err != nil {
		t.Fatalf("resolveRecordLookups() error = %v", err)
	}

	wantBatchCalls := []uiAPIRetrieveManyCall{
		{objectName: "User", ids: []string{"005-owner", "005-editor"}},
		{objectName: "Account", ids: []string{"001-parent"}},
	}
	if !reflect.DeepEqual(stub.retrieveManyCalls, wantBatchCalls) {
		t.Fatalf("RetrieveMany calls = %#v, want %#v", stub.retrieveManyCalls, wantBatchCalls)
	}
	if len(stub.retrieveCalls) != 0 {
		t.Fatalf("Retrieve calls = %#v, want none", stub.retrieveCalls)
	}
	for fieldName, want := range map[string]string{
		"OwnerId": "Owner User", "CreatedById": "Owner User", "LastModifiedById": "Editor User",
		"AccountId": "Parent Account", "RecordTypeId": "Business",
	} {
		if got := uiRecord.Fields[fieldName].DisplayValue; got == nil || *got != want {
			t.Fatalf("%s display value = %v, want %q", fieldName, got, want)
		}
	}
}

func TestUIAPIRecordUILookupBatchFailureFallsBackToPointReads(t *testing.T) {
	stub := newUIAPITestContracts()
	stub.schemas = map[string]*models.ObjectMetadata{"User": uiAPILookupTestSchema("User")}
	stub.records["005-owner"] = contract.Record{constants.FieldName: "Owner User"}
	stub.records["005-editor"] = contract.Record{constants.FieldName: "Editor User"}
	stub.retrieveManyErrs = map[string]error{"User": errors.New("batch unavailable")}

	uiRecord := &uiAPIRecord{
		ApiName: "Opportunity",
		Fields: map[string]uiAPIFieldValue{
			"OwnerId":          {Value: "005-owner"},
			"LastModifiedById": {Value: "005-editor"},
		},
	}
	lookups := []uiAPILookupTarget{
		{fieldName: "OwnerId", relName: "Owner", targetObj: "User", targetID: "005-owner"},
		{fieldName: "LastModifiedById", relName: "LastModifiedBy", targetObj: "User", targetID: "005-editor"},
	}

	service := &uiAPIService{records: stub, metadata: stub}
	if err := service.resolveRecordLookups(context.Background(), lookups, uiRecord, &models.UserSession{ID: "005-user"}); err != nil {
		t.Fatalf("resolveRecordLookups() error = %v", err)
	}

	wantBatchCalls := []uiAPIRetrieveManyCall{{objectName: "User", ids: []string{"005-owner", "005-editor"}}}
	if !reflect.DeepEqual(stub.retrieveManyCalls, wantBatchCalls) {
		t.Fatalf("RetrieveMany calls = %#v, want %#v", stub.retrieveManyCalls, wantBatchCalls)
	}
	wantPointCalls := []uiAPIRetrieveCall{
		{objectName: "User", id: "005-owner"},
		{objectName: "User", id: "005-editor"},
	}
	if !reflect.DeepEqual(stub.retrieveCalls, wantPointCalls) {
		t.Fatalf("Retrieve fallback calls = %#v, want %#v", stub.retrieveCalls, wantPointCalls)
	}
	for fieldName, want := range map[string]string{"OwnerId": "Owner User", "LastModifiedById": "Editor User"} {
		if got := uiRecord.Fields[fieldName].DisplayValue; got == nil || *got != want {
			t.Fatalf("%s display value = %v, want %q", fieldName, got, want)
		}
	}
}

func uiAPILookupTestSchema(objectName string) *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName: objectName,
		Fields:  []models.FieldMetadata{{APIName: constants.FieldName, Type: constants.FieldTypeString, IsNameField: true}},
	}
}

func TestUIAPIRecordUICachesPolicyAccessWithinRequest(t *testing.T) {
	stub := newUIAPITestContracts()

	resp, err := uiAPIRecordUINeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"recordIds": "001-a"},
		Query:   map[string][]string{"type": {"Account"}},
	}, uiAPIDeps(stub, nil), nil, nil)
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}

	calls := stub.accessCallSnapshot()
	want := map[string]int{
		"account|fields=false|recordTypes=false": 1,
		"account|fields=true|recordTypes=false":  1,
		"account|fields=true|recordTypes=true":   1,
	}
	if len(calls) != len(want) {
		t.Fatalf("policy access keys = %+v, want %+v", calls, want)
	}
	for key, wantCount := range want {
		if got := calls[key]; got != wantCount {
			t.Fatalf("policy access %s = %d, want %d; calls=%+v", key, got, wantCount, calls)
		}
	}
}

func TestUIAPIRecordUICachesDescribeWithinRequest(t *testing.T) {
	stub := newUIAPITestContracts()

	resp, err := uiAPIRecordUINeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"recordIds": "001-a"},
		Query:   map[string][]string{"type": {"Account"}},
	}, uiAPIDeps(stub, nil), nil, nil)
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}

	calls := stub.describeCallSnapshot()
	if got := calls["account"]; got != 1 {
		t.Fatalf("Describe(Account) calls = %d, want 1; calls=%+v", got, calls)
	}
}

func TestUIAPIRecordUIFiltersRelatedListsWithStableOrderAndAccessCache(t *testing.T) {
	stub := newUIAPITestContracts()
	stub.schemas = map[string]*models.ObjectMetadata{
		"Account": {
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Label: "Account Name", Type: constants.FieldTypeString, Createable: true, Updateable: true},
			},
		},
		"Contact": {
			APIName: "Contact",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Label: "Contact Name", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: "AccountId", Label: "Account", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}, Createable: true, Updateable: true},
			},
		},
		"Case": {
			APIName: "Case",
			Fields: []models.FieldMetadata{
				{APIName: "Subject", Label: "Subject", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: "AccountId", Label: "Account", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}, Createable: true, Updateable: true},
			},
		},
	}
	stub.schema = stub.schemas["Account"]
	stub.layout.RelatedLists = []models.RelatedListConfig{
		{ID: "contacts", Label: "Contacts", ObjectAPIName: "Contact", LookupField: "AccountId"},
		{ID: "cases", Label: "Cases", ObjectAPIName: "Case", LookupField: "AccountId", Fields: []string{"Subject"}},
		{ID: "contacts-again", Label: "Contacts Again", ObjectAPIName: "Contact", LookupField: "AccountId", Fields: []string{constants.FieldName}},
	}

	resp, err := uiAPIRecordUINeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"recordIds": "001-a"},
		Query:   map[string][]string{"type": {"Account"}},
	}, uiAPIDeps(stub, nil), nil, nil)
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	recordUI, ok := resp.Body.(*uiAPIRecordUI)
	if !ok {
		t.Fatalf("body type = %T, want *uiAPIRecordUI", resp.Body)
	}
	layouts := recordUI.Layouts["Account"]
	if len(layouts) != 1 {
		t.Fatalf("layouts = %+v, want one Account layout", layouts)
	}
	var relatedLists []uiAPIRelatedListInfo
	for _, layout := range layouts {
		relatedLists = layout.RelatedLists
	}
	if got := len(relatedLists); got != 3 {
		t.Fatalf("related list count = %d, want 3; lists=%+v", got, relatedLists)
	}
	wantIDs := []string{"contacts", "cases", "contacts-again"}
	for i, want := range wantIDs {
		if got := relatedLists[i].Id; got != want {
			t.Fatalf("related list %d id = %q, want %q; lists=%+v", i, got, want, relatedLists)
		}
	}

	calls := stub.accessCallSnapshot()
	if got := calls["contact|fields=true|recordTypes=true"]; got != 1 {
		t.Fatalf("Contact effective access calls = %d, want 1; calls=%+v", got, calls)
	}
	if got := calls["case|fields=true|recordTypes=true"]; got != 1 {
		t.Fatalf("Case effective access calls = %d, want 1; calls=%+v", got, calls)
	}
}

func TestUIAPIRecordUIKeepsFieldHistoryRelatedListUsingParentAccess(t *testing.T) {
	stub := newUIAPITestContracts()
	stub.layout.RelatedLists = []models.RelatedListConfig{{
		ID:            "history",
		Label:         "History",
		ObjectAPIName: "AccountHistory",
		LookupField:   "AccountId",
		Fields:        []string{"Field", "OldValue", "NewValue", "CreatedDate", "CreatedById"},
	}}

	resp, err := uiAPIRecordUINeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"recordIds": "001-a"},
		Query:   map[string][]string{"type": {"Account"}},
	}, uiAPIDeps(stub, nil), nil, nil)
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	recordUI, ok := resp.Body.(*uiAPIRecordUI)
	if !ok {
		t.Fatalf("body type = %T, want *uiAPIRecordUI", resp.Body)
	}
	layouts := recordUI.Layouts["Account"]
	if len(layouts) != 1 {
		t.Fatalf("Account layouts = %+v, want one layout with History", layouts)
	}
	var relatedLists []uiAPIRelatedListInfo
	for _, layout := range layouts {
		relatedLists = layout.RelatedLists
	}
	if len(relatedLists) != 1 {
		t.Fatalf("Account layouts = %+v, want one layout with History", layouts)
	}
	history := relatedLists[0]
	if history.ObjectApiName != "AccountHistory" || history.LookupField != "AccountId" {
		t.Fatalf("History related list = %+v", history)
	}
	wantFields := []string{"Field", "OldValue", "NewValue", "CreatedDate", "CreatedById"}
	if !reflect.DeepEqual(history.Fields, wantFields) {
		t.Fatalf("History fields = %v, want %v", history.Fields, wantFields)
	}
	if got := stub.accessCallSnapshot()["accounthistory|fields=true|recordTypes=true"]; got != 0 {
		t.Fatalf("AccountHistory effective access calls = %d, want parent Account access", got)
	}
	if got := uiAPIFieldHistoryAccess("Account", &contract.EffectiveObjectAccess{
		Object: contract.ObjectAccess{Read: true},
	}, "ContactHistory"); got != nil {
		t.Fatalf("cross-parent History access = %+v, want nil", got)
	}
}

func TestUIAPIRecordUICurrencyDisplayDefaultsToUSDWhenCurrencyISOCodeMissing(t *testing.T) {
	stub := newUIAPITestContracts()
	stub.schema.Fields = append(stub.schema.Fields, models.FieldMetadata{
		APIName:    "AnnualRevenue",
		Label:      "Annual Revenue",
		Type:       constants.FieldTypeCurrency,
		Createable: true,
		Updateable: true,
	})
	stub.records["001-a"]["AnnualRevenue"] = 140.0

	resp, err := uiAPIRecordUINeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"recordIds": "001-a"},
		Query:   map[string][]string{"type": {"Account"}},
	}, uiAPIDeps(stub, nil), nil, nil)
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	recordUI, ok := resp.Body.(*uiAPIRecordUI)
	if !ok {
		t.Fatalf("body type = %T, want *uiAPIRecordUI", resp.Body)
	}
	field := recordUI.Records["001-a"].Fields["AnnualRevenue"]
	if field.DisplayValue == nil {
		t.Fatalf("AnnualRevenue displayValue is nil; body=%s", mustJSON(t, resp.Body))
	}
	if got := *field.DisplayValue; got != "$140.00" {
		t.Fatalf("AnnualRevenue displayValue = %q, want $140.00; body=%s", got, mustJSON(t, resp.Body))
	}
}

func TestUIAPIRecordUILimitAndRecordAccessEmptyEnvelope(t *testing.T) {
	ids := make([]string, 201)
	for i := range ids {
		ids[i] = "001"
	}
	resp, err := UIAPIRecordUINeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"recordIds": strings.Join(ids, ",")},
	}, uiAPIDeps(newUIAPITestContracts(), nil))
	if err != nil {
		t.Fatalf("record-ui handler error: %v", err)
	}
	assertBody(t, "record ui limit", resp.Body,
		mustJSON(t, wire.SFErrorResponse(wire.SFCodeLimitExceeded, "Too many record IDs (max 200)")),
		`[{"errorCode":"LIMIT_EXCEEDED","message":"Too many record IDs (max 200)"}]`)

	resp, err = UIAPIBatchRecordAccessNeutralHandler(context.Background(), &dialect.Request{
		Body: []byte(`{"recordIds":[]}`),
	}, uiAPIDeps(newUIAPITestContracts(), nil))
	if err != nil {
		t.Fatalf("record-access handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("record-access status = %d, want 200", resp.Status)
	}
	assertBody(t, "record access empty", resp.Body, "", `{"recordAccess":{}}`)
}

func TestUIAPIListLayoutsInternalErrorsAreSanitized(t *testing.T) {
	secretErr := uiAPITestInternalError("metadata detail: SELECT * FROM _System_PageLayout")
	stub := newUIAPITestContracts()
	stub.listLayoutsErr = secretErr

	resp, err := UIAPIListLayoutsNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusInternalServerError {
		t.Fatalf("status = %d, want 500", resp.Status)
	}
	body := mustJSON(t, resp.Body)
	if strings.Contains(body, "SELECT *") || strings.Contains(body, "metadata detail") {
		t.Fatalf("internal detail leaked: %s", body)
	}
	assertBody(t, "layout list internal", resp.Body,
		mustJSON(t, wire.SFErrorResponse(wire.SFCodeInternalError, uiAPIInternalErrorMessage)),
		`[{"errorCode":"INTERNAL_ERROR","message":"An internal server error occurred. Contact your system administrator."}]`)
}

func TestUIAPIObjectAndLayoutMissingKeepLegacyNotFoundMessages(t *testing.T) {
	stub := newUIAPITestContracts()
	stub.schema = nil
	stub.layout = nil
	for _, tc := range []struct {
		name string
		run  func() (*dialect.Response, error)
		want string
	}{
		{
			name: "object",
			run: func() (*dialect.Response, error) {
				return UIAPIObjectInfoNeutralHandler(context.Background(), &dialect.Request{
					Params: map[string]string{"objectApiName": "Missing"},
				}, uiAPIDeps(stub, nil))
			},
			want: `[{"errorCode":"NOT_FOUND","message":"Object not found"}]`,
		},
		{
			name: "layout",
			run: func() (*dialect.Response, error) {
				return UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
					Params: map[string]string{"objectApiName": "Missing"},
				}, uiAPIDeps(stub, nil))
			},
			want: `[{"errorCode":"NOT_FOUND","message":"Layout not found"}]`,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			resp, err := tc.run()
			if err != nil {
				t.Fatalf("handler error: %v", err)
			}
			if resp.Status != http.StatusNotFound {
				t.Fatalf("status = %d, want 404", resp.Status)
			}
			assertBody(t, tc.name, resp.Body, "", tc.want)
		})
	}
}

func TestUIAPILayoutBySyntheticDefaultIDIgnoresAudienceAssignments(t *testing.T) {
	stub := newUIAPITestContracts()
	defaultLayout := &models.PageLayout{
		ID:            "default_Account",
		ObjectAPIName: "Account",
		LayoutName:    "Generated Default",
		Type:          "Detail",
		Sections:      []models.PageSection{{ID: "main", Label: "Main", Fields: []string{constants.FieldName}}},
	}
	assignedLayout := &models.PageLayout{
		ID:            "profile-layout",
		ObjectAPIName: "Account",
		LayoutName:    "Profile Layout",
		Type:          "Detail",
		Sections:      []models.PageSection{{ID: "profile", Label: "Profile", Fields: []string{constants.FieldName}}},
	}
	stub.layout = nil
	stub.layouts = []*models.PageLayout{assignedLayout}
	stub.resolveLayoutFunc = func(_ context.Context, _ string, user *models.UserSession, _ string) (*models.PageLayout, error) {
		if user != nil {
			return assignedLayout, nil
		}
		return defaultLayout, nil
	}

	resp, err := UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
		User:   &models.UserSession{ID: "005-user", ProfileID: "profile-special"},
		Params: map[string]string{"objectApiName": "Account"},
		Query:  map[string][]string{"layoutId": {"default_Account"}},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	if len(stub.resolveLayoutUsers) != 1 || stub.resolveLayoutUsers[0] != nil {
		t.Fatalf("ResolveLayout users = %#v, want one synthetic-default lookup without user context", stub.resolveLayoutUsers)
	}
	layout, ok := resp.Body.(*uiAPILayout)
	if !ok {
		t.Fatalf("body type = %T, want *uiAPILayout", resp.Body)
	}
	if layout.Id != "default_Account" {
		t.Fatalf("layout id = %q, want default_Account", layout.Id)
	}
	if body := mustJSON(t, resp.Body); strings.Contains(body, "profile-layout") {
		t.Fatalf("synthetic default layout response used assigned layout: %s", body)
	}
}

func TestUIAPIReadShapesFilterHiddenFields(t *testing.T) {
	const (
		hiddenField   = "Secret__c"
		hiddenLabel   = "Top Secret Field"
		hiddenDefault = "CLASSIFIED_DEFAULT"
		hiddenValue   = "CLASSIFIED_VALUE"
	)
	stub := newUIAPITestContracts()
	stub.schema.Fields = append(stub.schema.Fields, models.FieldMetadata{
		APIName:      hiddenField,
		Label:        hiddenLabel,
		Type:         constants.FieldTypeString,
		Createable:   true,
		Updateable:   true,
		DefaultValue: stringPtr(hiddenDefault),
	})
	stub.records["001-a"][hiddenField] = hiddenValue
	stub.layout.CompactLayout = []string{constants.FieldName, hiddenField}
	stub.layout.Sections = []models.PageSection{{
		ID:          "main",
		Label:       "Main",
		Columns:     2,
		Fields:      []string{constants.FieldName, hiddenField},
		FieldLabels: map[string]string{hiddenField: hiddenLabel},
	}}
	stub.listViews = []*models.ListView{{
		ID:            "AllAccounts",
		Label:         "All Accounts",
		ObjectAPIName: "Account",
		Fields:        []string{constants.FieldName, hiddenField},
	}}
	stub.fieldAccess = map[string]contract.FieldAccess{
		hiddenField: {},
	}
	deps := uiAPIDeps(stub, nil)

	for _, tc := range []struct {
		name string
		run  func() (*dialect.Response, error)
		want string
	}{
		{
			name: "object-info",
			run: func() (*dialect.Response, error) {
				return UIAPIObjectInfoNeutralHandler(context.Background(), &dialect.Request{
					Params: map[string]string{"objectApiName": "Account"},
				}, deps)
			},
			want: `"Name":`,
		},
		{
			name: "layout",
			run: func() (*dialect.Response, error) {
				return UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
					Params: map[string]string{"objectApiName": "Account"},
				}, deps)
			},
			want: `"apiName":"Name"`,
		},
		{
			name: "create-defaults",
			run: func() (*dialect.Response, error) {
				return UIAPIRecordDefaultsCreateNeutralHandler(context.Background(), &dialect.Request{
					Params: map[string]string{"objectApiName": "Account"},
				}, deps)
			},
			want: `"Name":`,
		},
		{
			name: "clone-defaults",
			run: func() (*dialect.Response, error) {
				return UIAPIRecordDefaultsCloneNeutralHandler(context.Background(), &dialect.Request{
					Params: map[string]string{"recordId": "001-a"},
					Query:  map[string][]string{"type": {"Account"}},
				}, deps)
			},
			want: `"value":"Acme"`,
		},
		{
			name: "list-info",
			run: func() (*dialect.Response, error) {
				return UIAPIListInfoNeutralHandler(context.Background(), &dialect.Request{
					Params: map[string]string{"objectApiName": "Account", "listViewApiName": "AllAccounts"},
				}, deps)
			},
			want: `"fieldApiName":"Name"`,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			resp, err := tc.run()
			if err != nil {
				t.Fatalf("handler error: %v", err)
			}
			if resp.Status != http.StatusOK {
				t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
			}
			body := mustJSON(t, resp.Body)
			if !strings.Contains(body, tc.want) {
				t.Fatalf("body missing visible marker %s: %s", tc.want, body)
			}
			for _, forbidden := range []string{hiddenField, hiddenLabel, hiddenDefault, hiddenValue} {
				if strings.Contains(body, forbidden) {
					t.Fatalf("body leaked hidden token %q: %s", forbidden, body)
				}
			}
		})
	}
}

func TestUIAPILayoutFiltersLookupExpansionFieldsAndLabelsByFLS(t *testing.T) {
	const hiddenTargetField = "Secret__c"
	parentRelationship := "Parent__r"
	stub := newUIAPITestContracts()
	stub.schema.Fields = append(stub.schema.Fields, models.FieldMetadata{
		APIName:          "Parent__c",
		Label:            "Parent",
		Type:             constants.FieldTypeReference,
		ReferenceTo:      []string{"Parent__c"},
		RelationshipName: &parentRelationship,
		Createable:       true,
		Updateable:       true,
	})
	stub.schemas = map[string]*models.ObjectMetadata{
		"Account": stub.schema,
		"Parent__c": {
			APIName: "Parent__c",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Label: "Parent Name", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: hiddenTargetField, Label: "Secret", Type: constants.FieldTypeString, Createable: true, Updateable: true},
			},
		},
	}
	stub.fieldAccessByObject = map[string]map[string]contract.FieldAccess{
		"Parent__c": {
			hiddenTargetField: {},
		},
	}
	stub.layout.Sections = []models.PageSection{{
		ID:      "main",
		Label:   "Main",
		Columns: 1,
		Fields: []string{
			constants.FieldName,
			"Parent__c." + constants.FieldName,
			"Parent__c." + hiddenTargetField,
		},
		FieldLabels: map[string]string{
			constants.FieldName:                "Account Visible",
			"Parent__c." + constants.FieldName: hiddenTargetField + " display label",
			"Parent__c." + hiddenTargetField:   "Hidden Target Label",
		},
	}}

	resp, err := UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	body := mustJSON(t, resp.Body)
	for _, want := range []string{`"apiName":"Name"`, `"apiName":"Parent__c.Name"`, "Account Visible"} {
		if !strings.Contains(body, want) {
			t.Fatalf("body missing visible lookup expansion marker %q: %s", want, body)
		}
	}
	for _, forbidden := range []string{hiddenTargetField, "Parent__c." + hiddenTargetField, "Hidden Target Label", hiddenTargetField + " display label"} {
		if strings.Contains(body, forbidden) {
			t.Fatalf("body leaked hidden lookup expansion metadata %q: %s", forbidden, body)
		}
	}
}

func TestUIAPILayoutFiltersExpressionMetadataByFLS(t *testing.T) {
	const hiddenField = "Secret__c"
	hiddenCondition := hiddenField + " == true"
	visibleCondition := constants.FieldName + " != ''"
	stub := newUIAPITestContracts()
	stub.schema.Fields = append(stub.schema.Fields, models.FieldMetadata{
		APIName:    hiddenField,
		Label:      "Secret",
		Type:       constants.FieldTypeString,
		Createable: true,
		Updateable: true,
	})
	stub.fieldAccess = map[string]contract.FieldAccess{
		hiddenField: {},
	}
	stub.layout.Sections = []models.PageSection{{
		ID:      "main",
		Label:   "Main",
		Columns: 1,
		Fields:  []string{constants.FieldName},
	}}
	stub.layout.RecordPanels = []models.RecordPanelConfig{
		{
			ID: "safe-panel", RendererID: "safeRenderer", VisibilityCondition: visibleCondition,
			Config: map[string]interface{}{"title": "Pipeline", "query": map[string]interface{}{"active": true}},
		},
		{ID: "hidden-panel", RendererID: "hiddenRenderer", VisibilityCondition: hiddenCondition},
	}
	stub.layout.HeaderActions = []models.ActionConfig{
		{Name: "safeHeader", Label: "Safe Header", Type: "Custom", VisibilityCondition: stringPtr(visibleCondition), Config: map[string]interface{}{"fieldApiName": constants.FieldName}},
		{Name: "hiddenHeader", Label: "Hidden Header", Type: "Custom", VisibilityCondition: stringPtr(hiddenCondition)},
	}
	stub.layout.QuickActions = []models.ActionConfig{
		{Name: "safeQuick", Label: "Safe Quick", Type: "Custom", Config: map[string]interface{}{"fieldApiName": constants.FieldName}},
		{Name: "hiddenQuick", Label: "Hidden Quick", Type: "Custom", Config: map[string]interface{}{"nested": map[string]interface{}{"fieldApiName": hiddenField}}},
	}
	stub.layout.RecordStates = []models.RecordStateRule{
		{ID: "safe-state", When: visibleCondition, Banner: &models.RecordStateBanner{Title: "Safe State"}},
		{ID: "hidden-state", When: hiddenCondition, Links: []models.RecordStateLink{{ID: "hidden-link", ReferenceFieldAPIName: hiddenField}}},
	}

	resp, err := UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	body := mustJSON(t, resp.Body)
	for _, want := range []string{"safe-panel", "Pipeline", "safeHeader", "safeQuick", "safe-state"} {
		if !strings.Contains(body, want) {
			t.Fatalf("body missing visible metadata %q: %s", want, body)
		}
	}
	for _, forbidden := range []string{hiddenField, "hidden-panel", "hiddenHeader", "hiddenQuick", "hidden-state"} {
		if strings.Contains(body, forbidden) {
			t.Fatalf("body leaked hidden layout metadata %q: %s", forbidden, body)
		}
	}
}

func TestUIAPILayoutFiltersActionLabelsByFLS(t *testing.T) {
	const (
		targetObject      = "Quote"
		hiddenBaseField   = "Secret__c"
		hiddenTargetField = "HiddenTarget__c"
	)
	stub := newUIAPITestContracts()
	stub.schema.Fields = append(stub.schema.Fields, models.FieldMetadata{
		APIName:    hiddenBaseField,
		Label:      "Secret",
		Type:       constants.FieldTypeString,
		Createable: true,
		Updateable: true,
	})
	stub.schemas = map[string]*models.ObjectMetadata{
		targetObject: {
			APIName: targetObject,
			Fields: []models.FieldMetadata{
				{APIName: hiddenTargetField, Label: "Hidden Target", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: "SafeTarget__c", Label: "Safe Target", Type: constants.FieldTypeString, Createable: true, Updateable: true},
			},
		},
	}
	stub.fieldAccessByObject = map[string]map[string]contract.FieldAccess{
		"Account": {
			hiddenBaseField: {},
		},
		targetObject: {
			hiddenTargetField: {},
		},
	}
	stub.layout.Sections = []models.PageSection{{
		ID:      "main",
		Label:   "Main",
		Columns: 1,
		Fields:  []string{constants.FieldName},
	}}
	stub.layout.HeaderActions = []models.ActionConfig{
		{Name: "safeHeader", Label: "Safe Header", Type: "Custom"},
		{Name: "hiddenBaseHeader", Label: "Show " + hiddenBaseField, Type: "Custom"},
	}
	stub.layout.QuickActions = []models.ActionConfig{
		{Name: "safeQuick", Label: "Safe Quick", Type: "Custom", TargetObject: stringPtr(targetObject)},
		{Name: "hiddenTargetQuick", Label: "Show " + hiddenTargetField, Type: "Custom", TargetObject: stringPtr(targetObject)},
	}

	resp, err := UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	body := mustJSON(t, resp.Body)
	for _, want := range []string{"safeHeader", "safeQuick"} {
		if !strings.Contains(body, want) {
			t.Fatalf("body missing safe action %q: %s", want, body)
		}
	}
	for _, forbidden := range []string{hiddenBaseField, hiddenTargetField, "hiddenBaseHeader", "hiddenTargetQuick"} {
		if strings.Contains(body, forbidden) {
			t.Fatalf("body leaked hidden action-label metadata %q: %s", forbidden, body)
		}
	}
}

func TestUIAPILayoutPreservesAuthoredActionPlacementSignalWhenAllFiltered(t *testing.T) {
	const hiddenField = "Secret__c"
	hiddenCondition := hiddenField + " == true"
	stub := newUIAPITestContracts()
	stub.schema.Fields = append(stub.schema.Fields, models.FieldMetadata{
		APIName:    hiddenField,
		Label:      "Secret",
		Type:       constants.FieldTypeString,
		Createable: true,
		Updateable: true,
	})
	stub.fieldAccess = map[string]contract.FieldAccess{
		hiddenField: {},
	}
	stub.layout.Sections = []models.PageSection{{
		ID:      "main",
		Label:   "Main",
		Columns: 1,
		Fields:  []string{constants.FieldName},
	}}
	stub.layout.HeaderActions = []models.ActionConfig{
		{Name: "hiddenHeader", Label: "Hidden Header", Type: "Custom", VisibilityCondition: stringPtr(hiddenCondition)},
	}
	stub.layout.QuickActions = []models.ActionConfig{
		{Name: "hiddenQuick", Label: "Hidden Quick", Type: "Custom", VisibilityCondition: stringPtr(hiddenCondition)},
	}

	resp, err := UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	body := mustJSON(t, resp.Body)
	for _, want := range []string{`"headerActions":[]`, `"quickActions":[]`, `"headerActionsAuthored":true`, `"quickActionsAuthored":true`} {
		if !strings.Contains(body, want) {
			t.Fatalf("body missing authored empty action signal %s: %s", want, body)
		}
	}
	for _, forbidden := range []string{hiddenField, "hiddenHeader", "hiddenQuick"} {
		if strings.Contains(body, forbidden) {
			t.Fatalf("body leaked hidden action metadata %q: %s", forbidden, body)
		}
	}
}

func TestUIAPILayoutPreservesAuthoredEmptyActionPlacementSignal(t *testing.T) {
	stub := newUIAPITestContracts()
	stub.layout.Sections = []models.PageSection{{
		ID:      "main",
		Label:   "Main",
		Columns: 1,
		Fields:  []string{constants.FieldName},
	}}
	stub.layout.HeaderActions = []models.ActionConfig{}
	stub.layout.QuickActions = []models.ActionConfig{}
	stub.layout.HeaderActionsAuthored = true
	stub.layout.QuickActionsAuthored = true

	resp, err := UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	body := mustJSON(t, resp.Body)
	for _, want := range []string{`"headerActions":[]`, `"quickActions":[]`, `"headerActionsAuthored":true`, `"quickActionsAuthored":true`} {
		if !strings.Contains(body, want) {
			t.Fatalf("body missing authored empty action signal %s: %s", want, body)
		}
	}
}

func TestUIAPILayoutKeepsDefaultEmptyActionPlacementsUnauthored(t *testing.T) {
	stub := newUIAPITestContracts()
	stub.layout.Sections = []models.PageSection{{
		ID:      "main",
		Label:   "Main",
		Columns: 1,
		Fields:  []string{constants.FieldName},
	}}
	stub.layout.HeaderActions = []models.ActionConfig{}
	stub.layout.QuickActions = []models.ActionConfig{}

	resp, err := UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	body := mustJSON(t, resp.Body)
	for _, want := range []string{`"headerActions":[]`, `"quickActions":[]`} {
		if !strings.Contains(body, want) {
			t.Fatalf("body missing default empty action array %s: %s", want, body)
		}
	}
	for _, forbidden := range []string{"headerActionsAuthored", "quickActionsAuthored"} {
		if strings.Contains(body, forbidden) {
			t.Fatalf("body marked default empty action placements as authored via %q: %s", forbidden, body)
		}
	}
}

func TestUIAPILayoutFiltersCaseVariantExpressionMetadataByFLS(t *testing.T) {
	const hiddenField = "Secret__c"
	stub := newUIAPITestContracts()
	stub.schema.Fields = append(stub.schema.Fields, models.FieldMetadata{
		APIName:    hiddenField,
		Label:      "Secret",
		Type:       constants.FieldTypeString,
		Createable: true,
		Updateable: true,
	})
	stub.fieldAccess = map[string]contract.FieldAccess{
		hiddenField: {},
	}
	stub.layout.Sections = []models.PageSection{{
		ID:      "main",
		Label:   "Main",
		Columns: 1,
		Fields:  []string{constants.FieldName},
	}}
	lowerCondition := "secret__c == true"
	mixedCondition := "ISBLANK(SeCrEt__C)"
	stub.layout.RecordPanels = []models.RecordPanelConfig{
		{ID: "safe-panel", RendererID: "safeRenderer", VisibilityCondition: constants.FieldName + " != ''"},
		{ID: "hidden-lower-panel", RendererID: "hiddenLowerRenderer", VisibilityCondition: lowerCondition},
	}
	stub.layout.HeaderActions = []models.ActionConfig{
		{Name: "hiddenLowerHeader", Label: "Hidden Lower Header", Type: "Custom", VisibilityCondition: stringPtr(lowerCondition)},
	}
	stub.layout.QuickActions = []models.ActionConfig{
		{Name: "hiddenMixedQuick", Label: "Hidden Mixed Quick", Type: "Custom", VisibilityCondition: stringPtr(mixedCondition)},
	}
	stub.layout.RecordStates = []models.RecordStateRule{
		{ID: "hidden-lower-state", When: lowerCondition, Banner: &models.RecordStateBanner{Title: "Hidden Lower State"}},
	}

	resp, err := UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	body := mustJSON(t, resp.Body)
	if !strings.Contains(body, "safe-panel") {
		t.Fatalf("body missing safe panel: %s", body)
	}
	for _, forbidden := range []string{"secret__c", "SeCrEt__C", hiddenField, "hidden-lower-panel", "hiddenLowerHeader", "hiddenMixedQuick", "hidden-lower-state"} {
		if strings.Contains(strings.ToLower(body), strings.ToLower(forbidden)) {
			t.Fatalf("body leaked case-variant hidden layout metadata %q: %s", forbidden, body)
		}
	}
}

func TestUIAPILayoutFiltersCrossObjectExpressionMetadataByFLS(t *testing.T) {
	const (
		sourceObject  = "Contract"
		relatedObject = "Parent__c"
		targetObject  = "Quote"
		hiddenSource  = "Applicable_Law__c"
		hiddenRelated = "Secret__c"
		hiddenTarget  = "HiddenTarget__c"
		hiddenHostFK  = "HiddenQuote__c"
		safeHostFK    = "Quote__c"
		safeTarget    = "SafeTarget__c"
		sharedStatus  = "Status"
	)
	hiddenDotCondition := "record." + sourceObject + "." + hiddenSource + " == 'NL'"
	hiddenRelationshipCondition := "record.Parent__r." + hiddenRelated + " == 'x'"
	hiddenOwnerCondition := "record.Owner.Name != ''"
	hiddenMultiHopCondition := "record.Parent__r.Owner.Name != ''"
	stub := newUIAPITestContracts()
	parentRelationship := "Parent__r"
	ownerRelationship := "Owner"
	whatRelationship := "What"
	stub.schema.Fields = append(stub.schema.Fields,
		models.FieldMetadata{APIName: sharedStatus, Label: "Status", Type: constants.FieldTypeString, Createable: true, Updateable: true},
		models.FieldMetadata{APIName: "Parent__c", Label: "Parent", Type: constants.FieldTypeReference, ReferenceTo: []string{relatedObject}, RelationshipName: &parentRelationship, Createable: true, Updateable: true},
		models.FieldMetadata{APIName: constants.FieldOwnerID, Label: "Owner", Type: constants.FieldTypeReference, ReferenceTo: []string{constants.TableUser}, RelationshipName: &ownerRelationship, Createable: true, Updateable: true},
		models.FieldMetadata{APIName: safeHostFK, Label: "Quote", Type: constants.FieldTypeReference, ReferenceTo: []string{targetObject}, Createable: true, Updateable: true},
		models.FieldMetadata{APIName: hiddenHostFK, Label: "Hidden Quote", Type: constants.FieldTypeReference, ReferenceTo: []string{targetObject}, Createable: true, Updateable: true},
		models.FieldMetadata{APIName: "WhatId", Label: "What", Type: constants.FieldTypeReference, ReferenceTo: []string{sourceObject, relatedObject}, RelationshipName: &whatRelationship, Createable: true, Updateable: true},
	)
	stub.schemas = map[string]*models.ObjectMetadata{
		"Account": stub.schema,
		sourceObject: {
			APIName: sourceObject,
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Label: "Contract Name", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: hiddenSource, Label: "Applicable Law", Type: constants.FieldTypeString, Createable: true, Updateable: true},
			},
		},
		relatedObject: {
			APIName: relatedObject,
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Label: "Parent Name", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: hiddenRelated, Label: "Secret", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: constants.FieldOwnerID, Label: "Owner", Type: constants.FieldTypeReference, ReferenceTo: []string{constants.TableUser}, RelationshipName: &ownerRelationship, Createable: true, Updateable: true},
			},
		},
		targetObject: {
			APIName: targetObject,
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Label: "Quote Name", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: hiddenTarget, Label: "Hidden Target", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: safeTarget, Label: "Safe Target", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: sharedStatus, Label: "Quote Status", Type: constants.FieldTypeString, Createable: true, Updateable: true},
			},
		},
		constants.TableUser: {
			APIName: constants.TableUser,
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Label: "User Name", Type: constants.FieldTypeString, Createable: true, Updateable: true},
			},
		},
	}
	stub.fieldAccessByObject = map[string]map[string]contract.FieldAccess{
		"Account": {
			hiddenHostFK: {},
		},
		sourceObject: {
			hiddenSource: {},
		},
		relatedObject: {
			hiddenRelated: {},
		},
		targetObject: {
			hiddenTarget: {Createable: true},
			sharedStatus: {},
		},
		constants.TableUser: {
			constants.FieldName: {},
		},
	}
	stub.layout.Sections = []models.PageSection{{
		ID:      "main",
		Label:   "Main",
		Columns: 1,
		Fields:  []string{constants.FieldName},
	}}
	stub.layout.RecordPanels = []models.RecordPanelConfig{
		{ID: "safe-cross-panel", RendererID: "safeRenderer", VisibilityCondition: constants.FieldName + " != ''"},
		{ID: "hidden-cross-panel", RendererID: "hiddenRenderer", VisibilityCondition: hiddenDotCondition},
		{ID: "hidden-relationship-panel", RendererID: "hiddenRelationshipRenderer", VisibilityCondition: hiddenRelationshipCondition},
		{ID: "hidden-owner-panel", RendererID: "hiddenOwnerRenderer", VisibilityCondition: hiddenOwnerCondition},
		{ID: "hidden-multihop-panel", RendererID: "hiddenMultiHopRenderer", VisibilityCondition: hiddenMultiHopCondition},
	}
	stub.layout.HeaderActions = []models.ActionConfig{
		{
			Name:         "safeCrossAction",
			Label:        "Safe Cross Action",
			Type:         "NavigateToNewRecord",
			TargetObject: stringPtr(targetObject),
			Config:       map[string]interface{}{"prefillMappings": map[string]interface{}{safeTarget: constants.FieldName}},
		},
		{
			Name:                "safeStatusCollisionAction",
			Label:               "Safe Status Collision Action",
			Type:                "NavigateToNewRecord",
			TargetObject:        stringPtr(targetObject),
			VisibilityCondition: stringPtr("record." + sharedStatus + " == 'Open'"),
			Config:              map[string]interface{}{"lookupField": safeTarget, "prefillMappings": map[string]interface{}{safeTarget: sharedStatus}},
		},
		{
			Name:         "hiddenTargetAction",
			Label:        "Hidden Target Action",
			Type:         "NavigateToNewRecord",
			TargetObject: stringPtr(targetObject),
			Config:       map[string]interface{}{"prefillMappings": map[string]interface{}{hiddenTarget: constants.FieldName}},
		},
		{
			Name:         "hiddenSourceAction",
			Label:        "Hidden Source Action",
			Type:         "NavigateToNewRecord",
			TargetObject: stringPtr(targetObject),
			Config:       map[string]interface{}{"prefillMappings": map[string]interface{}{safeTarget: sourceObject + "." + hiddenSource}},
		},
		{
			Name:         "hiddenRelationshipSourceAction",
			Label:        "Hidden Relationship Source Action",
			Type:         "NavigateToNewRecord",
			TargetObject: stringPtr(targetObject),
			Config:       map[string]interface{}{"prefillMappings": map[string]interface{}{safeTarget: "Parent__r." + hiddenRelated}},
		},
		{
			Name:         "hiddenPolymorphicSourceAction",
			Label:        "Hidden Polymorphic Source Action",
			Type:         "NavigateToNewRecord",
			TargetObject: stringPtr(targetObject),
			Config:       map[string]interface{}{"prefillMappings": map[string]interface{}{safeTarget: "What." + hiddenSource}},
		},
	}
	stub.layout.QuickActions = []models.ActionConfig{
		{
			Name:                "hiddenVisibilityAction",
			Label:               "Hidden Visibility Action",
			Type:                "NavigateToNewRecord",
			TargetObject:        stringPtr(targetObject),
			VisibilityCondition: stringPtr(hiddenDotCondition),
		},
		{
			Name:                "hiddenRelationshipVisibilityAction",
			Label:               "Hidden Relationship Visibility Action",
			Type:                "NavigateToNewRecord",
			TargetObject:        stringPtr(targetObject),
			VisibilityCondition: stringPtr(hiddenRelationshipCondition),
		},
	}
	stub.layout.RecordStates = []models.RecordStateRule{
		{ID: "safe-cross-state", When: constants.FieldName + " != ''", Links: []models.RecordStateLink{{ID: "safe-link", Label: "Quote", ObjectAPIName: targetObject, ReferenceFieldAPIName: safeHostFK}}},
		{ID: "safe-implicit-cross-state", When: constants.FieldName + " != ''", Links: []models.RecordStateLink{{ID: "safe-implicit-link", Label: "Quote", ReferenceFieldAPIName: safeHostFK}}},
		{ID: "hidden-cross-state", When: hiddenDotCondition},
		{ID: "hidden-relationship-state", When: hiddenRelationshipCondition},
		{ID: "hidden-multihop-state", When: hiddenMultiHopCondition},
		{ID: "hidden-host-link-state", When: constants.FieldName + " != ''", Links: []models.RecordStateLink{{ID: "removed-host-link", Label: "Hidden Quote", ObjectAPIName: targetObject, ReferenceFieldAPIName: hiddenHostFK}}},
		{ID: "hidden-target-link-label-state", When: constants.FieldName + " != ''", Links: []models.RecordStateLink{{ID: "removed-target-label-link", Label: hiddenTarget, ObjectAPIName: targetObject, ReferenceFieldAPIName: safeHostFK}}},
		{ID: "hidden-implicit-target-link-label-state", When: constants.FieldName + " != ''", Links: []models.RecordStateLink{{ID: "removed-implicit-target-label-link", Label: hiddenTarget, ReferenceFieldAPIName: safeHostFK}}},
		{ID: "hidden-banner-state", When: constants.FieldName + " != ''", Banner: &models.RecordStateBanner{Body: "record.Parent__r." + hiddenRelated + " blocks save"}},
		{ID: "hidden-override-state", When: constants.FieldName + " != ''", ActionOverrides: []models.RecordStateActionOverride{{ActionKey: "host:edit-record", Mode: "disable", Reason: sourceObject + "." + hiddenSource + " blocks edit"}}},
		{ID: "hidden-readonly-state", When: constants.FieldName + " != ''", ReadOnly: &models.RecordStateReadOnly{Enabled: true, Reason: targetObject + "." + hiddenTarget + " blocks save"}},
	}

	resp, err := UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	body := mustJSON(t, resp.Body)
	for _, want := range []string{"safe-cross-panel", "safeCrossAction", "safeStatusCollisionAction", "safe-cross-state", "safe-implicit-cross-state", "hidden-host-link-state", "hidden-target-link-label-state", "hidden-implicit-target-link-label-state", safeTarget} {
		if !strings.Contains(body, want) {
			t.Fatalf("body missing visible metadata %q: %s", want, body)
		}
	}
	for _, forbidden := range []string{hiddenSource, hiddenRelated, hiddenTarget, hiddenHostFK, "hidden-cross-panel", "hidden-relationship-panel", "hidden-owner-panel", "hidden-multihop-panel", "hiddenVisibilityAction", "hiddenRelationshipVisibilityAction", "hidden-cross-state", "hidden-relationship-state", "hidden-multihop-state", "removed-host-link", "removed-target-label-link", "removed-implicit-target-label-link", "hidden-banner-state", "hidden-override-state", "hidden-readonly-state"} {
		if strings.Contains(body, forbidden) {
			t.Fatalf("body leaked hidden cross-object metadata %q: %s", forbidden, body)
		}
	}
}

func TestUIAPILayoutSummariesDoNotLeakFullMetadata(t *testing.T) {
	now := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
	stub := newUIAPITestContracts()
	stub.layouts = []*models.PageLayout{{
		ID:               "layout-1",
		ObjectAPIName:    "Account",
		LayoutName:       "Account Layout",
		Type:             "Detail",
		IsDefault:        true,
		CreatedDate:      now,
		LastModifiedDate: now,
		CompactLayout:    []string{"Name"},
		Sections:         []models.PageSection{{ID: "section-1", Label: "Internal", Fields: []string{"Secret__c"}}},
		HeaderActions:    []models.ActionConfig{{Name: "internalAction", Label: "Internal Action"}},
		QuickActions:     []models.ActionConfig{{Name: "quickInternal", Label: "Quick Internal"}},
		RecordStates:     []models.RecordStateRule{{ID: "state-1", When: "Secret__c == true"}},
	}}
	resp, err := UIAPIListLayoutsNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200", resp.Status)
	}
	body := mustJSON(t, resp.Body)
	for _, want := range []string{`"id":"layout-1"`, `"layoutName":"Account Layout"`, `"objectApiName":"Account"`, `"type":"Detail"`, `"isDefault":true`} {
		if !strings.Contains(body, want) {
			t.Fatalf("layout summary missing %s: %s", want, body)
		}
	}
	for _, forbidden := range []string{"compactLayout", "sections", "headerActions", "quickActions", "recordStates", "Secret__c", "Internal Action"} {
		if strings.Contains(body, forbidden) {
			t.Fatalf("layout summary leaked %q: %s", forbidden, body)
		}
	}
}

func uiAPIDeps(stub *uiAPITestContracts, _ recordReadDecorator) dialect.Deps {
	if stub == nil {
		stub = newUIAPITestContracts()
	}
	return dialect.Deps{Contracts: contract.Deps{
		Records:           stub,
		Metadata:          stub,
		MetadataAuthoring: stub,
		Policy:            stub,
	}}
}

func newUIAPITestContracts() *uiAPITestContracts {
	stub := &uiAPITestContracts{objectType: "Account"}
	stub.schema = &models.ObjectMetadata{
		APIName:     "Account",
		KeyPrefix:   stringPtr("001"),
		Label:       "Account",
		PluralLabel: "Accounts",
		Searchable:  true,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldName, Label: "Account Name", Type: constants.FieldTypeString, Createable: true, Updateable: true, IsNameField: true},
			{APIName: "FileRef", Label: "File Ref", Type: constants.FieldTypeString, Createable: true, Updateable: true},
			// Required with nothing to satisfy it, so fieldRequiresUserInputOnCreate
			// holds for it. Without one such field every createRequiredFields
			// assertion in this package would read an empty list and could not
			// tell a working handler from one that discards the schema.
			{APIName: "Region__c", Label: "Region", Type: constants.FieldTypeString, Createable: true, Updateable: true, Required: true},
		},
	}
	stub.records = map[string]contract.Record{
		"001-a": {constants.FieldName: "Acme", "FileRef": "file-1"},
	}
	stub.access = map[string]contract.RecordAccess{
		"001-a": {Read: true, Update: true, Delete: false, Locked: false},
	}
	stub.layout = &models.PageLayout{
		ID:            "layout-1",
		ObjectAPIName: "Account",
		LayoutName:    "Account Layout",
		Type:          "Detail",
		Sections:      []models.PageSection{{ID: "main", Label: "Main", Fields: []string{constants.FieldName, "FileRef"}}},
	}
	stub.layouts = []*models.PageLayout{stub.layout}
	return stub
}

type uiAPITestContracts struct {
	accessMu            sync.Mutex
	accessCalls         map[string]int
	describeMu          sync.Mutex
	describeCalls       map[string]int
	objectType          string
	objectTypeErr       error
	records             map[string]contract.Record
	access              map[string]contract.RecordAccess
	fieldAccess         map[string]contract.FieldAccess
	fieldAccessByObject map[string]map[string]contract.FieldAccess
	schema              *models.ObjectMetadata
	schemas             map[string]*models.ObjectMetadata
	layout              *models.PageLayout
	layouts             []*models.PageLayout
	listLayoutsErr      error
	// layoutReads records which layout read each handler took. The two reads
	// return the same value for most fixtures, so only the record distinguishes
	// a handler that reads the stored layout from one that reads a rendering.
	layoutReads        []string
	assignments        []*models.LayoutAssignment
	listViews          []*models.ListView
	listViewsErr       error
	listViewsObject    string
	listViewsUser      *models.UserSession
	recordTypes        []*models.RecordType
	resolveLayoutFunc  func(context.Context, string, *models.UserSession, string) (*models.PageLayout, error)
	resolveLayoutUsers []*models.UserSession
	saveLayoutErr      error
	restoreLayoutErr   error
	savedLayout        *models.PageLayout
	historyLayoutID    string
	historyReason      string
	historyChangedBy   *string
	createdAssignment  *models.LayoutAssignment
	retrieveCalls      []uiAPIRetrieveCall
	retrieveManyCalls  []uiAPIRetrieveManyCall
	retrieveManyErrs   map[string]error

	// #3484 lockstep test hooks. createSchemaErr forces CreateSchema to fail;
	// effectiveSchemaForAuthoring, when set, overrides the value returned by
	// GetEffectiveSchemaForAuthoring (nil models "no live object" for the
	// orphan-blocked-create case). Zero values preserve prior behavior.
	createSchemaErr             error
	effectiveSchemaOverride     bool
	effectiveSchemaForAuthoring *models.ObjectMetadata
}

type uiAPIRetrieveCall struct {
	objectName string
	id         string
}

type uiAPIRetrieveManyCall struct {
	objectName string
	ids        []string
}

func (*uiAPITestContracts) ResolvePicklistValuesForRecordType(context.Context, string, string, string) (*models.ScopedPicklistValueSet, error) {
	return nil, nil
}

func (s *uiAPITestContracts) Insert(context.Context, string, contract.Record, *models.UserSession) (contract.Record, error) {
	return nil, nil
}
func (s *uiAPITestContracts) Retrieve(_ context.Context, objectName string, id string, _ *models.UserSession) (contract.Record, error) {
	s.retrieveCalls = append(s.retrieveCalls, uiAPIRetrieveCall{objectName: objectName, id: id})
	return s.records[id], nil
}
func (s *uiAPITestContracts) RetrieveMany(_ context.Context, objectName string, ids []string, _ *models.UserSession) (map[string]contract.Record, error) {
	s.retrieveManyCalls = append(s.retrieveManyCalls, uiAPIRetrieveManyCall{objectName: objectName, ids: append([]string(nil), ids...)})
	if err := s.retrieveManyErrs[objectName]; err != nil {
		return nil, err
	}
	out := make(map[string]contract.Record, len(ids))
	for _, id := range ids {
		if record := s.records[id]; record != nil {
			out[id] = record
		}
	}
	return out, nil
}
func (s *uiAPITestContracts) Update(context.Context, string, string, contract.Record, *models.UserSession) error {
	return nil
}
func (s *uiAPITestContracts) Delete(context.Context, string, string, *models.UserSession) error {
	return nil
}
func (s *uiAPITestContracts) UpsertByExternalID(context.Context, string, string, string, contract.Record, *models.UserSession) (contract.Record, bool, error) {
	return nil, false, nil
}
func (s *uiAPITestContracts) RetrieveByField(context.Context, string, string, string, *models.UserSession) (contract.Record, error) {
	return nil, nil
}
func (s *uiAPITestContracts) CreateRecords(context.Context, []contract.Record, bool, *models.UserSession) ([]contract.RecordMutationResult, error) {
	return nil, nil
}
func (s *uiAPITestContracts) UpdateRecords(context.Context, []contract.Record, bool, *models.UserSession) ([]contract.RecordMutationResult, error) {
	return nil, nil
}
func (s *uiAPITestContracts) DeleteRecords(context.Context, []string, bool, *models.UserSession) ([]contract.RecordMutationResult, error) {
	return nil, nil
}
func (s *uiAPITestContracts) UpsertRecords(context.Context, string, string, []contract.Record, bool, *models.UserSession) ([]contract.RecordMutationResult, error) {
	return nil, nil
}
func (s *uiAPITestContracts) RetrieveRecords(context.Context, string, []string, []string, *models.UserSession) ([]contract.Record, error) {
	return nil, nil
}
func (s *uiAPITestContracts) ObjectTypeForID(context.Context, string) (string, error) {
	if s.objectTypeErr != nil {
		return "", s.objectTypeErr
	}
	return s.objectType, nil
}
func (s *uiAPITestContracts) RetrieveWithAccess(_ context.Context, _ string, id string, _ *models.UserSession) (contract.Record, *contract.RecordAccess, error) {
	access := s.access[id]
	return s.records[id], &access, nil
}
func (s *uiAPITestContracts) BatchRecordAccess(_ context.Context, _ string, ids []string, _ *models.UserSession) (map[string]contract.RecordAccess, error) {
	out := make(map[string]contract.RecordAccess, len(ids))
	for _, id := range ids {
		if access, ok := s.access[id]; ok {
			out[id] = access
		}
	}
	return out, nil
}
func (s *uiAPITestContracts) ObjectTypesForIDs(_ context.Context, ids []string) (map[string]string, error) {
	out := make(map[string]string, len(ids))
	for _, id := range ids {
		out[id] = s.objectType
	}
	return out, nil
}
func (s *uiAPITestContracts) ValidateSiblingMaxDefaultConfig(context.Context, string, string) error {
	return nil
}
func (s *uiAPITestContracts) ValidateSiblingMaxDefaultParentForCreate(context.Context, string, string, any, *models.UserSession) error {
	return nil
}
func (s *uiAPITestContracts) PreviewSiblingMaxDefault(context.Context, string, string, string, any) (int64, error) {
	return 0, nil
}

func (s *uiAPITestContracts) Describe(_ context.Context, objectName string) (*models.ObjectMetadata, error) {
	s.describeMu.Lock()
	if s.describeCalls == nil {
		s.describeCalls = map[string]int{}
	}
	s.describeCalls[strings.ToLower(strings.TrimSpace(objectName))]++
	s.describeMu.Unlock()

	return s.schemaForObject(objectName), nil
}
func (s *uiAPITestContracts) ListObjects(context.Context) ([]models.ObjectMetadata, error) {
	return nil, nil
}
func (s *uiAPITestContracts) Relationships(context.Context, string) ([]*models.Relationship, error) {
	return nil, nil
}
func (s *uiAPITestContracts) ChildRelationships(context.Context, string) ([]*models.ObjectMetadata, error) {
	return nil, nil
}
func (s *uiAPITestContracts) KeyPrefix(context.Context, string) (string, bool) {
	if s.schema != nil && s.schema.KeyPrefix != nil {
		return *s.schema.KeyPrefix, true
	}
	return "", false
}
func (s *uiAPITestContracts) ResolveLayout(ctx context.Context, objectAPIName string, user *models.UserSession, recordTypeID string) (*models.PageLayout, error) {
	s.resolveLayoutUsers = append(s.resolveLayoutUsers, user)
	if s.resolveLayoutFunc != nil {
		return s.resolveLayoutFunc(ctx, objectAPIName, user, recordTypeID)
	}
	return s.layout, nil
}

func (s *uiAPITestContracts) ResolveRelatedListResolution(context.Context, string, string) (*contract.RelatedListResolution, bool) {
	return nil, false
}
func (s *uiAPITestContracts) Actions(context.Context, string) ([]*models.ActionMetadata, error) {
	return nil, nil
}
func (s *uiAPITestContracts) ActionByID(context.Context, string) (*models.ActionMetadata, error) {
	return nil, nil
}
func (s *uiAPITestContracts) ListLayouts(context.Context, string) ([]*models.PageLayout, error) {
	if s.listLayoutsErr != nil {
		return nil, s.listLayoutsErr
	}
	return s.layouts, nil
}

// GetLayoutForAuthoring returns the stored layout. The double keeps it distinct
// from GetLayoutByID on purpose: a handler that persists what it reads must take
// this one, and without the record below that choice is untestable.
func (s *uiAPITestContracts) GetLayoutForAuthoring(ctx context.Context, layoutID string) (*models.PageLayout, error) {
	s.layoutReads = append(s.layoutReads, "authoring")
	return s.lookupLayout(layoutID)
}

func (s *uiAPITestContracts) GetLayoutByID(ctx context.Context, layoutID string) (*models.PageLayout, error) {
	s.layoutReads = append(s.layoutReads, "presentation")
	return s.lookupLayout(layoutID)
}

func (s *uiAPITestContracts) lookupLayout(layoutID string) (*models.PageLayout, error) {
	if s.layout != nil && s.layout.ID == layoutID {
		return s.layout, nil
	}
	for _, layout := range s.layouts {
		if layout != nil && layout.ID == layoutID {
			return layout, nil
		}
	}
	return nil, nil
}
func (s *uiAPITestContracts) ListLayoutAssignments(context.Context, string) ([]*models.LayoutAssignment, error) {
	return s.assignments, nil
}
func (s *uiAPITestContracts) ListApprovalLayouts(context.Context, string) ([]*models.ApprovalPageLayout, error) {
	return nil, nil
}
func (s *uiAPITestContracts) GetApprovalLayoutByID(context.Context, string) (*models.ApprovalPageLayout, error) {
	return nil, nil
}
func (s *uiAPITestContracts) ListViews(_ context.Context, objectName string, user *models.UserSession) ([]*models.ListView, error) {
	s.listViewsObject = objectName
	s.listViewsUser = user
	if s.listViewsErr != nil {
		return nil, s.listViewsErr
	}
	return s.listViews, nil
}
func (s *uiAPITestContracts) RecordTypes(context.Context, string) ([]*models.RecordType, error) {
	return s.recordTypes, nil
}

func (s *uiAPITestContracts) ListSchemasForAuthoring(context.Context) []*models.ObjectMetadata {
	return nil
}
func (s *uiAPITestContracts) GetEffectiveSchemaForAuthoring(context.Context, string, *models.UserSession) *models.ObjectMetadata {
	if s.effectiveSchemaOverride {
		return s.effectiveSchemaForAuthoring
	}
	return s.schema
}
func (s *uiAPITestContracts) SharingParentRelationship(context.Context, string) *models.Relationship {
	return nil
}
func (s *uiAPITestContracts) CreateSchema(context.Context, contract.MetadataSchemaCreateRequest) (*models.ObjectMetadata, error) {
	if s.createSchemaErr != nil {
		return nil, s.createSchemaErr
	}
	return nil, nil
}
func (s *uiAPITestContracts) UpdateSchema(context.Context, string, *models.ObjectMetadata) error {
	return nil
}
func (s *uiAPITestContracts) DeleteSchema(context.Context, string) error {
	return nil
}
func (s *uiAPITestContracts) RefreshAuthoringMetadataCache(context.Context) error {
	return nil
}
func (s *uiAPITestContracts) CreateField(context.Context, string, *models.FieldMetadata) error {
	return nil
}
func (s *uiAPITestContracts) BatchCreateFields(context.Context, string, []models.FieldMetadata) ([]*models.FieldMetadata, error) {
	return nil, nil
}
func (s *uiAPITestContracts) UpdateField(context.Context, string, string, *models.FieldMetadata, contract.FieldPatchPresence) error {
	return nil
}
func (s *uiAPITestContracts) DeleteField(context.Context, string, string) error {
	return nil
}
func (s *uiAPITestContracts) ListValidationRules(context.Context, string) []models.ValidationRule {
	return nil
}
func (s *uiAPITestContracts) CreateValidationRule(context.Context, *models.ValidationRule) error {
	return nil
}
func (s *uiAPITestContracts) UpdateValidationRule(context.Context, string, *models.ValidationRule) error {
	return nil
}
func (s *uiAPITestContracts) DeleteValidationRule(context.Context, string) error {
	return nil
}
func (s *uiAPITestContracts) CreateAction(context.Context, *models.ActionMetadata) error {
	return nil
}
func (s *uiAPITestContracts) CreateLayoutAssignment(_ context.Context, assignment *models.LayoutAssignment) error {
	if assignment != nil {
		copy := *assignment
		s.createdAssignment = &copy
	}
	return nil
}
func (s *uiAPITestContracts) SaveLayout(_ context.Context, layout *models.PageLayout) error {
	if s.saveLayoutErr != nil {
		return s.saveLayoutErr
	}
	if layout != nil {
		copy := *layout
		s.savedLayout = &copy
	}
	return nil
}
func (s *uiAPITestContracts) WriteLayoutHistory(_ context.Context, layoutID string, reason string, changedByID *string) {
	s.historyLayoutID = layoutID
	s.historyReason = reason
	s.historyChangedBy = changedByID
}
func (s *uiAPITestContracts) SetDefaultLayout(context.Context, string, string, *string) error {
	return nil
}
func (s *uiAPITestContracts) DeleteLayout(context.Context, string) error {
	return nil
}
func (s *uiAPITestContracts) RestoreLayout(context.Context, string, int) error {
	return s.restoreLayoutErr
}
func (s *uiAPITestContracts) DeleteLayoutAssignment(context.Context, string) error {
	return nil
}
func (s *uiAPITestContracts) SaveApprovalLayout(context.Context, *models.ApprovalPageLayout) error {
	return nil
}
func (s *uiAPITestContracts) SetDefaultApprovalLayout(context.Context, string, string) error {
	return nil
}
func (s *uiAPITestContracts) DeleteApprovalLayout(context.Context, string) error {
	return nil
}

func (s *uiAPITestContracts) Access(_ context.Context, _ *models.UserSession, objectName string, req contract.AccessRequest) (*contract.EffectiveObjectAccess, error) {
	s.accessMu.Lock()
	if s.accessCalls == nil {
		s.accessCalls = map[string]int{}
	}
	s.accessCalls[fmt.Sprintf("%s|fields=%t|recordTypes=%t", strings.ToLower(strings.TrimSpace(objectName)), req.IncludeFields, req.IncludeRecordTypes)]++
	s.accessMu.Unlock()

	out := &contract.EffectiveObjectAccess{
		Object: contract.ObjectAccess{Create: true, Read: true, Update: true, Delete: true},
		Fields: map[string]contract.FieldAccess{},
	}
	if schema := s.schemaForObject(objectName); schema != nil {
		for _, field := range schema.Fields {
			access := contract.FieldAccess{
				Createable: field.Createable,
				Updateable: field.Updateable,
				Readable:   true,
			}
			if override, ok := s.fieldAccessOverride(objectName, field.APIName); ok {
				access = override
			}
			out.Fields[field.APIName] = access
		}
	}
	for _, rt := range s.recordTypes {
		out.RecordTypes = append(out.RecordTypes, contract.RecordTypeAvailability{
			ID:        rt.ID,
			Name:      rt.Name,
			Available: rt.IsActive,
			Default:   rt.IsDefault,
		})
		if rt.IsDefault {
			out.DefaultRecordTypeID = rt.ID
		}
	}
	return out, nil
}

func (s *uiAPITestContracts) accessCallSnapshot() map[string]int {
	s.accessMu.Lock()
	defer s.accessMu.Unlock()
	out := make(map[string]int, len(s.accessCalls))
	for key, count := range s.accessCalls {
		out[key] = count
	}
	return out
}

func (s *uiAPITestContracts) describeCallSnapshot() map[string]int {
	s.describeMu.Lock()
	defer s.describeMu.Unlock()
	out := make(map[string]int, len(s.describeCalls))
	for key, count := range s.describeCalls {
		out[key] = count
	}
	return out
}

func (s *uiAPITestContracts) schemaForObject(objectName string) *models.ObjectMetadata {
	objectName = strings.TrimSpace(objectName)
	if objectName == "" && s.schema != nil {
		return s.schema
	}
	if s.schemas != nil {
		if schema, ok := s.schemas[objectName]; ok {
			return schema
		}
		for name, schema := range s.schemas {
			if strings.EqualFold(name, objectName) || (schema != nil && strings.EqualFold(schema.APIName, objectName)) {
				return schema
			}
		}
	}
	if s.schema != nil && strings.EqualFold(s.schema.APIName, objectName) {
		return s.schema
	}
	return nil
}

func (s *uiAPITestContracts) fieldAccessOverride(objectName string, fieldName string) (contract.FieldAccess, bool) {
	if s.fieldAccessByObject != nil {
		if overrides, ok := s.fieldAccessByObject[objectName]; ok {
			if access, ok := overrides[fieldName]; ok {
				return access, true
			}
		}
		for name, overrides := range s.fieldAccessByObject {
			if strings.EqualFold(name, objectName) {
				if access, ok := overrides[fieldName]; ok {
					return access, true
				}
			}
		}
	}
	if s.schema == nil || strings.EqualFold(s.schema.APIName, objectName) {
		if access, ok := s.fieldAccess[fieldName]; ok {
			return access, true
		}
	}
	return contract.FieldAccess{}, false
}
func (s *uiAPITestContracts) HasCapability(context.Context, *models.UserSession, contract.Capability) bool {
	return true
}

type uiAPITestDecorator struct {
	prefix string
}

func (d *uiAPITestDecorator) DecorateRecordRead(_ context.Context, _ string, _ string, record contract.Record, req recordReadDecorationRequest) error {
	d.prefix = req.FileURLPrefix
	if raw, ok := record["FileRef"].(string); ok && raw != "" {
		record["FileRef"] = req.FileURLPrefix + "/" + raw
	}
	return nil
}

type uiAPITestInternalError string

func (e uiAPITestInternalError) Error() string {
	return string(e)
}

func (e uiAPITestInternalError) InternalCondition() bool {
	return true
}

func TestUIAPILayoutCarriesPrimaryHeaderActions(t *testing.T) {
	stub := newUIAPITestContracts()
	stub.layout.Sections = []models.PageSection{{
		ID:      "main",
		Label:   "Main",
		Columns: 1,
		Fields:  []string{constants.FieldName},
	}}
	stub.layout.PrimaryHeaderActions = []string{"standard-action:createQuote", "metadata-action:AddProducts"}

	resp, err := UIAPILayoutNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("handler error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	body := mustJSON(t, resp.Body)
	if !strings.Contains(body, `"primaryHeaderActions":["standard-action:createQuote","metadata-action:AddProducts"]`) {
		t.Fatalf("body missing primaryHeaderActions round-trip: %s", body)
	}
}
