// Package tinysql provides a lightweight, embeddable SQL database for Go applications.
//
// TinySQL is an educational SQL database that demonstrates core database concepts including:
//   - SQL parsing and execution (DDL, DML, SELECT with joins, aggregates, CTEs)
//   - Multi-Version Concurrency Control (MVCC) with snapshot isolation
//   - Write-Ahead Logging (WAL) for durability and crash recovery
//   - Multi-tenancy support for isolated data namespaces
//   - In-memory and persistent storage with GOB serialization
//
// # Basic Usage
//
// Create a database, execute SQL, and query results:
//
//	db := tinysql.NewDB()
//	ctx := context.Background()
//
//	// Parse and execute DDL
//	stmt, _ := tinysql.ParseSQL("CREATE TABLE users (id INT, name TEXT)")
//	tinysql.Execute(ctx, db, "default", stmt)
//
//	// Insert data
//	stmt, _ = tinysql.ParseSQL("INSERT INTO users VALUES (1, 'Alice')")
//	tinysql.Execute(ctx, db, "default", stmt)
//
//	// Query data
//	stmt, _ = tinysql.ParseSQL("SELECT * FROM users WHERE id = 1")
//	rs, _ := tinysql.Execute(ctx, db, "default", stmt)
//	for _, row := range rs.Rows {
//	    fmt.Println(row)
//	}
//
// # Persistence
//
// Save and load database snapshots:
//
//	// Save to file
//	tinysql.SaveToFile(db, "mydb.gob")
//
//	// Load from file
//	db, err := tinysql.LoadFromFile("mydb.gob")
//
// # Advanced Features
//
// Enable MVCC for concurrent transactions:
//
//	mvcc := db.MVCC()
//	tx, _ := mvcc.BeginTx(tinysql.SnapshotIsolation)
//	// ... perform transaction operations ...
//	mvcc.CommitTx(tx.ID)
//
// Enable WAL for durability:
//
//	wal, _ := tinysql.NewAdvancedWAL("data/wal.log")
//	db.AttachAdvancedWAL(wal)
//
// # Query Compilation
//
// Pre-compile queries for better performance:
//
//	cache := tinysql.NewQueryCache(100)
//	query, _ := cache.Compile("SELECT * FROM users WHERE id = ?")
//	rs, _ := query.Execute(ctx, db, "default")
//
// For more examples, see the example_test.go file in the repository.
package tinysql

import (
	"context"
	"fmt"
	"io"
	"strings"

	"github.com/SimonWaldherr/tinySQL/internal/engine"
	"github.com/SimonWaldherr/tinySQL/internal/importer"
	"github.com/SimonWaldherr/tinySQL/internal/storage"
	"github.com/SimonWaldherr/tinySQL/standards"
)

// ============================================================================
// Core Types - Re-exported from internal packages for public API
// ============================================================================

// DB represents a multi-tenant database instance with support for MVCC and WAL.
// Use NewDB to create a new instance.
type DB = storage.DB

// Table represents a database table with columns and rows.
// Tables are created via CREATE TABLE statements and accessed through the DB.
type Table = storage.Table

// Column represents a table column with a name and type.
type Column = storage.Column

// ColType enumerates supported column data types (INT, TEXT, BOOL, JSON, etc.).
type ColType = storage.ColType

// ConstraintType enumerates supported column constraints.
type ConstraintType = storage.ConstraintType

// ColumnStats and TableStats are the persisted results of an ANALYZE command.
type ColumnStats = storage.ColumnStats
type TableStats = storage.TableStats

// Row represents a single result row mapped by column name (case-insensitive).
// Keys include both qualified (table.column) and unqualified (column) names.
type Row = engine.Row

// ResultSet holds query results with column names and data rows.
// Returned by SELECT queries and available for inspection.
type ResultSet = engine.ResultSet

// VectorCacheConfig configures the optional process-wide VEC_SEARCH result
// cache and its opt-in analytics ring buffer.
type VectorCacheConfig = engine.VectorCacheConfig

// VectorCacheStats reports bounded result-cache state and, when enabled, the
// recent query window. It is a snapshot and safe to serialize for monitoring.
type VectorCacheStats = engine.VectorCacheStats

// VectorQueryEvent describes one recent VEC_SEARCH shape without exposing its
// raw vector contents.
type VectorQueryEvent = engine.VectorQueryEvent

// ConfigureVectorCache replaces the process-wide VEC_SEARCH result-cache and
// analytics configuration. A zero ResultCacheEntries disables result caching.
func ConfigureVectorCache(cfg VectorCacheConfig) { engine.ConfigureVectorCache(cfg) }

// VectorCacheAnalytics returns a point-in-time cache and optional query-window
// snapshot. It does not expose raw query-vector contents.
func VectorCacheAnalytics() VectorCacheStats { return engine.VectorCacheAnalytics() }

// DefaultVectorCacheConfig returns bounded cache/analytics defaults with both
// optional features disabled until explicitly enabled by the caller.
func DefaultVectorCacheConfig() VectorCacheConfig { return engine.DefaultVectorCacheConfig() }

// ProcedureContext is passed to in-memory stored procedures registered with
// RegisterStoredProcedure. It can execute nested SQL within the same CALL.
type ProcedureContext = engine.ProcedureContext

// StoredProcedureFunc is the Go handler signature for in-memory stored
// procedures invoked with CALL name(...).
type StoredProcedureFunc = engine.StoredProcedureFunc

// StoredProcedureInfo describes one registered in-memory stored procedure.
type StoredProcedureInfo = engine.StoredProcedureInfo

// SQLStateError attaches an ISO/IEC 9075 SQLSTATE code to an error.
type SQLStateError = standards.SQLStateError

// Permission enumerates RBAC operations that can be granted to roles.
type Permission = storage.Permission

// Grant authorizes a Permission on a schema/table pair, with "*" as wildcard.
type Grant = storage.Grant

// CatalogRole is a named RBAC role and its grants.
type CatalogRole = storage.CatalogRole

// CatalogUser is an RBAC user principal. PasswordHash is opaque.
type CatalogUser = storage.CatalogUser

// CatalogManager stores schema, RBAC, function, trigger, view, and job metadata.
type CatalogManager = storage.CatalogManager

// CatalogTable describes a table registered in the catalog.
type CatalogTable = storage.CatalogTable

// CatalogColumn describes a table column registered in the catalog.
type CatalogColumn = storage.CatalogColumn

// CatalogView describes a logical SQL view.
type CatalogView = storage.CatalogView

// CatalogMaterializedView describes a cached materialized view.
type CatalogMaterializedView = storage.CatalogMaterializedView

// CatalogDependency records a dependency between catalog objects.
type CatalogDependency = storage.CatalogDependency

