#!/usr/bin/env python3
"""
density_floor.py — synthesise scene boundaries when detection is starved.

Flood-fill presence snaps each actor claim to the shot it sits in, so a film
whose boundary detector fires almost nothing (Scarface: 1 cut in 171 min) floods
every actor across the whole film. This is a safety floor: when a film's DETECTED
boundary density is far below what a working detector should produce, fill the
long gaps between real detections with uniformly-spaced synthetic boundaries so no
flood-fill span can exceed ~1/target-density.

Design points (measured on the X-Ray corpus):
- The target density is a PRIOR from the central 60 min of films (avoids credits/
  intro/outro skew): median ~0.35 scenes/min.
- The trigger is detected-vs-prior, not prior-vs-anything: only fire when detected
  density < TRIGGER_FRAC × prior. Legitimately sparse films (long-scene ensembles
  like Downton/Many Saints) detect fine and are left alone.
- Real detections are never moved or dropped; synthetic boundaries only subdivide
  gaps that are longer than the target scene length.
"""
from __future__ import annotations

PRIOR_SCENES_PER_MIN = 0.35   # central-60min X-Ray median
TRIGGER_FRAC = 0.30           # fire only when detected < 30% of prior


def apply_density_floor(boundaries: list[float], duration_sec: float,
                        prior_per_min: float = PRIOR_SCENES_PER_MIN,
                        trigger_frac: float = TRIGGER_FRAC) -> list[float]:
    """Return boundaries augmented with synthetic ones iff detection is starved.

    boundaries: detected boundary timestamps (s), any order.
    duration_sec: film length.
    Returns a sorted list; unchanged (just sorted) when the film is not starved.
    """
    b = sorted(t for t in boundaries if 0.0 < t < duration_sec)
    minutes = duration_sec / 60.0
    if minutes <= 0:
        return b
    detected_density = len(b) / minutes
    if detected_density >= trigger_frac * prior_per_min:
        return b   # detector produced a reasonable amount — leave it alone

    target_gap = 60.0 / prior_per_min          # seconds per expected scene
    edges = [0.0] + b + [duration_sec]
    out = list(b)
    for lo, hi in zip(edges[:-1], edges[1:]):
        gap = hi - lo
        if gap <= target_gap:
            continue
        n_insert = int(gap // target_gap)      # how many synthetic cuts fit
        step = gap / (n_insert + 1)
        for k in range(1, n_insert + 1):
            out.append(lo + k * step)
    return sorted(out)


if __name__ == "__main__":
    # self-check on the Scarface failure and a healthy film
    scar = apply_density_floor([88.0], 171*60)          # 1 detected cut, 171 min
    print(f"Scarface: 1 detected → {len(scar)} after floor "
          f"({len(scar)/171:.2f}/min, prior {PRIOR_SCENES_PER_MIN})")
    healthy = apply_density_floor([i*130.0 for i in range(1, 47)], 122*60)
    print(f"healthy (46 detected/122min={46/122:.2f}/min): "
          f"{len(healthy)} after floor (unchanged = not triggered)")
