"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/builtins/sqlite-storage/index.ts
var sqlite_storage_exports = {};
__export(sqlite_storage_exports, {
  CORE_TABLE_DDL: () => CORE_TABLE_DDL,
  FileSystemStorage: () => FileSystemStorage,
  FilesystemStorageAddon: () => FilesystemStorageAddon,
  FilesystemStorageProvider: () => FilesystemStorageProvider,
  SettingsStore: () => SettingsStore,
  SqliteSettingsAddon: () => SqliteSettingsAddon,
  SqliteSettingsBackend: () => SqliteSettingsBackend,
  SqliteStorageAddon: () => SqliteStorageAddon,
  SqliteStorageProvider: () => SqliteStorageProvider,
  addonTableToDdl: () => addonTableToDdl,
  default: () => FilesystemStorageAddon
});
module.exports = __toCommonJS(sqlite_storage_exports);

// src/builtins/sqlite-storage/filesystem-storage-provider.ts
var fs = __toESM(require("fs"));
var path = __toESM(require("path"));
var STORAGE_LOCATION_TYPES = [
  "recordings-high",
  "recordings-low",
  "recordings-clips",
  "event-images",
  "models",
  "addons-data",
  "cache",
  "logs"
];
var DEFAULT_LOCATION_SUBDIRS = {
  "recordings-high": "recordings-high",
  "recordings-low": "recordings-low",
  "recordings-clips": "recordings-clips",
  "event-images": "event-images",
  "models": "models",
  "addons-data": "addons-data",
  "cache": "/tmp/camstack-cache",
  "logs": "logs"
};
var FilesystemStorageProvider = class {
  id = "local";
  name = "Local Filesystem";
  supportedLocations = [...STORAGE_LOCATION_TYPES];
  rootPath;
  locationPaths;
  constructor(rootPath, overrides) {
    this.rootPath = path.resolve(rootPath);
    this.locationPaths = /* @__PURE__ */ new Map();
    for (const loc of STORAGE_LOCATION_TYPES) {
      const override = overrides?.[loc];
      if (override) {
        this.locationPaths.set(loc, path.resolve(override));
      } else {
        const subdir = DEFAULT_LOCATION_SUBDIRS[loc];
        this.locationPaths.set(
          loc,
          path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir)
        );
      }
    }
  }
  resolve(location, relativePath) {
    const base = this.locationPaths.get(location);
    if (!base) throw new Error(`Unknown storage location: ${location}`);
    return path.join(base, relativePath);
  }
  async write(location, relativePath, data) {
    const filePath = this.resolve(location, relativePath);
    await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
    if (Buffer.isBuffer(data)) {
      await fs.promises.writeFile(filePath, data);
    } else {
      const writeStream = fs.createWriteStream(filePath);
      await new Promise((resolve2, reject) => {
        data.pipe(writeStream);
        writeStream.on("finish", resolve2);
        writeStream.on("error", reject);
      });
    }
  }
  async read(location, relativePath) {
    return fs.promises.readFile(this.resolve(location, relativePath));
  }
  async exists(location, relativePath) {
    try {
      await fs.promises.access(this.resolve(location, relativePath));
      return true;
    } catch {
      return false;
    }
  }
  async list(location, prefix) {
    const base = this.locationPaths.get(location);
    if (!base) return [];
    const dir = prefix ? path.join(base, prefix) : base;
    try {
      const entries = await fs.promises.readdir(dir, { withFileTypes: true });
      return entries.map((e) => prefix ? `${prefix}/${e.name}` : e.name);
    } catch {
      return [];
    }
  }
  async delete(location, relativePath) {
    const filePath = this.resolve(location, relativePath);
    await fs.promises.rm(filePath, { force: true });
  }
  async getAvailableSpace(location) {
    const base = this.locationPaths.get(location);
    if (!base) return null;
    try {
      const stats = await fs.promises.statfs(base);
      return stats.bavail * stats.bsize;
    } catch {
      return null;
    }
  }
  async initialize() {
    for (const [, dirPath] of this.locationPaths) {
      await fs.promises.mkdir(dirPath, { recursive: true });
    }
  }
  async shutdown() {
  }
  /** Get the resolved path for a location type */
  getLocationPath(location) {
    const p = this.locationPaths.get(location);
    if (!p) throw new Error(`Unknown storage location: ${location}`);
    return p;
  }
  /** Get the root path */
  getRootPath() {
    return this.rootPath;
  }
};

