package createsourceguard

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

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/recordsguard"
)

// fieldMappingSourcePrecedenceSQL is copied verbatim from
// backend/internal/bootstrap/field_mappings.go (where the original copy stays
// for that package's other callers) so the relocated guard package can build
// the identical effective-mapping ORDER BY without importing backend/*.
func fieldMappingSourcePrecedenceSQL() string {
	// IsCustomized is the admin override signal. Upgraded admin-authored rows
	// may still carry Source='legacy', so runtime readers must prefer them over
	// seed-owned rows instead of relying on owner alone.
	return `CASE
		   WHEN IsCustomized = 1 THEN 0
		   WHEN Source LIKE 'tenant:%' THEN 1
		   WHEN Source NOT IN ('standard', 'legacy') THEN 2
		   WHEN Source = 'standard' THEN 3
		   WHEN Source = 'legacy' THEN 4
		   ELSE 5
		 END, Source ASC`
}

// CreateFromSourceGuardEntry identifies one (TargetObject, SourceFKField)
// position whose write is owned by a configured standard action. The guard in
// backend/internal/features/records uses this to detect direct-create attempts
// that bypass the configured fan-out for issue #1234.
//
// Relocated verbatim to nexuscrm-contract/pkg/recordsguard (Wave-0 SLICE B);
// this alias keeps existing importers compiling unchanged. The DB-driven
// loaders below (GetCreateFromSourceGuardEntries /
// GetCreateFromSourceGuardCandidateFields) stay here as port symbols.
type CreateFromSourceGuardEntry = recordsguard.CreateFromSourceGuardEntry

// Queryer is the smallest database seam needed to derive create-from-source
// ownership. Both *sql.DB and *sql.Tx implement it, so Catalog publication can
// derive the guard from the same transaction/revision as every other mutation
// fact instead of opening a competing live-metadata read path.
type Queryer interface {
	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
}

type effectiveCreateFromSourceMapping struct {
	SourceObject                   string
	TargetObject                   string
	ActionType                     string
	SourceLookupField              string
	FieldMappingWritesSourceLookup bool
}

type effectiveCreateFromSourceTuple struct {
	SourceObject string
	TargetObject string
	ActionType   string
}

type effectiveCreateFromSourceMappingFacts struct {
	Mappings []effectiveCreateFromSourceMapping
	Tuples   []effectiveCreateFromSourceTuple
}

type createFromSourceLookupFieldKey struct {
	targetObject      string
	sourceLookupField string
}

type createFromSourceLookupFieldMetadata struct {
	fieldType   string
	referenceTo string
}

