"""MROI-01: shared detections, rule-local display semantics and stable Event references."""
from dataclasses import dataclass, replace
import json

from app.contracts.visualization import FrameResult
from app.crowd_event import CrowdEvent
from app.multi_roi.model import canonical


@dataclass(frozen=True)
class RuleDisplay:
    rule_id: int
    roi_id: int
    data: str
    state: str = 'NONE'
    event_id: str | None = None

    def payload(self):
        return dict(json.loads(self.data), ruleId=self.rule_id, roiId=self.roi_id)


def tag(part, rule, area):
    from app.region_scenario import members, alerted
    context = dict(rule.payload(), area=area.payload())
    display = dict(ruleId=rule.rule_id, roiId=area.roi_id, scenarioCode=rule.scenario,
                   roi=[list(p) for p in area.points], roi_name=area.label,
                   outside=area.outside, debug_roi=True)
    commands = []
    for command in part.commands:
        decisions = members(command.scenario)
        preferred = next((d for d in decisions if alerted(d)), decisions[0] if decisions else None)
        payload = preferred.payload() if preferred else dict(scenario=rule.scenario, state='NONE', eventId=None)
        payload.update(visible=command.visible, color=command.color, labelText=command.label_text,
                       eventIds=list(command.event_ids))
        decision = RuleDisplay(rule.rule_id, area.roi_id, canonical(payload),
                               payload.get('state', 'NONE'), payload.get('eventId'))
        commands.append(replace(command, scenario=decision))
    events = tuple(replace(e, rule_id=rule.rule_id, roi_id=area.roi_id,
                           rule_context=canonical(context)) for e in part.events)
    regions = tuple(canonical(dict(r.payload(), ruleId=rule.rule_id, roiId=area.roi_id))
                    for r in (part.crowd_regions or ()))
    return replace(part, commands=tuple(commands), events=events, scenario_config=None,
                   crowd_regions=None, rule_configs=(canonical(display),), rule_regions=regions)


def merge(frame, parts, *, camera_id, revision=None):
    from app.region_scenario import members, alerted
    commands, positions, events, configs, regions = [], {}, [], [], []
    pose_config = None
    for part in parts:
        if part.frame != frame:
            raise ValueError('Foreign rule result frame.')
        remap = {}
        for i, c in enumerate(part.commands):
            # Rule engines reuse these immutable observation instances; equal-valued
            # distinct provider detections must not be accidentally collapsed.
            key = id(c.detection)
            if key not in positions:
                positions[key] = len(commands)
                commands.append(c)
            else:
                pos = positions[key]
                previous = commands[pos]
                decisions = (*members(previous.scenario), *members(c.scenario))
                preferred = c if c.visible and (not previous.visible or alerted(c.scenario)) else previous
                commands[pos] = replace(preferred, visible=previous.visible or c.visible,
                    scenario=decisions, event_ids=tuple(dict.fromkeys((*previous.event_ids, *c.event_ids))))
            remap[i] = positions[key]
        events.extend(e if isinstance(e, CrowdEvent) else replace(e, object_ref=str(remap[int(e.object_ref)]))
                      for e in part.events)
        configs.extend(part.rule_configs)
        regions.extend(part.rule_regions)
        pose_config = part.pose_config or pose_config
    return FrameResult(frame, tuple(commands), tuple(events), scenario_revision=revision,
                       camera_id=camera_id, pose_config=pose_config,
                       rule_configs=tuple(dict.fromkeys(configs)), rule_regions=tuple(regions))
