import {
  clearCookieHeader,
  readCookieValue,
  serializeCookie
} from "../chunk-HFC4BTWJ.js";
import {
  isTangleBillingEnforcementDisabled,
  resolveTangleDevOrUserKey
} from "../chunk-7W5XSTUF.js";

// src/platform/sso.ts
var DEFAULT_STATE_TTL_SECONDS = 600;
var DEFAULT_SESSION_TTL_SECONDS = 60 * 60 * 24 * 7;
var DEFAULT_REDIRECT_PATH = "/app";
var DEFAULT_LOGIN_PATH = "/login";
var DEFAULT_SESSION_COOKIE = "better-auth.session_token";
function randomHex(bytes) {
  const buf = new Uint8Array(bytes);
  crypto.getRandomValues(buf);
  return Array.from(buf, (b) => b.toString(16).padStart(2, "0")).join("");
}
async function hmacBytes(secret, value) {
  const key = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"]
  );
  return new Uint8Array(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(value)));
}
async function hmacHex(secret, value) {
  return Array.from(await hmacBytes(secret, value), (b) => b.toString(16).padStart(2, "0")).join("");
}
function constantTimeEqual(a, b) {
  if (a.length !== b.length) return false;
  let diff = 0;
  for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
  return diff === 0;
}
async function createSignedSsoState(config) {
  if (!config.secret) throw new Error("SsoStateConfig.secret is required");
  const now = config.now ?? Date.now;
  const payload = `${randomHex(16)}.${now().toString(36)}`;
  return `${payload}.${await hmacHex(config.secret, payload)}`;
}
async function verifySignedSsoState(state, config) {
  if (!config.secret) throw new Error("SsoStateConfig.secret is required");
  const parts = state.split(".");
  if (parts.length !== 3) return false;
  const [random, timestamp, mac] = parts;
  if (!random || !timestamp || !mac) return false;
  const expected = await hmacHex(config.secret, `${random}.${timestamp}`);
  if (!constantTimeEqual(mac, expected)) return false;
  const mintedAt = parseInt(timestamp, 36);
  if (!Number.isFinite(mintedAt)) return false;
  const now = config.now ?? Date.now;
  const ttlMs = config.ttlMs ?? DEFAULT_STATE_TTL_SECONDS * 1e3;
  return now() - mintedAt <= ttlMs;
}
var TangleSsoUserCreateError = class extends Error {
  constructor(message = "Failed to create local user for Tangle SSO") {
    super(message);
    this.name = "TangleSsoUserCreateError";
  }
};
async function signSessionCookieValue(token, secret) {
  if (!secret) throw new Error("signSessionCookieValue requires a non-empty secret");
  const sig = await hmacBytes(secret, token);
  let bin = "";
  for (const byte of sig) bin += String.fromCharCode(byte);
  return `${token}.${btoa(bin)}`;
}
function createBetterAuthSessionCookieMinter(auth, options = {}) {
  const warn = options.warn ?? ((message) => console.warn(message));
  return async ({ token, ttlSeconds }) => {
    const ctx = await auth.$context;
    if (!ctx.secret) {
      throw new Error("createBetterAuthSessionCookieMinter: auth context has no secret");
    }
    const { name, attributes } = ctx.authCookies.sessionToken;
    if (attributes.domain) {
      throw new Error(
        `createBetterAuthSessionCookieMinter: refusing a domain-scoped session cookie (Domain=${attributes.domain}) \u2014 a domain-wide session cookie shadows sibling apps that share the parent domain`
      );
    }
    if (name === DEFAULT_SESSION_COOKIE || name === `__Secure-${DEFAULT_SESSION_COOKIE}`) {
      warn(
        `[tangle-sso] session cookie is named "${name}" \u2014 better-auth's default. The Tangle platform (id.tangle.tools) sets a Domain=.tangle.tools cookie under the same name, and the platform's (older) cookie wins the Cookie-header order, so this app's sessions read back null. Set a per-app prefix: betterAuth({ advanced: { cookiePrefix: '<app>' } }).`
      );
    }
    const sameSite = typeof attributes.sameSite === "string" ? attributes.sameSite : "lax";
    const cookieOptions = {
      name,
      path: typeof attributes.path === "string" ? attributes.path : "/",
      httpOnly: attributes.httpOnly !== false,
      sameSite: sameSite.charAt(0).toUpperCase() + sameSite.slice(1),
      secure: Boolean(attributes.secure) || name.startsWith("__Secure-"),
      maxAgeSeconds: ttlSeconds
    };
    const cookies = [serializeCookie(await signSessionCookieValue(token, ctx.secret), cookieOptions)];
    if (name !== DEFAULT_SESSION_COOKIE) {
      cookies.push(clearCookieHeader({ ...cookieOptions, name: DEFAULT_SESSION_COOKIE }));
    }
    return cookies;
  };
}
function sanitizeRedirectPath(value, fallback) {
  if (value && value.startsWith("/") && !value.startsWith("//")) return value;
  return fallback;
}
function redirectResponse(location, headers = new Headers()) {
  headers.set("Location", location);
  return new Response(null, { status: 302, headers });
}
function clientIp(request) {
  return request.headers.get("CF-Connecting-IP") ?? request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? null;
}
function parseStateCookiePayload(raw) {
  if (!raw) return null;
  try {
    const parsed = JSON.parse(raw);
    if (parsed === null || typeof parsed !== "object") return null;
    const { s, r } = parsed;
    if (typeof s !== "string" || typeof r !== "string") return null;
    return { s, r };
  } catch {
    return null;
  }
}
function createTangleSsoHandlers(opts) {
  if (!opts.stateSecret) throw new Error("TangleSsoHandlerOptions.stateSecret is required");
  if (!opts.callbackUrl) throw new Error("TangleSsoHandlerOptions.callbackUrl is required");
  if (!opts.stateCookieName) throw new Error("TangleSsoHandlerOptions.stateCookieName is required");
  const sessionCookieName = opts.sessionCookieName ?? DEFAULT_SESSION_COOKIE;
  let mintSessionCookies;
  if (opts.setSessionCookie) {
    const seam = opts.setSessionCookie;
    mintSessionCookies = async (args) => await seam(args);
  } else if (opts.sessionCookieSecret) {
    const secret = opts.sessionCookieSecret;
    mintSessionCookies = async ({ token, secure, ttlSeconds }) => [
      serializeCookie(await signSessionCookieValue(token, secret), {
        name: secure ? `__Secure-${sessionCookieName}` : sessionCookieName,
        secure,
        maxAgeSeconds: ttlSeconds
      })
    ];
  } else {
    throw new Error(
      "TangleSsoHandlerOptions requires setSessionCookie or sessionCookieSecret: better-auth only accepts HMAC-signed (and, on https, __Secure--prefixed) session cookies, so an unsigned default would mint sessions that read back null"
    );
  }
  const sessionTtlSeconds = opts.sessionTtlSeconds ?? DEFAULT_SESSION_TTL_SECONDS;
  const stateTtlSeconds = opts.stateTtlSeconds ?? DEFAULT_STATE_TTL_SECONDS;
  const defaultRedirectPath = opts.defaultRedirectPath ?? DEFAULT_REDIRECT_PATH;
  const loginPath = opts.loginPath ?? DEFAULT_LOGIN_PATH;
  const log = opts.log ?? (() => {
  });
  const now = opts.now ?? Date.now;
  const stateConfig = { secret: opts.stateSecret, ttlMs: stateTtlSeconds * 1e3, now };
  const stateCookieOpts = { name: opts.stateCookieName, secure: opts.secureCookies };
  function loginErrorRedirect(code) {
    const headers = new Headers();
    headers.append("Set-Cookie", clearCookieHeader(stateCookieOpts));
    return redirectResponse(`${loginPath}?error=${code}`, headers);
  }
  return {
    async start(request) {
      const url = new URL(request.url);
      const redirectPath = sanitizeRedirectPath(url.searchParams.get("redirect"), defaultRedirectPath);
      const state = await createSignedSsoState(stateConfig);
      const cookie = serializeCookie(JSON.stringify({ s: state, r: redirectPath }), {
        ...stateCookieOpts,
        maxAgeSeconds: stateTtlSeconds
      });
      const headers = new Headers();
      headers.append("Set-Cookie", cookie);
      return redirectResponse(opts.auth.authorizeUrl({ state, redirectUri: opts.callbackUrl }), headers);
    },
    async callback(request) {
      const url = new URL(request.url);
      const code = url.searchParams.get("code");
      const stateFromPlatform = url.searchParams.get("state");
      if (!code || !stateFromPlatform) return loginErrorRedirect("tangle_callback_missing");
      const payload = parseStateCookiePayload(readCookieValue(request.headers.get("cookie"), opts.stateCookieName));
      if (!payload || payload.s !== stateFromPlatform) return loginErrorRedirect("tangle_state_mismatch");
      if (!await verifySignedSsoState(payload.s, stateConfig)) return loginErrorRedirect("tangle_state_mismatch");
      let exchanged;
      try {
        exchanged = await opts.auth.exchange(code);
      } catch (err) {
        log("[tangle-sso] exchange failed", err);
        return loginErrorRedirect("tangle_exchange_failed");
      }
      let userId;
      try {
        ;
        ({ userId } = await opts.store.upsertUserByEmail({
          email: exchanged.user.email,
          name: exchanged.user.name ?? null,
          tangleUserId: exchanged.user.id
        }));
      } catch (err) {
        if (err instanceof TangleSsoUserCreateError) return loginErrorRedirect("tangle_user_create_failed");
        throw err;
      }
      const expiresAt = new Date(now() + sessionTtlSeconds * 1e3);
      const { token } = await opts.store.createSession({
        userId,
        expiresAt,
        ipAddress: clientIp(request),
        userAgent: request.headers.get("user-agent")
      });
      await opts.store.saveTangleLink({
        userId,
        sessionToken: token,
        tangleUserId: exchanged.user.id,
        email: exchanged.user.email,
        name: exchanged.user.name ?? null,
        apiKey: exchanged.apiKey,
        planTier: exchanged.plan?.tier ?? null
      });
      const headers = new Headers();
      headers.append("Set-Cookie", clearCookieHeader(stateCookieOpts));
      const sessionCookies = await mintSessionCookies({
        token,
        expiresAt,
        ttlSeconds: sessionTtlSeconds,
        secure: opts.secureCookies
      });
      for (const cookie of sessionCookies) headers.append("Set-Cookie", cookie);
      return redirectResponse(sanitizeRedirectPath(payload.r, defaultRedirectPath), headers);
    }
  };
}