// GetCreateFromSourceGuardEntries returns every (SourceFKField, owning-action)
// pair currently configured for inserts of `targetObject`. Reads the existing
// effective `_System_ObjectFieldMapping` rows using the same
// IsCustomized/Source precedence collapse as GetFieldMappings, then compares
// them to every active standard action's active Flow metadata. A standard
// action owns the guard only when its Flow has a recordCreate `fieldMapping`
// ref for `(m.SourceObject, m.TargetObject, m.ActionType)` AND an
// actual source-bound write surface for the guarded source FK: either a Flow
// inputAssignment writes `m.SourceLookupField` from the fieldMapping source
// record's Id, an inputAssignment writes a target reference field whose
// ReferenceTo contains SourceObject from that same source Id, or the effective
// field-mapping rows for the tuple include `SourceField == Id` and
// `TargetField == SourceLookupField`. The tuple is the same one the Flow
// executor passes to the FieldMappingApplier at runtime, and the source-bound
// write-surface check proves the Flow actually owns the guarded FK position, so
// the guard does not infer ownership from ActionSchema, StandardAction.Name, or
// TargetObjects placement metadata.
//
// Runtime tables are filtered by `IsActive = 1 AND IsDeleted = 0`; active Flow
// versions are reached through `StandardAction.FlowDeveloperName` →
// `FlowDefinition.ActiveVersionId`. Rows whose effective `SourceLookupField`
// is NULL / empty can still define a create-from-source tuple; in that case the
// guarded FK position is inferred only from Flow-owned target reference
// assignments for the same `(SourceObject, TargetObject, ActionType)`.
//
// Result ordering: deterministic by `(SourceFKField, OwningStandardActionId)`.
// Result de-duplication: same key collapsed. Multi-owner (two distinct actions
// both authoring entries on the same FK) returns multiple entries.
//
// Owner-precedence rationale: unlike GetFieldMappings / GetFieldMappingsByTarget
// which rank by `IsCustomized`/`Source` because each consumer applies AT MOST
// ONE row per group, the guard's question is set-shaped: "which standard
// actions own this FK position?" If two active standard actions' Flows both
// declare a recordCreate fieldMapping that covers the same FK position, BOTH
// owners are real authorities the operator authored — the guard accepts bypass
// when ctx matches ANY of them. De-duplication is keyed on (SourceFKField,
// OwningStandardActionId) because two matching Flow recordCreate elements for
// the same action are the same authority.
//
// `nil db` returns `(nil, nil)` — matches the records-layer applier's
// nil-rawDB no-op contract.
func GetCreateFromSourceGuardEntries(db *sql.DB, targetObject string) ([]CreateFromSourceGuardEntry, error) {
	if db == nil {
		return nil, nil
	}
	return GetCreateFromSourceGuardEntriesContext(context.Background(), db, targetObject)
}

// GetCreateFromSourceGuardEntriesContext is the transaction-capable form used
// by the Catalog compiler adapter. It preserves the historical *sql.DB wrapper
// for monolith callers while making the authoritative read context explicit.
func GetCreateFromSourceGuardEntriesContext(ctx context.Context, queryer Queryer, targetObject string) ([]CreateFromSourceGuardEntry, error) {
	if queryer == nil {
		return nil, nil
	}
	if ctx == nil {
		ctx = context.Background()
	}
	targetObject = strings.TrimSpace(targetObject)
	if targetObject == "" {
		return nil, nil
	}

	facts, err := getEffectiveCreateFromSourceMappingFactsWithoutLookupValidation(ctx, queryer, targetObject)
	if err != nil {
		return nil, err
	}
	if len(facts.Mappings) == 0 && len(facts.Tuples) == 0 {
		return nil, nil
	}
	facts.Mappings, err = filterValidCreateFromSourceGuardMappings(ctx, queryer, facts.Mappings)
	if err != nil {
		return nil, err
	}
	flows, err := getActiveStandardActionFlowsForCreateSource(ctx, queryer)
	if err != nil {
		return nil, err
	}
	ownerIndex := buildCreateFromSourceFlowOwnerIndex(flows)
	mappings, err := expandCreateFromSourceGuardMappingsFromFlowAssignments(ctx, queryer, facts, ownerIndex)
	if err != nil {
		return nil, err
	}
	if len(mappings) == 0 {
		return nil, nil
	}

	type dedupKey struct{ sourceFKField, owningStandardActionID string }
	seen := make(map[dedupKey]struct{})
	var out []CreateFromSourceGuardEntry
	for _, mapping := range mappings {
		for _, owner := range ownerIndex[createFromSourceFlowOwnerKeyForMapping(mapping)] {
			if !flowOwnerOwnsCreateFromSourceMapping(owner, mapping) {
				continue
			}
			entry := CreateFromSourceGuardEntry{
				SourceFKField:          mapping.SourceLookupField,
				OwningStandardActionId: owner.ID,
				ActionName:             owner.Name,
			}
			if entry.SourceFKField == "" || entry.OwningStandardActionId == "" {
				continue
			}
			key := dedupKey{entry.SourceFKField, entry.OwningStandardActionId}
			if _, dup := seen[key]; dup {
				continue
			}
			seen[key] = struct{}{}
			out = append(out, entry)
		}
	}

	// Deterministic sort: (SourceFKField, OwningStandardActionId) lex asc.
	// Multi-owner result ordering matters for the rejection-message action
	// list — stable order avoids spurious test churn and aids debugging.
	sortCreateFromSourceGuardEntries(out)
	return out, nil
}

