package packs

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

	"github.com/pingcap-inc/nexuscrm-controlplane/layoutownership"
	"github.com/nexuscrm/shared/pkg/models"
)

// observedCuratedLayoutState is the irreducible DB state for one
// (curated_layout_id, object_api_name) pair that drives both apply and
// verify. Observed once via observeCuratedLayoutState and never re-queried
// during a single classification.
type observedCuratedLayoutState struct {
	curatedID          string
	objectAPIName      string
	liveLayoutCount    int
	assignmentCount    int
	adminWildcardCount int

	rowExists     bool
	rowAdminOwned bool

	wildcardTarget *layoutownership.WildcardLayoutTarget

	adminDefaultCount     int
	adminDefaultID        string
	adminDefaultIsCurated bool
}

func observeCuratedLayoutState(
	ctx context.Context,
	db *sql.DB,
	expected models.PageLayout,
	packName string,
) (*observedCuratedLayoutState, error) {
	obs := &observedCuratedLayoutState{
		curatedID:     expected.ID,
		objectAPIName: expected.ObjectAPIName,
	}

	live, err := layoutownership.CountLivePageLayoutsForObject(ctx, db, expected.ObjectAPIName)
	if err != nil {
		return nil, fmt.Errorf("count live layouts: %w", err)
	}
	obs.liveLayoutCount = live

	assignments, err := layoutownership.CountWildcardLayoutAssignments(ctx, db, expected.ObjectAPIName)
	if err != nil {
		return nil, fmt.Errorf("count wildcard assignments: %w", err)
	}
	obs.assignmentCount = assignments

	adminWildcards, err := layoutownership.CountAdminOwnedWildcardLayoutAssignmentsForObject(ctx, db, expected.ObjectAPIName)
	if err != nil {
		return nil, fmt.Errorf("count admin-owned wildcard assignments: %w", err)
	}
	obs.adminWildcardCount = adminWildcards

	target, err := layoutownership.LoadWildcardLayoutTarget(ctx, db, expected.ObjectAPIName)
	if err != nil {
		return nil, fmt.Errorf("load wildcard target: %w", err)
	}
	obs.wildcardTarget = target

	row, resolvedID, err := resolveManagedCuratedLayoutRow(ctx, db, expected, packName)
	if err != nil {
		return nil, fmt.Errorf("load curated row: %w", err)
	}
	obs.curatedID = resolvedID
	obs.rowExists = row != nil
	if row != nil {
		obs.rowAdminOwned = layoutownership.IsAdminOwnedPageLayout(row.IsCustomized, row.Source)
	}

	adminDefault, err := layoutownership.LoadAdminOwnedDefaultPageLayoutRowForObject(ctx, db, expected.ObjectAPIName)
	if err != nil {
		return nil, fmt.Errorf("load admin default row: %w", err)
	}
	adminCount, err := layoutownership.CountAdminOwnedDefaultPageLayoutRowsForObject(ctx, db, expected.ObjectAPIName)
	if err != nil {
		return nil, fmt.Errorf("count admin default rows: %w", err)
	}
	obs.adminDefaultCount = adminCount
	if adminDefault != nil {
		obs.adminDefaultID = adminDefault.ID
		obs.adminDefaultIsCurated = adminDefault.ID == obs.curatedID
	}

	return obs, nil
}

// curatedLayoutCell is the closed taxonomy of states classifyCuratedLayout
// can return. Each cell pins both the apply action and the verify result;
// apply and verify share the cell so they cannot disagree.
//
// NEVER add a cell here without also adding it to stateMatrix and to the
// parity test. If observeCuratedLayoutState ever produces a state that
// classifyCuratedLayout cannot map to a cell, classify returns
// cellUnmodeledFailClosed which both apply and verify treat as a hard error.
type curatedLayoutCell int

