import {
  validateRouteTarget
} from "./chunk-2LGMW3DJ.js";
import {
  log
} from "./chunk-2ODBA7MQ.js";

// src/utils/iso-timestamp.ts
var ISO_UTC_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
var ISO_OFFSET_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
function validateDateComponents(isoString) {
  const match = isoString.match(
    /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/
  );
  if (!match) return false;
  const [, yStr, mStr, dStr, hStr, minStr, sStr] = match;
  const y = Number(yStr);
  const m = Number(mStr);
  const d = Number(dStr);
  const h = Number(hStr);
  const min = Number(minStr);
  const s = Number(sStr);
  if (m < 1 || m > 12) return false;
  if (d < 1 || d > 31) return false;
  if (h > 23 || min > 59 || s > 59) return false;
  const daysInMonth = new Date(y, m, 0).getDate();
  if (d > daysInMonth) return false;
  return true;
}
function validateOffset(isoString) {
  const offsetMatch = isoString.match(/([+-])(\d{2}):(\d{2})$/);
  if (!offsetMatch) return true;
  const oh = Number(offsetMatch[2]);
  const om = Number(offsetMatch[3]);
  if (oh > 14 || om > 59) return false;
  if (oh === 14 && om > 0) return false;
  return true;
}
function normalizeUtcForComparison(value) {
  const fracMatch = value.match(/\.(\d+)Z$/);
  if (fracMatch) {
    const ms = (fracMatch[1] + "000").slice(0, 3);
    return value.replace(/\.\d+Z$/, `.${ms}Z`);
  }
  return value.replace(/Z$/, ".000Z");
}
function parseIsoUtcTimestamp(value) {
  if (typeof value !== "string" || !ISO_UTC_TIMESTAMP_RE.test(value)) {
    return null;
  }
  const ts = Date.parse(value);
  if (!Number.isFinite(ts)) return null;
  if (!validateDateComponents(value)) return null;
  const roundTrip = new Date(ts).toISOString();
  if (roundTrip !== normalizeUtcForComparison(value)) return null;
  return ts;
}
function parseIsoOffsetTimestamp(value) {
  if (typeof value !== "string" || !ISO_OFFSET_TIMESTAMP_RE.test(value)) {
    return null;
  }
  const ts = Date.parse(value);
  if (!Number.isFinite(ts)) return null;
  if (!validateDateComponents(value)) return null;
  if (!validateOffset(value)) return null;
  if (value.endsWith("Z")) {
    const roundTrip = new Date(ts).toISOString();
    if (roundTrip !== normalizeUtcForComparison(value)) return null;
  }
  return ts;
}

// src/replay/types.ts
var VALID_SOURCES = /* @__PURE__ */ new Set(["openclaw", "claude", "chatgpt"]);
var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
var REPLAY_UNKNOWN_SESSION_KEY = "replay:unknown";
function isReplaySource(value) {
  return typeof value === "string" && VALID_SOURCES.has(value);
}
function isReplayRole(value) {
  return typeof value === "string" && VALID_ROLES.has(value);
}
function normalizeReplaySessionKey(value) {
  if (typeof value !== "string") return REPLAY_UNKNOWN_SESSION_KEY;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : REPLAY_UNKNOWN_SESSION_KEY;
}
function parseIsoTimestamp(value) {
  return parseIsoUtcTimestamp(value);
}
function validateReplayTurn(turn, index) {
  const issues = [];
  if (!turn || typeof turn !== "object") {
    issues.push({
      code: "turn.invalid",
      message: "Replay turn must be an object.",
      index
    });
    return issues;
  }
  if (!isReplayRole(turn.role)) {
    issues.push({
      code: "turn.role.invalid",
      message: `Replay role must be 'user' or 'assistant', received '${String(turn.role)}'.`,
      index
    });
  }
  if (!isReplaySource(turn.source)) {
    issues.push({
      code: "turn.source.invalid",
      message: `Replay source must be 'openclaw', 'claude', or 'chatgpt', received '${String(turn.source)}'.`,
      index
    });
  }
  if (!turn.sessionKey || typeof turn.sessionKey !== "string" || turn.sessionKey.trim().length === 0) {
    issues.push({
      code: "turn.sessionKey.invalid",
      message: "Replay sessionKey is required.",
      index
    });
  }
  if (!turn.content || typeof turn.content !== "string" || turn.content.trim().length === 0) {
    issues.push({
      code: "turn.content.invalid",
      message: "Replay content must be a non-empty string.",
      index
    });
  }
  if (!turn.timestamp || typeof turn.timestamp !== "string" || parseIsoTimestamp(turn.timestamp) === null) {
    issues.push({
      code: "turn.timestamp.invalid",
      message: `Replay timestamp must be a valid ISO timestamp, received '${String(turn.timestamp)}'.`,
      index
    });
  }
  return issues;
}

