import {
  DEFAULT_TANGLE_PLATFORM_URL
} from "./chunk-ZDK7Y4QG.js";

// src/consumer.ts
var PLATFORM_USER_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
var DEFAULT_TIMEOUT_MS = 1e4;
var DEFAULT_MAX_ATTEMPTS = 2;
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
var IntegrationHubRequestError = class extends Error {
  name = "IntegrationHubRequestError";
  /** HTTP status, or 0 for a network-level failure. */
  status;
  /** Platform error code (`VALIDATION_ERROR`, `scope_missing`, …) or
   *  `network_error` / `http_error` when no structured code was returned. */
  code;
  /** `METHOD /path` the request targeted. */
  endpoint;
  /** True when the failure class is transient and a retry could succeed. */
  retryable;
  constructor(input) {
    super(input.message);
    this.status = input.status;
    this.code = input.code;
    this.endpoint = input.endpoint;
    this.retryable = input.retryable;
  }
};
var IntegrationHubClient = class {
  endpoint;
  product;
  auth;
  fetchImpl;
  timeoutMs;
  maxAttempts;
  constructor(options) {
    if (!options.product) {
      throw new Error("IntegrationHubClient: product is required");
    }
    if (options.auth.mode === "service" && !options.auth.serviceToken) {
      throw new Error("IntegrationHubClient: service auth requires a serviceToken");
    }
    if (options.auth.mode === "service" && !options.auth.serviceName) {
      throw new Error("IntegrationHubClient: service auth requires a serviceName");
    }
    if (options.auth.mode === "user-key" && !options.auth.apiKey) {
      throw new Error("IntegrationHubClient: user-key auth requires an apiKey");
    }
    this.endpoint = (options.endpoint ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\/+$/, "");
    this.product = options.product;
    this.auth = options.auth;
    this.fetchImpl = options.fetchImpl ?? fetch;
    this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
    this.maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
  }
  /**
   * Resolve a manifest against a user's connections. The returned
   * `ready` / `missing` split is the canonical way to ask "does this user
   * have the connections this work needs" — the raw connection list is not
   * reachable by a service token by design.
   */
  async resolveManifest(input) {
    return this.request("POST", "/resolve-manifest", input.userId, {
      product: input.product ?? this.product,
      manifest: input.manifest,
      ownerUserId: input.userId
    });
  }
  /**
   * Convenience over {@link resolveManifest} — probe a single connector and
   * get back a boolean plus the satisfying connection. The Surface-A quest
   * primitive ("is the user's GitHub linked?").
   */
  async checkConnector(input) {
    const requirementId = input.connectorId;
    const resolution = await this.resolveManifest({
      userId: input.userId,
      manifest: {
        id: `connectivity-check:${input.connectorId}`,
        requirements: [
          {
            id: requirementId,
            connectorId: input.connectorId,
            reason: `Connectivity check for ${input.connectorId}`,
            mode: input.mode ?? "read",
            ...input.requiredScopes ? { requiredScopes: input.requiredScopes } : {},
            ...input.requiredActions ? { requiredActions: input.requiredActions } : {}
          }
        ]
      }
    });
    const requirement = resolution.ready.find((r) => r.requirement.id === requirementId) ?? resolution.missing.find((r) => r.requirement.id === requirementId) ?? resolution.optionalMissing.find((r) => r.requirement.id === requirementId);
    if (!requirement) {
      throw new IntegrationHubRequestError({
        status: 0,
        code: "malformed_response",
        message: `resolve-manifest returned no resolution for requirement ${requirementId}`,
        endpoint: "POST /resolve-manifest",
        retryable: false
      });
    }
    return {
      connected: requirement.status === "ready",
      ...requirement.connection ? { connection: requirement.connection } : {},
      resolution: requirement
    };
  }
  /** Create grants for every satisfiable requirement of a manifest. The
   *  platform rejects the call if any non-optional requirement is missing a
   *  connection. */
  async createGrants(input) {
    const data = await this.request(
      "POST",
      "/grants",
      input.userId,
      {
        grantee: input.grantee,
        manifest: input.manifest,
        ownerUserId: input.userId,
        ...input.metadata ? { metadata: input.metadata } : {}
      }
    );
    return data.grants;
  }
  /** List the acting user's grants, optionally filtered to one grantee. */
  async listGrants(input) {
    const query = input.grantee !== void 0 ? `?granteeType=${encodeURIComponent(input.grantee.type)}&granteeId=${encodeURIComponent(input.grantee.id)}` : "";
    const data = await this.request(
      "GET",
      `/grants${query}`,
      input.userId
    );
    return data.grants;
  }
  /** Mint a short-lived capability bundle for a sandbox / agent process.
   *  Provider credentials never leave the platform — the bundle carries only
   *  scoped, expiring capability tokens. */
  async mintCapabilityBundle(input) {
    if (!input.manifestId && !(input.grantIds && input.grantIds.length > 0)) {
      throw new Error(
        "IntegrationHubClient.mintCapabilityBundle: manifestId or a non-empty grantIds is required"
      );
    }
    return this.request("POST", "/capabilities/bundle", input.userId, {
      subject: input.subject,
      ...input.manifestId ? { manifestId: input.manifestId } : {},
      ...input.grantIds ? { grantIds: input.grantIds } : {},
      ...input.grantee ? { grantee: input.grantee } : {},
      ...input.ttlMs !== void 0 ? { ttlMs: input.ttlMs } : {}
    });
  }
  /** Run live healthchecks across all of the acting user's connections. */
  async runHealthchecks(input) {
    const data = await this.request(
      "POST",
      "/healthchecks/run",
      input.userId,
      { ownerUserId: input.userId }
    );
    return data.healthchecks;
  }
  buildHeaders(userId, hasBody) {
    const headers = new Headers({ accept: "application/json" });
    if (hasBody) headers.set("content-type", "application/json");
    if (this.auth.mode === "service") {
      headers.set("authorization", `Bearer ${this.auth.serviceToken}`);
      headers.set("x-service-name", this.auth.serviceName);
      headers.set("x-platform-user-id", userId);
    } else {
      headers.set("authorization", `Bearer ${this.auth.apiKey}`);
    }
    return headers;
  }
  async request(method, path, userId, body) {
    if (!PLATFORM_USER_ID_PATTERN.test(userId)) {
      throw new IntegrationHubRequestError({
        status: 0,
        code: "invalid_user_id",
        message: `userId ${JSON.stringify(userId)} is not a valid platform user id`,
        endpoint: `${method} ${path}`,
        retryable: false
      });
    }
    const url = `${this.endpoint}/v1/integrations${path}`;
    const endpointLabel = `${method} /v1/integrations${path}`;
    const headers = this.buildHeaders(userId, body !== void 0);
    const init = {
      method,
      headers,
      ...body !== void 0 ? { body: JSON.stringify(body) } : {}
    };
    let lastError;
    for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
      let response;
      try {
        response = await this.fetchImpl(url, {
          ...init,
          signal: AbortSignal.timeout(this.timeoutMs)
        });
      } catch (error) {
        lastError = new IntegrationHubRequestError({
          status: 0,
          code: "network_error",
          message: `${endpointLabel} failed: ${error instanceof Error ? error.message : String(error)}`,
          endpoint: endpointLabel,
          retryable: true
        });
        if (attempt < this.maxAttempts) {
          await delay(attempt);
          continue;
        }
        throw lastError;
      }
      const payload = await readJson(response);
      if (response.ok && isSuccessEnvelope(payload)) {
        return payload.data;
      }
      const retryable = RETRYABLE_STATUSES.has(response.status);
      lastError = new IntegrationHubRequestError({
        status: response.status,
        code: errorCode(payload, response.status),
        message: errorMessage(payload, endpointLabel, response.status),
        endpoint: endpointLabel,
        retryable
      });
      if (retryable && attempt < this.maxAttempts) {
        await delay(attempt);
        continue;
      }
      throw lastError;
    }
    throw lastError ?? new IntegrationHubRequestError({
      status: 0,
      code: "unknown",
      message: `${endpointLabel} exhausted retries without a result`,
      endpoint: endpointLabel,
      retryable: false
    });
  }
};
function createIntegrationHubClient(options) {
  return new IntegrationHubClient(options);
}
function isSuccessEnvelope(payload) {
  return typeof payload === "object" && payload !== null && payload.success === true && "data" in payload;
}
async function readJson(response) {
  const text = await response.text().catch(() => "");
  if (!text) return void 0;
  try {
    return JSON.parse(text);
  } catch {
    return { __text: text };
  }
}
function errorCode(payload, status) {
  if (typeof payload === "object" && payload !== null) {
    const error = payload.error;
    if (error && typeof error.code === "string") return error.code;
  }
  return status === 0 ? "network_error" : "http_error";
}
function errorMessage(payload, endpointLabel, status) {
  if (typeof payload === "object" && payload !== null) {
    const record = payload;
    if (record.error && typeof record.error.message === "string") {
      return `${endpointLabel} \u2192 ${status}: ${record.error.message}`;
    }
    if (typeof record.__text === "string" && record.__text.length > 0) {
      return `${endpointLabel} \u2192 ${status}: ${record.__text.slice(0, 300)}`;
    }
  }
  return `${endpointLabel} \u2192 ${status}`;
}
function delay(attempt) {
  return new Promise((resolve) => setTimeout(resolve, attempt * 200));
}

export {
  IntegrationHubRequestError,
  IntegrationHubClient,
  createIntegrationHubClient
};
//# sourceMappingURL=chunk-O553GSCX.js.map