#!/usr/bin/env node
/**
 * MemoryCore Facade — Unified memory access interface.
 * 
 * PR-2: MemoryCore Facade
 * Architecture Freeze v2 (PR-1): Core §2 — memory-core
 * 
 * This is a FACADE, not a new backend.
 * It wraps existing file-system memory access with a unified contract.
 * 
 * Internal delegates:
 *   - search → FTS5SearchAdapter (SQLite FTS5, PRIMARY) → MemorySearchAdapter (file, FALLBACK)
 *   - get    → MemoryStoreAdapter (file read)
 *   - write  → MemoryStoreAdapter (test namespace only)
 *   - delete → MemoryStoreAdapter (test namespace only)
 *   - stats  → MemoryStoreAdapter (file stats)
 *   - health → self-check against adapters
 * 
 * PR-3.5: search adapter upgraded to FTS5 (same data source as legacy memory_search).
 * File-search adapter retained as fallback.
 * 
 * Usage:
 *   import { createMemoryCore } from "./memory-core-facade.js";
 *   const mc = createMemoryCore("/path/to/workspace");
 *   const result = await mc.search("important decision");
 *   const file = await mc.get("MEMORY.md");
 * 
 * @module memory-core-facade
 */

import { createMemoryStoreAdapter } from "./memory-store-adapter.mjs";
import { createMemorySearchAdapter } from "./memory-search-adapter.mjs";
import { createFTS5SearchAdapter } from "./memory-fts5-search-adapter.mjs";
import { join } from "node:path";
import { homedir } from "node:os";

/**
 * Create a MemoryCore facade instance.
 * 
 * @param {string} workspaceRoot - Absolute path to workspace root
 * @returns {object} MemoryCore facade API
 */
