"""Local Knowledge Vault indexing and retrieval service."""

from __future__ import annotations

import hashlib
import logging
import os
import re
import threading
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from sqlalchemy import func, select, text
from sqlalchemy.engine import Engine
from sqlalchemy.orm import Session

from app.models.vault import VaultChunk, VaultFile, VaultPath
from app.services.intent_router import normalize_text

CHUNK_SIZE = 1200
CHUNK_OVERLAP = 200
DEFAULT_SEARCH_LIMIT = 10
MAX_SEARCH_LIMIT = 50
MAX_QUERY_TOKENS = 24
MAX_ERROR_MESSAGES = 30

LOGGER = logging.getLogger("sentineledge.vault")

IGNORED_DIRS = {
    ".git",
    ".hg",
    ".svn",
    ".venv",
    "venv",
    ".idea",
    ".vscode",
    "node_modules",
    "__pycache__",
    "dist",
    "build",
    ".mypy_cache",
    ".pytest_cache",
    ".ruff_cache",
    ".tox",
    ".cache",
    ".obsidian",
    ".orion-ai-cache",
    ".sentineledge",
}
ALLOWED_SUFFIXES = {".md", ".markdown", ".mdown", ".mkd", ".txt", ".log", ".json"}

_INDEX_LOCK = threading.Lock()
_STATUS_LOCK = threading.Lock()
_LAST_STATUS: dict[str, Any] = {
    "is_running": False,
    "last_run_at": None,
    "last_finished_at": None,
    "last_stats": None,
    "last_errors": [],
}


class VaultError(ValueError):
    """Raised when user-provided vault input is invalid."""


class VaultIndexInProgressError(RuntimeError):
    """Raised when index operation is already running."""


class VaultNotIndexedError(RuntimeError):
    """Raised when enabled vault paths have no indexed chunks."""


@dataclass(slots=True)
class VaultSearchHit:
    """Search result row used by API and assistant retrieval."""

    chunk_id: int
    file_path: str
    snippet: str
    rank: float
    chunk_text: str
    overlap: int


@dataclass(slots=True)
class VaultIndexStats:
    """Index run counters."""

    paths_indexed: int = 0
    files_scanned: int = 0
    files_indexed: int = 0
    files_skipped: int = 0
    files_deleted: int = 0
    chunks_created: int = 0
    errors: int = 0

    def as_dict(self) -> dict[str, int]:
        """Return serializable stats payload."""
        return {
            "paths_indexed": self.paths_indexed,
            "files_scanned": self.files_scanned,
            "files_indexed": self.files_indexed,
            "files_skipped": self.files_skipped,
            "files_deleted": self.files_deleted,
            "chunks_created": self.chunks_created,
            "errors": self.errors,
        }


@dataclass(slots=True)
class VaultReadiness:
    """Quick readiness signal for retrieval over enabled paths."""

    enabled_paths: int
    enabled_files: int
    enabled_chunks: int
    last_indexed_at: datetime | None

    @property
    def needs_index(self) -> bool:
        return self.enabled_paths > 0 and self.enabled_chunks == 0


def _utcnow() -> datetime:
    """Return timezone-aware UTC now."""
    return datetime.now(timezone.utc)


def _project_root() -> Path:
    """Return repository root."""
    return Path(__file__).resolve().parents[2]


def normalize_vault_path(raw_path: str, *, require_existing_dir: bool) -> Path:
    """Resolve user input into one canonical absolute path."""
    raw = str(raw_path or "").strip()
    if not raw:
        raise VaultError("path is required")

    candidate = Path(raw).expanduser()
    if not candidate.is_absolute():
        candidate = (_project_root() / candidate).resolve()
    else:
        candidate = candidate.resolve()

    if require_existing_dir:
        if not candidate.exists():
            raise VaultError(f"path does not exist: {candidate}")
        if not candidate.is_dir():
            raise VaultError(f"path is not a directory: {candidate}")
    return candidate


