const NOTIFICATIONS_KEY = "codexRemote.notifications";
const SESSION_SNAPSHOT_KEY = "codexRemote.notificationSessions";
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
const SEVEN_DAYS_MS = 7 * ONE_DAY_MS;
const MAX_DISMISSED_IDS = 500;

export function readPersistedNotifications(storage = safeStorage(), options = {}) {
  try {
    const raw = storage?.getItem(NOTIFICATIONS_KEY);
    if (!raw) return { notifications: [], dismissedIds: [] };
    const parsed = JSON.parse(raw);
    return cleanupNotificationState({
      notifications: sanitizeNotifications(parsed.notifications),
      dismissedIds: sanitizeStrings(parsed.dismissedIds)
    }, options);
  } catch {
    return { notifications: [], dismissedIds: [] };
  }
}

export function writePersistedNotifications(storage = safeStorage(), state = {}, options = {}) {
  if (!storage) return;
  const payload = cleanupNotificationState({
    notifications: sanitizeNotifications(state.notifications),
    dismissedIds: sanitizeStrings(state.dismissedIds)
  }, options);
  try {
    storage.setItem(NOTIFICATIONS_KEY, JSON.stringify(payload));
  } catch {
    return false;
  }
  return true;
}

export function readPersistedSessionSnapshot(storage = safeStorage()) {
  try {
    const raw = storage?.getItem(SESSION_SNAPSHOT_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) return null;
    return parsed.map(persistableSessionObservation).filter(Boolean);
  } catch {
    return null;
  }
}

export function writePersistedSessionSnapshot(storage = safeStorage(), sessions = []) {
  if (!storage) return false;
  const payload = Array.isArray(sessions)
    ? sessions.map(persistableSessionObservation).filter(Boolean)
    : [];
  try {
    storage.setItem(SESSION_SNAPSHOT_KEY, JSON.stringify(payload));
  } catch {
    return false;
  }
  return true;
}

export function persistableNotification(notification) {
  if (!notification || typeof notification.id !== "string" || !notification.id.trim()) return null;
  if (typeof notification.type !== "string" || !notification.type.trim()) return null;
  if (typeof notification.title !== "string" || !notification.title.trim()) return null;
  if (typeof notification.createdAt !== "string" || !notification.createdAt.trim()) return null;
  const conversationTitle = typeof notification.conversationTitle === "string"
    ? notification.conversationTitle.trim()
    : "";
  return {
    id: notification.id,
    type: notification.type,
    title: notification.title,
    detail: typeof notification.detail === "string" ? notification.detail : "",
    ...(conversationTitle ? { conversationTitle } : {}),
    accountId: typeof notification.accountId === "string" ? notification.accountId : "",
    sessionId: typeof notification.sessionId === "string" ? notification.sessionId : null,
    createdAt: notification.createdAt
  };
}

export function cleanupNotificationState(state = {}, { sessions, now = new Date().toISOString() } = {}) {
  const timestamp = Date.parse(now);
  const sessionsById = Array.isArray(sessions) ? new Map(sessions.map((session) => [session.id, session])) : null;
  const notifications = sanitizeNotifications(state.notifications)
    .map((notification) => fillNotificationConversationTitle(notification, sessionsById))
    .filter((notification) => !isExpiredNotification(notification, timestamp, sessionsById));
  return {
    notifications: collapseSessionNotifications(notifications),
    dismissedIds: sanitizeStrings(state.dismissedIds)
  };
}

function fillNotificationConversationTitle(notification, sessionsById) {
  if (notification.conversationTitle || !notification.sessionId || !sessionsById) return notification;
  const conversationTitle = sessionsById.get(notification.sessionId)?.conversationTitle?.trim();
  return conversationTitle ? { ...notification, conversationTitle } : notification;
}

export function mergeNotificationState(state = {}, items = []) {
  const notifications = sanitizeNotifications(state.notifications);
  const dismissedIds = sanitizeStrings(state.dismissedIds);
  const known = new Set(notifications.map((item) => item.id));
  const dismissed = new Set(dismissedIds);
  const incoming = sanitizeNotifications(items).filter((item) => !known.has(item.id) && !dismissed.has(item.id));
  if (incoming.length === 0) return state;
  return {
    notifications: collapseSessionNotifications([...incoming, ...notifications]),
    dismissedIds
  };
}

export function dismissSessionNotificationState(state = {}, sessionId, { types } = {}) {
  const notifications = sanitizeNotifications(state.notifications);
  const allowedTypes = Array.isArray(types) ? new Set(types) : null;
  const matches = (item) => item.sessionId === sessionId && (!allowedTypes || allowedTypes.has(item.type));
  const dismissed = notifications.filter(matches);
  if (dismissed.length === 0) return state;
  return {
    notifications: notifications.filter((item) => !matches(item)),
    dismissedIds: [...new Set([...dismissed.map((item) => item.id), ...sanitizeStrings(state.dismissedIds)])]
  };
}

function isExpiredNotification(notification, now, sessionsById) {
  if (isOlderThan(notification, now, ONE_DAY_MS) && ["usage-critical", "usage-low", "weekly-reset"].includes(notification.type)) return true;
  if (isOlderThan(notification, now, SEVEN_DAYS_MS) && notification.type === "completed") return true;
  if (notification.type !== "approval" || !sessionsById || !notification.sessionId) return false;
  const session = sessionsById.get(notification.sessionId);
  return Boolean(session && session.status !== "waiting_approval");
}

function isOlderThan(notification, now, maxAgeMs) {
  const createdAt = Date.parse(notification.createdAt);
  return Number.isFinite(now) && Number.isFinite(createdAt) && now - createdAt >= maxAgeMs;
}

function sanitizeNotifications(notifications) {
  if (!Array.isArray(notifications)) return [];
  return notifications.map(persistableNotification).filter(Boolean);
}

function collapseSessionNotifications(notifications) {
  const latestBySession = new Map();
  for (const notification of notifications) {
    if (!notification.sessionId) continue;
    const latest = latestBySession.get(notification.sessionId);
    if (!latest || isLater(notification.createdAt, latest.createdAt)) {
      latestBySession.set(notification.sessionId, notification);
    }
  }
  return notifications.filter((notification) => !notification.sessionId || latestBySession.get(notification.sessionId) === notification);
}

function isLater(candidate, current) {
  const candidateAt = Date.parse(candidate);
  const currentAt = Date.parse(current);
  if (!Number.isFinite(candidateAt)) return false;
  return !Number.isFinite(currentAt) || candidateAt > currentAt;
}

function sanitizeStrings(values) {
  if (!Array.isArray(values)) return [];
  return [...new Set(values.filter((value) => typeof value === "string" && value.trim()))].slice(0, MAX_DISMISSED_IDS);
}

function persistableSessionObservation(session) {
  if (!session || typeof session.id !== "string" || !session.id.trim()) return null;
  if (typeof session.status !== "string" || !session.status.trim()) return null;
  return {
    id: session.id,
    accountId: typeof session.accountId === "string" ? session.accountId : "",
    projectLabel: typeof session.projectLabel === "string" ? session.projectLabel : "",
    codexThreadId: typeof session.codexThreadId === "string" ? session.codexThreadId : "",
    conversationTitle: typeof session.conversationTitle === "string" ? session.conversationTitle.trim() : "",
    status: session.status,
    updatedAt: typeof session.updatedAt === "string" ? session.updatedAt : ""
  };
}

function safeStorage() {
  return typeof window === "undefined" ? null : window.localStorage;
}
