"""Video-friendly view control for the Fusion Electronics (EAGLE) editor.

One bridge call = a whole smooth motion, because the loop runs HERE inside the add-in
(tight timing + a repaint per frame), not as N round-trips from the bridge server.

  fusion_electron_zoom   - smooth zoom in/out (or fit)
  fusion_electron_pan    - smooth pan/zoom to a board-coordinate box
  fusion_electron_select - select a part by name (or x/y) and surface its properties

All EAGLE motion is `app.executeTextCommand("Electron.run WINDOW ...")`; each frame is
forced to render with `Electron.ImmediateRepaint /full` (the 2D analogue of vp.refresh()).
"""

import time
import adsk.core

# Last view box we set, in board mm (x1, y1, x2, y2). Lets pan interpolate from where
# the view actually is. None until the first pan establishes it.
_view_box = None


def _run(app, eagle_cmd):
    return app.executeTextCommand(f"Electron.run {eagle_cmd}")


def _repaint(app):
    try:
        app.executeTextCommand("Electron.ImmediateRepaint /full")
    except Exception:
        pass


def handle_electron_zoom(app: adsk.core.Application, args: dict) -> dict:
    """Smoothly zoom the schematic / 2D board view.

    Args:
        fit:          bool - zoom to fit the whole sheet/board (ignores factor).
        factor:       float - total zoom multiple (>1 = in, <1 = out). Default 2.0.
        steps:        int   - frames over which to ramp (smoothness). Default 16.
        frameDelayMs: int   - ms between frames. Default 35.
    """
    if args.get("fit"):
        _run(app, "WINDOW FIT")
        _repaint(app)
        return {"success": True, "output": "Zoomed to fit."}

    factor = float(args.get("factor", 2.0))
    steps = max(1, int(args.get("steps", 16)))
    delay = float(args.get("frameDelayMs", 35)) / 1000.0
    per = factor ** (1.0 / steps)  # geometric per-frame step -> perceptually even
    for _ in range(steps):
        _run(app, f"WINDOW {per:.5f}")
        _repaint(app)
        adsk.doEvents()
        time.sleep(delay)
    return {
        "success": True,
        "output": f"Zoomed {'in' if factor > 1 else 'out'} {factor}x over {steps} frames.",
    }


def handle_electron_pan(app: adsk.core.Application, args: dict) -> dict:
    """Smoothly pan + zoom the view to frame a board-coordinate box.

    Coords are board millimetres (get part positions from fusion_board_info). The view
    animates from wherever it last was (tracked) to the target box.

    Args:
        x1, y1, x2, y2: required floats - the target rectangle (board mm).
        steps:          int   - frames. Default 16.
        frameDelayMs:   int   - ms between frames. Default 35.
    """
    global _view_box
    try:
        tx1, ty1 = float(args["x1"]), float(args["y1"])
        tx2, ty2 = float(args["x2"]), float(args["y2"])
    except (KeyError, TypeError, ValueError):
        return {"success": False, "error": "Need x1,y1,x2,y2 (board mm). Get part positions from fusion_board_info."}

    steps = max(1, int(args.get("steps", 16)))
    delay = float(args.get("frameDelayMs", 35)) / 1000.0

    if _view_box is None:
        _run(app, f"WINDOW ({tx1:.3f} {ty1:.3f}) ({tx2:.3f} {ty2:.3f})")
        _repaint(app)
        _view_box = (tx1, ty1, tx2, ty2)
        return {"success": True, "output": "Framed region (no animation - first view set; later pans animate).",
                "data": {"box": _view_box}}

    sx1, sy1, sx2, sy2 = _view_box
    for i in range(1, steps + 1):
        t = i / steps
        x1 = sx1 + (tx1 - sx1) * t
        y1 = sy1 + (ty1 - sy1) * t
        x2 = sx2 + (tx2 - sx2) * t
        y2 = sy2 + (ty2 - sy2) * t
        _run(app, f"WINDOW ({x1:.3f} {y1:.3f}) ({x2:.3f} {y2:.3f})")
        _repaint(app)
        adsk.doEvents()
        time.sleep(delay)
    _view_box = (tx1, ty1, tx2, ty2)
    return {"success": True,
            "output": f"Panned to ({tx1:.1f},{ty1:.1f})-({tx2:.1f},{ty2:.1f}) over {steps} frames.",
            "data": {"box": _view_box}}


def handle_electron_select(app: adsk.core.Application, args: dict) -> dict:
    """Select a part (by reference designator, or by x/y board coords) and read its
    properties - the way clicking a part in the editor reveals the properties panel.

    Args:
        name (or component): str - reference designator (e.g. "R1"). Cleanest.
        x, y:                floats - board-mm point to click/select (alternative).
        properties:          bool - also return the selected device's info. Default true.
    """
    name = args.get("name") or args.get("component")
    try:
        if name:
            sel = app.executeTextCommand(f"Electron.SelectbyName {name}")
        elif "x" in args and "y" in args:
            app.executeTextCommand(f"Electron.LeftButtonDown {args['x']} {args['y']}")
            sel = app.executeTextCommand(f"Electron.Select {args['x']} {args['y']}")
        else:
            return {"success": False, "error": "Provide {name} (reference designator) or {x,y} (board mm)."}
    except Exception as e:
        return {"success": False, "error": f"Select failed: {e}"}

    _repaint(app)
    info = ""
    if args.get("properties", True):
        try:
            info = app.executeTextCommand("Electron.sch_get_deviceinfo")
        except Exception:
            pass
    return {
        "success": True,
        "output": f"Selected {name or (str(args.get('x')) + ',' + str(args.get('y')))}.",
        "data": {"selection": str(sel)[:300], "deviceInfo": str(info)[:600]},
    }