// src/routing/store.ts
import { lstat, mkdir, readFile, realpath, rename, rm, stat, writeFile } from "fs/promises";
import path from "path";
import { createHash } from "crypto";
function defaultState() {
  return {
    version: 1,
    updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
    rules: []
  };
}
function stableRuleId(rule) {
  const seed = JSON.stringify({
    patternType: rule.patternType,
    pattern: rule.pattern.trim(),
    priority: rule.priority,
    target: rule.target
  });
  return `route-${createHash("sha256").update(seed).digest("hex").slice(0, 12)}`;
}
function resolveStatePath(memoryDir, stateFile) {
  const root = path.resolve(memoryDir);
  const defaultPath = path.join(root, "state", "routing-rules.json");
  if (path.isAbsolute(stateFile)) {
    const absolute = path.resolve(stateFile);
    return absolute.startsWith(root + path.sep) ? absolute : defaultPath;
  }
  const resolved = path.resolve(root, stateFile);
  return resolved.startsWith(root + path.sep) ? resolved : defaultPath;
}
function normalizeRule(rule, options) {
  if (!rule || typeof rule !== "object") return null;
  if (rule.enabled === false) return null;
  if (rule.patternType !== "keyword" && rule.patternType !== "regex") return null;
  if (typeof rule.pattern !== "string" || rule.pattern.trim().length === 0) return null;
  if (typeof rule.priority !== "number" || !Number.isFinite(rule.priority)) return null;
  const targetValidation = validateRouteTarget(rule.target, options);
  if (!targetValidation.ok || !targetValidation.target) return null;
  const normalizedPriority = Math.trunc(rule.priority);
  const normalizedTarget = targetValidation.target;
  const id = typeof rule.id === "string" && rule.id.trim().length > 0 ? rule.id.trim() : stableRuleId({
    patternType: rule.patternType,
    pattern: rule.pattern.trim(),
    priority: normalizedPriority,
    target: normalizedTarget
  });
  return {
    id,
    patternType: rule.patternType,
    pattern: rule.pattern.trim(),
    priority: normalizedPriority,
    target: normalizedTarget,
    enabled: true
  };
}
var RoutingRulesStore = class {
  memoryRoot;
  statePath;
  lockPath;
  writeQueue = Promise.resolve();
  constructor(memoryDir, stateFile = "state/routing-rules.json") {
    this.memoryRoot = path.resolve(memoryDir);
    this.statePath = resolveStatePath(memoryDir, stateFile);
    this.lockPath = `${this.statePath}.lock`;
  }
  async read(options) {
    try {
      const persisted = await this.readPersistedRules();
      return persisted.map((rule) => normalizeRule(rule, options)).filter((rule) => rule !== null);
    } catch {
      return [];
    }
  }
  async write(rules, options) {
    return this.withWriteLock(async () => this.writeNormalized(rules, options));
  }
  async upsert(rule, options) {
    return this.withWriteLock(async () => {
      const existing = await this.readPersistedRules();
      const normalized = normalizeRule(rule, options);
      if (!normalized) return existing;
      const next = existing.filter((entry) => entry.id !== normalized.id);
      next.push(normalized);
      return this.writeNormalized(next);
    });
  }
  async removeByPattern(pattern) {
    return this.withWriteLock(async () => {
      const trimmed = pattern.trim();
      const existing = await this.readPersistedRules();
      const next = existing.filter((entry) => entry.pattern !== trimmed);
      if (next.length === existing.length) return existing;
      return this.writeNormalized(next);
    });
  }
  async reset() {
    await this.withWriteLock(async () => {
      const payload = defaultState();
      await this.assertStatePathScoped();
      await writeFile(this.statePath, JSON.stringify(payload, null, 2), "utf-8");
    });
  }
  dedupeById(rules) {
    const byId = /* @__PURE__ */ new Map();
    for (const rule of rules) {
      byId.set(rule.id, rule);
    }
    return Array.from(byId.values());
  }
  async readPersistedRules() {
    try {
      await this.assertStatePathScoped();
      const raw = await readFile(this.statePath, "utf-8");
      const parsed = JSON.parse(raw);
      if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.rules)) return [];
      const normalized = parsed.rules.map((rule) => normalizeRule(rule)).filter((rule) => rule !== null);
      return this.dedupeById(normalized);
    } catch {
      return [];
    }
  }
  async writeNormalized(rules, options) {
    const normalized = this.dedupeById(
      rules.map((rule) => normalizeRule(rule, options)).filter((rule) => rule !== null)
    );
    const payload = {
      version: 1,
      updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
      rules: normalized
    };
    const tmpPath = `${this.statePath}.tmp-${process.pid}-${Date.now()}`;
    try {
      await this.assertStatePathScoped();
      await writeFile(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
      await rename(tmpPath, this.statePath);
    } catch (err) {
      log.debug(`routing rules write failed: ${err}`);
      throw err;
    } finally {
      await rm(tmpPath, { force: true }).catch(() => {
      });
    }
    return normalized;
  }
  async withWriteLock(op) {
    const previous = this.writeQueue;
    let release = () => {
    };
    this.writeQueue = new Promise((resolve) => {
      release = resolve;
    });
    await previous;
    let unlock = null;
    try {
      unlock = await this.acquireFileLock();
      return await op();
    } finally {
      if (unlock) await unlock();
      release();
    }
  }
  async acquireFileLock() {
    const start = Date.now();
    const staleMs = 3e4;
    const timeoutMs = 5e3;
    let unexpectedLockError = null;
    await this.assertStatePathScoped();
    await mkdir(path.dirname(this.lockPath), { recursive: true });
    while (Date.now() - start < timeoutMs) {
      try {
        await mkdir(this.lockPath);
        return async () => {
          try {
            await rm(this.lockPath, { recursive: true, force: true });
          } catch {
          }
        };
      } catch (err) {
        const code = err.code;
        if (code !== "EEXIST") {
          unexpectedLockError = err;
          break;
        }
        try {
          const lockStat = await stat(this.lockPath);
          if (Date.now() - lockStat.mtimeMs > staleMs) {
            await rm(this.lockPath, { recursive: true, force: true });
            continue;
          }
        } catch {
        }
        await new Promise((resolve) => setTimeout(resolve, 25));
      }
    }
    if (unexpectedLockError) {
      throw unexpectedLockError;
    }
    throw new Error(`routing rules lock acquisition timed out after ${timeoutMs}ms`);
  }
  async assertStatePathScoped() {
    await mkdir(this.memoryRoot, { recursive: true });
    const canonicalRoot = await realpath(this.memoryRoot);
    const canonicalParent = await this.canonicalizePathWithoutCreating(path.dirname(this.statePath));
    const canonicalStatePath = path.join(canonicalParent, path.basename(this.statePath));
    if (!this.isPathInside(canonicalRoot, canonicalStatePath)) {
      throw new Error(`routing rules state path escaped memoryDir: ${canonicalStatePath}`);
    }
    await mkdir(path.dirname(this.statePath), { recursive: true });
    try {
      const stateStats = await lstat(this.statePath);
      if (stateStats.isSymbolicLink()) {
        const canonicalFile = await realpath(this.statePath);
        if (!this.isPathInside(canonicalRoot, canonicalFile)) {
          throw new Error(`routing rules state symlink escaped memoryDir: ${canonicalFile}`);
        }
      }
    } catch (err) {
      const code = err.code;
      if (code !== "ENOENT") {
        throw err;
      }
    }
  }
  isPathInside(root, candidate) {
    const normalizedRoot = path.resolve(root);
    const normalizedCandidate = path.resolve(candidate);
    if (normalizedCandidate === normalizedRoot) return true;
    if (normalizedRoot === path.parse(normalizedRoot).root) {
      return normalizedCandidate.startsWith(normalizedRoot);
    }
    return normalizedCandidate.startsWith(`${normalizedRoot}${path.sep}`);
  }
  async canonicalizePathWithoutCreating(targetPath) {
    const absoluteTarget = path.resolve(targetPath);
    let probe = absoluteTarget;
    while (true) {
      try {
        const canonicalProbe = await realpath(probe);
        const remainder = path.relative(probe, absoluteTarget);
        return path.resolve(canonicalProbe, remainder);
      } catch (err) {
        const code = err.code;
        if (code !== "ENOENT") {
          throw err;
        }
        const parent = path.dirname(probe);
        if (parent === probe) {
          return absoluteTarget;
        }
        probe = parent;
      }
    }
  }
};

export {
  parseIsoOffsetTimestamp,
  isReplaySource,
  isReplayRole,
  normalizeReplaySessionKey,
  parseIsoTimestamp,
  validateReplayTurn,
  RoutingRulesStore
};
//# sourceMappingURL=chunk-SRBJUAMP.js.map