"""R5-CONFIG-01: versioned tuning; engine activation remains AI-owned."""
from dataclasses import asdict, dataclass
from math import isfinite

LEGACY_PARAMETERS = frozenset(('pair_distance_ratio', 'pair_close_hold_seconds',
                               'arm_motion_ratio', 'required_bursts'))


class R5ConfigError(ValueError):
    pass


@dataclass(frozen=True)
class R5Config:
    schema_version: int = 1
    window_seconds: float = 1.0
    min_samples: int = 2
    contact_min_samples: int = 3

    def __post_init__(self):
        if type(self.schema_version) is not int or self.schema_version != 1:
            raise R5ConfigError('FIGHT_R5_SCHEMA_VERSION_UNSUPPORTED')
        if (type(self.window_seconds) not in (int, float) or not .3 <= self.window_seconds <= 3.
                or not isfinite(self.window_seconds)):
            raise R5ConfigError('FIGHT_R5_WINDOW_INVALID')
        for v in (self.min_samples, self.contact_min_samples):
            if type(v) is not int or not 1 <= v <= 16 or v > int(self.window_seconds * 10) + 1:
                raise R5ConfigError('FIGHT_R5_SAMPLE_COUNT_INVALID')

    def payload(self):
        return dict(asdict(self), window_seconds=float(self.window_seconds))

    @classmethod
    def parse(cls, value):
        if type(value) is not dict or set(value) - set(cls.__dataclass_fields__):
            raise R5ConfigError('FIGHT_R5_FIELDS_INVALID')
        if 'schema_version' not in value:
            raise R5ConfigError('FIGHT_R5_SCHEMA_VERSION_REQUIRED')
        return cls(**value)

    def patched(self, value):
        if type(value) is not dict or 'schema_version' not in value:
            raise R5ConfigError('FIGHT_R5_SCHEMA_VERSION_REQUIRED')
        if 'reset_to_defaults' in value:
            if set(value) != {'schema_version', 'reset_to_defaults'} or value['reset_to_defaults'] is not True:
                raise R5ConfigError('FIGHT_R5_RESET_INVALID')
            return self.parse({'schema_version': value['schema_version']})
        return self.parse(dict(self.payload(), **value))

    def decision(self):
        # Values are the reviewed final candidate, not integrated.py's exploratory DEFAULT.
        return dict(window=self.window_seconds, min_samples=self.min_samples, seed_seconds=.6,
            hand_speed=.55, hand_near=.18, hand_vertical=.12, direction=.35, approach=.15,
            foot_speed=.70, foot_near=.20, foot_vertical=.20,
            contact_speed=.25, contact_near=.075, contact_min_samples=self.contact_min_samples,
            response_min=.15, hand_contact_max=.12, target_hand_level=.15, display_hold=1.)


def score_floor(value, minimum):
    if type(value) not in (int, float) or not 0 <= value <= 1 or not isfinite(value):
        raise R5ConfigError('FIGHT_R5_SCORE_INVALID')
    return max(float(value), minimum)
