package persistence

import (
	"context"
	"database/sql"
	"fmt"
	"sort"
	"strings"
	"time"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/jsondocument"
	"github.com/nexuscrm/kernel/pkg/query"
	"github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/kernel/pkg/utils"
	"github.com/nexuscrm/persistence/layoutrouting"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/catalogassignmentidentityv1"
)

func normalizeRouteSource(routeSource *string) *string {
	if routeSource == nil {
		return nil
	}
	trimmed := strings.TrimSpace(*routeSource)
	if trimmed == "" {
		return nil
	}
	return &trimmed
}

func loadLiveLayoutIdentity(ctx context.Context, tx *sql.Tx, layoutID string) (string, string, error) {
	var canonicalLayoutID, objectAPIName string
	err := tx.QueryRowContext(
		ctx,
		fmt.Sprintf(
			"SELECT `%s`, `%s` FROM `%s` WHERE LOWER(`%s`) = LOWER(?) AND (`%s` = 0 OR `%s` IS NULL) LIMIT 1",
			constants.FieldID,
			constants.FieldObjectAPIName,
			constants.TablePageLayout,
			constants.FieldID,
			constants.FieldIsDeleted,
			constants.FieldIsDeleted,
		),
		layoutID,
	).Scan(&canonicalLayoutID, &objectAPIName)
	if err == sql.ErrNoRows {
		return "", "", apperrors.NewInvalidReferenceError(
			fmt.Sprintf("layoutId %q does not reference a live page layout", layoutID),
			"layoutId",
		)
	}
	if err != nil {
		return "", "", err
	}
	return canonicalLayoutID, objectAPIName, nil
}

func canonicalizeAssignmentObjectAPIName(ctx context.Context, tx *sql.Tx, assignment *models.LayoutAssignment) error {
	if assignment == nil {
		return nil
	}

	canonicalLayoutID, layoutObjectAPIName, err := loadLiveLayoutIdentity(ctx, tx, assignment.LayoutID)
	if err != nil {
		return err
	}
	if !strings.EqualFold(layoutObjectAPIName, assignment.ObjectAPIName) {
		return apperrors.NewInvalidReferenceError(
			fmt.Sprintf("layoutId %q belongs to %q, not %q", assignment.LayoutID, layoutObjectAPIName, assignment.ObjectAPIName),
			"layoutId",
			"objectApiName",
		)
	}
	assignment.LayoutID = canonicalLayoutID
	assignment.ObjectAPIName = layoutObjectAPIName
	return nil
}

func isWildcardLayoutAssignment(assignment *models.LayoutAssignment) bool {
	return assignment != nil &&
		assignment.RoleID == nil &&
		assignment.ProfileID == nil &&
		assignment.RecordTypeID == nil
}

func upsertRuntimeFallbackAssignmentTx(ctx context.Context, tx *sql.Tx, assignment *models.LayoutAssignment) error {
	var existingID, existingLayoutID string
	var existingRouteSource sql.NullString
	sqlStr := fmt.Sprintf(
		"SELECT `Id`, `LayoutId`, `RouteSource` FROM `%s` "+
			"WHERE LOWER(`ObjectApiName`) = LOWER(?) AND `RoleId` IS NULL AND `ProfileId` IS NULL AND `RecordTypeId` IS NULL "+
			"ORDER BY `CreatedDate` ASC LIMIT 1",
		constants.TableLayoutAssignment,
	)
	err := tx.QueryRowContext(ctx, sqlStr, assignment.ObjectAPIName).Scan(&existingID, &existingLayoutID, &existingRouteSource)
	if err != nil && err != sql.ErrNoRows {
		return err
	}
	if err == nil {
		routeSource := ""
		if existingRouteSource.Valid {
			routeSource = strings.TrimSpace(existingRouteSource.String)
		}
		if !strings.EqualFold(routeSource, models.LayoutAssignmentRouteSourceRuntimeFallback) {
			return nil
		}
		updateSQL := fmt.Sprintf(
			"UPDATE `%s` SET `LayoutId` = ?, `Specificity` = ?, `RouteSource` = ?, `OwnerPack` = ?, `LastModifiedDate` = CURRENT_TIMESTAMP WHERE `Id` = ?",
			constants.TableLayoutAssignment,
		)
		_, err := tx.ExecContext(ctx, updateSQL, assignment.LayoutID, assignment.Specificity, *assignment.RouteSource, assignmentOwnerPackForPersistence(*assignment.RouteSource), existingID)
		return err
	}

	if assignment.ID == "" {
		assignment.ID = utils.GenerateID()
	}
	values := map[string]interface{}{
		constants.FieldID:               assignment.ID,
		"ObjectApiName":                 assignment.ObjectAPIName,
		"LayoutId":                      assignment.LayoutID,
		"RoleId":                        nil,
		"ProfileId":                     nil,
		"RecordTypeId":                  nil,
		"Specificity":                   assignment.Specificity,
		constants.FieldCreatedDate:      query.Raw("CURRENT_TIMESTAMP"),
		constants.FieldLastModifiedDate: query.Raw("CURRENT_TIMESTAMP"),
		"CreatedById":                   assignment.CreatedByID,
		"RouteSource":                   *assignment.RouteSource,
		"OwnerPack":                     assignmentOwnerPackForPersistence(*assignment.RouteSource),
	}
	qr := query.Insert(constants.TableLayoutAssignment, values).Build()
	_, err = tx.ExecContext(ctx, qr.SQL, qr.Params...)
	return err
}

func upsertLayoutAssignmentTx(ctx context.Context, tx *sql.Tx, assignment *models.LayoutAssignment) error {
	assignment.ComputeSpecificity()
	assignment.RouteSource = normalizeRouteSource(assignment.RouteSource)
	if assignment.RouteSource == nil {
		return fmt.Errorf("routeSource is required")
	}

	if err := canonicalizeAssignmentObjectAPIName(ctx, tx, assignment); err != nil {
		return err
	}

	if isWildcardLayoutAssignment(assignment) &&
		strings.EqualFold(*assignment.RouteSource, models.LayoutAssignmentRouteSourceRuntimeFallback) {
		return upsertRuntimeFallbackAssignmentTx(ctx, tx, assignment)
	}

	routeSourceValue := *assignment.RouteSource

	whereClauses := []string{"LOWER(`ObjectApiName`) = LOWER(?)"}
	args := []interface{}{assignment.ObjectAPIName}

	if assignment.RoleID != nil {
		whereClauses = append(whereClauses, "`RoleId` = ?")
		args = append(args, *assignment.RoleID)
	} else {
		whereClauses = append(whereClauses, "`RoleId` IS NULL")
	}
	if assignment.ProfileID != nil {
		whereClauses = append(whereClauses, "`ProfileId` = ?")
		args = append(args, *assignment.ProfileID)
	} else {
		whereClauses = append(whereClauses, "`ProfileId` IS NULL")
	}
	if assignment.RecordTypeID != nil {
		whereClauses = append(whereClauses, "`RecordTypeId` = ?")
		args = append(args, *assignment.RecordTypeID)
	} else {
		whereClauses = append(whereClauses, "`RecordTypeId` IS NULL")
	}

	where := strings.Join(whereClauses, " AND ")

	ownerPackValue := assignmentOwnerPackForPersistence(routeSourceValue)
	updateSQL := fmt.Sprintf(
		"UPDATE `%s` SET `LayoutId` = ?, `Specificity` = ?, `RouteSource` = ?, `OwnerPack` = ?, `LastModifiedDate` = CURRENT_TIMESTAMP WHERE %s",
		constants.TableLayoutAssignment, where,
	)
	updateArgs := append([]interface{}{assignment.LayoutID, assignment.Specificity, routeSourceValue, ownerPackValue}, args...)
	if isAdminOwnedLayoutAssignment(assignment.CreatedByID) {
		updateSQL = fmt.Sprintf(
			"UPDATE `%s` SET `LayoutId` = ?, `Specificity` = ?, `CreatedById` = ?, `RouteSource` = ?, `OwnerPack` = ?, `LastModifiedDate` = CURRENT_TIMESTAMP WHERE %s",
			constants.TableLayoutAssignment, where,
		)
		updateArgs = append([]interface{}{assignment.LayoutID, assignment.Specificity, *assignment.CreatedByID, routeSourceValue, ownerPackValue}, args...)
	}
	result, err := tx.ExecContext(ctx, updateSQL, updateArgs...)
	if err != nil {
		return err
	}
	rowsAffected, _ := result.RowsAffected()
	if rowsAffected > 0 {
		return nil
	}

	if assignment.ID == "" {
		assignment.ID = utils.GenerateID()
	}
	values := map[string]interface{}{
		constants.FieldID:               assignment.ID,
		"ObjectApiName":                 assignment.ObjectAPIName,
		"LayoutId":                      assignment.LayoutID,
		"RoleId":                        assignment.RoleID,
		"ProfileId":                     assignment.ProfileID,
		"RecordTypeId":                  assignment.RecordTypeID,
		"Specificity":                   assignment.Specificity,
		constants.FieldCreatedDate:      query.Raw("CURRENT_TIMESTAMP"),
		constants.FieldLastModifiedDate: query.Raw("CURRENT_TIMESTAMP"),
		"CreatedById":                   assignment.CreatedByID,
		"RouteSource":                   routeSourceValue,
		"OwnerPack":                     ownerPackValue,
	}
	qr := query.Insert(constants.TableLayoutAssignment, values).Build()
	_, err = tx.ExecContext(ctx, qr.SQL, qr.Params...)
	return err
}

func (r *MetadataRepository) loadLiveWildcardLayoutID(ctx context.Context, objectAPIName string) (string, error) {
	target, err := layoutrouting.LoadLiveWildcardTarget(ctx, r.GetExecutor(ctx), objectAPIName)
	if err != nil {
		return "", err
	}
	if target == nil {
		return "", nil
	}
	return target.LayoutID, nil
}

