import { spawnSync } from "child_process"
import { createRequire } from "node:module"
import fs from "fs"
import os from "os"
import path from "path"

const require2 = createRequire(import.meta.url)

/**
 * Resolve the memory directory from OPENCODE_MEMORY_DIR env var or fall back
 * to the default ~/.local/share/opencode/opencode-memory. Identical behaviour
 * across all 5 memory tools — previously duplicated 5×.
 */
export function resolveMemoryDir(): string {
  const env = process.env.OPENCODE_MEMORY_DIR
  if (env && env.length > 0) return path.normalize(env)
  return path.join(os.homedir(), ".local/share/opencode/opencode-memory")
}

/**
 * Memory categories (also the directories auto-created by memory-save).
 * Previously duplicated in memory-save and memory-list.
 */
export const CATEGORIES = [
  "preferences",
  "repos",
  "technical",
  "people",
  "workflows",
  "snippets",
  "notes",
]

/**
 * Frontmatter delimiter regex (flat YAML between --- fences). Previously
 * duplicated in memory-search, memory-list, memory-access.
 */
export const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/

export interface FrontmatterMeta {
  title?: string
  summary?: string
  importance?: string
  updated?: string
  tags?: string[]
  related?: string[]
  access_count?: number
}

/**
 * Parse flat frontmatter (regex per line, NOT real YAML — see memory note
 * "Frontmatter parser is regex, not YAML"). Returns typed meta; callers
 * ignore fields they don't use. Previously duplicated in memory-search (full)
 * and memory-list (partial) — unified to the full version.
 */
export function parseFrontmatter(content: string): FrontmatterMeta {
  const match = content.match(FRONTMATTER_RE)
  if (!match) return {}
  const yaml = match[1]
  const meta: FrontmatterMeta = {}
  for (const line of yaml.split("\n")) {
    const kv = line.match(/^(\w+):\s*(.+)$/)
    if (!kv) continue
    const [, key, rawValue] = kv
    const value = rawValue.trim()
    if (key === "title") meta.title = value
    else if (key === "summary") meta.summary = value
    else if (key === "importance") meta.importance = value
    else if (key === "updated") meta.updated = value
    else if (key === "access_count") {
      const parsed = parseInt(value, 10)
      meta.access_count = Number.isFinite(parsed) ? parsed : 0
    } else if (key === "tags" || key === "related") {
      const arr = value.match(/\[([^\]]*)\]/)
      if (arr && arr[1].trim()) {
        meta[key] = arr[1].split(",").map((t) => t.trim()).filter(Boolean)
      } else {
        meta[key] = []
      }
    }
  }
  return meta
}

/**
 * Resolve the ripgrep binary path. Checks @vscode/ripgrep npm package first
 * (platform-specific binary). When `allowSystemFallback` is true (memory-search),
 * falls back to a system `rg` on $PATH. When false (memory-doctor), returns
 * null if the npm package is unavailable so the doctor can report the two
 * cases separately. Previously duplicated with divergent behaviour.
 */
export function resolveRgBinary(opts?: { allowSystemFallback?: boolean }): string | null {
  try {
    const mod = require2("@vscode/ripgrep")
    if (typeof mod.rgPath === "string" && mod.rgPath.length > 0 && fs.existsSync(mod.rgPath)) {
      return mod.rgPath
    }
  } catch (e) {
    process.stderr.write(`[memory] @vscode/ripgrep require failed: ${(e as Error).message}\n`)
  }
  if (opts?.allowSystemFallback) {
    const sys = spawnSync("rg", ["--version"], { encoding: "utf-8" })
    if (sys.status === 0) return "rg"
  }
  return null
}

/**
 * Recursively collect absolute paths of *.md files under `dir`, skipping
 * dotfiles, .git/, .rag/, and INDEX.md. Previously duplicated in memory-list
 * (collecting relative paths) and memory-doctor (counting only); unified to
 * return absolute paths — callers convert with path.relative() as needed.
 */
export function walkMd(dir: string): string[] {
  const out: string[] = []
  const walk = (d: string) => {
    let entries: fs.Dirent[]
    try {
      entries = fs.readdirSync(d, { withFileTypes: true })
    } catch (e) {
      process.stderr.write(`[memory] walkMd readdir failed on ${d}: ${(e as Error).message}\n`)
      return
    }
    for (const e of entries) {
      if (e.name.startsWith(".")) continue
      const full = path.join(d, e.name)
      if (e.isDirectory()) {
        if (e.name === ".git" || e.name === ".rag") continue
        walk(full)
      } else if (e.isFile() && e.name.endsWith(".md") && e.name !== "INDEX.md") {
        out.push(full)
      }
    }
  }
  walk(dir)
  return out
}