// 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 (opts.requireAuth) {
    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/validators.ts
var SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
function assertValidSdkId(field, value) {
  if (typeof value !== "string" || !SDK_ID_RE.test(value)) {
    throw new NeetruError(
      "validation_failed",
      `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`
    );
  }
}

// src/webhooks.ts
async function verifyWebhookSignature(payload, signature, secret, timestamp) {
  if (!signature || !secret) return false;
  const sigHex = signature.startsWith("sha256=") ? signature.slice(7) : signature;
  if (!/^[0-9a-f]+$/i.test(sigHex)) return false;
  const subtle = typeof globalThis !== "undefined" && "crypto" in globalThis ? globalThis.crypto?.subtle : void 0;
  if (!subtle) {
    throw new NeetruError(
      "runtime_unsupported",
      "verifyWebhookSignature requires globalThis.crypto.subtle (Node \u226520, modern browser, or Edge runtime)."
    );
  }
  const encoder = new TextEncoder();
  const keyBytes = encoder.encode(secret);
  const rawBytes = typeof payload === "string" ? encoder.encode(payload) : payload;
  let payloadBytes = rawBytes;
  if (timestamp !== void 0) {
    const prefix = encoder.encode(`${timestamp}.`);
    const merged = new Uint8Array(prefix.length + rawBytes.length);
    merged.set(prefix, 0);
    merged.set(rawBytes, prefix.length);
    payloadBytes = merged;
  }
  const key = await subtle.importKey(
    "raw",
    keyBytes,
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"]
  );
  const macBuffer = await subtle.sign(
    "HMAC",
    key,
    payloadBytes
  );
  const macBytes = new Uint8Array(macBuffer);
  const sigBytes = hexToBytes(sigHex.toLowerCase());
  if (!sigBytes || sigBytes.length !== macBytes.length) return false;
  let diff = 0;
  for (let i = 0; i < macBytes.length; i++) {
    diff |= macBytes[i] ^ sigBytes[i];
  }
  return diff === 0;
}
async function verifyWebhookRequest(payload, headers, secret, options = {}) {
  const toleranceMs = options.toleranceMs ?? 3e5;
  const getHeader = (name) => {
    if (typeof headers.get === "function") {
      return headers.get(name);
    }
    const rec = headers;
    const lower = name.toLowerCase();
    for (const key of Object.keys(rec)) {
      if (key.toLowerCase() === lower) return rec[key] ?? null;
    }
    return null;
  };
  const tsHeader = getHeader("x-neetru-timestamp");
  if (!tsHeader) {
    return { ok: false, reason: "timestamp_missing" };
  }
  const tsMs = Number(tsHeader);
  if (!Number.isFinite(tsMs)) {
    return { ok: false, reason: "timestamp_missing" };
  }
  const skewMs = Math.abs(Date.now() - tsMs);
  if (skewMs > toleranceMs) {
    return { ok: false, reason: "timestamp_expired" };
  }
  const sig = getHeader("x-neetru-signature");
  const valid = await verifyWebhookSignature(payload, sig, secret, tsHeader);
  if (!valid) {
    return { ok: false, reason: "invalid_signature" };
  }
  return { ok: true };
}
function hexToBytes(hex) {
  if (hex.length % 2 !== 0) return null;
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < bytes.length; i++) {
    const high = parseInt(hex[i * 2], 16);
    const low = parseInt(hex[i * 2 + 1], 16);
    if (Number.isNaN(high) || Number.isNaN(low)) return null;
    bytes[i] = high << 4 | low;
  }
  return bytes;
}
var VALID_EVENTS = [
  "subscription.activated",
  "subscription.cancelled",
  "subscription.payment_failed",
  "subscription.trial_ending",
  "usage.quota_exceeded",
  "account.suspended",
  "account.reactivated",
  "support.ticket_replied"
];
function toEndpoint(raw) {
  if (!raw || typeof raw !== "object") {
    throw new NeetruError("invalid_response", "Webhook response is not an object");
  }
  const r = raw;
  if (typeof r.id !== "string") {
    throw new NeetruError("invalid_response", "Webhook missing id");
  }
  return {
    id: r.id,
    url: typeof r.url === "string" ? r.url : "",
    events: Array.isArray(r.events) ? r.events.filter(
      (e) => VALID_EVENTS.includes(e)
    ) : [],
    hasSecret: r.hasSecret === true,
    status: r.status === "active" || r.status === "degraded" || r.status === "disabled" ? r.status : "active",
    lastDeliveryAt: typeof r.lastDeliveryAt === "string" ? r.lastDeliveryAt : void 0,
    consecutiveFailures: typeof r.consecutiveFailures === "number" ? r.consecutiveFailures : void 0,
    createdAt: typeof r.createdAt === "string" ? r.createdAt : (/* @__PURE__ */ new Date()).toISOString()
  };
}
function validateInput(input) {
  if (!input.url || typeof input.url !== "string") {
    throw new NeetruError("validation_failed", "url \xE9 obrigat\xF3ria");
  }
  try {
    const parsed = new URL(input.url);
    if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
      throw new Error("invalid protocol");
    }
  } catch {
    throw new NeetruError("validation_failed", `url inv\xE1lida: ${input.url}`);
  }
  if (!Array.isArray(input.events) || input.events.length === 0) {
    throw new NeetruError("validation_failed", "events deve ter pelo menos 1 evento");
  }
  for (const ev of input.events) {
    if (!VALID_EVENTS.includes(ev)) {
      throw new NeetruError("validation_failed", `evento desconhecido: ${ev}`);
    }
  }
  if (input.secret !== void 0 && input.secret.length < 16) {
    throw new NeetruError("validation_failed", "secret deve ter \u226516 chars (recomendado 32+)");
  }
}
function resolveProductId(config, ...candidates) {
  for (const c of candidates) {
    if (typeof c === "string" && c.length > 0) {
      assertValidSdkId("productId", c);
      return c;
    }
  }
  if (config.productId) {
    assertValidSdkId("productId", config.productId);
    return config.productId;
  }
  throw new NeetruError(
    "validation_failed",
    "productId required (pass to options/input or set on createNeetruClient)"
  );
}
function createWebhooksNamespace(config) {
  return {
    async register(input) {
      validateInput(input);
      const productId = resolveProductId(config, input.productId);
      const raw = await httpRequest(config, {
        method: "POST",
        path: "/api/sdk/v1/webhooks",
        body: {
          productId,
          url: input.url,
          events: input.events,
          ...input.secret ? { secret: input.secret } : {}
        },
        requireAuth: true
      });
      return toEndpoint(raw);
    },
    async list(options) {
      const productId = resolveProductId(config, options?.productId);
      const raw = await httpRequest(config, {
        method: "GET",
        path: "/api/sdk/v1/webhooks",
        query: { productId },
        requireAuth: true
      });
      const list = Array.isArray(raw?.endpoints) ? raw.endpoints : [];
      return list.map(toEndpoint);
    },
    async unregister(id, options) {
      if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
      const productId = resolveProductId(config, options?.productId);
      await httpRequest(config, {
        method: "DELETE",
        path: `/api/sdk/v1/webhooks/${encodeURIComponent(id)}`,
        query: { productId },
        requireAuth: true
      });
      return { ok: true };
    },
    async test(id, options) {
      if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
      const productId = resolveProductId(config, options?.productId);
      const raw = await httpRequest(config, {
        method: "POST",
        path: `/api/sdk/v1/webhooks/${encodeURIComponent(id)}/test`,
        query: { productId },
        requireAuth: true,
        idempotencyKey: true
      });
      if (!raw || typeof raw !== "object") {
        return { ok: false, error: "invalid response" };
      }
      const r = raw;
      return {
        ok: r.ok === true,
        statusCode: typeof r.statusCode === "number" ? r.statusCode : void 0,
        durationMs: typeof r.durationMs === "number" ? r.durationMs : void 0,
        error: typeof r.error === "string" ? r.error : void 0
      };
    }
  };
}
var MockWebhooks = class {
  endpoints = /* @__PURE__ */ new Map();
  nextId = 1;
  async register(input) {
    validateInput(input);
    const id = `mock_wh_${this.nextId++}`;
    const endpoint = {
      id,
      url: input.url,
      events: input.events,
      hasSecret: !!input.secret,
      status: "active",
      createdAt: (/* @__PURE__ */ new Date()).toISOString()
    };
    this.endpoints.set(id, endpoint);
    return endpoint;
  }
  async list(_options) {
    return [...this.endpoints.values()];
  }
  async unregister(id, _options) {
    if (!this.endpoints.has(id)) {
      throw new NeetruError("not_found", `Webhook ${id} n\xE3o encontrado`);
    }
    this.endpoints.delete(id);
    return { ok: true };
  }
  async test(id, _options) {
    if (!this.endpoints.has(id)) {
      throw new NeetruError("not_found", `Webhook ${id} n\xE3o encontrado`);
    }
    return { ok: true, statusCode: 200, durationMs: 42 };
  }
};

export { MockWebhooks, createWebhooksNamespace, verifyWebhookRequest, verifyWebhookSignature };
//# sourceMappingURL=webhooks.mjs.map
//# sourceMappingURL=webhooks.mjs.map