package executor

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	gocql "github.com/apache/cassandra-gocql-driver/v2"
	"github.com/everything-on-mysql/cassandra-on-mysql/pkg/cql/parser"
)

type castSpec struct {
	index      int
	name       string
	fromType   string
	toType     string
	sqlHandled bool
	timeFunc   *timeFunctionSpec
}

func (e *selectPlanner) translateCastExpr(castExpr *parser.CastExpr, alias string, typeInfo map[string]columnTypeInfo) (string, *castSpec, error) {
	if castExpr == nil {
		return "", nil, fmt.Errorf("cast expression is required")
	}
	toType := normalizeCastType(castExpr.Type.TypeString())
	lookup := func(name string) (string, bool) {
		if len(typeInfo) == 0 {
			return "", false
		}
		info, ok := typeInfo[strings.ToLower(name)]
		if !ok {
			return "", false
		}
		return normalizeCastType(info.cqlType), true
	}
	fromType, ok := inferCastSourceType(castExpr.Expr, lookup)
	if !ok {
		fromType = ""
	}
	sqlHandled := ok && castSQLSafe(fromType, toType)

	name := alias
	if name == "" {
		name = castExpr.String()
	}
	spec := &castSpec{
		name:       name,
		fromType:   fromType,
		toType:     toType,
		sqlHandled: sqlHandled,
	}

	var sqlExpr string
	if sqlHandled {
		sqlExpr = e.translator.TranslateExpression(castExpr)
	} else {
		sqlExpr = e.translator.TranslateExpression(castExpr.Expr)
	}
	if alias != "" {
		sqlExpr = sqlExpr + " AS " + e.translator.QuoteIdentifier(alias)
	}
	return sqlExpr, spec, nil
}

func inferCastSourceType(expr parser.Expression, lookup func(string) (string, bool)) (string, bool) {
	if expr == nil {
		return "", false
	}
	switch e := expr.(type) {
	case *parser.TypeHintExpr:
		typ := normalizeCastType(typeStringForParam(e.Type))
		if typ != "" {
			return typ, true
		}
		return inferCastSourceType(e.Expr, lookup)
	case *parser.AliasedExpr:
		return inferCastSourceType(e.Expr, lookup)
	case *parser.ColumnExpr:
		if lookup == nil {
			return "", false
		}
		return lookup(e.Name)
	case *parser.CastExpr:
		return normalizeCastType(e.Type.TypeString()), true
	case *parser.UnaryExpr:
		switch strings.TrimSpace(e.Op) {
		case "+", "-":
			return inferCastSourceType(e.Expr, lookup)
		}
	case *parser.IntegerLiteral:
		return "int", true
	case *parser.FloatLiteral:
		return "double", true
	case *parser.BooleanLiteral:
		return "boolean", true
	case *parser.StringLiteral:
		return "text", true
	case *parser.BlobLiteral:
		return "blob", true
	case *parser.UUIDLiteral:
		return "uuid", true
	}
	return "", false
}

func castSpecForResult(expr parser.Expression, index int, columns []ColumnInfo) (*castSpec, bool) {
	if expr == nil {
		return nil, false
	}
	alias := ""
	base := expr
	if aliased, ok := expr.(*parser.AliasedExpr); ok {
		alias = aliased.Alias
		base = aliased.Expr
	}
	castExpr, ok := base.(*parser.CastExpr)
	if !ok {
		return nil, false
	}
	lookup := func(name string) (string, bool) {
		for _, col := range columns {
			if strings.EqualFold(col.Name, name) {
				return normalizeCastType(col.Type), true
			}
		}
		return "", false
	}
	fromType, ok := inferCastSourceType(castExpr.Expr, lookup)
	if !ok {
		fromType = ""
	}
	name := alias
	if name == "" {
		name = castExpr.String()
	}
	spec := &castSpec{
		index:      index,
		name:       name,
		fromType:   fromType,
		toType:     normalizeCastType(castExpr.Type.TypeString()),
		sqlHandled: false,
	}
	return spec, true
}

func castSQLSafe(fromType, toType string) bool {
	fromType = normalizeCastType(fromType)
	toType = normalizeCastType(toType)
	if fromType == "" || toType == "" {
		return false
	}
	if isTextCastType(fromType) && isTextCastType(toType) {
		return true
	}
	fromRank, okFrom := numericCastRank(fromType)
	toRank, okTo := numericCastRank(toType)
	if okFrom && okTo {
		return fromRank <= toRank
	}
	return false
}

