import {
  DEFAULT_TANGLE_PLATFORM_URL,
  TangleIdentityUnreachableError,
  createTangleIdentityClient
} from "./chunk-Q5X3QNHR.js";

// src/connect/index.ts
function startConnectFlow(opts, input) {
  if (!input.appId) {
    throw new TangleIdentityUnreachableError("connect/start: appId is required");
  }
  if (!input.state) {
    throw new TangleIdentityUnreachableError(
      "connect/start: state is required for CSRF protection (matches the platform contract)"
    );
  }
  const baseUrl = (opts.baseUrl ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\/+$/, "");
  const url = new URL(`${baseUrl}/cross-site/authorize`);
  url.searchParams.set("app", input.appId);
  url.searchParams.set("state", input.state);
  if (input.redirectUri) url.searchParams.set("redirect", input.redirectUri);
  return { authorizeUrl: url.toString() };
}
async function finishConnectFlow(opts, input) {
  if (!input.code) {
    throw new TangleIdentityUnreachableError("connect/finish: code is required");
  }
  if (!input.appId) {
    throw new TangleIdentityUnreachableError("connect/finish: appId is required");
  }
  const baseUrl = (opts.baseUrl ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\/+$/, "");
  const fetchImpl = opts.fetchImpl ?? fetch;
  const timeoutMs = opts.timeoutMs ?? 5e3;
  let res;
  try {
    res = await fetchImpl(`${baseUrl}/cross-site/exchange`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ code: input.code, app: input.appId }),
      signal: AbortSignal.timeout(timeoutMs)
    });
  } catch (err) {
    throw new TangleIdentityUnreachableError("connect/finish: exchange request failed", { cause: err });
  }
  if (res.status === 401) {
    throw new TangleIdentityUnreachableError(
      "connect/finish: exchange code rejected \u2014 replay, expired, or wrong app",
      { status: 401 }
    );
  }
  if (!res.ok) {
    const detail = await res.text().catch(() => "");
    throw new TangleIdentityUnreachableError(
      `connect/finish: /cross-site/exchange returned ${res.status}: ${detail.slice(0, 200)}`,
      { status: res.status }
    );
  }
  const body = await res.json().catch(() => null);
  if (!body || typeof body.apiKey !== "string" || !body.user || typeof body.user.id !== "string") {
    throw new TangleIdentityUnreachableError("connect/finish: exchange response had an invalid shape");
  }
  return {
    apiKey: body.apiKey,
    user: {
      id: body.user.id,
      ...typeof body.user.email === "string" ? { email: body.user.email } : {},
      ...body.user.name !== void 0 ? { name: body.user.name } : {},
      ...body.user.image !== void 0 ? { image: body.user.image } : {}
    },
    balance: typeof body.balance === "number" && Number.isFinite(body.balance) ? body.balance : 0
  };
}
async function revokeConnectFlow(opts, input) {
  if (!input.apiKey) {
    throw new TangleIdentityUnreachableError("connect/revoke: apiKey is required");
  }
  const client = createTangleIdentityClient(opts);
  await client.revokeSession(input.apiKey);
}
var InMemoryConnectStateStore = class {
  entries = /* @__PURE__ */ new Map();
  put(state, value) {
    this.entries.set(state, {
      appId: value.appId,
      expiresAt: Date.now() + (value.ttlMs ?? 10 * 6e4)
    });
  }
  consume(state) {
    const entry = this.entries.get(state);
    this.entries.delete(state);
    if (!entry || entry.expiresAt <= Date.now()) return void 0;
    return { appId: entry.appId };
  }
  /** Test-only — drop pending state between unit-test runs. */
  clear() {
    this.entries.clear();
  }
};

export {
  startConnectFlow,
  finishConnectFlow,
  revokeConnectFlow,
  InMemoryConnectStateStore
};
//# sourceMappingURL=chunk-JCHD6L3B.js.map