from __future__ import annotations

import json
from dataclasses import dataclass, field
from typing import Any, Literal

SearchStrategy = Literal["bm25", "vector", "hybrid", "exact"]


@dataclass
class EmbeddingConfig:
    key: str
    source_column: str
    provider: str = "default"
    model: str = "default"

    @property
    def target_column(self) -> str:
        return f"ds_{self.key}"


@dataclass
class BM25Config:
    key: str
    source_column: str


@dataclass
class ExactConfig:
    key: str
    source_column: str


@dataclass
class TableConfig:
    table: str
    embeddings: list[EmbeddingConfig]
    pk_column: str = "id"
    bm25: list[BM25Config] = field(default_factory=list)
    exact: list[ExactConfig] = field(default_factory=list)
    batch_size: int = 500
    index_id: str = ""

    def __post_init__(self) -> None:
        if not self.index_id:
            self.index_id = self.table
        all_keys = (
            [e.key for e in self.embeddings]
            + [b.key for b in self.bm25]
            + [e.key for e in self.exact]
        )
        dupes = [k for k in all_keys if all_keys.count(k) > 1]
        if dupes:
            raise ValueError(f"Duplicate keys in TableConfig: {sorted(set(dupes))}")

    @classmethod
    def from_json(cls, raw: str) -> TableConfig:
        data = json.loads(raw)
        data["embeddings"] = [EmbeddingConfig(**e) for e in data["embeddings"]]
        data["bm25"] = [BM25Config(**b) for b in data["bm25"]]
        data["exact"] = [ExactConfig(**e) for e in data.get("exact", [])]
        return cls(**data)


@dataclass
class SearchConfig:
    strategy: SearchStrategy = "hybrid"
    limit: int = 10
    where: str | None = None
    weights: dict[str, float] = field(default_factory=dict)


@dataclass
class SearchResult:
    id: Any
    score: float
    row: dict[str, Any]  # all non-ds_ columns