// CatalogFunction describes a scalar, aggregate, or table function.
type CatalogFunction = storage.CatalogFunction

// CatalogTrigger describes a trigger registered in the catalog.
type CatalogTrigger = storage.CatalogTrigger

// Statement is the base interface for all parsed SQL statements.
// Use Parser.ParseStatement() to obtain a Statement from SQL text.
type Statement = engine.Statement

// Parser parses SQL text into executable Statement objects.
// Create with NewParser and call ParseStatement() to parse.
type Parser = engine.Parser

// QueryCache stores compiled queries for reuse, similar to prepared statements.
// Create with NewQueryCache and use Compile() to cache queries.
type QueryCache = engine.QueryCache

// CompiledQuery represents a pre-parsed SQL statement that can be executed
// multiple times efficiently.
type CompiledQuery = engine.CompiledQuery

const (
	SQLStateSuccessfulCompletion = standards.SQLStateSuccessfulCompletion
	SQLStateWarning              = standards.SQLStateWarning
	SQLStateNoData               = standards.SQLStateNoData
	SQLStateDataException        = standards.SQLStateDataException
	SQLStateInvalidDatetime      = standards.SQLStateInvalidDatetime
	SQLStateInvalidParameter     = standards.SQLStateInvalidParameter
	SQLStateSyntaxError          = standards.SQLStateSyntaxError
	SQLStateUndefinedTable       = standards.SQLStateUndefinedTable
	SQLStateIntegrityViolation   = standards.SQLStateIntegrityViolation
	SQLStateInvalidAuthorization = standards.SQLStateInvalidAuthorization
	SQLStateTransactionRollback  = standards.SQLStateTransactionRollback
	SQLStateInternalError        = standards.SQLStateInternalError
)

const (
	NoConstraint ConstraintType = storage.NoConstraint
	PrimaryKey   ConstraintType = storage.PrimaryKey
	Unique       ConstraintType = storage.Unique
)

const (
	PermSelect Permission = storage.PermSelect
	PermInsert Permission = storage.PermInsert
	PermUpdate Permission = storage.PermUpdate
	PermDelete Permission = storage.PermDelete
	PermDDL    Permission = storage.PermDDL
	PermAll    Permission = storage.PermAll
)

// JobExecutor executes SQL text for scheduled jobs.
type JobExecutor = storage.JobExecutor

// Scheduler manages scheduled job execution.
type Scheduler = storage.Scheduler

// CatalogJob describes a scheduled job.
type CatalogJob = storage.CatalogJob

// CatalogJobHistory records a scheduled job run.
type CatalogJobHistory = storage.CatalogJobHistory

// DBHealth describes database lifecycle, storage, scheduler, and recovery state.
type DBHealth = storage.DBHealth

// RecoveryStatus describes the last recovery pass performed while opening a DB.
type RecoveryStatus = storage.RecoveryStatus

// ============================================================================
// MVCC Types - Transaction management and isolation
// ============================================================================

// MVCCManager coordinates multi-version concurrency control with snapshot isolation.
// Provides transaction begin/commit/abort operations and visibility checking.
type MVCCManager = storage.MVCCManager

// TxID represents a unique transaction identifier.
type TxID = storage.TxID

// TxContext holds the state of an active transaction including read/write sets
// and isolation level.
type TxContext = storage.TxContext

// TxStatus represents the current state of a transaction.
type TxStatus = storage.TxStatus

// Timestamp represents a logical timestamp for MVCC visibility checks.
type Timestamp = storage.Timestamp

// IsolationLevel defines transaction isolation semantics.
type IsolationLevel = storage.IsolationLevel

// Transaction status constants
const (
	TxStatusInProgress = storage.TxStatusInProgress // Transaction is active
	TxStatusCommitted  = storage.TxStatusCommitted  // Transaction committed successfully
	TxStatusAborted    = storage.TxStatusAborted    // Transaction was aborted
)

// Isolation level constants
const (
	ReadCommitted     IsolationLevel = storage.ReadCommitted     // Read only committed data
	RepeatableRead    IsolationLevel = storage.RepeatableRead    // Repeatable reads within transaction
	SnapshotIsolation IsolationLevel = storage.SnapshotIsolation // Full snapshot isolation
	Serializable      IsolationLevel = storage.Serializable      // Serializable transactions with conflict detection
)

// ============================================================================
// WAL Types - Write-Ahead Logging for durability
// ============================================================================

// AdvancedWAL manages row-level write-ahead logging with REDO/UNDO support.
// Provides durability, crash recovery, and point-in-time recovery.
type AdvancedWAL = storage.AdvancedWAL

// AdvancedWALConfig configures the advanced WAL behavior.
type AdvancedWALConfig = storage.AdvancedWALConfig

// LSN (Log Sequence Number) provides total ordering of log records.
type LSN = storage.LSN

// WALRecord represents a single log entry with before/after images.
type WALRecord = storage.WALRecord

// WALOperationType defines the type of WAL operation.
type WALOperationType = storage.WALOperationType

// WAL operation type constants
const (
	WALOpBegin      = storage.WALOpBegin      // Transaction begin
	WALOpInsert     = storage.WALOpInsert     // Row insert
	WALOpUpdate     = storage.WALOpUpdate     // Row update
	WALOpDelete     = storage.WALOpDelete     // Row delete
	WALOpCommit     = storage.WALOpCommit     // Transaction commit
	WALOpAbort      = storage.WALOpAbort      // Transaction abort
	WALOpCheckpoint = storage.WALOpCheckpoint // Checkpoint operation
)

// ============================================================================
// Audit Log - tamper-evident, hash-chained record of every executed statement
// ============================================================================

// AuditLog is an append-only, hash-chained log of statement executions. See
// OpenAuditLog/NewAuditLog and DB.AttachAuditLog.
type AuditLog = storage.AuditLog

// AuditEntry is one recorded statement execution, including the hash chain
// fields (PrevHash/Hash) that make tampering detectable via AuditLog.Verify.
type AuditEntry = storage.AuditEntry

// NewAuditLog creates an in-memory-only audit log (not persisted to disk).
func NewAuditLog() *AuditLog {
	return storage.NewAuditLog()
}

// OpenAuditLog opens (creating if needed) a file-backed, append-only audit
// log at path. If the file already contains entries, they're replayed and
// the hash chain is verified as part of opening — a tampered or corrupted
// existing log is rejected immediately rather than silently trusted.
//
// Example:
//
//	auditLog, err := tinysql.OpenAuditLog("data/audit.jsonl")
//	if err != nil {
//	    log.Fatal(err) // includes "tampered with" detail if verification failed
//	}
//	defer auditLog.Close()
//	db.AttachAuditLog(auditLog)
//	...
//	ctx = tinysql.WithAuditText(tinysql.WithUser(ctx, "alice"), sqlText)
//	tinysql.Execute(ctx, db, "default", stmt)
func OpenAuditLog(path string) (*AuditLog, error) {
	return storage.OpenAuditLog(path)
}