// src/platform/hub.ts
var TangleBearerMissingError = class extends Error {
  constructor(userId) {
    super(`No Tangle platform link for user ${userId}`);
    this.userId = userId;
    this.name = "TangleBearerMissingError";
  }
  userId;
};
async function resolveUserTangleHubBearer(opts) {
  const resolved = await resolveTangleDevOrUserKey({
    environment: opts.environment,
    env: opts.env,
    getUserApiKey: opts.getUserApiKey
  });
  if (resolved) return { bearer: resolved.apiKey, source: resolved.source };
  throw new TangleBearerMissingError(opts.userId);
}
async function resolveUserTangleHubBearerForUser(opts) {
  return resolveUserTangleHubBearer({
    userId: String(opts.userId),
    environment: opts.environment,
    env: opts.env,
    getUserApiKey: () => opts.getUserApiKey(opts.userId)
  });
}
function isTangleBearerMissingError(error) {
  return error instanceof Error && error.name === "TangleBearerMissingError" && typeof error.userId === "string";
}
function isPlatformHubErrorLike(error) {
  return error instanceof Error && error.name === "PlatformHubError" && typeof error.status === "number";
}
function createHubProxyRoutes(ctx) {
  async function proxy(request, call) {
    const userId = await ctx.requireUserId(request);
    try {
      const bearer = await ctx.getBearer(userId);
      return await call(ctx.createHubClient(bearer));
    } catch (err) {
      if (isTangleBearerMissingError(err)) {
        return Response.json({ error: "tangle_link_required" }, { status: 412 });
      }
      if (isPlatformHubErrorLike(err)) {
        return Response.json({ error: err.message, code: err.code }, { status: err.status });
      }
      throw err;
    }
  }
  return {
    catalog: ({ request }) => proxy(request, async (hub) => Response.json({ catalog: await hub.catalog() })),
    connections: ({ request }) => proxy(request, async (hub) => Response.json({ connections: await hub.listConnections() })),
    connectionDelete: async ({ request, params }) => {
      if (request.method !== "DELETE") {
        return Response.json({ error: "Method not allowed" }, { status: 405 });
      }
      return proxy(request, async (hub) => Response.json(await hub.revokeConnection(params.connectionId)));
    },
    healthchecks: ({ request }) => proxy(request, async (hub) => Response.json({ healthchecks: await hub.listHealthchecks() })),
    authStart: async ({ request }) => {
      if (request.method !== "POST") {
        return Response.json({ error: "Method not allowed" }, { status: 405 });
      }
      const userId = await ctx.requireUserId(request);
      let body;
      try {
        body = await request.json();
      } catch {
        return Response.json({ error: "Invalid JSON body" }, { status: 400 });
      }
      if (!body.providerId || !body.connectorId || !body.returnUrl) {
        return Response.json({ error: "providerId, connectorId, and returnUrl are required" }, { status: 400 });
      }
      try {
        const bearer = await ctx.getBearer(userId);
        const result = await ctx.createHubClient(bearer).startAuth({
          providerId: body.providerId,
          connectorId: body.connectorId,
          returnUrl: body.returnUrl,
          requestedScopes: body.requestedScopes
        });
        return Response.json({ authorizationUrl: result.authorizationUrl, state: result.state });
      } catch (err) {
        if (isTangleBearerMissingError(err)) {
          return Response.json({ error: "tangle_link_required" }, { status: 412 });
        }
        if (isPlatformHubErrorLike(err)) {
          return Response.json({ error: err.message, code: err.code }, { status: err.status });
        }
        throw err;
      }
    }
  };
}