func applyCastTransform(result *QueryResult, casts []castSpec) error {
	if result == nil || len(casts) == 0 {
		return nil
	}
	for _, spec := range casts {
		if spec.index >= len(result.Columns) {
			continue
		}
		result.Columns[spec.index].Name = spec.name
		result.Columns[spec.index].Type = spec.toType
		result.Columns[spec.index].TypeInfo = nil
	}
	for i := range result.Rows {
		for _, spec := range casts {
			if spec.index >= len(result.Rows[i].Values) {
				continue
			}
			if spec.sqlHandled {
				continue
			}
			value := result.Rows[i].Values[spec.index]
			if spec.timeFunc != nil {
				converted, err := evalTimeFunction(*spec.timeFunc, value, spec.fromType)
				if err != nil {
					return fmt.Errorf("%s conversion failed: %w", spec.name, err)
				}
				result.Rows[i].Values[spec.index] = converted
				continue
			}
			converted, err := castValue(value, spec.fromType, spec.toType)
			if err != nil {
				return fmt.Errorf("cast %q to %q failed: %w", spec.fromType, spec.toType, err)
			}
			result.Rows[i].Values[spec.index] = converted
		}
	}
	return nil
}

func normalizeCastType(cqlType string) string {
	normalized := normalizeCQLTypeString(cqlType)
	switch normalized {
	case "varchar", "ascii":
		return "text"
	case "counter":
		return "bigint"
	default:
		return normalized
	}
}

func isTextCastType(cqlType string) bool {
	return normalizeCastType(cqlType) == "text"
}

func isNumericCastType(cqlType string) bool {
	switch normalizeCastType(cqlType) {
	case "tinyint", "smallint", "int", "bigint", "float", "double", "decimal", "varint":
		return true
	default:
		return false
	}
}

func numericCastRank(cqlType string) (int, bool) {
	switch normalizeCastType(cqlType) {
	case "tinyint":
		return 1, true
	case "smallint":
		return 2, true
	case "int":
		return 3, true
	case "bigint":
		return 4, true
	case "float":
		return 5, true
	case "double":
		return 6, true
	default:
		return 0, false
	}
}

func castValue(value interface{}, fromType, toType string) (interface{}, error) {
	if value == nil {
		return nil, nil
	}
	toType = normalizeCastType(toType)
	if toType == "" {
		return nil, fmt.Errorf("cast target type is required")
	}
	fromType = normalizeCastType(fromType)
	if fromType == "" {
		fromType = inferCastTypeFromValue(value)
	}
	if fromType == "" {
		return nil, fmt.Errorf("cast source type is required")
	}

	switch toType {
	case "text":
		return castToText(value, fromType)
	case "tinyint":
		return castToInt(value, fromType, 8)
	case "smallint":
		return castToInt(value, fromType, 16)
	case "int":
		return castToInt(value, fromType, 32)
	case "bigint":
		return castToInt(value, fromType, 64)
	case "float":
		return castToFloat(value, fromType, 32)
	case "double":
		return castToFloat(value, fromType, 64)
	case "decimal":
		return castToDecimal(value, fromType)
	case "varint":
		return castToVarint(value, fromType)
	case "timestamp":
		return castToTimestamp(value, fromType)
	case "date":
		return castToDate(value, fromType)
	case "time":
		return castToTime(value, fromType)
	case "uuid":
		return castToUUID(value, fromType, false)
	case "timeuuid":
		return castToUUID(value, fromType, true)
	default:
		return nil, fmt.Errorf("unsupported cast target type %q", toType)
	}
}

func inferCastTypeFromValue(value interface{}) string {
	switch value.(type) {
	case int8:
		return "tinyint"
	case int16:
		return "smallint"
	case int32, int:
		return "int"
	case int64:
		return "bigint"
	case uint, uint8, uint16, uint32, uint64:
		return "bigint"
	case float32:
		return "float"
	case float64:
		return "double"
	case bool:
		return "boolean"
	case time.Time:
		return "timestamp"
	case time.Duration:
		return "time"
	case []byte:
		return "blob"
	case string:
		return "text"
	default:
		return ""
	}
}

