"""Verify the 3-state Tags toolbar cycle: tags -> numbers -> off -> tags.

Also verify that:
- The toolbar button label reflects the current mode ("Tags" vs "Nmbrs").
- The toolbar button is .active in tags/numbers modes and inactive in off mode.
- drawSketchCanvas runs without error in all three modes.
- The dimension value is rendered on the canvas in numbers mode.
"""
import os
import sys
import time

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"

DRAW_JS = """
const canvas = document.getElementById('sketchCanvas');
canvas.setPointerCapture = function(){};
canvas.releasePointerCapture = function(){};
canvas.hasPointerCapture = function(){ return false; };
const rect = canvas.getBoundingClientRect();
const opts = { bubbles: true, cancelable: true, pointerId: 1, pointerType: 'touch', isPrimary: true };
const sx = rect.left + rect.width*0.3, sy = rect.top + rect.height*0.4;
const ex = rect.left + rect.width*0.7, ey = rect.top + rect.height*0.6;
canvas.dispatchEvent(new PointerEvent('pointerdown', Object.assign({}, opts, {clientX: sx, clientY: sy})));
for (let i = 1; i <= 5; i++) canvas.dispatchEvent(new PointerEvent('pointermove', Object.assign({}, opts, {clientX: sx+(ex-sx)*i/5, clientY: sy+(ey-sy)*i/5})));
canvas.dispatchEvent(new PointerEvent('pointerup', Object.assign({}, opts, {clientX: ex, clientY: ey})));
return 'drawn';
"""

# Sample a small box centered on the line midpoint (in canvas pixel coords) and
# return the count of dark pixels (text fill) and white pixels (label bg).
# Returns {dark, white, total}.
SAMPLE_LABEL_AREA_JS = """
const T = window.__test;
const sk = T.state.jobs[0].sketches[0];
const line = sk.lines[0];
const canvas = document.getElementById('sketchCanvas');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const mx = Math.round(((line.start.x + line.end.x) / 2) * W);
const my = Math.round(((line.start.y + line.end.y) / 2) * H);
const boxW = 80, boxH = 24;
const x0 = Math.max(0, mx - boxW / 2);
const y0 = Math.max(0, my - boxH);
const data = ctx.getImageData(x0, y0, boxW, boxH).data;
let dark = 0, white = 0;
for (let i = 0; i < data.length; i += 4) {
  const r = data[i], g = data[i+1], b = data[i+2];
  if (r < 80 && g < 80 && b < 80) dark++;
  else if (r > 230 && g > 230 && b > 230) white++;
}
return JSON.stringify({ dark, white, total: boxW * boxH });
"""

