package rest

import (
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// fieldLength returns the field length from metadata or type-based Salesforce defaults
func fieldLength(f models.FieldMetadata) int {
	if f.MaxLength != nil && *f.MaxLength > 0 {
		return *f.MaxLength
	}
	sfType := mapFieldTypeToSF(f)
	switch sfType {
	case "id", "reference":
		return 18
	case "boolean":
		return 0
	case "textarea":
		return 32000
	case "double", "int", "currency", "percent":
		return 0
	case "multipicklist":
		return 4099 // Salesforce default for multi-select picklist
	case "encryptedstring":
		return 175 // Salesforce standard for encrypted text
	default:
		return 255
	}
}

// resolveEffectiveFieldType applies the LogicalType override and the
// formula→ReturnType substitution that the SF describe contract requires.
// Both `mapFieldTypeToSF` (the `type` column) and the SoapType call site
// at sobject_describe.go must route through this helper — otherwise a
// formula field's `type` and `soapType` drift apart (e.g. type="double"
// while soapType="xsd:string"). Issue #2188.
func resolveEffectiveFieldType(f models.FieldMetadata) models.FieldType {
	t := f.Type
	if f.LogicalType != nil && *f.LogicalType != "" {
		t = *f.LogicalType
	}
	if t == constants.FieldTypeFormula && f.ReturnType != nil && *f.ReturnType != "" {
		t = *f.ReturnType
	}
	return t
}

func mapFieldTypeToSF(f models.FieldMetadata) string {
	if f.APIName == "Id" {
		return "id"
	}

	effectiveType := string(resolveEffectiveFieldType(f))

	switch effectiveType {
	case "text", "short_text", "string":
		return "string"
	case "long_text", "rich_text", "textarea", "richtextarea":
		return "textarea"
	case "number", "double":
		return "double"
	case "currency":
		return "currency"
	case "percent":
		return "percent"
	case "int", "integer":
		return "int"
	case "checkbox", "boolean":
		return "boolean"
	case "date":
		return "date"
	case "datetime":
		return "datetime"
	case "lookup", "master_detail", "masterdetail", "reference":
		return "reference"
	case "select", "picklist":
		return "picklist"
	case "multi_select", "multipicklist":
		return "multipicklist"
	case "email":
		return "email"
	case "phone":
		return "phone"
	case "url":
		return "url"
	case "password", "encryptedstring":
		return "encryptedstring"
	case "location":
		return "location"
	case "address":
		return "address"
	case "anyType":
		return "anyType"
	default:
		return "string"
	}
}

// fieldDigits returns the Salesforce `digits` value for the field.
// Salesforce defines `digits` as the max number of digits for integer-type fields.
// For non-integer types, it returns 0.
func fieldDigits(f models.FieldMetadata) int {
	sfType := mapFieldTypeToSF(f)
	switch sfType {
	case "int":
		if f.Precision != nil && *f.Precision > 0 {
			return *f.Precision
		}
		return 18 // Salesforce default for integer fields
	default:
		return 0
	}
}

// mapPicklistValues converts Options to Salesforce-standard picklist value entries.
// Salesforce wire format: [{active: true, label: "...", value: "...", defaultValue: false}, ...]
func mapPicklistValues(f models.FieldMetadata) []interface{} {
	sfType := mapFieldTypeToSF(f)
	if (sfType != "picklist" && sfType != "multipicklist") || len(f.Options) == 0 {
		return []interface{}{}
	}

	values := make([]interface{}, len(f.Options))
	for i, opt := range f.Options {
		entry := map[string]interface{}{
			"active":       true,
			"label":        opt.Label,
			"value":        opt.Value,
			"defaultValue": f.DefaultValue != nil && *f.DefaultValue == opt.Value,
		}
		if len(opt.Tags) > 0 {
			entry["tags"] = opt.Tags
		}
		values[i] = entry
	}
	return values
}

// fieldPrecision returns the Salesforce `precision` value.
// For double/currency/percent: total digits (left + right of decimal).
// For other types: 0.
func fieldPrecision(f models.FieldMetadata) int {
	sfType := mapFieldTypeToSF(f)
	switch sfType {
	case "double", "currency", "percent":
		if f.Precision != nil && *f.Precision > 0 {
			return *f.Precision
		}
		return 18 // Salesforce default precision for numeric fields
	default:
		return 0
	}
}

// fieldScale returns the Salesforce `scale` value.
// For double/currency/percent: number of decimal places.
// For other types: 0.
func fieldScale(f models.FieldMetadata) int {
	sfType := mapFieldTypeToSF(f)
	switch sfType {
	case "double", "currency", "percent":
		if f.Scale != nil && *f.Scale > 0 {
			return *f.Scale
		}
		return 0
	default:
		return 0
	}
}

// nonNilStringSlice ensures a nil []string is returned as an empty slice,
// so JSON marshalling produces [] instead of null (Salesforce spec requirement).
func nonNilStringSlice(s []string) []string {
	if s == nil {
		return []string{}
	}
	return s
}

// isSortable returns false for field types Salesforce marks as non-sortable.
func isSortable(sfType string) bool {
	switch sfType {
	case "textarea", "base64", "anyType", "address", "location",
		"multipicklist", "encryptedstring":
		return false
	default:
		return true
	}
}

// isAggregatable returns false for field types Salesforce marks as non-aggregatable.
// In Salesforce, aggregatable means "can appear in an aggregate SOQL query".
// datetime, email, phone, url ARE aggregatable (same set as isSortable).
func isAggregatable(sfType string) bool {
	switch sfType {
	case "textarea", "base64", "anyType", "address", "location",
		"multipicklist", "encryptedstring":
		return false
	default:
		return true
	}
}

// isGroupable returns false for field types Salesforce marks as non-groupable.
// In Salesforce, groupable means "can appear in a GROUP BY clause".
// datetime is aggregatable but NOT groupable; email/phone/url ARE groupable.
func isGroupable(sfType string) bool {
	switch sfType {
	case "textarea", "base64", "anyType", "address", "location",
		"multipicklist", "encryptedstring",
		"datetime":
		return false
	default:
		return true
	}
}

// isFilterable returns false for field types Salesforce marks as non-filterable.
// In Salesforce, encryptedstring fields cannot be used in WHERE clauses.
func isFilterable(sfType string) bool {
	switch sfType {
	case "encryptedstring":
		return false
	default:
		return true
	}
}

// isNillable returns whether a field can be null. Salesforce booleans are never
// nillable (they default to false), regardless of the required flag.
func isNillable(sfType string, required bool) bool {
	if sfType == "boolean" {
		return false
	}
	return !required
}