const (
	// OK cells — verify returns nil. Apply may still refresh pack-managed
	// curated row content because config-drift is a separate dimension from
	// routing state. Each base cell has an *AdminOwned variant when the
	// curated row is admin-owned (L2 authority); the variant routes to a
	// row-preserving apply op instead of carrying a guard inside the action.
	// The two *AdminOwned-at-curated variants additively merge pack-owned
	// recordPanels into the admin row (see applyMergePackRecordPanelsPreservingRow)
	// so pack panel additions still reach customized layouts; all other
	// admin-owned variants are route-only and never touch row content.
	cellOKManagedSeedAtCurated curatedLayoutCell = iota + 1
	cellOKManagedSeedAtCuratedAdminOwned
	cellOKAdminAuthoredAtCurated
	cellOKAdminAuthoredAtCuratedAdminOwned
	cellOKAdminAuthoredAtAdminOther
	cellOKAdminAuthoredAtAdminOtherAdminOwned

	// Missing-class repairable cells — apply must upsert curated row
	// and/or adopt wildcard. Verify returns curatedPageLayoutMissingError.
	// These cells require !obs.rowExists, so no AdminOwned variant exists.
	cellMissingGreenfield               // no live layouts, no wildcard, no admin default, no curated row
	cellMissingCuratedWithSiblings      // curated row absent but other live layouts exist (no wildcard, no admin default)
	cellMissingOrphanWildcardGreenfield // orphan wildcard rows, no live layouts, no admin default, no curated row
	cellMissingCuratedUnderAdminRoute   // admin route points at admin-other; curated row absent

	// Routing-class repairable cells — apply must take over or canonicalize
	// the wildcard route. Verify returns curatedPageLayoutRoutingError.
	// *AdminOwned variants protect the curated row from being overwritten.
	cellRoutingAdoptManagedSeed                 // curated row exists pack-managed, no wildcard → seed managed_seed at curated
	cellRoutingAdoptManagedSeedAdminOwned       // curated row admin-owned, no wildcard → seed managed_seed pointing at admin-owned curated row
	cellRoutingTakeoverFromNonAdmin             // wildcard points elsewhere with non-admin route and non-admin target → take over to managed_seed at curated (pack-managed curated row)
	cellRoutingTakeoverFromNonAdminAdminOwned   // same as above but curated row admin-owned → adopt wildcard at curated, preserve row
	cellRoutingCanonicalizeAdminRoute           // wildcard points at admin-owned other layout with non-admin route → repair route to admin_authored (pack-managed curated row)
	cellRoutingCanonicalizeAdminRouteAdminOwned // same as above but curated row admin-owned → canonicalize route at target, preserve curated row
	cellRoutingRepairOpaqueSource               // wildcard points at curated row but RouteSource is opaque → set to managed_seed (pack-managed curated row)
	cellRoutingRepairOpaqueSourceAdminOwned     // same as above but curated row admin-owned → canonicalize route at curated to admin_authored, preserve row

	// Conflict-class non-repairable cells — verify returns
	// curatedPageLayoutRoutingConflictError, apply also returns the same.
	// Operator must intervene via data-spec resolution.
	cellConflictMultipleWildcards           // assignmentCount > 1
	cellConflictMultipleAdminDefaults       // adminDefaultCount > 1 with no wildcard target
	cellConflictLegacyAdminDefault          // single admin default elsewhere with no wildcard target
	cellConflictOrphanWildcardsWithRows     // orphan wildcards exist with live rows but not the safe greenfield-recover shape
	cellConflictAdminWildcardsWithoutTarget // admin-owned wildcard rows present but no live target

	// Hard failure: observed state does not match any modeled cell.
	cellUnmodeledFailClosed
)