func castToText(value interface{}, fromType string) (string, error) {
	if isTextCastType(fromType) {
		return fmt.Sprint(value), nil
	}
	return cqlValueToString(value, fromType)
}

func castToInt(value interface{}, fromType string, bits int) (interface{}, error) {
	if !isNumericCastType(fromType) && !isTextCastType(fromType) {
		return nil, fmt.Errorf("unsupported cast from %q to int", fromType)
	}
	i64, err := castToInt64ForBits(value, fromType, bits)
	if err != nil {
		return nil, err
	}
	switch bits {
	case 8:
		return int8(i64), nil
	case 16:
		return int16(i64), nil
	case 32:
		return int32(i64), nil
	case 64:
		return int64(i64), nil
	default:
		return nil, fmt.Errorf("unsupported int size %d", bits)
	}
}

func castToInt64ForBits(value interface{}, fromType string, bits int) (int64, error) {
	if bits <= 0 || bits > 64 {
		return 0, fmt.Errorf("unsupported int size %d", bits)
	}
	normalized := normalizeCastType(fromType)
	if isFloatCastSource(normalized, value) {
		f64, err := castToFloat64(value)
		if err != nil {
			return 0, err
		}
		return floatToInt64ForBits(f64, bits), nil
	}
	if isTextCastType(normalized) {
		switch v := value.(type) {
		case string:
			trimmed := strings.TrimSpace(v)
			if strings.ContainsAny(trimmed, ".eE") {
				f64, err := parseFloatFromString(trimmed)
				if err != nil {
					return 0, err
				}
				return floatToInt64ForBits(f64, bits), nil
			}
		case []byte:
			trimmed := strings.TrimSpace(string(v))
			if strings.ContainsAny(trimmed, ".eE") {
				f64, err := parseFloatFromString(trimmed)
				if err != nil {
					return 0, err
				}
				return floatToInt64ForBits(f64, bits), nil
			}
		}
	}
	return castToInt64(value)
}

func isFloatCastSource(fromType string, value interface{}) bool {
	switch normalizeCastType(fromType) {
	case "float", "double":
		return true
	}
	switch value.(type) {
	case float32, float64:
		return true
	default:
		return false
	}
}

func castToFloat(value interface{}, fromType string, bits int) (interface{}, error) {
	if !isNumericCastType(fromType) && !isTextCastType(fromType) {
		return nil, fmt.Errorf("unsupported cast from %q to float", fromType)
	}
	f64, err := castToFloat64(value)
	if err != nil {
		return nil, err
	}
	if bits == 32 {
		return float32(f64), nil
	}
	return f64, nil
}

func castToDecimal(value interface{}, fromType string) (interface{}, error) {
	if !isNumericCastType(fromType) && !isTextCastType(fromType) {
		return nil, fmt.Errorf("unsupported cast from %q to decimal", fromType)
	}
	if _, _, ok := decimalPartsFromValue(value); !ok {
		return nil, fmt.Errorf("invalid decimal value")
	}
	return strings.TrimSpace(fmt.Sprint(value)), nil
}

func castToVarint(value interface{}, fromType string) (interface{}, error) {
	if !isNumericCastType(fromType) && !isTextCastType(fromType) {
		return nil, fmt.Errorf("unsupported cast from %q to varint", fromType)
	}
	if _, ok := bigIntFromValue(value); !ok {
		return nil, fmt.Errorf("invalid varint value")
	}
	return strings.TrimSpace(fmt.Sprint(value)), nil
}

func castToTimestamp(value interface{}, fromType string) (interface{}, error) {
	switch normalizeCastType(fromType) {
	case "timestamp":
		if ts, ok := parseTimestampValue(value); ok {
			return ts.UTC(), nil
		}
	case "date":
		if t, ok := value.(time.Time); ok {
			return dateFromTime(t), nil
		}
		if s, ok := value.(string); ok {
			if d, ok := parseDateString(s); ok {
				return d, nil
			}
		}
	case "timeuuid":
		ts, err := timeuuidToTime(value)
		if err != nil {
			return nil, err
		}
		return ts, nil
	case "text":
		if ts, ok := parseTimestampValue(value); ok {
			return ts.UTC(), nil
		}
	default:
		if isNumericCastType(fromType) {
			if ts, ok := parseTimestampValue(value); ok {
				return ts.UTC(), nil
			}
		}
	}
	return nil, fmt.Errorf("unsupported cast from %q to timestamp", fromType)
}