// WithAuditText attaches the original SQL text of the statement about to
// run to ctx, so an attached audit log records the real query instead of a
// best-effort statement-type fallback. See OpenAuditLog's example.
func WithAuditText(ctx context.Context, sqlText string) context.Context {
	return engine.WithAuditText(ctx, sqlText)
}

// ============================================================================
// Storage Modes - Pluggable persistence strategies
// ============================================================================

// StorageMode defines how the database manages data between memory and disk.
// Different modes trade off speed, memory usage, and durability.
type StorageMode = storage.StorageMode

// StorageConfig configures database storage behaviour. Pass to OpenDB to
// create a database with the desired persistence strategy.
type StorageConfig = storage.StorageConfig

// StorageBackend is the interface implemented by all storage backends.
type StorageBackend = storage.StorageBackend

// BackendStats provides observability into storage backend behaviour.
type BackendStats = storage.BackendStats

// ExtensionCapability describes a resource category a statically linked Go
// extension declares it may need. Declarations are visible through
// sys.extensions; capability enforcement is not implemented yet.
type ExtensionCapability = storage.ExtensionCapability

const (
	CapabilityFilesystem = storage.CapabilityFilesystem
	CapabilityNetwork    = storage.CapabilityNetwork
	CapabilityWrite      = storage.CapabilityWrite
	CapabilitySecrets    = storage.CapabilitySecrets
)

// ExtensionInfo describes a Go extension active for one DB instance.
type ExtensionInfo = storage.ExtensionInfo

// Extension is implemented by statically linked Go extension packages. Import
// an extension package in the embedding application and activate it with
// db.Use(extension). Dynamic Go shared-object plugins are intentionally not
// supported because they are not portable across tinySQL's Go, TinyGo, and
// WebAssembly targets.
type Extension = storage.Extension

// ============================================================================
// Encryption at rest - AES-256-GCM for disk-backed table files
// ============================================================================

// EncryptionKeySize is the required key length, in bytes, for
// StorageConfig.EncryptionKey and NewEncryptor (AES-256).
const EncryptionKeySize = storage.EncryptionKeySize

// EncryptionSaltSize is the recommended salt length, in bytes, for
// DeriveKeyFromPassphrase.
const EncryptionSaltSize = storage.EncryptionSaltSize

// Encryptor performs authenticated AES-256-GCM encryption/decryption.
// StorageConfig.EncryptionKey is the usual way to enable it for disk-backed
// table files; construct one directly only if you need to encrypt/decrypt
// something outside the storage backend's own file I/O.
type Encryptor = storage.Encryptor

// NewEncryptor creates an Encryptor from an EncryptionKeySize-byte key.
func NewEncryptor(key []byte) (*Encryptor, error) {
	return storage.NewEncryptor(key)
}

// DeriveKeyFromPassphrase derives an AES-256 key from a human-chosen
// passphrase using Argon2id (memory-hard, resists large-scale offline
// guessing better than a plain PBKDF2/SHA hash). salt must be a fixed,
// persisted value (see NewEncryptionSalt) — the same passphrase and salt
// always derive the same key, but a different salt derives a different
// key from the same passphrase.
//
// Example:
//
//	salt, _ := tinysql.NewEncryptionSalt()   // generate once, persist it yourself
//	key := tinysql.DeriveKeyFromPassphrase("correct horse battery staple", salt)
//	db, err := tinysql.OpenDB(tinysql.StorageConfig{
//	    Mode: tinysql.ModeDisk, Path: "./data", EncryptionKey: key,
//	})
func DeriveKeyFromPassphrase(passphrase string, salt []byte) []byte {
	return storage.DeriveKeyFromPassphrase(passphrase, salt)
}

// NewEncryptionSalt returns a fresh, cryptographically random salt for
// DeriveKeyFromPassphrase.
func NewEncryptionSalt() ([]byte, error) {
	return storage.NewEncryptionSalt()
}

// Storage mode constants.
const (
	// ModeMemory keeps all data in RAM. Persistence only occurs via explicit
	// SaveToFile calls or when DB.Close is invoked. Fastest mode.
	ModeMemory StorageMode = storage.ModeMemory

	// ModeWAL keeps all data in RAM and writes a Write-Ahead Log for crash
	// recovery. Periodic checkpoints create full GOB snapshots.
	ModeWAL StorageMode = storage.ModeWAL

	// ModeDisk stores each table as a separate GOB file on disk. Tables are
	// loaded into memory on demand and flushed back on Sync/Close.
	ModeDisk StorageMode = storage.ModeDisk

	// ModeJSON stores each table as a separate human-readable JSON file on
	// disk — same lazy-load/dirty-tracking behavior as ModeDisk, which uses
	// GOB instead. Larger on disk and big.Rat/uuid.UUID values round-trip as
	// plain strings, but files can be read, diffed, or hand-edited with any
	// text tool.
	ModeJSON StorageMode = storage.ModeJSON

	// ModeIndex keeps table schemas in RAM while row data resides on disk.
	// Rows are loaded on demand with aggressive eviction. Memory usage is
	// proportional to schema size, not data size.
	ModeIndex StorageMode = storage.ModeIndex

	// ModeHybrid uses an LRU buffer pool with a configurable memory limit.
	// Hot tables stay in RAM; cold tables spill to disk.
	ModeHybrid StorageMode = storage.ModeHybrid
)

// ============================================================================
// Column Type Constants - Supported data types
// ============================================================================