func (c curatedLayoutCell) name() string {
	switch c {
	case cellOKManagedSeedAtCurated:
		return "OK_ManagedSeedAtCurated"
	case cellOKManagedSeedAtCuratedAdminOwned:
		return "OK_ManagedSeedAtCuratedAdminOwned"
	case cellOKAdminAuthoredAtCurated:
		return "OK_AdminAuthoredAtCurated"
	case cellOKAdminAuthoredAtCuratedAdminOwned:
		return "OK_AdminAuthoredAtCuratedAdminOwned"
	case cellOKAdminAuthoredAtAdminOther:
		return "OK_AdminAuthoredAtAdminOther"
	case cellOKAdminAuthoredAtAdminOtherAdminOwned:
		return "OK_AdminAuthoredAtAdminOtherAdminOwned"
	case cellMissingGreenfield:
		return "Missing_Greenfield"
	case cellMissingCuratedWithSiblings:
		return "Missing_CuratedWithSiblings"
	case cellMissingOrphanWildcardGreenfield:
		return "Missing_OrphanWildcardGreenfield"
	case cellMissingCuratedUnderAdminRoute:
		return "Missing_CuratedUnderAdminRoute"
	case cellRoutingAdoptManagedSeed:
		return "Routing_AdoptManagedSeed"
	case cellRoutingAdoptManagedSeedAdminOwned:
		return "Routing_AdoptManagedSeedAdminOwned"
	case cellRoutingTakeoverFromNonAdmin:
		return "Routing_TakeoverFromNonAdmin"
	case cellRoutingTakeoverFromNonAdminAdminOwned:
		return "Routing_TakeoverFromNonAdminAdminOwned"
	case cellRoutingCanonicalizeAdminRoute:
		return "Routing_CanonicalizeAdminRoute"
	case cellRoutingCanonicalizeAdminRouteAdminOwned:
		return "Routing_CanonicalizeAdminRouteAdminOwned"
	case cellRoutingRepairOpaqueSource:
		return "Routing_RepairOpaqueSource"
	case cellRoutingRepairOpaqueSourceAdminOwned:
		return "Routing_RepairOpaqueSourceAdminOwned"
	case cellConflictMultipleWildcards:
		return "Conflict_MultipleWildcards"
	case cellConflictMultipleAdminDefaults:
		return "Conflict_MultipleAdminDefaults"
	case cellConflictLegacyAdminDefault:
		return "Conflict_LegacyAdminDefault"
	case cellConflictOrphanWildcardsWithRows:
		return "Conflict_OrphanWildcardsWithRows"
	case cellConflictAdminWildcardsWithoutTarget:
		return "Conflict_AdminWildcardsWithoutTarget"
	case cellUnmodeledFailClosed:
		return "Unmodeled_FailClosed"
	}
	return fmt.Sprintf("cell(%d)", int(c))
}

