#ifndef SRC_SHIMS_SQLITE_ERRORS_H_
#define SRC_SHIMS_SQLITE_ERRORS_H_

#include "../sqlite_exception.h"
#include <napi.h>
#include <sqlite3.h>
#include <string>

namespace node {

// Helper function to get SQLite error code name
inline const char *GetSqliteErrorCodeName(int code) {
  switch (code) {
  case SQLITE_OK:
    return "SQLITE_OK";
  case SQLITE_ERROR:
    return "SQLITE_ERROR";
  case SQLITE_INTERNAL:
    return "SQLITE_INTERNAL";
  case SQLITE_PERM:
    return "SQLITE_PERM";
  case SQLITE_ABORT:
    return "SQLITE_ABORT";
  case SQLITE_BUSY:
    return "SQLITE_BUSY";
  case SQLITE_LOCKED:
    return "SQLITE_LOCKED";
  case SQLITE_NOMEM:
    return "SQLITE_NOMEM";
  case SQLITE_READONLY:
    return "SQLITE_READONLY";
  case SQLITE_INTERRUPT:
    return "SQLITE_INTERRUPT";
  case SQLITE_IOERR:
    return "SQLITE_IOERR";
  case SQLITE_CORRUPT:
    return "SQLITE_CORRUPT";
  case SQLITE_NOTFOUND:
    return "SQLITE_NOTFOUND";
  case SQLITE_FULL:
    return "SQLITE_FULL";
  case SQLITE_CANTOPEN:
    return "SQLITE_CANTOPEN";
  case SQLITE_PROTOCOL:
    return "SQLITE_PROTOCOL";
  case SQLITE_EMPTY:
    return "SQLITE_EMPTY";
  case SQLITE_SCHEMA:
    return "SQLITE_SCHEMA";
  case SQLITE_TOOBIG:
    return "SQLITE_TOOBIG";
  case SQLITE_CONSTRAINT:
    return "SQLITE_CONSTRAINT";
  case SQLITE_MISMATCH:
    return "SQLITE_MISMATCH";
  case SQLITE_MISUSE:
    return "SQLITE_MISUSE";
  case SQLITE_NOLFS:
    return "SQLITE_NOLFS";
  case SQLITE_AUTH:
    return "SQLITE_AUTH";
  case SQLITE_FORMAT:
    return "SQLITE_FORMAT";
  case SQLITE_RANGE:
    return "SQLITE_RANGE";
  case SQLITE_NOTADB:
    return "SQLITE_NOTADB";
  case SQLITE_NOTICE:
    return "SQLITE_NOTICE";
  case SQLITE_WARNING:
    return "SQLITE_WARNING";
  case SQLITE_ROW:
    return "SQLITE_ROW";
  case SQLITE_DONE:
    return "SQLITE_DONE";
  default:
    // For extended error codes, get the base error code
    int baseCode = code & 0xFF;
    return GetSqliteErrorCodeName(baseCode);
  }
}

// Enhanced SQLite error that includes system errno information
// Error format matches Node.js node:sqlite for API compatibility:
// - code: 'ERR_SQLITE_ERROR' (constant, matches Node.js)
// - errcode: number (SQLite error code, matches Node.js)
// - errstr: string (SQLite error string, matches Node.js)
// We also add extra properties for enhanced debugging:
// - sqliteCode: number (same as errcode, for backward compat)
// - sqliteExtendedCode: number (extended SQLite error code)
// - sqliteErrorString: string (same as errstr, for backward compat)
inline void ThrowEnhancedSqliteError(Napi::Env env, sqlite3 *db,
                                     int sqlite_code,
                                     const std::string &message) {
  // Use c_str() explicitly to avoid potential ABI issues on Windows ARM
  // where passing std::string directly to Napi::Error::New can result in
  // truncated or corrupted error messages
  Napi::Error error = Napi::Error::New(env, message.c_str());

  // Node.js compatible properties
  error.Set("code", Napi::String::New(env, "ERR_SQLITE_ERROR"));
  error.Set("errcode", Napi::Number::New(env, sqlite_code));

  const char *err_str = sqlite3_errstr(sqlite_code);
  if (err_str) {
    error.Set("errstr", Napi::String::New(env, err_str));
  }

  // Our enhanced properties (for backward compatibility and debugging)
  error.Set("sqliteCode", Napi::Number::New(env, sqlite_code));

  if (db) {
    // Get extended error code (more specific than basic error code)
    int extended_code = sqlite3_extended_errcode(db);
    error.Set("sqliteExtendedCode", Napi::Number::New(env, extended_code));

    // Get system errno if available (for I/O errors)
    int sys_errno = sqlite3_system_errno(db);
    if (sys_errno != 0) {
      error.Set("systemErrno", Napi::Number::New(env, sys_errno));
    }
  }

  // Keep original code name as sqliteCodeName for debugging
  const char *code_name = GetSqliteErrorCodeName(sqlite_code);
  error.Set("sqliteCodeName", Napi::String::New(env, code_name));

  // Also set the human-readable error string
  if (err_str) {
    error.Set("sqliteErrorString", Napi::String::New(env, err_str));
  }

  error.ThrowAsJavaScriptException();
}

// Database-aware version available when sqlite_impl.h is included
// This will be specialized in sqlite_impl.cpp to avoid forward declaration
// issues

// Helper to create enhanced error from just a message (when we have the db
// handle)
inline void ThrowSqliteError(Napi::Env env, sqlite3 *db,
                             const std::string &message) {
  if (db) {
    // Use extended error code (e.g., 1555 for SQLITE_CONSTRAINT_PRIMARYKEY)
    // instead of basic code (e.g., 19 for SQLITE_CONSTRAINT) to match Node.js
    int errcode = sqlite3_extended_errcode(db);
    ThrowEnhancedSqliteError(env, db, errcode, message);
  } else {
    // Fallback to simple error when no db handle available
    // Use c_str() explicitly to avoid potential ABI issues on Windows ARM
    Napi::Error::New(env, message.c_str()).ThrowAsJavaScriptException();
  }
}

// Helper to throw from a SqliteException with captured error info
// Uses same format as ThrowEnhancedSqliteError for consistency
inline void
ThrowFromSqliteException(Napi::Env env,
                         const photostructure::sqlite::SqliteException &ex) {
  Napi::Error error = Napi::Error::New(env, ex.what());

  // Node.js compatible properties
  error.Set("code", Napi::String::New(env, "ERR_SQLITE_ERROR"));
  error.Set("errcode", Napi::Number::New(env, ex.sqlite_code()));
  if (!ex.error_string().empty()) {
    error.Set("errstr", Napi::String::New(env, ex.error_string().c_str()));
  }

  // Our enhanced properties (for backward compatibility and debugging)
  error.Set("sqliteCode", Napi::Number::New(env, ex.sqlite_code()));
  error.Set("sqliteExtendedCode", Napi::Number::New(env, ex.extended_code()));

  if (ex.system_errno() != 0) {
    error.Set("systemErrno", Napi::Number::New(env, ex.system_errno()));
  }

  // Keep original code name as sqliteCodeName for debugging
  const char *code_name = GetSqliteErrorCodeName(ex.sqlite_code());
  error.Set("sqliteCodeName", Napi::String::New(env, code_name));

  // Also set the human-readable error string
  // Use c_str() explicitly to avoid potential ABI issues on Windows ARM
  if (!ex.error_string().empty()) {
    error.Set("sqliteErrorString",
              Napi::String::New(env, ex.error_string().c_str()));
  }

  error.ThrowAsJavaScriptException();
}

} // namespace node

#endif // SRC_SHIMS_SQLITE_ERRORS_H_