func castToDate(value interface{}, fromType string) (interface{}, error) {
	switch normalizeCastType(fromType) {
	case "date":
		if t, ok := value.(time.Time); ok {
			return dateFromTime(t), nil
		}
		if s, ok := value.(string); ok {
			if d, ok := parseDateString(s); ok {
				return d, nil
			}
		}
	case "timestamp":
		if ts, ok := parseTimestampValue(value); ok {
			return dateFromTime(ts), nil
		}
	case "timeuuid":
		ts, err := timeuuidToTime(value)
		if err != nil {
			return nil, err
		}
		return dateFromTime(ts), nil
	case "text":
		if s, ok := value.(string); ok {
			if d, ok := parseDateString(s); ok {
				return d, nil
			}
			if ts, ok := parseTimestampString(s); ok {
				return dateFromTime(ts), nil
			}
		}
	}
	return nil, fmt.Errorf("unsupported cast from %q to date", fromType)
}

func castToTime(value interface{}, fromType string) (interface{}, error) {
	switch normalizeCastType(fromType) {
	case "time":
		switch v := value.(type) {
		case time.Duration:
			return v, nil
		case time.Time:
			return timeOfDay(v), nil
		case string:
			if d, ok := parseTimeString(v); ok {
				return d, nil
			}
		}
	case "text":
		if s, ok := value.(string); ok {
			if d, ok := parseTimeString(s); ok {
				return d, nil
			}
		}
	}
	return nil, fmt.Errorf("unsupported cast from %q to time", fromType)
}

func castToUUID(value interface{}, fromType string, requireTimeuuid bool) (interface{}, error) {
	switch normalizeCastType(fromType) {
	case "uuid", "timeuuid", "text":
		s, ok := value.(string)
		if !ok {
			s = fmt.Sprint(value)
		}
		parsed, err := gocql.ParseUUID(strings.TrimSpace(s))
		if err != nil {
			return nil, err
		}
		if requireTimeuuid {
			if parsed.Time().IsZero() {
				return nil, fmt.Errorf("timeuuid value is required")
			}
		}
		return parsed.String(), nil
	default:
		return nil, fmt.Errorf("unsupported cast from %q to uuid", fromType)
	}
}

func castToInt64(value interface{}) (int64, error) {
	switch v := value.(type) {
	case int:
		return int64(v), nil
	case int8:
		return int64(v), nil
	case int16:
		return int64(v), nil
	case int32:
		return int64(v), nil
	case int64:
		return v, nil
	case uint:
		return int64(v), nil
	case uint8:
		return int64(v), nil
	case uint16:
		return int64(v), nil
	case uint32:
		return int64(v), nil
	case uint64:
		const maxInt64 = int64(^uint64(0) >> 1)
		if v > uint64(maxInt64) {
			return 0, fmt.Errorf("value out of range")
		}
		return int64(v), nil
	case float32:
		return floatToInt64(float64(v)), nil
	case float64:
		return floatToInt64(v), nil
	case string:
		return parseIntFromString(v)
	case []byte:
		return parseIntFromString(string(v))
	default:
		return 0, fmt.Errorf("invalid numeric value %T", value)
	}
}

func castToFloat64(value interface{}) (float64, error) {
	switch v := value.(type) {
	case float64:
		return v, nil
	case float32:
		return float64(v), nil
	case int:
		return float64(v), nil
	case int8:
		return float64(v), nil
	case int16:
		return float64(v), nil
	case int32:
		return float64(v), nil
	case int64:
		return float64(v), nil
	case uint:
		return float64(v), nil
	case uint8:
		return float64(v), nil
	case uint16:
		return float64(v), nil
	case uint32:
		return float64(v), nil
	case uint64:
		return float64(v), nil
	case string:
		return parseFloatFromString(v)
	case []byte:
		return parseFloatFromString(string(v))
	default:
		return 0, fmt.Errorf("invalid numeric value %T", value)
	}
}

func floatToInt64(value float64) int64 {
	const maxInt64 = int64(^uint64(0) >> 1)
	const minInt64 = -maxInt64 - 1
	return floatToInt64Range(value, minInt64, maxInt64)
}