def _resolve_registered_path(path: str) -> Path:
    """Resolve user input path to a canonical absolute existing directory."""
    return normalize_vault_path(path, require_existing_dir=True)


def _is_path_inside(child: Path, parent: Path, *, allow_equal: bool = True) -> bool:
    """Return True when child is located under parent."""
    if not allow_equal and child == parent:
        return False
    try:
        child.relative_to(parent)
        return True
    except ValueError:
        return False


def _iter_candidate_files(root: Path) -> list[Path]:
    """Collect candidate files recursively under root."""
    root_resolved = root.resolve()
    results: list[Path] = []
    seen_dirs: set[str] = set()
    ignored_lookup = {name.casefold() for name in IGNORED_DIRS}

    for dirpath, dirnames, filenames in os.walk(root_resolved, topdown=True, followlinks=True):
        current_dir = Path(dirpath).resolve()
        current_key = str(current_dir)
        if current_key in seen_dirs:
            dirnames[:] = []
            continue
        seen_dirs.add(current_key)
        if not _is_path_inside(current_dir, root_resolved):
            dirnames[:] = []
            continue

        dirnames[:] = [
            name
            for name in dirnames
            if name.casefold() not in ignored_lookup
        ]

        for filename in filenames:
            suffix = Path(filename).suffix.lower()
            if suffix not in ALLOWED_SUFFIXES:
                continue

            candidate = (Path(dirpath) / filename).resolve()
            if not candidate.is_file():
                continue
            if not _is_path_inside(candidate, root_resolved):
                continue
            results.append(candidate)

    results.sort()
    return results


def _read_file_text(path: Path) -> str:
    """Read text file with utf-8 preferred and latin-1 fallback."""
    data = path.read_bytes()
    try:
        return data.decode("utf-8-sig")
    except UnicodeDecodeError:
        try:
            return data.decode("cp1252")
        except UnicodeDecodeError:
            return data.decode("latin-1", errors="replace")


def _sha256_file(path: Path) -> str:
    """Compute SHA256 for file bytes."""
    digest = hashlib.sha256()
    with path.open("rb") as fh:
        for block in iter(lambda: fh.read(1024 * 64), b""):
            digest.update(block)
    return digest.hexdigest()


def _chunk_text(text_value: str, chunk_size: int = CHUNK_SIZE, overlap: int = CHUNK_OVERLAP) -> list[tuple[int, int, str]]:
    """Split text into overlapping character chunks."""
    text_clean = text_value or ""
    if not text_clean.strip():
        return []

    chunks: list[tuple[int, int, str]] = []
    start = 0
    length = len(text_clean)

    while start < length:
        end = min(length, start + chunk_size)
        chunk_text = text_clean[start:end]
        chunks.append((start, end, chunk_text))
        if end >= length:
            break
        start = max(0, end - overlap)

    return chunks


def _normalize_query_tokens(query: str) -> list[str]:
    """Extract normalized search tokens from user query."""
    normalized = normalize_text(query)
    tokens = re.findall(r"[a-z0-9_]{2,}", normalized)
    unique: list[str] = []
    seen: set[str] = set()
    for token in tokens:
        if token in seen:
            continue
        seen.add(token)
        unique.append(token)
        if len(unique) >= MAX_QUERY_TOKENS:
            break
    return unique


def _fts_match_expression(query: str) -> str:
    """Build FTS-safe MATCH expression."""
    tokens = _normalize_query_tokens(query)
    if not tokens:
        raise VaultError("query must include at least one alphanumeric term")
    terms: list[str] = []
    seen_terms: set[str] = set()
    for token in tokens:
        exact = f'"{token}"'
        if exact not in seen_terms:
            seen_terms.add(exact)
            terms.append(exact)
        if len(token) >= 4:
            prefix = f"{token}*"
            if prefix not in seen_terms:
                seen_terms.add(prefix)
                terms.append(prefix)
    return " OR ".join(terms)


