"""Explicit physical-client rectangle boundary; never guess from values.

Current context observations use LTRB. Actuator safe_rect uses XYWH, while
source_rect remains LTRB. All are physical client pixels: desktop origin and
Windows DPI must not be applied here a second time. The input guard still
validates containment and occlusion; this conversion does not clip a rectangle.
"""
import math

CLIENT_LTRB = 'physical-client-ltrb'
CLIENT_XYWH = 'physical-client-xywh'


def ltrb_to_xywh(rect):
    if (not isinstance(rect, (list, tuple)) or len(rect) != 4 or
            any(type(v) not in (int, float) or not math.isfinite(v) for v in rect)):
        raise ValueError('finite physical-client LTRB rectangle required')
    left, top, right, bottom = rect
    if right <= left or bottom <= top:
        raise ValueError('positive physical-client LTRB rectangle required')
    return [left, top, right-left, bottom-top]


def safe_rect_xywh_from_frame(frame, *, full_client_fallback=False):
    context = frame.get('current_context') or {}
    source = context if context.get('safe_rect') is not None else frame
    raw = source.get('safe_rect')
    if raw is None and full_client_fallback:
        left, top, right, bottom = frame['physical_client_rect']
        raw = [0, 0, right-left, bottom-top]
    if source.get('safe_rect_format', CLIENT_LTRB) != CLIENT_LTRB:
        raise ValueError('current context safe rectangle must declare physical-client LTRB')
    return ltrb_to_xywh(raw)
