"""Data types for Condensa Code contracts."""

from __future__ import annotations

from dataclasses import dataclass, field


@dataclass
class PropSpec:
    """A single prop/field specification."""
    name: str
    type: str  # e.g., "Program[]", "fn(id:n)->void", "s*", "n?"
    required: bool = True  # True unless type ends with '?'
    is_function: bool = False  # True if type starts with 'fn'

    @classmethod
    def parse(cls, spec: str) -> PropSpec:
        """Parse 'name:type' into a PropSpec."""
        if ":" not in spec:
            return cls(name=spec, type="any")

        name, type_str = spec.split(":", 1)
        required = not type_str.endswith("?")
        is_fn = type_str.startswith("fn")
        return cls(name=name, type=type_str, required=required, is_function=is_fn)


@dataclass
class FnContract:
    """Function/component contract from !:fn."""
    name: str
    props: list[PropSpec] = field(default_factory=list)
    returns: str = "void"
    throws: str = ""
    renders: list[str] = field(default_factory=list)

    @property
    def prop_names(self) -> set[str]:
        return {p.name for p in self.props}

    @property
    def required_props(self) -> set[str]:
        return {p.name for p in self.props if p.required}

    @property
    def callback_props(self) -> set[str]:
        return {p.name for p in self.props if p.is_function}


@dataclass
class FieldSpec:
    """A single field in a type definition."""
    name: str
    type: str
    required: bool = False  # True if type ends with '*'
    optional: bool = False  # True if type ends with '?'
    constraints: list[str] = field(default_factory=list)  # pk, fk:X, unique, idx


@dataclass
class TypeContract:
    """Type/interface contract from !:type."""
    name: str
    fields: list[FieldSpec] = field(default_factory=list)

    @property
    def field_names(self) -> set[str]:
        return {f.name for f in self.fields}


@dataclass
class WireContract:
    """Wiring contract from !:wire."""
    source: str  # e.g., "dashboard.onSelect"
    target: str  # e.g., "programs.highlight"

    @property
    def source_component(self) -> str:
        return self.source.split(".")[0] if "." in self.source else self.source

    @property
    def source_port(self) -> str:
        return self.source.split(".", 1)[1] if "." in self.source else ""

    @property
    def target_component(self) -> str:
        return self.target.split(".")[0] if "." in self.target else self.target

    @property
    def target_port(self) -> str:
        return self.target.split(".", 1)[1] if "." in self.target else ""


@dataclass
class DefContract:
    """Module definition from !:def."""
    name: str
    exports: list[str] = field(default_factory=list)
    deps: list[str] = field(default_factory=list)
    pages: list[str] = field(default_factory=list)
    types: list[str] = field(default_factory=list)


@dataclass
class ApiContract:
    """API endpoint from !:api."""
    method: str  # GET, POST, PUT, DELETE, PATCH
    path: str
    params: list[PropSpec] = field(default_factory=list)
    body: str = ""
    returns: str = ""
    status_codes: list[int] = field(default_factory=list)


@dataclass
class SchemaContract:
    """Database schema from !:schema."""
    name: str
    fields: list[FieldSpec] = field(default_factory=list)


@dataclass
class CheckResult:
    """Result of a contract validation check."""
    passed: bool
    component: str
    found_props: list[str] = field(default_factory=list)
    missing_props: list[str] = field(default_factory=list)
    extra_props: list[str] = field(default_factory=list)
    missing_callbacks: list[str] = field(default_factory=list)
    type_mismatches: list[str] = field(default_factory=list)
    issues: list[str] = field(default_factory=list)

    @property
    def confidence(self) -> str:
        if not self.issues and not self.missing_props and not self.type_mismatches:
            return "!"
        if self.missing_props or self.missing_callbacks:
            return "~~"
        return "?"

    def to_condensa(self) -> str:
        """Format as Condensa =:check result."""
        status = "pass" if self.passed else "fail"
        found = ",".join(self.found_props) if self.found_props else "none"
        missing = ",".join(self.missing_props) if self.missing_props else ""
        extra = ",".join(self.extra_props) if self.extra_props else ""
        return f"=:check {status} props:({found}) missing:({missing}) extra:({extra}) {self.confidence}"

    def __str__(self) -> str:
        return self.to_condensa()


@dataclass
class ContractSet:
    """A collection of parsed contracts."""
    functions: dict[str, FnContract] = field(default_factory=dict)
    types: dict[str, TypeContract] = field(default_factory=dict)
    wires: list[WireContract] = field(default_factory=list)
    modules: dict[str, DefContract] = field(default_factory=dict)
    apis: list[ApiContract] = field(default_factory=list)
    schemas: dict[str, SchemaContract] = field(default_factory=dict)

    def get_fn(self, name: str) -> FnContract | None:
        return self.functions.get(name)

    def get_type(self, name: str) -> TypeContract | None:
        return self.types.get(name)

    @property
    def all_component_names(self) -> set[str]:
        return set(self.functions.keys())

    @property
    def all_type_names(self) -> set[str]:
        return set(self.types.keys())

    @property
    def wired_components(self) -> set[str]:
        """All component names referenced in wires."""
        names = set()
        for w in self.wires:
            names.add(w.source_component)
            names.add(w.target_component)
        return names