def _token_overlap_count(tokens: list[str], text_value: str) -> int:
    """Count how many query tokens appear in chunk text."""
    if not tokens:
        return 0
    normalized = normalize_text(text_value)
    return sum(1 for token in tokens if f" {token} " in f" {normalized} ")


def _set_status(
    *,
    running: bool | None = None,
    last_run_at: datetime | None = None,
    last_finished_at: datetime | None = None,
    stats: dict[str, int] | None = None,
    errors: list[str] | None = None,
) -> None:
    """Update in-memory indexing status in a thread-safe way."""
    with _STATUS_LOCK:
        if running is not None:
            _LAST_STATUS["is_running"] = running
        if last_run_at is not None:
            _LAST_STATUS["last_run_at"] = last_run_at
        if last_finished_at is not None:
            _LAST_STATUS["last_finished_at"] = last_finished_at
        if stats is not None:
            _LAST_STATUS["last_stats"] = stats
        if errors is not None:
            _LAST_STATUS["last_errors"] = errors[:MAX_ERROR_MESSAGES]


def get_last_status() -> dict[str, Any]:
    """Return latest in-memory index status."""
    with _STATUS_LOCK:
        return {
            "is_running": bool(_LAST_STATUS.get("is_running")),
            "last_run_at": _LAST_STATUS.get("last_run_at"),
            "last_finished_at": _LAST_STATUS.get("last_finished_at"),
            "last_stats": _LAST_STATUS.get("last_stats"),
            "last_errors": list(_LAST_STATUS.get("last_errors") or []),
        }


def ensure_vault_fts(engine: Engine) -> None:
    """Create FTS5 table and triggers used by vault search."""
    fts_statements = [
        """
        CREATE VIRTUAL TABLE IF NOT EXISTS vault_chunks_fts
        USING fts5(text, content='vault_chunks', content_rowid='id')
        """,
        """
        CREATE TRIGGER IF NOT EXISTS vault_chunks_ai
        AFTER INSERT ON vault_chunks
        BEGIN
            INSERT INTO vault_chunks_fts(rowid, text) VALUES (new.id, new.text);
        END
        """,
        """
        CREATE TRIGGER IF NOT EXISTS vault_chunks_ad
        AFTER DELETE ON vault_chunks
        BEGIN
            INSERT INTO vault_chunks_fts(vault_chunks_fts, rowid, text)
            VALUES ('delete', old.id, old.text);
        END
        """,
        """
        CREATE TRIGGER IF NOT EXISTS vault_chunks_au
        AFTER UPDATE ON vault_chunks
        BEGIN
            INSERT INTO vault_chunks_fts(vault_chunks_fts, rowid, text)
            VALUES ('delete', old.id, old.text);
            INSERT INTO vault_chunks_fts(rowid, text) VALUES (new.id, new.text);
        END
        """,
    ]

    with engine.begin() as conn:
        for statement in fts_statements:
            conn.execute(text(statement))

        chunks_count = conn.execute(text("SELECT COUNT(*) FROM vault_chunks")).scalar_one()
        fts_count = conn.execute(text("SELECT COUNT(*) FROM vault_chunks_fts")).scalar_one()
        if chunks_count != fts_count:
            conn.execute(text("INSERT INTO vault_chunks_fts(vault_chunks_fts) VALUES ('rebuild')"))


def register_vault_path(db: Session, raw_path: str) -> VaultPath:
    """Register one vault path (or re-enable if already present)."""
    resolved = _resolve_registered_path(raw_path)
    resolved_str = str(resolved)

    existing = db.scalar(select(VaultPath).where(VaultPath.path == resolved_str))
    if existing is not None:
        if not existing.enabled:
            existing.enabled = True
            db.commit()
            db.refresh(existing)
        return existing

    item = VaultPath(path=resolved_str, enabled=True, created_at=_utcnow())
    db.add(item)
    db.commit()
    db.refresh(item)
    return item


