from webdriver.bidi.modules.script import ContextTarget

from .. import get_object_from_context


def remote_mapping_to_dict(js_object):
    obj = {}
    for key, value in js_object:
        obj[key] = value["value"]

    return obj


def assert_events(events, expected_events):
    events_not_seen = events.copy()

    for expected_event in expected_events:
       match_found = False
       for event in events_not_seen:
           # Check that all expected fields are present
           if all(item in event.items() for item in expected_event.items()):
               events_not_seen.remove(event)
               match_found = True
               break
       assert match_found, f"Expected event not found: {expected_event}"

    assert len(
        events_not_seen) == 0, f"Extra events received: {events_not_seen}"


async def assert_pointer_events(
    bidi_session, context, expected_events, target, pointer_type
):
    events = await get_object_from_context(
        bidi_session, context["context"], "window.recordedEvents"
    )

    assert len(events) == len(expected_events)
    event_types = [e["type"] for e in events]
    assert expected_events == event_types

    for e in events:
        assert e["target"] == target
        assert e["pointerType"] == pointer_type



async def get_inview_center_bidi(bidi_session, context, element):
    elem_rect = await get_element_rect(bidi_session,
                                       context=context,
                                       element=element)
    viewport_rect = await get_viewport_rect(bidi_session,
                                            context=context)

    x = {
        "left": max(0, min(elem_rect["x"],
                           elem_rect["x"] + elem_rect["width"])),
        "right": min(
            viewport_rect["width"],
            max(elem_rect["x"], elem_rect["x"] + elem_rect["width"]),
        ),
    }

    y = {
        "top": max(0, min(elem_rect["y"],
                          elem_rect["y"] + elem_rect["height"])),
        "bottom": min(
            viewport_rect["height"],
            max(elem_rect["y"], elem_rect["y"] + elem_rect["height"]),
        ),
    }

    return {
        "x": (x["left"] + x["right"]) / 2,
        "y": (y["top"] + y["bottom"]) / 2,
    }


async def get_element_rect(bidi_session, context, element):
    result = await bidi_session.script.call_function(
        function_declaration="""
el => el.getBoundingClientRect().toJSON()
""",
        arguments=[element],
        target=ContextTarget(context["context"]),
        await_promise=False,
    )

    return remote_mapping_to_dict(result["value"])


async def get_shadow_root_from_test_page(bidi_session, context, nested=False):
    custom_element = await bidi_session.script.call_function(
        function_declaration="""() => document.querySelector("custom-element")""",
        target=ContextTarget(context["context"]),
        await_promise=False,
    )

    shadow_root = custom_element["value"]["shadowRoot"]

    if nested:
        custom_element = await bidi_session.script.call_function(
            function_declaration="""shadowRoot => shadowRoot.querySelector("inner-custom-element")""",
            target=ContextTarget(context["context"]),
            arguments=[shadow_root],
            await_promise=False,
        )
        shadow_root = custom_element["value"]["shadowRoot"]

    return shadow_root


async def get_viewport_rect(bidi_session, context):
    expression = """
        ({
          height: window.innerHeight || document.documentElement.clientHeight,
          width: window.innerWidth || document.documentElement.clientWidth,
        });
    """
    result = await bidi_session.script.evaluate(
        expression=expression,
        target=ContextTarget(context["context"]),
        await_promise=False,
    )

    return remote_mapping_to_dict(result["value"])


async def record_pointer_events(bidi_session, context, container, selector):
    # Record basic mouse / pointer events on the element matching the given
    # selector in the container.
    # The serialized element will be returned
    target = await bidi_session.script.call_function(
        function_declaration=f"""container => {{
            const target = container.querySelector("{selector}");
            window.recordedEvents = [];
            function onPointerEvent(event) {{
                window.recordedEvents.push({{
                    "type": event.type,
                    "pointerType": event.pointerType,
                    "target": event.target.id
                }});
            }}
            target.addEventListener("pointerdown", onPointerEvent);
            target.addEventListener("pointerup", onPointerEvent);
            return target;
        }}
        """,
        arguments=[container],
        target=ContextTarget(context["context"]),
        await_promise=False,
    )

    return target