// src/builtins/sqlite-storage/filesystem-storage.addon.ts
var FilesystemStorageAddon = class {
  manifest = {
    id: "filesystem-storage",
    name: "Local Filesystem Storage",
    version: "1.0.0",
    capabilities: [{ name: "storage", mode: "collection" }]
  };
  provider = null;
  currentConfig = {
    rootPath: "camstack-data"
  };
  async initialize(context) {
    const rootPath = context.addonConfig.rootPath ?? this.currentConfig.rootPath;
    this.currentConfig.rootPath = rootPath;
    const overrides = {};
    for (const key of Object.keys(context.addonConfig)) {
      if (key.startsWith("override.")) {
        const loc = key.slice("override.".length);
        overrides[loc] = context.addonConfig[key];
      }
    }
    this.provider = new FilesystemStorageProvider(rootPath, overrides);
    await this.provider.initialize();
    context.logger.info(`Filesystem storage initialized at ${this.provider.getRootPath()}`);
  }
  async shutdown() {
    await this.provider?.shutdown();
  }
  getCapabilityProvider(name) {
    if (name === "storage" && this.provider) {
      return this.provider;
    }
    return null;
  }
  getProvider() {
    return this.provider;
  }
  getConfigSchema() {
    return { sections: [] };
  }
  getConfig() {
    return { ...this.currentConfig };
  }
  async onConfigChange(config) {
    this.currentConfig.rootPath = config.rootPath ?? this.currentConfig.rootPath;
  }
};