def list_vault_paths(db: Session) -> list[dict[str, Any]]:
    """List registered vault paths with per-path file/chunk stats."""
    stmt = (
        select(
            VaultPath.id,
            VaultPath.path,
            VaultPath.enabled,
            VaultPath.created_at,
            func.count(func.distinct(VaultFile.id)).label("file_count"),
            func.count(VaultChunk.id).label("chunk_count"),
            func.max(VaultFile.last_indexed_at).label("last_indexed_at"),
        )
        .select_from(VaultPath)
        .outerjoin(VaultFile, VaultFile.vault_path_id == VaultPath.id)
        .outerjoin(VaultChunk, VaultChunk.file_id == VaultFile.id)
        .group_by(VaultPath.id)
        .order_by(VaultPath.created_at.desc())
    )

    rows = db.execute(stmt).all()
    return [
        {
            "id": row.id,
            "path": row.path,
            "enabled": bool(row.enabled),
            "created_at": row.created_at,
            "file_count": int(row.file_count or 0),
            "chunk_count": int(row.chunk_count or 0),
            "last_indexed_at": row.last_indexed_at,
        }
        for row in rows
    ]


def set_vault_path_enabled(db: Session, path_id: int, *, enabled: bool) -> dict[str, Any]:
    """Enable or disable one registered vault path."""
    item = db.scalar(select(VaultPath).where(VaultPath.id == path_id))
    if item is None:
        raise LookupError(f"vault path id {path_id} was not found")

    item.enabled = bool(enabled)
    db.commit()
    db.refresh(item)

    for row in list_vault_paths(db):
        if int(row["id"]) == int(item.id):
            return row

    return {
        "id": item.id,
        "path": item.path,
        "enabled": bool(item.enabled),
        "created_at": item.created_at,
        "file_count": 0,
        "chunk_count": 0,
        "last_indexed_at": None,
    }


def _paths_for_index(db: Session, path_id: int | None) -> list[VaultPath]:
    """Resolve path selection for indexing request."""
    if path_id is not None:
        item = db.scalar(select(VaultPath).where(VaultPath.id == path_id))
        if item is None:
            raise LookupError(f"vault path id {path_id} was not found")
        if not item.enabled:
            raise VaultError(f"vault path id {path_id} is disabled")
        return [item]

    return list(db.scalars(select(VaultPath).where(VaultPath.enabled.is_(True))).all())


def _index_single_file(
    db: Session,
    *,
    file_path: Path,
    owner_path_id: int,
    existing: VaultFile | None,
    now: datetime,
) -> tuple[bool, int, VaultFile]:
    """Index one file and return (was_reindexed, file_row)."""
    stat = file_path.stat()
    mtime = int(stat.st_mtime_ns)
    size = int(stat.st_size)

    if existing is not None and existing.mtime == mtime and existing.size == size and existing.sha256:
        existing.vault_path_id = owner_path_id
        existing.last_indexed_at = now
        return False, 0, existing

    digest = _sha256_file(file_path)
    if existing is not None and existing.sha256 == digest:
        existing.vault_path_id = owner_path_id
        existing.mtime = mtime
        existing.size = size
        existing.last_indexed_at = now
        return False, 0, existing

    text_value = _read_file_text(file_path)
    chunks = _chunk_text(text_value)

    if existing is None:
        existing = VaultFile(
            path=str(file_path),
            vault_path_id=owner_path_id,
            mtime=mtime,
            size=size,
            sha256=digest,
            last_indexed_at=now,
        )
        db.add(existing)
        db.flush()
    else:
        existing.vault_path_id = owner_path_id
        existing.mtime = mtime
        existing.size = size
        existing.sha256 = digest
        existing.last_indexed_at = now
        db.query(VaultChunk).filter(VaultChunk.file_id == existing.id).delete(synchronize_session=False)

    if chunks:
        db.add_all(
            [
                VaultChunk(
                    file_id=existing.id,
                    chunk_index=index,
                    start_char=start,
                    end_char=end,
                    text=chunk_text,
                )
                for index, (start, end, chunk_text) in enumerate(chunks)
            ],
        )

    return True, len(chunks), existing


