#ifndef NODE_SQLITE3_SRC_STATEMENT_H
#define NODE_SQLITE3_SRC_STATEMENT_H

#include <cstdlib>
#include <cstring>
#include <string>
#include <queue>
#include <vector>
#include <sqlite3.h>
#include <napi.h>
#include <uv.h>

#include "convert.h"
#include "database.h"
#include "threading.h"

using namespace Napi;

namespace node_sqlite3 {



class Statement : public Napi::ObjectWrap<Statement> {
public:
    // Row shape requested from the synchronous read paths. SYNC_ROW_OBJECT
    // is the historical default (plain objects, result-column order,
    // last-duplicate-wins); SYNC_ROW_ARRAY is the bulk-reader opt-in
    // (`{ rowMode: 'array' }`), which skips the per-cell property stores
    // entirely — napi_set_element on a pre-sized array has no shape to
    // transition, which is what makes it the fastest row we can build.
    // SYNC_ROW_PLUCK (`{ rowMode: 'pluck' }`) serves only the first result
    // column — better-sqlite3's pluck(), as a per-call option.
    enum SyncRowMode {
        SYNC_ROW_OBJECT = 0,
        SYNC_ROW_ARRAY = 1,
        SYNC_ROW_PLUCK = 2,
    };

    static Napi::Object Init(Napi::Env env, Napi::Object exports);
    static Napi::Value New(const Napi::CallbackInfo& info);

    // Cross-class argument validation (e.g. "is this a Statement?"),
    // against this env's constructor — see Database::HasInstanceIn.
    static inline bool HasInstance(Napi::Value val) {
        return Database::HasInstanceIn(val,
            &Database::AddonData::statement_ctor);
    }

    friend class Database;

    // Runs the finalize work of an original finalize baton (directly, or
    // via the deferred wrappers below).
    struct Baton;
    static void FinishFinalizeBaton(Baton* baton);

    struct Baton {
        napi_async_work request = NULL;
        Statement* stmt;
        Napi::FunctionReference callback;
        Parameters parameters;
        // True when the call site passed a bind argument at all (possibly
        // an empty array/object). False means "re-step with the previous
        // bindings", which must skip the parameter-count check.
        bool bind_supplied = false;

        Baton(Statement* stmt_, Napi::Function cb_) : stmt(stmt_) {
            stmt->Ref();
            callback.Reset(cb_, 1);
        }

        // Delivers `error` to whichever callback settles this call, used
        // by CleanQueue when the statement is torn down with work still
        // queued. Returns false when the call has no callback to settle,
        // so the caller can fall back to an 'error' event. For most calls
        // that callback is `callback`; each() overrides this, because
        // there `callback` is the per-row callback and firing it would
        // hand the caller a phantom row.
        virtual bool Fail(Napi::Value error) {
            Napi::Function cb = callback.Value();
            if (cb.IsEmpty() || !cb.IsFunction()) return false;
            Napi::Value argv[] = { error };
            // A throwing callback still counts as settled: it ran.
            TRY_CATCH_CALL(stmt->Value(), cb, 1, argv, true);
            return true;
        }

        virtual ~Baton() {
            parameters.clear();
            if (request) napi_delete_async_work(stmt->Env(), request);
            stmt->Unref();
            callback.Reset();
        }
    };

    struct RowBaton : Baton {
        RowBaton(Statement* stmt_, Napi::Function cb_) :
            Baton(stmt_, cb_) {}
        Row row;
        Columns columns;
        // Row shape requested through a trailing `{ rowMode: ... }` options
        // bag on the asynchronous read paths (Phase 1 ergonomics parity):
        // the same SYNC_ROW_* values the synchronous paths use.
        int row_mode = SYNC_ROW_OBJECT;
        virtual ~RowBaton() override = default;
    };

    struct RunBaton : Baton {
        RunBaton(Statement* stmt_, Napi::Function cb_) :
            Baton(stmt_, cb_), inserted_id(0), changes(0) {}
        sqlite3_int64 inserted_id;
        int changes;
        virtual ~RunBaton() override = default;
    };

