//! Virtual Table API
//!
//! Provides the foundation for creating custom SQLite virtual tables in Zig.
//! Virtual tables allow you to expose any data source (files, APIs, computed data)
//! as if it were a regular SQL table.
//!
//! This is an advanced feature - most users should use the higher-level helpers
//! like FTS5, JSON, or R-Tree modules instead.

const std = @import("std");
const c = @import("c.zig").c;
const Database = @import("database.zig").Database;
const errors = @import("errors.zig");

const Error = errors.Error;
const resultToError = errors.resultToError;

/// Index constraint operators.
pub const ConstraintOp = enum(u8) {
    eq = 2, // SQLITE_INDEX_CONSTRAINT_EQ
    gt = 4, // SQLITE_INDEX_CONSTRAINT_GT
    le = 8, // SQLITE_INDEX_CONSTRAINT_LE
    lt = 16, // SQLITE_INDEX_CONSTRAINT_LT
    ge = 32, // SQLITE_INDEX_CONSTRAINT_GE
    match = 64, // SQLITE_INDEX_CONSTRAINT_MATCH
    like = 65, // SQLITE_INDEX_CONSTRAINT_LIKE
    glob = 66, // SQLITE_INDEX_CONSTRAINT_GLOB
    regexp = 67, // SQLITE_INDEX_CONSTRAINT_REGEXP
    ne = 68, // SQLITE_INDEX_CONSTRAINT_NE
    isnot = 69, // SQLITE_INDEX_CONSTRAINT_ISNOT
    isnotnull = 70, // SQLITE_INDEX_CONSTRAINT_ISNOTNULL
    isnull = 71, // SQLITE_INDEX_CONSTRAINT_ISNULL
    is = 72, // SQLITE_INDEX_CONSTRAINT_IS
    limit = 73, // SQLITE_INDEX_CONSTRAINT_LIMIT
    offset = 74, // SQLITE_INDEX_CONSTRAINT_OFFSET
    function = 150, // SQLITE_INDEX_CONSTRAINT_FUNCTION
};

/// Constraint information for query planning.
pub const IndexConstraint = struct {
    column: i32,
    op: ConstraintOp,
    usable: bool,
};

/// Order by information for query planning.
pub const IndexOrderBy = struct {
    column: i32,
    desc: bool,
};

/// Index info passed to xBestIndex.
pub const IndexInfo = struct {
    constraints: []IndexConstraint,
    order_by: []IndexOrderBy,
    // Output fields set by xBestIndex
    idx_num: i32 = 0,
    idx_str: ?[]const u8 = null,
    order_by_consumed: bool = false,
    estimated_cost: f64 = 1000000.0,
    estimated_rows: i64 = 1000000,
};

/// Column type declaration for virtual table schema.
pub const ColumnDef = struct {
    name: []const u8,
    col_type: []const u8,
    constraints: ?[]const u8 = null,
};

/// Virtual table module definition.
/// Implement this interface to create a custom virtual table.
pub fn VTableModule(comptime Context: type, comptime Cursor: type) type {
    return struct {
        /// Called when the virtual table is created.
        /// Should return the column definitions for CREATE TABLE.
        create_fn: *const fn (db: *Database, args: []const []const u8) anyerror!Context,

        /// Called when the virtual table is connected (opened).
        connect_fn: *const fn (db: *Database, args: []const []const u8) anyerror!Context,

        /// Called to query the best index for a query.
        best_index_fn: *const fn (ctx: *Context, info: *IndexInfo) anyerror!void,

        /// Called when the virtual table is destroyed.
        destroy_fn: *const fn (ctx: *Context) void,

        /// Called to open a cursor for scanning.
        open_fn: *const fn (ctx: *Context) anyerror!Cursor,

        /// Called to close a cursor.
        close_fn: *const fn (cursor: *Cursor) void,

        /// Called to start a query with the chosen index.
        filter_fn: *const fn (cursor: *Cursor, idx_num: i32, idx_str: ?[]const u8, args: []const ?*c.sqlite3_value) anyerror!void,

        /// Called to advance to the next row.
        next_fn: *const fn (cursor: *Cursor) anyerror!void,

        /// Called to check if we've reached end of data.
        eof_fn: *const fn (cursor: *Cursor) bool,

        /// Called to get a column value.
        column_fn: *const fn (cursor: *Cursor, col_idx: i32, result_ctx: *c.sqlite3_context) anyerror!void,

        /// Called to get the rowid.
        rowid_fn: *const fn (cursor: *Cursor) anyerror!i64,

        /// Optional: Called to update a row.
        update_fn: ?*const fn (ctx: *Context, argc: i32, argv: [*]?*c.sqlite3_value, rowid: *i64) anyerror!void = null,

        /// Returns the schema for this virtual table.
        schema_fn: *const fn (ctx: *Context) []const ColumnDef,
    };
}

/// Simple read-only virtual table helper.
/// Easier to use than the full VTableModule for simple cases.
pub fn SimpleVTable(comptime Row: type) type {
    return struct {
        data: []const Row,
        columns: []const ColumnDef,

        const Self = @This();

        pub fn init(data: []const Row, columns: []const ColumnDef) Self {
            return .{
                .data = data,
                .columns = columns,
            };
        }

        /// Cursor for iterating over rows.
        pub const Cursor = struct {
            table: *const Self,
            current_row: usize,

            pub fn next(self: *Cursor) void {
                self.current_row += 1;
            }

            pub fn eof(self: *const Cursor) bool {
                return self.current_row >= self.table.data.len;
            }

            pub fn currentRow(self: *const Cursor) ?Row {
                if (self.eof()) return null;
                return self.table.data[self.current_row];
            }

            pub fn rowid(self: *const Cursor) i64 {
                return @intCast(self.current_row + 1);
            }
        };

        pub fn openCursor(self: *const Self) Cursor {
            return .{
                .table = self,
                .current_row = 0,
            };
        }
    };
}

