import { b as private_env } from './shared-server-DaWdgxVh.js';
import { L as LibraryAPNsService } from './library-apns-D8RPINlv.js';
import { c as createPendingRequest } from './pending-requests-8rWjrF6d.js';
import { v as validateAuth } from './auth-DuunT7Cg.js';
import admin from 'firebase-admin';
import { t as toErrorMessage } from './error-DDXB3duW.js';
import { b as broadcastEvent } from './guest-registry-Dxvd7p-g.js';
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import './super-session-handler-DPyxFgmz.js';
import { j as json } from './index-CoD1IJuy.js';
import 'child_process';
import 'jsonwebtoken';
import 'util';
import 'better-sqlite3';
import './shooter-home-4f_HkdGI.js';
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);
}
let app = null;
function isFCMConfigured() {
  return !!(process.env.FCM_PROJECT_ID && process.env.FCM_CLIENT_EMAIL && process.env.FCM_PRIVATE_KEY);
}
async function sendFCMNotification(deviceToken, payload) {
  try {
    const fcmApp = getApp();
    const message = {
      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 : ""
      },
      token: deviceToken
    };
    const messageId = await admin.messaging(fcmApp).send(message);
    return { messageId, success: true };
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    console.error("[FCM] Send failed:", errorMessage);
    return { error: errorMessage, success: false };
  }
}
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;
}
function readPersistedDeviceToken(platform) {
  const tokensFile = join(homedir(), ".shooter", "device-tokens.json");
  if (!existsSync(tokensFile)) {
    return void 0;
  }
  try {
    const parsed = JSON.parse(readFileSync(tokensFile, "utf-8"));
    if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
      const value = parsed[platform];
      return typeof value === "string" && value ? value : void 0;
    }
  } catch {
  }
  return void 0;
}
let apnsSingleton = null;
function getAPNsClient() {
  if (!apnsSingleton) {
    apnsSingleton = new LibraryAPNsService();
  }
  return apnsSingleton;
}
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 (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 platform = private_env.DEVICE_PLATFORM || "ios";
    if (platform === "android") {
      if (!isFCMConfigured()) {
        return json(
          {
            details: "Missing FCM_PROJECT_ID, FCM_CLIENT_EMAIL, or FCM_PRIVATE_KEY environment variables",
            error: "FCM not configured"
          },
          { status: 500 }
        );
      }
      const androidToken = requestDeviceToken?.trim() || private_env.ANDROID_DEVICE_TOKEN?.trim() || readPersistedDeviceToken("android");
      if (!androidToken) {
        return json(
          {
            details: "No Android device token available — set ANDROID_DEVICE_TOKEN, pass deviceToken in the request body, or open the Android app so it can auto-register its FCM token.",
            error: "No device token configured"
          },
          { status: 500 }
        );
      }
      const fcmResult = await sendFCMNotification(androidToken, payload);
      if (fcmResult.success) {
        recordNotification(title, message, data);
        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, "sent", data));
        return json({
          message: "Notification sent successfully",
          requestId: canonicalRequestId,
          result: { messageId: fcmResult.messageId },
          success: true,
          timestamp: (/* @__PURE__ */ new Date()).toISOString()
        });
      } else {
        console.error(`[notify] FCM delivery failed: ${fcmResult.error}`);
        releaseNotification(title, message, data);
        addNotification(
          buildNotificationRecord(
            canonicalRequestId,
            title,
            message,
            "failed",
            data,
            fcmResult.error
          )
        );
        return json(
          {
            details: fcmResult.error,
            error: "Failed to send notification",
            requestId: canonicalRequestId,
            timestamp: (/* @__PURE__ */ new Date()).toISOString()
          },
          { status: 500 }
        );
      }
    } else {
      if (!apnsClient.isConfigured()) {
        return json(
          {
            details: "Missing APNS_KEY, APNS_KEY_ID, or APNS_TEAM_ID environment variables",
            error: "APNs client not configured"
          },
          { status: 500 }
        );
      }
      const deviceToken = requestDeviceToken?.trim() || private_env.DEVICE_TOKEN?.trim();
      if (!deviceToken) {
        return json(
          {
            details: "DEVICE_TOKEN environment variable is missing and no deviceToken in request body",
            error: "No device token configured"
          },
          { status: 500 }
        );
      }
      try {
        const result = await apnsClient.sendNotification(deviceToken, payload);
        recordNotification(title, message, data);
        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, "sent", data));
        return json({
          message: "Notification sent successfully",
          requestId: canonicalRequestId,
          result,
          success: true,
          timestamp: (/* @__PURE__ */ new Date()).toISOString()
        });
      } catch (notificationError) {
        const notifErrMsg = toErrorMessage(notificationError);
        console.error(`[notify] APNs delivery failed: ${notifErrMsg}`);
        releaseNotification(title, message, data);
        addNotification(
          buildNotificationRecord(canonicalRequestId, title, message, "failed", data, notifErrMsg)
        );
        return json(
          {
            details: notifErrMsg,
            error: "Failed to send notification",
            requestId: canonicalRequestId,
            timestamp: (/* @__PURE__ */ new Date()).toISOString()
          },
          { status: 500 }
        );
      }
    }
  } 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-05JJOdcX.js.map