    struct RowsBaton : Baton {
        RowsBaton(Statement* stmt_, Napi::Function cb_) :
            Baton(stmt_, cb_) {}
        Rows rows;
        Columns columns;
        // See RowBaton::row_mode.
        int row_mode = SYNC_ROW_OBJECT;
        virtual ~RowsBaton() override = default;
    };

    // fetch(count, ...): like all(), but steps at most `count` rows so a
    // pull-based iterator can apply backpressure. The statement is not
    // reset between fetches; the cursor keeps its position.
    struct FetchBaton : RowsBaton {
        FetchBaton(Statement* stmt_, Napi::Function cb_) :
            RowsBaton(stmt_, cb_), count(1), done(false) {}
        int count;
        // True when sqlite3_step returned SQLITE_DONE: the cursor is
        // exhausted and only a rebind can produce more rows.
        bool done;
        virtual ~FetchBaton() override = default;
    };

    struct Async;

    struct EachBaton : Baton {
        Napi::FunctionReference completed;
        Async* async; // Isn't deleted when the baton is deleted.
        // See RowBaton::row_mode (published into the Async watcher).
        int row_mode = SYNC_ROW_OBJECT;

        EachBaton(Statement* stmt_, Napi::Function cb_) :
            Baton(stmt_, cb_) {}

        // When each() was given a completion handler that handler settles
        // the call, and takes (err, rowCount). Without one the per-row
        // callback is the only thing the caller passed, so the error goes
        // there instead — never to both, and never a row-shaped call with
        // no error.
        virtual bool Fail(Napi::Value error) override {
            Napi::Function done = completed.Value();
            if (!done.IsEmpty() && done.IsFunction()) {
                Napi::Value argv[] = {
                    error, Napi::Number::New(error.Env(), 0)
                };
                TRY_CATCH_CALL(stmt->Value(), done, 2, argv, true);
                return true;
            }
            return Baton::Fail(error);
        }

        virtual ~EachBaton() override {
            completed.Reset();
        }
    };

    struct PrepareBaton : Database::Baton {
        Statement* stmt;
        std::string sql;
        PrepareBaton(Database* db_, Napi::Function cb_, Statement* stmt_) :
            Baton(db_, cb_), stmt(stmt_) {
            stmt->Ref();
        }
        virtual ~PrepareBaton() override {
            stmt->Unref();
            if (db->IsClosed()) {
                // The database handle was closed before the statement could be
                // prepared.
                stmt->Finalize_();
            }
        }
    };

    typedef void (*Work_Callback)(Baton* baton);

    struct Call {
        Call(Work_Callback cb_, Baton* baton_) : callback(cb_), baton(baton_) {};
        Work_Callback callback;
        Baton* baton;
    };

    // Deferral wrappers for the main-thread finalize paths (see
    // Database::MayBlockOnWorkerRoundTrip): they carry the original work
    // through the database's exclusive queue so the sqlite3_finalize runs
    // on this thread only once no worker can hold the connection mutex.
    struct DeferredFinalizeBaton : Database::Baton {
        Statement::Baton* inner;
        DeferredFinalizeBaton(Database* db_, Statement::Baton* inner_) :
                Baton(db_, Napi::Function()), inner(inner_) {}
        virtual ~DeferredFinalizeBaton() override {
            delete inner;
        }
    };

    struct HandleFinalizeBaton : Database::Baton {
        sqlite3_stmt* handle;
        HandleFinalizeBaton(Database* db_, sqlite3_stmt* handle_) :
                Baton(db_, Napi::Function()), handle(handle_) {}
        virtual ~HandleFinalizeBaton() override = default;
    };

    struct Async {
        uv_async_t watcher;
        Statement* stmt;
        Rows data;
        // Column names for the rows currently in `data`. Written by the
        // worker thread and read by the main thread, both under the mutex
        // below, so a mid-stream re-prepare cannot be missed.
        Columns columns;
        // Row shape for the delivered rows (EachBaton::row_mode), set once
        // before the work starts and read on the JS thread only.
        int row_mode = SYNC_ROW_OBJECT;
        NODE_SQLITE3_MUTEX_t;
        bool completed;
        int retrieved;

        // Store the callbacks here because we don't have
        // access to the baton in the async callback.
        Napi::FunctionReference item_cb;
        Napi::FunctionReference completed_cb;

