#!/usr/bin/env python3
"""
meshdb MCP Server - MeshPOP Search Engine as MCP Tool

Exposes meshdb search engine functions as MCP tools for AI integration.
Designed for mpop MCP ecosystem. Uses meshdb API layer directly (no subprocess).

Installation:
  Add to Claude Desktop ~/Library/Application Support/Claude/claude_desktop_config.json:
  {
    "mcpServers": {
      "meshdb": {
        "command": "python3",
        "args": ["/path/to/meshdb_mcp.py"]
      }
    }
  }

  Or integrate into mpop-mcp-server.py by importing handle_tool_call().
"""

import json
import sys
import os

# Ensure meshdb module is importable (same directory)
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import meshdb


# ═══════════════════════════════════════════════════════════
#  MCP Protocol (JSON-RPC over stdio, newline-delimited)
# ═══════════════════════════════════════════════════════════

def send_message(msg: dict):
    """Send MCP message."""
    sys.stdout.write(json.dumps(msg) + "\n")
    sys.stdout.flush()

def read_message() -> dict:
    """Receive MCP message."""
    line = sys.stdin.readline()
    if not line:
        return None
    line = line.strip()
    if not line:
        return None
    try:
        return json.loads(line)
    except json.JSONDecodeError:
        return None


# ═══════════════════════════════════════════════════════════
#  Tool Definitions
# ═══════════════════════════════════════════════════════════

TOOLS = [
    {
        "name": "meshdb_search",
        "description": "Full-text search across indexed files. BM25 ranking with filename 10x, content 5x weighting. "
                       "Supports FTS5 syntax: AND, OR, NOT, phrase \"quotes\". "
                       "For Korean queries, expand keywords yourself before calling (e.g., \'배포\' -> \'배포 OR deploy OR deployment\').",
        "inputSchema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query (FTS5 syntax supported)"},
                "limit": {"type": "integer", "description": "Max results (default: 20)", "default": 20},
                "type": {"type": "string", "enum": ["docs", "code", "config", "log"],
                         "description": "Filter by file type (optional)"},
                "source_dir": {"type": "string", "description": "Filter by indexed directory (optional)"}
            },
            "required": ["query"]
        }
    },
    {
        "name": "meshdb_index",
        "description": "Index a directory for full-text search. Scans text files, extracts content, "
                       "builds FTS5 index. Skips unchanged files on re-run. Supports incremental updates.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Directory path to index (e.g., ~/projects)"},
                "replace": {"type": "boolean", "description": "Delete existing index for this path and rebuild (default: false)",
                            "default": False}
            },
            "required": ["path"]
        }
    },
    {
        "name": "meshdb_find",
        "description": "Search by filename pattern. Fast lookup without content matching. "
                       "Useful for finding specific files by name.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Filename pattern (e.g., \'config\', \'.py\', \'README\')"},
                "limit": {"type": "integer", "description": "Max results (default: 10)", "default": 10}
            },
            "required": ["query"]
        }
    },
    {
        "name": "meshdb_read",
        "description": "Read file content by path or filename. Resolves filename to full path via DB lookup.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "File path or filename to read"}
            },
            "required": ["query"]
        }
    },
    {
        "name": "meshdb_status",
        "description": "Get meshdb database statistics: total documents, types, sources, DB size, last indexed time.",
        "inputSchema": {
            "type": "object",
            "properties": {}
        }
    },
    {
        "name": "meshdb_reindex",
        "description": "Re-index a directory: deletes existing entries for the path, then rebuilds from scratch.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Directory path to re-index"}
            },
            "required": ["path"]
        }
    },
]


# ═══════════════════════════════════════════════════════════
#  Tool Handler
# ═══════════════════════════════════════════════════════════

def handle_tool_call(name: str, arguments: dict) -> str:
    """Handle MCP tool call. Returns JSON string."""
    try:
        if name == "meshdb_search":
            query = arguments.get("query", "")
            limit = arguments.get("limit", 20)
            doc_type = arguments.get("type")
            source_dir = arguments.get("source_dir")
            result = meshdb.api_search(query, limit=limit, doc_type=doc_type, source_dir=source_dir)
            return json.dumps(result, ensure_ascii=False, indent=2)

        elif name == "meshdb_index":
            path = arguments.get("path", "")
            replace = arguments.get("replace", False)
            result = meshdb.api_index(path, replace=replace)
            return json.dumps(result, ensure_ascii=False, indent=2)

        elif name == "meshdb_find":
            query = arguments.get("query", "")
            limit = arguments.get("limit", 10)
            result = meshdb.api_find(query, limit=limit)
            return json.dumps(result, ensure_ascii=False, indent=2)

        elif name == "meshdb_read":
            query = arguments.get("query", "")
            result = meshdb.api_read(query)
            return json.dumps(result, ensure_ascii=False, indent=2)

        elif name == "meshdb_status":
            result = meshdb.api_status()
            return json.dumps(result, ensure_ascii=False, indent=2)

        elif name == "meshdb_reindex":
            path = arguments.get("path", "")
            result = meshdb.api_index(path, replace=True)
            return json.dumps(result, ensure_ascii=False, indent=2)

        else:
            return json.dumps({"error": f"Unknown tool: {name}"})

    except Exception as e:
        return json.dumps({"error": str(e)})


# ═══════════════════════════════════════════════════════════
#  Main Loop
# ═══════════════════════════════════════════════════════════

def main():
    """MCP server main loop (JSON-RPC over stdio)."""
    while True:
        msg = read_message()
        if msg is None:
            break

        method = msg.get("method", "")
        msg_id = msg.get("id")

        if method == "initialize":
            send_message({
                "jsonrpc": "2.0",
                "id": msg_id,
                "result": {
                    "protocolVersion": "2024-11-05",
                    "capabilities": {"tools": {}},
                    "serverInfo": {"name": "meshdb", "version": "1.0.0"}
                }
            })

        elif method == "notifications/initialized":
            pass

        elif method == "tools/list":
            send_message({
                "jsonrpc": "2.0",
                "id": msg_id,
                "result": {"tools": TOOLS}
            })

        elif method == "tools/call":
            params = msg.get("params", {})
            tool_name = params.get("name", "")
            arguments = params.get("arguments", {})
            result = handle_tool_call(tool_name, arguments)
            send_message({
                "jsonrpc": "2.0",
                "id": msg_id,
                "result": {
                    "content": [{"type": "text", "text": result}]
                }
            })

        elif method == "shutdown":
            send_message({"jsonrpc": "2.0", "id": msg_id, "result": None})
            break


if __name__ == "__main__":
    main()
