"""Test the new scale + rectangle-object features:
1. Tap the orange value copies the decimal + shows a toast.
2. Scale calibration: the longest dimensioned line sets the canvas scale.
3. Auto-guess: a new line's dimension is pre-filled from the scale.
4. Snap-to-length: entering a length resizes a line to match.
5. Rectangle as one object: copy whole, drag whole, explode.
6. Rect: one width + one height set all 4 sides.
"""
import os, sys, time, json
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service

HERE = os.path.dirname(os.path.abspath(__file__))
GECKODRIVER = os.path.join(HERE, "geckodriver.exe")
BASE = "http://localhost:8000/field-capture.html?debug"

def setup(d):
    d.get(BASE)
    time.sleep(2)
    d.find_element(By.CSS_SELECTOR, '[data-action="newjob"]').click()
    time.sleep(0.8)
    d.find_element(By.CSS_SELECTOR, '[data-action="opensketch"]').click()
    time.sleep(0.8)
    d.execute_script("""
        const T = window.__test;
        const job = T.state.jobs[0]; const sk = job.sketches[0];
        const c = document.createElement('canvas'); c.width=200; c.height=150;
        const ctx = c.getContext('2d'); ctx.fillStyle='#ccc'; ctx.fillRect(0,0,200,150);
        sk.photo = { dataUrl: c.toDataURL('image/jpeg', 0.8), width: 200, height: 150 };
        T.saveState();
    """)
    d.execute_script(
        "const T=window.__test; T.ui.screen='sketch'; T.ui.sketchId=T.state.jobs[0].sketches[0].id; T.render();"
    )
    time.sleep(0.6)

def draw_line(d, sx, sy, ex, ey, pid):
    d.execute_script("""
        return (function(sx, sy, ex, ey, pid){
          const canvas = document.getElementById('sketchCanvas');
          canvas.setPointerCapture = function(){};
          canvas.releasePointerCapture = function(){};
          canvas.hasPointerCapture = function(){ return false; };
          const shell = document.querySelector('.sketch-shell');
          const sr = shell.getBoundingClientRect();
          const left = sr.left + shell.clientLeft, top = sr.top + shell.clientTop;
          const view = document.getElementById('sketchView');
          const T = window.__test;
          const v = T.ui.viewState[T.ui.sketchId] || { scale: 1, tx: 0, ty: 0 };
          const w = view.offsetWidth, h = view.offsetHeight;
          const toC = (nx, ny) => ({ x: left + v.tx + nx*w*v.scale, y: top + v.ty + ny*h*v.scale });
          const s = toC(sx, sy), e = toC(ex, ey);
          const opts = { bubbles: true, cancelable: true, pointerType: 'touch', isPrimary: true };
          canvas.dispatchEvent(new PointerEvent('pointerdown', Object.assign({}, opts, {pointerId: pid, clientX: s.x, clientY: s.y})));
          for (let i = 1; i <= 5; i++) canvas.dispatchEvent(new PointerEvent('pointermove', Object.assign({}, opts, {pointerId: pid, clientX: s.x+(e.x-s.x)*i/5, clientY: s.y+(e.y-s.y)*i/5})));
          canvas.dispatchEvent(new PointerEvent('pointerup', Object.assign({}, opts, {pointerId: pid, clientX: e.x, clientY: e.y})));
        })(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
    """, sx, sy, ex, ey, pid)
    time.sleep(0.5)
    try:
        d.execute_script('document.querySelector(".sheet-minimal [data-action=closesheet]").click()')
        time.sleep(0.3)
    except Exception:
        pass

def get_lines(d):
    return json.loads(d.execute_script(
        "const T=window.__test; const sk=T.state.jobs[0].sketches[0]; return JSON.stringify(sk.lines.map(l=>({start:l.start,end:l.end,feet:l.feet,inches:l.inches,sixteenths:l.sixteenths,groupId:l.groupId||null,num:l.num})));"
    ))

def get_rects(d):
    return json.loads(d.execute_script(
        "const T=window.__test; const sk=T.state.jobs[0].sketches[0]; return JSON.stringify(sk.rects||[]);"
    ))