// src/platform/billing.ts
function normalizeTanglePlanTier(plan) {
  return plan === "pro" || plan === "enterprise" ? plan : "free";
}
var PlatformBillingHttpError = class extends Error {
  constructor(status, detail) {
    super(`Platform request failed (${status}): ${detail}`);
    this.status = status;
    this.name = "PlatformBillingHttpError";
  }
  status;
};
function isPlatformBillingHttpError(error) {
  return error instanceof Error && error.name === "PlatformBillingHttpError" && typeof error.status === "number";
}
function createPlatformBillingHttp(opts) {
  const baseUrl = opts.baseUrl.replace(/\/+$/, "");
  if (!baseUrl) throw new Error("PlatformBillingHttpOptions.baseUrl is required");
  if (!opts.productSlug) throw new Error("PlatformBillingHttpOptions.productSlug is required");
  const fetchImpl = opts.fetchImpl ?? fetch;
  const timeoutMs = opts.timeoutMs ?? 1e4;
  function resolveServiceToken() {
    const token = typeof opts.serviceToken === "function" ? opts.serviceToken() : opts.serviceToken;
    if (!token) throw new Error("A platform service token is required for deduct");
    return token;
  }
  async function request(path, init, headers) {
    const res = await fetchImpl(`${baseUrl}${path}`, {
      ...init,
      headers,
      signal: AbortSignal.timeout(timeoutMs)
    });
    if (!res.ok) {
      const body = await res.json().catch(() => null);
      throw new PlatformBillingHttpError(res.status, body?.error?.message ?? res.statusText);
    }
    return res.json();
  }
  function userRead(userApiKey, path) {
    const headers = new Headers();
    headers.set("Authorization", `Bearer ${userApiKey}`);
    return request(path, {}, headers);
  }
  return {
    async getSubscription(userApiKey) {
      const body = await userRead(userApiKey, "/v1/plans/current");
      const sub = body.data?.subscription ?? null;
      return { tier: normalizeTanglePlanTier(sub?.plan), status: sub?.status ?? null };
    },
    async getBalance(userApiKey) {
      const body = await userRead(userApiKey, "/v1/billing/balance");
      return {
        balance: body.data?.balance ?? 0,
        lifetimeSpent: body.data?.lifetimeSpent ?? 0,
        updatedAt: body.data?.updatedAt
      };
    },
    async getUsageByProduct(userApiKey) {
      const body = await userRead(userApiKey, "/v1/billing/usage");
      return (body.data ?? []).map((row) => ({
        product: row.product ?? null,
        totalSpent: row.totalSpent ?? 0,
        count: row.count ?? 0
      }));
    },
    async getProductEntitlement(userApiKey, productId) {
      const slug = encodeURIComponent(productId);
      const body = await userRead(userApiKey, `/v1/billing/product-entitlement?product=${slug}`);
      const data = body.data ?? {};
      const hasSeat = data.hasSeat === true;
      return {
        seatStatus: data.seatStatus ?? "none",
        currentPeriodEnd: data.currentPeriodEnd ?? null,
        lifetimeSpentUsd: data.lifetimeSpentUsd ?? 0,
        hasSeat,
        // Free access only when there is no seat AND the platform says so.
        onFreeTier: !hasSeat && data.onFreeTier === true
      };
    },
    async deduct(input) {
      const headers = new Headers();
      headers.set("Authorization", `Bearer ${resolveServiceToken()}`);
      headers.set("X-Service-Name", opts.productSlug);
      headers.set("Content-Type", "application/json");
      await request("/v1/billing/deduct", {
        method: "POST",
        body: JSON.stringify({
          userId: input.platformUserId,
          amount: input.amountUsd,
          type: input.type,
          product: opts.productSlug,
          description: input.description,
          referenceId: input.referenceId
        })
      }, headers);
    },
    billingUrl() {
      return `${baseUrl}/app/billing`;
    },
    seatCheckoutUrl(productId) {
      return seatCheckoutUrl(baseUrl, productId);
    }
  };
}
function seatCheckoutUrl(baseUrl, productId) {
  const root = baseUrl.replace(/\/+$/, "");
  return `${root}/app/billing/seat/checkout?product=${encodeURIComponent(productId)}`;
}
var DEFAULT_TANGLE_TIER_POLICY = {
  free: { concurrency: 1, overageAllowed: false },
  pro: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },
  enterprise: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true }
};
async function readTangleTierState(http, userApiKey, policy = DEFAULT_TANGLE_TIER_POLICY) {
  if (!userApiKey) {
    return {
      tier: "free",
      subscriptionStatus: null,
      remainingBalanceUsd: 0,
      lifetimeSpentUsd: 0,
      ...policy.free
    };
  }
  const [subscription, balance] = await Promise.all([
    http.getSubscription(userApiKey),
    http.getBalance(userApiKey)
  ]);
  return {
    tier: subscription.tier,
    subscriptionStatus: subscription.status,
    remainingBalanceUsd: balance.balance,
    lifetimeSpentUsd: balance.lifetimeSpent,
    ...policy[subscription.tier]
  };
}
var FREE_TIER_SPEND_CAP_USD = 2;
var DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR = "SEAT_BILLING_ENABLED";
function isSeatBillingEnabled(opts = {}) {
  const env = opts.env ?? (typeof process !== "undefined" ? process.env : void 0);
  if (!env) return false;
  const flag = env[opts.flagEnvVar ?? DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR]?.trim().toLowerCase();
  return flag === "true" || flag === "1" || flag === "on" || flag === "enabled";
}
async function getProductEntitlement(http, userApiKey, productId, flag = true) {
  if (!flag || !userApiKey) return failOpenEntitlement();
  try {
    return await http.getProductEntitlement(userApiKey, productId);
  } catch {
    return failOpenEntitlement();
  }
}
function failOpenEntitlement() {
  return {
    seatStatus: "active",
    currentPeriodEnd: null,
    lifetimeSpentUsd: 0,
    hasSeat: true,
    onFreeTier: false
  };
}
function isProductEntitled(ent) {
  return ent.hasSeat || ent.onFreeTier;
}
function createTanglePlatformBillingClient(http, identity) {
  return {
    resolveIdentity: (userId) => identity.resolveIdentity(userId),
    getPlan: async (apiKey) => (await http.getSubscription(apiKey)).tier,
    getBalance: async (apiKey) => {
      const snapshot = await http.getBalance(apiKey);
      return { balance: snapshot.balance, lifetimeSpent: snapshot.lifetimeSpent };
    },
    getUsageByProduct: (apiKey) => http.getUsageByProduct(apiKey),
    deduct: (input) => http.deduct(input)
  };
}

