#!/usr/bin/env node
/**
 * Memory Store Adapter — File-system operations for MemoryCore facade.
 * 
 * PR-2: MemoryCore Facade
 * Architecture Freeze v2 (PR-1): Adapter layer
 * 
 * Handles:
 *   - Reading memory files (MEMORY.md, memory/*.md)
 *   - Writing to test namespace only
 *   - Deleting from test namespace only
 *   - Collecting file stats
 * 
 * Safety: write() and delete() are locked to "test" namespace.
 * Real MEMORY.md and memory/*.md are read-only for this adapter.
 */

import { readFileSync, writeFileSync, unlinkSync, existsSync, statSync, readdirSync } from "node:fs";
import { join, resolve, relative, dirname, extname } from "node:path";
import { mkdirSync } from "node:fs";

// ─── Constants ────────────────────────────────────────

/**
 * Allowed read paths relative to workspace root.
 * write/delete are restricted to "test" namespace only.
 */
const ALLOWED_READ_ROOTS = [
  "MEMORY.md",
  "SOUL.md",
  "USER.md",
  "AGENTS.md",
  "IDENTITY.md",
  "TOOLS.md",
  "HEARTBEAT.md",
  "memory/",
  "docs/",
];

const TEST_NAMESPACE_PREFIX = "test-memory-core-";
const TEST_DIR = ".memory-core-test";

/**
 * @param {string} workspaceRoot
 */