# Read the line value as the formatted dimension string the app would display.
GET_LINE_VALUE_JS = """
const T = window.__test;
const sk = T.state.jobs[0].sketches[0];
const line = sk.lines[0];
const v = line;
const f = v.feet || 0, i = v.inches || 0, s = v.sixteenths || 0;
let inches = i, sixteenths = s;
if (sixteenths >= 16) { inches += Math.floor(sixteenths / 16); sixteenths = sixteenths % 16; }
const fracMap = {0:'', 1:'1/16', 2:'1/8', 3:'3/16', 4:'1/4', 5:'5/16', 6:'3/8', 7:'7/16', 8:'1/2', 9:'9/16', 10:'5/8', 11:'11/16', 12:'3/4', 13:'13/16', 14:'7/8', 15:'15/16'};
const frac = fracMap[sixteenths] || '';
const inchStr = frac ? (inches + ' ' + frac) : String(inches);
if (f > 0) return f + "'-" + inchStr + '"';
return inchStr + '"';
"""


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:
        d.get(BASE + "&_=" + str(int(time.time())))
        time.sleep(2)

        # Set up: job + sketch + photo + first line with a known value.
        d.find_element(By.CSS_SELECTOR, '[data-action="newjob"]').click()
        time.sleep(0.5)
        d.find_element(By.CSS_SELECTOR, '[data-action="opensketch"]').click()
        time.sleep(0.5)
        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 = 400; c.height = 300;
            const ctx = c.getContext('2d');
            ctx.fillStyle = '#cccccc'; ctx.fillRect(0,0,400,300);
            sk.photo = { dataUrl: c.toDataURL('image/jpeg', 0.8), width: 400, height: 300 };
            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.5)

        # Draw a line.
        print("draw:", d.execute_script(DRAW_JS))
        time.sleep(0.5)
        # Close the auto-opened dimension sheet.
        d.execute_script('document.querySelector(".sheet-minimal [data-action=closesheet]").click()')
        time.sleep(0.4)

        # Set a known value on the line so we can check what gets drawn.
        # setLineValue takes decimal inches: 8'4 1/8" = 8*12 + 4 + 2/16 = 100.125
        d.execute_script("""
            const T = window.__test;
            const sk = T.state.jobs[0].sketches[0];
            T.setLineValue(sk.lines[0], 100.125);
            T.saveState();
        """)
        time.sleep(0.4)

        expected_dim = d.execute_script(GET_LINE_VALUE_JS)
        print("expected dimension string:", expected_dim)
        assert expected_dim == "8'-4 1/8\"", f"unexpected dim string: {expected_dim}"

        # --- 1. Default mode is "tags" ---
        mode = d.execute_script("return window.__test.ui.tagMode")
        label = d.execute_script("return document.querySelector('[data-action=\"toggle-tags\"]').textContent.trim()")
        active = d.execute_script("return document.querySelector('[data-action=\"toggle-tags\"]').classList.contains('active')")
        print(f"default: tagMode={mode!r} label={label!r} active={active}")
        assert mode == "tags", f"default mode should be 'tags', got {mode!r}"
        assert label == "Tags", f"default label should be 'Tags', got {label!r}"
        assert active, "tags button should be active in tags mode"

        # Sample canvas: should have dark pixels (tag text) in the label area.
        sample = d.execute_script(SAMPLE_LABEL_AREA_JS)
        import json
        s_tags = json.loads(sample)
        print(f"tags mode sample: dark={s_tags['dark']} white={s_tags['white']}")
        assert s_tags["dark"] > 5, "tags mode should draw dark text pixels at midpoint"

        # --- 2. Click 1: tags -> numbers ---
        d.find_element(By.CSS_SELECTOR, '[data-action="toggle-tags"]').click()
        time.sleep(0.4)
        mode = d.execute_script("return window.__test.ui.tagMode")
        label = d.execute_script("return document.querySelector('[data-action=\"toggle-tags\"]').textContent.trim()")
        active = d.execute_script("return document.querySelector('[data-action=\"toggle-tags\"]').classList.contains('active')")
        print(f"click 1: tagMode={mode!r} label={label!r} active={active}")
        assert mode == "numbers", f"after click 1, mode should be 'numbers', got {mode!r}"
        assert label == "Nmbrs", f"numbers mode label should be 'Nmbrs', got {label!r}"
        assert active, "tags button should still be active in numbers mode"

        # Sample canvas: should have dark pixels (dimension text) at midpoint.
        sample = d.execute_script(SAMPLE_LABEL_AREA_JS)
        s_num = json.loads(sample)
        print(f"numbers mode sample: dark={s_num['dark']} white={s_num['white']}")
        assert s_num["dark"] > 5, "numbers mode should draw dark text pixels at midpoint"
        assert s_num["white"] > 0, "numbers mode should draw white pill background"
        # Numbers mode draws a wider pill (more white bg) than tags mode, but
        # the text is regular weight so may have fewer dark pixels. The key
        # invariant is that numbers has a wider pill (white >= tags).
        assert s_num["white"] >= s_tags["white"], \
            f"numbers mode pill ({s_num['white']}) should be at least as wide as tags pill ({s_tags['white']})"

        # --- 3. Click 2: numbers -> off ---
        d.find_element(By.CSS_SELECTOR, '[data-action="toggle-tags"]').click()
        time.sleep(0.4)
        mode = d.execute_script("return window.__test.ui.tagMode")
        label = d.execute_script("return document.querySelector('[data-action=\"toggle-tags\"]').textContent.trim()")
        active = d.execute_script("return document.querySelector('[data-action=\"toggle-tags\"]').classList.contains('active')")
        print(f"click 2: tagMode={mode!r} label={label!r} active={active}")
        assert mode == "off", f"after click 2, mode should be 'off', got {mode!r}"
        assert label == "Tags", f"off mode label should be 'Tags', got {label!r}"
        assert not active, "tags button should NOT be active in off mode"

        # Sample canvas: should have NO white pill bg in the label area.
        # (Dark pixels in off mode are the photo + line stroke, which is fine.)
        sample = d.execute_script(SAMPLE_LABEL_AREA_JS)
        s_off = json.loads(sample)
        print(f"off mode sample: dark={s_off['dark']} white={s_off['white']}")
        assert s_off["white"] == 0, f"off mode should have no white pill bg, got {s_off['white']}"

        # --- 4. Click 3: off -> tags (wraps around) ---
        d.find_element(By.CSS_SELECTOR, '[data-action="toggle-tags"]').click()
        time.sleep(0.4)
        mode = d.execute_script("return window.__test.ui.tagMode")
        active = d.execute_script("return document.querySelector('[data-action=\"toggle-tags\"]').classList.contains('active')")
        print(f"click 3: tagMode={mode!r} active={active}")
        assert mode == "tags", f"after click 3, mode should wrap back to 'tags', got {mode!r}"
        assert active, "tags button should be active again after wrap"

        # --- 5. Drawing still works after cycling ---
        print("draw2:", d.execute_script(DRAW_JS))
        time.sleep(0.5)
        line_count = d.execute_script("return window.__test.state.jobs[0].sketches[0].lines.length")
        print("lines count:", line_count)
        assert line_count == 2, f"expected 2 lines after second draw, got {line_count}"

        print("ALL TAG-MODE TESTS PASSED")
    finally:
        d.quit()


if __name__ == "__main__":
    main()