from __future__ import annotations

import subprocess
import textwrap
from pathlib import Path


def test_dashboard_overlay_cleans_timers_and_ignores_late_fetches():
    plugin_root = Path(__file__).resolve().parents[1]
    script = textwrap.dedent(
        f"""
        (async function () {{
        const assert = require('assert');
        const fs = require('fs');
        const vm = require('vm');

        const timers = new Map();
        let nextTimer = 1;
        const events = [];
        const cleanups = [];
        let registeredComponent = null;
        let stateValue = {{ enabled: true, mode: 'idle', mood: 'calm', name: 'Signal', revision: 1 }};
        let errorWrites = 0;
        let stateWrites = 0;

        function makeElement(type, props, ...children) {{
          return {{ type, props: props || {{}}, children }};
        }}
        const React = {{
          createElement: makeElement,
          useState(initial) {{
            if (initial === null) {{
              const setter = function (value) {{ stateWrites += 1; stateValue = value; }};
              return [stateValue, setter];
            }}
            const setter = function () {{ errorWrites += 1; }};
            return [initial, setter];
          }},
          useRef(initial) {{ return {{ current: initial }}; }},
          useEffect(fn) {{
            const cleanup = fn();
            if (cleanup) cleanups.push(cleanup);
          }},
        }};
        const windowObj = {{
          __HERMES_PLUGIN_SDK__: {{
            React,
            hooks: React,
            authedFetch(url, options) {{
              if (options && options.method === 'POST') {{
                events.push(JSON.parse(options.body).type);
              }}
              return Promise.resolve({{
                ok: true,
                json: () => Promise.resolve({{ ok: true, state: stateValue }}),
              }});
            }},
          }},
          __HERMES_PLUGINS__: {{
            registerSlot(plugin, slot, component) {{ registeredComponent = component; }}
          }},
          setTimeout(fn, delay) {{
            const id = nextTimer++;
            timers.set(id, {{ fn, delay }});
            return id;
          }},
          clearTimeout(id) {{ timers.delete(id); }},
          setInterval(fn, delay) {{
            const id = nextTimer++;
            timers.set(id, {{ fn, delay, interval: true }});
            return id;
          }},
          clearInterval(id) {{ timers.delete(id); }},
        }};
        const context = {{ window: windowObj, fetch: windowObj.__HERMES_PLUGIN_SDK__.authedFetch, console }};
        vm.createContext(context);
        vm.runInContext(fs.readFileSync({str(plugin_root / 'dashboard/dist/index.js')!r}, 'utf8'), context);
        assert.equal(typeof registeredComponent, 'function');

        const tree = registeredComponent();
        const avatar = tree.children[1];
        avatar.props.onMouseEnter();
        avatar.props.onMouseEnter();
        const hoverTimers = Array.from(timers.values()).filter(t => t.delay === 1000);
        assert.equal(hoverTimers.length, 1, 'duplicate hover enters should leave one hover timer');

        for (const cleanup of cleanups) cleanup();
        assert.equal(timers.size, 0, 'unmount should clear interval, hover, and transition timers');

        avatar.props.onClick();
        await Promise.resolve();
        assert.deepEqual(events, ['click']);
        assert.equal(stateWrites, 0, 'late fetch resolution after unmount must not write state');
        assert.equal(errorWrites, 0, 'late fetch resolution after unmount must not write errors');
        }})();
        """
    )
    result = subprocess.run(["node", "-e", script], text=True, capture_output=True, check=False)
    assert result.returncode == 0, result.stdout + result.stderr
