/*
 * Pure workspace-freshness computation. Kept separate from JsonStorage so
 * it can be unit-tested without spinning up file watchers / mutex state /
 * the rest of the storage scaffolding. The storage method just does the
 * disk read and delegates here.
 *
 * Definition (see also feedback_workspace_dormancy.md in user memory):
 *   A workspace is "active" iff EITHER
 *     (a) any task created or updated within the last
 *         `interactionWindowDays`, OR
 *     (b) any open task with a deadline within the next
 *         `futureDeadlineHorizonDays`.
 *   Otherwise it is "dormant".
 *
 * The freshness score is an Ebbinghaus-style decayed sum of all interaction
 * timestamps with time-constant `decayTauDays`. It is informational; the
 * active/dormant boolean is governed by the two cutoffs above. Score
 * exists so callers can rank active workspaces against each other.
 */

import type { Task } from '../types/schema';
import {
  DEFAULT_WORKSPACE_FRESHNESS_THRESHOLDS,
  type WorkspaceFreshness,
  type WorkspaceFreshnessThresholds,
} from '../types/schema';

const FINISHED_STATUSES = new Set(['complete', 'completed', 'cancelled']);

// Minimal shape required from each task; uses WeightedTask in practice but
// also accepts the bare Task interface so callers can use either.
export type FreshnessTaskInput = Task & {
  deadline?: string;
};

export function computeFreshness(
  tasks: ReadonlyArray<FreshnessTaskInput>,
  thresholds: WorkspaceFreshnessThresholds = DEFAULT_WORKSPACE_FRESHNESS_THRESHOLDS,
  now: Date = new Date(),
): WorkspaceFreshness {
  const tauMs = thresholds.decayTauDays * 86400 * 1000;
  const interactionWindowMs = thresholds.interactionWindowDays * 86400 * 1000;
  const horizonMs = thresholds.futureDeadlineHorizonDays * 86400 * 1000;
  const nowMs = now.getTime();

  let freshnessScore = 0;
  let lastTouchMs: number | null = null;
  let openTaskCount = 0;
  let futureDeadlineCount = 0;

  for (const t of tasks) {
    for (const field of ['createdAt', 'updatedAt'] as const) {
      const raw = t[field];
      if (!raw) continue;
      const ts = Date.parse(raw);
      if (Number.isNaN(ts)) continue;
      const ageMs = nowMs - ts;
      if (ageMs >= 0) {
        freshnessScore += Math.exp(-ageMs / tauMs);
      }
      if (lastTouchMs === null || ts > lastTouchMs) {
        lastTouchMs = ts;
      }
    }

    if (!FINISHED_STATUSES.has(t.status)) {
      openTaskCount += 1;
      const dl = t.deadline;
      if (dl) {
        const dlMs = Date.parse(dl);
        if (
          !Number.isNaN(dlMs) &&
          dlMs >= nowMs &&
          dlMs - nowMs <= horizonMs
        ) {
          futureDeadlineCount += 1;
        }
      }
    }
  }

  const daysSinceTouch =
    lastTouchMs === null
      ? Number.POSITIVE_INFINITY
      : (nowMs - lastTouchMs) / 86400 / 1000;

  const recentInteraction =
    lastTouchMs !== null && nowMs - lastTouchMs <= interactionWindowMs;
  const hasFutureWork = futureDeadlineCount > 0;
  const active = recentInteraction || hasFutureWork;
  const activeReason: WorkspaceFreshness['activeReason'] = recentInteraction
    ? 'recent_interaction'
    : hasFutureWork
    ? 'future_deadline'
    : null;

  return {
    freshnessScore,
    lastTouchedAt: lastTouchMs === null ? null : new Date(lastTouchMs).toISOString(),
    daysSinceTouch,
    openTaskCount,
    futureDeadlineCount,
    active,
    dormant: !active,
    activeReason,
  };
}