const (
	// IntType represents a generic integer column type.
	IntType ColType = storage.IntType
	// Int8Type represents an 8-bit signed integer column type.
	Int8Type ColType = storage.Int8Type
	// Int16Type represents a 16-bit signed integer column type.
	Int16Type ColType = storage.Int16Type
	// Int32Type represents a 32-bit signed integer column type.
	Int32Type ColType = storage.Int32Type
	// Int64Type represents a 64-bit signed integer column type.
	Int64Type ColType = storage.Int64Type
	// UintType represents an unsigned integer column type.
	UintType ColType = storage.UintType
	// Uint8Type represents an 8-bit unsigned integer column type.
	Uint8Type ColType = storage.Uint8Type

	// Float32Type represents a 32-bit floating point column type.
	Float32Type ColType = storage.Float32Type
	// Float64Type represents a 64-bit floating point column type.
	Float64Type ColType = storage.Float64Type
	// FloatType is an alias for Float64Type.
	FloatType ColType = storage.FloatType

	// StringType represents a short string/text column.
	StringType ColType = storage.StringType
	// TextType represents a longer textual column.
	TextType ColType = storage.TextType

	// BoolType represents a boolean column (true/false).
	BoolType ColType = storage.BoolType

	// TimeType represents a time-only column type.
	TimeType ColType = storage.TimeType
	// DateType represents a date-only column type.
	DateType ColType = storage.DateType
	// DateTimeType represents a combined date and time column.
	DateTimeType ColType = storage.DateTimeType
	// TimestampType represents a timestamp column type.
	TimestampType ColType = storage.TimestampType

	// JsonType represents a JSON textual column.
	JsonType ColType = storage.JsonType
	// JsonbType represents a binary JSON column (if supported by backend).
	JsonbType ColType = storage.JsonbType
	// GeometryType represents spatial geometry values (GeoJSON/WKB).
	GeometryType ColType = storage.GeometryType
	// Decimal and money types
	DecimalType  ColType = storage.DecimalType
	MoneyType    ColType = storage.MoneyType
	UUIDType     ColType = storage.UUIDType
	BlobType     ColType = storage.BlobType
	XMLType      ColType = storage.XMLType
	IntervalType ColType = storage.IntervalType
)

// ============================================================================
// Database Creation and Management
// ============================================================================

// NewDB creates a new in-memory multi-tenant database instance with MVCC support.
//
// The database starts empty with no tables. Use SQL DDL statements to create tables,
// or load from a file with LoadFromFile.
//
// Example:
//
//	db := tinysql.NewDB()
//	defer db.Close() // Optional cleanup
//
// The returned DB is safe for concurrent use and includes an integrated MVCC manager.
func NewDB() *DB {
	return storage.NewDB()
}

// OpenDB creates or opens a database with the specified storage mode and
// configuration. This is the primary entry point for creating databases that
// persist data to disk using different strategies.
//
// Storage Mode Overview:
//
//	ModeMemory  – All data in RAM. Fast. Save on Close if Path is set.
//	ModeWAL     – All data in RAM + Write-Ahead Log for crash recovery.
//	ModeDisk    – Per-table files on disk, lazy-loaded into RAM.
//	ModeIndex   – Schemas in RAM, rows on disk with small LRU cache.
//	ModeHybrid  – Disk-backed with configurable LRU memory limit.
//
// Examples:
//
//	// In-memory with save-on-close
//	db, _ := tinysql.OpenDB(tinysql.StorageConfig{
//	    Mode: tinysql.ModeMemory,
//	    Path: "mydb.gob",
//	})
//	defer db.Close()
//
//	// Disk mode for large databases
//	db, _ := tinysql.OpenDB(tinysql.StorageConfig{
//	    Mode: tinysql.ModeDisk,
//	    Path: "/data/mydb",
//	})
//	defer db.Close()
//
//	// Hybrid mode with 512 MB cache
//	db, _ := tinysql.OpenDB(tinysql.StorageConfig{
//	    Mode:           tinysql.ModeHybrid,
//	    Path:           "/data/mydb",
//	    MaxMemoryBytes: 512 * 1024 * 1024,
//	})
//	defer db.Close()
//
// Always call db.Close() to ensure data is flushed to disk.
func OpenDB(cfg StorageConfig) (*DB, error) {
	return storage.OpenDB(cfg)
}

// ParseStorageMode converts a string like "memory", "wal", "disk", "index",
// or "hybrid" into a StorageMode constant (case-insensitive).
func ParseStorageMode(s string) (StorageMode, error) {
	return storage.ParseStorageMode(s)
}

// DefaultStorageConfig returns a StorageConfig with sensible defaults for
// the given mode. The caller should set Path before passing to OpenDB.
func DefaultStorageConfig(mode StorageMode) StorageConfig {
	return storage.DefaultStorageConfig(mode)
}

// ============================================================================
// SQL Parsing
// ============================================================================

// NewParser creates a new SQL parser for the provided input string.
//
// The parser supports a practical subset of SQL including:
//   - DDL: CREATE TABLE, DROP TABLE
//   - DML: INSERT, UPDATE, DELETE
//   - Queries: SELECT with WHERE, JOIN, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET
//   - Set operations: UNION, INTERSECT, EXCEPT
//   - CTEs: WITH clauses
//   - Expressions: arithmetic, comparisons, functions, aggregates
//
// Example:
//
//	parser := tinysql.NewParser("SELECT * FROM users WHERE active = true")
//	stmt, err := parser.ParseStatement()
//	if err != nil {
//	    log.Fatal(err)
//	}
//
// For better performance with repeated queries, consider using QueryCache.
func NewParser(sql string) *Parser {
	return engine.NewParser(sql)
}

// ParseSQL is a convenience function that creates a parser and parses a SQL statement
// in one call. Equivalent to NewParser(sql).ParseStatement().
//
// Example:
//
//	stmt, err := tinysql.ParseSQL("SELECT id, name FROM users")
//	if err != nil {
//	    log.Fatal(err)
//	}
//
// Returns the parsed Statement or an error if parsing fails.
func ParseSQL(sql string) (Statement, error) {
	return NewParser(sql).ParseStatement()
}

// MustParseSQL is like ParseSQL but panics if parsing fails.
// Useful for static SQL in tests or initialization code.
//
// Example:
//
//	stmt := tinysql.MustParseSQL("CREATE TABLE users (id INT, name TEXT)")
func MustParseSQL(sql string) Statement {
	stmt, err := ParseSQL(sql)
	if err != nil {
		panic(err)
	}
	return stmt
}

// WithSQLState wraps err with a SQLSTATE code. It is useful for integrations
// that expose tinySQL errors over SQL drivers, HTTP APIs, or observability
// pipelines where stable machine-readable error classes matter.
func WithSQLState(state string, err error) error {
	return standards.WithSQLState(state, err)
}

// SQLState returns the SQLSTATE attached to err, if present.
func SQLState(err error) string {
	return standards.SQLState(err)
}

// ============================================================================
// Query Compilation and Caching
// ============================================================================

// NewQueryCache creates a new query cache for compiling and reusing parsed queries.
//
// Query compilation parses SQL once and reuses the AST for multiple executions,
// similar to prepared statements. This improves performance for repeated queries.
//
// The maxSize parameter limits the number of cached queries (LRU eviction).
// Use 0 for unlimited cache size (not recommended for production).
//
// Example:
//
//	cache := tinysql.NewQueryCache(100)
//	query, _ := cache.Compile("SELECT * FROM users WHERE id = ?")
//
//	// Execute multiple times
//	for _, id := range userIDs {
//	    rs, _ := query.Execute(ctx, db, "default")
//	    // process results...
//	}
func NewQueryCache(maxSize int) *QueryCache {
	return engine.NewQueryCache(maxSize)
}

