// src/errors.ts
var NeetruError = class _NeetruError extends Error {
  code;
  status;
  requestId;
  constructor(code, message, status, requestId) {
    super(message);
    this.name = "NeetruError";
    this.code = code;
    this.status = status;
    this.requestId = requestId;
    Object.setPrototypeOf(this, _NeetruError.prototype);
  }
};

// src/idempotency.ts
function generateIdempotencyKey() {
  const cryptoObj = typeof globalThis !== "undefined" && "crypto" in globalThis ? globalThis.crypto : void 0;
  if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
    return cryptoObj.randomUUID();
  }
  throw new NeetruError(
    "runtime_unsupported",
    "crypto.randomUUID not available in this runtime. SDK 3.0 requires Node \u226520 or modern browser/Edge."
  );
}

// src/http.ts
var COMPAT_MATRIX = {
  // SDK major 3.x requer Core ≥ 2026.05.1 (suporte a X-Neetru-Version header,
  // runTransaction write-after-read, verifyWebhookRequest, webhooks 3.0+)
  3: "2026.05.1"
};
function stripRevision(v) {
  return v.split("+")[0] ?? v;
}
function compareCalVer(a, b) {
  const partsA = stripRevision(a).split(".").map(Number);
  const partsB = stripRevision(b).split(".").map(Number);
  const len = Math.max(partsA.length, partsB.length);
  for (let i = 0; i < len; i++) {
    const pa = partsA[i] ?? 0;
    const pb = partsB[i] ?? 0;
    if (pa !== pb) return pa - pb;
  }
  return 0;
}
function getSdkMajor() {
  try {
    const ver = globalThis.__NEETRU_SDK_VERSION__;
    if (ver) return parseInt(ver.split(".")[0] ?? "3", 10);
  } catch {
  }
  return 3;
}
var _warnedCoreTooOld = /* @__PURE__ */ new Set();
function checkCoreVersionCompat(coreVersion) {
  if (!coreVersion) return;
  const sdkMajor = getSdkMajor();
  const minCore = COMPAT_MATRIX[sdkMajor];
  if (!minCore) return;
  const stripped = stripRevision(coreVersion);
  if (compareCalVer(stripped, minCore) < 0) {
    const key = `${sdkMajor}:${stripped}`;
    if (!_warnedCoreTooOld.has(key)) {
      _warnedCoreTooOld.add(key);
      console.warn(
        `[Neetru SDK] Aten\xE7\xE3o: Core vers\xE3o "${coreVersion}" \xE9 anterior ao m\xEDnimo recomendado "${minCore}" para SDK major ${sdkMajor}. Alguns recursos podem n\xE3o funcionar corretamente. Atualize o Core para a vers\xE3o mais recente.`
      );
    }
  }
}
var DEFAULT_RETRIES_READ = 2;
var DEFAULT_RETRIES_WRITE = 0;
var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
var RETRYABLE_CODES = /* @__PURE__ */ new Set([
  "rate_limited",
  "server_error",
  "network_error"
]);
function backoffMs(attempt) {
  const base = 200 * Math.pow(4, attempt);
  const jitter = base * 0.2 * (Math.random() * 2 - 1);
  return Math.max(50, Math.round(base + jitter));
}
var MAX_RETRY_AFTER_MS = 6e4;
function parseRetryAfter(value) {
  if (!value) return null;
  const secs = Number(value);
  if (Number.isFinite(secs) && secs >= 0) return Math.min(Math.round(secs * 1e3), MAX_RETRY_AFTER_MS);
  const dateMs = Date.parse(value);
  if (Number.isFinite(dateMs)) {
    const delta = dateMs - Date.now();
    if (delta > 0) return Math.min(delta, MAX_RETRY_AFTER_MS);
  }
  return null;
}
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
function statusToCode(status) {
  if (status === 401) return "unauthorized";
  if (status === 403) return "forbidden";
  if (status === 404) return "not_found";
  if (status === 409) return "conflict";
  if (status === 422 || status === 400) return "validation_failed";
  if (status === 429) return "rate_limited";
  if (status >= 500) return "server_error";
  return "unknown";
}
function buildUrl(baseUrl, path, query) {
  const base = baseUrl.replace(/\/+$/, "");
  const p = path.startsWith("/") ? path : `/${path}`;
  const url = new URL(`${base}${p}`);
  if (query) {
    for (const [k, v] of Object.entries(query)) {
      if (v === void 0) continue;
      url.searchParams.set(k, String(v));
    }
  }
  return url.toString();
}
async function safeJson(res) {
  const text = await res.text();
  if (!text) return void 0;
  try {
    return JSON.parse(text);
  } catch {
    return void 0;
  }
}
async function httpRequest(config, opts) {
  const method = opts.method ?? "GET";
  const url = buildUrl(config.baseUrl, opts.path, opts.query);
  const defaultRetries = READ_METHODS.has(method) ? DEFAULT_RETRIES_READ : DEFAULT_RETRIES_WRITE;
  const maxRetries = opts.retries ?? defaultRetries;
  const headers = {
    accept: "application/json",
    ...opts.headers
  };
  {
    if (!config.apiKey) {
      throw new NeetruError(
        "missing_api_key",
        "This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var."
      );
    }
    headers.authorization = `Bearer ${config.apiKey}`;
  }
  if (opts.idempotencyKey) {
    const key = typeof opts.idempotencyKey === "string" ? opts.idempotencyKey : generateIdempotencyKey();
    headers["idempotency-key"] = key;
  }
  const bodyString = opts.body !== void 0 && method !== "GET" && method !== "DELETE" ? JSON.stringify(opts.body) : void 0;
  if (bodyString !== void 0) {
    headers["content-type"] = "application/json";
  }
  let lastError = null;
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const init = { method, headers };
    if (bodyString !== void 0) init.body = bodyString;
    init.signal = AbortSignal.timeout(3e4);
    let res;
    try {
      res = await config.fetch(url, init);
    } catch (err2) {
      const message2 = err2 instanceof DOMException && err2.name === "TimeoutError" ? "Network error: timeout after 30s" : `Network error: ${err2 instanceof Error ? err2.message : "fetch failed"}`;
      lastError = new NeetruError("network_error", message2);
      if (attempt < maxRetries) {
        await sleep(backoffMs(attempt));
        continue;
      }
      throw lastError;
    }
    const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
    if (res.ok) {
      const coreVersion = res.headers.get("x-neetru-version");
      if (coreVersion) checkCoreVersionCompat(coreVersion);
      const parsed = await safeJson(res);
      return parsed;
    }
    const body = await safeJson(res);
    let code = statusToCode(res.status);
    let message = `HTTP ${res.status}`;
    if (body && typeof body === "object" && "error" in body) {
      const errField = body.error;
      if (typeof errField === "string") {
        message = errField;
      } else if (errField && typeof errField === "object") {
        if (typeof errField.code === "string") code = errField.code;
        if (typeof errField.message === "string") message = errField.message;
      }
    }
    const err = new NeetruError(code, message, res.status, requestId);
    lastError = err;
    const isRetryable = RETRYABLE_CODES.has(code);
    if (isRetryable && attempt < maxRetries) {
      const retryAfter = parseRetryAfter(res.headers.get("retry-after"));
      const delay = retryAfter ?? backoffMs(attempt);
      await sleep(delay);
      continue;
    }
    throw err;
  }
  throw lastError ?? new NeetruError("unknown", "unexpected httpRequest exit");
}