def index_vault(db: Session, path_id: int | None = None) -> dict[str, Any]:
    """Run incremental index for all enabled paths or one path id."""
    if not _INDEX_LOCK.acquire(blocking=False):
        raise VaultIndexInProgressError("vault indexing is already running")

    stats = VaultIndexStats()
    errors: list[str] = []
    run_started = _utcnow()
    _set_status(running=True, last_run_at=run_started, errors=[])

    try:
        targets = _paths_for_index(db, path_id=path_id)
        if not targets:
            raise LookupError("no enabled vault paths registered")

        stats.paths_indexed = len(targets)
        target_ids = [item.id for item in targets]
        target_id_set = set(target_ids)
        LOGGER.info(
            "Vault index start path_id=%s paths_indexed=%s effective_paths=%s",
            path_id,
            stats.paths_indexed,
            [item.path for item in targets],
        )

        all_existing_files = list(db.scalars(select(VaultFile)).all())
        existing_files_for_targets = [item for item in all_existing_files if int(item.vault_path_id) in target_id_set]
        existing_by_path: dict[str, VaultFile] = {item.path: item for item in all_existing_files}
        seen_paths: set[str] = set()
        now = _utcnow()

        for target in targets:
            root = Path(target.path)
            if not root.exists() or not root.is_dir():
                stats.errors += 1
                errors.append(f"path unavailable during index: {target.path}")
                LOGGER.warning("Vault index skipped unavailable path path_id=%s path=%s", target.id, target.path)
                continue

            candidate_files = _iter_candidate_files(root)
            LOGGER.info(
                "Vault index scanning path_id=%s path=%s candidate_files=%s",
                target.id,
                target.path,
                len(candidate_files),
            )
            for file_path in candidate_files:
                file_key = str(file_path)
                if file_key in seen_paths:
                    continue
                seen_paths.add(file_key)
                stats.files_scanned += 1

                try:
                    with db.begin_nested():
                        reindexed, chunks_created, file_row = _index_single_file(
                            db,
                            file_path=file_path,
                            owner_path_id=target.id,
                            existing=existing_by_path.get(file_key),
                            now=now,
                        )
                        existing_by_path[file_key] = file_row
                    if reindexed:
                        stats.files_indexed += 1
                        stats.chunks_created += chunks_created
                    else:
                        stats.files_skipped += 1
                except Exception as exc:  # pragma: no cover - runtime defensive path
                    stats.errors += 1
                    errors.append(f"index error for {file_key}: {exc}")
                    LOGGER.exception("Vault index file failure file=%s", file_key)

        for existing in existing_files_for_targets:
            if existing.path in seen_paths:
                continue
            with db.begin_nested():
                db.delete(existing)
            stats.files_deleted += 1

        db.commit()
        LOGGER.info(
            "Vault index complete scanned=%s indexed=%s skipped=%s deleted=%s chunks_created=%s errors=%s",
            stats.files_scanned,
            stats.files_indexed,
            stats.files_skipped,
            stats.files_deleted,
            stats.chunks_created,
            stats.errors,
        )
    except Exception:
        db.rollback()
        LOGGER.exception("Vault index failed path_id=%s", path_id)
        raise
    finally:
        finished = _utcnow()
        _set_status(
            running=False,
            last_finished_at=finished,
            stats=stats.as_dict(),
            errors=errors,
        )
        _INDEX_LOCK.release()

    return {
        "started_at": run_started,
        "finished_at": finished,
        "stats": stats.as_dict(),
        "errors": errors,
    }


