// 暗号・トークン・時刻ユーティリティ（Workers / Node 双方で動作）

const enc = new TextEncoder();

/** 推測困難なランダムトークン（URLセーフ） */
export function randomToken(bytes = 24): string {
  const buf = new Uint8Array(bytes);
  crypto.getRandomValues(buf);
  return base64url(buf);
}

export function base64url(buf: Uint8Array): string {
  let s = "";
  for (const b of buf) s += String.fromCharCode(b);
  return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

function base64urlToBytes(s: string): Uint8Array {
  const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - (s.length % 4));
  const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
  const bin = atob(b64);
  const out = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
  return out;
}

/** HMAC-SHA256 署名（base64url） */
export async function hmacSign(secret: string, data: string): Promise<string> {
  const key = await crypto.subtle.importKey(
    "raw",
    enc.encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"],
  );
  const sig = await crypto.subtle.sign("HMAC", key, enc.encode(data));
  return base64url(new Uint8Array(sig));
}

/** 定数時間比較（タイミング攻撃対策） */
export function timingSafeEqual(a: string, b: string): boolean {
  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;
}

export { base64urlToBytes };

/** email 正規化（小文字・トリム） */
export function normalizeEmail(email: string): string {
  return email.trim().toLowerCase();
}

const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export function isValidEmail(email: string): boolean {
  return EMAIL_RE.test(email);
}

/**
 * SMS専用読者（電話番号だけで登録した人）のメールアドレスに使うドメイン。
 * `.invalid` は RFC 2606 で「絶対に名前解決されない」と予約されたTLDなので、
 * 誤って送信されても実在の誰かに届くことがない。
 * subscribers.email が NOT NULL UNIQUE のため、番号から一意な値を作って埋める。
 */
export const SMS_ONLY_EMAIL_DOMAIN = "sms.invalid";

/** 電話番号(E.164)から、SMS専用読者用の一意なダミーアドレスを作る */
export function smsOnlyEmailFor(phoneE164: string): string {
  return `${phoneE164.replace(/^\+/, "")}@${SMS_ONLY_EMAIL_DOMAIN}`;
}

/** 電話番号だけで登録された読者か（メールアドレスを持っていない） */
export function isSmsOnlyEmail(email: string): boolean {
  return email.endsWith(`@${SMS_ONLY_EMAIL_DOMAIN}`);
}

/** UTC日付キー（日次上限の集計用） */
export function dayKey(tsMs: number): string {
  return new Date(tsMs).toISOString().slice(0, 10);
}

export function escapeHtml(s: string): string {
  return s
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}
