"""Part-local sampling grid mapped back to the unchanged L01 input timeline."""
import bisect
import math
from sampling import make_plan

def make_part_plan(pts, bounds, part_id):
    lo, hi = bounds
    if not pts or not 1 <= lo <= hi <= len(pts):
        raise ValueError('Invalid part bounds')
    if any(not math.isfinite(x) for x in pts) or any(b <= a for a,b in zip(pts,pts[1:])):
        raise ValueError('Invalid full-input PTS')
    subset = pts[lo-1:hi]
    plan = make_plan(subset, None)
    origin = pts[0]
    offset = subset[0] - origin
    plan.update(part_id=part_id, source_frames=len(pts), part_source_frames=len(subset),
                source_pts_origin=origin, part_origin_seconds=offset,
                display_bounds=[lo,hi], tracking_start='part start; new independent ByteTracker',
                input_context='this part only; no cross-gap observations')
    plan['target_seconds'] = [t + offset for t in plan['target_seconds']]
    # Select and infer on the same global clock so floating-point ties agree.
    times = [t-origin for t in pts]
    indices = []
    for target in plan['target_seconds']:
        right = bisect.bisect_left(times,target,lo-1,hi)
        candidates = [i for i in (right-1,right) if lo-1<=i<hi]
        indices.append(min(candidates,key=lambda i:(abs(times[i]-target),i)))
    plan['source_frame_indices'] = [i+1 for i in indices]
    plan['relative_seconds'] = [times[i] for i in indices]
    plan['max_sampling_error_seconds'] = max(abs(times[i]-t) for i,t in zip(indices,plan['target_seconds']))
    plan['nearest_selection_clock'] = 'L01-relative double precision; exact ties choose earlier frame'
    assert all(lo <= n <= hi for n in plan['source_frame_indices'])
    assert len(set(plan['source_frame_indices'])) == len(plan['source_frame_indices'])
    return plan