// src/platform/guards.ts
function createAuthGuard(opts) {
  const loginPath = opts.loginPath ?? "/login";
  async function requireSession(request, o = {}) {
    const session = await opts.getSession(request);
    if (!session) {
      if (o.apiResponse) {
        throw Response.json({ error: "Unauthorized", code: "auth.unauthenticated" }, { status: 401 });
      }
      throw new Response(null, { status: 302, headers: { Location: loginPath } });
    }
    return session;
  }
  return {
    requireSession,
    requireUser: (request) => requireSession(request),
    requireApiUser: (request) => requireSession(request, { apiResponse: true }),
    getOptionalSession: async (request) => await opts.getSession(request) ?? null
  };
}
function parseAdminEmails(raw) {
  return (raw ?? "").split(/[,\s]+/).map((e) => e.trim().toLowerCase()).filter(Boolean);
}
function createAdminGuard(opts) {
  return async (request) => {
    const session = await opts.requireUser(request);
    const allowed = opts.allowedEmails();
    if (allowed.length === 0) throw new Response("Not found", { status: 404 });
    const email = (opts.emailOf(session) ?? "").toLowerCase();
    if (!allowed.includes(email)) throw new Response("Not found", { status: 404 });
    return session;
  };
}
function assertBillableBalance(state, opts = {}) {
  if (isTangleBillingEnforcementDisabled({ env: opts.env, enforcementEnvVar: opts.enforcementEnvVar })) return;
  if (state.overageAllowed || state.remainingBalanceUsd > 0) return;
  throw Response.json(
    {
      ...opts.errorBody,
      error: opts.errorMessage ?? "Add balance or upgrade your plan to invoke this agent.",
      code: "billing.balance_required"
    },
    { status: 402 }
  );
}
export {
  DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR,
  DEFAULT_TANGLE_TIER_POLICY,
  FREE_TIER_SPEND_CAP_USD,
  PlatformBillingHttpError,
  TangleBearerMissingError,
  TangleSsoUserCreateError,
  assertBillableBalance,
  createAdminGuard,
  createAuthGuard,
  createBetterAuthSessionCookieMinter,
  createHubProxyRoutes,
  createPlatformBillingHttp,
  createSignedSsoState,
  createTanglePlatformBillingClient,
  createTangleSsoHandlers,
  getProductEntitlement,
  isPlatformBillingHttpError,
  isPlatformHubErrorLike,
  isProductEntitled,
  isSeatBillingEnabled,
  isTangleBearerMissingError,
  normalizeTanglePlanTier,
  parseAdminEmails,
  readTangleTierState,
  resolveUserTangleHubBearer,
  resolveUserTangleHubBearerForUser,
  seatCheckoutUrl,
  signSessionCookieValue,
  verifySignedSsoState
};
//# sourceMappingURL=index.js.map