package models

import "strings"

// LogicalFields returns every field declared on the schema — virtual and
// physical. Callers that ask "does this field name exist on this object"
// (validation, canonicalization, FLS, describe) use this. It is identical
// to schema.Fields today; the helper exists to give those callers a
// deliberate name so audits can distinguish intent from accidental use of
// the physical-column slice.
func LogicalFields(schema *ObjectMetadata) []FieldMetadata {
	if schema == nil {
		return nil
	}
	return schema.Fields
}

// PersistableFields returns every field that is NOT marked IsVirtual.
// The contract this helper guarantees is narrow: it filters fields the
// SchemaDecorator injected as virtual-only (e.g. Permissions<X> on
// PermissionSet). It does NOT filter fields that are computed-at-query-
// time via other mechanisms — formulas, rollups, and other non-stored
// types are still included here. Callers that need "only rows safe to
// use in a raw INSERT/SELECT column list" typically layer an additional
// type check on top (see getStorageColumns in persistence_bulk_insert.go
// which also excludes FieldTypeFormula / FieldTypeRollupSummary).
//
// Use this helper when the risk you're guarding against is specifically
// "a schema-level virtual leaked into SQL" — which is the exact failure
// mode the response-series P2 decorator could otherwise produce if a
// SQL-building site iterated schema.Fields directly.
//
// Returns nil (not an empty slice) when schema is nil, matching the
// other helpers in this file.
func PersistableFields(schema *ObjectMetadata) []FieldMetadata {
	if schema == nil {
		return nil
	}
	out := make([]FieldMetadata, 0, len(schema.Fields))
	for _, f := range schema.Fields {
		if f.IsVirtual {
			continue
		}
		out = append(out, f)
	}
	return out
}

// FindField looks up a field by APIName case-insensitively. Prefers the
// FieldsMap fast path when populated; falls back to a linear scan so the
// helper works on partially constructed schemas (decorators, tests).
// Returns nil when the field is absent, when the schema is nil, or when
// apiName is empty.
func FindField(schema *ObjectMetadata, apiName string) *FieldMetadata {
	if schema == nil || apiName == "" {
		return nil
	}
	key := strings.ToLower(apiName)
	if schema.FieldsMap != nil {
		if f, ok := schema.FieldsMap[key]; ok {
			return f
		}
	}
	for i := range schema.Fields {
		if strings.EqualFold(schema.Fields[i].APIName, apiName) {
			return &schema.Fields[i]
		}
	}
	return nil
}

// VirtualFieldsByKind returns every virtual field whose VirtualKind
// matches kind. Handlers use this to enumerate the fields they own — for
// example, the system-permission write interceptor iterates
// VirtualFieldsByKind(schema, constants.VirtualFieldSystemPermission) to
// learn which keys in an incoming payload belong to it.
//
// Return shape: nil when schema is nil or kind is empty (caller-error
// fast paths); otherwise an empty slice when no field matches. Matches
// the nil-on-bad-input convention used by LogicalFields and
// PersistableFields so callers can treat `len(result) == 0` as the
// "nothing to do" signal uniformly.
func VirtualFieldsByKind(schema *ObjectMetadata, kind string) []FieldMetadata {
	if schema == nil || kind == "" {
		return nil
	}
	out := make([]FieldMetadata, 0)
	for _, f := range schema.Fields {
		if f.IsVirtual && f.VirtualKind == kind {
			out = append(out, f)
		}
	}
	return out
}