// GetCreateFromSourceGuardCandidateFields returns the unvalidated set of
// source-FK field names that could activate the runtime direct-create guard
// for targetObject. It intentionally avoids SourceLookupField semantic
// validation so ordinary creates that do not set any candidate FK are not
// taken down by unrelated stale pack/overlay metadata; the full guard loader
// only emits entries for live reference-compatible positions, while
// VerifyCreateFromSourceGuardCoverage remains the strict pack/overlay drift
// signal.
func GetCreateFromSourceGuardCandidateFields(db *sql.DB, targetObject string) ([]string, error) {
	return GetCreateFromSourceGuardCandidateFieldsContext(context.Background(), db, targetObject)
}

// GetCreateFromSourceGuardCandidateFieldsContext is the transaction-capable
// counterpart of GetCreateFromSourceGuardCandidateFields.
func GetCreateFromSourceGuardCandidateFieldsContext(ctx context.Context, queryer Queryer, targetObject string) ([]string, error) {
	if queryer == nil {
		return nil, nil
	}
	if ctx == nil {
		ctx = context.Background()
	}
	targetObject = strings.TrimSpace(targetObject)
	if targetObject == "" {
		return nil, nil
	}
	facts, err := getEffectiveCreateFromSourceMappingFactsWithoutLookupValidation(ctx, queryer, targetObject)
	if err != nil {
		return nil, err
	}
	seen := map[string]struct{}{}
	var out []string
	mappings := facts.Mappings
	if len(facts.Tuples) > 0 {
		flows, err := getActiveStandardActionFlowsForCreateSource(ctx, queryer)
		if err != nil {
			return nil, err
		}
		ownerIndex := buildCreateFromSourceFlowOwnerIndex(flows)
		explicit := map[string]struct{}{}
		for _, mapping := range facts.Mappings {
			explicit[createFromSourceMappingKey(mapping)] = struct{}{}
		}
		inferred, err := inferCreateFromSourceGuardMappingsFromFlowAssignments(ctx, queryer, facts.Tuples, ownerIndex, explicit)
		if err != nil {
			return nil, err
		}
		mappings = mergeCreateFromSourceGuardMappings(mappings, inferred)
	}
	for _, mapping := range mappings {
		if mapping.ActionType == "insertInheritFromLookup" {
			continue
		}
		field := strings.TrimSpace(mapping.SourceLookupField)
		if field == "" {
			continue
		}
		key := strings.ToLower(field)
		if _, dup := seen[key]; dup {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, field)
	}
	sortStrings(out)
	return out, nil
}

// sortCreateFromSourceGuardEntries sorts in place by (SourceFKField,
// OwningStandardActionId). Extracted so callers and tests can rely on a
// single source of truth for the ordering.
func sortCreateFromSourceGuardEntries(entries []CreateFromSourceGuardEntry) {
	// Insertion sort (typical N is small, ≤ low dozens) — avoids importing
	// sort just for one call site and keeps the deterministic behavior
	// self-evident from the source.
	for i := 1; i < len(entries); i++ {
		for j := i; j > 0; j-- {
			if compareCreateFromSourceGuardEntries(entries[j-1], entries[j]) <= 0 {
				break
			}
			entries[j-1], entries[j] = entries[j], entries[j-1]
		}
	}
}

func compareCreateFromSourceGuardEntries(a, b CreateFromSourceGuardEntry) int {
	if a.SourceFKField != b.SourceFKField {
		if a.SourceFKField < b.SourceFKField {
			return -1
		}
		return 1
	}
	if a.OwningStandardActionId != b.OwningStandardActionId {
		if a.OwningStandardActionId < b.OwningStandardActionId {
			return -1
		}
		return 1
	}
	return 0
}