export function createMemoryStoreAdapter(workspaceRoot) {
  const root = resolve(workspaceRoot);
  const testDir = join(root, TEST_DIR);

  // Ensure test dir exists
  function ensureTestDir() {
    if (!existsSync(testDir)) {
      mkdirSync(testDir, { recursive: true });
    }
  }

  /**
   * Check if a path is within allowed read scope.
   * @param {string} relativePath
   * @returns {boolean}
   */
  function isAllowedReadPath(relativePath) {
    const normalized = relativePath.replace(/^\/+/, "");
    return ALLOWED_READ_ROOTS.some((prefix) => normalized.startsWith(prefix));
  }

  /**
   * Read a memory file. Returns content + metadata or error.
   * @param {string} relativePath - Path relative to workspace root
   * @returns {{ok: boolean, content?: string, metadata?: object, error?: {code: string, message: string}}}
   */
  function readFile(relativePath) {
    try {
      const normalized = relativePath.replace(/^\/+/, "");
      
      if (normalized.includes("..")) {
        return { ok: false, error: { code: "PATH_TRAVERSAL", message: "Path traversal denied" } };
      }

      if (!isAllowedReadPath(normalized)) {
        // Allow reading test namespace files
        const basename = normalized.split("/").pop() || "";
        if (!basename.startsWith(TEST_NAMESPACE_PREFIX)) {
          return { ok: false, error: { code: "PATH_NOT_ALLOWED", message: `Path not in allowed read scope: ${normalized}` } };
        }
      }

      const fullPath = join(root, normalized);
      
      if (!existsSync(fullPath)) {
        return { ok: false, error: { code: "NOT_FOUND", message: `File not found: ${normalized}` } };
      }

      const stat = statSync(fullPath);
      if (!stat.isFile()) {
        return { ok: false, error: { code: "NOT_A_FILE", message: `Not a regular file: ${normalized}` } };
      }

      const content = readFileSync(fullPath, "utf8");
      const metadata = {
        path: normalized,
        sizeBytes: stat.size,
        modifiedAt: stat.mtime.toISOString(),
      };

      return { ok: true, content, metadata };
    } catch (e) {
      return { ok: false, error: { code: "READ_ERROR", message: e.message } };
    }
  }

  /**
   * Write content to test namespace only.
   * @param {string} key - Entry key (will be prefixed with TEST_NAMESPACE_PREFIX)
   * @param {string} content
   * @returns {{ok: boolean, path?: string, error?: {code: string, message: string}}}
   */
  function writeTestEntry(key, content) {
    try {
      ensureTestDir();

      // Safety: only allow test namespace writes
      if (!key || typeof key !== "string") {
        return { ok: false, error: { code: "INVALID_KEY", message: "Key must be a non-empty string" } };
      }

      // Reject keys that try to escape
      if (key.includes("..") || key.includes("/") || key.includes("\\")) {
        return { ok: false, error: { code: "INVALID_KEY", message: "Key must not contain path separators or .." } };
      }

      // Reject empty or suspicious content
      if (typeof content !== "string") {
        return { ok: false, error: { code: "INVALID_CONTENT", message: "Content must be a string" } };
      }

      const filename = `${TEST_NAMESPACE_PREFIX}${key}.tmp`;
      const filepath = join(testDir, filename);
      
      writeFileSync(filepath, content, "utf8");
      
      return { ok: true, path: relative(root, filepath) };
    } catch (e) {
      return { ok: false, error: { code: "WRITE_ERROR", message: e.message } };
    }
  }

  /**
   * Delete a test namespace entry only.
   * @param {string} key
   * @returns {{ok: boolean, error?: {code: string, message: string}}}
   */
  function deleteTestEntry(key) {
    try {
      if (!key || typeof key !== "string") {
        return { ok: false, error: { code: "INVALID_KEY", message: "Key must be a non-empty string" } };
      }

      if (key.includes("..") || key.includes("/") || key.includes("\\")) {
        return { ok: false, error: { code: "INVALID_KEY", message: "Key must not contain path separators or .." } };
      }

      const filename = `${TEST_NAMESPACE_PREFIX}${key}.tmp`;
      const filepath = join(testDir, filename);

      if (!existsSync(filepath)) {
        return { ok: false, error: { code: "NOT_FOUND", message: `Test entry not found: ${key}` } };
      }

      unlinkSync(filepath);
      return { ok: true };
    } catch (e) {
      return { ok: false, error: { code: "DELETE_ERROR", message: e.message } };
    }
  }

  /**
   * Walk a directory recursively and collect file stats.
   * @param {string} dir
   * @param {Function} onFile
   */
  function walkDir(dir, onFile) {
    try {
      const entries = readdirSync(dir, { withFileTypes: true });
      for (const entry of entries) {
        const fullPath = join(dir, entry.name);
        if (entry.isDirectory() && !entry.name.startsWith(".")) {
          walkDir(fullPath, onFile);
        } else if (entry.isFile() && entry.name.endsWith(".md")) {
          onFile(fullPath);
        }
      }
    } catch {
      // Skip directories we can't read
    }
  }

  /**
   * Collect memory file statistics.
   * @returns {{memoryFileCount: number, memoryTotalSizeBytes: number, breakdown: object}}
   */
  function getStats() {
    const memoryDir = join(root, "memory");
    const breakdown = { daily: 0, registers: 0, vault: 0, core: 0, other: 0 };
    let totalFiles = 0;
    let totalSize = 0;

    // Count core files
    for (const coreFile of ["MEMORY.md", "SOUL.md", "USER.md", "AGENTS.md", "IDENTITY.md", "TOOLS.md", "HEARTBEAT.md"]) {
      const fp = join(root, coreFile);
      if (existsSync(fp)) {
        try {
          const s = statSync(fp);
          totalFiles++;
          totalSize += s.size;
          breakdown.core++;
        } catch {}
      }
    }

    // Count memory/ files
    if (existsSync(memoryDir)) {
      walkDir(memoryDir, (filePath) => {
        try {
          const s = statSync(filePath);
          totalFiles++;
          totalSize += s.size;
          
          const rel = relative(memoryDir, filePath);
          if (rel.startsWith("daily")) breakdown.daily++;
          else if (rel.startsWith("registers")) breakdown.registers++;
          else if (rel.includes("vault")) breakdown.vault++;
          else breakdown.other++;
        } catch {}
      });
    }

    return {
      memoryFileCount: totalFiles,
      memoryTotalSizeBytes: totalSize,
      breakdown,
    };
  }

  /**
   * List all readable memory file paths.
   * @returns {string[]}
   */
  function listMemoryFiles() {
    const files = [];

    for (const coreFile of ["MEMORY.md", "SOUL.md", "USER.md", "AGENTS.md", "IDENTITY.md", "TOOLS.md", "HEARTBEAT.md"]) {
      const fp = join(root, coreFile);
      if (existsSync(fp)) files.push(coreFile);
    }

    const memoryDir = join(root, "memory");
    if (existsSync(memoryDir)) {
      walkDir(memoryDir, (filePath) => {
        files.push(relative(root, filePath));
      });
    }

    return files;
  }

  return {
    readFile,
    writeTestEntry,
    deleteTestEntry,
    getStats,
    listMemoryFiles,
    root,
    testDir,
  };
}