func classifyCuratedLayout(obs *observedCuratedLayoutState) curatedLayoutCell {
	// Conflict_MultipleWildcards: any number > 1 of wildcard rows is
	// ambiguous regardless of other state.
	if obs.assignmentCount > 1 {
		return cellConflictMultipleWildcards
	}

	target := obs.wildcardTarget

	// Branch 1: a single live wildcard target exists. Classification is
	// driven by (target identity, route source, target ownership, row
	// ownership).
	if target != nil && obs.assignmentCount == 1 {
		routeSource := strings.TrimSpace(strings.ToLower(deref(target.RouteSource)))
		routeIsManagedSeed := routeSource == strings.ToLower(models.LayoutAssignmentRouteSourceManagedSeed)
		routeIsAdminAuthored := routeSource == strings.ToLower(models.LayoutAssignmentRouteSourceAdminAuthored)

		if target.LayoutID == obs.curatedID {
			// Wildcard points at the curated layout row, so target IS curated
			// row and target ownership = row ownership. Route source is the
			// separate dimension: admin may have taken over the row content
			// (Source=admin_created / IsCustomized=1) even though the
			// original managed_seed route still stands.
			if !obs.rowExists {
				// LoadLiveWildcardTarget JOINs against PageLayout so this
				// branch should be unreachable; classify defensively if
				// observed state diverges from invariants.
				return cellMissingCuratedUnderAdminRoute
			}
			switch {
			case routeIsManagedSeed:
				if obs.rowAdminOwned {
					return cellOKManagedSeedAtCuratedAdminOwned
				}
				return cellOKManagedSeedAtCurated
			case routeIsAdminAuthored:
				if obs.rowAdminOwned {
					return cellOKAdminAuthoredAtCuratedAdminOwned
				}
				return cellOKAdminAuthoredAtCurated
			default:
				if obs.rowAdminOwned {
					return cellRoutingRepairOpaqueSourceAdminOwned
				}
				return cellRoutingRepairOpaqueSource
			}
		}

		// target.LayoutID != curatedID — wildcard points elsewhere. Target
		// ownership and curated-row ownership describe different rows and
		// must both enter cell classification so apply ops stay row-aware
		// without internal guards.
		targetAdminOwned := layoutownership.IsAdminOwnedPageLayout(target.IsCustomized, target.Source)
		switch {
		case routeIsAdminAuthored:
			// Legit admin choice: RouteSource is the assignment ownership
			// marker. The target layout row can still be pack-managed; setting a
			// default route is routing intent, not content ownership.
			// Curated row must still exist (pack-managed fallback) — if not,
			// classify as Missing under the admin route.
			if !obs.rowExists {
				return cellMissingCuratedUnderAdminRoute
			}
			if obs.rowAdminOwned {
				return cellOKAdminAuthoredAtAdminOtherAdminOwned
			}
			return cellOKAdminAuthoredAtAdminOther
		case !routeIsAdminAuthored && targetAdminOwned:
			// Admin layout exposed via non-admin route. Canonicalize the
			// route source to admin_authored, preserving the admin choice.
			if obs.rowAdminOwned {
				return cellRoutingCanonicalizeAdminRouteAdminOwned
			}
			return cellRoutingCanonicalizeAdminRoute
		default:
			// Non-admin route at non-admin layout: pure takeover.
			if obs.rowAdminOwned {
				return cellRoutingTakeoverFromNonAdminAdminOwned
			}
			return cellRoutingTakeoverFromNonAdmin
		}
	}

	// Branch 2: assignmentCount > 0 but no live target (orphaned wildcard
	// assignments — target layout was soft-deleted or never existed).
	if obs.assignmentCount > 0 {
		if obs.adminWildcardCount > 0 {
			return cellConflictAdminWildcardsWithoutTarget
		}
		// Safe recovery shape: nothing else in the way, can recreate.
		greenfieldRecover := obs.liveLayoutCount == 0 && obs.adminDefaultCount == 0 && !obs.rowExists
		if greenfieldRecover {
			return cellMissingOrphanWildcardGreenfield
		}
		return cellConflictOrphanWildcardsWithRows
	}

	// Branch 3: no wildcard assignments at all. Classification is driven by
	// admin-default rows and the curated row's presence.
	if obs.adminWildcardCount > 0 {
		// Wildcard count == 0 but admin-owned wildcard count > 0 should be
		// impossible (admin wildcards are a subset). Treat as conflict —
		// schema invariant violated.
		return cellConflictAdminWildcardsWithoutTarget
	}

	if obs.adminDefaultCount > 1 {
		return cellConflictMultipleAdminDefaults
	}

	if obs.adminDefaultCount == 1 && !obs.adminDefaultIsCurated {
		// Single admin-owned default row that is NOT the curated layout —
		// operator must decide whether to demote the legacy row or keep it.
		return cellConflictLegacyAdminDefault
	}

	// adminDefaultCount is 0, or it's 1 and points at the curated row.
	switch {
	case !obs.rowExists && obs.liveLayoutCount == 0:
		return cellMissingGreenfield
	case !obs.rowExists && obs.liveLayoutCount > 0:
		return cellMissingCuratedWithSiblings
	case obs.rowExists && obs.rowAdminOwned:
		// Curated row exists and is admin-owned; we need to adopt a
		// managed_seed wildcard pointing at it without overwriting the row.
		return cellRoutingAdoptManagedSeedAdminOwned
	case obs.rowExists && !obs.rowAdminOwned:
		return cellRoutingAdoptManagedSeed
	}

	return cellUnmodeledFailClosed
}

// verifyResult is the shape of error the verifier should emit for a cell.
type verifyResult int