// src/entitlements.ts
function toEntitlementCheck(raw) {
  if (!raw || typeof raw !== "object") {
    throw new NeetruError("invalid_response", "Entitlement response is not an object");
  }
  const r = raw;
  if (typeof r.allowed !== "boolean") {
    throw new NeetruError("invalid_response", "Entitlement response missing `allowed` boolean");
  }
  return {
    allowed: r.allowed,
    productSlug: typeof r.productSlug === "string" ? r.productSlug : "",
    feature: typeof r.feature === "string" ? r.feature : "",
    reason: typeof r.reason === "string" ? r.reason : void 0
  };
}
var CACHE_TTL_MS = 6e4;
var CACHE_MAX = 100;
function createEntitlementsNamespace(config) {
  const cache = /* @__PURE__ */ new Map();
  function cacheKey(productSlug, feature) {
    return `${productSlug}::${feature}`;
  }
  function readCache(key) {
    const entry = cache.get(key);
    if (!entry) return null;
    if (entry.expiresAt < Date.now()) {
      cache.delete(key);
      return null;
    }
    cache.delete(key);
    cache.set(key, entry);
    return entry.value;
  }
  function writeCache(key, value) {
    if (cache.size >= CACHE_MAX) {
      const oldest = cache.keys().next().value;
      if (oldest !== void 0) cache.delete(oldest);
    }
    cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
  }
  async function checkDetailed(productSlug, feature, opts = {}) {
    if (!productSlug) throw new NeetruError("validation_failed", "productSlug is required");
    if (!feature) throw new NeetruError("validation_failed", "feature is required");
    const key = cacheKey(productSlug, feature);
    if (!opts.cacheBust) {
      const cached = readCache(key);
      if (cached) return cached;
    }
    const raw = await httpRequest(config, {
      method: "GET",
      path: "/api/v1/sdk/entitlements/check",
      query: { slug: productSlug, feature }});
    const result = toEntitlementCheck(raw);
    writeCache(key, result);
    return result;
  }
  return {
    /**
     * Verifica se o caller pode usar `feature` no produto `productSlug`.
     * Retorno simples: `true` libera, `false` bloqueia. Cache 60s automático.
     *
     * Use `checkDetailed` se precisar do `reason` pra mensagem de upgrade.
     */
    async check(productSlug, feature, opts) {
      const result = await checkDetailed(productSlug, feature, opts);
      return result.allowed;
    },
    checkDetailed,
    /** Test helper: limpa o cache LRU. */
    __resetCache() {
      cache.clear();
    }
  };
}

export { createEntitlementsNamespace };
//# sourceMappingURL=entitlements.mjs.map
//# sourceMappingURL=entitlements.mjs.map