def vault_readiness(db: Session) -> VaultReadiness:
    """Return retrieval readiness based on currently enabled vault paths."""
    enabled_paths = int(db.scalar(select(func.count(VaultPath.id)).where(VaultPath.enabled.is_(True))) or 0)
    enabled_files = int(
        db.scalar(
            select(func.count(VaultFile.id))
            .select_from(VaultFile)
            .join(VaultPath, VaultPath.id == VaultFile.vault_path_id)
            .where(VaultPath.enabled.is_(True)),
        )
        or 0
    )
    enabled_chunks = int(
        db.scalar(
            select(func.count(VaultChunk.id))
            .select_from(VaultChunk)
            .join(VaultFile, VaultFile.id == VaultChunk.file_id)
            .join(VaultPath, VaultPath.id == VaultFile.vault_path_id)
            .where(VaultPath.enabled.is_(True)),
        )
        or 0
    )
    last_indexed_at = db.scalar(
        select(func.max(VaultFile.last_indexed_at))
        .select_from(VaultFile)
        .join(VaultPath, VaultPath.id == VaultFile.vault_path_id)
        .where(VaultPath.enabled.is_(True)),
    )
    return VaultReadiness(
        enabled_paths=enabled_paths,
        enabled_files=enabled_files,
        enabled_chunks=enabled_chunks,
        last_indexed_at=last_indexed_at,
    )


def search_vault(db: Session, query: str, limit: int = DEFAULT_SEARCH_LIMIT) -> list[VaultSearchHit]:
    """Search indexed chunks via FTS5."""
    q = (query or "").strip()
    if not q:
        raise VaultError("q is required")

    readiness = vault_readiness(db)
    if readiness.needs_index:
        LOGGER.info(
            "Vault search blocked: enabled_paths=%s enabled_files=%s enabled_chunks=%s query=%r",
            readiness.enabled_paths,
            readiness.enabled_files,
            readiness.enabled_chunks,
            q,
        )
        raise VaultNotIndexedError("vault configured but not indexed for enabled paths")

    bounded_limit = max(1, min(limit, MAX_SEARCH_LIMIT))
    match_expr = _fts_match_expression(q)
    query_tokens = _normalize_query_tokens(q)
    LOGGER.info(
        "Vault search query=%r tokens=%s limit=%s enabled_paths=%s",
        q,
        query_tokens,
        bounded_limit,
        readiness.enabled_paths,
    )

    sql = text(
        """
        SELECT
            c.id AS chunk_id,
            f.path AS file_path,
            COALESCE(
                snippet(vault_chunks_fts, 0, '<mark>', '</mark>', ' ... ', 24),
                substr(c.text, 1, 260)
            ) AS snippet,
            bm25(vault_chunks_fts) AS rank,
            c.text AS chunk_text
        FROM vault_chunks_fts
        JOIN vault_chunks c ON c.id = vault_chunks_fts.rowid
        JOIN vault_files f ON f.id = c.file_id
        JOIN vault_paths p ON p.id = f.vault_path_id
        WHERE p.enabled = 1
          AND vault_chunks_fts MATCH :match_expr
        ORDER BY rank
        LIMIT :limit
        """,
    )

    rows = db.execute(sql, {"match_expr": match_expr, "limit": bounded_limit}).mappings().all()
    hits: list[VaultSearchHit] = []
    for row in rows:
        chunk_text = str(row.get("chunk_text") or "")
        hits.append(
            VaultSearchHit(
                chunk_id=int(row["chunk_id"]),
                file_path=str(row["file_path"]),
                snippet=str(row.get("snippet") or chunk_text[:260]),
                rank=float(row.get("rank") or 0.0),
                chunk_text=chunk_text,
                overlap=_token_overlap_count(query_tokens, chunk_text),
            ),
        )
    LOGGER.info("Vault search results query=%r matches=%s", q, len(hits))
    return hits


def retrieve_relevant_chunks(
    db: Session,
    query: str,
    *,
    limit: int = 5,
    explicit: bool = False,
) -> list[VaultSearchHit]:
    """Return relevant chunks for assistant/report/notes retrieval."""
    hits = search_vault(db=db, query=query, limit=max(limit, 8))
    if not hits:
        return []

    if explicit:
        return hits[:limit]

    best_overlap = max(hit.overlap for hit in hits)
    if best_overlap >= 2:
        filtered = [hit for hit in hits if hit.overlap >= 1]
        return filtered[:limit]
    if best_overlap == 1:
        return hits[:limit]
    return []