const (
	vrOK verifyResult = iota
	vrMissing
	vrRoutingDrift
	vrConflict
	vrUnmodeled
)

// applyAction is the shape of write the applier should perform for a cell.
// `verifyAfterApply` documents the expected post-apply classification so
// the parity test can prove convergence.
type applyAction struct {
	op               applyOp
	verifyAfterApply curatedLayoutCell
}

type applyOp int

const (
	// No-op: state already matches the OK contract.
	applyNoop applyOp = iota
	// Emit conflict error: operator must intervene.
	applyConflict
	// Emit unmodeled error: classify failed to map state to a cell.
	applyUnmodeled
	// Refresh pack-managed curated row content while preserving existing
	// wildcard routing at the curated row.
	applyRefreshManagedCuratedAtCurated
	// Greenfield seed: insert curated row (managed) + insert wildcard
	// (managed_seed) + flag curated as default.
	applySeedManagedFromGreenfield
	// Upsert curated row (managed) + adopt wildcard (managed_seed) at
	// curated. Replaces any orphan/non-admin wildcard rows.
	applyUpsertCuratedAdoptManagedSeed
	// Adopt wildcard (managed_seed) at the curated row WITHOUT upserting
	// the row (used when row is admin-owned and must be preserved).
	applyAdoptManagedSeedPreservingRow
	// Upsert curated row (managed) + adopt wildcard (admin_authored) at
	// the existing wildcard target (which is admin-owned).
	applyUpsertCuratedCanonicalizeAdminRouteAtTarget
	// Upsert curated row (managed) + clear the curated row's default flag
	// (admin route legitimately points elsewhere).
	applyUpsertCuratedClearDefaultFlag
	// Adopt wildcard (admin_authored) at the existing wildcard target
	// WITHOUT upserting the curated row. Used when the curated row is
	// admin-owned and routing must be canonicalized to admin_authored
	// without overwriting L2 row content.
	applyCanonicalizeAdminRouteAtTargetPreservingRow
	// Clear the curated row's default flag WITHOUT upserting the row.
	// Used when admin route legitimately points elsewhere and the curated
	// row is admin-owned (L2 row content must be preserved).
	applyClearCuratedDefaultFlagOnly
	// Additively merge pack-authored recordPanels (keyed by panel Id) into an
	// admin-owned curated row's Config WITHOUT overwriting admin-authored
	// panels/sections or altering the row's ownership tuple. Delivers pack
	// record-panel additions (e.g. crm.recordActivityFeed) onto layouts a
	// tenant admin has customized — which the admin-owned OK cells previously
	// left frozen under applyNoop. Purely additive and idempotent: a no-op
	// once every pack panel is already present, so it converges to the same
	// OK cell (config-drift is a separate dimension from routing state, exactly
	// as applyRefreshManagedCuratedAtCurated already refreshes managed rows).
	applyMergePackRecordPanelsPreservingRow
	// Additively merge pack-authored recordPanels into an admin-owned curated
	// row AND clear its IsDefault flag, both preserving the row. Used when the
	// admin has routed the default to a different layout (AdminOther) but the
	// curated row remains the pack-managed fallback: the pack still keeps its
	// recordPanels current on that row (mirroring applyUpsertCuratedClearDefaultFlag
	// for managed rows), while the cleared default flag stops it competing with
	// the admin's chosen target. Additive + idempotent, converges to the same cell.
	applyMergePackRecordPanelsAndClearDefaultPreservingRow
)