        Async(Statement* st, uv_async_cb async_cb) :
                stmt(st), completed(false), retrieved(0) {
            watcher.data = this;
            NODE_SQLITE3_MUTEX_INIT
            stmt->Ref();
            uv_loop_t *loop;
            napi_get_uv_event_loop(stmt->Env(), &loop);
            uv_async_init(loop, &watcher, async_cb);
        }

        ~Async() {
            stmt->Unref();
            item_cb.Reset();
            completed_cb.Reset();
            NODE_SQLITE3_MUTEX_DESTROY
        }
    };

    Statement(const Napi::CallbackInfo& info);

    // Finalize-on-GC safety net: tears down a collected statement that
    // was never finalized. Runs in the ObjectWrap finalizer, so it must
    // not call into JS; see the definition in statement.cc for why each
    // step is safe there.
    ~Statement();

    WORK_DEFINITION(Bind)
    WORK_DEFINITION(Get)
    WORK_DEFINITION(Run)
    WORK_DEFINITION(All)
    WORK_DEFINITION(Each)
    WORK_DEFINITION(Reset)
    WORK_DEFINITION(Fetch)

    Napi::Value Finalize_(const Napi::CallbackInfo& info);

    // End-of-call bookkeeping for an asynchronous statement operation.
    void EndCall();

    // Runs EndCall() on every exit path from a Work_After* handler. This
    // matters because TRY_CATCH_CALL returns early when a JS callback
    // throws: doing the bookkeeping inline would then be skipped, leaving
    // `locked` set and db->pending elevated forever. The statement's queue
    // would never drain again and the sync fast path's idle gate could
    // never pass, so one throwing callback would permanently disable
    // getSync/runSync/allSync on that connection.
    struct CallGuard {
        Statement* stmt;
        explicit CallGuard(Statement* s) : stmt(s) {}
        ~CallGuard() { stmt->EndCall(); }
        CallGuard(const CallGuard&) = delete;
        CallGuard& operator=(const CallGuard&) = delete;
    };

    // Opt-in synchronous fast path. Only legal when the database is fully
    // idle (no worker in flight, nothing queued); otherwise throws.
    Napi::Value GetSync(const Napi::CallbackInfo& info);
    Napi::Value RunSync(const Napi::CallbackInfo& info);
    Napi::Value AllSync(const Napi::CallbackInfo& info);

    // Mode-aware accessors for the result of the last run(). lastID throws
    // a RangeError in 'number' mode when the rowid is not a safe integer;
    // lastIDBigInt is exact in every mode. changes is always a safe number.
    Napi::Value GetLastID(const Napi::CallbackInfo& info);
    Napi::Value GetLastIDBigInt(const Napi::CallbackInfo& info);
    Napi::Value GetChanges(const Napi::CallbackInfo& info);

    // True once the statement has been finalized: explicitly, after a
    // failed prepare, or by the GC safety net. Operations on a finalized
    // statement fail with SQLITE_MISUSE.
    Napi::Value FinalizedGetter(const Napi::CallbackInfo& info);