// getEffectiveDefaultLayoutID resolves the one default-layout read path for
// runtime/UI consumers. The wildcard assignment is the only SSoT.
func (r *MetadataRepository) getEffectiveDefaultLayoutID(ctx context.Context, objectAPIName string) (string, error) {
	return r.loadLiveWildcardLayoutID(ctx, objectAPIName)
}

func projectEffectiveDefault(layout *models.PageLayout, defaultLayoutID string) {
	if layout == nil {
		return
	}
	layout.IsDefault = defaultLayoutID != "" && strings.EqualFold(layout.ID, defaultLayoutID)
}

type pageLayoutOwnerState struct {
	ID               string
	ObjectAPIName    string
	LayoutName       string
	Source           string
	IsCustomized     bool
	OwnerPack        string
	LastModifiedDate time.Time
}

func isTrustedAdminPageLayoutSource(source string) bool {
	switch strings.ToLower(strings.TrimSpace(source)) {
	case "admin_created", "cloned", "custom":
		return true
	default:
		return false
	}
}

func isTrustedAdminPageLayoutOwnerState(state pageLayoutOwnerState) bool {
	return strings.TrimSpace(state.OwnerPack) == "" &&
		isTrustedAdminPageLayoutSource(state.Source)
}

func isDefaultsOnlyManagedPageLayoutOwnerState(state pageLayoutOwnerState) bool {
	return strings.TrimSpace(state.OwnerPack) != "" &&
		!state.IsCustomized &&
		strings.EqualFold(strings.TrimSpace(state.Source), "curated")
}

func isPlatformGeneratedPageLayoutOwnerState(state pageLayoutOwnerState) bool {
	return strings.TrimSpace(state.OwnerPack) == "" &&
		!state.IsCustomized &&
		strings.EqualFold(strings.TrimSpace(state.Source), "auto_generated")
}

func loadPageLayoutOwnerStateForUpdate(ctx context.Context, tx *sql.Tx, layoutID string) (*pageLayoutOwnerState, error) {
	sqlStr := fmt.Sprintf(
		"SELECT `%s`, `%s`, `%s`, COALESCE(`%s`, ''), COALESCE(`%s`, 0), COALESCE(`OwnerPack`, ''), `%s` "+
			"FROM `%s` WHERE `%s` = ? AND (`%s` = 0 OR `%s` IS NULL) LIMIT 1 FOR UPDATE",
		constants.FieldID,
		constants.FieldObjectAPIName,
		constants.FieldSysPageLayout_LayoutName,
		constants.FieldSysPageLayout_Source,
		constants.FieldSysPageLayout_IsCustomized,
		constants.FieldLastModifiedDate,
		constants.TablePageLayout,
		constants.FieldID,
		constants.FieldIsDeleted,
		constants.FieldIsDeleted,
	)
	var state pageLayoutOwnerState
	var lastModifiedDate interface{}
	if err := tx.QueryRowContext(ctx, sqlStr, layoutID).Scan(
		&state.ID,
		&state.ObjectAPIName,
		&state.LayoutName,
		&state.Source,
		&state.IsCustomized,
		&state.OwnerPack,
		&lastModifiedDate,
	); err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}
	state.LastModifiedDate = parseTime(lastModifiedDate)
	return &state, nil
}

func loadPageLayoutBusinessKeyCollisionForUpdate(ctx context.Context, tx *sql.Tx, objectAPIName, layoutName, excludeID string) (*pageLayoutOwnerState, error) {
	sqlStr := fmt.Sprintf(
		"SELECT `%s`, COALESCE(`%s`, ''), COALESCE(`%s`, 0), COALESCE(`OwnerPack`, '') "+
			"FROM `%s` WHERE LOWER(`%s`) = LOWER(?) AND LOWER(`%s`) = LOWER(?) AND `%s` <> ? "+
			"AND (`%s` = 0 OR `%s` IS NULL) ORDER BY `%s` ASC, `%s` ASC LIMIT 1 FOR UPDATE",
		constants.FieldID,
		constants.FieldSysPageLayout_Source,
		constants.FieldSysPageLayout_IsCustomized,
		constants.TablePageLayout,
		constants.FieldObjectAPIName,
		constants.FieldSysPageLayout_LayoutName,
		constants.FieldID,
		constants.FieldIsDeleted,
		constants.FieldIsDeleted,
		constants.FieldCreatedDate,
		constants.FieldID,
	)
	var state pageLayoutOwnerState
	if err := tx.QueryRowContext(ctx, sqlStr, objectAPIName, layoutName, excludeID).Scan(
		&state.ID,
		&state.Source,
		&state.IsCustomized,
		&state.OwnerPack,
	); err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}
	return &state, nil
}

func normalizeAdminPageLayoutForPersistence(layout *models.PageLayout) {
	layout.ObjectAPIName = strings.TrimSpace(layout.ObjectAPIName)
	layout.LayoutName = strings.TrimSpace(layout.LayoutName)
	// Clone provenance is an authored lifecycle fact. Every other accepted
	// source becomes admin-owned when this strict administration route writes
	// it, but a clone must not be silently rewritten into a direct create.
	if !strings.EqualFold(strings.TrimSpace(layout.Source), "cloned") {
		layout.Source = "admin_created"
	} else {
		layout.Source = "cloned"
	}
	layout.IsCustomized = true
	layout.GenerationVersion = 0
}

func pageLayoutOwnerTupleError(layoutID string, state pageLayoutOwnerState) error {
	return apperrors.NewValidationError(
		"owner",
		fmt.Sprintf(
			"malformed page layout owner tuple for %s (Source=%q IsCustomized=%v OwnerPack=%q); refusing admin content write",
			layoutID, state.Source, state.IsCustomized, state.OwnerPack,
		),
	)
}

func pageLayoutBusinessKeyConflict(layout *models.PageLayout, existingID string) error {
	value := layout.ObjectAPIName + "/" + layout.LayoutName
	if strings.TrimSpace(existingID) != "" {
		value += " (existing " + existingID + ")"
	}
	return apperrors.NewConflictError("PageLayout", "objectApiName/layoutName", value)
}

func pageLayoutBusinessKeyMismatch(layout *models.PageLayout, state pageLayoutOwnerState) error {
	return apperrors.NewValidationError(
		"objectApiName/layoutName",
		fmt.Sprintf(
			"page layout update business key mismatch for %s: request %q/%q does not match existing %q/%q",
			layout.ID, layout.ObjectAPIName, layout.LayoutName, state.ObjectAPIName, state.LayoutName,
		),
	)
}

// GetLayouts queries all layouts for an object
func (r *MetadataRepository) GetLayouts(ctx context.Context, objectAPIName string) ([]*models.PageLayout, error) {
	qr := query.From(constants.TablePageLayout).
		Select(constants.ColsSystemPageLayout).
		Where(constants.FieldObjectAPIName, objectAPIName).
		Where(constants.FieldIsDeleted, false).
		WhereNot("Type", "Create"). // Exclude stale Type='Create' layouts from PR #580
		OrderBy(constants.FieldSysPageLayout_LayoutName, "ASC").
		Build()

	rows, err := r.GetExecutor(ctx).QueryContext(ctx, qr.SQL, qr.Params...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	layouts := make([]*models.PageLayout, 0)
	for rows.Next() {
		layout, err := r.scanLayout(rows)
		if err != nil {
			return nil, apperrors.NewInternalError("failed to scan Layout workspace row", err)
		}
		layouts = append(layouts, layout)
	}
	if err := rows.Err(); err != nil {
		return nil, apperrors.NewInternalError("error iterating layouts", err)
	}
	// Catalog compilation reads through one transaction. Consume and close this
	// result set before querying the wildcard assignment on the same connection.
	if err := rows.Close(); err != nil {
		return nil, apperrors.NewInternalError("error closing layout rows", err)
	}
	defaultLayoutID, err := r.getEffectiveDefaultLayoutID(ctx, objectAPIName)
	if err != nil {
		return nil, err
	}
	for _, layout := range layouts {
		projectEffectiveDefault(layout, defaultLayoutID)
	}
	sort.SliceStable(layouts, func(i, j int) bool {
		if layouts[i].IsDefault == layouts[j].IsDefault {
			return false
		}
		return layouts[i].IsDefault
	})
	return layouts, nil
}

// GetLayout queries a single layout by ID (excludes soft-deleted)
func (r *MetadataRepository) GetLayout(ctx context.Context, layoutID string) (*models.PageLayout, error) {
	qr := query.From(constants.TablePageLayout).
		Select(constants.ColsSystemPageLayout).
		Where(constants.FieldID, layoutID).
		Where(constants.FieldIsDeleted, false).
		Build()

	layout, err := r.scanLayout(r.GetExecutor(ctx).QueryRowContext(ctx, qr.SQL, qr.Params...))
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}
	defaultLayoutID, err := r.getEffectiveDefaultLayoutID(ctx, layout.ObjectAPIName)
	if err != nil {
		return nil, err
	}
	projectEffectiveDefault(layout, defaultLayoutID)
	return layout, nil
}

// GetLayoutIDForProfile returns the live layout ID assigned to a profile for an object.
// Profile-specific routing now lives in _System_LayoutAssignment; ProfileLayout is
// legacy compat storage and no longer participates in runtime resolution.
func (r *MetadataRepository) GetLayoutIDForProfile(ctx context.Context, objectAPIName, profileID string) (string, error) {
	var layoutID string

	sqlStr := fmt.Sprintf(
		"SELECT la.`LayoutId` "+
			"FROM `%s` la "+
			"JOIN `%s` pl ON pl.`%s` = la.`LayoutId` "+
			"AND LOWER(pl.`%s`) = LOWER(la.`ObjectApiName`) "+
			"AND (pl.`%s` = 0 OR pl.`%s` IS NULL) "+
			"WHERE LOWER(la.`ObjectApiName`) = LOWER(?) "+
			"AND la.`ProfileId` = ? "+
			"AND la.`RoleId` IS NULL "+
			"AND la.`RecordTypeId` IS NULL "+
			"ORDER BY la.`CreatedDate` ASC LIMIT 1",
		constants.TableLayoutAssignment,
		constants.TablePageLayout,
		constants.ColID,
		constants.FieldObjectAPIName,
		constants.FieldIsDeleted,
		constants.FieldIsDeleted,
	)

	err := r.db.QueryRowContext(ctx, sqlStr, objectAPIName, profileID).Scan(&layoutID)
	if err == sql.ErrNoRows {
		return "", nil // Not assigned
	}
	if err != nil {
		return "", err
	}
	return layoutID, nil
}