func sortStrings(values []string) {
	for i := 1; i < len(values); i++ {
		for j := i; j > 0 && values[j] < values[j-1]; j-- {
			values[j-1], values[j] = values[j], values[j-1]
		}
	}
}

func getEffectiveCreateFromSourceMappingFactsWithoutLookupValidation(ctx context.Context, queryer Queryer, targetObject string) (effectiveCreateFromSourceMappingFacts, error) {
	targetObject = strings.TrimSpace(targetObject)
	query := fmt.Sprintf(
		`SELECT SourceObject, TargetObject, ActionType, SourceField, TargetField, SourceLookupField
		   FROM %s
		  WHERE IsActive = 1 AND IsDeleted = 0
		    AND COALESCE(ProjectionOnly, 0) = 0
		    AND Direction = 'legacy'
		    %s
		  ORDER BY %s, SortOrder ASC, SourceObject ASC, TargetObject ASC, ActionType ASC, SourceField ASC, TargetField ASC`,
		constants.TableObjectFieldMapping,
		createFromSourceTargetObjectPredicate(targetObject),
		fieldMappingSourcePrecedenceSQL(),
	)
	var args []interface{}
	if targetObject != "" {
		args = append(args, targetObject)
	}
	rows, err := queryer.QueryContext(ctx, query, args...)
	if err != nil {
		if targetObject != "" {
			return effectiveCreateFromSourceMappingFacts{}, fmt.Errorf("query effective create-from-source mappings for %q: %w", targetObject, err)
		}
		return effectiveCreateFromSourceMappingFacts{}, fmt.Errorf("query effective create-from-source mappings: %w", err)
	}

	seenSourceFields := map[string]struct{}{}
	tupleIndex := map[string]int{}
	positionIndex := map[string]int{}
	var mappings []effectiveCreateFromSourceMapping
	var tuples []effectiveCreateFromSourceTuple
	for rows.Next() {
		var sourceObject, rowTargetObject, actionType, sourceField, targetField string
		var sourceLookup sql.NullString
		if err := rows.Scan(&sourceObject, &rowTargetObject, &actionType, &sourceField, &targetField, &sourceLookup); err != nil {
			rows.Close()
			return effectiveCreateFromSourceMappingFacts{}, fmt.Errorf("could not scan effective create-from-source mapping: %w", err)
		}
		sourceObject = strings.TrimSpace(sourceObject)
		rowTargetObject = strings.TrimSpace(rowTargetObject)
		actionType = strings.TrimSpace(actionType)
		sourceField = strings.TrimSpace(sourceField)
		targetField = strings.TrimSpace(targetField)
		sourceFieldKey := sourceObject + "\x00" + rowTargetObject + "\x00" + actionType + "\x00" + sourceField
		if _, dup := seenSourceFields[sourceFieldKey]; dup {
			continue
		}
		seenSourceFields[sourceFieldKey] = struct{}{}
		tupleKey := sourceObject + "\x00" + rowTargetObject + "\x00" + actionType
		if _, ok := tupleIndex[tupleKey]; !ok {
			tupleIndex[tupleKey] = len(tuples)
			tuples = append(tuples, effectiveCreateFromSourceTuple{
				SourceObject: sourceObject,
				TargetObject: rowTargetObject,
				ActionType:   actionType,
			})
		}
		sourceLookupField := strings.TrimSpace(sourceLookup.String)
		if sourceLookupField == "" {
			continue
		}
		positionKey := sourceObject + "\x00" + rowTargetObject + "\x00" + actionType + "\x00" + sourceLookupField
		idx, ok := positionIndex[positionKey]
		if !ok {
			idx = len(mappings)
			positionIndex[positionKey] = idx
			mappings = append(mappings, effectiveCreateFromSourceMapping{
				SourceObject:      sourceObject,
				TargetObject:      rowTargetObject,
				ActionType:        actionType,
				SourceLookupField: sourceLookupField,
			})
		}
		if fieldMappingWritesSourceIDToLookup(sourceField, targetField, sourceLookupField) {
			mappings[idx].FieldMappingWritesSourceLookup = true
		}
	}
	if err := rows.Err(); err != nil {
		rows.Close()
		return effectiveCreateFromSourceMappingFacts{}, fmt.Errorf("could not iterate effective create-from-source mappings: %w", err)
	}
	if err := rows.Close(); err != nil {
		return effectiveCreateFromSourceMappingFacts{}, fmt.Errorf("could not close effective create-from-source mapping rows: %w", err)
	}
	return effectiveCreateFromSourceMappingFacts{Mappings: mappings, Tuples: tuples}, nil
}