export function createMemoryCore(workspaceRoot) {
  const store = createMemoryStoreAdapter(workspaceRoot);

  // PR-3.5: FTS5 adapter (primary) with file-search as fallback
  const fileSearchAdapter = createMemorySearchAdapter(workspaceRoot);
  const memoryDbPath = join(homedir(), ".openclaw", "memory", "main.sqlite");
  const fts5Adapter = createFTS5SearchAdapter(memoryDbPath, fileSearchAdapter);
  // Default search adapter: FTS5 primary, file-search fallback
  const searchAdapter = fts5Adapter;

  // Known memory plugin count (hardcoded from freeze doc)
  const KNOWN_PLUGIN_COUNT = 2; // memory-core + memory-wiki
  const LEGACY_ITEM_COUNT = 9;   // From architecture-freeze-v2.md §4

  // ─── Public API ──────────────────────────────────

  /**
   * Search across memory files.
   * 
   * @param {string} query - Search query
   * @param {import("./memory-core-types.js").MemoryCoreSearchOpts} [opts]
   * @returns {Promise<import("./memory-core-types.js").MemoryCoreSearchResponse>}
   */
  async function search(query, opts = {}) {
    const startTime = Date.now();
    
    try {
      const result = await searchAdapter.search(query, opts);
      return {
        ...result,
        latencyMs: Date.now() - startTime,
      };
    } catch (e) {
      return {
        ok: false,
        results: [],
        source: "memory-core-facade",
        latencyMs: Date.now() - startTime,
        error: { code: "FACADE_ERROR", message: e.message },
      };
    }
  }

  /**
   * Read a memory file by path.
   * Wraps current memory_get behavior.
   * 
   * @param {string} idOrPath - File path relative to workspace (e.g. "MEMORY.md", "memory/daily/2026-06-04.md")
   * @param {import("./memory-core-types.js").MemoryCoreGetOpts} [opts]
   * @returns {Promise<import("./memory-core-types.js").MemoryCoreGetResponse>}
   */
  async function get(idOrPath, opts = {}) {
    const { maxLines, includeMetadata = true } = opts;
    
    try {
      const result = store.readFile(idOrPath);
      
      if (!result.ok) {
        return {
          ok: false,
          error: result.error,
        };
      }

      let content = result.content;
      if (maxLines && maxLines > 0) {
        const lines = content.split("\n");
        content = lines.slice(0, maxLines).join("\n");
        if (lines.length > maxLines) {
          content += `\n... (truncated, ${lines.length - maxLines} more lines)`;
        }
      }

      const response = { ok: true, content };
      if (includeMetadata && result.metadata) {
        response.metadata = result.metadata;
      }
      
      return response;
    } catch (e) {
      return {
        ok: false,
        error: { code: "GET_ERROR", message: e.message },
      };
    }
  }

  /**
   * Write an entry. PR-2: test namespace only.
   * Does NOT write to real MEMORY.md or memory/*.md.
   * 
   * @param {import("./memory-core-types.js").MemoryCoreWriteEntry} entry
   * @returns {Promise<import("./memory-core-types.js").MemoryCoreWriteResponse>}
   */
  async function write(entry) {
    const { namespace, key, content } = entry;

    // Safety gate: only "test" namespace allowed in PR-2
    if (namespace !== "test") {
      return {
        ok: false,
        error: {
          code: "NAMESPACE_LOCKED",
          message: `PR-2: Only "test" namespace allowed. Got: "${namespace}". Real memory write is not available through this facade yet.`,
        },
      };
    }

    return store.writeTestEntry(key, content);
  }

  /**
   * Delete an entry. PR-2: test namespace only.
   * Does NOT delete real memory files.
   * 
   * @param {string} id - Entry key in test namespace
   * @returns {Promise<import("./memory-core-types.js").MemoryCoreDeleteResponse>}
   */
  async function delete_(id) {
    return store.deleteTestEntry(id);
  }

  /**
   * Get memory system statistics.
   * 
   * @returns {Promise<import("./memory-core-types.js").MemoryCoreStats>}
   */
  async function stats() {
    const fileStats = store.getStats();
    
    return {
      memoryFileCount: fileStats.memoryFileCount,
      memoryTotalSizeBytes: fileStats.memoryTotalSizeBytes,
      knownPluginCount: KNOWN_PLUGIN_COUNT,
      legacyItemCount: LEGACY_ITEM_COUNT,
      breakdown: fileStats.breakdown,
    };
  }

  /**
   * Health check — verify all facade capabilities.
   * 
   * @returns {Promise<import("./memory-core-types.js").MemoryCoreHealth>}
   */
  async function health() {
    const checks = {
      memoryCoreAvailable: false,
      legacyBackendReachable: false,
      canSearch: false,
      canGet: false,
      canWriteTestNamespace: false,
      errors: [],
    };

    // Check: can read MEMORY.md (core availability)
    try {
      const memResult = store.readFile("MEMORY.md");
      checks.memoryCoreAvailable = memResult.ok;
      if (!memResult.ok) checks.errors.push(`MEMORY.md: ${memResult.error?.message}`);
    } catch {
      checks.errors.push("MEMORY.md read threw exception");
    }

    // Check: can read memory/vault.md (legacy backend)
    try {
      const vaultResult = store.readFile("memory/vault.md");
      checks.legacyBackendReachable = vaultResult.ok;
    } catch {
      // vault.md may not exist — not a hard error
    }

    // Check: can search (FTS5 primary, file-search fallback)
    try {
      const searchResult = await searchAdapter.search("test", { maxResults: 1 });
      checks.canSearch = searchResult.ok;
      if (!searchResult.ok) checks.errors.push(`search: ${searchResult.error?.message}`);
      // Record which source is active
      checks.searchSource = searchResult.source || "unknown";
    } catch (e) {
      checks.errors.push(`search threw: ${e.message}`);
    }

    // Check: can get
    try {
      const getResult = store.readFile("MEMORY.md");
      checks.canGet = getResult.ok;
      if (!getResult.ok) checks.errors.push(`get: ${getResult.error?.message}`);
    } catch (e) {
      checks.errors.push(`get threw: ${e.message}`);
    }

    // Check: can write to test namespace
    try {
      const writeResult = store.writeTestEntry("health-check", "health-check-content-" + Date.now());
      checks.canWriteTestNamespace = writeResult.ok;
      if (writeResult.ok) {
        // Clean up
        store.deleteTestEntry("health-check");
      } else {
        checks.errors.push(`write: ${writeResult.error?.message}`);
      }
    } catch (e) {
      checks.errors.push(`write threw: ${e.message}`);
    }

    const allOk = checks.memoryCoreAvailable && checks.canSearch && checks.canGet && checks.canWriteTestNamespace;

    return {
      ok: allOk,
      memoryCoreAvailable: checks.memoryCoreAvailable,
      legacyBackendReachable: checks.legacyBackendReachable,
      canSearch: checks.canSearch,
      canGet: checks.canGet,
      canWriteTestNamespace: checks.canWriteTestNamespace,
      searchSource: checks.searchSource || "unknown",
      error: checks.errors.length > 0 ? checks.errors.join("; ") : undefined,
    };
  }

  // ─── Return facade API ──────────────────────────

  return {
    search,
    get,
    write,
    delete: delete_,
    stats,
    health,
    // Expose internals for testing
    _store: store,
    _searchAdapter: fts5Adapter,
    _fileSearchAdapter: fileSearchAdapter,
  };
}

// ─── Default export for convenience ────────────────

export default createMemoryCore;
