"""FALL v1 local-only policy. No Runtime settings or public wire registration."""
from dataclasses import asdict, dataclass, fields
import math


def seconds_ns(value: float) -> int:
    return round(value * 1_000_000_000)


@dataclass(frozen=True)
class FallConfig:
    enabled: bool = False
    person_score: float = 0.25
    joint_score: float = 0.15
    upright_angle_deg: float = 35.0
    down_angle_deg: float = 60.0
    torso_delta_deg: float = 25.0
    hip_drop_ratio: float = 0.25
    span_collapse_ratio: float = 0.70
    knee_guard_angle_deg: float = 100.0
    knee_guard_torso_max_deg: float = 45.0
    upright_hold_seconds: float = 0.5
    transition_window_seconds: float = 0.8
    transition_to_down_seconds: float = 0.8
    down_hold_seconds: float = 0.7
    test_skip_down_hold: bool = False
    missing_grace_seconds: float = 0.5
    recovery_hold_seconds: float = 1.0
    confirmed_retention_seconds: float = 5.0
    reference_window_seconds: float = 1.0
    unconfirmed_retention_seconds: float = 5.0
    max_states: int = 1024
    history_capacity: int = 256

    def __post_init__(self):
        for name in ('enabled', 'test_skip_down_hold'):
            if type(getattr(self, name)) is not bool:
                raise ValueError(f'FALL {name} must be boolean.')
        for item in fields(self):
            name, value = item.name, getattr(self, item.name)
            if name in ('enabled', 'test_skip_down_hold', 'max_states', 'history_capacity'):
                continue
            if type(value) not in (int, float) or not math.isfinite(value):
                raise ValueError(f'FALL {name} must be finite numeric, not boolean.')
            if name.endswith('_seconds') and not 0.001 <= value <= 3600:
                raise ValueError(f'FALL {name} must be in [0.001, 3600].')
        for name in ('person_score', 'joint_score'):
            if not 0 <= getattr(self, name) <= 1:
                raise ValueError(f'FALL {name} must be in [0, 1].')
        if not 0 <= self.upright_angle_deg < self.knee_guard_torso_max_deg < self.down_angle_deg <= 90:
            raise ValueError('FALL angle order must be upright < knee torso < down <= 90.')
        if not 0 < self.torso_delta_deg <= 90 or not 0 < self.knee_guard_angle_deg < 180:
            raise ValueError('Invalid FALL angle threshold.')
        if not 0 < self.hip_drop_ratio <= 2 or not 0 < self.span_collapse_ratio < 1:
            raise ValueError('Invalid FALL ratio threshold.')
        if self.reference_window_seconds < self.upright_hold_seconds:
            raise ValueError('FALL reference window must cover arming hold.')
        if self.missing_grace_seconds > self.transition_window_seconds:
            raise ValueError('FALL grace must not exceed recent transition window.')
        if min(self.confirmed_retention_seconds, self.unconfirmed_retention_seconds) <= self.missing_grace_seconds:
            raise ValueError('FALL retention must exceed missing grace.')
        for name, maximum in (('max_states', 8192), ('history_capacity', 4096)):
            value = getattr(self, name)
            minimum = 1 if name == 'max_states' else 2
            if type(value) is not int or not minimum <= value <= maximum:
                raise ValueError(f'Invalid FALL {name}.')

    @classmethod
    def parse(cls, value):
        if type(value) is not dict or set(value) - {f.name for f in fields(cls)}:
            raise ValueError('Invalid local FALL settings.')
        return cls(**value)

    def payload(self):
        """Local config only; never attach this to public Metadata/Event payloads."""
        return asdict(self)