func validateCreateFromSourceLookupFields(ctx context.Context, queryer Queryer, mappings []effectiveCreateFromSourceMapping) error {
	if len(mappings) == 0 {
		return nil
	}
	fields, err := loadCreateFromSourceLookupFieldMetadata(ctx, queryer, mappings)
	if err != nil {
		return err
	}
	for _, mapping := range mappings {
		key := createFromSourceLookupKey(mapping.TargetObject, mapping.SourceLookupField)
		field, ok := fields[key]
		if !ok {
			return fmt.Errorf("create-from-source SourceLookupField %s.%s for ActionType=%q SourceObject=%q is not a live target field",
				mapping.TargetObject, mapping.SourceLookupField, mapping.ActionType, mapping.SourceObject)
		}
		if !isReferenceLikeFieldType(field.fieldType) {
			return fmt.Errorf("create-from-source SourceLookupField %s.%s has Type=%q, want reference/masterdetail",
				mapping.TargetObject, mapping.SourceLookupField, field.fieldType)
		}
		if !referenceListContains(field.referenceTo, mapping.SourceObject) {
			return fmt.Errorf("create-from-source SourceLookupField %s.%s ReferenceTo=%q does not include SourceObject=%q",
				mapping.TargetObject, mapping.SourceLookupField, field.referenceTo, mapping.SourceObject)
		}
	}
	return nil
}

func filterValidCreateFromSourceGuardMappings(ctx context.Context, queryer Queryer, mappings []effectiveCreateFromSourceMapping) ([]effectiveCreateFromSourceMapping, error) {
	if len(mappings) == 0 {
		return nil, nil
	}
	fields, err := loadCreateFromSourceLookupFieldMetadata(ctx, queryer, mappings)
	if err != nil {
		return nil, err
	}
	out := make([]effectiveCreateFromSourceMapping, 0, len(mappings))
	for _, mapping := range mappings {
		key := createFromSourceLookupKey(mapping.TargetObject, mapping.SourceLookupField)
		field, ok := fields[key]
		if !ok || !isReferenceLikeFieldType(field.fieldType) || !referenceListContains(field.referenceTo, mapping.SourceObject) {
			continue
		}
		out = append(out, mapping)
	}
	return out, nil
}

func expandCreateFromSourceGuardMappingsFromFlowAssignments(
	ctx context.Context,
	queryer Queryer,
	facts effectiveCreateFromSourceMappingFacts,
	ownerIndex map[createFromSourceFlowOwnerKey][]createFromSourceFlowOwner,
) ([]effectiveCreateFromSourceMapping, error) {
	if len(ownerIndex) == 0 {
		return facts.Mappings, nil
	}
	explicit := map[string]struct{}{}
	for _, mapping := range facts.Mappings {
		explicit[createFromSourceMappingKey(mapping)] = struct{}{}
	}
	inferred, err := inferCreateFromSourceGuardMappingsFromFlowAssignments(ctx, queryer, facts.Tuples, ownerIndex, explicit)
	if err != nil {
		return nil, err
	}
	return mergeCreateFromSourceGuardMappings(facts.Mappings, inferred), nil
}