// stateMatrix is the single source of truth that pins apply action and
// verify result for every cell. apply and verify both consult this table
// — by construction they cannot disagree.
//
// To add a cell: add the cellXxx constant, give it a name(), classify it
// in classifyCuratedLayout, then add a row here. The parity test will
// fail until every cell has a stateMatrix entry.
var stateMatrix = map[curatedLayoutCell]struct {
	verify verifyResult
	apply  applyAction
}{
	cellOKManagedSeedAtCurated: {
		verify: vrOK,
		apply:  applyAction{op: applyRefreshManagedCuratedAtCurated, verifyAfterApply: cellOKManagedSeedAtCurated},
	},
	cellOKManagedSeedAtCuratedAdminOwned: {
		verify: vrOK,
		apply:  applyAction{op: applyMergePackRecordPanelsPreservingRow, verifyAfterApply: cellOKManagedSeedAtCuratedAdminOwned},
	},
	cellOKAdminAuthoredAtCurated: {
		verify: vrOK,
		apply:  applyAction{op: applyRefreshManagedCuratedAtCurated, verifyAfterApply: cellOKAdminAuthoredAtCurated},
	},
	cellOKAdminAuthoredAtCuratedAdminOwned: {
		verify: vrOK,
		apply:  applyAction{op: applyMergePackRecordPanelsPreservingRow, verifyAfterApply: cellOKAdminAuthoredAtCuratedAdminOwned},
	},
	cellOKAdminAuthoredAtAdminOther: {
		verify: vrOK,
		apply:  applyAction{op: applyUpsertCuratedClearDefaultFlag, verifyAfterApply: cellOKAdminAuthoredAtAdminOther},
	},
	cellOKAdminAuthoredAtAdminOtherAdminOwned: {
		verify: vrOK,
		apply:  applyAction{op: applyMergePackRecordPanelsAndClearDefaultPreservingRow, verifyAfterApply: cellOKAdminAuthoredAtAdminOtherAdminOwned},
	},

	cellMissingGreenfield: {
		verify: vrMissing,
		apply:  applyAction{op: applySeedManagedFromGreenfield, verifyAfterApply: cellOKManagedSeedAtCurated},
	},
	cellMissingCuratedWithSiblings: {
		verify: vrMissing,
		apply:  applyAction{op: applyUpsertCuratedAdoptManagedSeed, verifyAfterApply: cellOKManagedSeedAtCurated},
	},
	cellMissingOrphanWildcardGreenfield: {
		verify: vrMissing,
		apply:  applyAction{op: applyUpsertCuratedAdoptManagedSeed, verifyAfterApply: cellOKManagedSeedAtCurated},
	},
	cellMissingCuratedUnderAdminRoute: {
		verify: vrMissing,
		apply:  applyAction{op: applyUpsertCuratedClearDefaultFlag, verifyAfterApply: cellOKAdminAuthoredAtAdminOther},
	},

	cellRoutingAdoptManagedSeed: {
		verify: vrRoutingDrift,
		apply:  applyAction{op: applyUpsertCuratedAdoptManagedSeed, verifyAfterApply: cellOKManagedSeedAtCurated},
	},
	cellRoutingAdoptManagedSeedAdminOwned: {
		verify: vrRoutingDrift,
		apply:  applyAction{op: applyAdoptManagedSeedPreservingRow, verifyAfterApply: cellOKManagedSeedAtCurated},
	},
	cellRoutingTakeoverFromNonAdmin: {
		verify: vrRoutingDrift,
		apply:  applyAction{op: applyUpsertCuratedAdoptManagedSeed, verifyAfterApply: cellOKManagedSeedAtCurated},
	},
	cellRoutingTakeoverFromNonAdminAdminOwned: {
		verify: vrRoutingDrift,
		apply:  applyAction{op: applyAdoptManagedSeedPreservingRow, verifyAfterApply: cellOKManagedSeedAtCuratedAdminOwned},
	},
	cellRoutingCanonicalizeAdminRoute: {
		verify: vrRoutingDrift,
		apply:  applyAction{op: applyUpsertCuratedCanonicalizeAdminRouteAtTarget, verifyAfterApply: cellOKAdminAuthoredAtAdminOther},
	},
	cellRoutingCanonicalizeAdminRouteAdminOwned: {
		verify: vrRoutingDrift,
		apply:  applyAction{op: applyCanonicalizeAdminRouteAtTargetPreservingRow, verifyAfterApply: cellOKAdminAuthoredAtAdminOtherAdminOwned},
	},
	cellRoutingRepairOpaqueSource: {
		verify: vrRoutingDrift,
		apply:  applyAction{op: applyUpsertCuratedAdoptManagedSeed, verifyAfterApply: cellOKManagedSeedAtCurated},
	},
	cellRoutingRepairOpaqueSourceAdminOwned: {
		verify: vrRoutingDrift,
		apply:  applyAction{op: applyCanonicalizeAdminRouteAtTargetPreservingRow, verifyAfterApply: cellOKAdminAuthoredAtCuratedAdminOwned},
	},

	cellConflictMultipleWildcards: {
		verify: vrConflict,
		apply:  applyAction{op: applyConflict, verifyAfterApply: cellConflictMultipleWildcards},
	},
	cellConflictMultipleAdminDefaults: {
		verify: vrConflict,
		apply:  applyAction{op: applyConflict, verifyAfterApply: cellConflictMultipleAdminDefaults},
	},
	cellConflictLegacyAdminDefault: {
		verify: vrConflict,
		apply:  applyAction{op: applyConflict, verifyAfterApply: cellConflictLegacyAdminDefault},
	},
	cellConflictOrphanWildcardsWithRows: {
		verify: vrConflict,
		apply:  applyAction{op: applyConflict, verifyAfterApply: cellConflictOrphanWildcardsWithRows},
	},
	cellConflictAdminWildcardsWithoutTarget: {
		verify: vrConflict,
		apply:  applyAction{op: applyConflict, verifyAfterApply: cellConflictAdminWildcardsWithoutTarget},
	},

	cellUnmodeledFailClosed: {
		verify: vrUnmodeled,
		apply:  applyAction{op: applyUnmodeled, verifyAfterApply: cellUnmodeledFailClosed},
	},
}