func floatToInt64ForBits(value float64, bits int) int64 {
	if bits < 64 {
		const maxInt32 = int64(^uint32(0) >> 1)
		const minInt32 = -maxInt32 - 1
		return floatToInt64Range(value, minInt32, maxInt32)
	}
	return floatToInt64(value)
}

func floatToInt64Range(value float64, min, max int64) int64 {
	if math.IsNaN(value) {
		return 0
	}
	if value > float64(max) {
		return max
	}
	if value < float64(min) {
		return min
	}
	return int64(value)
}

func parseIntFromString(raw string) (int64, error) {
	s := strings.TrimSpace(raw)
	if s == "" {
		return 0, fmt.Errorf("empty numeric string")
	}
	if strings.ContainsAny(s, ".eE") {
		f, err := strconv.ParseFloat(s, 64)
		if err != nil {
			return 0, err
		}
		return floatToInt64(f), nil
	}
	return strconv.ParseInt(s, 10, 64)
}

func parseFloatFromString(raw string) (float64, error) {
	s := strings.TrimSpace(raw)
	if s == "" {
		return 0, fmt.Errorf("empty numeric string")
	}
	return strconv.ParseFloat(s, 64)
}

func timeuuidToTime(value interface{}) (time.Time, error) {
	s, ok := value.(string)
	if !ok {
		s = fmt.Sprint(value)
	}
	parsed, err := gocql.ParseUUID(strings.TrimSpace(s))
	if err != nil {
		return time.Time{}, err
	}
	ts := parsed.Time()
	if ts.IsZero() {
		return time.Time{}, fmt.Errorf("timeuuid value is required")
	}
	return ts, nil
}

func dateFromTime(t time.Time) time.Time {
	utc := t.UTC()
	return time.Date(utc.Year(), utc.Month(), utc.Day(), 0, 0, 0, 0, time.UTC)
}

func timeOfDay(t time.Time) time.Duration {
	utc := t.UTC()
	return time.Duration(utc.Hour())*time.Hour +
		time.Duration(utc.Minute())*time.Minute +
		time.Duration(utc.Second())*time.Second +
		time.Duration(utc.Nanosecond())
}