/// Generates CREATE TABLE statement for a virtual table schema.
pub fn generateSchema(allocator: std.mem.Allocator, table_name: []const u8, columns: []const ColumnDef) ![]u8 {
    var sql = std.ArrayList(u8).init(allocator);
    errdefer sql.deinit();

    try sql.appendSlice("CREATE TABLE ");
    try sql.appendSlice(table_name);
    try sql.appendSlice("(");

    for (columns, 0..) |col, i| {
        if (i > 0) try sql.appendSlice(", ");
        try sql.appendSlice(col.name);
        try sql.append(' ');
        try sql.appendSlice(col.col_type);
        if (col.constraints) |cons| {
            try sql.append(' ');
            try sql.appendSlice(cons);
        }
    }

    try sql.appendSlice(")");
    return sql.toOwnedSlice();
}

/// Helper to set a result value in a sqlite3_context.
pub const ResultHelper = struct {
    ctx: *c.sqlite3_context,

    const Self = @This();

    pub fn init(ctx: *c.sqlite3_context) Self {
        return .{ .ctx = ctx };
    }

    pub fn setNull(self: Self) void {
        c.sqlite3_result_null(self.ctx);
    }

    pub fn setInt(self: Self, value: i64) void {
        c.sqlite3_result_int64(self.ctx, value);
    }

    pub fn setFloat(self: Self, value: f64) void {
        c.sqlite3_result_double(self.ctx, value);
    }

    pub fn setText(self: Self, value: []const u8) void {
        c.sqlite3_result_text(self.ctx, value.ptr, @intCast(value.len), c.SQLITE_TRANSIENT);
    }

    pub fn setBlob(self: Self, value: []const u8) void {
        c.sqlite3_result_blob(self.ctx, value.ptr, @intCast(value.len), c.SQLITE_TRANSIENT);
    }

    pub fn setError(self: Self, msg: []const u8) void {
        c.sqlite3_result_error(self.ctx, msg.ptr, @intCast(msg.len));
    }

    pub fn setErrorCode(self: Self, code: c_int) void {
        c.sqlite3_result_error_code(self.ctx, code);
    }
};

/// Helper to read values from sqlite3_value.
pub const ValueHelper = struct {
    value: *c.sqlite3_value,

    const Self = @This();

    pub fn init(value: *c.sqlite3_value) Self {
        return .{ .value = value };
    }

    pub fn getType(self: Self) c_int {
        return c.sqlite3_value_type(self.value);
    }

    pub fn isNull(self: Self) bool {
        return self.getType() == c.SQLITE_NULL;
    }

    pub fn getInt(self: Self) i64 {
        return c.sqlite3_value_int64(self.value);
    }

    pub fn getFloat(self: Self) f64 {
        return c.sqlite3_value_double(self.value);
    }

    pub fn getText(self: Self) ?[]const u8 {
        const len = c.sqlite3_value_bytes(self.value);
        const text = c.sqlite3_value_text(self.value);
        if (text) |t| {
            return t[0..@intCast(len)];
        }
        return null;
    }

    pub fn getBlob(self: Self) ?[]const u8 {
        const len = c.sqlite3_value_bytes(self.value);
        const blob = c.sqlite3_value_blob(self.value);
        if (blob) |b| {
            const ptr: [*]const u8 = @ptrCast(b);
            return ptr[0..@intCast(len)];
        }
        return null;
    }
};

/// Eponymous virtual table - appears automatically without CREATE VIRTUAL TABLE.
/// Useful for table-valued functions.
pub const EponymousModule = struct {
    name: []const u8,
    num_args: i32,

    /// Generates SQL to query an eponymous module.
    pub fn query(self: *const EponymousModule, allocator: std.mem.Allocator, args: []const []const u8) ![]u8 {
        var sql = std.ArrayList(u8).init(allocator);
        errdefer sql.deinit();

        try sql.appendSlice("SELECT * FROM ");
        try sql.appendSlice(self.name);
        try sql.append('(');

        for (args, 0..) |arg, i| {
            if (i > 0) try sql.appendSlice(", ");
            try sql.append('\'');
            try sql.appendSlice(arg);
            try sql.append('\'');
        }

        try sql.append(')');
        return sql.toOwnedSlice();
    }
};

// Note: Full virtual table registration requires complex C callback trampolines
// similar to what we do for UDFs. The helpers above provide the building blocks
// but the actual registration would need additional C interop code.
//
// For most use cases, the built-in virtual tables (FTS5, R-Tree, JSON) are
// sufficient. Custom virtual tables are an advanced feature that few applications
// need.

/// Common virtual table modules available in SQLite.
pub const BuiltinModules = struct {
    /// FTS5 full-text search
    pub const fts5 = "fts5";
    /// R-Tree spatial index
    pub const rtree = "rtree";
    /// CSV virtual table (if compiled with)
    pub const csv = "csv";
    /// Generate series (built-in table-valued function)
    pub const generate_series = "generate_series";
    /// PRAGMA function as table
    pub const pragma = "pragma";
};