def build_context_chunks(
    hits: list[VaultSearchHit],
    *,
    max_chunks: int = 6,
    max_context_chars: int = 6000,
    per_chunk_chars: int = 900,
) -> tuple[list[str], list[str]]:
    """Build bounded context snippets and source paths."""
    context_parts: list[str] = []
    sources: list[str] = []
    seen_sources: set[str] = set()
    used = 0

    for hit in hits[:max_chunks]:
        clean_text = " ".join(hit.chunk_text.split())
        if len(clean_text) > per_chunk_chars:
            clean_text = f"{clean_text[: per_chunk_chars - 3]}..."

        block = f"[chunk {hit.chunk_id}] {hit.file_path}\n{clean_text}"
        projected = used + len(block) + 2
        if context_parts and projected > max_context_chars:
            break
        context_parts.append(block)
        used = projected

        if hit.file_path not in seen_sources:
            seen_sources.add(hit.file_path)
            sources.append(hit.file_path)

    return context_parts, sources


def summarize_retrieval(hits: list[VaultSearchHit], language: str) -> str:
    """Render concise retrieval-only answer when LLM mode is unavailable."""
    if not hits:
        if language == "es":
            return "No encontré fragmentos relevantes en tu vault.\n\nSources:\n- none"
        return "I did not find relevant snippets in your vault.\n\nSources:\n- none"

    lead = (
        "Resumen breve basado en tus notas:"
        if language == "es"
        else "Short summary based on your notes:"
    )
    preview_lines = [lead]
    for hit in hits[:3]:
        preview = " ".join(re.sub(r"</?mark>", "", hit.snippet).split())
        preview_lines.append(f"- {preview}")

    sources = []
    seen: set[str] = set()
    for hit in hits:
        if hit.file_path in seen:
            continue
        seen.add(hit.file_path)
        sources.append(hit.file_path)

    source_lines = ["", "Sources:", *[f"- {item}" for item in sources]]
    return "\n".join(preview_lines + source_lines)


def status_summary(db: Session) -> dict[str, Any]:
    """Return persisted counts plus latest in-memory run status."""
    enabled_files = int(
        db.scalar(
            select(func.count(VaultFile.id))
            .select_from(VaultFile)
            .join(VaultPath, VaultPath.id == VaultFile.vault_path_id)
            .where(VaultPath.enabled.is_(True)),
        )
        or 0
    )
    enabled_chunks = int(
        db.scalar(
            select(func.count(VaultChunk.id))
            .select_from(VaultChunk)
            .join(VaultFile, VaultFile.id == VaultChunk.file_id)
            .join(VaultPath, VaultPath.id == VaultFile.vault_path_id)
            .where(VaultPath.enabled.is_(True)),
        )
        or 0
    )
    last_indexed_at = db.scalar(select(func.max(VaultFile.last_indexed_at)).select_from(VaultFile))
    enabled_last_indexed_at = db.scalar(
        select(func.max(VaultFile.last_indexed_at))
        .select_from(VaultFile)
        .join(VaultPath, VaultPath.id == VaultFile.vault_path_id)
        .where(VaultPath.enabled.is_(True)),
    )
    counts = {
        "paths": int(db.scalar(select(func.count(VaultPath.id))) or 0),
        "enabled_paths": int(db.scalar(select(func.count(VaultPath.id)).where(VaultPath.enabled.is_(True))) or 0),
        "files": int(db.scalar(select(func.count(VaultFile.id))) or 0),
        "chunks": int(db.scalar(select(func.count(VaultChunk.id))) or 0),
        "enabled_files": enabled_files,
        "enabled_chunks": enabled_chunks,
        "last_indexed_at": last_indexed_at,
        "enabled_last_indexed_at": enabled_last_indexed_at,
        "needs_index": False,
    }
    counts["needs_index"] = bool(counts["enabled_paths"] > 0 and enabled_chunks == 0)
    return {**counts, **get_last_status()}
