# """
# ChunkHive Embedding Server
# ===========================

# Keeps models loaded in memory permanently.
# Load once, serve forever — no reloading between requests!

# Run:
#     python -m chunkhive.embeddings.server

# Then use:
#     chunkhive embed generate --server  (uses server instead of reloading)
    
# Or call directly:
#     import requests
#     r = requests.post("http://localhost:8765/embed", json={"texts": ["code..."]})
# """

# from pathlib import Path
# from typing import List, Optional, Dict, Any
# import logging
# import json

# logger = logging.getLogger(__name__)


# # =============================================================================
# # SIMPLE IN-MEMORY MODEL CACHE
# # =============================================================================

# class ModelCache:
#     """
#     Keeps models loaded in memory.
    
#     First call: loads model (~5 seconds)
#     Subsequent calls: instant! (already in RAM)
    
#     Example:
#         cache = ModelCache()
#         engine1 = cache.get("codebert")  # loads from disk: 5 sec
#         engine2 = cache.get("codebert")  # instant! (cached in RAM)
#         engine3 = cache.get("minilm")    # loads minilm: 3 sec
#         engine4 = cache.get("minilm")    # instant!
#     """
    
#     _instance = None  # Singleton pattern
    
#     def __new__(cls):
#         """Singleton - only one cache exists per process."""
#         if cls._instance is None:
#             cls._instance = super().__new__(cls)
#             cls._instance._models = {}
#             cls._instance._initialized = False
#         return cls._instance
    
#     def __init__(self):
#         if not self._initialized:
#             self._models: Dict[str, Any] = {}
#             self._initialized = True
#             logger.info("ModelCache initialized")
    
#     def get(self, model_name: str, device: str = "cpu") -> Any:
#         """
#         Get a model from cache. Loads from disk only on first call.
        
#         Args:
#             model_name: Name of model (minilm, codebert, etc.)
#             device: Device to load on (cpu/cuda)
            
#         Returns:
#             SemanticEmbeddingEngine (loaded and ready)
#         """
#         cache_key = f"{model_name}:{device}"
        
#         if cache_key not in self._models:
#             logger.info(f"Loading {model_name} into memory cache...")
#             from .embedding_engine import SemanticEmbeddingEngine
#             self._models[cache_key] = SemanticEmbeddingEngine(
#                 model_name=model_name,
#                 device=device
#             )
#             logger.info(f"✓ {model_name} loaded and cached")
#         else:
#             logger.debug(f"✓ {model_name} served from memory cache (instant!)")
        
#         return self._models[cache_key]
    
#     def is_loaded(self, model_name: str, device: str = "cpu") -> bool:
#         """Check if a model is already in memory."""
#         return f"{model_name}:{device}" in self._models
    
#     def loaded_models(self) -> List[str]:
#         """List models currently in memory."""
#         return list(self._models.keys())
    
#     def unload(self, model_name: str, device: str = "cpu") -> None:
#         """Remove a model from memory (free RAM)."""
#         cache_key = f"{model_name}:{device}"
#         if cache_key in self._models:
#             del self._models[cache_key]
#             logger.info(f"Unloaded {model_name} from memory")


# # Global singleton cache
# _cache = ModelCache()


# def get_model(model_name: str, device: str = "cpu") -> Any:
#     """
#     Get cached model engine. Loads once, reuses forever.
    
#     This is the KEY function — use this instead of creating
#     new SemanticEmbeddingEngine() every time!
    
#     Usage:
#         engine = get_model("codebert")  # Fast after first load!
#         embeddings = engine.generate_embeddings(chunks)
#     """
#     return _cache.get(model_name, device)


# # =============================================================================
# # OPTIONAL: BACKGROUND SERVER (Keep model alive between CLI calls)
# # =============================================================================

# try:
#     from fastapi import FastAPI
#     from pydantic import BaseModel
#     import uvicorn
#     SERVER_AVAILABLE = True
# except ImportError:
#     SERVER_AVAILABLE = False


# if SERVER_AVAILABLE:
    
#     server_app = FastAPI(title="ChunkHive Embedding Server")
    
#     # Models loaded at startup
#     _server_engines: Dict[str, Any] = {}
    
#     class EmbedRequest(BaseModel):
#         texts: List[str]
#         model: str = "minilm"
#         normalize: bool = True
    
#     class EmbedResponse(BaseModel):
#         embeddings: List[List[float]]
#         model: str
#         dimension: int
    
#     @server_app.on_event("startup")
#     async def load_default_model():
#         """Pre-load default model at server startup."""
#         logger.info("Pre-loading minilm model...")
#         _server_engines["minilm"] = get_model("minilm")
#         logger.info("✓ Server ready! Model in memory.")
    
#     @server_app.post("/embed", response_model=EmbedResponse)
#     async def embed_texts(request: EmbedRequest):
#         """
#         Generate embeddings for texts.
        
#         Model is already in memory — no reload needed!
#         """
#         # Load model if not already loaded
#         if request.model not in _server_engines:
#             _server_engines[request.model] = get_model(request.model)
        
#         engine = _server_engines[request.model]
        
#         import numpy as np
#         embeddings = engine.model.encode(
#             request.texts,
#             convert_to_numpy=True,
#             normalize_embeddings=request.normalize
#         )
        
#         return EmbedResponse(
#             embeddings=embeddings.tolist(),
#             model=engine.model_config.model_id,
#             dimension=engine.dimension
#         )
    
#     @server_app.get("/models")
#     async def list_loaded_models():
#         """Show which models are currently in memory."""
#         return {
#             "loaded": list(_server_engines.keys()),
#             "available": ["minilm", "codebert", "unixcoder", "codesage"]
#         }
    
#     @server_app.get("/health")
#     async def health_check():
#         return {"status": "ok", "models_loaded": len(_server_engines)}
    
    
#     def start_server(host: str = "localhost", port: int = 8765, 
#                      preload: str = "minilm"):
#         """
#         Start the embedding server.
        
#         Run once, keeps model in memory forever!
        
#         Usage:
#             # Terminal 1: Start server
#             python -c "from chunkhive.embeddings.server import start_server; start_server()"
            
#             # Terminal 2: Use CLI (model loads from server, not disk!)
#             chunkhive embed generate chunks.jsonl --server
#         """
#         print(f"🚀 Starting ChunkHive Embedding Server on {host}:{port}")
#         print(f"📦 Pre-loading model: {preload}")
#         print(f"💡 Keep this running to avoid reloading models!")
#         uvicorn.run(server_app, host=host, port=port)


# def is_server_running(host: str = "localhost", port: int = 8765) -> bool:
#     """Check if embedding server is running."""
#     try:
#         import requests
#         r = requests.get(f"http://{host}:{port}/health", timeout=1)
#         return r.status_code == 200
#     except Exception:
#         return False


# def embed_via_server(
#     texts: List[str], 
#     model: str = "minilm",
#     host: str = "localhost",
#     port: int = 8765
# ) -> Optional[List[List[float]]]:
#     """
#     Generate embeddings using the server (no model reload!).
    
#     Returns None if server is not running.
#     """
#     try:
#         import requests
#         r = requests.post(
#             f"http://{host}:{port}/embed",
#             json={"texts": texts, "model": model},
#             timeout=30
#         )
#         if r.status_code == 200:
#             return r.json()["embeddings"]
#     except Exception:
#         pass
#     return None