func inferCreateFromSourceGuardMappingsFromFlowAssignments(
	ctx context.Context,
	queryer Queryer,
	tuples []effectiveCreateFromSourceTuple,
	ownerIndex map[createFromSourceFlowOwnerKey][]createFromSourceFlowOwner,
	explicit map[string]struct{},
) ([]effectiveCreateFromSourceMapping, error) {
	var candidates []effectiveCreateFromSourceMapping
	for _, tuple := range tuples {
		if tuple.ActionType == "insertInheritFromLookup" {
			continue
		}
		owners := ownerIndex[createFromSourceFlowOwnerKeyForTuple(tuple.SourceObject, tuple.TargetObject, tuple.ActionType)]
		for _, owner := range owners {
			for _, assignedField := range owner.assignedFields {
				candidate := effectiveCreateFromSourceMapping{
					SourceObject:      tuple.SourceObject,
					TargetObject:      tuple.TargetObject,
					ActionType:        tuple.ActionType,
					SourceLookupField: assignedField,
				}
				if _, ok := explicit[createFromSourceMappingKey(candidate)]; ok {
					continue
				}
				candidates = append(candidates, candidate)
			}
		}
	}
	if len(candidates) == 0 {
		return nil, nil
	}
	fields, err := loadCreateFromSourceLookupFieldMetadata(ctx, queryer, candidates)
	if err != nil {
		return nil, err
	}
	var out []effectiveCreateFromSourceMapping
	seen := map[string]struct{}{}
	for _, candidate := range candidates {
		key := createFromSourceLookupKey(candidate.TargetObject, candidate.SourceLookupField)
		field, ok := fields[key]
		if !ok || !isReferenceLikeFieldType(field.fieldType) || !referenceListContains(field.referenceTo, candidate.SourceObject) {
			continue
		}
		mappingKey := createFromSourceMappingKey(candidate)
		if _, dup := seen[mappingKey]; dup {
			continue
		}
		seen[mappingKey] = struct{}{}
		out = append(out, candidate)
	}
	return out, nil
}

func mergeCreateFromSourceGuardMappings(explicit, inferred []effectiveCreateFromSourceMapping) []effectiveCreateFromSourceMapping {
	out := make([]effectiveCreateFromSourceMapping, 0, len(explicit)+len(inferred))
	index := map[string]int{}
	for _, mapping := range explicit {
		key := createFromSourceMappingKey(mapping)
		if i, ok := index[key]; ok {
			out[i].FieldMappingWritesSourceLookup = out[i].FieldMappingWritesSourceLookup || mapping.FieldMappingWritesSourceLookup
			continue
		}
		index[key] = len(out)
		out = append(out, mapping)
	}
	for _, mapping := range inferred {
		key := createFromSourceMappingKey(mapping)
		if _, ok := index[key]; ok {
			continue
		}
		index[key] = len(out)
		out = append(out, mapping)
	}
	return out
}

func createFromSourceMappingKey(mapping effectiveCreateFromSourceMapping) string {
	return strings.ToLower(strings.TrimSpace(mapping.SourceObject)) + "\x00" +
		strings.ToLower(strings.TrimSpace(mapping.TargetObject)) + "\x00" +
		strings.TrimSpace(mapping.ActionType) + "\x00" +
		strings.ToLower(strings.TrimSpace(mapping.SourceLookupField))
}