// src/builtins/sqlite-storage/sqlite-settings-backend.ts
var import_better_sqlite3 = __toESM(require("better-sqlite3"));
var import_node_crypto = require("crypto");
var SqliteSettingsBackend = class {
  constructor(dbPath, runtimeDefaults) {
    this.dbPath = dbPath;
    this.runtimeDefaults = runtimeDefaults ?? {};
  }
  db = null;
  ensuredTables = /* @__PURE__ */ new Set();
  runtimeDefaults;
  async initialize() {
    const dir = this.dbPath.substring(0, this.dbPath.lastIndexOf("/"));
    if (dir) {
      const fs3 = await import("fs");
      fs3.mkdirSync(dir, { recursive: true });
    }
    this.db = new import_better_sqlite3.default(this.dbPath);
    this.db.pragma("journal_mode = WAL");
    this.db.pragma("foreign_keys = ON");
    const isEmpty = await this.isEmpty("system-settings");
    if (isEmpty) {
      await this.seedDefaults();
    }
  }
  async shutdown() {
    this.db?.close();
    this.db = null;
  }
  // ---------------------------------------------------------------------------
  // ISettingsBackend implementation
  // ---------------------------------------------------------------------------
  async get(collection, key) {
    this.ensureTable(collection);
    const row = this.getDb().prepare(`SELECT data FROM "${collection}" WHERE id = ?`).get(key);
    if (!row) return void 0;
    return JSON.parse(row.data);
  }
  async set(collection, key, value) {
    this.ensureTable(collection);
    this.getDb().prepare(`INSERT INTO "${collection}" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`).run(key, JSON.stringify(value));
  }
  async query(collection, filter) {
    this.ensureTable(collection);
    let sql = `SELECT id, data FROM "${collection}"`;
    const params = [];
    const whereClauses = [];
    if (filter?.where) {
      for (const [field, value] of Object.entries(filter.where)) {
        if (field === "id") {
          whereClauses.push("id = ?");
          params.push(value);
        } else {
          whereClauses.push(`json_extract(data, '$.${field}') = ?`);
          params.push(typeof value === "object" ? JSON.stringify(value) : value);
        }
      }
    }
    if (filter?.whereIn) {
      for (const [field, values] of Object.entries(filter.whereIn)) {
        const placeholders = values.map(() => "?").join(", ");
        if (field === "id") {
          whereClauses.push(`id IN (${placeholders})`);
        } else {
          whereClauses.push(`json_extract(data, '$.${field}') IN (${placeholders})`);
        }
        params.push(...values);
      }
    }
    if (filter?.whereBetween) {
      for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {
        if (field === "id") {
          whereClauses.push("id BETWEEN ? AND ?");
        } else {
          whereClauses.push(`json_extract(data, '$.${field}') BETWEEN ? AND ?`);
        }
        params.push(low, high);
      }
    }
    if (whereClauses.length > 0) {
      sql += ` WHERE ${whereClauses.join(" AND ")}`;
    }
    if (filter?.orderBy) {
      const dir = filter.orderBy.direction === "desc" ? "DESC" : "ASC";
      if (filter.orderBy.field === "id") {
        sql += ` ORDER BY id ${dir}`;
      } else {
        sql += ` ORDER BY json_extract(data, '$.${filter.orderBy.field}') ${dir}`;
      }
    }
    if (filter?.limit) {
      sql += ` LIMIT ?`;
      params.push(filter.limit);
    }
    if (filter?.offset) {
      sql += ` OFFSET ?`;
      params.push(filter.offset);
    }
    const rows = this.getDb().prepare(sql).all(...params);
    return rows.map((row) => ({
      id: row.id,
      data: JSON.parse(row.data)
    }));
  }
  async insert(collection, record) {
    this.ensureTable(collection);
    const id = record.id || (0, import_node_crypto.randomUUID)();
    this.getDb().prepare(`INSERT INTO "${collection}" (id, data) VALUES (?, ?)`).run(id, JSON.stringify(record.data));
  }
  async update(collection, id, data) {
    this.ensureTable(collection);
    this.getDb().prepare(`UPDATE "${collection}" SET data = ? WHERE id = ?`).run(JSON.stringify(data), id);
  }
  async delete(collection, key) {
    this.ensureTable(collection);
    this.getDb().prepare(`DELETE FROM "${collection}" WHERE id = ?`).run(key);
  }
  async count(collection, filter) {
    this.ensureTable(collection);
    if (!filter) {
      const row = this.getDb().prepare(`SELECT COUNT(*) AS cnt FROM "${collection}"`).get();
      return row?.cnt ?? 0;
    }
    const rows = await this.query(collection, { ...filter, limit: void 0, offset: void 0 });
    return rows.length;
  }
  async isEmpty(collection) {
    this.ensureTable(collection);
    const row = this.getDb().prepare(`SELECT COUNT(*) AS cnt FROM "${collection}"`).get();
    return (row?.cnt ?? 0) === 0;
  }
  // ---------------------------------------------------------------------------
  // Legacy SettingsStore compatibility (used by ConfigManager.setSettingsStore)
  // ---------------------------------------------------------------------------
  /** Get a system setting by dot-notation key */
  getSystem(key) {
    this.ensureTable("system-settings");
    const row = this.getDb().prepare('SELECT data FROM "system-settings" WHERE id = ?').get(key);
    if (!row) return void 0;
    return JSON.parse(row.data);
  }
  /** Set a system setting */
  setSystem(key, value) {
    this.ensureTable("system-settings");
    this.getDb().prepare('INSERT INTO "system-settings" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data').run(key, JSON.stringify(value));
  }
  /** Get all system settings as flat key-value */
  getAllSystem() {
    this.ensureTable("system-settings");
    const rows = this.getDb().prepare('SELECT id, data FROM "system-settings"').all();
    return Object.fromEntries(rows.map((r) => [r.id, JSON.parse(r.data)]));
  }
  /** Get all settings for an addon */
  getAllAddon(addonId) {
    this.ensureTable("addon-settings");
    const rows = this.getDb().prepare(`SELECT id, data FROM "addon-settings" WHERE json_extract(data, '$.addonId') = ?`).all(addonId);
    if (rows.length === 0) return {};
    const result = {};
    for (const row of rows) {
      const parsed = JSON.parse(row.data);
      const key = row.id.startsWith(`${addonId}.`) ? row.id.slice(addonId.length + 1) : row.id;
      result[key] = parsed.value ?? parsed;
    }
    return result;
  }
  /** Bulk-set all settings for an addon */
  setAllAddon(addonId, config) {
    this.ensureTable("addon-settings");
    const db = this.getDb();
    const deleteStmt = db.prepare(`DELETE FROM "addon-settings" WHERE id LIKE ? || '%'`);
    const insertStmt = db.prepare('INSERT INTO "addon-settings" (id, data) VALUES (?, ?)');
    db.transaction(() => {
      deleteStmt.run(`${addonId}.`);
      for (const [key, value] of Object.entries(config)) {
        insertStmt.run(`${addonId}.${key}`, JSON.stringify({ addonId, key, value }));
      }
    })();
  }
  /** Get all settings for a provider */
  getAllProvider(providerId) {
    return this.getAllScoped("provider-settings", providerId);
  }
  /** Set a provider setting */
  setProvider(providerId, key, value) {
    this.setScopedKey("provider-settings", providerId, key, value);
  }
  /** Get all settings for a device */
  getAllDevice(deviceId) {
    return this.getAllScoped("device-settings", deviceId);
  }
  /** Set a device setting */
  setDevice(deviceId, key, value) {
    this.setScopedKey("device-settings", deviceId, key, value);
  }
  /** Seed system-settings with runtime defaults (first boot) */
  async seedDefaults() {
    this.ensureTable("system-settings");
    const insert = this.getDb().prepare(
      'INSERT OR IGNORE INTO "system-settings" (id, data) VALUES (?, ?)'
    );
    this.getDb().transaction(() => {
      for (const [key, value] of Object.entries(this.runtimeDefaults)) {
        insert.run(key, JSON.stringify(value));
      }
    })();
  }
  // ---------------------------------------------------------------------------
  // Private helpers
  // ---------------------------------------------------------------------------
  getDb() {
    if (!this.db) throw new Error("SqliteSettingsBackend not initialized \u2014 call initialize() first");
    return this.db;
  }
  ensureTable(collection) {
    if (this.ensuredTables.has(collection)) return;
    this.getDb().exec(
      `CREATE TABLE IF NOT EXISTS "${collection}" (id TEXT PRIMARY KEY, data TEXT NOT NULL)`
    );
    this.ensuredTables.add(collection);
  }
  getAllScoped(collection, scopeId) {
    this.ensureTable(collection);
    const rows = this.getDb().prepare(`SELECT id, data FROM "${collection}" WHERE id LIKE ? || '.%'`).all(scopeId);
    const result = {};
    for (const row of rows) {
      const key = row.id.slice(scopeId.length + 1);
      const parsed = JSON.parse(row.data);
      result[key] = parsed.value ?? parsed;
    }
    return result;
  }
  setScopedKey(collection, scopeId, key, value) {
    this.ensureTable(collection);
    this.getDb().prepare(`INSERT INTO "${collection}" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`).run(`${scopeId}.${key}`, JSON.stringify({ scopeId, key, value }));
  }
};

