from __future__ import annotations

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


KnobLocation = Literal["flag", "env"]
KnobValueType = Literal["int", "float", "bool", "enum", "str"]
SearchGeometry = Literal["linear", "positive_capacity", "bounded_fraction", "toggle"]


@dataclass(frozen=True)
class KnobConstraints:
    min_value: float | None = None
    max_value: float | None = None
    integer: bool = False
    multiple_of: int | None = None


@dataclass(frozen=True)
class KnobDescriptor:
    """Declarative serving-knob semantics used by generic planners.

    The descriptor intentionally does not enumerate target values for continuous
    or large integer knobs. It describes how a generic operator may perturb the
    knob and which mechanism each direction is expected to affect.
    """

    name: str
    location: KnobLocation
    value_type: KnobValueType
    mechanisms: tuple[str, ...]
    search_geometry: SearchGeometry
    operators: tuple[str, ...]
    constraints: KnobConstraints = field(default_factory=KnobConstraints)
    directional_effects: Mapping[str, tuple[str, ...]] = field(default_factory=dict)
    risk_effects: Mapping[str, tuple[str, ...]] = field(default_factory=dict)

    def current_value(self, config: Mapping[str, Any]) -> Any:
        return config.get(self.name)
