// 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/telemetry.ts
var VALID_LOG_LEVELS = ["debug", "info", "warn", "error", "fatal"];
function consoleFor(level) {
  switch (level) {
    case "debug":
      return console.debug.bind(console);
    case "info":
      return console.info.bind(console);
    case "warn":
      return console.warn.bind(console);
    case "error":
      return console.error.bind(console);
    case "fatal":
      return console.error.bind(console);
    default:
      return console.log.bind(console);
  }
}
var TRACK_FLUSH_MS = 500;
var TRACK_MAX_QUEUE = 50;
function createTelemetryNamespace(config) {
  const queue = [];
  let flushTimer = null;
  let _inflightDrain = null;
  async function drainQueue() {
    if (queue.length === 0) return;
    const batch = queue.splice(0, queue.length);
    const work = Promise.allSettled(
      batch.map(async (ev) => {
        try {
          const body = { name: ev.name };
          if (ev.properties && typeof ev.properties === "object") {
            body.properties = ev.properties;
          }
          if (ev.timestamp) body.timestamp = ev.timestamp;
          await httpRequest(config, {
            method: "POST",
            path: "/api/sdk/v1/telemetry/event",
            body,
            requireAuth: true,
            retries: 1
          });
        } catch (err) {
          if (typeof console !== "undefined") {
            console.warn("[neetru-sdk] telemetry.track flush failed for event", ev.name, err);
          }
        }
      })
    );
    _inflightDrain = work.then(() => {
      _inflightDrain = null;
    });
    await work;
    flushTimer = null;
    if (queue.length > 0) {
      scheduleFlush();
    }
  }
  function scheduleFlush() {
    if (flushTimer) return;
    flushTimer = setTimeout(() => {
      void drainQueue();
    }, TRACK_FLUSH_MS);
  }
  return {
    /**
     * Persiste um evento de uso. Lança `NeetruError` em qualquer falha
     * (incluindo rate-limit).
     *
     * @example
     * ```ts
     * await client.telemetry.event({
     *   name: 'dashboard_opened',
     *   properties: { plan: 'pro', tab: 'overview' },
     * });
     * ```
     */
    async event(input) {
      if (!input || typeof input !== "object") {
        throw new NeetruError("validation_failed", "event input is required");
      }
      if (typeof input.name !== "string" || input.name.length === 0) {
        throw new NeetruError("validation_failed", "event.name is required");
      }
      if (input.name.length > 128) {
        throw new NeetruError("validation_failed", "event.name max 128 chars");
      }
      const body = { name: input.name };
      if (input.properties && typeof input.properties === "object") {
        body.properties = input.properties;
      }
      if (input.timestamp) body.timestamp = input.timestamp;
      const raw = await httpRequest(config, {
        method: "POST",
        path: "/api/sdk/v1/telemetry/event",
        body,
        requireAuth: true
      });
      if (!raw || raw.ok !== true || typeof raw.eventId !== "string") {
        throw new NeetruError("invalid_response", "Telemetry response missing eventId");
      }
      return { ok: true, eventId: raw.eventId };
    },
    /**
     * Fire-and-forget: enqueue + flush 500ms debounce. Não retorna `eventId`
     * — falhas são logadas como warning. Ideal pra alta frequência.
     *
     * @example
     * ```ts
     * client.telemetry.track({ name: 'page_view', properties: { path: '/' } });
     * // segue execução; flush async em background
     * ```
     */
    track(input) {
      if (!input || typeof input !== "object") return;
      if (typeof input.name !== "string" || input.name.length === 0) return;
      if (input.name.length > 128) return;
      queue.push(input);
      if (queue.length >= TRACK_MAX_QUEUE) {
        if (flushTimer) {
          clearTimeout(flushTimer);
          flushTimer = null;
        }
        void drainQueue();
      } else {
        scheduleFlush();
      }
    },
    /**
     * Força flush imediato da queue de `track()`. Resolva antes de
     * page unload / SSR boundary pra não perder eventos.
     */
    async flush() {
      if (flushTimer) {
        clearTimeout(flushTimer);
        flushTimer = null;
      }
      if (_inflightDrain) await _inflightDrain;
      await drainQueue();
    },
    /**
     * Registra um log estruturado per-product (Sprint 6).
     *
     * - `NEETRU_ENV=dev`: console.{level}, retorna ack mock.
     * - workspace/prod: HTTP POST com Bearer auth + correlationId no header.
     *
     * @example
     * ```ts
     * await client.telemetry.log({
     *   level: 'error',
     *   message: 'Falha ao calcular total',
     *   metadata: { orderId: 'o-123' },
     * });
     * ```
     */
    async log(input) {
      if (!input || typeof input !== "object") {
        throw new NeetruError("validation_failed", "log input is required");
      }
      if (!VALID_LOG_LEVELS.includes(input.level)) {
        throw new NeetruError("validation_failed", `level must be one of ${VALID_LOG_LEVELS.join(", ")}`);
      }
      if (typeof input.message !== "string" || input.message.length === 0) {
        throw new NeetruError("validation_failed", "message is required");
      }
      if (input.message.length > 4e3) {
        throw new NeetruError("validation_failed", "message max 4000 chars");
      }
      if (config.env === "dev") {
        const fn = consoleFor(input.level);
        fn(`[neetru-sdk] ${input.level}: ${input.message}`, input.metadata ?? {});
        return { ok: true, mode: "mock" };
      }
      const body = {
        level: input.level,
        message: input.message
      };
      if (input.metadata) body.metadata = input.metadata;
      const productSlug = input.productSlug ?? config.productId;
      if (productSlug) body.productSlug = productSlug;
      let cid = input.correlationId;
      if (!cid) {
        try {
          const g = globalThis.NEETRU_CORRELATION_ID;
          if (typeof g === "string" && g.length > 0) cid = g;
        } catch {
        }
      }
      const headers = {};
      if (cid) headers["x-correlation-id"] = cid;
      const raw = await httpRequest(config, {
        method: "POST",
        path: "/api/sdk/v1/telemetry/log",
        body,
        requireAuth: true,
        headers
      });
      if (!raw || raw.ok !== true) {
        throw new NeetruError("invalid_response", "Telemetry log response missing ok");
      }
      return {
        ok: true,
        logId: typeof raw.logId === "string" ? raw.logId : void 0,
        mode: "http"
      };
    }
  };
}

export { createTelemetryNamespace };
//# sourceMappingURL=telemetry.mjs.map
//# sourceMappingURL=telemetry.mjs.map