// src/builtins/sqlite-storage/sqlite-settings.addon.ts
var SqliteSettingsAddon = class {
  manifest = {
    id: "sqlite-settings",
    name: "SQLite Settings",
    version: "1.0.0",
    capabilities: [{ name: "settings-store", mode: "singleton" }]
  };
  backend = null;
  async initialize(context) {
    const dbPath = context.storageProvider ? context.storageProvider.resolve("addons-data", `${context.id.replace("addon:", "")}/camstack.db`) : context.dataDir ? `${context.dataDir}/camstack.db` : "camstack-data/addons-data/sqlite-settings/camstack.db";
    const runtimeDefaults = context.addonConfig._runtimeDefaults ?? {};
    this.backend = new SqliteSettingsBackend(dbPath, runtimeDefaults);
    await this.backend.initialize();
    context.logger.info(`SQLite settings initialized at ${dbPath}`);
  }
  async shutdown() {
    await this.backend?.shutdown();
  }
  getBackend() {
    return this.backend;
  }
  getCapabilityProvider(name) {
    if (name === "settings-store" && this.backend) {
      return this.backend;
    }
    return null;
  }
  getConfigSchema() {
    return { sections: [] };
  }
  getConfig() {
    return {};
  }
  async onConfigChange(_config) {
  }
};