// Compile parses and caches a SQL query for reuse, similar to regexp.Compile.
//
// The compiled query can be executed multiple times without re-parsing.
// This is useful for queries executed repeatedly with different parameters.
//
// Example:
//
//	cache := tinysql.NewQueryCache(100)
//	query, err := tinysql.Compile(cache, "SELECT * FROM users WHERE active = true")
//	if err != nil {
//	    log.Fatal(err)
//	}
//
//	// Execute the compiled query
//	rs, _ := query.Execute(ctx, db, "default")
//
// Returns a CompiledQuery that can be executed via ExecuteCompiled or query.Execute.
func Compile(cache *QueryCache, sql string) (*CompiledQuery, error) {
	return cache.Compile(sql)
}

// MustCompile is like Compile but panics on error, similar to regexp.MustCompile.
//
// Useful for static queries in tests or initialization where errors are unexpected.
//
// Example:
//
//	cache := tinysql.NewQueryCache(100)
//	query := tinysql.MustCompile(cache, "SELECT * FROM users")
func MustCompile(cache *QueryCache, sql string) *CompiledQuery {
	return cache.MustCompile(sql)
}

// ============================================================================
// SQL Execution
// ============================================================================

// Execute executes a parsed SQL statement against the database.
//
// The context allows for cancellation and timeout control. The tenant parameter
// provides data isolation - each tenant has its own namespace of tables.
//
// For DDL statements (CREATE TABLE, DROP TABLE), returns nil ResultSet.
// For DML statements (INSERT, UPDATE, DELETE), returns nil ResultSet.
// For SELECT queries, returns ResultSet with columns and rows.
//
// Example:
//
//	ctx := context.Background()
//	stmt, _ := tinysql.ParseSQL("SELECT * FROM users WHERE age > 18")
//	rs, err := tinysql.Execute(ctx, db, "default", stmt)
//	if err != nil {
//	    log.Fatal(err)
//	}
//
//	// Process results
//	for _, row := range rs.Rows {
//	    name, _ := tinysql.GetVal(row, "name")
//	    fmt.Println(name)
//	}
//
// The tenant parameter is required. Use "default" for single-tenant applications.
func Execute(ctx context.Context, db *DB, tenant string, stmt Statement) (*ResultSet, error) {
	return engine.Execute(ctx, db, tenant, stmt)
}

// ExecSQL parses and executes exactly one SQL statement. It is the concise
// public entry point for dynamic SQL, scripts with one statement per call, and
// small embedded applications. For repeated SQL, prefer Compile or database/sql
// prepared statements so parsing and planning can be reused.
//
// The tenant is required; use "default" for single-tenant applications.
func ExecSQL(ctx context.Context, db *DB, tenant, sql string) (*ResultSet, error) {
	stmt, err := ParseSQL(sql)
	if err != nil {
		return nil, err
	}
	return Execute(ctx, db, tenant, stmt)
}

// WithUser returns a context carrying the acting username for RBAC
// permission checks (see CREATE USER/CREATE ROLE/GRANT below). Pass the
// result to Execute/ExecuteCompiled in place of a plain context.
//
// RBAC is opt-in: until the first CREATE USER statement runs against a
// database, every Execute call is permitted regardless of context — so
// existing code that never touches users/roles sees no behavior change.
// Once a user exists, every subsequent Execute call requires one via
// WithUser, or it's rejected with an "access denied" error.
//
// Example:
//
//	tinysql.Execute(ctx, db, "default", mustParse(`CREATE ROLE admin_role`))
//	tinysql.Execute(ctx, db, "default", mustParse(`GRANT ALL ON * TO ROLE admin_role`))
//	tinysql.Execute(ctx, db, "default", mustParse(`CREATE USER admin WITH PASSWORD 'x' ROLE admin_role`))
//	// RBAC is now active; every further call needs an authenticated context:
//	adminCtx := tinysql.WithUser(ctx, "admin")
//	tinysql.Execute(adminCtx, db, "default", stmt)
func WithUser(ctx context.Context, username string) context.Context {
	return engine.WithUser(ctx, username)
}

// UserFromContext returns the username set by WithUser, if any.
func UserFromContext(ctx context.Context) (string, bool) {
	return engine.UserFromContext(ctx)
}

// ParsePermission validates and normalizes an RBAC permission keyword.
func ParsePermission(s string) (Permission, error) {
	return storage.ParsePermission(s)
}

// ExecuteCompiled executes a pre-compiled query against the database.
//
// This is more efficient than Execute for queries executed repeatedly,
// as parsing is done only once during compilation.
//
// Example:
//
//	cache := tinysql.NewQueryCache(100)
//	query, _ := cache.Compile("SELECT * FROM users")
//	rs, err := tinysql.ExecuteCompiled(ctx, db, "default", query)
//
// Returns ResultSet for SELECT queries, nil for DDL/DML statements.
func ExecuteCompiled(ctx context.Context, db *DB, tenant string, compiled *CompiledQuery) (*ResultSet, error) {
	return compiled.Execute(ctx, db, tenant)
}

// SQLJobExecutor executes scheduled job SQL against a tinySQL database tenant.
type SQLJobExecutor struct {
	DB     *DB
	Tenant string
}

// NewSQLJobExecutor creates a scheduler executor backed by tinySQL itself.
func NewSQLJobExecutor(db *DB, tenant string) *SQLJobExecutor {
	if tenant == "" {
		tenant = "default"
	}
	return &SQLJobExecutor{DB: db, Tenant: tenant}
}

// ExecuteSQL parses and executes one SQL statement for the scheduler.
func (e *SQLJobExecutor) ExecuteSQL(ctx context.Context, sql string) (interface{}, error) {
	if e == nil || e.DB == nil {
		return nil, fmt.Errorf("nil SQL job executor")
	}
	stmt, err := ParseSQL(sql)
	if err != nil {
		return nil, err
	}
	return Execute(ctx, e.DB, e.Tenant, stmt)
}

// StartJobScheduler starts the database job scheduler for a tenant.
func StartJobScheduler(db *DB, tenant string) error {
	if db == nil {
		return fmt.Errorf("nil DB")
	}
	return db.StartJobScheduler(NewSQLJobExecutor(db, tenant))
}

// RestartJobScheduler restarts the database job scheduler for a tenant.
func RestartJobScheduler(db *DB, tenant string) error {
	if db == nil {
		return fmt.Errorf("nil DB")
	}
	return db.RestartJobScheduler(NewSQLJobExecutor(db, tenant))
}

// StopJobScheduler stops the database job scheduler.
func StopJobScheduler(db *DB) {
	if db == nil {
		return
	}
	db.StopJobScheduler()
}

