/**
 * Memory-directory scanning primitives. Split out of findRelevantMemories.ts
 * so extractMemories can import the scan without pulling in sideQuery and
 * the API-client chain (which closed a cycle through memdir.ts — #25372).
 */

import { readdir } from 'fs/promises'
import { basename, join } from 'path'
import { parseFrontmatter } from '../utils/frontmatterParser.js'
import { readFileInRange } from '../utils/readFileInRange.js'
import { type MemoryType, parseMemoryType } from './memoryTypes.js'

export type MemoryHeader = {
  filename: string
  filePath: string
  mtimeMs: number
  description: string | null
  type: MemoryType | undefined
}

const MAX_MEMORY_FILES = 200
const FRONTMATTER_MAX_LINES = 30

/**
 * Scan a memory directory for .md files, read their frontmatter, and return
 * a header list sorted newest-first (capped at MAX_MEMORY_FILES). Shared by
 * findRelevantMemories (query-time recall) and extractMemories (pre-injects
 * the listing so the extraction agent doesn't spend a turn on `ls`).
 *
 * Single-pass: readFileInRange stats internally and returns mtimeMs, so we
 * read-then-sort rather than stat-sort-read. For the common case (N ≤ 200)
 * this halves syscalls vs a separate stat round; for large N we read a few
 * extra small files but still avoid the double-stat on the surviving 200.
 */
export async function scanMemoryFiles(
  memoryDir: string,
  signal: AbortSignal,
): Promise<MemoryHeader[]> {
  try {
    const entries = await readdir(memoryDir, { recursive: true })
    const mdFiles = entries.filter((f) => f.endsWith('.md') && basename(f) !== 'MEMORY.md')

    const headerResults = await Promise.allSettled(
      mdFiles.map(async (relativePath): Promise<MemoryHeader> => {
        const filePath = join(memoryDir, relativePath)
        const { content, mtimeMs } = await readFileInRange(
          filePath,
          0,
          FRONTMATTER_MAX_LINES,
          undefined,
          signal,
        )
        const { frontmatter } = parseFrontmatter(content, filePath)
        return {
          filename: relativePath,
          filePath,
          mtimeMs,
          description: frontmatter.description || null,
          type: parseMemoryType(frontmatter.type),
        }
      }),
    )

    return headerResults
      .filter((r): r is PromiseFulfilledResult<MemoryHeader> => r.status === 'fulfilled')
      .map((r) => r.value)
      .sort((a, b) => b.mtimeMs - a.mtimeMs)
      .slice(0, MAX_MEMORY_FILES)
  } catch {
    return []
  }
}

export type MemoryStaleness = 'fresh' | 'stale' | 'very-stale'

const DAY_MS = 24 * 60 * 60 * 1000
const STALE_DAYS = 30
const VERY_STALE_DAYS = 90

/**
 * Classify a memory's freshness by last-modification age. The model uses
 * this to prioritize verification: fresh memories can be trusted
 * directly, stale ones should be double-checked against current code
 * state, very-stale ones should be verified before acting on them.
 *
 * Future mtimes (clock skew, local/UTC drift) are treated as fresh
 * rather than tagged — a harmless mis-classification vs the alternative
 * of flagging a brand-new memory as stale.
 */
export function computeMemoryStaleness(mtimeMs: number, now: number = Date.now()): MemoryStaleness {
  const ageDays = Math.max(0, (now - mtimeMs) / DAY_MS)
  if (ageDays >= VERY_STALE_DAYS) return 'very-stale'
  if (ageDays >= STALE_DAYS) return 'stale'
  return 'fresh'
}

function stalenessTag(staleness: MemoryStaleness): string {
  switch (staleness) {
    case 'fresh':
      return ''
    case 'stale':
      return '[stale] '
    case 'very-stale':
      return '[very-stale, verify before trusting] '
  }
}

/**
 * Format memory headers as a text manifest: one line per file with
 * [type] filename (timestamp): description. Used by both the recall
 * selector prompt and the extraction-agent prompt.
 *
 * Memories older than 30 days are tagged [stale]; older than 90 days
 * [very-stale, verify before trusting]. The tags steer the model to
 * verify stale memories before acting on them, which closes the
 * "no TTL signal" gap from the harness self-critique.
 */
export function formatMemoryManifest(memories: MemoryHeader[], now: number = Date.now()): string {
  return memories
    .map((m) => {
      const typeTag = m.type ? `[${m.type}] ` : ''
      const staleTag = stalenessTag(computeMemoryStaleness(m.mtimeMs, now))
      const ts = new Date(m.mtimeMs).toISOString()
      return m.description
        ? `- ${staleTag}${typeTag}${m.filename} (${ts}): ${m.description}`
        : `- ${staleTag}${typeTag}${m.filename} (${ts})`
    })
    .join('\n')
}
