"""CROWD result-worker seam: accepted observations -> existing FrameResult.

No scheduler, publisher, tracker, pixel retention or Event/Artifact ownership.
The caller must perform run/queue/late-result admission BEFORE process().
"""
from app.contracts.visualization import FrameResult, VisualizationCommand
from app.crowd_config import CROWD_COLORS, CROWD_MODEL, CROWD_NAMES, CrowdDisplayConfig
from app.crowd_scenario import Crowd
from app.crowd_event import CrowdEvent, CrowdRegion
from app.event_cooldown import EventCooldown, allow_event
import time
from uuid import uuid4


class CrowdResultProcessor:
    def __init__(self, engine: Crowd, *, camera_id=None, scenario_revision=None,
                 wall_ns=time.time_ns, event_id_factory=lambda: uuid4().hex):
        if not isinstance(engine, Crowd):
            raise ValueError('CROWD result processing requires its own engine.')
        self.engine = engine
        self.camera_id = camera_id
        self.scenario_revision = scenario_revision
        self._closed = False
        self._danger_emitted = False
        self.wall_ns, self.event_id_factory = wall_ns, event_id_factory
        self.event_cooldown = EventCooldown(clock=engine.clock)
        engine.event_admission = self.event_cooldown.allow

    def process(self, frame, observations, *, usable=True, gap_before=False):
        if self._closed:
            raise RuntimeError('CROWD result processor is closed.')
        # Validate output identity before evaluate() can mutate the window.
        if self.camera_id is not None and (type(self.camera_id) not in (str, int)
                or str(self.camera_id) != frame.source_id):
            raise ValueError('CROWD camera/source mismatch.')
        evaluation = self.engine.evaluate(frame, observations, usable=usable, gap_before=gap_before)
        if not evaluation.accepted:
            return None
        representatives = {}
        for i in evaluation.indices:
            o = observations[i]
            old = representatives.get(o.class_id)
            if old is None or o.score > observations[old].score:
                representatives[o.class_id] = i  # Equal score keeps original index.
        commands = []
        for i in evaluation.indices:
            o = observations[i]
            label = CROWD_NAMES[o.class_id]
            if evaluation.estimate is not None and representatives[o.class_id] == i:
                n = (evaluation.estimate.person_count if o.class_id == 0
                     else evaluation.estimate.head_count)
                label += f' | {"P" if o.class_id == 0 else "H"}≈{n}'
            commands.append(VisualizationCommand(o, label,
                color=CROWD_COLORS[o.class_id], visible=True,
                track_id=None, event_ids=(), scenario=None))
        # Never pass nms_kept: legacy serialization of that field is Pose-specific.
        # Current boxes only; a previous count is not a previous detection.
        regions, events = None, ()
        if self.engine.config.event_enabled:
            regions = (() if evaluation.estimate is None else
                       (CrowdRegion.from_estimate(self.engine.config, evaluation.estimate),))
            if evaluation.reported and regions:
                region = regions[0]
                if region.status != 'DANGER':
                    self._danger_emitted = False
                elif not self._danger_emitted or not self.engine.config.suppress_repeated_danger:
                    if allow_event(self.engine, 'CROWD'):
                        events = (CrowdEvent(self.event_id_factory(), frame, self.engine.config.roi_id,
                                             region, self.wall_ns() // 1_000_000),)
                        self._danger_emitted = True
                        self.engine.metrics['events_created'] += 1
        return FrameResult(frame, tuple(commands), events=events,
            scenario_config=CrowdDisplayConfig(self.engine.config),
            scenario_revision=self.scenario_revision, provider_model=CROWD_MODEL,
            camera_id=self.camera_id, crowd_regions=regions)

    def expire_idle(self):
        return False if self._closed else self.engine.expire_idle()

    def close(self):
        self.engine.clear()
        self._closed = True