// OwnerPack lifecycle note for the shared persistence helpers:
//
// _System_PageLayout: SaveLayout is the admin content-write route. It normalizes
// Source/IsCustomized, clears OwnerPack, and refuses malformed ownership tuples
// instead of trusting caller-provided owner bits. UpsertLayout is reserved for
// the trusted platform auto-generated layout route. Source-backed pack writers
// thread OwnerPack=<packName> via the layoutownership package helpers
// (UpsertManagedPageLayout etc.).
//
// _System_LayoutAssignment (upsertLayoutAssignmentTx, upsertRuntimeFallbackAssignmentTx):
// these helpers DERIVE OwnerPack from routeSource via the same contract used by
// the layoutownership package (admin_authored => NULL, otherwise packName). Since
// these admin/runtime helpers have no pack context (packName==""), the result is
// effectively always NULL — but the derivation is wired so the semantic intent
// stays documented and a future caller threading a real packName Just Works.
//
// Read-path migration to retire the legacy heuristic is tracked in #1012.

// SaveLayout saves or updates a page layout
func (r *MetadataRepository) SaveLayout(ctx context.Context, layout *models.PageLayout) error {
	return r.saveLayout(ctx, layout, layoutWriteUpsert)
}

type layoutWriteMode uint8

const (
	layoutWriteUpsert layoutWriteMode = iota
	layoutWriteCreate
	layoutWriteUpdate
)

// CreateLayoutForAdministration is the strict create route. A pre-existing
// target identity is a collision and is never interpreted as an update.
func (r *MetadataRepository) CreateLayoutForAdministration(ctx context.Context, layout *models.PageLayout) error {
	return r.saveLayout(ctx, layout, layoutWriteCreate)
}

// SaveLayoutForAdministration is the storage update route used after the
// MetadataService has locked the complete authoring aggregate and validated
// the database-owned expected version. LastModifiedDate is audit metadata, not
// a concurrency token. Legacy SaveLayout retains its historical last-write
// behavior.
func (r *MetadataRepository) SaveLayoutForAdministration(ctx context.Context, layout *models.PageLayout) error {
	return r.saveLayout(ctx, layout, layoutWriteUpdate)
}

func (r *MetadataRepository) saveLayout(ctx context.Context, layout *models.PageLayout, mode layoutWriteMode) error {
	if layout == nil {
		return apperrors.NewValidationError("layout", "page layout is required")
	}
	normalizeAdminPageLayoutForPersistence(layout)
	if layout.ID == "" {
		return apperrors.NewValidationError("id", "page layout id is required")
	}
	if layout.ObjectAPIName == "" {
		return apperrors.NewValidationError("objectApiName", "objectApiName is required")
	}
	if layout.LayoutName == "" {
		return apperrors.NewValidationError("layoutName", "layoutName is required")
	}

	tx, ownsTransaction := r.GetExecutor(ctx).(*sql.Tx)
	cleanup := func() error { return nil }
	var err error
	if !ownsTransaction {
		tx, cleanup, err = BeginPessimisticTx(ctx, r.db)
		if err != nil {
			return err
		}
	}
	committed := false
	defer func() {
		if ownsTransaction {
			return
		}
		if !committed {
			_ = tx.Rollback()
		}
		_ = cleanup()
	}()

	current, err := loadPageLayoutOwnerStateForUpdate(ctx, tx, layout.ID)
	if err != nil {
		return err
	}
	if current != nil &&
		!isTrustedAdminPageLayoutOwnerState(*current) &&
		!isDefaultsOnlyManagedPageLayoutOwnerState(*current) &&
		!isPlatformGeneratedPageLayoutOwnerState(*current) {
		return pageLayoutOwnerTupleError(layout.ID, *current)
	}
	if current != nil && mode == layoutWriteCreate {
		return apperrors.NewConflictError("PageLayout", "id", layout.ID)
	}
	if current != nil &&
		!strings.EqualFold(strings.TrimSpace(layout.ObjectAPIName), strings.TrimSpace(current.ObjectAPIName)) {
		return pageLayoutBusinessKeyMismatch(layout, *current)
	}
	// Name is part of the authored Layout document. The strict authoring update
	// route may rename it after the identity/object and resource snapshot have
	// been fenced. Legacy SaveLayout retains its historical immutable-business-
	// key behavior until its callers migrate to the authoring lifecycle.
	if current != nil && mode != layoutWriteUpdate &&
		!strings.EqualFold(strings.TrimSpace(layout.LayoutName), strings.TrimSpace(current.LayoutName)) {
		return pageLayoutBusinessKeyMismatch(layout, *current)
	}
	if mode == layoutWriteUpdate {
		if current == nil {
			return apperrors.NewNotFoundError("PageLayout", layout.ID)
		}
	}
	collision, err := loadPageLayoutBusinessKeyCollisionForUpdate(ctx, tx, layout.ObjectAPIName, layout.LayoutName, layout.ID)
	if err != nil {
		return err
	}
	if collision != nil {
		return pageLayoutBusinessKeyConflict(layout, collision.ID)
	}

	// Default/deleted are routing and lifecycle facts, not caller-authored
	// document fields. Create starts live and non-default. Update derives its
	// compatibility default shadow from the wildcard assignment in this same
	// transaction, so a content write also converges historical raw drift.
	layout.IsDeleted = false
	if current == nil {
		layout.IsDefault = false
	} else {
		wildcard, err := layoutrouting.LoadLiveWildcardTarget(ctx, tx, layout.ObjectAPIName)
		if err != nil {
			return apperrors.NewInternalError("failed to read authoritative default Layout assignment", err)
		}
		layout.IsDefault = wildcard != nil && strings.EqualFold(layout.ID, wildcard.LayoutID)
	}
	configJSON, err := r.marshalJSON(layout)
	if err != nil {
		return apperrors.NewInternalError("failed to marshal layout", err)
	}

	if current == nil {
		qr := query.Insert(constants.TablePageLayout, map[string]interface{}{
			constants.FieldID:                              layout.ID,
			constants.FieldObjectAPIName:                   layout.ObjectAPIName,
			constants.FieldSysPageLayout_LayoutName:        layout.LayoutName,
			constants.FieldSysPageLayout_Type:              layout.Type,
			constants.FieldSysPageLayout_IsDefault:         layout.IsDefault,
			constants.FieldIsDeleted:                       layout.IsDeleted,
			constants.FieldSysPageLayout_Config:            configJSON,
			"LayoutVersion":                                layout.LayoutVersion,
			"IsCustomized":                                 true,
			constants.FieldSysPageLayout_Source:            layout.Source,
			constants.FieldSysPageLayout_GenerationVersion: layout.GenerationVersion,
			"OwnerPack":                                    nil,
			constants.FieldCreatedDate:                     query.Raw("CURRENT_TIMESTAMP"),
			constants.FieldLastModifiedDate:                query.Raw("CURRENT_TIMESTAMP"),
		}).Build()
		if _, err := tx.ExecContext(ctx, qr.SQL, qr.Params...); err != nil {
			if IsDuplicateKeyError(err) {
				return pageLayoutBusinessKeyConflict(layout, "")
			}
			return err
		}
		if !ownsTransaction {
			if err := tx.Commit(); err != nil {
				return err
			}
			committed = true
		}
		return nil
	}

	updateSQL := fmt.Sprintf(
		"UPDATE `%s` SET `%s` = ?, `%s` = ?, `%s` = ?, `%s` = ?, `%s` = ?, `%s` = ?, `LayoutVersion` = ?, `IsCustomized` = ?, `%s` = ?, `%s` = ?, `OwnerPack` = NULL, `%s` = CURRENT_TIMESTAMP "+
			"WHERE `%s` = ? AND LOWER(COALESCE(`%s`, '')) = LOWER(?) AND LOWER(COALESCE(`%s`, '')) = LOWER(?) AND COALESCE(`%s`, '') = ? AND COALESCE(`%s`, 0) = ? AND COALESCE(`OwnerPack`, '') = ? AND (`%s` = 0 OR `%s` IS NULL)",
		constants.TablePageLayout,
		constants.FieldObjectAPIName,
		constants.FieldSysPageLayout_LayoutName,
		constants.FieldSysPageLayout_Type,
		constants.FieldSysPageLayout_IsDefault,
		constants.FieldIsDeleted,
		constants.FieldSysPageLayout_Config,
		constants.FieldSysPageLayout_Source,
		constants.FieldSysPageLayout_GenerationVersion,
		constants.FieldLastModifiedDate,
		constants.FieldID,
		constants.FieldObjectAPIName,
		constants.FieldSysPageLayout_LayoutName,
		constants.FieldSysPageLayout_Source,
		constants.FieldSysPageLayout_IsCustomized,
		constants.FieldIsDeleted,
		constants.FieldIsDeleted,
	)
	result, err := tx.ExecContext(
		ctx,
		updateSQL,
		layout.ObjectAPIName,
		layout.LayoutName,
		layout.Type,
		layout.IsDefault,
		layout.IsDeleted,
		configJSON,
		layout.LayoutVersion,
		true,
		layout.Source,
		layout.GenerationVersion,
		layout.ID,
		strings.TrimSpace(current.ObjectAPIName),
		strings.TrimSpace(current.LayoutName),
		strings.TrimSpace(current.Source),
		current.IsCustomized,
		strings.TrimSpace(current.OwnerPack),
	)
	if err != nil {
		return err
	}
	rowsAffected, _ := result.RowsAffected()
	if rowsAffected == 0 {
		guardSQL := fmt.Sprintf(
			"SELECT 1 FROM `%s` WHERE `%s` = ? AND LOWER(COALESCE(`%s`, '')) = LOWER(?) AND LOWER(COALESCE(`%s`, '')) = LOWER(?) AND COALESCE(`%s`, '') = ? AND COALESCE(`%s`, 0) = ? AND COALESCE(`OwnerPack`, '') = ? AND (`%s` = 0 OR `%s` IS NULL)",
			constants.TablePageLayout,
			constants.FieldID,
			constants.FieldObjectAPIName,
			constants.FieldSysPageLayout_LayoutName,
			constants.FieldSysPageLayout_Source,
			constants.FieldSysPageLayout_IsCustomized,
			constants.FieldIsDeleted,
			constants.FieldIsDeleted,
		)
		var matched int
		if err := tx.QueryRowContext(
			ctx, guardSQL, layout.ID,
			strings.TrimSpace(current.ObjectAPIName), strings.TrimSpace(current.LayoutName),
			strings.TrimSpace(current.Source), current.IsCustomized, strings.TrimSpace(current.OwnerPack),
		).Scan(&matched); err != nil {
			if err == sql.ErrNoRows {
				return apperrors.NewConflictError("PageLayout", "owner", layout.ID)
			}
			return err
		}
	}
	if !ownsTransaction {
		if err := tx.Commit(); err != nil {
			return err
		}
		committed = true
	}
	return nil
}

