import {
  init_logger,
  logger
} from "./chunk-SZXICFQ3.mjs";
import "./chunk-UCMJJ3IM.mjs";
import {
  __esm,
  __require
} from "./chunk-J7LXIPZS.mjs";

// src/enterprise/scheduler/knex-store.ts
import * as fs from "fs";
import * as path from "path";
import { v4 as uuidv4 } from "uuid";
function toNum(val) {
  if (val === null || val === void 0) return void 0;
  return typeof val === "string" ? parseInt(val, 10) : val;
}
function safeJsonParse(value) {
  if (!value) return void 0;
  try {
    return JSON.parse(value);
  } catch {
    return void 0;
  }
}
function fromTriggerRow(row) {
  return {
    id: row.id,
    creatorId: row.creator_id,
    creatorContext: row.creator_context ?? void 0,
    creatorName: row.creator_name ?? void 0,
    description: row.description ?? void 0,
    channels: safeJsonParse(row.channels),
    fromUsers: safeJsonParse(row.from_users),
    fromBots: row.from_bots === true || row.from_bots === 1,
    contains: safeJsonParse(row.contains),
    matchPattern: row.match_pattern ?? void 0,
    threads: row.threads,
    workflow: row.workflow,
    inputs: safeJsonParse(row.inputs),
    outputContext: safeJsonParse(row.output_context),
    status: row.status,
    enabled: row.enabled === true || row.enabled === 1,
    createdAt: toNum(row.created_at)
  };
}
function toTriggerInsertRow(trigger) {
  return {
    id: trigger.id,
    creator_id: trigger.creatorId,
    creator_context: trigger.creatorContext ?? null,
    creator_name: trigger.creatorName ?? null,
    description: trigger.description ?? null,
    channels: trigger.channels ? JSON.stringify(trigger.channels) : null,
    from_users: trigger.fromUsers ? JSON.stringify(trigger.fromUsers) : null,
    from_bots: trigger.fromBots,
    contains: trigger.contains ? JSON.stringify(trigger.contains) : null,
    match_pattern: trigger.matchPattern ?? null,
    threads: trigger.threads,
    workflow: trigger.workflow,
    inputs: trigger.inputs ? JSON.stringify(trigger.inputs) : null,
    output_context: trigger.outputContext ? JSON.stringify(trigger.outputContext) : null,
    status: trigger.status,
    enabled: trigger.enabled,
    created_at: trigger.createdAt
  };
}
function fromDbRow(row) {
  return {
    id: row.id,
    creatorId: row.creator_id,
    creatorContext: row.creator_context ?? void 0,
    creatorName: row.creator_name ?? void 0,
    timezone: row.timezone,
    schedule: row.schedule_expr,
    runAt: toNum(row.run_at),
    isRecurring: row.is_recurring === true || row.is_recurring === 1,
    originalExpression: row.original_expression,
    workflow: row.workflow ?? void 0,
    workflowInputs: safeJsonParse(row.workflow_inputs),
    outputContext: safeJsonParse(row.output_context),
    status: row.status,
    createdAt: toNum(row.created_at),
    lastRunAt: toNum(row.last_run_at),
    nextRunAt: toNum(row.next_run_at),
    runCount: row.run_count,
    failureCount: row.failure_count,
    lastError: row.last_error ?? void 0,
    previousResponse: row.previous_response ?? void 0
  };
}
function toInsertRow(schedule) {
  return {
    id: schedule.id,
    creator_id: schedule.creatorId,
    creator_context: schedule.creatorContext ?? null,
    creator_name: schedule.creatorName ?? null,
    timezone: schedule.timezone,
    schedule_expr: schedule.schedule,
    run_at: schedule.runAt ?? null,
    is_recurring: schedule.isRecurring,
    original_expression: schedule.originalExpression,
    workflow: schedule.workflow ?? null,
    workflow_inputs: schedule.workflowInputs ? JSON.stringify(schedule.workflowInputs) : null,
    output_context: schedule.outputContext ? JSON.stringify(schedule.outputContext) : null,
    status: schedule.status,
    created_at: schedule.createdAt,
    last_run_at: schedule.lastRunAt ?? null,
    next_run_at: schedule.nextRunAt ?? null,
    run_count: schedule.runCount,
    failure_count: schedule.failureCount,
    last_error: schedule.lastError ?? null,
    previous_response: schedule.previousResponse ?? null
  };
}
var KnexStoreBackend;
var init_knex_store = __esm({
  "src/enterprise/scheduler/knex-store.ts"() {
    init_logger();
    KnexStoreBackend = class {
      knex = null;
      driver;
      connection;
      constructor(driver, storageConfig, _haConfig) {
        this.driver = driver;
        this.connection = storageConfig.connection || {};
      }
      async initialize() {
        const { createRequire } = __require("module");
        const runtimeRequire = createRequire(__filename);
        let knexFactory;
        try {
          knexFactory = runtimeRequire("knex");
        } catch (err) {
          const code = err?.code;
          if (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") {
            throw new Error(
              "knex is required for PostgreSQL/MySQL/MSSQL schedule storage. Install it with: npm install knex"
            );
          }
          throw err;
        }
        const clientMap = {
          postgresql: "pg",
          mysql: "mysql2",
          mssql: "tedious"
        };
        const client = clientMap[this.driver];
        let connection;
        if (this.connection.connection_string) {
          connection = this.connection.connection_string;
        } else if (this.driver === "mssql") {
          connection = this.buildMssqlConnection();
        } else {
          connection = this.buildStandardConnection();
        }
        this.knex = knexFactory({
          client,
          connection,
          pool: {
            min: this.connection.pool?.min ?? 0,
            max: this.connection.pool?.max ?? 10
          }
        });
        await this.migrateSchema();
        logger.info(`[KnexStore] Initialized (${this.driver})`);
      }
      buildStandardConnection() {
        return {
          host: this.connection.host || "localhost",
          port: this.connection.port,
          database: this.connection.database || "visor",
          user: this.connection.user,
          password: this.connection.password,
          ssl: this.resolveSslConfig()
        };
      }
      buildMssqlConnection() {
        const ssl = this.connection.ssl;
        const sslEnabled = ssl === true || typeof ssl === "object" && ssl.enabled !== false;
        return {
          server: this.connection.host || "localhost",
          port: this.connection.port,
          database: this.connection.database || "visor",
          user: this.connection.user,
          password: this.connection.password,
          options: {
            encrypt: sslEnabled,
            trustServerCertificate: typeof ssl === "object" ? ssl.reject_unauthorized === false : !sslEnabled
          }
        };
      }
      resolveSslConfig() {
        const ssl = this.connection.ssl;
        if (ssl === false || ssl === void 0) return false;
        if (ssl === true) return { rejectUnauthorized: true };
        if (ssl.enabled === false) return false;
        const result = {
          rejectUnauthorized: ssl.reject_unauthorized !== false
        };
        if (ssl.ca) {
          const caPath = this.validateSslPath(ssl.ca, "CA certificate");
          result.ca = fs.readFileSync(caPath, "utf8");
        }
        if (ssl.cert) {
          const certPath = this.validateSslPath(ssl.cert, "client certificate");
          result.cert = fs.readFileSync(certPath, "utf8");
        }
        if (ssl.key) {
          const keyPath = this.validateSslPath(ssl.key, "client key");
          result.key = fs.readFileSync(keyPath, "utf8");
        }
        return result;
      }
      validateSslPath(filePath, label) {
        const resolved = path.resolve(filePath);
        if (resolved !== path.normalize(resolved)) {
          throw new Error(`SSL ${label} path contains invalid sequences: ${filePath}`);
        }
        if (!fs.existsSync(resolved)) {
          throw new Error(`SSL ${label} not found: ${filePath}`);
        }
        return resolved;
      }
      async shutdown() {
        if (this.knex) {
          await this.knex.destroy();
          this.knex = null;
        }
      }
      async migrateSchema() {
        const knex = this.getKnex();
        const exists = await knex.schema.hasTable("schedules");
        if (!exists) {
          await knex.schema.createTable("schedules", (table) => {
            table.string("id", 36).primary();
            table.string("creator_id", 255).notNullable().index();
            table.string("creator_context", 255);
            table.string("creator_name", 255);
            table.string("timezone", 64).notNullable().defaultTo("UTC");
            table.string("schedule_expr", 255);
            table.bigInteger("run_at");
            table.boolean("is_recurring").notNullable();
            table.text("original_expression");
            table.string("workflow", 255);
            table.text("workflow_inputs");
            table.text("output_context");
            table.string("status", 20).notNullable().index();
            table.bigInteger("created_at").notNullable();
            table.bigInteger("last_run_at");
            table.bigInteger("next_run_at");
            table.integer("run_count").notNullable().defaultTo(0);
            table.integer("failure_count").notNullable().defaultTo(0);
            table.text("last_error");
            table.text("previous_response");
            table.index(["status", "next_run_at"]);
          });
        }
        const triggersExist = await knex.schema.hasTable("message_triggers");
        if (!triggersExist) {
          await knex.schema.createTable("message_triggers", (table) => {
            table.string("id", 36).primary();
            table.string("creator_id", 255).notNullable().index();
            table.string("creator_context", 255);
            table.string("creator_name", 255);
            table.text("description");
            table.text("channels");
            table.text("from_users");
            table.boolean("from_bots").notNullable().defaultTo(false);
            table.text("contains");
            table.text("match_pattern");
            table.string("threads", 20).notNullable().defaultTo("any");
            table.string("workflow", 255).notNullable();
            table.text("inputs");
            table.text("output_context");
            table.string("status", 20).notNullable().defaultTo("active").index();
            table.boolean("enabled").notNullable().defaultTo(true);
            table.bigInteger("created_at").notNullable();
          });
        }
        const locksExist = await knex.schema.hasTable("scheduler_locks");
        if (!locksExist) {
          await knex.schema.createTable("scheduler_locks", (table) => {
            table.string("lock_id", 255).primary();
            table.string("node_id", 255).notNullable();
            table.string("lock_token", 36).notNullable();
            table.bigInteger("acquired_at").notNullable();
            table.bigInteger("expires_at").notNullable();
          });
        }
      }
      getKnex() {
        if (!this.knex) {
          throw new Error("[KnexStore] Not initialized. Call initialize() first.");
        }
        return this.knex;
      }
      // --- CRUD ---
      async create(schedule) {
        const knex = this.getKnex();
        const newSchedule = {
          ...schedule,
          id: uuidv4(),
          createdAt: Date.now(),
          runCount: 0,
          failureCount: 0,
          status: "active"
        };
        await knex("schedules").insert(toInsertRow(newSchedule));
        logger.info(`[KnexStore] Created schedule ${newSchedule.id} for user ${newSchedule.creatorId}`);
        return newSchedule;
      }
      async importSchedule(schedule) {
        const knex = this.getKnex();
        const existing = await knex("schedules").where("id", schedule.id).first();
        if (existing) return;
        await knex("schedules").insert(toInsertRow(schedule));
      }
      async get(id) {
        const knex = this.getKnex();
        const row = await knex("schedules").where("id", id).first();
        return row ? fromDbRow(row) : void 0;
      }
      async update(id, patch) {
        const knex = this.getKnex();
        const existing = await knex("schedules").where("id", id).first();
        if (!existing) return void 0;
        const current = fromDbRow(existing);
        const updated = { ...current, ...patch, id: current.id };
        const row = toInsertRow(updated);
        delete row.id;
        await knex("schedules").where("id", id).update(row);
        return updated;
      }
      async delete(id) {
        const knex = this.getKnex();
        const deleted = await knex("schedules").where("id", id).del();
        if (deleted > 0) {
          logger.info(`[KnexStore] Deleted schedule ${id}`);
          return true;
        }
        return false;
      }
      // --- Queries ---
      async getByCreator(creatorId) {
        const knex = this.getKnex();
        const rows = await knex("schedules").where("creator_id", creatorId);
        return rows.map((r) => fromDbRow(r));
      }
      async getActiveSchedules() {
        const knex = this.getKnex();
        const rows = await knex("schedules").where("status", "active");
        return rows.map((r) => fromDbRow(r));
      }
      async getDueSchedules(now) {
        const ts = now ?? Date.now();
        const knex = this.getKnex();
        const bFalse = this.driver === "mssql" ? 0 : false;
        const bTrue = this.driver === "mssql" ? 1 : true;
        const rows = await knex("schedules").where("status", "active").andWhere(function() {
          this.where(function() {
            this.where("is_recurring", bFalse).whereNotNull("run_at").where("run_at", "<=", ts);
          }).orWhere(function() {
            this.where("is_recurring", bTrue).whereNotNull("next_run_at").where("next_run_at", "<=", ts);
          });
        });
        return rows.map((r) => fromDbRow(r));
      }
      async findByWorkflow(creatorId, workflowName) {
        const knex = this.getKnex();
        const escaped = workflowName.toLowerCase().replace(/[%_\\]/g, "\\$&");
        const pattern = `%${escaped}%`;
        const rows = await knex("schedules").where("creator_id", creatorId).where("status", "active").whereRaw("LOWER(workflow) LIKE ? ESCAPE '\\'", [pattern]);
        return rows.map((r) => fromDbRow(r));
      }
      async getAll() {
        const knex = this.getKnex();
        const rows = await knex("schedules");
        return rows.map((r) => fromDbRow(r));
      }
      async getStats() {
        const knex = this.getKnex();
        const boolTrue = this.driver === "mssql" ? "1" : "true";
        const boolFalse = this.driver === "mssql" ? "0" : "false";
        const result = await knex("schedules").select(
          knex.raw("COUNT(*) as total"),
          knex.raw("SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active"),
          knex.raw("SUM(CASE WHEN status = 'paused' THEN 1 ELSE 0 END) as paused"),
          knex.raw("SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed"),
          knex.raw("SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed"),
          knex.raw(`SUM(CASE WHEN is_recurring = ${boolTrue} THEN 1 ELSE 0 END) as recurring`),
          knex.raw(`SUM(CASE WHEN is_recurring = ${boolFalse} THEN 1 ELSE 0 END) as one_time`)
        ).first();
        return {
          total: Number(result.total) || 0,
          active: Number(result.active) || 0,
          paused: Number(result.paused) || 0,
          completed: Number(result.completed) || 0,
          failed: Number(result.failed) || 0,
          recurring: Number(result.recurring) || 0,
          oneTime: Number(result.one_time) || 0
        };
      }
      async validateLimits(creatorId, isRecurring, limits) {
        const knex = this.getKnex();
        if (limits.maxGlobal) {
          const result = await knex("schedules").count("* as cnt").first();
          if (Number(result?.cnt) >= limits.maxGlobal) {
            throw new Error(`Global schedule limit reached (${limits.maxGlobal})`);
          }
        }
        if (limits.maxPerUser) {
          const result = await knex("schedules").where("creator_id", creatorId).count("* as cnt").first();
          if (Number(result?.cnt) >= limits.maxPerUser) {
            throw new Error(`You have reached the maximum number of schedules (${limits.maxPerUser})`);
          }
        }
        if (isRecurring && limits.maxRecurringPerUser) {
          const bTrue = this.driver === "mssql" ? 1 : true;
          const result = await knex("schedules").where("creator_id", creatorId).where("is_recurring", bTrue).count("* as cnt").first();
          if (Number(result?.cnt) >= limits.maxRecurringPerUser) {
            throw new Error(
              `You have reached the maximum number of recurring schedules (${limits.maxRecurringPerUser})`
            );
          }
        }
      }
      // --- HA Distributed Locking (via scheduler_locks table) ---
      async tryAcquireLock(lockId, nodeId, ttlSeconds) {
        const knex = this.getKnex();
        const now = Date.now();
        const expiresAt = now + ttlSeconds * 1e3;
        const token = uuidv4();
        const updated = await knex("scheduler_locks").where("lock_id", lockId).where("expires_at", "<", now).update({
          node_id: nodeId,
          lock_token: token,
          acquired_at: now,
          expires_at: expiresAt
        });
        if (updated > 0) return token;
        try {
          await knex("scheduler_locks").insert({
            lock_id: lockId,
            node_id: nodeId,
            lock_token: token,
            acquired_at: now,
            expires_at: expiresAt
          });
          return token;
        } catch {
          return null;
        }
      }
      async releaseLock(lockId, lockToken) {
        const knex = this.getKnex();
        await knex("scheduler_locks").where("lock_id", lockId).where("lock_token", lockToken).del();
      }
      async renewLock(lockId, lockToken, ttlSeconds) {
        const knex = this.getKnex();
        const now = Date.now();
        const expiresAt = now + ttlSeconds * 1e3;
        const updated = await knex("scheduler_locks").where("lock_id", lockId).where("lock_token", lockToken).update({ acquired_at: now, expires_at: expiresAt });
        return updated > 0;
      }
      async flush() {
      }
      // --- Message Trigger CRUD ---
      async createTrigger(trigger) {
        const knex = this.getKnex();
        const newTrigger = {
          ...trigger,
          id: uuidv4(),
          createdAt: Date.now()
        };
        await knex("message_triggers").insert(toTriggerInsertRow(newTrigger));
        logger.info(`[KnexStore] Created trigger ${newTrigger.id} for user ${newTrigger.creatorId}`);
        return newTrigger;
      }
      async getTrigger(id) {
        const knex = this.getKnex();
        const row = await knex("message_triggers").where("id", id).first();
        return row ? fromTriggerRow(row) : void 0;
      }
      async updateTrigger(id, patch) {
        const knex = this.getKnex();
        const existing = await knex("message_triggers").where("id", id).first();
        if (!existing) return void 0;
        const current = fromTriggerRow(existing);
        const updated = {
          ...current,
          ...patch,
          id: current.id,
          createdAt: current.createdAt
        };
        const row = toTriggerInsertRow(updated);
        delete row.id;
        await knex("message_triggers").where("id", id).update(row);
        return updated;
      }
      async deleteTrigger(id) {
        const knex = this.getKnex();
        const deleted = await knex("message_triggers").where("id", id).del();
        if (deleted > 0) {
          logger.info(`[KnexStore] Deleted trigger ${id}`);
          return true;
        }
        return false;
      }
      async getTriggersByCreator(creatorId) {
        const knex = this.getKnex();
        const rows = await knex("message_triggers").where("creator_id", creatorId);
        return rows.map((r) => fromTriggerRow(r));
      }
      async getActiveTriggers() {
        const knex = this.getKnex();
        const rows = await knex("message_triggers").where("status", "active").where("enabled", this.driver === "mssql" ? 1 : true);
        return rows.map((r) => fromTriggerRow(r));
      }
    };
  }
});
init_knex_store();
export {
  KnexStoreBackend
};
//# sourceMappingURL=knex-store-CRORFJE6.mjs.map