// conflictDetail produces a stable operator-facing detail string for a
// conflict cell, sourced from the observed state.
func conflictDetail(cell curatedLayoutCell, obs *observedCuratedLayoutState) string {
	switch cell {
	case cellConflictMultipleWildcards:
		return fmt.Sprintf("has %d wildcard routing rows", obs.assignmentCount)
	case cellConflictMultipleAdminDefaults:
		return fmt.Sprintf("has %d admin-owned default layouts without wildcard routing", obs.adminDefaultCount)
	case cellConflictLegacyAdminDefault:
		return fmt.Sprintf("has legacy admin-owned default row %s without persisted wildcard routing", obs.adminDefaultID)
	case cellConflictOrphanWildcardsWithRows:
		return "has orphaned or unresolved wildcard routing rows without a live target"
	case cellConflictAdminWildcardsWithoutTarget:
		return fmt.Sprintf("has %d admin-owned wildcard routing rows without a live target", obs.adminWildcardCount)
	}
	return ""
}

// routingDetail produces a stable operator-facing detail string for a
// routing-drift cell, sourced from the observed state.
func routingDetail(cell curatedLayoutCell, obs *observedCuratedLayoutState) string {
	switch cell {
	case cellRoutingAdoptManagedSeed, cellRoutingAdoptManagedSeedAdminOwned:
		return "is missing persisted wildcard routing"
	case cellRoutingTakeoverFromNonAdmin, cellRoutingTakeoverFromNonAdminAdminOwned:
		if obs.wildcardTarget != nil {
			return fmt.Sprintf("points to non-admin layout %q instead of curated default", obs.wildcardTarget.LayoutID)
		}
		return "points away from curated default"
	case cellRoutingCanonicalizeAdminRoute, cellRoutingCanonicalizeAdminRouteAdminOwned:
		if obs.wildcardTarget != nil {
			return fmt.Sprintf("admin-owned layout %q is routed by non-admin wildcard authority", obs.wildcardTarget.LayoutID)
		}
		return "admin-owned layout routed by non-admin wildcard authority"
	case cellRoutingRepairOpaqueSource, cellRoutingRepairOpaqueSourceAdminOwned:
		return "has unresolved wildcard RouteSource classifier"
	}
	return ""
}

func deref(p *string) string {
	if p == nil {
		return ""
	}
	return *p
}