// src/builtins/sqlite-storage/sqlite-storage.provider.ts
var import_better_sqlite32 = __toESM(require("better-sqlite3"));
var fs2 = __toESM(require("fs"));
var path2 = __toESM(require("path"));
var import_node_crypto2 = require("crypto");
var LOCATION_TYPES = {
  // New location names (from StorageLocationManager)
  data: "structured",
  // settings, events, trails — SQL only
  media: "files",
  // crops, snapshots, thumbnails — files only
  recordings: "files",
  // video segments — files only
  models: "files",
  // ONNX/TFLite models — files only
  cache: "files",
  // temp files — files only
  logs: "files",
  // Winston log files — files only
  // Legacy location names (backward compat)
  config: "structured",
  events: "structured",
  addon: "both"
};
var SqliteStructuredStorage = class {
  constructor(db) {
    this.db = db;
  }
  ensuredTables = /* @__PURE__ */ new Set();
  ensureTable(collection) {
    if (this.ensuredTables.has(collection)) return;
    this.db.exec(
      `CREATE TABLE IF NOT EXISTS "${collection}" (id TEXT PRIMARY KEY, data TEXT)`
    );
    this.ensuredTables.add(collection);
  }
  async insert(record) {
    this.ensureTable(record.collection);
    const id = record.id || (0, import_node_crypto2.randomUUID)();
    const newRecord = {
      collection: record.collection,
      id,
      data: record.data
    };
    this.db.prepare(`INSERT INTO "${record.collection}" (id, data) VALUES (?, ?)`).run(id, JSON.stringify(newRecord.data));
    return newRecord;
  }
  async query(collection, filter) {
    this.ensureTable(collection);
    const { sql, params } = this.buildSelect(collection, filter);
    const rows = this.db.prepare(sql).all(...params);
    return rows.map((row) => ({
      collection,
      id: row.id,
      data: JSON.parse(row.data)
    }));
  }
  async update(collection, id, data) {
    this.ensureTable(collection);
    this.db.prepare(`UPDATE "${collection}" SET data = ? WHERE id = ?`).run(JSON.stringify(data), id);
    return { collection, id, data };
  }
  async delete(collection, id) {
    this.ensureTable(collection);
    this.db.prepare(`DELETE FROM "${collection}" WHERE id = ?`).run(id);
  }
  async count(collection, filter) {
    this.ensureTable(collection);
    const { sql, params } = this.buildCount(collection, filter);
    const row = this.db.prepare(sql).get(...params);
    return row.cnt;
  }
  buildWhereClause(filter) {
    if (!filter) return { clause: "", params: [] };
    const conditions = [];
    const params = [];
    if (filter.where) {
      for (const [field, value] of Object.entries(filter.where)) {
        if (field === "id") {
          conditions.push("id = ?");
          params.push(value);
        } else {
          conditions.push(`json_extract(data, '$.${field}') = ?`);
          params.push(value);
        }
      }
    }
    if (filter.whereIn) {
      for (const [field, values] of Object.entries(filter.whereIn)) {
        const placeholders = values.map(() => "?").join(", ");
        if (field === "id") {
          conditions.push(`id IN (${placeholders})`);
        } else {
          conditions.push(`json_extract(data, '$.${field}') IN (${placeholders})`);
        }
        params.push(...values);
      }
    }
    if (filter.whereBetween) {
      for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {
        if (field === "id") {
          conditions.push("id BETWEEN ? AND ?");
        } else {
          conditions.push(`json_extract(data, '$.${field}') BETWEEN ? AND ?`);
        }
        params.push(low, high);
      }
    }
    const clause = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
    return { clause, params };
  }
  buildSelect(collection, filter) {
    const { clause, params } = this.buildWhereClause(filter);
    let sql = `SELECT id, data FROM "${collection}"${clause}`;
    if (filter?.orderBy) {
      const dir = filter.orderBy.direction === "desc" ? "DESC" : "ASC";
      if (filter.orderBy.field === "id") {
        sql += ` ORDER BY id ${dir}`;
      } else {
        sql += ` ORDER BY json_extract(data, '$.${filter.orderBy.field}') ${dir}`;
      }
    }
    if (filter?.limit !== void 0) {
      sql += ` LIMIT ?`;
      params.push(filter.limit);
    }
    if (filter?.offset !== void 0) {
      sql += ` OFFSET ?`;
      params.push(filter.offset);
    }
    return { sql, params };
  }
  buildCount(collection, filter) {
    const { clause, params } = this.buildWhereClause(filter);
    return { sql: `SELECT COUNT(*) as cnt FROM "${collection}"${clause}`, params };
  }
};
var FileSystemStorage = class {
  constructor(basePath) {
    this.basePath = basePath;
  }
  async readFile(filePath) {
    const fullPath = path2.join(this.basePath, filePath);
    return fs2.promises.readFile(fullPath);
  }
  async writeFile(filePath, data) {
    const fullPath = path2.join(this.basePath, filePath);
    fs2.mkdirSync(path2.dirname(fullPath), { recursive: true });
    await fs2.promises.writeFile(fullPath, data);
  }
  async deleteFile(filePath) {
    const fullPath = path2.join(this.basePath, filePath);
    await fs2.promises.unlink(fullPath);
  }
  async listFiles(prefix) {
    const searchDir = prefix ? path2.join(this.basePath, prefix) : this.basePath;
    try {
      const entries = await fs2.promises.readdir(searchDir, { recursive: true });
      const files = [];
      for (const entry of entries) {
        const entryStr = String(entry);
        const relative = prefix ? path2.join(prefix, entryStr) : entryStr;
        const fullPath = path2.join(this.basePath, relative);
        try {
          const stat = await fs2.promises.stat(fullPath);
          if (stat.isFile()) {
            files.push(relative);
          }
        } catch {
        }
      }
      return files;
    } catch {
      return [];
    }
  }
  async getFileUrl(_path) {
    return null;
  }
  async exists(filePath) {
    const fullPath = path2.join(this.basePath, filePath);
    try {
      await fs2.promises.access(fullPath, fs2.constants.F_OK);
      return true;
    } catch {
      return false;
    }
  }
};
var SqliteStorageProvider = class {
  mainDb = null;
  sharedStructured = null;
  locations = /* @__PURE__ */ new Map();
  async initialize() {
  }
  /**
   * Configure all storage locations.
   * ONE single SQLite database (camstack.db) is used for ALL structured storage.
   * File-based locations use the filesystem at their configured path.
   */
  async configure(config) {
    const dataPath = config.locations["data"] ?? config.locations["config"] ?? Object.values(config.locations)[0];
    if (!dataPath) throw new Error("No data path configured for SQLite storage");
    fs2.mkdirSync(dataPath, { recursive: true });
    const dbPath = path2.join(dataPath, "camstack.db");
    this.mainDb = new import_better_sqlite32.default(dbPath);
    this.mainDb.pragma("journal_mode = WAL");
    this.sharedStructured = new SqliteStructuredStorage(this.mainDb);
    for (const [name, dirPath] of Object.entries(config.locations)) {
      const locationName = name;
      const locationType = LOCATION_TYPES[name] ?? "files";
      fs2.mkdirSync(dirPath, { recursive: true });
      const location = {};
      if (locationType === "structured" || locationType === "both") {
        location.structured = this.sharedStructured;
      }
      if (locationType === "files" || locationType === "both") {
        location.files = new FileSystemStorage(dirPath);
      }
      this.locations.set(locationName, location);
    }
  }
  getLocation(name) {
    const location = this.locations.get(name);
    if (!location) {
      throw new Error(`Storage location "${name}" not found`);
    }
    return location;
  }
  async shutdown() {
    if (this.mainDb) {
      this.mainDb.close();
      this.mainDb = null;
      this.sharedStructured = null;
    }
    this.locations.clear();
  }
  async export(_locationName) {
    throw new Error("Export not yet implemented");
  }
  async import(_locationName, _data) {
    throw new Error("Import not yet implemented");
  }
};