func applyCastConversionsForDML(stmt parser.Statement, params []interface{}, columnTypes map[string]string) (parser.Statement, []interface{}, bool, error) {
	if stmt == nil {
		return stmt, params, false, nil
	}
	markerIndex := buildBindMarkerIndex(stmt)
	lookup := func(name string) (string, bool) {
		if columnTypes == nil {
			return "", false
		}
		typ, ok := columnTypes[strings.ToLower(name)]
		if !ok {
			return "", false
		}
		return normalizeCastType(typ), true
	}

	changed := false
	switch s := stmt.(type) {
	case *parser.InsertStatement:
		out := *s
		if s.JSON {
			if s.JSONValue != nil {
				expr, ch, err := rewriteCastsInExpr(s.JSONValue, params, markerIndex, lookup)
				if err != nil {
					return nil, nil, false, err
				}
				if ch {
					out.JSONValue = expr
					changed = true
				}
			}
		} else if len(s.Values) > 0 {
			values := make([][]parser.Expression, len(s.Values))
			for i, row := range s.Values {
				if len(row) == 0 {
					continue
				}
				newRow := make([]parser.Expression, len(row))
				rowChanged := false
				for j, expr := range row {
					updated, ch, err := rewriteCastsInExpr(expr, params, markerIndex, lookup)
					if err != nil {
						return nil, nil, false, err
					}
					if ch {
						rowChanged = true
					}
					newRow[j] = updated
				}
				if rowChanged {
					changed = true
					values[i] = newRow
				} else {
					values[i] = row
				}
			}
			if changed {
				out.Values = values
			}
		}
		if s.TTL != nil {
			expr, ch, err := rewriteCastsInExpr(s.TTL, params, markerIndex, lookup)
			if err != nil {
				return nil, nil, false, err
			}
			if ch {
				out.TTL = expr
				changed = true
			}
		}
		if s.Timestamp != nil {
			expr, ch, err := rewriteCastsInExpr(s.Timestamp, params, markerIndex, lookup)
			if err != nil {
				return nil, nil, false, err
			}
			if ch {
				out.Timestamp = expr
				changed = true
			}
		}
		if changed {
			return &out, params, true, nil
		}
		return stmt, params, false, nil
	case *parser.UpdateStatement:
		out := *s
		if s.TTL != nil {
			expr, ch, err := rewriteCastsInExpr(s.TTL, params, markerIndex, lookup)
			if err != nil {
				return nil, nil, false, err
			}
			if ch {
				out.TTL = expr
				changed = true
			}
		}
		if s.Timestamp != nil {
			expr, ch, err := rewriteCastsInExpr(s.Timestamp, params, markerIndex, lookup)
			if err != nil {
				return nil, nil, false, err
			}
			if ch {
				out.Timestamp = expr
				changed = true
			}
		}
		if len(s.Assigments) > 0 {
			assignments := make([]*parser.Assignment, len(s.Assigments))
			for i, assignment := range s.Assigments {
				if assignment == nil {
					continue
				}
				target, targetChanged, err := rewriteCastsInExpr(assignment.Target, params, markerIndex, lookup)
				if err != nil {
					return nil, nil, false, err
				}
				value, valueChanged, err := rewriteCastsInExpr(assignment.Value, params, markerIndex, lookup)
				if err != nil {
					return nil, nil, false, err
				}
				if targetChanged || valueChanged {
					assignments[i] = &parser.Assignment{
						Target: target,
						Column: assignment.Column,
						Value:  value,
						Op:     assignment.Op,
					}
					changed = true
				} else {
					assignments[i] = assignment
				}
			}
			if changed {
				out.Assigments = assignments
			}
		}
		if s.Where != nil {
			expr, ch, err := rewriteCastsInExpr(s.Where, params, markerIndex, lookup)
			if err != nil {
				return nil, nil, false, err
			}
			if ch {
				out.Where = expr
				changed = true
			}
		}
		if s.IfCondition != nil {
			expr, ch, err := rewriteCastsInExpr(s.IfCondition, params, markerIndex, lookup)
			if err != nil {
				return nil, nil, false, err
			}
			if ch {
				out.IfCondition = expr
				changed = true
			}
		}
		if changed {
			return &out, params, true, nil
		}
		return stmt, params, false, nil
	case *parser.DeleteStatement:
		out := *s
		if len(s.Targets) > 0 {
			targets := make([]parser.Expression, len(s.Targets))
			for i, target := range s.Targets {
				updated, ch, err := rewriteCastsInExpr(target, params, markerIndex, lookup)
				if err != nil {
					return nil, nil, false, err
				}
				if ch {
					changed = true
				}
				targets[i] = updated
			}
			if changed {
				out.Targets = targets
			}
		}
		if s.Where != nil {
			expr, ch, err := rewriteCastsInExpr(s.Where, params, markerIndex, lookup)
			if err != nil {
				return nil, nil, false, err
			}
			if ch {
				out.Where = expr
				changed = true
			}
		}
		if s.IfCondition != nil {
			expr, ch, err := rewriteCastsInExpr(s.IfCondition, params, markerIndex, lookup)
			if err != nil {
				return nil, nil, false, err
			}
			if ch {
				out.IfCondition = expr
				changed = true
			}
		}
		if s.Timestamp != nil {
			expr, ch, err := rewriteCastsInExpr(s.Timestamp, params, markerIndex, lookup)
			if err != nil {
				return nil, nil, false, err
			}
			if ch {
				out.Timestamp = expr
				changed = true
			}
		}
		if changed {
			return &out, params, true, nil
		}
		return stmt, params, false, nil
	default:
		return stmt, params, false, nil
	}
}