    // --- Introspection (Deliverable 07). readonly/parameterCount/
    // parameterNames/columns serve a snapshot taken once, under the
    // connection mutex, so the accessors never touch the sqlite3_stmt*
    // and cannot race a stepping worker or a round-tripping JS callback.
    // The sqlite3_stmt result shape does not change across a transparent
    // re-prepare, so the snapshot holds for the statement's lifetime.
    // status() reads live counters and takes the mutex instead, refusing
    // while a JS round trip could hold it.
    //
    // Taking the snapshot and publishing it are deliberately separate.
    // The async prepare runs on a libuv worker, and the accessors run on
    // the JS thread: had the worker written the accessor-visible fields
    // directly, a getter called during the prepare window would walk a
    // std::vector mid-push_back — a dangling read, not merely a stale
    // one. So SnapshotMetadata() (preparing thread, mutex held) fills
    // pending_meta, and PublishMetadata() (JS thread only) moves it into
    // place. The napi async-work completion that calls PublishMetadata()
    // is ordered after the worker, so the move needs no atomics.
    struct ColumnMeta {
        std::string name;
        std::string decltype_;  // empty = none (expression/alias columns)
        std::string database;   // SQLITE_ENABLE_COLUMN_METADATA only
        std::string table;
        std::string origin;
    };
    struct Metadata {
        bool readonly = false;
        int param_count = 0;
        std::vector<std::string> param_names;  // "" entries: unnamed (?)
        std::vector<ColumnMeta> columns;
    };
    void SnapshotMetadata();
    void PublishMetadata();
    Napi::Value ReadonlyGetter(const Napi::CallbackInfo& info);
    Napi::Value ParameterCountGetter(const Napi::CallbackInfo& info);
    Napi::Value ParameterNamesGetter(const Napi::CallbackInfo& info);
    Napi::Value ColumnsGetter(const Napi::CallbackInfo& info);
    Napi::Value Status(const Napi::CallbackInfo& info);
    // SQL text accessors (Phase 6): the statement's SQL with bound values
    // substituted (sqlite3_expanded_sql) and with literals normalized to
    // `?` (sqlite3_normalized_sql, needs SQLITE_ENABLE_NORMALIZE). Both
    // read the live statement, so they refuse while a worker round trip
    // could hold the connection mutex.
    Napi::Value ExpandedSQLGetter(const Napi::CallbackInfo& info);
    Napi::Value NormalizedSQLGetter(const Napi::CallbackInfo& info);
    // Shared refusal prelude of the two SQL accessors: true when the live
    // statement may be read right now.
    bool SQLAccessorGate(Napi::Env env);
    // Per-statement integer-mode override (Phase 6 parity): 0 = none
    // (follow the connection's mode), otherwise one of INTEGER_NUMBER /
    // INTEGER_BIGINT / INTEGER_MIXED. Applied by the JS layer right after
    // construction, so it is only ever read on the JS thread (all row
    // conversion happens there).
    Napi::Value SetIntegerMode(const Napi::CallbackInfo& info);
    int EffectiveIntegerMode() const {
        return has_integer_override ? integer_mode_override
                                    : db->integer_mode;
    }

protected:
    static void Work_BeginPrepare(Database::Baton* baton);
    static void Work_Prepare(napi_env env, void* data);
    static void Work_AfterPrepare(napi_env env, napi_status status, void* data);

    static void AsyncEach(uv_async_t* handle);
    static void CloseCallback(uv_handle_t* handle);

    static void Finalize_(Baton* baton);
    void Finalize_();

    template <class T> inline std::unique_ptr<Values::Field> BindParameter(const Napi::Value source, T pos);
    // Binds [start, end) of the call into a baton, popping a trailing
    // callback into the baton unless `preset_callback` carries one
    // (Get/All/Each/Fetch pop it themselves when a `{ rowMode }` options
    // bag sits between the bind parameters and the callback).
    template <class T> T* Bind(const Napi::CallbackInfo& info, int start = 0,
        int end = -1, Napi::Function preset_callback = Napi::Function());
    // The bind-argument shapes (one array / N positional / one named
    // object), shared by Bind<T> (into a Baton, for the queued async
    // paths) and called directly by the synchronous fast paths, which
    // have no Baton to fill. Returns false with a pending exception on
    // unsupported values or a malformed shape.
    bool ParseBindArguments(const Napi::CallbackInfo& info, int start,
        int last, Parameters* parameters);
    bool Bind(Parameters&& parameters, bool supplied);

    // The synchronous counterpart of ParseBindArguments + Bind: applies the
    // call's bind arguments straight onto the statement, with no
    // Parameters vector and no heap Values::Field per parameter.
    //
    // Accepts the same three argument shapes, performs the same arity and
    // named-parameter checks, produces the same error text, and leaves the
    // statement in the same state on failure. The asynchronous paths keep
    // the Field route: they read the arguments on the JS thread and apply
    // them on a worker, so there the materialisation is the hand-off.
    bool BindArgumentsDirect(const Napi::CallbackInfo& info, int start,
        int last, bool supplied);

