import crypto from "node:crypto";
function base64url(input) {
  return (typeof input === "string" ? Buffer.from(input) : input).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
}
const DEFAULT_ISSUER = "open-mercato";
const DEFAULT_STAFF_AUDIENCE = "staff";
const AUDIENCE_SECRET_LABEL = "open-mercato:jwt:v1";
function getLegacyGraceEnabled() {
  const raw = process.env.JWT_LEGACY_GRACE_MINUTES;
  if (raw === "0" || raw === "false" || raw === "off") return false;
  return true;
}
function readBaseSecret(explicit) {
  const secret = explicit ?? process.env.JWT_SECRET;
  if (!secret) throw new Error("JWT_SECRET is not set");
  return secret;
}
function normalizeAudience(audience) {
  return audience.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_");
}
const derivedSecretCache = /* @__PURE__ */ new Map();
function deriveAudienceSecretFromBase(normalized, base) {
  const cacheKey = `${normalized}::${base}`;
  const cached = derivedSecretCache.get(cacheKey);
  if (cached !== void 0) return cached;
  const label = `${AUDIENCE_SECRET_LABEL}:${normalized}`;
  const derived = crypto.createHmac("sha256", base).update(label).digest("hex");
  derivedSecretCache.set(cacheKey, derived);
  return derived;
}
function deriveJwtAudienceSecret(audience, baseSecret) {
  const normalized = normalizeAudience(audience);
  if (!normalized) throw new Error("Audience is required to derive a JWT secret");
  const overrideName = `JWT_${normalized.toUpperCase()}_SECRET`;
  const override = process.env[overrideName];
  if (override && override.trim().length > 0) return override;
  const base = readBaseSecret(baseSecret);
  return deriveAudienceSecretFromBase(normalized, base);
}
function isSignOptions(value) {
  return typeof value === "object" && value !== null;
}
function isVerifyOptions(value) {
  return typeof value === "object" && value !== null;
}
function toSignOptions(secretOrOptions, expiresInSec) {
  if (isSignOptions(secretOrOptions)) {
    const audience = secretOrOptions.audience ?? DEFAULT_STAFF_AUDIENCE;
    const secret = secretOrOptions.secret ?? deriveJwtAudienceSecret(audience);
    if (!secret) throw new Error("JWT_SECRET is not set");
    return {
      secret,
      expiresInSec: secretOrOptions.expiresInSec ?? 60 * 60 * 8,
      audience,
      issuer: secretOrOptions.issuer ?? DEFAULT_ISSUER
    };
  }
  if (typeof secretOrOptions === "string") {
    if (!secretOrOptions) throw new Error("JWT_SECRET is not set");
    return {
      secret: secretOrOptions,
      expiresInSec: expiresInSec ?? 60 * 60 * 8
    };
  }
  return {
    secret: deriveJwtAudienceSecret(DEFAULT_STAFF_AUDIENCE),
    expiresInSec: expiresInSec ?? 60 * 60 * 8,
    audience: DEFAULT_STAFF_AUDIENCE,
    issuer: DEFAULT_ISSUER
  };
}
function toVerifyOptions(secretOrOptions) {
  if (isVerifyOptions(secretOrOptions)) {
    const audience = secretOrOptions.audience ?? DEFAULT_STAFF_AUDIENCE;
    const secret = secretOrOptions.secret ?? deriveJwtAudienceSecret(audience);
    if (!secret) throw new Error("JWT_SECRET is not set");
    return {
      secret,
      audience,
      issuer: secretOrOptions.issuer ?? DEFAULT_ISSUER
    };
  }
  if (typeof secretOrOptions === "string") {
    if (!secretOrOptions) throw new Error("JWT_SECRET is not set");
    return { secret: secretOrOptions };
  }
  return {
    secret: deriveJwtAudienceSecret(DEFAULT_STAFF_AUDIENCE),
    audience: DEFAULT_STAFF_AUDIENCE,
    issuer: DEFAULT_ISSUER
  };
}
function signJwt(payload, secretOrOptions, expiresInSec) {
  const options = toSignOptions(secretOrOptions, expiresInSec);
  const header = { alg: "HS256", typ: "JWT" };
  const now = Math.floor(Date.now() / 1e3);
  const body = { iat: now, exp: now + options.expiresInSec, ...payload };
  if (options.issuer && body.iss === void 0) body.iss = options.issuer;
  if (options.audience && body.aud === void 0) body.aud = options.audience;
  const encHeader = base64url(JSON.stringify(header));
  const encBody = base64url(JSON.stringify(body));
  const data = `${encHeader}.${encBody}`;
  const sig = crypto.createHmac("sha256", options.secret).update(data).digest();
  const encSig = base64url(sig);
  return `${data}.${encSig}`;
}
function verifyWithOptions(token, options) {
  const parts = token.split(".");
  if (parts.length !== 3) return null;
  const [h, p, s] = parts;
  const data = `${h}.${p}`;
  const expected = base64url(crypto.createHmac("sha256", options.secret).update(data).digest());
  const providedSignature = Buffer.from(s);
  const expectedSignature = Buffer.from(expected);
  if (providedSignature.length !== expectedSignature.length) return null;
  if (!crypto.timingSafeEqual(providedSignature, expectedSignature)) return null;
  let payload;
  try {
    payload = JSON.parse(Buffer.from(p, "base64").toString("utf8"));
  } catch {
    return null;
  }
  const now = Math.floor(Date.now() / 1e3);
  if (payload.exp && now > payload.exp) return null;
  if (options.audience !== void 0) {
    if (payload.aud !== options.audience) return null;
  }
  if (options.issuer !== void 0) {
    if (payload.iss !== options.issuer) return null;
  }
  return payload;
}
function verifyJwt(token, secretOrOptions) {
  const options = toVerifyOptions(secretOrOptions);
  const result = verifyWithOptions(token, options);
  if (result) return result;
  if (secretOrOptions === void 0 && getLegacyGraceEnabled()) {
    const rawSecret = process.env.JWT_SECRET;
    if (rawSecret) {
      const legacyResult = verifyWithOptions(token, { secret: rawSecret });
      if (legacyResult) {
        legacyResult._legacyToken = true;
        return legacyResult;
      }
    }
  }
  return null;
}
function signAudienceJwt(audience, payload, expiresInSec = 60 * 60 * 8) {
  return signJwt(payload, { audience, expiresInSec });
}
function verifyAudienceJwt(audience, token) {
  return verifyJwt(token, { audience });
}
export {
  deriveJwtAudienceSecret,
  signAudienceJwt,
  signJwt,
  verifyAudienceJwt,
  verifyJwt
};
//# sourceMappingURL=jwt.js.map
