// Package orm implements the Odoo ORM in Go.
// Mirrors: odoo/orm/models.py, odoo/orm/fields.py
package orm

import (
	"fmt"
	"sync"
	"time"
)

// FieldType mirrors Odoo's field type system.
// See: odoo/orm/fields.py
type FieldType int

const (
	TypeChar FieldType = iota
	TypeText
	TypeHTML
	TypeInteger
	TypeFloat
	TypeMonetary
	TypeBoolean
	TypeDate
	TypeDatetime
	TypeBinary
	TypeSelection
	TypeJson
	TypeMany2one
	TypeOne2many
	TypeMany2many
	TypeReference
	TypeProperties
)

func (ft FieldType) String() string {
	names := [...]string{
		"char", "text", "html", "integer", "float", "monetary",
		"boolean", "date", "datetime", "binary", "selection",
		"json", "many2one", "one2many", "many2many", "reference", "properties",
	}
	if int(ft) < len(names) {
		return names[ft]
	}
	return fmt.Sprintf("unknown(%d)", ft)
}

// SQLType returns the PostgreSQL column type for this field type.
// Mirrors: odoo/orm/fields.py column_type property
func (ft FieldType) SQLType() string {
	switch ft {
	case TypeChar:
		return "varchar"
	case TypeText, TypeHTML:
		return "text"
	case TypeInteger:
		return "int4"
	case TypeFloat:
		return "numeric"
	case TypeMonetary:
		return "numeric"
	case TypeBoolean:
		return "bool"
	case TypeDate:
		return "date"
	case TypeDatetime:
		return "timestamp without time zone"
	case TypeBinary:
		return "bytea"
	case TypeSelection:
		return "varchar"
	case TypeJson, TypeProperties:
		return "jsonb"
	case TypeMany2one:
		return "int4"
	case TypeReference:
		return "varchar"
	case TypeOne2many, TypeMany2many:
		return "" // no column, computed
	default:
		return ""
	}
}

// IsRelational returns true for relational field types.
func (ft FieldType) IsRelational() bool {
	return ft == TypeMany2one || ft == TypeOne2many || ft == TypeMany2many
}

// IsStored returns true if this field type has a database column.
func (ft FieldType) IsStored() bool {
	return ft != TypeOne2many && ft != TypeMany2many
}

// ModelType mirrors Odoo's model categories.
// See: odoo/orm/models.py BaseModel._auto
type ModelType int

const (
	// ModelRegular corresponds to odoo.models.Model (_auto=True, _abstract=False)
	ModelRegular ModelType = iota
	// ModelTransient corresponds to odoo.models.TransientModel (_transient=True)
	ModelTransient
	// ModelAbstract corresponds to odoo.models.AbstractModel (_auto=False, _abstract=True)
	ModelAbstract
)

// OnDelete mirrors Odoo's ondelete parameter for Many2one fields.
// See: odoo/orm/fields_relational.py Many2one.ondelete
type OnDelete string

const (
	OnDeleteSetNull  OnDelete = "set null"
	OnDeleteRestrict OnDelete = "restrict"
	OnDeleteCascade  OnDelete = "cascade"
)

// Value represents any value that can be stored in or read from a field.
// Mirrors Odoo's dynamic typing for field values.
type Value interface{}

// Values is a map of field names to values, used for create/write operations.
// Mirrors Odoo's vals dict passed to create() and write().
type Values = map[string]interface{}

// SelectionItem represents one option in a Selection field.
// Mirrors: odoo/orm/fields.py Selection.selection items
type SelectionItem struct {
	Value string
	Label string
}

// NullTime wraps time.Time to handle NULL dates from PostgreSQL.
type NullTime struct {
	Time  time.Time
	Valid bool
}

// NullInt wraps int64 to handle NULL integers (e.g., Many2one FK).
type NullInt struct {
	Int64 int64
	Valid bool
}

// NullString wraps string to handle NULL text fields.
type NullString struct {
	String string
	Valid  bool
}

// Registry is the global model registry.
// Mirrors: odoo/orm/registry.py Registry
// Holds all registered models, keyed by model name (e.g., "res.partner").
var Registry = &ModelRegistry{
	models: make(map[string]*Model),
}

// ModelRegistry manages all model definitions.
// Thread-safe for concurrent module loading.
type ModelRegistry struct {
	mu     sync.RWMutex
	models map[string]*Model
	loaded bool
}

// Get returns a model by name, or nil if not found.
func (r *ModelRegistry) Get(name string) *Model {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.models[name]
}

// MustGet returns a model by name, panics if not found.
func (r *ModelRegistry) MustGet(name string) *Model {
	m := r.Get(name)
	if m == nil {
		panic(fmt.Sprintf("orm: model %q not registered", name))
	}
	return m
}

// Register adds a model to the registry.
func (r *ModelRegistry) Register(m *Model) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.models[m.name] = m
}

// All returns all registered model names.
func (r *ModelRegistry) All() []string {
	r.mu.RLock()
	defer r.mu.RUnlock()
	names := make([]string, 0, len(r.models))
	for name := range r.models {
		names = append(names, name)
	}
	return names
}

// Models returns all registered models.
func (r *ModelRegistry) Models() map[string]*Model {
	r.mu.RLock()
	defer r.mu.RUnlock()
	// Return a copy to avoid race conditions
	result := make(map[string]*Model, len(r.models))
	for k, v := range r.models {
		result[k] = v
	}
	return result
}