    static void GetRow(Row* row, sqlite3_stmt* stmt, Columns* columns);
    // Rebuilds the rooted JS key strings if `columns` differs from the set
    // they were built from. Call once per batch, before RowToJS.
    void SyncColumnKeys(Napi::Env env, const Columns& columns);
    Napi::Value RowToJS(Napi::Env env, Row* row,
        int row_mode = SYNC_ROW_OBJECT);

    // The scopeless core of RowToJS, and the asynchronous counterpart of
    // ConvertCurrentRow: converts one already-materialised Row into the
    // caller's HandleScope. Returns false when the 'number'-mode RangeError
    // left a pending exception. Callers must store `*out` into a rooted JS
    // object before their scope closes.
    bool ConvertCellRow(Napi::Env env, Row* row,
        const std::vector<napi_value>& keys, int row_mode, napi_value* out);

    // Converts a whole materialised result set into a JS array, resolving
    // the column keys once and opening one HandleScope per batch of rows
    // rather than one per row.
    //
    // This is the shared tail of every asynchronous read completion
    // (all/fetch, and their promise forms). Those paths must materialise
    // Cells — the rows are read on a worker thread and converted later on
    // the JS thread — but the *conversion* is a plain synchronous pass and
    // has no reason to cost more per row than the synchronous paths do.
    //
    // Returns false when a row raised the RangeError, leaving it pending
    // for the caller to deliver to the callback.
    bool CellRowsToJS(Napi::Env env, Rows& rows, const Columns& columns,
        int row_mode, Napi::Array* out);
    // The synchronous counterpart of RowToJS: builds the row object from
    // the live statement, with no intermediate Row. Requires the column
    // keys to have been synced for the current result shape. `row_mode`
    // picks the row shape (object or array); the array shape never reads
    // `keys`.
    Napi::Value CurrentRowToJS(Napi::Env env,
        const std::vector<napi_value>& keys, int row_mode = SYNC_ROW_OBJECT);

    // The scopeless core of CurrentRowToJS: converts the current row into
    // the caller's HandleScope and reports via the return value whether it
    // completed (false = the 'number'-mode RangeError left a pending
    // exception). Callers must store `*out` into a rooted JS object before
    // their scope closes — which the synchronous paths do immediately,
    // into the result array they are building.
    bool ConvertCurrentRow(Napi::Env env, const std::vector<napi_value>& keys,
        int row_mode, int cols, napi_value* out);
    // Resolves the cached column-key references into raw napi_values once
    // per call, so a multi-row read does not re-dereference them per cell.
    void ResolveColumnKeys(std::vector<napi_value>* out);

    // Returns the compiled row factory for the current result shape, or
    // NULL when this build/realm/shape cannot use one (see
    // AddonData::row_factory_generator, and kMaxFactoryColumns). Compiled
    // on first use per shape and dropped whenever the column keys are
    // rebuilt, so a mid-stream re-prepare cannot reuse a stale shape.
    napi_value RowFactoryForShape(Napi::Env env, int row_mode);
    // Drops the compiled factories. Called from the two places that
    // invalidate the column keys.
    void ResetRowFactories(Napi::Env env);

    // Builds one row by calling the shape's factory with the cells as
    // arguments — one napi_call_function instead of one V8 store per
    // column. `cells` must already hold `cols` converted values.
    bool CallRowFactory(Napi::Env env, napi_value factory,
        const std::vector<napi_value>& cells, int cols, napi_value* out);

    // Above this column count the generated function is not worth it (and
    // approaches V8's parameter limit): those shapes keep the store loop.
    static const int kMaxFactoryColumns = 256;

    // Compiled factories for this statement's current result shape,
    // indexed by SyncRowMode. NULL until first used for that mode.
    napi_ref row_factory_[2] = { NULL, NULL };
    // Converts an int64 cell/rowid according to the database's integer
    // mode. Throws a RangeError in 'number' mode for unsafe values;
    // callers must check env.IsExceptionPending() afterwards.
    Napi::Value Int64ToJS(Napi::Env env, sqlite3_int64 value, const std::string& what);
    // Stores the result of a completed run() for the lastID/lastIDBigInt/
    // changes accessors.
    void RecordRunResult(sqlite3_int64 id, int changes);
    void Schedule(Work_Callback callback, Baton* baton);
    void Process();
    void CleanQueue();
    template <class T> static void Error(T* baton);

