(function () {
    'use strict';

    var cs = new CSInterface();

    // ─── Licensing ───────────────────────────────────────────────────────
    var extPath = cs.getSystemPath(SystemPath.EXTENSION).replace(/\\/g, '/');
    var _require = (typeof require === 'function') ? require :
                   (typeof window.require === 'function') ? window.require : null;
    if (!_require) {
        document.body.innerHTML = '<p style="color:#f66;padding:12px;font-size:11px">Type ReAlign requires --enable-nodejs.<br>Check manifest CEFCommandLine flags.</p>';
        return;
    }
    var AESP = _require(extPath + '/client/license/aesp.umd.js');
    var isLicensed = false;
    var aesp = new AESP({
        productVersion: '1.1.1',
        aboutText: 'Change type alignment, keep type placement.\n\nby [Adam Sacks](https://aescripts.com/authors/adam-sacks/)',
        // Register Settings through aesp's flyout so it doesn't fight aesp's own menu.
        // Calling cs.setPanelFlyoutMenu directly races aesp's async registration and gets
        // clobbered on Windows. customFlyoutMenuEntries keeps our "Settings" label on both.
        customFlyoutMenuEntries: {
            xml: '<MenuItem Id="settings" Label="Settings" />',
            callback: function (event) {
                if (event && event.data && event.data.menuId === 'settings') openSettings();
            }
        }
    }, function (isValidLicense) {
        isLicensed = isValidLicense;
    });

    // ─── State ───────────────────────────────────────────────────────────
    var lockAnchor  = false;   // Lock Anchor toggle — managed in JS
    var centerMode  = 'optical';
    var currentJust = null;    // "left"|"center"|"right"|"mixed"|null

    // ─── Elements ────────────────────────────────────────────────────────
    var btnLeft    = document.getElementById('btn-left');
    var btnCenter  = document.getElementById('btn-center');
    var btnRight   = document.getElementById('btn-right');
    var btnLock    = document.getElementById('btn-lock');
    var btnReset   = document.getElementById('btn-reset');
    var btnHAlign  = document.getElementById('btn-halign');
    var btnVAlign  = document.getElementById('btn-valign');
    var ctxMenu    = document.getElementById('ctx-menu');
    var ctxMath    = document.getElementById('ctx-mathematical');
    var ctxOptical = document.getElementById('ctx-optical');
    var tooltip    = document.getElementById('tooltip');

    var alignGroup  = document.getElementById('align-group');
    var anchorGroup = document.getElementById('anchor-group');
    var centerGroup = document.getElementById('center-group');
    var panel       = document.getElementById('panel');

    // ─── Layout constants (px) — match ScriptUI values ───────────────────
    var BTN_W        = 23;
    var BTN_H        = 23;
    var BTN_GAP_INNER = 3;   // gap between align buttons (CSS gap varies per group)
    var GROUP_GAP    = 8;    // gap between groups
    var ROW_GAP      = 4;    // vertical gap between rows
    var PANEL_PAD    = 8;    // padding around the content area
    var TOP_OFFSET   = 7;   // extra space below panel title bar
    var LEFT_PAD     = 5;   // left margin for button groups

    // Natural widths of each group
    var AW    = 22 * 3 + BTN_GAP_INNER * 2;   // 72px (align buttons are 22px wide, 3px gap)
    var ANCW  = BTN_W * 2 + 1;                // 47px (1px gap)
    var CENTW = BTN_W * 2 + 1;                // 47px (1px gap)

    // ─── Polling (with the selsync coordinator) ────────────────────────────
    // selsync (shared coordinator, loaded before this file) owns the LIVE background sync:
    // across the cohort ONE panel polls AE and broadcasts; this panel re-reads + renders via
    // its onChange (readAndRender) on a subscribed-segment change. There is NO local timer —
    // Type ReAlign has no in-panel speed-up (it's the text tool). Hover-peek does a single
    // read when the cursor ENTERS the panel and clears the highlight when it leaves.
    var pollBusy = false;
    var _pollEnabled = true;    // live-poll setting (false = hover-peek)
    var _pollActive  = false;   // reads may paint now (guards a read in-flight after leave)

    // The coordinator's fingerprint is the change-trigger, so we read the heavy justification
    // directly — the old two-tier tr_getSelKey cheap-key is superseded by the fingerprint.
    function readAndRender() {
        if (pollBusy || !_pollActive) return;
        pollBusy = true;
        cs.evalScript('tr_getJustification()', function (result) {
            pollBusy = false;
            if (!_pollActive) return;   // cursor left mid-read (hover-peek)
            if (!result || result === 'EvalScript error.') return;
            try {
                var state = JSON.parse(result);
                if (state.just !== currentJust) {
                    currentJust = state.just;
                    syncJustButtons();
                }
            } catch (e) {}
        });
    }

    function clearJust() {
        currentJust = null;
        syncJustButtons();
    }

    function refreshPoll() {
        selsync.setActive(_pollEnabled);   // live -> join election; hover-peek -> resign + opt out
        _pollActive = _pollEnabled;
        if (!_pollEnabled) clearJust();    // hover-peek: clear (a read fires on mouse-enter)
    }

    function applyPollSetting(on) {
        _pollEnabled = on;
        refreshPoll();
    }

    // Hover-peek only: one read when the cursor enters the panel, clear when it leaves.
    // Live mode ignores the cursor (selsync drives it).
    document.body.addEventListener('mouseenter', function () {
        if (_pollEnabled) return;
        _pollActive = true;
        readAndRender();
    });
    document.body.addEventListener('mouseleave', function () {
        if (_pollEnabled) return;
        _pollActive = false;   // drop any in-flight read so it can't paint after we clear
        clearJust();
    });

    // ─── Button state sync ───────────────────────────────────────────────
    function syncJustButtons() {
        btnLeft.classList.toggle('active',   currentJust === 'left');
        btnCenter.classList.toggle('active', currentJust === 'center');
        btnRight.classList.toggle('active',  currentJust === 'right');
    }

    function syncLockButton() {
        btnLock.classList.toggle('toggle-on', lockAnchor);
    }

    function syncContextMenu() {
        ctxMath.classList.toggle('active',    centerMode === 'mathematical');
        ctxOptical.classList.toggle('active', centerMode === 'optical');
    }

    // ─── Layout ──────────────────────────────────────────────────────────
    function place(el, x, y, w, h) {
        el.style.left   = x + 'px';
        el.style.top    = y + 'px';
        el.style.width  = w + 'px';
        el.style.height = h + 'px';
    }

    function relayout() {
        var availW = panel.clientWidth - LEFT_PAD * 2;
        if (availW <= 0) return;

        var h;

        if (availW >= AW + GROUP_GAP + ANCW + GROUP_GAP + CENTW) {
            // Row 1: align + anchor + center
            place(alignGroup,  LEFT_PAD,                                         TOP_OFFSET, AW,    BTN_H);
            place(anchorGroup, LEFT_PAD + AW + GROUP_GAP,                        TOP_OFFSET, ANCW,  BTN_H);
            place(centerGroup, LEFT_PAD + AW + GROUP_GAP + ANCW + GROUP_GAP,     TOP_OFFSET, CENTW, BTN_H);
            h = BTN_H;
        } else if (availW >= ANCW + GROUP_GAP + CENTW) {
            // Row 1: align  Row 2: anchor + center
            place(alignGroup,  LEFT_PAD,                    TOP_OFFSET,                    AW,    BTN_H);
            place(anchorGroup, LEFT_PAD,                    TOP_OFFSET + BTN_H + ROW_GAP,  ANCW,  BTN_H);
            place(centerGroup, LEFT_PAD + ANCW + GROUP_GAP, TOP_OFFSET + BTN_H + ROW_GAP,  CENTW, BTN_H);
            h = BTN_H * 2 + ROW_GAP;
        } else {
            // Row 1: align  Row 2: anchor  Row 3: center
            place(alignGroup,  LEFT_PAD, TOP_OFFSET,                          AW,    BTN_H);
            place(anchorGroup, LEFT_PAD, TOP_OFFSET + BTN_H + ROW_GAP,        ANCW,  BTN_H);
            place(centerGroup, LEFT_PAD, TOP_OFFSET + (BTN_H + ROW_GAP) * 2,  CENTW, BTN_H);
            h = BTN_H * 3 + ROW_GAP * 2;
        }

        var totalH = TOP_OFFSET + h + PANEL_PAD * 2;
        panel.style.height = totalH + 'px';
    }

    // ─── Context menu ────────────────────────────────────────────────────
    function showContextMenu(x, y) {
        syncContextMenu();
        ctxMenu.style.left = x + 'px';
        ctxMenu.style.top  = y + 'px';
        ctxMenu.classList.add('visible');
        var rect = ctxMenu.getBoundingClientRect();
        var docW = document.documentElement.clientWidth;
        var docH = document.documentElement.clientHeight;
        if (rect.right  > docW) ctxMenu.style.left = Math.max(0, x - rect.width)  + 'px';
        if (rect.bottom > docH) ctxMenu.style.top  = Math.max(0, y - rect.height) + 'px';
    }

    function hideContextMenu() {
        ctxMenu.classList.remove('visible');
    }

    // ─── Button handlers ─────────────────────────────────────────────────
    function doAlign(justName) {
        cs.evalScript('tr_doAlign("' + justName + '",' + (lockAnchor ? 1 : 0) + ',' + (isLicensed ? 1 : 0) + ')', function (result) {
            if (result === 'unlicensed') { aesp.openDialogOnPage('license'); return; }
            if (result && result !== 'EvalScript error.') {
                // Same layers, different justification — the selsync fingerprint won't change,
                // so re-read directly. (Cursor is over the panel to click, so _pollActive is set.)
                currentJust = null;
                pollBusy = false;
                readAndRender();
            }
        });
    }

    btnLeft.addEventListener('click',   function () { doAlign('left'); });
    btnCenter.addEventListener('click', function () { doAlign('center'); });
    btnRight.addEventListener('click',  function () { doAlign('right'); });

    btnLock.addEventListener('click', function () {
        if (!isLicensed) { aesp.openDialogOnPage('license'); return; }
        lockAnchor = !lockAnchor;
        syncLockButton();
    });

    btnReset.addEventListener('click', function () {
        if (!isLicensed) { aesp.openDialogOnPage('license'); return; }
        cs.evalScript('tr_doResetAnchor()', function () {});
    });

    btnHAlign.addEventListener('click', function () {
        var modeIndex = centerMode === 'optical' ? 1 : 0;
        cs.evalScript('tr_doAlignToComp(0,' + modeIndex + ',' + (isLicensed ? 1 : 0) + ')', function (result) {
            if (result === 'unlicensed') aesp.openDialogOnPage('license');
        });
    });

    btnVAlign.addEventListener('click', function () {
        var modeIndex = centerMode === 'optical' ? 1 : 0;
        cs.evalScript('tr_doAlignToComp(1,' + modeIndex + ',' + (isLicensed ? 1 : 0) + ')', function (result) {
            if (result === 'unlicensed') aesp.openDialogOnPage('license');
        });
    });

    btnHAlign.addEventListener('contextmenu', function (e) {
        e.preventDefault();
        showContextMenu(e.clientX, e.clientY);
    });

    btnVAlign.addEventListener('contextmenu', function (e) {
        e.preventDefault();
        showContextMenu(e.clientX, e.clientY);
    });

    ctxMath.addEventListener('click', function (e) {
        e.stopPropagation();
        centerMode = 'mathematical';
        cs.evalScript('tr_setCenterMode(0)', function () {});
        hideContextMenu();
        syncContextMenu();
    });

    ctxOptical.addEventListener('click', function (e) {
        e.stopPropagation();
        centerMode = 'optical';
        cs.evalScript('tr_setCenterMode(1)', function () {});
        hideContextMenu();
        syncContextMenu();
    });

    document.addEventListener('mousedown', function (e) {
        if (ctxMenu.classList.contains('visible') &&
            !ctxMenu.contains(e.target) && e.target !== btnHAlign && e.target !== btnVAlign)
            hideContextMenu();
    });

    document.addEventListener('click', function (e) {
        if (!ctxMenu.contains(e.target) && e.target !== btnHAlign && e.target !== btnVAlign)
            hideContextMenu();
    });

    // ─── Tooltip ─────────────────────────────────────────────────────────
    var tooltipTimer = null;
    var mouseX = 0, mouseY = 0;

    document.addEventListener('mousemove', function (e) {
        mouseX = e.clientX;
        mouseY = e.clientY;
    });

    document.addEventListener('mouseover', function (e) {
        var el = e.target;
        while (el && el !== document.body) {
            if (el.getAttribute('data-tip')) {
                var tip = el.getAttribute('data-tip');
                clearTimeout(tooltipTimer);
                tooltipTimer = setTimeout(function () {
                    tooltip.textContent = tip;
                    tooltip.style.display = 'block';
                    var tw = tooltip.offsetWidth, th = tooltip.offsetHeight;
                    var docW = document.documentElement.clientWidth;
                    var docH = document.documentElement.clientHeight;
                    var tx = mouseX + 12, ty = mouseY + 16;
                    if (tx + tw > docW) tx = mouseX - tw - 4;
                    if (ty + th > docH) ty = mouseY - th - 4;
                    tx = Math.max(4, tx);
                    ty = Math.max(4, ty);
                    tooltip.style.left = tx + 'px';
                    tooltip.style.top  = ty + 'px';
                }, 600);
                return;
            }
            el = el.parentElement;
        }
        clearTimeout(tooltipTimer);
        tooltip.style.display = 'none';
    });

    document.addEventListener('mouseout', function (e) {
        var rel = e.relatedTarget;
        while (rel && rel !== document.body) {
            if (rel.getAttribute('data-tip')) return;
            rel = rel.parentElement;
        }
        clearTimeout(tooltipTimer);
        tooltip.style.display = 'none';
    });

    document.addEventListener('mousedown', function () {
        clearTimeout(tooltipTimer);
        tooltip.style.display = 'none';
    });

    // ─── Init ────────────────────────────────────────────────────────────
    window.__trPollTimer = window.__trPollTimer || null;
    if (window.__trPollTimer) { clearInterval(window.__trPollTimer); window.__trPollTimer = null; }

    // Settings dialog (wired into the aesp flyout via customFlyoutMenuEntries).
    // aesp owns the panel flyout and already injects "About & License…", so we
    // only contribute this one item. Don't call cs.setPanelFlyoutMenu here.
    function openSettings() {
        cs.evalScript('tr_openSettingsDialog()', function () {
            cs.evalScript('tr_getSettingsJSON()', function (result) {
                var live = true;
                try { var s = JSON.parse(result); if (typeof s.livePoll === 'boolean') live = s.livePoll; } catch (e) {}
                applyPollSetting(live);
            });
        });
    }

    // Register with the shared selsync coordinator. cap 1 = {comp, ids, frame}: justification
    // can be keyframed, so it re-reads on a time change too. onChange -> readAndRender.
    selsync.register({
        csInterface: cs,
        bundleId: 'com.adamsacks.typerealign',
        cap: 1,
        subscribes: ['comp', 'ids', 'frame'],
        onChange: readAndRender
    });

    // Load center mode + poll setting from prefs, then start
    cs.evalScript('tr_getCenterMode()', function (result) {
        if (result && result !== 'EvalScript error.') {
            centerMode = result;
        }
        syncContextMenu();
        cs.evalScript('tr_getSettingsJSON()', function (r2) {
            var live = true;
            try { var s = JSON.parse(r2); if (typeof s.livePoll === 'boolean') live = s.livePoll; } catch (e) {}
            applyPollSetting(live);
        });
    });

    // Responsive layout
    relayout();
    window.addEventListener('resize', relayout);

    if (window.ResizeObserver) {
        new ResizeObserver(relayout).observe(panel);
    }

    // DEV: hot-reload host.jsx — gates on .debug file, which is removed before packaging
    (function() {
        var extPath = cs.getSystemPath(SystemPath.EXTENSION);
        fetch(extPath + '/.debug')
            .then(function(r) {
                if (!r.ok) return;
                var hostPath = extPath + '/host/host.jsx';
                var lastMod = null;
                setInterval(function() {
                    cs.evalScript(
                        '(function(){ var f=new File("' + hostPath + '"); var m=f.modified; return m?m.getTime():0; })()',
                        function(result) {
                            if (lastMod === null) { lastMod = result; return; }
                            if (result !== lastMod) {
                                lastMod = result;
                                cs.evalScript('$.evalFile("' + hostPath + '")');
                            }
                        }
                    );
                }, 700);
            })
            .catch(function() {});
    }());

}());
