#!/usr/bin/env node
/**
 * Memory Search Adapter — Text search across memory files for MemoryCore facade.
 * 
 * PR-2: MemoryCore Facade
 * Architecture Freeze v2 (PR-1): Adapter layer
 * 
 * Implements basic full-text search across workspace memory files.
 * When MemoryCore FTS5 index is available (post PR-5), this adapter
 * can be upgraded to delegate to the indexed search.
 * 
 * Currently: line-by-line grep with simple relevance scoring.
 */

import { readFileSync, existsSync } from "node:fs";
import { join, relative } from "node:path";
import { createMemoryStoreAdapter } from "./memory-store-adapter.mjs";

/**
 * Split text into overlapping snippets around matches.
 * @param {string} text
 * @param {string} query
 * @param {number} snippetLength
 * @returns {Array<{snippet: string, score: number}>}
 */
function extractSnippets(text, query, snippetLength = 200) {
  const snippets = [];
  const lowerText = text.toLowerCase();
  const lowerQuery = query.toLowerCase();
  const queryTerms = lowerQuery.split(/\s+/).filter(t => t.length > 0);

  if (queryTerms.length === 0) return snippets;

  const lines = text.split("\n");
  
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const lowerLine = line.toLowerCase();
    
    // Count matching terms
    let matchCount = 0;
    for (const term of queryTerms) {
      if (lowerLine.includes(term)) matchCount++;
    }
    
    if (matchCount > 0) {
      // Build a snippet around the matching line
      const start = Math.max(0, i - 1);
      const end = Math.min(lines.length, i + 2);
      const snippet = lines.slice(start, end).join("\n");
      
      // Score: proportion of query terms matched × line weight
      const termRatio = matchCount / queryTerms.length;
      const lineBonus = Math.min(line.length, snippetLength) / snippetLength;
      const score = termRatio * 0.7 + lineBonus * 0.3;
      
      snippets.push({
        snippet: snippet.substring(0, snippetLength),
        score: Math.round(score * 100) / 100,
      });
    }
  }

  // Sort by score descending, take unique
  return snippets
    .sort((a, b) => b.score - a.score)
    .filter((s, i, arr) => arr.findIndex(x => x.snippet === s.snippet) === i);
}

/**
 * Determine source category from file path.
 * @param {string} filePath
 * @returns {string}
 */
function categorizeSource(filePath) {
  if (filePath === "MEMORY.md") return "MEMORY.md";
  if (filePath.startsWith("memory/daily/")) return "daily";
  if (filePath.startsWith("memory/registers/")) return "register";
  if (filePath.includes("vault")) return "vault";
  if (filePath.startsWith("memory/")) return "memory";
  return "core";
}

/**
 * @param {string} workspaceRoot
 */
export function createMemorySearchAdapter(workspaceRoot) {
  const root = workspaceRoot;

  /**
   * Search across memory files.
   * @param {string} query
   * @param {{maxResults?: number, source?: string, includeSnippets?: boolean, snippetLength?: number}} opts
   * @returns {{ok: boolean, results: Array<object>, source: string, latencyMs: number, error?: object}}
   */
  function search(query, opts = {}) {
    const startTime = Date.now();
    const {
      maxResults = 10,
      source = "all",
      includeSnippets = true,
      snippetLength = 200,
    } = opts;

    try {
      if (!query || typeof query !== "string" || query.trim().length === 0) {
        return {
          ok: false,
          results: [],
          source: "memory-core-facade",
          latencyMs: Date.now() - startTime,
          error: { code: "INVALID_QUERY", message: "Query must be a non-empty string" },
        };
      }

            const store = createMemoryStoreAdapter(root);
      const allFiles = store.listMemoryFiles();

      // Filter by source category
      const files = source === "all"
        ? allFiles
        : allFiles.filter(f => categorizeSource(f) === source);

      const allResults = [];

      for (const filePath of files) {
        try {
          const fullPath = join(root, filePath);
          if (!existsSync(fullPath)) continue;
          
          const content = readFileSync(fullPath, "utf8");
          const snippets = extractSnippets(content, query, snippetLength);

          for (const s of snippets) {
            allResults.push({
              id: filePath,
              path: filePath,
              snippet: includeSnippets ? s.snippet : "",
              score: s.score,
              source: categorizeSource(filePath),
            });
          }
        } catch {
          // Skip unreadable files
        }
      }

      // Sort by score descending, deduplicate by id+snippet
      const seen = new Set();
      const sorted = allResults
        .sort((a, b) => b.score - a.score)
        .filter(r => {
          const key = `${r.id}:${r.snippet.substring(0, 50)}`;
          if (seen.has(key)) return false;
          seen.add(key);
          return true;
        })
        .slice(0, maxResults);

      return {
        ok: true,
        results: sorted,
        source: "memory-core-facade",
        latencyMs: Date.now() - startTime,
      };
    } catch (e) {
      return {
        ok: false,
        results: [],
        source: "memory-core-facade",
        latencyMs: Date.now() - startTime,
        error: { code: "SEARCH_ERROR", message: e.message },
      };
    }
  }

  return { search };
}