// src/builtins/sqlite-storage/sqlite-storage.addon.ts
var SqliteStorageAddon = class {
  manifest = {
    id: "sqlite-storage",
    name: "SQLite Storage",
    version: "1.0.0",
    capabilities: ["storage"]
  };
  provider = null;
  async initialize(context) {
    const storageConfig = {
      locations: { ...context.locationPaths }
    };
    this.provider = new SqliteStorageProvider();
    await this.provider.configure(storageConfig);
    context.logger.info("SQLite storage initialized");
  }
  async shutdown() {
    await this.provider?.shutdown();
  }
  getProvider() {
    if (!this.provider) throw new Error("SQLite storage not initialized");
    return this.provider;
  }
  getCapabilityProvider(name) {
    if (name === "storage" && this.provider) {
      return this.provider;
    }
    return null;
  }
  getConfigSchema() {
    return {
      sections: []
    };
  }
  getConfig() {
    return {};
  }
  async onConfigChange(_config) {
  }
};

// src/builtins/sqlite-storage/settings-store.ts
var import_better_sqlite33 = __toESM(require("better-sqlite3"));

// src/builtins/sqlite-storage/sql-schema.ts
var CORE_TABLE_DDL = [
  // Settings tables
  `CREATE TABLE IF NOT EXISTS system_settings (
    key TEXT PRIMARY KEY,
    value JSON NOT NULL,
    updated_at INTEGER NOT NULL DEFAULT (unixepoch())
  )`,
  `CREATE TABLE IF NOT EXISTS addon_settings (
    addon_id TEXT NOT NULL,
    key TEXT NOT NULL,
    value JSON NOT NULL,
    updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
    PRIMARY KEY (addon_id, key)
  )`,
  `CREATE TABLE IF NOT EXISTS provider_settings (
    provider_id TEXT NOT NULL,
    key TEXT NOT NULL,
    value JSON NOT NULL,
    updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
    PRIMARY KEY (provider_id, key)
  )`,
  `CREATE TABLE IF NOT EXISTS device_settings (
    device_id TEXT NOT NULL,
    key TEXT NOT NULL,
    value JSON NOT NULL,
    updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
    PRIMARY KEY (device_id, key)
  )`,
  // Detection events
  `CREATE TABLE IF NOT EXISTS detection_events (
    id TEXT PRIMARY KEY,
    timestamp INTEGER NOT NULL,
    device_id TEXT NOT NULL,
    class_name TEXT NOT NULL,
    score REAL NOT NULL,
    severity TEXT NOT NULL,
    track_id TEXT,
    zones JSON,
    recognition JSON,
    media_files JSON,
    data JSON
  )`,
  `CREATE INDEX IF NOT EXISTS idx_det_device_ts ON detection_events(device_id, timestamp)`,
  `CREATE INDEX IF NOT EXISTS idx_det_class_ts ON detection_events(class_name, timestamp)`,
  // Audio levels
  `CREATE TABLE IF NOT EXISTS audio_levels (
    id TEXT PRIMARY KEY,
    timestamp INTEGER NOT NULL,
    device_id TEXT NOT NULL,
    dbfs REAL NOT NULL,
    rms REAL NOT NULL,
    state TEXT NOT NULL
  )`,
  `CREATE INDEX IF NOT EXISTS idx_audio_device_ts ON audio_levels(device_id, timestamp)`,
  // Track trails
  `CREATE TABLE IF NOT EXISTS track_trails (
    track_id TEXT PRIMARY KEY,
    device_id TEXT NOT NULL,
    class_name TEXT NOT NULL,
    first_seen INTEGER NOT NULL,
    last_seen INTEGER NOT NULL,
    positions JSON NOT NULL,
    snapshots JSON,
    total_distance REAL,
    zones_visited JSON
  )`,
  `CREATE INDEX IF NOT EXISTS idx_trails_device_ts ON track_trails(device_id, first_seen)`
];
function addonTableToDdl(schema) {
  const pks = schema.columns.filter((c) => c.primaryKey).map((c) => c.name);
  const colDefs = schema.columns.map((c) => {
    const parts = [c.name, c.type];
    if (c.notNull) parts.push("NOT NULL");
    return parts.join(" ");
  });
  let ddl = `CREATE TABLE IF NOT EXISTS ${schema.name} (
  ${colDefs.join(",\n  ")}`;
  if (pks.length > 0) {
    ddl += `,
  PRIMARY KEY (${pks.join(", ")})`;
  }
  ddl += "\n)";
  const stmts = [ddl];
  for (const idx of schema.indexes ?? []) {
    const unique = idx.unique ? "UNIQUE " : "";
    stmts.push(`CREATE ${unique}INDEX IF NOT EXISTS ${idx.name} ON ${schema.name}(${idx.columns.join(", ")})`);
  }
  return stmts;
}