    // True when nothing anywhere on this database is in flight or queued,
    // so sqlite can be driven from the main thread without racing the
    // worker pool or breaking FIFO ordering.
    bool IdleForInline();
    // The synchronous methods' shared safety gate: not finalized, fully
    // idle, no JavaScript collation or progress handler that would have to
    // run on the thread blocked inside SQLite. Throws; false means the
    // caller must return env.Null().
    bool SyncGate(Napi::Env env);
    // RAII marker for "this statement's VM is currently executing on the
    // JS thread": a user-defined function invoked from the sync path can
    // call back into the synchronous methods, but not into *this*
    // statement — re-entering a stepping VM is the one hard rule SQLite
    // has (node:sqlite enforces the same since v26.8). Other statements
    // on the same connection stay legal: the connection mutex is
    // recursive, so a nested step just re-enters it.
    struct SyncStepGuard {
        Statement* stmt;
        explicit SyncStepGuard(Statement* s) : stmt(s) { stmt->sync_in_flight = true; }
        ~SyncStepGuard() { stmt->sync_in_flight = false; }
        SyncStepGuard(const SyncStepGuard&) = delete;
        SyncStepGuard& operator=(const SyncStepGuard&) = delete;
    };
    // Throws the pending status/message as a JS error with errno/code.
    void ThrowStatementError(Napi::Env env);

    void FailQueue(Napi::Value error, bool emit_if_unhandled = true);


protected:
    // NULL when the constructor threw before validation completed; every
    // destructor path gates on it.
    Database* db = NULL;

    sqlite3_stmt* _handle = NULL;
    int status = SQLITE_OK;
    bool prepared = false;
    bool locked = true;
    bool finalized = false;

    // True while this statement's VM is executing inside a *Sync call on
    // the JS thread (see SyncStepGuard): a re-entrant sync call from a
    // user-defined function must refuse rather than corrupt the VDBE.
    bool sync_in_flight = false;

    // Byte offset of the failing token of the most recent failed prepare,
    // from sqlite3_error_offset() (-1 when the last error was not a
    // prepare error or there was no error). Attached to the thrown
    // SqliteError as `offset` (bun:sqlite exposes the same as byteOffset;
    // no other Node driver has it). Written under the connection mutex
    // wherever a prepare fails; read on the JS thread when the error is
    // built, which the async-work completion ordering serializes.
    int error_offset = -1;

    // Per-statement integer-mode override (see SetIntegerMode).
    int integer_mode_override = 0;
    bool has_integer_override = false;

    // Result of the most recent run(), exposed through the lastID,
    // lastIDBigInt and changes accessors.
    sqlite3_int64 last_insert_id = 0;
    int last_changes = 0;
    bool has_run_result = false;

    // Payloads of the currently SQLITE_STATIC-bound text/blob parameters.
    // Owned until the next rebind or finalize so sqlite never sees a
    // dangling pointer when a statement is stepped again without rebinding.
    Parameters bound_payloads;

    // Rooted JS key strings used for row conversion, plus the column names
    // they were built from so a schema change can invalidate them.
    std::vector<std::string> column_keys_source;
    std::vector<Napi::Reference<Napi::String>> column_keys;

    // Shape tracking for the synchronous read paths' key cache: a sync
    // call re-reads the live column names only when the statement's shape
    // has moved (transparent re-prepare counter or column count changed).
    // One sqlite3_stmt_status call per call replaces the fresh heap
    // vector<string> of names the old per-call Columns captured.
    void SyncColumnKeysLive(Napi::Env env);
    Columns sync_columns;
    bool sync_keys_valid = false;
    int sync_keys_reprepares = -1;
    int sync_keys_cols = -1;

    // Introspection snapshot. pending_meta is written by the preparing
    // thread; meta and meta_valid are touched only by the JS thread, and
    // meta_valid gates the accessors (the async prepare window leaves
    // them undefined). See the note above PublishMetadata().
    Metadata pending_meta;
    Metadata meta;
    bool meta_valid = false;

    std::queue<Call*> queue;
    std::string message;
};

}

#endif
