"""POSEPATH-01: category/output requirements select one provider at config time."""
from dataclasses import asdict, dataclass
import math

POSE_MODEL = 'RFDETRKeypointPreview'
PERSON_MODELS = frozenset(('RFDETRMedium', POSE_MODEL))
JOINT_NAMES = ('nose', 'left_eye', 'right_eye', 'left_ear', 'right_ear',
               'left_shoulder', 'right_shoulder', 'left_elbow', 'right_elbow',
               'left_wrist', 'right_wrist', 'left_hip', 'right_hip',
               'left_knee', 'right_knee', 'left_ankle', 'right_ankle')


@dataclass(frozen=True)
class PoseConfig:
    enabled: bool = False
    show_bbox: bool = True
    show_joints: bool = False
    show_edges: bool = False
    person_score: float = .25
    joint_score: float = .15
    min_joints: int = 10
    nms_enabled: bool = True
    nms_iou_threshold: float = .85
    detail_track_id: int | None = None
    detail_object_ref: str | None = None
    detail_frame_seq: int | None = None
    show_joint_names: bool = False
    show_joint_scores: bool = False
    capture_interval_s: float = 3.

    @classmethod
    def parse(cls, value):
        if not isinstance(value, dict) or set(value)-set(cls.__dataclass_fields__):
            raise ValueError('Invalid Pose settings.')
        return cls(**value)

    def __post_init__(self):
        for name in ('enabled','show_bbox','show_joints','show_edges','show_joint_names','show_joint_scores','nms_enabled'):
            if type(getattr(self,name)) is not bool:
                raise ValueError('Invalid Pose toggle.')
        for value in (self.person_score,self.joint_score,self.nms_iou_threshold):
            if type(value) not in (int,float) or not math.isfinite(value) or not 0 <= value <= 1:
                raise ValueError('Invalid Pose score.')
        if type(self.min_joints) is not int or not 1 <= self.min_joints <= 17:
            raise ValueError('Invalid minimum joint count.')
        if (type(self.capture_interval_s) not in (int,float) or not math.isfinite(self.capture_interval_s)
                or not .5 <= self.capture_interval_s <= 30):
            raise ValueError('Invalid capture interval.')
        if self.detail_track_id is not None and (type(self.detail_track_id) is not int or self.detail_track_id < 1):
            raise ValueError('Invalid detail track.')
        if self.detail_object_ref is not None:
            if (type(self.detail_object_ref) is not str or not self.detail_object_ref.isascii()
                    or not self.detail_object_ref.isdigit() or len(self.detail_object_ref)>3
                    or not 0 <= int(self.detail_object_ref)<300
                    or type(self.detail_frame_seq) is not int or self.detail_frame_seq<0
                    or self.detail_track_id is not None):
                raise ValueError('Object detail requires exact Frame reference.')
        elif self.detail_frame_seq is not None:
            raise ValueError('Frame detail requires object reference.')

    @property
    def required(self):
        return self.enabled or self.show_joints or self.show_edges

    def payload(self):
        return asdict(self)


def execution_plan(spec):
    from app.crowd_runtime import crowd_plan
    crowd = crowd_plan(spec)
    if crowd is not None:
        return crowd
    pose = PoseConfig.parse(spec.get('pose', {}))
    from app.fall_runtime import validate as validate_fall
    from app.fight_runtime import validate as validate_fight
    pose_required = pose.required or validate_fall(spec).enabled or validate_fight(spec).enabled
    from app.fire_config import fire_plan, FIRE_MODEL, FIRE_NAMES
    selected = fire_plan(spec, pose_required)
    if selected is not None:
        return selected
    if (spec.get('inference_profile') or {}).get('det_model') == FIRE_MODEL:
        raise ValueError('Fire provider requires explicit fire.enabled.')
    if any(k in FIRE_NAMES for k in ((spec.get('visualization') or {}).get('categories') or [])):
        raise ValueError('Person provider cannot supply Fire categories.')
    if pose_required:
        if not spec.get('server_id') or not spec.get('source_id'):
            raise ValueError('Canonical identity is required for Pose.')
        categories = (spec.get('visualization') or {}).get('categories')
        if categories is not None and any(k != 'person' for k in categories):
            raise ValueError('Pose provider cannot supply requested non-person categories.')
        if spec.get('store', True) or spec.get('active_learning', True):
            raise ValueError('Pose storage/Active Learning path is not supported yet.')
    return dict(category='person',model=POSE_MODEL if pose_required else 'RFDETRMedium',
                role='pose' if pose_required else 'det',
                outputs=['bbox','keypoints'] if pose_required else ['bbox'])


def planned_profile(spec):
    plan = execution_plan(spec)
    raw = dict(spec.get('inference_profile') or {})
    if not any(k in spec for k in ('pose','fire','fall','fight','crowd')):
        return raw or None  # Legacy profiles retain their existing meaning.
    raw['roles'] = [plan['role']]
    raw[plan['role']+'_model'] = plan['model']
    return raw