// HealthCheck returns a production-oriented database health snapshot.
func HealthCheck(db *DB) DBHealth {
	if db == nil {
		return DBHealth{OK: false, Error: "nil DB"}
	}
	return db.HealthCheck()
}

// ============================================================================
// Result Access Helpers
// ============================================================================

// GetVal retrieves a value from a result row by column name (case-insensitive).
//
// Returns the value and true if the column exists, or nil and false otherwise.
// This is the recommended way to access row data as it handles case-insensitivity.
//
// Example:
//
//	for _, row := range rs.Rows {
//	    id, ok := tinysql.GetVal(row, "id")
//	    if ok {
//	        fmt.Printf("ID: %v\n", id)
//	    }
//
//	    name, _ := tinysql.GetVal(row, "Name") // Case-insensitive
//	    fmt.Printf("Name: %v\n", name)
//	}
//
// For type-safe access, use type assertion after retrieving the value.
func GetVal(row Row, name string) (any, bool) {
	v, ok := row[strings.ToLower(name)]
	return v, ok
}

// ============================================================================
// Persistence - GOB Serialization
// ============================================================================

// SaveToFile serializes the entire database to a GOB file for persistence.
//
// This creates a snapshot of all tables, rows, and metadata for all tenants.
// The file can be loaded later with LoadFromFile to restore the database state.
//
// Example:
//
//	err := tinysql.SaveToFile(db, "mydb.gob")
//	if err != nil {
//	    log.Fatal(err)
//	}
//
// Note: This saves the current state only. For durability during crashes,
// use AttachAdvancedWAL to enable write-ahead logging.
func SaveToFile(db *DB, filename string) error {
	return storage.SaveToFile(db, filename)
}

// SaveToWriter serializes a consistent database snapshot to w. It is useful
// for embedded targets, HTTP responses, and callers that own their storage.
func SaveToWriter(db *DB, w io.Writer) error {
	return storage.SaveToWriter(db, w)
}

// LoadFromFile deserializes a database from a GOB file created by SaveToFile.
//
// This restores all tables, rows, and metadata from the file. The returned
// database instance is ready for use immediately.
//
// Example:
//
//	db, err := tinysql.LoadFromFile("mydb.gob")
//	if err != nil {
//	    log.Fatal(err)
//	}
//	defer db.Close()
//
// Returns a new DB instance or an error if the file cannot be read.
func LoadFromFile(filename string) (*DB, error) {
	return storage.LoadFromFile(filename)
}

// LoadFromReader restores a database snapshot from r without requiring a
// temporary file. Callers should bound untrusted input before passing it here.
func LoadFromReader(r io.Reader) (*DB, error) {
	return storage.LoadFromReader(r)
}

// SaveToBytes serializes the entire database to an in-memory GOB snapshot.
//
// This is useful for embedding targets such as WebAssembly where writing to a
// filesystem is unavailable or expensive. The returned bytes can be restored
// with LoadFromBytes.
func SaveToBytes(db *DB) ([]byte, error) {
	return storage.SaveToBytes(db)
}

// LoadFromBytes deserializes a database from a GOB snapshot created by
// SaveToBytes.
func LoadFromBytes(snapshot []byte) (*DB, error) {
	return storage.LoadFromBytes(snapshot)
}

// ============================================================================
// Advanced WAL - Write-Ahead Logging
// ============================================================================

// NewAdvancedWAL creates a new write-ahead log manager with default configuration.
//
// The WAL logs all database modifications (INSERT, UPDATE, DELETE) to disk before
// applying them. This enables:
//   - Crash recovery: replay committed transactions after restart
//   - Point-in-time recovery: restore to any previous state
//   - Durability: changes survive system crashes
//
// Example:
//
//	wal, err := tinysql.NewAdvancedWAL("data/wal.log")
//	if err != nil {
//	    log.Fatal(err)
//	}
//	defer wal.Close()
//
//	db.AttachAdvancedWAL(wal)
//
//	// Now all database modifications are logged
//	stmt, _ := tinysql.ParseSQL("INSERT INTO users VALUES (1, 'Alice')")
//	tinysql.Execute(ctx, db, "default", stmt)
//
// The path parameter specifies the WAL file location.
func NewAdvancedWAL(path string) (*AdvancedWAL, error) {
	return storage.OpenAdvancedWAL(storage.AdvancedWALConfig{
		Path:               path,
		CheckpointPath:     path + ".checkpoint",
		CheckpointEvery:    1000,
		CheckpointInterval: 300000000000, // 5 minutes in nanoseconds
		BufferSize:         65536,        // 64KB
	})
}

// OpenAdvancedWAL creates or opens a WAL with custom configuration.
//
// This provides full control over WAL behavior including checkpoint
// intervals and buffer sizes. Checkpoint-snapshot compression is configured
// separately, via StorageConfig.CompressFiles when opening through OpenDB
// with Mode: ModeAdvancedWAL — the live WAL log itself is never compressed.
//
// Example:
//
//	wal, err := tinysql.OpenAdvancedWAL(tinysql.AdvancedWALConfig{
//	    Path:               "data/wal.log",
//	    CheckpointPath:     "data/checkpoint",
//	    CheckpointEvery:    5000,
//	    CheckpointInterval: 10 * time.Minute,
//	    BufferSize:         128 * 1024,
//	})
//
// Returns the WAL instance or an error if initialization fails.
func OpenAdvancedWAL(config AdvancedWALConfig) (*AdvancedWAL, error) {
	return storage.OpenAdvancedWAL(config)
}

// ============================================================================
// Table Operations - Direct table access
// ============================================================================

// NewTable creates a new table with the specified columns.
//
// This is a low-level API. Normally tables are created via CREATE TABLE statements.
// Use this when programmatically building table structures.
//
// Example:
//
//	cols := []tinysql.Column{
//	    {Name: "id", Type: tinysql.IntType},
//	    {Name: "name", Type: tinysql.TextType},
//	}
//	table := tinysql.NewTable("users", cols, false)
//	db.Put("default", table)
//
// The isTemp parameter indicates if this is a temporary table (not persisted).
func NewTable(name string, cols []Column, isTemp bool) *Table {
	return storage.NewTable(name, cols, isTemp)
}

// ============================================================================
// File Import - Auto-import structured data files
// ============================================================================

// ImportOptions re-exports importer.ImportOptions for convenience.
// Configure import behavior including type inference, batching, and null handling.
type ImportOptions = importer.ImportOptions

// ImportResult re-exports importer.ImportResult for convenience.
// Contains metadata about the import operation.
type ImportResult = importer.ImportResult

