import time
import random
import math

def get_cubic_bezier_point(t, p0, p1, p2, p3):
    x = (1-t)**3 * p0[0] + 3*(1-t)**2 * t * p1[0] + 3*(1-t) * t**2 * p2[0] + t**3 * p3[0]
    y = (1-t)**3 * p0[1] + 3*(1-t)**2 * t * p1[1] + 3*(1-t) * t**2 * p2[1] + t**3 * p3[1]
    return (x, y)

def ease_out_quad(x):
    return 1 - (1 - x) * (1 - x)

def generate_human_path(start_x, start_y, end_x, end_y, steps=30):
    path = []
    dist = math.hypot(end_x - start_x, end_y - start_y)
    offset = dist * 0.2
    
    p0 = (start_x, start_y)
    p3 = (end_x, end_y)
    
    p1 = (
        start_x + (end_x - start_x) * 0.3 + random.uniform(-offset, offset),
        start_y + (end_y - start_y) * 0.3 + random.uniform(-offset, offset)
    )
    p2 = (
        start_x + (end_x - start_x) * 0.7 + random.uniform(-offset, offset),
        start_y + (end_y - start_y) * 0.7 + random.uniform(-offset, offset)
    )

    for i in range(steps + 1):
        t = i / steps
        eased_t = ease_out_quad(t)
        point = get_cubic_bezier_point(eased_t, p0, p1, p2, p3)
        jitter = 1.5
        final_x = point[0] + random.uniform(-jitter, jitter)
        final_y = point[1] + random.uniform(-jitter, jitter)
        if i == steps:
            final_x, final_y = end_x, end_y
        path.append((final_x, final_y))
    return path

class HumanMouse:
    def __init__(self, page):
        self.page = page
        self.curr_x = random.randint(100, 500)
        self.curr_y = random.randint(100, 500)
        
        self.page.run_cdp('Input.dispatchMouseEvent', **{
            'type': 'mouseMoved',
            'x': self.curr_x,
            'y': self.curr_y
        })

    def _get_center(self, ele):
        """兼容性获取中心点"""
        rect = ele.rect
        try:
            tl_x, tl_y = rect.location 
            width, height = rect.size
        except AttributeError:
            tl_x, tl_y, width, height = rect
        return tl_x + (width / 2), tl_y + (height / 2)

    def move_to(self, ele, duration=0.5):
        center_x, center_y = self._get_center(ele)
        
        # 目标稍微带点随机偏移
        target_x = center_x + random.uniform(-3, 3)
        target_y = center_y + random.uniform(-3, 3)
        
        if self.curr_x == 0 and self.curr_y == 0:
            self.curr_x = target_x - random.randint(300, 500)
            self.curr_y = target_y - random.randint(300, 500)
            self.page.run_cdp('Input.dispatchMouseEvent', **{
                'type': 'mouseMoved', 
                'x': self.curr_x, 
                'y': self.curr_y
            })

        steps = int(duration * 60)
        if steps < 10: steps = 10
        
        points = generate_human_path(self.curr_x, self.curr_y, target_x, target_y, steps)
        
        for x, y in points:
            self.page.run_cdp('Input.dispatchMouseEvent', **{
                'type': 'mouseMoved',
                'x': x,
                'y': y
            })
            self.curr_x = x
            self.curr_y = y
            time.sleep(duration / steps * random.uniform(0.8, 1.2))

    def scroll_to_visible(self, ele):
        viewport_height = self.page.run_js("return window.innerHeight")
        while True:
            # 使用 arguments[0] 修复 run_js 参数问题
            rect = self.page.run_js("""
                var rect = arguments[0].getBoundingClientRect();
                return {top: rect.top, bottom: rect.bottom, height: rect.height};
            """, ele)
            
            element_top = rect['top']
            element_bottom = rect['bottom']
            
            scroll_needed = False
            delta_y = 0
            
            # 增加一些缓冲区，不要滚得太极限
            if element_top > viewport_height * 0.7: 
                scroll_needed = True
                delta_y = random.randint(100, 250) 
            elif element_bottom < viewport_height * 0.3:
                scroll_needed = True
                delta_y = -random.randint(100, 250)
            
            if not scroll_needed:
                break
                
            self._dispatch_scroll(delta_y)
            time.sleep(random.uniform(0.1, 0.2))

    def _dispatch_scroll(self, delta_y):
        self.page.run_cdp('Input.dispatchMouseEvent', **{
            'type': 'mouseWheel',
            'x': self.curr_x, 
            'y': self.curr_y,
            'deltaX': 0,
            'deltaY': delta_y,
            'modifiers': 0,
            'pointerType': 'mouse'
        })