func isAdminOwnedLayoutAssignment(createdByID *string) bool {
	return createdByID != nil && strings.TrimSpace(*createdByID) != ""
}

// assignmentOwnerPackForPersistence derives the OwnerPack value for a
// LayoutAssignment row from its RouteSource. This mirrors the contract in
// layoutownership.assignmentOwnerPackFromRouteSource (added in 36fee588):
//
//	admin_authored => "" (NULL in DB)
//	anything else  => packName
//
// Persistence helpers have no pack context so packName is always ""; the
// result is effectively always NULL today, but wiring the derivation documents
// the semantic intent and keeps LayoutAssignment ownership consistent with the
// layoutownership package helpers.
func assignmentOwnerPackForPersistence(routeSource string) interface{} {
	if strings.EqualFold(strings.TrimSpace(routeSource), models.LayoutAssignmentRouteSourceAdminAuthored) {
		return nil
	}
	// packName is "" in this context (no pack caller); return nil so the DB
	// column is NULL. A future caller with a real packName would return it here.
	return nil
}

// SetDefaultLayout clears isDefault on all layouts for the object, then sets it on the given layout.
// Both operations are wrapped in a transaction so that a failure in the second statement
// does not leave the object with no default layout.
func (r *MetadataRepository) SetDefaultLayout(ctx context.Context, objectAPIName string, layoutID string, actorID *string) error {
	tx, ownsTransaction := r.GetExecutor(ctx).(*sql.Tx)
	if !ownsTransaction {
		var err error
		tx, err = r.db.BeginTx(ctx, nil)
		if err != nil {
			return fmt.Errorf("failed to begin transaction: %w", err)
		}
		defer tx.Rollback() //nolint:errcheck // rollback is a no-op after commit
	}

	routeSource := models.LayoutAssignmentRouteSourceAdminAuthored
	defaultAssignment := &models.LayoutAssignment{
		ObjectAPIName: objectAPIName,
		LayoutID:      layoutID,
		CreatedByID:   actorID,
		RouteSource:   &routeSource,
	}
	if err := canonicalizeAssignmentObjectAPIName(ctx, tx, defaultAssignment); err != nil {
		return err
	}

	clearSQL := fmt.Sprintf("UPDATE `%s` SET `%s` = false, `%s` = CURRENT_TIMESTAMP WHERE `%s` = ?",
		constants.TablePageLayout,
		constants.FieldSysPageLayout_IsDefault,
		constants.FieldLastModifiedDate,
		constants.FieldObjectAPIName,
	)
	if _, err := tx.ExecContext(ctx, clearSQL, defaultAssignment.ObjectAPIName); err != nil {
		return fmt.Errorf("failed to clear default: %w", err)
	}

	setSQL := fmt.Sprintf("UPDATE `%s` SET `%s` = true, `%s` = CURRENT_TIMESTAMP WHERE `%s` = ? AND `%s` = ? AND (`%s` = false OR `%s` IS NULL)",
		constants.TablePageLayout,
		constants.FieldSysPageLayout_IsDefault,
		constants.FieldLastModifiedDate,
		constants.FieldID,
		constants.FieldObjectAPIName,
		constants.FieldIsDeleted,
		constants.FieldIsDeleted,
	)
	result, err := tx.ExecContext(ctx, setSQL, defaultAssignment.LayoutID, defaultAssignment.ObjectAPIName)
	if err != nil {
		return fmt.Errorf("failed to set default: %w", err)
	}
	rowsAffected, _ := result.RowsAffected()
	if rowsAffected == 0 {
		return apperrors.NewInvalidReferenceError(
			fmt.Sprintf("layoutId %q does not reference a live page layout for %q", defaultAssignment.LayoutID, defaultAssignment.ObjectAPIName),
			"layoutId",
		)
	}
	if err := upsertLayoutAssignmentTx(ctx, tx, defaultAssignment); err != nil {
		return fmt.Errorf("failed to upsert wildcard assignment: %w", err)
	}

	// "Set Default" is routing intent, not content ownership. Persist the
	// wildcard assignment authorship, but keep Source/GenerationVersion on the
	// layout row unchanged so schema-driven regeneration continues to work.
	if ownsTransaction {
		return nil
	}
	return tx.Commit()
}

// DeleteLayout soft-deletes a layout by setting IsDeleted = true.
// This is the legacy unguarded route; durable administration uses the strict
// ownership-fenced method below.
func (r *MetadataRepository) DeleteLayout(ctx context.Context, layoutID string) error {
	sql := fmt.Sprintf("UPDATE `%s` SET `%s` = true, `%s` = CURRENT_TIMESTAMP WHERE `%s` = ?",
		constants.TablePageLayout,
		constants.FieldIsDeleted,
		constants.FieldLastModifiedDate,
		constants.FieldID,
	)
	_, err := r.GetExecutor(ctx).ExecContext(ctx, sql, layoutID)
	return err
}

// DeleteLayoutForAdministration soft-deletes only a transaction-current,
// trusted admin-owned Layout. Managed and generated rows have another
// lifecycle owner and cannot acquire a durable admin tombstone through this
// route; malformed ownership tuples fail closed as well.
func (r *MetadataRepository) DeleteLayoutForAdministration(ctx context.Context, layoutID string) error {
	tx, ok := r.GetExecutor(ctx).(*sql.Tx)
	if !ok {
		return apperrors.NewInternalError("strict Layout administration delete requires the owning transaction", nil)
	}
	current, err := loadPageLayoutOwnerStateForUpdate(ctx, tx, layoutID)
	if err != nil {
		return err
	}
	if current == nil {
		return apperrors.NewNotFoundError("PageLayout", layoutID)
	}
	if !isTrustedAdminPageLayoutOwnerState(*current) {
		if isDefaultsOnlyManagedPageLayoutOwnerState(*current) || isPlatformGeneratedPageLayoutOwnerState(*current) {
			return apperrors.NewForbiddenError("only admin-owned PageLayouts may be deleted through Layout administration")
		}
		return apperrors.NewInternalError("PageLayout ownership tuple is invalid; refusing administration delete", nil)
	}
	deleteSQL := fmt.Sprintf(
		"UPDATE `%s` SET `%s` = true, `%s` = CURRENT_TIMESTAMP "+
			"WHERE `%s` = ? AND COALESCE(`%s`, '') = ? AND COALESCE(`%s`, 0) = ? "+
			"AND COALESCE(`OwnerPack`, '') = ? AND (`%s` = false OR `%s` IS NULL)",
		constants.TablePageLayout,
		constants.FieldIsDeleted,
		constants.FieldLastModifiedDate,
		constants.FieldID,
		constants.FieldSysPageLayout_Source,
		constants.FieldSysPageLayout_IsCustomized,
		constants.FieldIsDeleted,
		constants.FieldIsDeleted,
	)
	result, err := tx.ExecContext(
		ctx, deleteSQL, current.ID, current.Source, current.IsCustomized, strings.TrimSpace(current.OwnerPack),
	)
	if err != nil {
		return err
	}
	rowsAffected, _ := result.RowsAffected()
	if rowsAffected != 1 {
		return apperrors.NewConflictError("PageLayout", "owner", layoutID)
	}
	return nil
}

// AssignLayoutToProfile assigns a layout to a profile using the layout-assignment SSoT.
func (r *MetadataRepository) AssignLayoutToProfile(ctx context.Context, profileID, objectAPIName, layoutID string) error {
	routeSource := models.LayoutAssignmentRouteSourceAdminAuthored
	assignment := &models.LayoutAssignment{
		ObjectAPIName: objectAPIName,
		LayoutID:      layoutID,
		ProfileID:     &profileID,
		RouteSource:   &routeSource,
	}
	return r.CreateAssignment(ctx, assignment)
}