func loadCreateFromSourceLookupFieldMetadata(ctx context.Context, queryer Queryer, mappings []effectiveCreateFromSourceMapping) (map[createFromSourceLookupFieldKey]createFromSourceLookupFieldMetadata, error) {
	fieldsByTarget := make(map[string][]string)
	var targetOrder []string
	for _, mapping := range mappings {
		targetObject := strings.TrimSpace(mapping.TargetObject)
		sourceLookupField := strings.TrimSpace(mapping.SourceLookupField)
		if targetObject == "" || sourceLookupField == "" {
			continue
		}
		if _, ok := fieldsByTarget[targetObject]; !ok {
			targetOrder = append(targetOrder, targetObject)
		}
		if !stringSliceContainsFold(fieldsByTarget[targetObject], sourceLookupField) {
			fieldsByTarget[targetObject] = append(fieldsByTarget[targetObject], sourceLookupField)
		}
	}

	out := make(map[createFromSourceLookupFieldKey]createFromSourceLookupFieldMetadata)
	for _, targetObject := range targetOrder {
		sourceLookupFields := fieldsByTarget[targetObject]
		if len(sourceLookupFields) == 0 {
			continue
		}
		query := fmt.Sprintf(`
SELECT o.ApiName, f.ApiName, f.Type, f.ReferenceTo
  FROM %s AS o
  JOIN %s AS f
    ON f.ObjectId = o.Id
   AND f.IsDeleted = 0
 WHERE o.ApiName = ?
   AND o.IsDeleted = 0
   AND f.ApiName IN (%s)`,
			constants.TableObject, constants.TableField, placeholders(len(sourceLookupFields)),
		)
		args := make([]interface{}, 0, 1+len(sourceLookupFields))
		args = append(args, targetObject)
		for _, sourceLookupField := range sourceLookupFields {
			args = append(args, sourceLookupField)
		}
		rows, err := queryer.QueryContext(ctx, query, args...)
		if err != nil {
			return nil, fmt.Errorf("query create-from-source SourceLookupField metadata for %q: %w", targetObject, err)
		}
		for rows.Next() {
			var rowTargetObject, fieldAPIName, fieldType string
			var referenceTo sql.NullString
			if err := rows.Scan(&rowTargetObject, &fieldAPIName, &fieldType, &referenceTo); err != nil {
				rows.Close()
				return nil, fmt.Errorf("could not scan create-from-source SourceLookupField metadata for %q: %w", targetObject, err)
			}
			out[createFromSourceLookupKey(rowTargetObject, fieldAPIName)] = createFromSourceLookupFieldMetadata{
				fieldType:   strings.TrimSpace(fieldType),
				referenceTo: referenceTo.String,
			}
		}
		if err := rows.Err(); err != nil {
			rows.Close()
			return nil, fmt.Errorf("could not iterate create-from-source SourceLookupField metadata for %q: %w", targetObject, err)
		}
		if err := rows.Close(); err != nil {
			return nil, fmt.Errorf("could not close create-from-source SourceLookupField metadata rows for %q: %w", targetObject, err)
		}
	}
	return out, nil
}

func createFromSourceLookupKey(targetObject, sourceLookupField string) createFromSourceLookupFieldKey {
	return createFromSourceLookupFieldKey{
		targetObject:      strings.ToLower(strings.TrimSpace(targetObject)),
		sourceLookupField: strings.ToLower(strings.TrimSpace(sourceLookupField)),
	}
}

func placeholders(n int) string {
	if n <= 0 {
		return ""
	}
	return strings.TrimSuffix(strings.Repeat("?,", n), ",")
}

func isReferenceLikeFieldType(fieldType string) bool {
	switch strings.ToLower(strings.TrimSpace(fieldType)) {
	case "reference", "masterdetail":
		return true
	default:
		return false
	}
}

func referenceListContains(rawReferenceTo, sourceObject string) bool {
	sourceObject = strings.TrimSpace(sourceObject)
	if sourceObject == "" {
		return false
	}
	var refs []string
	if err := json.Unmarshal([]byte(strings.TrimSpace(rawReferenceTo)), &refs); err != nil {
		return false
	}
	for _, ref := range refs {
		if strings.EqualFold(strings.TrimSpace(ref), sourceObject) {
			return true
		}
	}
	return false
}

func stringSliceContainsFold(items []string, needle string) bool {
	for _, item := range items {
		if strings.EqualFold(item, needle) {
			return true
		}
	}
	return false
}

func createFromSourceTargetObjectPredicate(targetObject string) string {
	if strings.TrimSpace(targetObject) == "" {
		return ""
	}
	return "AND TargetObject = ?"
}
