import { b as private_env } from './shared-server-DaWdgxVh.js';
import { L as LibraryAPNsService } from './library-apns-DMlL1BAg.js';
import { c as createPendingRequest } from './pending-requests-8rWjrF6d.js';
import { v as validateAuth } from './auth-DuunT7Cg.js';
import admin from 'firebase-admin';
import { m as maskToken } from './device-format-DTgEz4Yr.js';
import { d as deviceTokenStore } from './device-token-store-Ct7aTeR8.js';
import { t as toErrorMessage } from './error-DDXB3duW.js';
import { b as broadcastEvent } from './guest-registry-Dxvd7p-g.js';
import './super-session-handler-DPyxFgmz.js';
import { j as json } from './index-lhTMmBNn.js';
import 'child_process';
import 'jsonwebtoken';
import 'util';
import 'better-sqlite3';
import 'fs';
import 'path';
import './shooter-home-4f_HkdGI.js';
import 'os';
import 'crypto';
import './registry-D4J_CuzW.js';
import './qwen-reader-DGfUbKaJ.js';
import './coordinator-DMU_ADXf.js';

const MAX_HISTORY = 100;
const history = [];
function addNotification(record) {
  history.unshift(record);
  if (history.length > MAX_HISTORY) {
    history.length = MAX_HISTORY;
  }
}
function getNotifications(limit = 50) {
  return history.slice(0, limit);
}
function selectPlatforms(platformFilter) {
  if (platformFilter === "ios") {
    return { doAndroid: false, doIos: true };
  }
  if (platformFilter === "android") {
    return { doAndroid: true, doIos: false };
  }
  return { doAndroid: true, doIos: true };
}
function summarizeNotifyDelivery(apns, fcm) {
  const sent = apns.totalSent + fcm.successCount;
  const failed = apns.totalFailed + fcm.failureCount;
  const staleTokens = [...apns.staleTokens, ...fcm.staleTokens];
  const succeededTokens = [
    ...apns.results.filter((r) => r.success).map((r) => r.token),
    ...fcm.results.filter((r) => r.success).map((r) => r.token)
  ];
  return { delivered: sent > 0, failed, sent, staleTokens, succeededTokens };
}
const PRUNABLE_FCM_CODES = /* @__PURE__ */ new Set([
  "messaging/registration-token-not-registered"
]);
function chunk(items, size) {
  const out = [];
  for (let i = 0; i < items.length; i += size) {
    out.push(items.slice(i, i + size));
  }
  return out;
}
function classifyFcmError(code) {
  return code && PRUNABLE_FCM_CODES.has(code) ? "prune" : "keep";
}
function summarizeFcmFanOut(outcomes) {
  const results = [];
  const staleTokens = [];
  let successCount = 0;
  let failureCount = 0;
  for (const o of outcomes) {
    if (o.success) {
      successCount += 1;
    } else {
      failureCount += 1;
      if (classifyFcmError(o.errorCode) === "prune") {
        staleTokens.push(o.token);
      }
    }
    results.push({
      error: o.errorCode,
      messageId: o.messageId,
      success: o.success,
      token: o.token
    });
  }
  return { failureCount, results, staleTokens, successCount };
}
let app = null;
const FCM_MULTICAST_CHUNK = 100;
function isFCMConfigured() {
  return !!(process.env.FCM_PROJECT_ID && process.env.FCM_CLIENT_EMAIL && process.env.FCM_PRIVATE_KEY);
}
async function sendFCMNotificationMulti(tokens, payload) {
  if (tokens.length === 0) {
    return { failureCount: 0, results: [], staleTokens: [], successCount: 0 };
  }
  let fcmApp;
  try {
    fcmApp = getApp();
  } catch (error) {
    const msg = error instanceof Error ? error.message : String(error);
    console.error("[FCM] multi-send setup failed:", fcmErrorLabel(error));
    return {
      failureCount: tokens.length,
      results: tokens.map((token) => ({ error: msg, messageId: null, success: false, token })),
      staleTokens: [],
      successCount: 0
    };
  }
  const base = buildDataMessage(payload);
  const outcomes = [];
  for (const group of chunk(tokens, FCM_MULTICAST_CHUNK)) {
    try {
      const batch = await admin.messaging(fcmApp).sendEachForMulticast({ ...base, tokens: [...group] });
      batch.responses.forEach((resp, i) => {
        const errorCode = resp.error?.code ?? null;
        if (errorCode === "messaging/invalid-argument") {
          console.error(`[FCM] invalid-argument for token ${maskToken(group[i])}: ${errorCode}`);
        }
        outcomes.push({
          errorCode,
          messageId: resp.messageId ?? null,
          success: resp.success,
          token: group[i]
        });
      });
    } catch (error) {
      console.error("[FCM] multicast chunk failed:", fcmErrorLabel(error));
      for (const token of group) {
        outcomes.push({ errorCode: null, messageId: null, success: false, token });
      }
    }
  }
  return summarizeFcmFanOut(outcomes);
}
function buildDataMessage(payload) {
  return {
    android: {
      priority: "high",
      // Ensures delivery even in Doze mode
      ttl: 3e5
      // 5 minutes TTL (matches pending request expiry)
    },
    data: {
      body: payload.body || payload.message || "",
      category: payload.category ?? (typeof payload.data?.category === "string" ? payload.data.category : ""),
      project: typeof payload.data?.project === "string" ? payload.data.project : "",
      requestId: typeof payload.data?.requestId === "string" ? payload.data.requestId : "",
      source: typeof payload.data?.source === "string" ? payload.data.source : "",
      subtitle: payload.subtitle ?? "",
      timestamp: (/* @__PURE__ */ new Date()).toISOString(),
      title: payload.title,
      toolInput: payload.data?.toolInput ? JSON.stringify(payload.data.toolInput) : "",
      toolName: typeof payload.data?.toolName === "string" ? payload.data.toolName : "",
      type: typeof payload.data?.type === "string" ? payload.data.type : ""
    }
  };
}
function fcmErrorLabel(error) {
  if (error && typeof error === "object" && "code" in error) {
    const code = error.code;
    if (typeof code === "string" && code.length > 0) {
      return code;
    }
  }
  return error instanceof Error ? error.message : String(error);
}
function getApp() {
  if (!app) {
    const projectId = process.env.FCM_PROJECT_ID;
    const clientEmail = process.env.FCM_CLIENT_EMAIL;
    const privateKey = process.env.FCM_PRIVATE_KEY?.replace(/\\n/g, "\n");
    if (!projectId || !clientEmail || !privateKey) {
      throw new Error(
        "FCM not configured: missing FCM_PROJECT_ID, FCM_CLIENT_EMAIL, or FCM_PRIVATE_KEY"
      );
    }
    app = admin.initializeApp({
      credential: admin.credential.cert({ clientEmail, privateKey, projectId })
    });
  }
  return app;
}
let apnsSingleton = null;
function getAPNsClient() {
  if (!apnsSingleton) {
    apnsSingleton = new LibraryAPNsService();
  }
  return apnsSingleton;
}
const EMPTY_APNS_RESULT = {
  results: [],
  staleTokens: [],
  totalFailed: 0,
  totalSent: 0
};
const EMPTY_FCM_RESULT = {
  failureCount: 0,
  results: [],
  staleTokens: [],
  successCount: 0
};
function resolveAndroidTokens() {
  const rows = deviceTokenStore.listActive("android");
  if (rows.length > 0) {
    return rows.map((r) => r.token);
  }
  const seed = private_env.ANDROID_DEVICE_TOKEN?.trim();
  return seed ? [seed] : [];
}
function resolveIosDevices(override) {
  const appEnv = serverApnEnv();
  if (override) {
    return [syntheticSeedDevice(override, "ios", appEnv)];
  }
  const rows = deviceTokenStore.listActiveForEnv("ios", appEnv);
  if (rows.length > 0) {
    return rows;
  }
  const seed = private_env.DEVICE_TOKEN?.trim();
  return seed ? [syntheticSeedDevice(seed, "ios", appEnv)] : [];
}
function serverApnEnv() {
  return private_env.APNS_PRODUCTION === "true" ? "production" : "sandbox";
}
function syntheticSeedDevice(token, platform, appEnv) {
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
  return {
    appEnv,
    bundleId: null,
    deviceId: null,
    failureCount: 0,
    friendlyName: null,
    id: `seed-${platform}`,
    isActive: true,
    lastSeenAt: nowIso,
    platform,
    registeredAt: nowIso,
    token
  };
}
const notificationCache = /* @__PURE__ */ new Map();
const DEDUP_WINDOW = 1e4;
function broadcastHookEvent(body) {
  const data = body.data ?? {};
  const eventType = typeof data.eventType === "string" ? data.eventType : typeof body.eventType === "string" ? body.eventType : "";
  const tool = typeof data.tool === "string" ? data.tool : "";
  const terminalId = typeof data.terminalId === "string" ? data.terminalId : void 0;
  const sessionId = typeof data.sessionId === "string" ? data.sessionId : void 0;
  switch (eventType) {
    case "error":
      broadcastEvent({
        error: typeof data.error === "string" ? data.error : typeof data.message === "string" ? data.message : "Unknown error",
        terminalId,
        tool,
        type: "tool-failed"
      });
      break;
    case "idle_input":
    case "session.idle":
      broadcastEvent({
        message: typeof data.message === "string" ? data.message : "",
        sessionId,
        terminalId,
        type: "agent-idle"
      });
      break;
    case "permission":
    case "permission_notification":
      if (data.requestId && data.toolName) {
        broadcastEvent({
          input: typeof data.toolInput === "object" && data.toolInput !== null ? data.toolInput : {},
          requestId: data.requestId,
          tool: data.toolName,
          type: "permission-requested"
        });
      }
      break;
    case "question":
      broadcastEvent({
        message: typeof data.message === "string" ? data.message : "",
        sessionId,
        terminalId,
        type: "agent-question"
      });
      break;
    case "tool.after":
      broadcastEvent({ success: true, terminalId, tool, type: "tool-completed" });
      break;
    case "tool.before":
      broadcastEvent({
        command: typeof data.command === "string" ? data.command : "",
        filePath: typeof data.filePath === "string" ? data.filePath : typeof data.files === "string" ? data.files : "",
        terminalId,
        tool,
        type: "tool-started"
      });
      break;
  }
}
function buildNotificationRecord(id, title, message, status, data, error) {
  return {
    category: data?.category ?? null,
    data: data ?? null,
    error: error ?? null,
    id,
    message,
    project: data?.project ?? null,
    source: data?.source ?? null,
    status,
    timestamp: (/* @__PURE__ */ new Date()).toISOString(),
    title,
    tool: data?.tool ?? null
  };
}
function intelligentNotificationFilter(title, message, data, waitForResponse = false) {
  const source = data?.source || "unknown";
  if (waitForResponse) {
    return { reason: "Bidirectional request — never filtered", send: true };
  }
  if (isDuplicateNotification(title, message, data, waitForResponse)) {
    return {
      reason: "Duplicate notification within 10-second window",
      send: false
    };
  }
  if (source === "smart-completion-detector" || source === "shooter-completion-detector") {
    return {
      reason: "Smart completion detector - completion or intervention needed",
      send: true
    };
  }
  const spamPatterns = [
    /PreToolUse/i,
    // Any PreToolUse notifications
    /PostToolUse/i,
    // Any PostToolUse notifications
    /^(Read|Write|Edit|Bash)\s+Starting\s*\|\s*\w+$/i,
    // Only basic tool starting patterns
    /^unknown\s*\|\s*\w+$/i
    // Only "unknown | projectname" exactly
  ];
  const isSpam = spamPatterns.some((pattern) => pattern.test(title) || pattern.test(message));
  if (isSpam) {
    return {
      reason: "Filtered spam notification from old hook system",
      send: false
    };
  }
  const importantPatterns = [
    /session complete/i,
    /intervention needed/i,
    /error/i,
    /failed/i,
    /blocked/i,
    /attention/i
  ];
  const isImportant = importantPatterns.some(
    (pattern) => pattern.test(title) || pattern.test(message)
  );
  if (isImportant) {
    return {
      reason: "Important notification - completion or intervention",
      send: true
    };
  }
  return {
    reason: "General notification - allowing by default",
    send: true
  };
}
function isDuplicateNotification(title, message, data, waitForResponse = false) {
  if (waitForResponse) {
    return false;
  }
  const dataRecord = data;
  const key = dataRecord?.dedupKey ? dataRecord.dedupKey : `${title}|${message}|${data?.category || "unknown"}`;
  const now = Date.now();
  if (notificationCache.has(key)) {
    const lastSent = notificationCache.get(key) ?? 0;
    if (now - lastSent < DEDUP_WINDOW) {
      return true;
    }
  }
  for (const [k, v] of notificationCache.entries()) {
    if (now - v > DEDUP_WINDOW) {
      notificationCache.delete(k);
    }
  }
  notificationCache.set(key, now);
  return false;
}
function notificationKey(title, message, data) {
  const dataRecord = data;
  return dataRecord?.dedupKey ?? `${title}|${message}|${data?.category || "unknown"}`;
}
function recordNotification(title, message, data) {
  notificationCache.set(notificationKey(title, message, data), Date.now());
}
function releaseNotification(title, message, data) {
  notificationCache.delete(notificationKey(title, message, data));
}
const POST = async ({ request }) => {
  try {
    const apnsClient = getAPNsClient();
    const authError = validateAuth(request);
    if (authError) {
      return authError;
    }
    const rawBody = await request.json();
    if (!rawBody || typeof rawBody !== "object" || Array.isArray(rawBody)) {
      return json({ error: "Request body must be a JSON object" }, { status: 400 });
    }
    const body = rawBody;
    const data = body.data;
    const requestDeviceToken = body.deviceToken;
    const message = body.message;
    const title = body.title;
    const subtitle = typeof body.subtitle === "string" ? body.subtitle : void 0;
    const skipPush = typeof body.skipPush === "boolean" ? body.skipPush : false;
    const waitForResponse = typeof body.waitForResponse === "boolean" ? body.waitForResponse : false;
    const forcePush = typeof body.forcePush === "boolean" ? body.forcePush : false;
    const notificationCategory = typeof body.notificationCategory === "string" && body.notificationCategory.length > 0 ? body.notificationCategory : void 0;
    const question = typeof body.question === "string" ? body.question : void 0;
    const options = Array.isArray(body.options) && body.options.every((o) => o && typeof o === "object") ? body.options : void 0;
    const responseKindRaw = body.responseKind;
    const responseKind = responseKindRaw === "hook" || responseKindRaw === "pty" || responseKindRaw === "info" ? responseKindRaw : void 0;
    try {
      broadcastHookEvent(body);
    } catch {
    }
    if (!title || typeof title !== "string" || !message || typeof message !== "string") {
      return json({ error: "Title and message are required and must be strings" }, { status: 400 });
    }
    if (requestDeviceToken !== void 0 && typeof requestDeviceToken !== "string") {
      return json({ error: "deviceToken must be a string" }, { status: 400 });
    }
    const requestId = Math.random().toString(36).substring(2, 15);
    const dataRequestId = typeof data?.requestId === "string" ? data.requestId : void 0;
    const canonicalRequestId = dataRequestId || requestId;
    const shouldSendNotification = intelligentNotificationFilter(
      title,
      message,
      data,
      waitForResponse
    );
    if (!shouldSendNotification.send) {
      addNotification(
        buildNotificationRecord(
          canonicalRequestId,
          title,
          message,
          "filtered",
          data,
          shouldSendNotification.reason
        )
      );
      return json({
        message: "Notification filtered (not sent)",
        reason: shouldSendNotification.reason,
        success: true,
        timestamp: (/* @__PURE__ */ new Date()).toISOString()
      });
    }
    if (skipPush && !forcePush) {
      if (waitForResponse) {
        createPendingRequest(canonicalRequestId, {
          options,
          question: question ?? null,
          responseKind: responseKind ?? "hook",
          sessionId: data?.sessionId || "",
          toolInput: data?.toolInput || {},
          toolName: data?.toolName || ""
        });
      }
      addNotification(buildNotificationRecord(canonicalRequestId, title, message, "skipped", data));
      return json({
        message: "Push skipped (WebSocket clients connected)",
        requestId: canonicalRequestId,
        success: true,
        timestamp: (/* @__PURE__ */ new Date()).toISOString()
      });
    }
    const payload = {
      badge: 1,
      body: message,
      // notificationCategory wins when explicitly set so plan-mode /
      // elicitation flows can pick CLAUDE_PLAN_APPROVAL / CHOICE_2..4.
      // Falls back to CLAUDE_PERMISSION for the legacy bidirectional
      // permission flow when only waitForResponse is provided.
      category: notificationCategory ?? (waitForResponse ? "CLAUDE_PERMISSION" : void 0),
      data: {
        ...data,
        requestId: canonicalRequestId,
        source: "modern-apns-api",
        timestamp: (/* @__PURE__ */ new Date()).toISOString(),
        waitForResponse: waitForResponse || false
      },
      message: null,
      sound: "default",
      ...subtitle ? { subtitle } : {},
      title
    };
    const override = requestDeviceToken?.trim() || void 0;
    const { doAndroid, doIos } = selectPlatforms(private_env.DEVICE_PLATFORM);
    const iosDevices = doIos ? override ? resolveIosDevices(override) : resolveIosDevices() : [];
    const androidTokens = doAndroid ? override ? [override] : resolveAndroidTokens() : [];
    const collapseId = waitForResponse ? canonicalRequestId : void 0;
    const [apnsResult, fcmResult] = await Promise.all([
      iosDevices.length > 0 && apnsClient.isConfigured() ? apnsClient.sendToMany(iosDevices, payload, collapseId) : Promise.resolve(EMPTY_APNS_RESULT),
      androidTokens.length > 0 && isFCMConfigured() ? sendFCMNotificationMulti(androidTokens, payload) : Promise.resolve(EMPTY_FCM_RESULT)
    ]);
    const summary = summarizeNotifyDelivery(apnsResult, fcmResult);
    const pruned = !override && summary.staleTokens.length ? deviceTokenStore.pruneByTokens(summary.staleTokens) : 0;
    if (summary.succeededTokens.length > 0) {
      deviceTokenStore.touchLastSeen(summary.succeededTokens);
    }
    if (summary.delivered && summary.failed === 0) {
      recordNotification(title, message, data);
    } else {
      releaseNotification(title, message, data);
    }
    if (waitForResponse && summary.delivered) {
      createPendingRequest(canonicalRequestId, {
        options,
        question: question ?? null,
        responseKind: responseKind ?? "hook",
        sessionId: data?.sessionId || "",
        toolInput: data?.toolInput || {},
        toolName: data?.toolName || ""
      });
    }
    addNotification(
      buildNotificationRecord(
        canonicalRequestId,
        title,
        message,
        summary.delivered ? "sent" : "failed",
        data,
        summary.delivered ? null : "No registered device accepted the notification"
      )
    );
    return json({
      failed: summary.failed,
      pruned,
      requestId: canonicalRequestId,
      sent: summary.sent,
      success: summary.delivered,
      timestamp: (/* @__PURE__ */ new Date()).toISOString()
    });
  } catch (error) {
    console.error("Notification error:", error);
    return json(
      {
        details: toErrorMessage(error),
        error: "Failed to send notification",
        timestamp: (/* @__PURE__ */ new Date()).toISOString()
      },
      { status: 500 }
    );
  }
};
const GET = ({ request, url }) => {
  const authError = validateAuth(request);
  if (authError) {
    return authError;
  }
  const limit = parseInt(url.searchParams.get("limit") || "50");
  const notifications = getNotifications(limit);
  return json({
    count: notifications.length,
    notifications,
    timestamp: (/* @__PURE__ */ new Date()).toISOString()
  });
};

export { GET, POST };
//# sourceMappingURL=_server.ts-B7BLxK5u.js.map
