from __future__ import annotations

from pathlib import Path
from typing import Any, Dict, List, Optional, Set

import yaml
from pydantic import BaseModel, Field


class DimensionDef(BaseModel):
    column: str
    type: str = "string"
    description: str = ""


class MetricDef(BaseModel):
    expr: str
    description: str = ""


class EntityDef(BaseModel):
    physical_table: str
    description: str = ""
    time_column: str
    dimensions: Dict[str, DimensionDef] = Field(default_factory=dict)
    metrics: Dict[str, MetricDef] = Field(default_factory=dict)


class SemanticLayer(BaseModel):
    version: int = 1
    defaults: Dict[str, Any] = Field(default_factory=dict)
    entities: Dict[str, EntityDef]

    @classmethod
    def load(cls, path: Optional[Path] = None) -> "SemanticLayer":
        p = path or Path(__file__).resolve().parent.parent / "semantic_layer.yaml"
        raw = yaml.safe_load(p.read_text(encoding="utf-8"))
        return cls.model_validate(raw)

    def entity_names(self) -> List[str]:
        return sorted(self.entities.keys())

    def metric_names(self) -> List[str]:
        names: Set[str] = set()
        for e in self.entities.values():
            names.update(e.metrics.keys())
        return sorted(names)

    def dimension_names(self) -> List[str]:
        names: Set[str] = set()
        for e in self.entities.values():
            names.update(e.dimensions.keys())
        return sorted(names)