def main():
    opts = Options()
    opts.headless = False
    svc = Service(executable_path=GECKODRIVER)
    d = webdriver.Firefox(options=opts, service=svc)
    d.set_window_size(480, 900)
    try:
        setup(d)

        # ---- 1. Tap-to-copy + toast ----
        # Draw a line, set a value, then tap the orange preview.
        draw_line(d, 0.2, 0.3, 0.5, 0.3, 41)
        d.execute_script("""
            const T = window.__test;
            const sk = T.state.jobs[0].sketches[0];
            const l = sk.lines[0];
            l.feet = 2; l.inches = 3; l.sixteenths = 8; l.quickTouched = true;
            T.saveState();
            T.ui.pinModal = { sketchId: sk.id, pinId: l.id, kind: 'line' };
            T.ui.modalMode = 'quick'; T.render();
        """)
        time.sleep(0.5)
        ok1 = d.execute_script("""
            window.__capturedCopy = null;
            window.copyText = (t) => { window.__capturedCopy = t; return Promise.resolve(); };
            const el = document.getElementById('quickPreview');
            if (!el) return false;
            const opts = { bubbles: true, cancelable: true, pointerType: 'touch', isPrimary: true };
            const r = el.getBoundingClientRect();
            el.dispatchEvent(new PointerEvent('pointerdown', Object.assign({}, opts, {pointerId: 51, clientX: r.left+r.width/2, clientY: r.top+r.height/2})));
            el.dispatchEvent(new PointerEvent('pointerup', Object.assign({}, opts, {pointerId: 51, clientX: r.left+r.width/2, clientY: r.top+r.height/2})));
            return true;
        """)
        time.sleep(0.6)
        captured = d.execute_script("return window.__capturedCopy;")
        toastShown = d.execute_script("const t=document.getElementById('mini-toast'); return t ? t.classList.contains('show') : false;")
        ok1 = captured == "27.5" and toastShown
        print("tap copy:", captured, "toast:", toastShown, "->", "PASS" if ok1 else "FAIL")
        # Close the sheet.
        try:
            d.execute_script('document.querySelector(".sheet-minimal [data-action=closesheet]").click()')
            time.sleep(0.3)
        except Exception:
            pass

        # ---- 2. Scale calibration (longest dimensioned line wins) ----
        # Line 1 already exists (draw_line above). Set it to 100 inches.
        d.execute_script("""
            const T = window.__test;
            const sk = T.state.jobs[0].sketches[0];
            const l = sk.lines[0];
            l.feet = 8; l.inches = 4; l.sixteenths = 0; l.quickTouched = true;  // 100"
            T.saveState();
            T.maybeRecalibrate(sk, l);
        """)
        scale_info = json.loads(d.execute_script(
            "const T=window.__test; const sk=T.state.jobs[0].sketches[0]; return JSON.stringify({scale:sk.scale, ref:sk.scaleRef});"
        ))
        # Line 1 drawn length = 0.3 normalized. scale = 100 / 0.3.
        print("scale after 100in on 0.3-length:", scale_info)
        ok2 = abs(scale_info["scale"] - (100/0.3)) < 0.01
        print("PASS scale calibration" if ok2 else "FAIL scale calibration")

        # ---- 3. Auto-guess a new line ----
        # Draw a second line. Its dimension should be pre-filled from scale.
        d.execute_script("const T=window.__test; T.ui.snapEnabled=false; T.render();")
        time.sleep(0.3)
        draw_line(d, 0.2, 0.5, 0.5, 0.5, 42)
        lines = get_lines(d)
        # Line 2 drawn length 0.3, guessed = 0.3 * scale = 100.
        print("line2 guessed:", lines[1]["feet"], lines[1]["inches"], lines[1]["sixteenths"])
        ok3 = lines[1]["feet"] == 8 and lines[1]["inches"] == 4
        print("PASS auto-guess" if ok3 else "FAIL auto-guess")
        try:
            d.execute_script('document.querySelector(".sheet-minimal [data-action=closesheet]").click()')
            time.sleep(0.3)
        except Exception:
            pass

        # ---- 4. Snap-to-length (plain line) ----
        # Recalibrate: set line1 to 100 (already done). Now set line2 to 200".
        # Its drawn length should double from 0.3 to 0.6.
        d.execute_script("""
            const T = window.__test;
            const sk = T.state.jobs[0].sketches[0];
            const l = sk.lines[1];
            T.snapLineToLength(l, 200);
        """)
        lines = get_lines(d)
        print("line2 after snap to 200:", lines[1]["start"], lines[1]["end"])
        ok4 = abs((lines[1]["end"]["x"] - lines[1]["start"]["x"]) - 0.6) < 0.01
        print("PASS snap-to-length" if ok4 else "FAIL snap-to-length")

        # ---- 5. Rectangle as one object ----
        # Create a rectangle via the rect mode.
        d.execute_script("const T=window.__test; T.ui.rectMode=true; T.render();")
        time.sleep(0.3)
        draw_line(d, 0.2, 0.2, 0.6, 0.5, 43)
        lines = get_lines(d)
        rects = get_rects(d)
        print("after rect draw: lines count =", len(lines), "rects =", len(rects))
        # The 4 new lines (indices 2-5) should share a groupId.
        group = lines[2]["groupId"]
        ok5 = len(rects) == 1 and all(l["groupId"] == group for l in lines[2:6]) and rects[0]["lineIds"] and len(rects[0]["lineIds"]) == 4
        print("PASS rect grouped" if ok5 else "FAIL rect grouped")
        # Copy the whole rect.
        d.execute_script("const T=window.__test; T.ui.rectMode=false; T.render();")
        time.sleep(0.3)
        # Copy via copyRect directly.
        d.execute_script("""
            const T = window.__test;
            const sk = T.state.jobs[0].sketches[0];
            const rect = sk.rects[0];
            T.copyRect(sk, rect, { x: 0.1, y: 0 });
        """)
        rects = get_rects(d)
        lines = get_lines(d)
        print("after copy: rects =", len(rects), "lines =", len(lines))
        ok6 = len(rects) == 2 and len(lines) == 10
        print("PASS rect copy whole" if ok6 else "FAIL rect copy whole")

        # ---- 6. Rect width+height set all 4 sides ----
        # Resize the first rect to 30 (w) x 70 (h).
        d.execute_script("""
            const T = window.__test;
            const sk = T.state.jobs[0].sketches[0];
            const rect = sk.rects[0];
            T.resizeRectToDimensions(sk, rect, 30, 70);
        """)
        rects = get_rects(d)
        lines = get_lines(d)
        r0 = rects[0]
        print("rect0 after resize:", {"w": r0["widthInches"], "h": r0["heightInches"]})
        ok7 = abs(r0["widthInches"] - 30) < 0.01 and abs(r0["heightInches"] - 70) < 0.01
        print("PASS rect 2-number entry" if ok7 else "FAIL rect 2-number entry")

        # ---- 7. Explode ----
        d.execute_script("""
            const T = window.__test;
            const sk = T.state.jobs[0].sketches[0];
            const rect = sk.rects[0];
            T.explodeRect(sk, rect);
        """)
        rects = get_rects(d)
        lines = get_lines(d)
        groups = set(l["groupId"] for l in lines if l["groupId"])
        print("after explode: rects =", len(rects), "lines =", len(lines), "groups =", groups)
        # Rect 0 was exploded (its 4 lines now ungrouped). Rect 1 (the copy)
        # remains as one group of 4 lines.
        ok8 = len(rects) == 1 and len(groups) == 1
        print("PASS rect explode" if ok8 else "FAIL rect explode")

        if ok1 and ok2 and ok3 and ok4 and ok5 and ok6 and ok7 and ok8:
            print("ALL SCALE+RECT TESTS PASSED")
        else:
            print("SOME SCALE+RECT TESTS FAILED")
    finally:
        d.quit()

if __name__ == "__main__":
    main()