// UpsertLayout updates or inserts a page layout
func (r *MetadataRepository) UpsertLayout(ctx context.Context, layout *models.PageLayout) error {
	if layout == nil {
		return apperrors.NewValidationError("layout", "page layout is required")
	}
	if !strings.EqualFold(strings.TrimSpace(layout.Source), "auto_generated") || layout.IsCustomized {
		return apperrors.NewValidationError("route", "UpsertLayout is reserved for the trusted platform page layout route")
	}
	configJSON, err := r.marshalJSON(layout)
	if err != nil {
		return apperrors.NewInternalError("failed to marshal layout", err)
	}

	qr := query.Insert(constants.TablePageLayout, map[string]interface{}{
		constants.FieldID:                              layout.ID,
		constants.FieldObjectAPIName:                   layout.ObjectAPIName,
		constants.FieldSysPageLayout_LayoutName:        layout.LayoutName,
		constants.FieldSysPageLayout_Type:              layout.Type,
		constants.FieldSysPageLayout_IsDefault:         layout.IsDefault,
		constants.FieldIsDeleted:                       layout.IsDeleted,
		constants.FieldSysPageLayout_Config:            configJSON,
		"LayoutVersion":                                layout.LayoutVersion,
		"IsCustomized":                                 layout.IsCustomized,
		constants.FieldSysPageLayout_Source:            layout.Source,
		constants.FieldSysPageLayout_GenerationVersion: layout.GenerationVersion,
		constants.FieldCreatedDate:                     query.Raw("CURRENT_TIMESTAMP"),
		constants.FieldLastModifiedDate:                query.Raw("CURRENT_TIMESTAMP"),
	}).
		OnDuplicateKeyUpdate(map[string]interface{}{
			constants.FieldObjectAPIName:                   layout.ObjectAPIName,
			constants.FieldSysPageLayout_LayoutName:        layout.LayoutName,
			constants.FieldSysPageLayout_Type:              layout.Type,
			constants.FieldSysPageLayout_IsDefault:         layout.IsDefault,
			constants.FieldIsDeleted:                       layout.IsDeleted,
			constants.FieldSysPageLayout_Config:            configJSON,
			"LayoutVersion":                                layout.LayoutVersion,
			"IsCustomized":                                 layout.IsCustomized,
			constants.FieldSysPageLayout_Source:            layout.Source,
			constants.FieldSysPageLayout_GenerationVersion: layout.GenerationVersion,
			constants.FieldLastModifiedDate:                query.Raw("NOW()"),
		}).
		Build()

	_, err = r.db.ExecContext(ctx, qr.SQL, qr.Params...)
	return err
}

// --- Layout Assignment (specificity-based routing) ---

// FindBestAssignment finds the highest-specificity assignment matching the user context.
// roleAncestors should include the user's own RoleID + all ancestors (or nil if no role).
func (r *MetadataRepository) FindBestAssignment(ctx context.Context, objectAPIName string, roleAncestors []string, profileID string, recordTypeID string) (*models.LayoutAssignment, error) {
	var args []interface{}
	args = append(args, objectAPIName)
	var roleOrderArgs []interface{}

	// Role clause: if no role, only match wildcard-role assignments
	roleClause := "`RoleId` IS NULL"
	if len(roleAncestors) > 0 {
		placeholders := make([]string, len(roleAncestors))
		for i, r := range roleAncestors {
			placeholders[i] = "?"
			args = append(args, r)
			roleOrderArgs = append(roleOrderArgs, r)
		}
		roleClause = fmt.Sprintf("(`RoleId` IS NULL OR `RoleId` IN (%s))", strings.Join(placeholders, ","))
	}

	// Profile clause
	profileClause := "`ProfileId` IS NULL"
	if profileID != "" {
		profileClause = "(`ProfileId` IS NULL OR `ProfileId` = ?)"
		args = append(args, profileID)
	}

	// RecordType clause
	rtClause := "`RecordTypeId` IS NULL"
	if recordTypeID != "" {
		rtClause = "(`RecordTypeId` IS NULL OR `RecordTypeId` = ?)"
		args = append(args, recordTypeID)
	}

	roleOrderClause := ""
	if len(roleOrderArgs) > 0 {
		when := make([]string, len(roleOrderArgs))
		for index := range roleOrderArgs {
			when[index] = fmt.Sprintf("WHEN `RoleId` = ? THEN %d", index)
		}
		roleOrderClause = ", CASE " + strings.Join(when, " ") +
			fmt.Sprintf(" ELSE %d END ASC", len(roleOrderArgs))
		args = append(args, roleOrderArgs...)
	}

	sqlStr := fmt.Sprintf(
		"SELECT `Id`, `ObjectApiName`, `LayoutId`, `RoleId`, `ProfileId`, `RecordTypeId`, `Specificity`, `CreatedDate`, `LastModifiedDate`, `CreatedById`, `RouteSource` "+
			"FROM `%s` WHERE LOWER(`ObjectApiName`) = LOWER(?) AND %s AND %s AND %s "+
			"ORDER BY `Specificity` DESC, (`RecordTypeId` IS NOT NULL) DESC, (`ProfileId` IS NOT NULL) DESC, (`RoleId` IS NOT NULL) DESC%s, `CreatedDate` ASC "+
			"LIMIT 1",
		constants.TableLayoutAssignment, roleClause, profileClause, rtClause, roleOrderClause,
	)

	var a models.LayoutAssignment
	var roleID, profID, rtID, createdByID, routeSource sql.NullString
	var createdDate, lastModifiedDate interface{}

	err := r.db.QueryRowContext(ctx, sqlStr, args...).Scan(
		&a.ID, &a.ObjectAPIName, &a.LayoutID,
		&roleID, &profID, &rtID,
		&a.Specificity, &createdDate, &lastModifiedDate, &createdByID, &routeSource,
	)

	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	if roleID.Valid {
		a.RoleID = &roleID.String
	}
	if profID.Valid {
		a.ProfileID = &profID.String
	}
	if rtID.Valid {
		a.RecordTypeID = &rtID.String
	}
	if createdByID.Valid {
		a.CreatedByID = &createdByID.String
	}
	if routeSource.Valid {
		a.RouteSource = &routeSource.String
	}
	a.CreatedDate = parseTime(createdDate)
	a.LastModifiedDate = parseTime(lastModifiedDate)
	return &a, nil
}

