/**
 * Semantic memory search tool
 * Uses local embeddings (transformers.js) to search .opencode/memory/ markdown files
 */

import { tool } from "@opencode-ai/plugin";
import { readdirSync, readFileSync, existsSync } from "fs";
import { join } from "path";
import { chunkMarkdown } from "../utils/chunk-markdown.js";
import { generateEmbedding, cosineSimilarity } from "../utils/embeddings.js";

interface SearchResult {
  file: string;
  line: number;
  content: string;
  score: number;
  header: string;
}

/**
 * Search .opencode/memory/ files semantically
 * @param query - Natural language search query
 * @param topK - Number of results to return (default: 5)
 * @param memoryDir - Path to memory directory (default: .opencode/memory)
 * @returns Ranked search results with file:line references
 */
async function searchMemory(
  query: string,
  topK = 5,
  memoryDir = ".opencode/memory",
): Promise<SearchResult[]> {
  const memoryPath = join(process.cwd(), memoryDir);

  if (!existsSync(memoryPath)) {
    throw new Error(`Memory directory not found: ${memoryPath}`);
  }

  // Find all .md files recursively
  const mdFiles = findMarkdownFiles(memoryPath);

  if (mdFiles.length === 0) {
    return [];
  }

  // Chunk all files
  const allChunks: Array<{
    file: string;
    line: number;
    content: string;
    header: string;
  }> = [];

  for (const file of mdFiles) {
    const content = readFileSync(file, "utf-8");
    const relativePath = file.replace(memoryPath + "/", "");
    const chunks = chunkMarkdown(file, content);

    for (const chunk of chunks) {
      allChunks.push({
        file: relativePath,
        line: chunk.line,
        content: chunk.content,
        header: chunk.heading,
      });
    }
  }

  // Generate query embedding
  const queryEmbedding = await generateEmbedding(query);

  // Generate embeddings for all chunks and rank by similarity
  const results: SearchResult[] = [];

  for (const chunk of allChunks) {
    const chunkEmbedding = await generateEmbedding(chunk.content);
    const score = cosineSimilarity(queryEmbedding, chunkEmbedding);

    results.push({
      file: chunk.file,
      line: chunk.line,
      content: chunk.content,
      score,
      header: chunk.header,
    });
  }

  // Sort by score descending, return top K
  results.sort((a, b) => b.score - a.score);
  return results.slice(0, topK);
}

/**
 * Find all .md files recursively in directory
 */
function findMarkdownFiles(dir: string): string[] {
  const files: string[] = [];
  const entries = readdirSync(dir, { withFileTypes: true });

  for (const entry of entries) {
    const fullPath = join(dir, entry.name);

    if (entry.isDirectory()) {
      // Skip .index/ cache directory
      if (entry.name !== ".index" && entry.name !== "_templates") {
        files.push(...findMarkdownFiles(fullPath));
      }
    } else if (entry.isFile() && entry.name.endsWith(".md")) {
      files.push(fullPath);
    }
  }

  return files;
}

// OpenCode tool definition
export default tool({
  description:
    "Semantic search over .opencode/memory/ markdown files using local embeddings (offline, no API calls)",
  args: {
    query: tool.schema
      .string()
      .describe(
        "Natural language search query (e.g., 'agent prompt refactoring')",
      ),
    topK: tool.schema
      .number()
      .optional()
      .default(5)
      .describe("Number of results to return (default: 5)"),
  },
  async execute(args) {
    const results = await searchMemory(args.query, args.topK);

    if (results.length === 0) {
      return "No results found in .opencode/memory/";
    }

    // Format results for agent consumption
    const formatted = results
      .map((r) => {
        const preview = r.content.slice(0, 200);
        return `${r.file}:${r.line}\nHeader: ${r.header}\nScore: ${r.score.toFixed(4)}\n${preview}${preview.length < r.content.length ? "..." : ""}\n`;
      })
      .join("\n---\n");

    return formatted;
  },
});
