package models

import "github.com/nexuscrm/shared/pkg/constants"

// FieldRelationshipSemanticsLifecycleStage distinguishes authored-write from
// persisted-read resolution. Authored-write paths normalize lookup aliases to
// reference and infer polymorphic ownership for multi-target fields.
// Persisted-read paths preserve the legacy lookup exception so old rows do not
// silently gain discriminator ownership unless backfilled or explicitly marked.
type FieldRelationshipSemanticsLifecycleStage string

const (
	// LifecycleAuthoredWrite is used by metadata CRUD and pack/tenant-overlay
	// authoring before persistence or DDL. The resolver normalizes lookup to
	// reference and infers discriminator ownership for authored multi-target
	// relationships.
	LifecycleAuthoredWrite FieldRelationshipSemanticsLifecycleStage = "authored_write"

	// LifecyclePersistedRead is used for rows or column definitions already
	// loaded from _System_Field/table definitions. The resolver preserves the
	// legacy lookup exception so old rows do not silently gain discriminator
	// ownership unless backfilled or explicitly marked.
	LifecyclePersistedRead FieldRelationshipSemanticsLifecycleStage = "persisted_read"
)

// FieldRelationshipSemantics is the single resolved tuple for any field
// declaration. All relationship eligibility, kind, target, master-detail, and
// polymorphic ownership decisions flow through this type.
type FieldRelationshipSemantics struct {
	// CanonicalType is the normalized field type. Raw lookup aliases normalize
	// to reference; other normalized field types pass through.
	CanonicalType constants.SchemaFieldType

	// IsRelationship is true for reference, masterdetail, or explicit
	// IsMasterDetail fields.
	IsRelationship bool

	// RelationshipKind is reference, masterdetail, or empty.
	RelationshipKind constants.SchemaFieldType

	// ReferenceTargets copied from metadata.ReferenceTo.
	ReferenceTargets []string

	// WritableReferenceTargets copied from metadata.WritableReferenceTo.
	WritableReferenceTargets []string

	// IsPolymorphic is true when the field owns a server-managed discriminator.
	// Explicit IsPolymorphic flag wins; multi-target authored references infer
	// ownership; persisted legacy lookup rows do not infer ownership without
	// explicit IsPolymorphic.
	IsPolymorphic bool

	// IsMasterDetail is true for masterdetail canonical type or explicit
	// IsMasterDetail.
	IsMasterDetail bool
}

// ResolveFieldRelationshipSemantics is the single resolver for field
// relationship semantics. Thin adapters may delegate to it, but relationship
// eligibility, relationship kind, target, master-detail, and polymorphic
// ownership decisions must not be independently derived.
func ResolveFieldRelationshipSemantics(field *FieldMetadata, lifecycleStage FieldRelationshipSemanticsLifecycleStage) FieldRelationshipSemantics {
	if field == nil {
		return FieldRelationshipSemantics{}
	}

	// Normalize the raw type. Lookup is an authoring alias only; durable new
	// writes must stamp CanonicalType=reference before SQL type resolution,
	// LogicalType stamping, and metadata persistence.
	rawType := constants.NormalizeFieldType(string(field.Type))
	canonicalType := rawType
	if canonicalType == "lookup" {
		canonicalType = constants.FieldTypeReference
	}

	// Copy reference targets.
	var referenceTargets []string
	if len(field.ReferenceTo) > 0 {
		referenceTargets = append([]string(nil), field.ReferenceTo...)
	}
	var writableReferenceTargets []string
	if len(field.WritableReferenceTo) > 0 {
		writableReferenceTargets = append([]string(nil), field.WritableReferenceTo...)
	}

	// Determine relationship eligibility and kind.
	isRelationship := canonicalType == constants.FieldTypeReference ||
		canonicalType == constants.FieldTypeMasterDetail ||
		field.IsMasterDetail
	relationshipKind := canonicalType
	if relationshipKind != constants.FieldTypeReference && relationshipKind != constants.FieldTypeMasterDetail {
		relationshipKind = ""
	}
	isMasterDetail := canonicalType == constants.FieldTypeMasterDetail || field.IsMasterDetail

	// Determine polymorphic ownership. Explicit IsPolymorphic flag is
	// authoritative for transitional single-target polymorphic references.
	isPolymorphic := field.IsPolymorphic
	if !isPolymorphic && len(referenceTargets) > 1 {
		// Multi-target inference. Authored-write paths infer ownership for
		// both lookup and reference aliases. Persisted-read paths infer
		// ownership only for Type=reference rows; legacy Type=lookup rows
		// do not infer ownership unless the bootstrap backfill has already
		// stamped IsPolymorphic=true.
		switch lifecycleStage {
		case LifecycleAuthoredWrite:
			if canonicalType == constants.FieldTypeReference {
				isPolymorphic = true
			}
		case LifecyclePersistedRead:
			if rawType == constants.FieldTypeReference {
				isPolymorphic = true
			}
		}
	}

	return FieldRelationshipSemantics{
		CanonicalType:            canonicalType,
		IsRelationship:           isRelationship,
		RelationshipKind:         relationshipKind,
		ReferenceTargets:         referenceTargets,
		WritableReferenceTargets: writableReferenceTargets,
		IsPolymorphic:            isPolymorphic,
		IsMasterDetail:           isMasterDetail,
	}
}

// IsRelationshipField reports whether a field is a relationship. It delegates
// to ResolveFieldRelationshipSemantics with LifecyclePersistedRead.
func IsRelationshipField(field *FieldMetadata) bool {
	return ResolveFieldRelationshipSemantics(field, LifecyclePersistedRead).IsRelationship
}

// FieldCanonicalType returns the canonical field type. It delegates to
// ResolveFieldRelationshipSemantics with LifecyclePersistedRead.
func FieldCanonicalType(field *FieldMetadata) constants.SchemaFieldType {
	return ResolveFieldRelationshipSemantics(field, LifecyclePersistedRead).CanonicalType
}

// NormalizeLookupType normalizes lookup aliases to reference for type-only
// SQL/logical-type stamping where no relationship, target, master-detail, or
// polymorphic ownership decision is made. New relationship decisions must not
// call it directly.
func NormalizeLookupType(fieldType constants.SchemaFieldType) constants.SchemaFieldType {
	normalized := constants.NormalizeFieldType(string(fieldType))
	if normalized == "lookup" {
		return constants.FieldTypeReference
	}
	return normalized
}
