/*
 * Memory event salience — PF-aligned exponential decay (see workspaceFreshness).
 */

import type { TeamMemoryEvent } from './types';
import type { SalienceThresholds } from './team-hub-config';
import { DEFAULT_SALIENCE_THRESHOLDS } from './team-hub-config';

export interface RankedMemoryEvent extends TeamMemoryEvent {
  salience: number;
  finalScore: number;
}

export function defaultSinceTs(
  thresholds: SalienceThresholds = DEFAULT_SALIENCE_THRESHOLDS,
  now: Date = new Date(),
): string {
  const ms = thresholds.interactionWindowDays * 86400 * 1000;
  return new Date(now.getTime() - ms).toISOString();
}

/** MVP1: decay from event timestamp only. */
export function computeEventSalience(
  event: TeamMemoryEvent,
  thresholds: SalienceThresholds = DEFAULT_SALIENCE_THRESHOLDS,
  now: Date = new Date(),
  opts?: {
    lastAccessedAt?: string | null;
    accessCount?: number;
  },
): number {
  const ts = Date.parse(event.timestamp);
  if (Number.isNaN(ts)) return 0;
  const nowMs = now.getTime();
  let effectiveAgeMs = nowMs - ts;
  if (opts?.lastAccessedAt) {
    const la = Date.parse(opts.lastAccessedAt);
    if (!Number.isNaN(la) && la > ts) {
      effectiveAgeMs = nowMs - la;
    }
  }
  if (effectiveAgeMs < 0) effectiveAgeMs = 0;

  const accessCount = opts?.accessCount ?? 0;
  const strengthMs =
    thresholds.strengthBaseMs +
    thresholds.strengthAccessK * Math.log(1 + accessCount);
  const tauMs = Math.max(thresholds.decayTauDays * 86400 * 1000, 1);
  const divisor = accessCount > 0 ? strengthMs : tauMs;
  return Math.exp(-effectiveAgeMs / divisor);
}

export function rankMemoryResults(
  events: TeamMemoryEvent[],
  thresholds: SalienceThresholds = DEFAULT_SALIENCE_THRESHOLDS,
  semanticScores?: Map<string, number>,
  now: Date = new Date(),
  accessByEventId?: Map<string, { lastAccessedAt: string | null; accessCount: number }>,
): RankedMemoryEvent[] {
  const ranked = events.map((event) => {
    const access = accessByEventId?.get(event.eventId);
    const salience = computeEventSalience(event, thresholds, now, {
      lastAccessedAt:
        access?.lastAccessedAt ??
        (typeof event.metadata.lastAccessedAt === 'string'
          ? event.metadata.lastAccessedAt
          : null),
      accessCount:
        access?.accessCount ??
        (typeof event.metadata.accessCount === 'number'
          ? event.metadata.accessCount
          : Number(event.metadata.accessCount ?? 0)),
    });
    const semantic = semanticScores?.get(event.eventId) ?? 1;
    const finalScore = semantic * salience;
    return {
      ...event,
      metadata: {
        ...event.metadata,
        _salience: salience,
        _finalScore: finalScore,
        ...(semanticScores?.has(event.eventId)
          ? { _score: semanticScores.get(event.eventId) }
          : {}),
      },
      salience,
      finalScore,
    };
  });
  ranked.sort((a, b) => b.finalScore - a.finalScore);
  return ranked;
}

export function isTombstoned(event: TeamMemoryEvent): boolean {
  return event.metadata.tombstonedAt != null && event.metadata.tombstonedAt !== '';
}

export function isPinned(event: TeamMemoryEvent): boolean {
  return event.metadata.pinned === true;
}

export function listDecayCandidates(
  events: TeamMemoryEvent[],
  thresholds: SalienceThresholds = DEFAULT_SALIENCE_THRESHOLDS,
  now: Date = new Date(),
): TeamMemoryEvent[] {
  const minAgeMs = thresholds.minRetentionDays * 86400 * 1000;
  return events.filter((ev) => {
    if (isTombstoned(ev) || isPinned(ev)) return false;
    const ts = Date.parse(ev.timestamp);
    if (Number.isNaN(ts) || now.getTime() - ts < minAgeMs) return false;
    const salience = computeEventSalience(ev, thresholds, now);
    return salience < thresholds.candidateEpsilon;
  });
}
