'use strict';

// 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/db-errors.ts
var RETRYABLE_CODES = /* @__PURE__ */ new Set([
  "db_unavailable",
  "db_conflict",
  "db_timeout"
]);
var NeetruDbError = class _NeetruDbError extends NeetruError {
  /** Código de erro fechado — específico de DB. */
  code;
  /**
   * `true` para erros transientes que o produto pode tentar novamente.
   * São retryable: `db_unavailable`, `db_conflict`, `db_timeout`.
   */
  retryable;
  /**
   * ID opaco do banco lógico — só para correlação com logs do Core.
   * Nunca deve ser exibido ao usuário final.
   */
  dbId;
  constructor(code, message, dbId) {
    super(code, message);
    this.name = "NeetruDbError";
    this.code = code;
    this.retryable = RETRYABLE_CODES.has(code);
    this.dbId = dbId;
    Object.setPrototypeOf(this, _NeetruDbError.prototype);
  }
};

// src/mocks.ts
var DEV_FIXTURE_USER = Object.freeze({
  uid: "dev-fixture-uid-0001",
  email: "dev@neetru.local",
  emailVerified: true,
  displayName: "Dev Fixture",
  isCustomer: true,
  isStaff: false
});
var MockAuth = class {
  _user;
  _listeners;
  constructor(initialUser = null) {
    this._user = initialUser;
    this._listeners = /* @__PURE__ */ new Set();
  }
  async signIn(_options) {
    if (!this._user) this._user = { ...DEV_FIXTURE_USER };
    this._notify();
    return this._user;
  }
  async signOut() {
    this._user = null;
    this._notify();
  }
  getUser() {
    return this._user;
  }
  onAuthStateChanged(listener) {
    this._listeners.add(listener);
    try {
      listener(this._user);
    } catch {
    }
    return () => {
      this._listeners.delete(listener);
    };
  }
  /**
   * Mock de `verifyToken` — decodifica o JWT sem verificar assinatura (pra testes).
   * Em dev, retorna o user atual do mock se já estiver logado. Aceita qualquer token
   * não-vazio sem chamar rede.
   *
   * Para simular falhas em testes, use `MockAuth.__mockVerifyToken(fn)`.
   */
  async verifyToken(token) {
    if (this._verifyTokenOverride) {
      return this._verifyTokenOverride(token);
    }
    if (!token) {
      throw Object.assign(new Error("Token inv\xE1lido"), { code: "token_invalid_signature" });
    }
    try {
      const parts = token.split(".");
      if (parts.length === 3) {
        const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
        const pad = "=".repeat((4 - b64.length % 4) % 4);
        const decoded = JSON.parse(
          typeof atob === "function" ? atob(b64 + pad) : Buffer.from(b64 + pad, "base64").toString("utf-8")
        );
        if (typeof decoded.sub === "string") {
          return {
            uid: decoded.sub,
            email: typeof decoded.email === "string" ? decoded.email : "",
            emailVerified: typeof decoded.email_verified === "boolean" ? decoded.email_verified : void 0,
            displayName: typeof decoded.name === "string" ? decoded.name : void 0,
            photoURL: typeof decoded.picture === "string" ? decoded.picture : void 0,
            isStaff: decoded.is_staff === true,
            isCustomer: decoded.is_customer === true,
            tenantId: typeof decoded.tenant_id === "string" ? decoded.tenant_id : void 0,
            ...decoded
          };
        }
      }
    } catch {
    }
    if (this._user) return this._user;
    throw Object.assign(new Error("Nenhum user mock dispon\xEDvel"), { code: "token_invalid_signature" });
  }
  /**
   * Mock de `handleRedirectCallback` (Bug 2 / 3.1.3) — não há redirect real no
   * mock, então retorna `null` por default. Use `__setPendingCallback(result)`
   * em testes pra simular um retorno do IdP.
   */
  async handleRedirectCallback(_currentUrl) {
    return this._pendingCallback;
  }
  /**
   * Mock de `getIdToken` (Bug 2 / 3.1.3). Retorna `null` por default; use
   * `__setIdToken(token)` pra injetar um token cru em testes.
   */
  getIdToken() {
    return this._idToken;
  }
  _pendingCallback = null;
  _idToken = null;
  /** Test helper — simula o material devolvido por um callback OIDC. */
  __setPendingCallback(result) {
    this._pendingCallback = result;
  }
  /** Test helper — injeta o id_token cru retornado por `getIdToken`. */
  __setIdToken(token) {
    this._idToken = token;
  }
  _verifyTokenOverride = null;
  /**
   * Injeta override pra `verifyToken` em testes — permite simular expiração,
   * assinatura inválida etc sem precisar de JWT real.
   *
   * @example
   * ```ts
   * const mock = new MockAuth(null);
   * mock.__mockVerifyToken(async () => { throw new NeetruError('token_expired', '...'); });
   * ```
   */
  __mockVerifyToken(fn) {
    this._verifyTokenOverride = fn;
  }
  /** Helper de tests — força um user state arbitrário. */
  __setUser(user) {
    this._user = user;
    this._notify();
  }
  _notify() {
    for (const l of this._listeners) {
      try {
        l(this._user);
      } catch {
      }
    }
  }
};
var MockUsage = class {
  _reports = [];
  _quotas;
  _counters = /* @__PURE__ */ new Map();
  constructor(initialQuotas = {}) {
    this._quotas = new Map(Object.entries(initialQuotas));
  }
  async report(resource, qty = 1, options) {
    if (!resource) throw new Error("resource required");
    const safeQty = Number.isFinite(qty) && qty > 0 ? Math.floor(qty) : 1;
    const existing = this._counters.get(resource) ?? 0;
    const next = existing + safeQty;
    this._counters.set(resource, next);
    this._reports.push({
      resource,
      qty: safeQty,
      productId: options?.productId,
      tenantId: options?.tenantId,
      timestamp: (/* @__PURE__ */ new Date()).toISOString()
    });
    const limitFixture = this._quotas.get(resource);
    const limit = limitFixture?.limit ?? -1;
    return {
      ok: true,
      counterId: `mock_${resource}`,
      value: next,
      limit,
      remaining: limit < 0 ? -1 : Math.max(0, limit - next),
      status: limit > 0 && next >= limit ? "read_only_overlimit" : "ok"
    };
  }
  async check(resource, _options) {
    const quota = this._quotas.get(resource);
    const used = this._counters.get(resource) ?? 0;
    if (!quota) {
      return { allowed: true, reason: "granted", limit: -1, remaining: -1, behavior: null };
    }
    const remaining = quota.limit < 0 ? -1 : Math.max(0, quota.limit - used);
    if (remaining === 0) {
      return {
        allowed: false,
        reason: "limit_exceeded",
        limit: quota.limit,
        remaining: 0,
        behavior: "readonly"
      };
    }
    return {
      allowed: true,
      reason: "granted",
      limit: quota.limit,
      remaining,
      behavior: null
    };
  }
  /** Test helper — retorna histórico de `report()` calls. */
  __getReports() {
    return [...this._reports];
  }
  /** @deprecated Removido em 3.0 (usage.track foi removido). Use `__getReports`. */
  __getRecords() {
    return this.__getReports();
  }
  /** Test helper — substitui quota fixture. */
  __setQuota(metric, quota) {
    this._quotas.set(metric, quota);
  }
  /** Test helper — limpa estado. */
  __reset() {
    this._reports = [];
    this._quotas.clear();
    this._counters.clear();
  }
};
var MockSupport = class {
  _tickets = [];
  _ticketSeq = 0;
  async createTicket(input) {
    if (!input?.subject) throw new NeetruError("validation_failed", "subject required");
    if (!input?.message) throw new NeetruError("validation_failed", "message required");
    if (!input?.productSlug) {
      throw new NeetruError(
        "validation_failed",
        "productSlug required (3.0 breaking \u2014 pass to input or set on createNeetruClient)"
      );
    }
    const ticket = {
      id: `mock-ticket-${++this._ticketSeq}`,
      subject: input.subject,
      message: input.message,
      severity: input.severity ?? "normal",
      status: "open",
      createdAt: (/* @__PURE__ */ new Date()).toISOString(),
      productSlug: input.productSlug
    };
    this._tickets.push(ticket);
    return ticket;
  }
  async listMyTickets(options) {
    if (options?.productSlug) {
      return this._tickets.filter((t) => t.productSlug === options.productSlug);
    }
    return [...this._tickets];
  }
  /** Test helper. */
  __reset() {
    this._tickets = [];
    this._ticketSeq = 0;
  }
};
var MockEntitlements = class {
  _denied = /* @__PURE__ */ new Set();
  async check(productSlug, feature) {
    return !this._denied.has(`${productSlug}:${feature}`);
  }
  async checkDetailed(productSlug, feature) {
    const allowed = await this.check(productSlug, feature);
    return {
      allowed,
      productSlug,
      feature,
      reason: allowed ? "granted" : "mock_denied"
    };
  }
  /** Test helper. */
  __deny(productSlug, feature) {
    this._denied.add(`${productSlug}:${feature}`);
  }
  /** Test helper. */
  __reset() {
    this._denied.clear();
  }
};
var MockDb = class {
  _store = /* @__PURE__ */ new Map();
  _fixtures;
  constructor(initialFixtures = {}) {
    this._fixtures = new Map(Object.entries(initialFixtures));
  }
  collection(name) {
    const _store = this._store;
    const _fixtures = this._fixtures;
    if (!_store.has(name)) {
      const init = _fixtures.get(name);
      _store.set(
        name,
        init ? new Map(Object.entries(init)) : /* @__PURE__ */ new Map()
      );
    }
    const coll = _store.get(name);
    const matchesFilter = (doc, f) => {
      const v = doc[f.field];
      switch (f.op) {
        case "==":
          return v === f.value;
        case "!=":
          return v !== f.value;
        case "<":
          return typeof v === "number" && typeof f.value === "number" && v < f.value;
        case "<=":
          return typeof v === "number" && typeof f.value === "number" && v <= f.value;
        case ">":
          return typeof v === "number" && typeof f.value === "number" && v > f.value;
        case ">=":
          return typeof v === "number" && typeof f.value === "number" && v >= f.value;
        case "in":
          return Array.isArray(f.value) && f.value.includes(v);
        case "array-contains":
          return Array.isArray(v) && v.includes(f.value);
        case "not-in":
          return Array.isArray(f.value) && !f.value.includes(v);
        default:
          return true;
      }
    };
    let autoSeq = 0;
    function buildListResult(items) {
      return {
        docs: items.map((d, i) => ({ id: d.id ?? String(i), data: d })),
        nextCursor: null,
        fromCache: false,
        stale: false,
        hasPendingWrites: false,
        changes: []
      };
    }
    function buildGetResult(data, id) {
      return {
        docs: [{ id, data }],
        fromCache: false,
        stale: false,
        hasPendingWrites: false,
        changes: []
      };
    }
    const ref = {
      async list(q) {
        let items = Array.from(coll.values());
        if (q?.where && q.where.length > 0) {
          items = items.filter(
            (doc) => q.where.every((f) => matchesFilter(doc, f))
          );
        }
        if (q?.orderBy) {
          const { field, direction } = q.orderBy;
          items = items.slice().sort((a, b) => {
            const av = a[field];
            const bv = b[field];
            const avs = av ?? "";
            const bvs = bv ?? "";
            const cmp = avs < bvs ? -1 : avs > bvs ? 1 : 0;
            return direction === "desc" ? -cmp : cmp;
          });
        }
        const limit = q?.limit ?? 20;
        items = items.slice(0, limit);
        return buildListResult(items);
      },
      async get(id) {
        const data = coll.get(id);
        if (!data) return null;
        return buildGetResult(data, id);
      },
      async add(data) {
        const id = `mock-${++autoSeq}-${Math.random().toString(36).slice(2, 8)}`;
        coll.set(id, { ...data, id });
        return { ok: true, id };
      },
      async set(id, data) {
        coll.set(id, { ...data, id });
        return { ok: true };
      },
      async update(id, data) {
        const cur = coll.get(id);
        if (!cur) {
          coll.set(id, { ...data, id });
        } else {
          coll.set(id, { ...cur, ...data });
        }
        return { ok: true };
      },
      async remove(id) {
        coll.delete(id);
        return { ok: true };
      },
      async batch(ops) {
        for (const op of ops) {
          const collMap = coll;
          if (op.kind === "add") {
            const id = `mock-batch-${++autoSeq}`;
            collMap.set(id, { ...op.data, id });
          } else if (op.kind === "set") {
            collMap.set(op.id, { ...op.data, id: op.id });
          } else if (op.kind === "update") {
            const cur = collMap.get(op.id);
            collMap.set(op.id, { ...cur ?? {}, ...op.data, id: op.id });
          } else if (op.kind === "remove") {
            collMap.delete(op.id);
          }
        }
        return { ok: true };
      },
      onDoc(id, cb) {
        const data = coll.get(id);
        Promise.resolve().then(() => cb(data ? data : null));
        return () => {
        };
      },
      onSnapshot(q, cb) {
        Promise.resolve().then(async () => {
          const snap = await ref.list(q);
          cb(snap);
        });
        return () => {
        };
      },
      doc(id) {
        return {
          get id() {
            return id;
          },
          get path() {
            return `${name}/${id}`;
          },
          get collection() {
            return name;
          },
          async get() {
            const data = coll.get(id);
            if (!data) return null;
            return buildGetResult(data, id);
          },
          async set(data) {
            coll.set(id, { ...data, id });
            return { ok: true };
          },
          async update(data) {
            const cur = coll.get(id);
            if (!cur) {
              coll.set(id, { ...data, id });
            } else {
              coll.set(id, { ...cur, ...data });
            }
            return { ok: true };
          },
          async remove() {
            coll.delete(id);
            return { ok: true };
          },
          onSnapshot(cb) {
            const data = coll.get(id);
            Promise.resolve().then(
              () => cb(data ? buildGetResult(data, id) : null)
            );
            return () => {
            };
          }
        };
      }
    };
    return ref;
  }
  /**
   * Implementa SQL in-memory para testes — preferência: `@electric-sql/pglite`
   * + `drizzle-orm/pglite`; fallback: `pg-mem` + `drizzle-orm/node-postgres`
   * com strip interceptor.
   *
   * bug_6bae8674461f4600809e2f9de13c6043 (HIGH 26/05): fix anterior (2.3.3)
   * teve efeito INVERSO. Injetar `pool.types.getTypeParser` fez drizzle-orm
   * passar `query.types.getTypeParser` pro pg-mem, que tem guard interno
   * `if (query.types?.getTypeParser) throw NotSupported`. Ou seja: presença
   * de getTypeParser é o gatilho do erro — não ausência.
   *
   * Solução A (pglite, preferida): `@electric-sql/pglite` é WASM-Postgres
   * nativo — sem adapter pg-mem, sem esse conflito de tipos.
   * Solução B (fallback pg-mem): strip interceptor no pool.query que deleta
   * `types.getTypeParser` do objeto de query ANTES do pg-mem ver. Compat com
   * consumers que só têm pg-mem nas devDeps.
   *
   * Use `options.initSql` pra rodar DDL no setup (CREATE TABLE etc).
   */
  async sql(schema, options) {
    const ddls = options?.initSql ? Array.isArray(options.initSql) ? options.initSql : [options.initSql] : [];
    try {
      const pglitePkg = await import(
        /* webpackIgnore: true */
        '@electric-sql/pglite'
      );
      const drizzlePglitePkg = await import(
        /* webpackIgnore: true */
        'drizzle-orm/pglite'
      );
      const pglite = await pglitePkg.PGlite.create();
      for (const ddl of ddls) {
        await pglite.query(ddl);
      }
      const pgliteOrm = drizzlePglitePkg.drizzle(pglite, { schema });
      const ormAsNode = pgliteOrm;
      return {
        orm: ormAsNode,
        async transaction(fn, _opts) {
          return await pgliteOrm.transaction(fn);
        },
        async close() {
          if (!pglite.closed) await pglite.close();
        }
      };
    } catch (_pgliteErr) {
    }
    let pgMemMod;
    let drizzlePg;
    try {
      pgMemMod = await import(
        /* webpackIgnore: true */
        'pg-mem'
      );
      drizzlePg = await import(
        /* webpackIgnore: true */
        'drizzle-orm/node-postgres'
      );
    } catch {
      throw new NeetruDbError(
        "db_unavailable",
        "[MockDb] sql() requer @electric-sql/pglite OU pg-mem + drizzle-orm + pg como devDependencies. Preferido: npm install -D @electric-sql/pglite drizzle-orm. Alternativo: npm install -D pg-mem drizzle-orm pg."
      );
    }
    const memDb = pgMemMod.newDb({ noAstCoverageCheck: true });
    if (ddls.length > 0) {
      for (const ddl of ddls) {
        memDb.public.none(ddl);
      }
    }
    const adapters = memDb.adapters.createPg();
    const PoolCtor = adapters.Pool;
    const pool = new PoolCtor();
    const origQuery = pool.query.bind(pool);
    pool.query = (q, ...rest) => {
      if (q !== null && typeof q === "object") {
        const queryObj = q;
        const types = queryObj["types"];
        if (types !== null && typeof types === "object" && "getTypeParser" in types) {
          delete types["getTypeParser"];
        }
      }
      return origQuery(q, ...rest);
    };
    const orm = drizzlePg.drizzle(pool, { schema });
    return {
      orm,
      async transaction(fn, _opts) {
        return await orm.transaction(fn);
      },
      async close() {
        const p = pool;
        if (p.end) await p.end();
      }
    };
  }
  get syncState() {
    return {
      status: "idle",
      pendingWrites: 0,
      lastSyncedAt: null,
      isLeaderTab: true
    };
  }
  onSyncStateChanged(_cb) {
    return () => {
    };
  }
  async flush() {
  }
  async clearCache() {
    this._store.clear();
  }
  async getConflicts() {
    return [];
  }
  /** Test helper — substitui fixture inteira de uma collection. */
  __setFixture(name, items) {
    this._store.set(name, new Map(Object.entries(items)));
  }
  /** Test helper — reset total. */
  __reset() {
    this._store.clear();
  }
};

exports.DEV_FIXTURE_USER = DEV_FIXTURE_USER;
exports.MockAuth = MockAuth;
exports.MockDb = MockDb;
exports.MockEntitlements = MockEntitlements;
exports.MockSupport = MockSupport;
exports.MockUsage = MockUsage;
//# sourceMappingURL=mocks.cjs.map
//# sourceMappingURL=mocks.cjs.map