// Package fieldnormalize centralizes value-shape coercion between the DDL
// surface (`_System_Field.default_value`, which stores values exactly as
// declared in CREATE TABLE — so text defaults arrive with DDL quotes like
// `'Draft'` and bools as `'0'` / `'1'`) and the describe / UI surfaces
// (which expect typed JSON: `"Draft"`, `false`, `0`).
//
// Issue #1582 RFC v1.5 PR D — was a hand-rolled strip scattered across
// describe/uiapi paths; now a single pure function so the round trip
// DDL → FieldMetadata → describe JSON is lossless for every known
// logical type.
package fieldnormalize

import (
	"strconv"
	"strings"
)

// NormalizeDDLDefault strips DDL single-quote wrapping and coerces the string
// into the Go-typed value expected by the describe JSON for fieldType.
//
// The input is whatever was read from `_System_Field.default_value` (or
// anywhere else that mirrors DDL). The output is `any` so the caller can
// marshal it directly into JSON with the right shape:
//
//	"'Draft'"   + "string"  → "Draft"         (string)
//	"'0'"       + "boolean" → false            (bool)
//	"'1'"       + "boolean" → true             (bool)
//	"42"        + "number"  → 42.0             (float64)
//	""                      → nil              (no default)
//
// When the raw value can't be coerced to fieldType, the trimmed string is
// returned as-is — describe still sees a usable default and the caller's
// caching layer hasn't lost information.
func NormalizeDDLDefault(raw, fieldType string) any {
	if raw == "" {
		return nil
	}
	s := stripDDLQuotes(raw)
	switch strings.ToLower(fieldType) {
	case "boolean", "bool", "checkbox":
		if b, ok := parseBool(s); ok {
			return b
		}
		// Unknown boolean spelling: fall back to the trimmed string per
		// the function contract (don't silently coerce to false).
	case "int", "integer", "number", "double", "currency", "percent":
		if n, err := strconv.ParseFloat(s, 64); err == nil {
			return n
		}
	}
	return s
}

// NormalizeDDLDefaultString is a convenience wrapper for callers that need a
// string shape (e.g. `_System_Field.default_value` write path where the column
// is a VARCHAR). It strips DDL quotes but preserves the original spelling of
// booleans/numbers so the stored row stays DDL-round-trippable.
func NormalizeDDLDefaultString(raw string) string {
	if raw == "" {
		return ""
	}
	return stripDDLQuotes(raw)
}

func stripDDLQuotes(s string) string {
	s = strings.TrimSpace(s)
	if len(s) >= 2 && s[0] == '\'' && s[len(s)-1] == '\'' {
		inner := s[1 : len(s)-1]
		// Un-escape doubled single quotes (DDL's escape form).
		inner = strings.ReplaceAll(inner, "''", "'")
		return inner
	}
	return s
}

// parseBool reports (true, true) for known truthy spellings,
// (false, true) for known falsy spellings, and (false, false) for
// anything else so callers can fall back without silently coercing
// unknown tokens to false.
func parseBool(s string) (bool, bool) {
	switch strings.ToLower(strings.TrimSpace(s)) {
	case "1", "true", "t", "yes", "y":
		return true, true
	case "0", "false", "f", "no", "n":
		return false, true
	}
	return false, false
}
