'use strict';

const crypto = require('crypto');

function stableStringify(value) {
  if (value === null || typeof value !== 'object') return JSON.stringify(value);
  if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
  const keys = Object.keys(value).sort();
  return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
}

function canonicalSessionForRevision(session) {
  const out = {};
  for (const key of Object.keys(session || {}).sort()) {
    if (key === 'ageMs') continue;
    if (key === 'evidence' && Array.isArray(session[key])) {
      out[key] = session[key].filter((item) => !/^last activity\b/i.test(String(item || '')));
      continue;
    }
    out[key] = session[key];
  }
  return out;
}

function publicLaneSummary(lane) {
  const { sessions, ...rest } = lane || {};
  return rest;
}

function hashValue(value) {
  return crypto.createHash('sha1').update(stableStringify(value)).digest('hex');
}

function snapshotIndex(snapshot) {
  const sessions = Array.isArray(snapshot?.sessions) ? snapshot.sessions : [];
  const sessionHashes = new Map();
  const sessionsById = new Map();
  const order = [];

  for (const session of sessions) {
    const id = String(session?.id || '');
    if (!id) continue;
    order.push(id);
    sessionsById.set(id, session);
    sessionHashes.set(id, hashValue(canonicalSessionForRevision(session)));
  }

  const revisionPayload = {
    counts: snapshot?.counts || {},
    order,
    sessions: order.map((id) => [id, sessionHashes.get(id)]),
  };

  return {
    revision: hashValue(revisionPayload).slice(0, 20),
    generatedAt: snapshot?.generatedAt || new Date().toISOString(),
    counts: snapshot?.counts || {},
    lanes: Array.isArray(snapshot?.lanes) ? snapshot.lanes.map(publicLaneSummary) : [],
    order,
    sessionsById,
    sessionHashes,
  };
}

function createStandupIncrementalCache(options = {}) {
  const maxEntries = Math.max(2, Number(options.maxEntries) || 12);
  const entries = new Map();

  function remember(index) {
    if (!index?.revision) return;
    entries.delete(index.revision);
    entries.set(index.revision, index);
    while (entries.size > maxEntries) {
      const first = entries.keys().next().value;
      entries.delete(first);
    }
  }

  function buildResponse(snapshot, sinceRevision = '') {
    const current = snapshotIndex(snapshot);
    const since = String(sinceRevision || '').trim();

    let response;
    if (since && since === current.revision) {
      response = {
        generatedAt: current.generatedAt,
        revision: current.revision,
        incremental: true,
        unchanged: true,
        counts: current.counts,
        lanes: current.lanes,
        sessionOrder: current.order,
        removedSessionIds: [],
        sessions: [],
      };
    } else {
      const previous = since ? entries.get(since) : null;
      if (previous) {
        const changed = [];
        for (const id of current.order) {
          if (previous.sessionHashes.get(id) !== current.sessionHashes.get(id)) {
            changed.push(current.sessionsById.get(id));
          }
        }
        const removed = [];
        for (const id of previous.order) {
          if (!current.sessionsById.has(id)) removed.push(id);
        }
        response = {
          generatedAt: current.generatedAt,
          revision: current.revision,
          incremental: true,
          unchanged: false,
          counts: current.counts,
          lanes: current.lanes,
          sessionOrder: current.order,
          removedSessionIds: removed,
          sessions: changed,
        };
      } else {
        response = {
          ...snapshot,
          revision: current.revision,
          incremental: false,
          unchanged: false,
        };
      }
    }

    remember(current);
    return response;
  }

  return {
    buildResponse,
    _entries: entries,
  };
}

module.exports = {
  createStandupIncrementalCache,
  stableStringify,
  snapshotIndex,
};
