// Nucleic Geometry — a GNOME Shell extension exposing window geometry the compositor is the only
// source of truth for (docs/LINUX_VM_SEMANTIC_AGENT.md Phase 2).
//
// Why: on Wayland a client can't know its surface's on-screen position, so AT-SPI's
// `Component.getExtents(SCREEN)` loses the window origin (it reports 0,0 — confirmed in
// docs/M_A11Y_RESULTS.md). Only Mutter knows where each window actually is. This extension runs INSIDE
// gnome-shell (which owns that truth) and re-publishes the focused window's global frame — and all
// toplevels — over a private session-bus name `org.nucleic.Geometry`. The in-guest AT-SPI agent
// (nucleic-a11y-agent) reads it and computes true global element frames as
// `window_origin + getExtents(WINDOW)` for the focused surface.
//
// This is the *consumer half* of the Newton idea (compositor as geometry source of truth), vendored and
// scoped to our controlled VM — NOT a general protocol. GNOME 45+ ESM extension.

import Gio from 'gi://Gio';
import Meta from 'gi://Meta';

const IFACE = `
<node>
  <interface name="org.nucleic.Geometry">
    <!-- The focused window's global frame rect + title. (0,0,0,0,"") when there is no focused window. -->
    <method name="GetFocusedWindowFrame">
      <arg type="i" direction="out" name="x"/>
      <arg type="i" direction="out" name="y"/>
      <arg type="i" direction="out" name="w"/>
      <arg type="i" direction="out" name="h"/>
      <arg type="s" direction="out" name="title"/>
    </method>
    <!-- All normal toplevel windows: (title, x, y, w, h) in global coords — for background-window
         correlation (Phase 3). -->
    <method name="GetWindows">
      <arg type="a(siiii)" direction="out" name="windows"/>
    </method>
  </interface>
</node>`;

export default class NucleicGeometryExtension {
    enable() {
        this._impl = Gio.DBusExportedObject.wrapJSObject(IFACE, this);
        this._impl.export(Gio.DBus.session, '/org/nucleic/Geometry');
        this._nameId = Gio.bus_own_name(
            Gio.BusType.SESSION,
            'org.nucleic.Geometry',
            Gio.BusNameOwnerFlags.NONE,
            null, null, null);
    }

    disable() {
        if (this._nameId) {
            Gio.bus_unown_name(this._nameId);
            this._nameId = 0;
        }
        if (this._impl) {
            this._impl.unexport();
            this._impl = null;
        }
    }

    // The keyboard-focused window, or — when nothing has keyboard focus (common in an automated
    // session before the agent sends any input) — the most-recently-used normal window. This is the
    // window the agent is about to dump/act on.
    _activeWindow() {
        const focused = global.display.get_focus_window();
        if (focused)
            return focused;
        const mru = global.display.get_tab_list(Meta.TabList.NORMAL, null);
        return mru && mru.length ? mru[0] : null;
    }

    GetFocusedWindowFrame() {
        const win = this._activeWindow();
        if (!win)
            return [0, 0, 0, 0, ''];
        const r = win.get_frame_rect();
        return [r.x, r.y, r.width, r.height, win.get_title() || ''];
    }

    GetWindows() {
        const out = [];
        for (const actor of global.get_window_actors()) {
            const w = actor.meta_window;
            if (!w || w.is_override_redirect())
                continue;
            const r = w.get_frame_rect();
            out.push([w.get_title() || '', r.x, r.y, r.width, r.height]);
        }
        return out;
    }
}