// ImportFile imports a structured data file into a table.
// The format is auto-detected from the file extension or content.
//
// Supported formats:
//   - CSV (.csv) - Comma-separated values with auto-detected delimiters
//   - TSV (.tsv, .tab) - Tab-separated values
//   - JSON (.json) - Array of objects format: [{"id": 1, "name": "Alice"}, ...]
//   - YAML (.yaml, .yml) - Sequence of mappings or a single mapping
//   - XML (.xml) - Simple row-based XML (limited support)
//   - GeoJSON (.geojson), KML (.kml), Shapefile (.shp)
//   - Shapefile ZIP archives (.zip)
//   - OSM XML (.osm, .osm.xml)
//   - MBTiles (.mbtiles)
//   - Routing graph (.rg, .routinggraph)
//   - Compressed (.gz) - Transparent gzip decompression
//
// Example:
//
//	db := tinysql.NewDB()
//	result, err := tinysql.ImportFile(ctx, db, "default", "users", "data.csv", &tinysql.ImportOptions{
//	    CreateTable: true,
//	    TypeInference: true,
//	    BatchSize: 1000,
//	})
//	fmt.Printf("Imported %d rows\n", result.RowsInserted)
//
// Parameters:
//   - ctx: Context for cancellation
//   - db: Target database instance
//   - tenant: Tenant/schema name (use "default" for single-tenant mode)
//   - tableName: Target table name (if empty, derived from filename)
//   - filePath: Path to the file to import
//   - opts: Optional configuration (nil uses sensible defaults)
//
// Returns ImportResult with metadata and any error encountered.
func ImportFile(ctx context.Context, db *DB, tenant, tableName, filePath string, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportFile(ctx, db, tenant, tableName, filePath, opts)
}

// ImportCSV imports CSV/TSV data from a reader into a table.
// Use this for streaming imports or when you already have an io.Reader.
//
// Example:
//
//	f, _ := os.Open("data.csv")
//	defer f.Close()
//	result, err := tinysql.ImportCSV(ctx, db, "default", "users", f, &tinysql.ImportOptions{
//	    HeaderMode: "auto",
//	    DelimiterCandidates: []rune{',', ';', '\t'},
//	})
//
// Parameters:
//   - ctx: Context for cancellation
//   - db: Target database instance
//   - tenant: Tenant/schema name
//   - tableName: Target table name
//   - src: Input reader (file, network stream, stdin, etc.)
//   - opts: Optional configuration (nil uses defaults)
//
// Returns ImportResult with metadata and any error encountered.
func ImportCSV(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportCSV(ctx, db, tenant, tableName, src, opts)
}

// ImportJSON imports JSON data from a reader into a table.
// Supports array of objects format: [{"id": 1, "name": "Alice"}, ...]
//
// Example:
//
//	jsonData := `[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]`
//	result, err := tinysql.ImportJSON(ctx, db, "default", "users",
//	    strings.NewReader(jsonData), nil)
//
// Parameters:
//   - ctx: Context for cancellation
//   - db: Target database instance
//   - tenant: Tenant/schema name
//   - tableName: Target table name
//   - src: Input reader
//   - opts: Optional configuration
//
// Returns ImportResult with metadata and any error encountered.
func ImportJSON(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportJSON(ctx, db, tenant, tableName, src, opts)
}

// ImportYAML imports YAML data from a reader into a table.
func ImportYAML(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportYAML(ctx, db, tenant, tableName, src, opts)
}

// ImportXML imports row-oriented XML data from a reader into a table.
func ImportXML(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportXML(ctx, db, tenant, tableName, src, opts)
}

// ImportGeoJSON imports GeoJSON FeatureCollection or Feature data from a reader.
func ImportGeoJSON(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportGeoJSON(ctx, db, tenant, tableName, src, opts)
}

// ImportKML imports KML Placemark data from a reader.
func ImportKML(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportKML(ctx, db, tenant, tableName, src, opts)
}

// ImportShapefile imports an ESRI Shapefile path and its sidecar DBF attributes.
// It requires a build with -tags=shapefile.
func ImportShapefile(ctx context.Context, db *DB, tenant, tableName, filePath string, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportShapefile(ctx, db, tenant, tableName, filePath, opts)
}

// ImportShapefileZip imports a ZIP archive containing Shapefile sidecar files from a reader.
// It requires a build with -tags=shapefile.
func ImportShapefileZip(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportShapefileZip(ctx, db, tenant, tableName, src, opts)
}

// ImportOSM imports OSM XML (.osm or .osm.xml) from a reader.
func ImportOSM(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportOSM(ctx, db, tenant, tableName, src, opts)
}

// ImportMBTiles imports tiles from an MBTiles SQLite database path. It requires
// a build with -tags=sqliteimport; SQLite remains the recommended production
// backend for standard MBTiles serving.
func ImportMBTiles(ctx context.Context, db *DB, tenant, tableName, filePath string, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportMBTiles(ctx, db, tenant, tableName, filePath, opts)
}

// ImportMBTilesReader imports MBTiles from a reader by spooling to a temporary
// SQLite file. It requires a build with -tags=sqliteimport.
func ImportMBTilesReader(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportMBTilesReader(ctx, db, tenant, tableName, src, opts)
}

// MBTiles artifact compatibility API. Artifacts are portable, read-only,
// atomically published paged-index directories intended for large MBTiles
// datasets. New integrations should use github.com/SimonWaldherr/tinySQL/tiles:
// it exposes typed TMS keys and an interface-based reader without leaking a
// concrete internal implementation.
//
// Deprecated: use package tiles for new artifact integrations.
type MBTilesArtifactSchema = importer.MBTilesArtifactSchema
type MBTilesArtifactOptions = importer.MBTilesArtifactOptions
type MBTilesProgress = importer.MBTilesProgress
type MBTilesResourceEstimate = importer.MBTilesResourceEstimate
type MBTilesArtifactTable = importer.MBTilesArtifactTable
type MBTilesArtifactIndex = importer.MBTilesArtifactIndex
type MBTilesArtifactManifest = importer.MBTilesArtifactManifest
type MBTilesArtifactResult = importer.MBTilesArtifactResult
type MBTilesReader = importer.MBTilesReader

const (
	MBTilesSchemaAuto       = importer.MBTilesSchemaAuto
	MBTilesSchemaFlat       = importer.MBTilesSchemaFlat
	MBTilesSchemaNormalized = importer.MBTilesSchemaNormalized
)

// Deprecated: use tiles.ImportMBTiles.
func ImportMBTilesArtifact(ctx context.Context, sourcePath, artifactPath string, opts *MBTilesArtifactOptions) (*MBTilesArtifactResult, error) {
	return importer.ImportMBTilesArtifact(ctx, sourcePath, artifactPath, opts)
}

// Deprecated: use tiles.ValidateArtifact.
func ValidateMBTilesArtifact(ctx context.Context, artifactPath string) (*MBTilesArtifactManifest, error) {
	return importer.ValidateMBTilesArtifact(ctx, artifactPath)
}