// src/builtins/sqlite-storage/settings-store.ts
var import_kernel = require("@camstack/kernel");
var SettingsStore = class {
  db;
  constructor(dbPath) {
    this.db = new import_better_sqlite33.default(dbPath);
    this.db.pragma("journal_mode = WAL");
    this.db.pragma("foreign_keys = ON");
    this.initTables();
  }
  // ---------------------------------------------------------------------------
  // System settings
  // ---------------------------------------------------------------------------
  getSystem(key) {
    const row = this.db.prepare("SELECT value FROM system_settings WHERE key = ?").get(key);
    if (row === void 0) return void 0;
    return JSON.parse(row.value);
  }
  setSystem(key, value) {
    this.db.prepare(
      `INSERT INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())
         ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
    ).run(key, JSON.stringify(value));
  }
  getAllSystem() {
    const rows = this.db.prepare("SELECT key, value FROM system_settings").all();
    return Object.fromEntries(rows.map((r) => [r.key, JSON.parse(r.value)]));
  }
  // ---------------------------------------------------------------------------
  // Addon settings
  // ---------------------------------------------------------------------------
  getAddon(addonId, key) {
    const row = this.db.prepare(
      "SELECT value FROM addon_settings WHERE addon_id = ? AND key = ?"
    ).get(addonId, key);
    if (row === void 0) return void 0;
    return JSON.parse(row.value);
  }
  setAddon(addonId, key, value) {
    this.db.prepare(
      `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())
         ON CONFLICT(addon_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
    ).run(addonId, key, JSON.stringify(value));
  }
  getAllAddon(addonId) {
    const rows = this.db.prepare(
      "SELECT key, value FROM addon_settings WHERE addon_id = ?"
    ).all(addonId);
    return Object.fromEntries(rows.map((r) => [r.key, JSON.parse(r.value)]));
  }
  /** Bulk-replace all keys for an addon (within a transaction). */
  setAllAddon(addonId, config) {
    const deleteStmt = this.db.prepare("DELETE FROM addon_settings WHERE addon_id = ?");
    const insertStmt = this.db.prepare(
      `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())`
    );
    this.db.transaction(() => {
      deleteStmt.run(addonId);
      for (const [key, value] of Object.entries(config)) {
        insertStmt.run(addonId, key, JSON.stringify(value));
      }
    })();
  }
  // ---------------------------------------------------------------------------
  // Provider settings
  // ---------------------------------------------------------------------------
  getProvider(providerId, key) {
    const row = this.db.prepare(
      "SELECT value FROM provider_settings WHERE provider_id = ? AND key = ?"
    ).get(providerId, key);
    if (row === void 0) return void 0;
    return JSON.parse(row.value);
  }
  setProvider(providerId, key, value) {
    this.db.prepare(
      `INSERT INTO provider_settings (provider_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())
         ON CONFLICT(provider_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
    ).run(providerId, key, JSON.stringify(value));
  }
  getAllProvider(providerId) {
    const rows = this.db.prepare(
      "SELECT key, value FROM provider_settings WHERE provider_id = ?"
    ).all(providerId);
    return Object.fromEntries(rows.map((r) => [r.key, JSON.parse(r.value)]));
  }
  // ---------------------------------------------------------------------------
  // Device settings
  // ---------------------------------------------------------------------------
  getDevice(deviceId, key) {
    const row = this.db.prepare(
      "SELECT value FROM device_settings WHERE device_id = ? AND key = ?"
    ).get(deviceId, key);
    if (row === void 0) return void 0;
    return JSON.parse(row.value);
  }
  setDevice(deviceId, key, value) {
    this.db.prepare(
      `INSERT INTO device_settings (device_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())
         ON CONFLICT(device_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
    ).run(deviceId, key, JSON.stringify(value));
  }
  getAllDevice(deviceId) {
    const rows = this.db.prepare(
      "SELECT key, value FROM device_settings WHERE device_id = ?"
    ).all(deviceId);
    return Object.fromEntries(rows.map((r) => [r.key, JSON.parse(r.value)]));
  }
  // ---------------------------------------------------------------------------
  // Lifecycle
  // ---------------------------------------------------------------------------
  /** Close the SQLite connection (call on shutdown). */
  close() {
    this.db.close();
  }
  /** Check if system_settings is empty (used for first-boot seeding). */
  isSystemSettingsEmpty() {
    const row = this.db.prepare("SELECT COUNT(*) AS cnt FROM system_settings").get();
    return (row?.cnt ?? 0) === 0;
  }
  /** Seed system_settings with RUNTIME_DEFAULTS (only on first boot). */
  seedDefaults() {
    const insert = this.db.prepare(
      `INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())`
    );
    this.db.transaction(() => {
      for (const [key, value] of Object.entries(import_kernel.RUNTIME_DEFAULTS)) {
        insert.run(key, JSON.stringify(value));
      }
    })();
  }
  // ---------------------------------------------------------------------------
  // Private helpers
  // ---------------------------------------------------------------------------
  initTables() {
    this.db.transaction(() => {
      for (const stmt of CORE_TABLE_DDL) {
        this.db.prepare(stmt).run();
      }
    })();
  }
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  CORE_TABLE_DDL,
  FileSystemStorage,
  FilesystemStorageAddon,
  FilesystemStorageProvider,
  SettingsStore,
  SqliteSettingsAddon,
  SqliteSettingsBackend,
  SqliteStorageAddon,
  SqliteStorageProvider,
  addonTableToDdl
});
//# sourceMappingURL=index.js.map