func rewriteCastsInExpr(expr parser.Expression, params []interface{}, markerIndex map[parser.Expression]int, lookup func(string) (string, bool)) (parser.Expression, bool, error) {
	if expr == nil {
		return nil, false, nil
	}
	switch e := expr.(type) {
	case *parser.CastExpr:
		inner, innerChanged, err := rewriteCastsInExpr(e.Expr, params, markerIndex, lookup)
		if err != nil {
			return nil, false, err
		}
		if innerChanged {
			e = &parser.CastExpr{Token: e.Token, Expr: inner, Type: e.Type}
		}
		fromType, _ := inferCastSourceType(inner, lookup)
		toType := normalizeCastType(e.Type.TypeString())
		if castSQLSafe(fromType, toType) {
			return e, innerChanged, nil
		}
		if idx, ok := castBindMarkerIndex(inner, markerIndex); ok {
			if idx >= len(params) {
				return nil, false, fmt.Errorf("cast bind marker index out of range")
			}
			if params[idx] != UnsetValue {
				converted, err := castValue(params[idx], fromType, toType)
				if err != nil {
					return nil, false, err
				}
				params[idx] = converted
			}
			return e, innerChanged, nil
		}
		if lit, ok := literalValueFromExpr(inner); ok {
			converted, err := castValue(lit, fromType, toType)
			if err != nil {
				return nil, false, err
			}
			return literalExprForCastValue(converted, toType), true, nil
		}
		return nil, false, fmt.Errorf("CAST requires bind marker or literal for proxy evaluation")
	case *parser.TypeHintExpr:
		inner, changed, err := rewriteCastsInExpr(e.Expr, params, markerIndex, lookup)
		if err != nil {
			return nil, false, err
		}
		if !changed {
			return e, false, nil
		}
		return &parser.TypeHintExpr{Token: e.Token, Expr: inner, Type: e.Type}, true, nil
	case *parser.AliasedExpr:
		inner, changed, err := rewriteCastsInExpr(e.Expr, params, markerIndex, lookup)
		if err != nil {
			return nil, false, err
		}
		if !changed {
			return e, false, nil
		}
		return &parser.AliasedExpr{Token: e.Token, Expr: inner, Alias: e.Alias}, true, nil
	case *parser.IsNullExpr:
		inner, changed, err := rewriteCastsInExpr(e.Expr, params, markerIndex, lookup)
		if err != nil {
			return nil, false, err
		}
		if !changed {
			return e, false, nil
		}
		return &parser.IsNullExpr{Token: e.Token, Expr: inner, Not: e.Not}, true, nil
	case *parser.BinaryExpr:
		left, leftChanged, err := rewriteCastsInExpr(e.Left, params, markerIndex, lookup)
		if err != nil {
			return nil, false, err
		}
		right, rightChanged, err := rewriteCastsInExpr(e.Right, params, markerIndex, lookup)
		if err != nil {
			return nil, false, err
		}
		if !leftChanged && !rightChanged {
			return e, false, nil
		}
		return &parser.BinaryExpr{Token: e.Token, Op: e.Op, Left: left, Right: right}, true, nil
	case *parser.UnaryExpr:
		inner, changed, err := rewriteCastsInExpr(e.Expr, params, markerIndex, lookup)
		if err != nil {
			return nil, false, err
		}
		if !changed {
			return e, false, nil
		}
		return &parser.UnaryExpr{Token: e.Token, Op: e.Op, Expr: inner}, true, nil
	case *parser.FunctionCall:
		if len(e.Args) == 0 {
			return e, false, nil
		}
		args := make([]parser.Expression, len(e.Args))
		changed := false
		for i, arg := range e.Args {
			updated, ch, err := rewriteCastsInExpr(arg, params, markerIndex, lookup)
			if err != nil {
				return nil, false, err
			}
			if ch {
				changed = true
			}
			args[i] = updated
		}
		if !changed {
			return e, false, nil
		}
		return &parser.FunctionCall{Name: e.Name, Args: args}, true, nil
	case *parser.CollectionAccessExpr:
		target, targetChanged, err := rewriteCastsInExpr(e.Target, params, markerIndex, lookup)
		if err != nil {
			return nil, false, err
		}
		start, startChanged, err := rewriteCastsInExpr(e.Start, params, markerIndex, lookup)
		if err != nil {
			return nil, false, err
		}
		end, endChanged, err := rewriteCastsInExpr(e.End, params, markerIndex, lookup)
		if err != nil {
			return nil, false, err
		}
		if !targetChanged && !startChanged && !endChanged {
			return e, false, nil
		}
		return &parser.CollectionAccessExpr{
			Token:  e.Token,
			Target: target,
			Start:  start,
			End:    end,
			Slice:  e.Slice,
		}, true, nil
	case *parser.ListLiteral:
		return rewriteCastsInExprList(e.Token, e.Elements, params, markerIndex, lookup, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.ListLiteral{Token: token, Elements: elems}
		})
	case *parser.VectorLiteral:
		return rewriteCastsInExprList(e.Token, e.Elements, params, markerIndex, lookup, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.VectorLiteral{Token: token, Elements: elems}
		})
	case *parser.SetLiteral:
		return rewriteCastsInExprList(e.Token, e.Elements, params, markerIndex, lookup, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.SetLiteral{Token: token, Elements: elems}
		})
	case *parser.TupleLiteral:
		return rewriteCastsInExprList(e.Token, e.Elements, params, markerIndex, lookup, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.TupleLiteral{Token: token, Elements: elems}
		})
	case *parser.MapLiteral:
		changed := false
		entries := make([]parser.MapEntry, len(e.Entries))
		for i, entry := range e.Entries {
			keyExpr, keyChanged, err := rewriteCastsInExpr(entry.Key, params, markerIndex, lookup)
			if err != nil {
				return nil, false, err
			}
			valExpr, valChanged, err := rewriteCastsInExpr(entry.Value, params, markerIndex, lookup)
			if err != nil {
				return nil, false, err
			}
			if keyChanged || valChanged {
				changed = true
			}
			entries[i] = parser.MapEntry{Key: keyExpr, Value: valExpr}
		}
		if !changed {
			return e, false, nil
		}
		return &parser.MapLiteral{Token: e.Token, Entries: entries}, true, nil
	default:
		return expr, false, nil
	}
}