// Deprecated: use tiles.OpenArtifact. This function exposes a generic DB for
// compatibility and should not be used as a tile-serving hot path.
func OpenMBTilesArtifact(ctx context.Context, artifactPath string, maxMemoryBytes int64) (*DB, *MBTilesArtifactManifest, error) {
	return importer.OpenMBTilesArtifact(ctx, artifactPath, maxMemoryBytes)
}

// Deprecated: use tiles.OpenArtifact.
func OpenMBTilesReader(ctx context.Context, artifactPath string, maxMemoryBytes int64) (*MBTilesReader, error) {
	return importer.OpenMBTilesReader(ctx, artifactPath, maxMemoryBytes)
}

// ImportRoutingGraph imports routing graph JSON or CSV edge-list data from a reader.
func ImportRoutingGraph(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *ImportOptions) (*ImportResult, error) {
	return importer.ImportRoutingGraph(ctx, db, tenant, tableName, src, opts)
}

// OpenFile opens a data file and returns a DB with the data loaded.
// This is a convenience function for quick data exploration.
//
// Example:
//
//	db, tableName, _ := tinysql.OpenFile(context.Background(), "data.csv", nil)
//	stmt, _ := tinysql.ParseSQL(fmt.Sprintf("SELECT * FROM %s LIMIT 10", tableName))
//	rs, _ := tinysql.Execute(ctx, db, "default", stmt)
//	for _, row := range rs.Rows {
//	    fmt.Println(row)
//	}
//
// Parameters:
//   - ctx: Context for cancellation
//   - filePath: Path to the data file
//   - opts: Optional import configuration
//
// Returns:
//   - db: New database instance with imported data
//   - tableName: The table name where data was loaded
//   - error: Any error encountered during import
func OpenFile(ctx context.Context, filePath string, opts *ImportOptions) (*DB, string, error) {
	return importer.OpenFile(ctx, filePath, opts)
}

// ============================================================================
// Fuzzy Import - Tolerant parsing for malformed data
// ============================================================================

// FuzzyImportOptions extends ImportOptions with fuzzy parsing capabilities.
// Use this for importing data that may have formatting issues, inconsistent
// delimiters, malformed quotes, or other common data quality problems.
type FuzzyImportOptions = importer.FuzzyImportOptions

// FuzzyImportCSV is a more forgiving version of ImportCSV that handles malformed data.
// It attempts to automatically fix common issues like:
//   - Inconsistent column counts (pads/truncates rows)
//   - Unmatched quotes in CSV fields
//   - Mixed delimiters within the same file
//   - Invalid UTF-8 characters
//   - Numbers with thousand separators
//   - Mixed data types in columns
//
// Example:
//
//	opts := &tinysql.FuzzyImportOptions{
//	    ImportOptions: &tinysql.ImportOptions{
//	        CreateTable: true,
//	        TypeInference: true,
//	    },
//	    SkipInvalidRows: true,
//	    FixQuotes: true,
//	    CoerceTypes: true,
//	}
//	result, err := tinysql.FuzzyImportCSV(ctx, db, "default", "messy_data", file, opts)
//
// Parameters are the same as ImportCSV.
// Returns ImportResult which includes errors encountered (non-fatal in fuzzy mode).
func FuzzyImportCSV(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *FuzzyImportOptions) (*ImportResult, error) {
	return importer.FuzzyImportCSV(ctx, db, tenant, tableName, src, opts)
}

// FuzzyImportJSON attempts to parse malformed JSON data.
// It handles common JSON issues like:
//   - Single quotes instead of double quotes
//   - Unquoted object keys
//   - Line-delimited JSON (NDJSON format)
//   - Trailing commas
//
// Example:
//
//	// Works even with malformed JSON like {'name': 'Alice', 'age': 30}
//	result, err := tinysql.FuzzyImportJSON(ctx, db, "default", "users", file, nil)
//
// Parameters are the same as ImportJSON.
func FuzzyImportJSON(ctx context.Context, db *DB, tenant, tableName string, src io.Reader, opts *FuzzyImportOptions) (*ImportResult, error) {
	return importer.FuzzyImportJSON(ctx, db, tenant, tableName, src, opts)
}

// ============================================================================
// External Table-Valued Functions
// ============================================================================

// ExternalTableFunc is a simplified interface for registering table-valued
// functions from outside the tinySQL core. Unlike the internal TableFunction
// interface, it receives pre-evaluated argument values (string, int64,
// float64, bool, or nil) rather than AST expression nodes.
//
// This interface is the intended extension point for domain-specific layers
// (such as FSQL) that need to expose custom data sources as SQL tables.
//
// Example:
//
//	type MyFunc struct{}
//
//	func (f *MyFunc) Name() string { return "my_func" }
//	func (f *MyFunc) ValidateArgCount(n int) error {
//	    if n != 1 { return fmt.Errorf("my_func expects 1 argument") }
//	    return nil
//	}
//	func (f *MyFunc) Execute(ctx context.Context, args []any) (*tinysql.ResultSet, error) {
//	    // build and return ResultSet...
//	}
//
//	tinysql.RegisterExternalTableFunc(&MyFunc{})
type ExternalTableFunc interface {
	// Name returns the SQL function name (case-insensitive).
	Name() string

	// ValidateArgCount checks whether the number of arguments is acceptable.
	ValidateArgCount(n int) error

	// Execute evaluates the function with pre-evaluated argument values and
	// returns a ResultSet. The args slice contains one entry per SQL argument,
	// with values converted to Go native types (string, int64, float64, bool,
	// or nil).
	Execute(ctx context.Context, args []any) (*ResultSet, error)
}

// RegisterExternalTableFunc registers an ExternalTableFunc so it can be used
// in SQL FROM clauses and JOIN expressions. The function name is
// case-insensitive. Re-registering the same name replaces the previous
// registration.
func RegisterExternalTableFunc(fn ExternalTableFunc) {
	engine.RegisterExternalTableFunc(fn)
}

// RegisterStoredProcedure registers a process-local in-memory stored procedure.
// The procedure can be invoked from SQL with CALL name(arg1, arg2, ...).
// Registrations are not persisted in database snapshots.
func RegisterStoredProcedure(name string, fn StoredProcedureFunc) error {
	return engine.RegisterStoredProcedure(name, fn)
}

// UnregisterStoredProcedure removes a process-local stored procedure.
func UnregisterStoredProcedure(name string) bool {
	return engine.UnregisterStoredProcedure(name)
}

// ListStoredProcedures returns registered in-memory stored procedures sorted by name.
func ListStoredProcedures() []StoredProcedureInfo {
	return engine.ListStoredProcedures()
}
