"""Distiller — distills raw conversation turns into clean knowledge entries.

Uses any LLMClient to extract factual, standalone knowledge from noisy
conversation logs. Produces structured KnowledgeEntry objects ready for
embedding and storage.
"""

import json
import logging
from typing import TYPE_CHECKING

from pydantic import BaseModel, Field

if TYPE_CHECKING:
    from neo_cortex.llm import LLMClient

logger = logging.getLogger("neo-cortex-distiller")

from functools import partial
from neo_cortex._log import boot_log
_bl = partial(boot_log, "DISTILLER")



class KnowledgeEntry(BaseModel):
    """A single distilled knowledge entry from conversation."""
    topic: str
    content: str
    type: str = "feature"
    project: str = "general"
    concepts: list[str] = Field(default_factory=list)


class Distiller:
    """Distills raw conversation turns into clean knowledge entries via any LLMClient."""

    def __init__(self, llm: 'LLMClient'):
        self._llm = llm
        _bl(f"init: provider={llm.name}")

    async def distill(self, turns: list[str]) -> list[KnowledgeEntry]:
        """Send conversation turns to LLM, return structured knowledge entries."""
        if not turns:
            return []

        from neo_cortex import config
        projects_hint = ", ".join(config.PROJECT_NAMES)
        text = f"Known projects: {projects_hint}\n\n" + "\n\n".join(turns)
        _bl(f"distill: {len(turns)} turns, {len(text)} chars")

        try:
            content = await self._llm.distill(text)
        except Exception as e:
            _bl(f"distill: LLM error: {e}")
            logger.error("LLM distill error: %s", e)
            return []

        return self._parse_entries(content)

    def _parse_entries(self, raw: str) -> list[KnowledgeEntry]:
        """Parse JSON array from LLM response into KnowledgeEntry list."""
        start = raw.find("[")
        end = raw.rfind("]")
        if start < 0 or end < start:
            _bl(f"distill: no JSON array in response ({len(raw)} chars)")
            return []

        try:
            items = json.loads(raw[start:end + 1])
        except json.JSONDecodeError as e:
            _bl(f"distill: JSON parse error: {e}")
            return []

        entries = []
        for item in items:
            if not isinstance(item, dict):
                continue
            topic = item.get("topic", "").strip()
            content = item.get("content", "").strip()
            if not topic or not content:
                continue
            entries.append(KnowledgeEntry(
                topic=topic,
                content=content,
                type=item.get("type", "feature"),
                project=item.get("project", "general"),
                concepts=item.get("concepts", []),
            ))

        _bl(f"distill: parsed {len(entries)} entries from {len(items)} items")
        return entries