func rewriteCastsInExprList(token parser.Position, elems []parser.Expression, params []interface{}, markerIndex map[parser.Expression]int, lookup func(string) (string, bool), builder func(parser.Position, []parser.Expression) parser.Expression) (parser.Expression, bool, error) {
	if len(elems) == 0 {
		return builder(token, nil), false, nil
	}
	changed := false
	out := make([]parser.Expression, len(elems))
	for i, elem := range elems {
		updated, ch, err := rewriteCastsInExpr(elem, params, markerIndex, lookup)
		if err != nil {
			return nil, false, err
		}
		if ch {
			changed = true
		}
		out[i] = updated
	}
	if !changed {
		return builder(token, elems), false, nil
	}
	return builder(token, out), true, nil
}

func castBindMarkerIndex(expr parser.Expression, markerIndex map[parser.Expression]int) (int, bool) {
	if expr == nil || markerIndex == nil {
		return -1, false
	}
	switch e := expr.(type) {
	case *parser.BindMarker, *parser.NamedBindMarker:
		idx, ok := markerIndex[expr]
		return idx, ok
	case *parser.TypeHintExpr:
		return castBindMarkerIndex(e.Expr, markerIndex)
	default:
		return -1, false
	}
}

func literalValueFromExpr(expr parser.Expression) (interface{}, bool) {
	if expr == nil {
		return nil, false
	}
	switch e := expr.(type) {
	case *parser.TypeHintExpr:
		return literalValueFromExpr(e.Expr)
	case *parser.UnaryExpr:
		if e.Op == "+" {
			return literalValueFromExpr(e.Expr)
		}
		if e.Op == "-" {
			if value, ok := literalValueFromExpr(e.Expr); ok {
				switch v := value.(type) {
				case int64:
					return -v, true
				case float64:
					return -v, true
				}
			}
		}
	case *parser.IntegerLiteral:
		return e.Value, true
	case *parser.FloatLiteral:
		return e.Value, true
	case *parser.BooleanLiteral:
		return strings.EqualFold(e.Value, "true"), true
	case *parser.StringLiteral:
		return e.Value, true
	case *parser.BlobLiteral:
		return e.Value, true
	case *parser.UUIDLiteral:
		return e.Value, true
	case *parser.NullLiteral:
		return nil, true
	}
	return nil, false
}

func literalExprForCastValue(value interface{}, toType string) parser.Expression {
	if value == nil {
		return &parser.NullLiteral{}
	}
	switch normalizeCastType(toType) {
	case "tinyint", "smallint", "int", "bigint":
		if v, err := castToInt64(value); err == nil {
			return &parser.IntegerLiteral{Value: v}
		}
	case "float", "double":
		if v, err := castToFloat64(value); err == nil {
			return &parser.FloatLiteral{Value: v}
		}
	case "timestamp":
		return &parser.StringLiteral{Value: formatTimestampValue(value)}
	case "date":
		return &parser.StringLiteral{Value: formatDateValue(value)}
	case "time":
		return &parser.StringLiteral{Value: formatTimeValue(value)}
	case "uuid", "timeuuid", "text":
		return &parser.StringLiteral{Value: fmt.Sprint(value)}
	}
	return literalExprFromValue(value)
}