// ReadCurrentObjectListFields reads the current tenant-owned ordered list
// presentation for a bounded object set in one query. Results are keyed by the
// lower-cased canonical object identity supplied by storage.
func (r *MetadataRepository) ReadCurrentObjectListFields(
	ctx context.Context,
	objectAPIName []string,
) (map[string][]string, error) {
	if len(objectAPIName) == 0 {
		return map[string][]string{}, nil
	}
	placeholders := make([]string, len(objectAPIName))
	args := make([]interface{}, len(objectAPIName))
	for index, objectID := range objectAPIName {
		placeholders[index] = "?"
		args[index] = objectID
	}
	statement := fmt.Sprintf(
		"SELECT `%s`, `%s` FROM `%s` WHERE `%s` IN (%s) AND (`%s` = false OR `%s` IS NULL)",
		constants.FieldAPIName,
		constants.FieldSysObject_ListFields,
		constants.TableObject,
		constants.FieldAPIName,
		strings.Join(placeholders, ","),
		constants.FieldIsDeleted,
		constants.FieldIsDeleted,
	)
	rows, err := r.GetExecutor(ctx).QueryContext(ctx, statement, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	result := make(map[string][]string, len(objectAPIName))
	for rows.Next() {
		var canonicalObjectID string
		var encoded sql.NullString
		if err := rows.Scan(&canonicalObjectID, &encoded); err != nil {
			return nil, err
		}
		key := strings.ToLower(canonicalObjectID)
		if _, duplicate := result[key]; duplicate {
			return nil, apperrors.NewInternalError("current ListFields object identity is ambiguous", nil)
		}
		fields := []string{}
		if encoded.Valid && encoded.String != "" {
			if err := jsondocument.DecodeUseNumber([]byte(encoded.String), &fields); err != nil {
				return nil, apperrors.NewInternalError("stored current ListFields are invalid", err)
			}
			if fields == nil {
				fields = []string{}
			}
		}
		result[key] = fields
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	for _, objectID := range objectAPIName {
		if _, found := result[strings.ToLower(objectID)]; !found {
			return nil, apperrors.NewNotFoundError("Object", objectID)
		}
	}
	return result, nil
}

// CreateAssignment upserts a layout assignment.
// Because NULL != NULL in SQL unique indexes, ON DUPLICATE KEY UPDATE cannot
// deduplicate on (ObjectApiName, RoleId, ProfileId, RecordTypeId) when dimensions
// are NULL. Instead, we find any existing row with matching dimensions and UPDATE it,
// or INSERT a new row if none exists.
func (r *MetadataRepository) CreateAssignment(ctx context.Context, assignment *models.LayoutAssignment) error {
	assignment.RouteSource = normalizeRouteSource(assignment.RouteSource)
	if assignment.RouteSource == nil {
		return fmt.Errorf("routeSource is required")
	}

	tx, ownsTransaction := r.GetExecutor(ctx).(*sql.Tx)
	if !ownsTransaction {
		var err error
		tx, err = r.db.BeginTx(ctx, nil)
		if err != nil {
			return fmt.Errorf("failed to begin transaction: %w", err)
		}
		defer tx.Rollback() //nolint:errcheck // rollback is a no-op after commit
	}

	if err := upsertLayoutAssignmentTx(ctx, tx, assignment); err != nil {
		return err
	}
	// Non-wildcard assignment writes are also routing intent only. The
	// assignment row carries authorship; the target layout row keeps whatever
	// provenance it already had.
	if ownsTransaction {
		return nil
	}
	return tx.Commit()
}

// DeleteAssignment hard-deletes a layout assignment by ID.
func (r *MetadataRepository) DeleteAssignment(ctx context.Context, id string) error {
	sqlStr := fmt.Sprintf("DELETE FROM `%s` WHERE `%s` = ?", constants.TableLayoutAssignment, constants.FieldID)
	_, err := r.GetExecutor(ctx).ExecContext(ctx, sqlStr, id)
	return err
}

type layoutAssignmentOwnerState struct {
	ID           string
	RoleID       sql.NullString
	ProfileID    sql.NullString
	RecordTypeID sql.NullString
	RouteSource  string
	OwnerPack    string
}

// DeleteAssignmentForAdministration deletes only a specific admin-authored
// routing override. The selectorless wildcard is the default-layout authority
// and may be changed only through SetDefaultLayout.
func (r *MetadataRepository) DeleteAssignmentForAdministration(ctx context.Context, assignmentID string) error {
	tx, ok := r.GetExecutor(ctx).(*sql.Tx)
	if !ok {
		return apperrors.NewInternalError("strict Layout assignment delete requires the owning transaction", nil)
	}
	querySQL := fmt.Sprintf(
		"SELECT `%s`, `RoleId`, `ProfileId`, `RecordTypeId`, COALESCE(`RouteSource`, ''), COALESCE(`OwnerPack`, '') "+
			"FROM `%s` WHERE `%s` = ? LIMIT 1 FOR UPDATE",
		constants.FieldID, constants.TableLayoutAssignment, constants.FieldID,
	)
	var current layoutAssignmentOwnerState
	if err := tx.QueryRowContext(ctx, querySQL, assignmentID).Scan(
		&current.ID, &current.RoleID, &current.ProfileID, &current.RecordTypeID,
		&current.RouteSource, &current.OwnerPack,
	); err != nil {
		if err == sql.ErrNoRows {
			return apperrors.NewNotFoundError("LayoutAssignment", assignmentID)
		}
		return err
	}
	if current.RoleID.Valid && strings.TrimSpace(current.RoleID.String) == "" ||
		current.ProfileID.Valid && strings.TrimSpace(current.ProfileID.String) == "" ||
		current.RecordTypeID.Valid && strings.TrimSpace(current.RecordTypeID.String) == "" {
		return apperrors.NewInternalError("LayoutAssignment selector tuple is invalid; refusing administration delete", nil)
	}
	routeSource := strings.ToLower(strings.TrimSpace(current.RouteSource))
	ownerPack := strings.TrimSpace(current.OwnerPack)
	adminOwned := routeSource == models.LayoutAssignmentRouteSourceAdminAuthored && ownerPack == ""
	managedOwned := routeSource == models.LayoutAssignmentRouteSourceManagedSeed && ownerPack != ""
	platformOwned := routeSource == models.LayoutAssignmentRouteSourceRuntimeFallback && ownerPack == ""
	if !adminOwned && !managedOwned && !platformOwned {
		return apperrors.NewInternalError("LayoutAssignment ownership tuple is invalid; refusing administration delete", nil)
	}
	if !adminOwned {
		return apperrors.NewForbiddenError("only admin-authored Layout assignments may be deleted through Layout administration")
	}
	if !current.RoleID.Valid && !current.ProfileID.Valid && !current.RecordTypeID.Valid {
		return apperrors.NewConflictError("LayoutAssignment", "default", assignmentID)
	}
	deleteSQL := fmt.Sprintf(
		"DELETE FROM `%s` WHERE `%s` = ? AND LOWER(COALESCE(`RouteSource`, '')) = LOWER(?) "+
			"AND COALESCE(`OwnerPack`, '') = '' AND (`RoleId` IS NOT NULL OR `ProfileId` IS NOT NULL OR `RecordTypeId` IS NOT NULL)",
		constants.TableLayoutAssignment, constants.FieldID,
	)
	result, err := tx.ExecContext(ctx, deleteSQL, current.ID, models.LayoutAssignmentRouteSourceAdminAuthored)
	if err != nil {
		return err
	}
	rowsAffected, _ := result.RowsAffected()
	if rowsAffected != 1 {
		return apperrors.NewConflictError("LayoutAssignment", "owner", assignmentID)
	}
	return nil
}

// GetAssignmentsForObject returns all assignments for an object.
func (r *MetadataRepository) GetAssignmentsForObject(ctx context.Context, objectAPIName string) ([]*models.LayoutAssignment, error) {
	sqlStr := fmt.Sprintf(
		"SELECT `Id`, `ObjectApiName`, `LayoutId`, `RoleId`, `ProfileId`, `RecordTypeId`, `Specificity`, `CreatedDate`, `LastModifiedDate`, `CreatedById`, `RouteSource` "+
			"FROM `%s` WHERE LOWER(`ObjectApiName`) = LOWER(?) ORDER BY `Specificity` DESC",
		constants.TableLayoutAssignment,
	)

	rows, err := r.GetExecutor(ctx).QueryContext(ctx, sqlStr, objectAPIName)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	assignments := []*models.LayoutAssignment{}
	for rows.Next() {
		assignment, err := scanLayoutAssignment(rows)
		if err != nil {
			return nil, apperrors.NewInternalError("failed to scan Layout assignment workspace row", err)
		}
		assignments = append(assignments, assignment)
	}
	return assignments, rows.Err()
}

func scanLayoutAssignment(row Scannable) (*models.LayoutAssignment, error) {
	var assignment models.LayoutAssignment
	var roleID, profileID, recordTypeID, createdByID, routeSource sql.NullString
	var createdDate, lastModifiedDate interface{}
	if err := row.Scan(
		&assignment.ID, &assignment.ObjectAPIName, &assignment.LayoutID,
		&roleID, &profileID, &recordTypeID, &assignment.Specificity,
		&createdDate, &lastModifiedDate, &createdByID, &routeSource,
	); err != nil {
		return nil, err
	}
	if roleID.Valid {
		assignment.RoleID = &roleID.String
	}
	if profileID.Valid {
		assignment.ProfileID = &profileID.String
	}
	if recordTypeID.Valid {
		assignment.RecordTypeID = &recordTypeID.String
	}
	if createdByID.Valid {
		assignment.CreatedByID = &createdByID.String
	}
	if routeSource.Valid {
		assignment.RouteSource = &routeSource.String
	}
	assignment.CreatedDate = parseTime(createdDate)
	assignment.LastModifiedDate = parseTime(lastModifiedDate)
	return &assignment, nil
}

// DeleteAssignmentsByLayoutID removes all assignments pointing to a layout.
func (r *MetadataRepository) DeleteAssignmentsByLayoutID(ctx context.Context, layoutID string) error {
	sqlStr := fmt.Sprintf("DELETE FROM `%s` WHERE `LayoutId` = ?", constants.TableLayoutAssignment)
	_, err := r.GetExecutor(ctx).ExecContext(ctx, sqlStr, layoutID)
	return err
}

// SetLayoutAuthoringListFields writes the ordered object list presentation as
// one explicit authoring fact. A non-nil empty slice is an authored clear, not
// PRESERVE. The caller owns the transaction that also writes the Layout and
// history row.
func (r *MetadataRepository) SetLayoutAuthoringListFields(ctx context.Context, objectAPIName string, fields []string) error {
	const writerID = "nexuscrm-persistence/repo_layout.go#MetadataRepository.SetLayoutAuthoringListFields"
	if r.permissionStateDirectWriterShouldRun(ctx, writerID) {
		return r.runPermissionStateDirectWriter(
			ctx,
			writerID,
			[]permissionStateDirectTarget{
				permissionStateDirectTargetFor(
					constants.TableObject,
					permissionStateDirectCaseInsensitiveEqual(constants.FieldAPIName),
					objectAPIName,
				),
			},
			func(txCtx context.Context) error {
				return r.SetLayoutAuthoringListFields(
					withPermissionStateDirectWriter(txCtx, writerID), objectAPIName, fields,
				)
			},
		)
	}
	encoded, err := r.marshalJSON(fields)
	if err != nil {
		return apperrors.NewInternalError("failed to marshal Layout authoring ListFields", err)
	}
	executor, ok := r.GetExecutor(ctx).(*sql.Tx)
	if !ok {
		return apperrors.NewInternalError(
			"Layout authoring ListFields write requires an active transaction", nil,
		)
	}
	lockStatement := fmt.Sprintf(
		"SELECT 1 FROM `%s` WHERE LOWER(`%s`) = LOWER(?) AND (`%s` = false OR `%s` IS NULL) FOR UPDATE",
		constants.TableObject,
		constants.FieldAPIName,
		constants.FieldIsDeleted,
		constants.FieldIsDeleted,
	)
	var exists int
	if err := executor.QueryRowContext(ctx, lockStatement, objectAPIName).Scan(&exists); err != nil {
		if err == sql.ErrNoRows {
			return apperrors.NewNotFoundError("Object", objectAPIName)
		}
		return err
	}
	statement := fmt.Sprintf(
		"UPDATE `%s` SET `%s` = ?, `%s` = true, `%s` = CURRENT_TIMESTAMP "+
			"WHERE LOWER(`%s`) = LOWER(?) AND (`%s` = false OR `%s` IS NULL)",
		constants.TableObject,
		constants.FieldSysObject_ListFields,
		constants.FieldSysObject_ListFieldsCustomized,
		constants.FieldLastModifiedDate,
		constants.FieldAPIName,
		constants.FieldIsDeleted,
		constants.FieldIsDeleted,
	)
	_, err = executor.ExecContext(ctx, statement, encoded, objectAPIName)
	if err != nil {
		return err
	}
	return nil
}

// ReadLayoutAuthoringAggregate returns the complete stored-authoring
// projection from the caller's transaction snapshot. It intentionally does
// not reuse GetLayouts: that method owns caller-effective rendering semantics,
// including filtering legacy Layout kinds.
func (r *MetadataRepository) ReadLayoutAuthoringAggregate(ctx context.Context, objectAPIName string) (*models.LayoutAuthoringAggregate, error) {
	tx, ok := r.GetExecutor(ctx).(*sql.Tx)
	if !ok {
		return nil, apperrors.NewInternalError(
			"Layout authoring aggregate read requires an active transaction", nil,
		)
	}
	return r.readLayoutAuthoringAggregate(ctx, tx, objectAPIName, false)
}

// ReadAutoNumberFieldNames reads the current rendering exclusions from the same
// snapshot as the caller's layouts. Both legacy types and format-backed fields
// are authoritative; no process cache participates in this read.
func (r *MetadataRepository) ReadAutoNumberFieldNames(ctx context.Context, objectAPIName string) (map[string]struct{}, error) {
	tx, ok := r.GetExecutor(ctx).(*sql.Tx)
	if !ok {
		return nil, apperrors.NewInternalError("Current autonumber field read requires an active transaction", nil)
	}
	rows, err := tx.QueryContext(ctx, `SELECT f.ApiName FROM _System_Field f
 JOIN _System_Object o ON o.Id = f.ObjectId
 WHERE o.ApiName = ? AND (o.IsDeleted = 0 OR o.IsDeleted IS NULL)
 AND (f.IsDeleted = 0 OR f.IsDeleted IS NULL)
 AND (f.Type = ? OR (f.auto_number_format IS NOT NULL AND f.auto_number_format != ''))`, objectAPIName, constants.FieldTypeAutonumber)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	result := make(map[string]struct{})
	for rows.Next() {
		var name string
		if err := rows.Scan(&name); err != nil {
			return nil, err
		}
		result[strings.ToLower(name)] = struct{}{}
	}
	return result, rows.Err()
}

// LockLayoutAuthoringRecordTypes locks exact selector lifecycle rows after the
// object-scoped Layout aggregate root is held. RecordType lifecycle writers
// prelock that same object root, so this current read and deactivate/delete are
// one serialization protocol rather than competing snapshot reads.
func (r *MetadataRepository) LockLayoutAuthoringRecordTypes(
	ctx context.Context,
	recordTypeIDs []string,
) ([]*models.RecordType, error) {
	tx, ok := r.GetExecutor(ctx).(*sql.Tx)
	if !ok {
		return nil, apperrors.NewInternalError(
			"Layout authoring RecordType locks require the owning transaction", nil,
		)
	}
	ids := append([]string(nil), recordTypeIDs...)
	sort.Strings(ids)
	result := make([]*models.RecordType, 0, len(ids))
	previous := ""
	for _, id := range ids {
		if strings.TrimSpace(id) == "" || id == previous {
			continue
		}
		previous = id
		var recordType models.RecordType
		err := tx.QueryRowContext(ctx,
			fmt.Sprintf(
				"SELECT `Id`, `%s`, COALESCE(`IsActive`, 0) FROM `_System_RecordType` "+
					"WHERE `Id` = ? AND COALESCE(`IsDeleted`, 0) = 0 LIMIT 1 FOR UPDATE",
				constants.FieldSysRecordType_ObjectAPIName,
			),
			id,
		).Scan(&recordType.ID, &recordType.ObjectAPIName, &recordType.IsActive)
		if err == sql.ErrNoRows {
			continue
		}
		if err != nil {
			return nil, apperrors.NewInternalError("failed to lock Layout authoring RecordType", err)
		}
		result = append(result, &recordType)
	}
	return result, nil
}

// LockLayoutAuthoringSchemaReferences binds resolved object/field identities
// to transaction-current live rows and proves that the resolved incarnation
// has not disappeared or been replaced before commit.
func (r *MetadataRepository) LockLayoutAuthoringSchemaReferences(
	ctx context.Context,
	references []schema.IncarnationReference,
) error {
	tx, ok := r.GetExecutor(ctx).(*sql.Tx)
	if !ok {
		return apperrors.NewInternalError(
			"Layout authoring schema incarnation locks require the owning transaction", nil,
		)
	}
	ordered := append([]schema.IncarnationReference(nil), references...)
	sort.Slice(ordered, func(i, j int) bool {
		if ordered[i].ObjectID != ordered[j].ObjectID {
			return ordered[i].ObjectID < ordered[j].ObjectID
		}
		return ordered[i].FieldID < ordered[j].FieldID
	})

	lockedObjects := make(map[string]struct{}, len(ordered))
	for _, reference := range ordered {
		objectID := strings.TrimSpace(reference.ObjectID)
		objectAPIName := strings.TrimSpace(reference.ObjectAPIName)
		if objectID == "" || objectAPIName == "" {
			return apperrors.NewValidationError("layout", "Layout schema object has no exact identity")
		}
		objectKey := strings.ToLower(objectID)
		if _, alreadyLocked := lockedObjects[objectKey]; alreadyLocked {
			continue
		}
		var currentID, currentAPIName string
		var deleted bool
		err := tx.QueryRowContext(ctx,
			"SELECT `Id`, `ApiName`, COALESCE(`IsDeleted`, 0) FROM `_System_Object` "+
				"WHERE `Id` = ? LIMIT 1 FOR UPDATE",
			objectID,
		).Scan(&currentID, &currentAPIName, &deleted)
		if err == sql.ErrNoRows {
			return apperrors.NewValidationError(
				"layout", fmt.Sprintf("Layout schema object %q no longer has its resolved incarnation", objectAPIName),
			)
		}
		if err != nil {
			return apperrors.NewInternalError("failed to lock Layout schema object incarnation", err)
		}
		if deleted || currentID != objectID || currentAPIName != objectAPIName {
			return apperrors.NewValidationError(
				"layout", fmt.Sprintf("Layout schema object %q no longer has its resolved incarnation", objectAPIName),
			)
		}
		lockedObjects[objectKey] = struct{}{}
	}

	lockedFields := make(map[string]struct{}, len(ordered))
	for _, reference := range ordered {
		fieldID := strings.TrimSpace(reference.FieldID)
		fieldAPIName := strings.TrimSpace(reference.FieldAPIName)
		if fieldID == "" && fieldAPIName == "" {
			continue
		}
		if fieldID == "" || fieldAPIName == "" {
			return apperrors.NewValidationError("layout", "Layout schema field has no exact identity")
		}
		fieldKey := strings.ToLower(fieldID)
		if _, alreadyLocked := lockedFields[fieldKey]; alreadyLocked {
			continue
		}
		var currentObjectID, currentFieldID, currentAPIName string
		var deleted bool
		err := tx.QueryRowContext(ctx,
			"SELECT `ObjectId`, `Id`, `ApiName`, COALESCE(`IsDeleted`, 0) FROM `_System_Field` "+
				"WHERE `Id` = ? LIMIT 1 FOR UPDATE",
			fieldID,
		).Scan(&currentObjectID, &currentFieldID, &currentAPIName, &deleted)
		if err == sql.ErrNoRows {
			return apperrors.NewValidationError(
				"layout", fmt.Sprintf("Layout schema field %q.%s no longer has its resolved incarnation", reference.ObjectAPIName, fieldAPIName),
			)
		}
		if err != nil {
			return apperrors.NewInternalError("failed to lock Layout schema field incarnation", err)
		}
		if deleted || currentObjectID != reference.ObjectID || currentFieldID != fieldID || currentAPIName != fieldAPIName {
			return apperrors.NewValidationError(
				"layout", fmt.Sprintf("Layout schema field %q.%s no longer has its resolved incarnation", reference.ObjectAPIName, fieldAPIName),
			)
		}
		lockedFields[fieldKey] = struct{}{}
	}
	return nil
}

// LockLayoutAuthoringAggregate returns the same stored-authoring projection as
// ReadLayoutAuthoringAggregate and adds only current-read locking. The object
// row serializes every conforming writer; child rows also use FOR UPDATE so a
// transaction that waited under TiDB REPEATABLE READ cannot combine the newly
// acquired workspace lock with its transaction-start child snapshot.
func (r *MetadataRepository) LockLayoutAuthoringAggregate(ctx context.Context, objectAPIName string) (*models.LayoutAuthoringAggregate, error) {
	tx, ok := r.GetExecutor(ctx).(*sql.Tx)
	if !ok {
		return nil, apperrors.NewInternalError(
			"Layout authoring aggregate lock requires an active transaction", nil,
		)
	}
	return r.readLayoutAuthoringAggregate(ctx, tx, objectAPIName, true)
}

func (r *MetadataRepository) readLayoutAuthoringAggregate(
	ctx context.Context,
	tx *sql.Tx,
	objectAPIName string,
	lock bool,
) (*models.LayoutAuthoringAggregate, error) {
	lockClause := ""
	readKind := "Layout authoring"
	if lock {
		lockClause = " FOR UPDATE"
		readKind = "locked Layout authoring"
	}
	statement := fmt.Sprintf(
		"SELECT `%s`, `%s` FROM `%s` WHERE LOWER(`%s`) = LOWER(?) AND (`%s` = false OR `%s` IS NULL)%s",
		constants.FieldAPIName,
		constants.FieldSysObject_ListFields,
		constants.TableObject,
		constants.FieldAPIName,
		constants.FieldIsDeleted,
		constants.FieldIsDeleted,
		lockClause,
	)
	var canonicalObjectAPIName string
	var encoded sql.NullString
	if err := tx.QueryRowContext(ctx, statement, objectAPIName).Scan(&canonicalObjectAPIName, &encoded); err != nil {
		if err == sql.ErrNoRows {
			return nil, apperrors.NewNotFoundError("Object", objectAPIName)
		}
		return nil, err
	}
	canonicalObjectKey, err := catalogassignmentidentityv1.NormalizeObjectAPIName(canonicalObjectAPIName)
	if err != nil {
		return nil, apperrors.NewInternalError("stored Layout authoring object identity is invalid", err)
	}
	fields := []string{}
	if encoded.Valid && encoded.String != "" {
		if err := jsondocument.DecodeUseNumber([]byte(encoded.String), &fields); err != nil {
			return nil, apperrors.NewInternalError("stored Layout authoring ListFields is invalid", err)
		}
		if fields == nil {
			fields = []string{}
		}
	}

	quotedLayoutColumns := make([]string, len(constants.ColsSystemPageLayout))
	for index, column := range constants.ColsSystemPageLayout {
		quotedLayoutColumns[index] = "`" + column + "`"
	}
	layoutSQL := fmt.Sprintf(
		"SELECT %s FROM `%s` WHERE LOWER(`%s`) = LOWER(?) AND (`%s` = false OR `%s` IS NULL) ORDER BY `%s` ASC%s",
		strings.Join(quotedLayoutColumns, ", "), constants.TablePageLayout,
		constants.FieldObjectAPIName, constants.FieldIsDeleted, constants.FieldIsDeleted, constants.FieldID, lockClause,
	)
	layoutRows, err := tx.QueryContext(ctx, layoutSQL, canonicalObjectAPIName)
	if err != nil {
		return nil, err
	}
	layouts := []*models.PageLayout{}
	layoutIDs := map[string]string{}
	for layoutRows.Next() {
		layout, scanErr := r.scanLayout(layoutRows)
		if scanErr != nil {
			_ = layoutRows.Close()
			return nil, apperrors.NewInternalError("failed to scan "+readKind+" row", scanErr)
		}
		objectKey, identityErr := catalogassignmentidentityv1.NormalizeObjectAPIName(layout.ObjectAPIName)
		if identityErr != nil || objectKey != canonicalObjectKey {
			_ = layoutRows.Close()
			return nil, apperrors.NewInternalError(
				fmt.Sprintf("Layout %q stores non-canonical object identity %q; expected %q", layout.ID, layout.ObjectAPIName, canonicalObjectAPIName), nil,
			)
		}
		// SQL collation and Unicode folding are broader than Catalog object-key
		// identity. Only validated ASCII aliases may use _System_Object's spelling;
		// snapshot and locked reads must supply identical canonical version inputs.
		layout.ObjectAPIName = canonicalObjectAPIName
		foldedID := strings.ToLower(layout.ID)
		if previous, duplicate := layoutIDs[foldedID]; duplicate {
			_ = layoutRows.Close()
			return nil, apperrors.NewInternalError(
				fmt.Sprintf("Layout identities %q and %q collide case-insensitively", previous, layout.ID), nil,
			)
		}
		layoutIDs[foldedID] = layout.ID
		layouts = append(layouts, layout)
	}
	if err := layoutRows.Err(); err != nil {
		_ = layoutRows.Close()
		return nil, err
	}
	if err := layoutRows.Close(); err != nil {
		return nil, err
	}

	assignmentSQL := fmt.Sprintf(
		"SELECT `Id`, `ObjectApiName`, `LayoutId`, `RoleId`, `ProfileId`, `RecordTypeId`, `Specificity`, `CreatedDate`, `LastModifiedDate`, `CreatedById`, `RouteSource` "+
			"FROM `%s` WHERE LOWER(`ObjectApiName`) = LOWER(?) ORDER BY `Id` ASC%s",
		constants.TableLayoutAssignment, lockClause,
	)
	assignmentRows, err := tx.QueryContext(ctx, assignmentSQL, canonicalObjectAPIName)
	if err != nil {
		return nil, err
	}
	defer assignmentRows.Close()
	assignments := []*models.LayoutAssignment{}
	assignmentIDs := map[string]string{}
	for assignmentRows.Next() {
		assignment, scanErr := scanLayoutAssignment(assignmentRows)
		if scanErr != nil {
			return nil, apperrors.NewInternalError("failed to scan "+readKind+" assignment row", scanErr)
		}
		objectKey, identityErr := catalogassignmentidentityv1.NormalizeObjectAPIName(assignment.ObjectAPIName)
		if identityErr != nil || objectKey != canonicalObjectKey {
			return nil, apperrors.NewInternalError(
				fmt.Sprintf("Layout assignment %q stores non-canonical object identity %q; expected %q", assignment.ID, assignment.ObjectAPIName, canonicalObjectAPIName), nil,
			)
		}
		assignment.ObjectAPIName = canonicalObjectAPIName
		foldedID := strings.ToLower(assignment.ID)
		if previous, duplicate := assignmentIDs[foldedID]; duplicate {
			return nil, apperrors.NewInternalError(
				fmt.Sprintf("Layout assignment identities %q and %q collide case-insensitively", previous, assignment.ID), nil,
			)
		}
		assignmentIDs[foldedID] = assignment.ID
		for field, selector := range map[string]*string{
			"roleId": assignment.RoleID, "profileId": assignment.ProfileID, "recordTypeId": assignment.RecordTypeID,
		} {
			if selector != nil && (strings.TrimSpace(*selector) == "" || *selector != strings.TrimSpace(*selector)) {
				return nil, apperrors.NewInternalError(
					fmt.Sprintf("Layout assignment %q stores invalid %s", assignment.ID, field), nil,
				)
			}
		}
		expectedSpecificity := 0
		if assignment.RoleID != nil {
			expectedSpecificity++
		}
		if assignment.ProfileID != nil {
			expectedSpecificity++
		}
		if assignment.RecordTypeID != nil {
			expectedSpecificity++
		}
		if assignment.Specificity != expectedSpecificity {
			return nil, apperrors.NewInternalError(
				fmt.Sprintf("Layout assignment %q stores specificity %d; expected %d from its selectors", assignment.ID, assignment.Specificity, expectedSpecificity), nil,
			)
		}
		if _, exists := layoutIDs[strings.ToLower(assignment.LayoutID)]; !exists {
			return nil, apperrors.NewInternalError(
				fmt.Sprintf("Layout assignment %q references absent Layout %q", assignment.ID, assignment.LayoutID), nil,
			)
		}
		assignments = append(assignments, assignment)
	}
	if err := assignmentRows.Err(); err != nil {
		return nil, err
	}
	wildcardLayoutID := ""
	for _, assignment := range assignments {
		if !isWildcardLayoutAssignment(assignment) {
			continue
		}
		if wildcardLayoutID != "" {
			return nil, apperrors.NewInternalError(
				fmt.Sprintf("Layout authoring workspace %q stores multiple wildcard assignments", canonicalObjectAPIName), nil,
			)
		}
		wildcardLayoutID = assignment.LayoutID
	}
	for _, layout := range layouts {
		projectEffectiveDefault(layout, wildcardLayoutID)
	}
	return &models.LayoutAuthoringAggregate{
		ObjectAPIName: canonicalObjectAPIName, OrderedListFields: fields,
		Layouts: layouts, Assignments: assignments,
	}, nil
}

// --- Layout History ---

// WriteLayoutHistory appends a version snapshot atomically using MAX()+1 subquery.
// This avoids the TOCTOU race of reading max version then inserting version+1.
func (r *MetadataRepository) WriteLayoutHistory(ctx context.Context, entry *models.PageLayoutHistoryEntry) error {
	if entry.ID == "" {
		entry.ID = utils.GenerateID()
	}

	sqlStr := fmt.Sprintf(
		"INSERT INTO `%s` (`Id`, `LayoutId`, `VersionNumber`, `Config`, `ChangedById`, `ChangeReason`, `CreatedDate`) "+
			"SELECT ?, ?, COALESCE(MAX(`VersionNumber`), 0) + 1, ?, ?, ?, CURRENT_TIMESTAMP "+
			"FROM `%s` WHERE `LayoutId` = ?",
		constants.TablePageLayoutHistory, constants.TablePageLayoutHistory,
	)
	_, err := r.GetExecutor(ctx).ExecContext(ctx, sqlStr, entry.ID, entry.LayoutID, entry.Config, entry.ChangedByID, entry.ChangeReason, entry.LayoutID)
	return err
}

// GetLayoutHistoryMaxVersion returns the max version number for a layout's history (0 if none).
func (r *MetadataRepository) GetLayoutHistoryMaxVersion(ctx context.Context, layoutID string) (int, error) {
	var maxVersion sql.NullInt64
	sqlStr := fmt.Sprintf("SELECT MAX(`VersionNumber`) FROM `%s` WHERE `LayoutId` = ?", constants.TablePageLayoutHistory)
	err := r.GetExecutor(ctx).QueryRowContext(ctx, sqlStr, layoutID).Scan(&maxVersion)
	if err != nil {
		return 0, err
	}
	if maxVersion.Valid {
		return int(maxVersion.Int64), nil
	}
	return 0, nil
}

// PruneLayoutHistory deletes history entries with VersionNumber <= keepAboveVersion.
func (r *MetadataRepository) PruneLayoutHistory(ctx context.Context, layoutID string, keepAboveVersion int) error {
	sqlStr := fmt.Sprintf("DELETE FROM `%s` WHERE `LayoutId` = ? AND `VersionNumber` <= ?", constants.TablePageLayoutHistory)
	_, err := r.GetExecutor(ctx).ExecContext(ctx, sqlStr, layoutID, keepAboveVersion)
	return err
}

// GetLayoutHistoryConfig returns the Config JSON snapshot for a specific history version.
func (r *MetadataRepository) GetLayoutHistoryConfig(ctx context.Context, layoutID string, versionNumber int) (string, error) {
	var configJSON string
	sqlStr := fmt.Sprintf("SELECT `Config` FROM `%s` WHERE `LayoutId` = ? AND `VersionNumber` = ?", constants.TablePageLayoutHistory)
	err := r.GetExecutor(ctx).QueryRowContext(ctx, sqlStr, layoutID, versionNumber).Scan(&configJSON)
	if err != nil {
		return "", err
	}
	return configJSON, nil
}

// LockLayoutHistoryConfig returns the immutable restore source through a
// current locking read. This is required when the surrounding authoring
// transaction may already have established an older REPEATABLE READ snapshot.
func (r *MetadataRepository) LockLayoutHistoryConfig(ctx context.Context, layoutID string, versionNumber int) (string, error) {
	tx, ok := r.GetExecutor(ctx).(*sql.Tx)
	if !ok {
		return "", apperrors.NewInternalError("Layout history lock requires an active transaction", nil)
	}
	var configJSON string
	sqlStr := fmt.Sprintf("SELECT `Config` FROM `%s` WHERE `LayoutId` = ? AND `VersionNumber` = ? FOR UPDATE", constants.TablePageLayoutHistory)
	err := tx.QueryRowContext(ctx, sqlStr, layoutID, versionNumber).Scan(&configJSON)
	return configJSON, err
}

// --- Migration ---

// GetMigrationVersion returns the version for a migration ID (0 if not found).
func (r *MetadataRepository) GetMigrationVersion(ctx context.Context, migrationID string) (int, error) {
	var version int
	sqlStr := fmt.Sprintf("SELECT `Version` FROM `%s` WHERE `Id` = ?", constants.TableMigration)
	err := r.db.QueryRowContext(ctx, sqlStr, migrationID).Scan(&version)
	if err == sql.ErrNoRows {
		return 0, nil
	}
	if err != nil {
		return 0, err
	}
	return version, nil
}

// SetMigrationVersion records a migration version (upsert).
func (r *MetadataRepository) SetMigrationVersion(ctx context.Context, migrationID string, version int, description string) error {
	sqlStr := fmt.Sprintf(
		"INSERT INTO `%s` (`Id`, `Version`, `AppliedAt`, `Description`) VALUES (?, ?, CURRENT_TIMESTAMP, ?) "+
			"ON DUPLICATE KEY UPDATE `Version` = ?, `AppliedAt` = CURRENT_TIMESTAMP, `Description` = ?",
		constants.TableMigration,
	)
	_, err := r.db.ExecContext(ctx, sqlStr, migrationID, version, description, version, description)
	return err
}
