const dbus = require('dbus-next');
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);

class SwaiRegistry {
    constructor() {
        this.bus = null;
        this.proxy = null;
        this.available = false; // True if either DBus or xprop backend is ready
        this.useXprop = false;  // Flag to denote fallback mode
        this.registeredTitles = new Set();
        this.initializationPromise = null;
        this.initializationComplete = false;
    }

    // Returns a promise that resolves when initialization is complete
    async initialize() {
        if (this.initializationPromise) {
            return this.initializationPromise;
        }
        
        this.initializationPromise = this.initializeDBus().finally(() => {
            this.initializationComplete = true;
        });
        return this.initializationPromise;
    }

    async initializeDBus() {
        try {
            this.bus = dbus.sessionBus();
            
            // Check if the Mutter SwaiRegistry interface is available
            await this.checkInterfaceAvailability();
            
            if (this.available) {
                console.log('Mutter SwaiRegistry interface is available');
                this.proxy = await this.bus.getProxyObject('org.gnome.Mutter.SwaiRegistry', '/org/gnome/Mutter/SwaiRegistry');
                this.swaiRegistry = this.proxy.getInterface('org.gnome.Mutter.SwaiRegistry');
                
                // Test the interface with a harmless call
                console.log('Testing SwaiRegistry interface...');
                await this.testInterface();
            } else {
                console.log('Mutter SwaiRegistry interface not available, DBus customization disabled');
            }
        } catch (error) {
            console.warn('Failed to initialize DBus connection for SwaiRegistry:', error.message);
            this.available = false;
        }
    }
    // Depricated: WM_Class is changed by callig setName and setDesktopName before new base window
    async initializeXprop() {
        try {
            await execPromise('which xprop');
            console.log('xprop is available, WM_CLASS customization falling back to xprop');
            this.available = true;
            this.useXprop = true;
        } catch (error) {
            console.warn('Failed to find xprop on the system:', error.message);
            this.available = false;
        }
    }

    async checkInterfaceAvailability() {
        console.log('Checking for Mutter SwaiRegistry interface...');
        
        try {
            // Method 1: Check if the service name exists on the bus
            const busProxy = await this.bus.getProxyObject('org.freedesktop.DBus', '/org/freedesktop/DBus');
            const dbusInterface = busProxy.getInterface('org.freedesktop.DBus');
            
            console.log('Checking if org.gnome.Mutter.SwaiRegistry service exists...');
            const names = await dbusInterface.ListNames();
            const hasSwaiRegistryService = names.includes('org.gnome.Mutter.SwaiRegistry');
            console.log(`org.gnome.Mutter.SwaiRegistry service found: ${hasSwaiRegistryService}`);
            
            if (!hasSwaiRegistryService) {
                console.log('org.gnome.Mutter.SwaiRegistry service not found on DBus');
                this.available = false;
                return;
            }

            // Method 2: Try to get the proxy object and interface directly
            console.log('Attempting to get SwaiRegistry proxy object...');
            const proxy = await this.bus.getProxyObject('org.gnome.Mutter.SwaiRegistry', '/org/gnome/Mutter/SwaiRegistry');
            
            console.log('Attempting to get SwaiRegistry interface...');
            const swaiInterface = proxy.getInterface('org.gnome.Mutter.SwaiRegistry');
            
            // Method 3: Verify the interface has the expected methods
            console.log('Verifying interface methods...');
            if (typeof swaiInterface.RegisterTitle === 'function') {
                console.log('SwaiRegistry interface methods verified');
                this.available = true;
                return;
            } else {
                console.log('SwaiRegistry interface methods not found');
                console.log('RegisterTitle type:', typeof swaiInterface.RegisterTitle);
                this.available = false;
                return;
            }
            
        } catch (error) {
            console.log('Error checking SwaiRegistry interface:', error.message);
            this.available = false;
        }
    }

async registerTitle(internalTitle, displayTitle) {
        // Ensure initialization is complete before attempting registration
        if (!this.initializationComplete) {
            await this.initialize();
        }

        if (!this.available || !this.swaiRegistry) {
            console.log(`Cannot register title ${internalTitle} - DBus interface not available`);
            return false;
        }

        try {
            await this.swaiRegistry.RegisterTitle(internalTitle, displayTitle);
            this.registeredTitles.add(internalTitle);
            console.log(`Registered title: ${internalTitle} -> ${displayTitle}`);
            return true;
        } catch (error) {
            console.error(`Failed to register title ${internalTitle}:`, error.message);
            return false;
        }
    }

    async testInterface() {
        try {
            // Test the interface by registering a test title
            const testTitle = 'swai_test_interface';
            await this.swaiRegistry.RegisterTitle(testTitle, 'SWAI Test');
            console.log('SwaiRegistry interface test successful');
            return true;
        } catch (error) {
            console.log('SwaiRegistry interface test failed:', error.message);
            this.available = false;
            return false;
        }
    }

    async updateTitle(internalTitle, displayTitle, browserWindow = null) {
        // For the Mutter patch, updating is the same as registering
        // (RegisterTitle can be called multiple times for the same internal title)
        return await this.registerTitle(internalTitle, displayTitle, browserWindow);
    }

    async reclassifyWindow(internalTitle, displayTitle, browserWindow) {
        if (!browserWindow || browserWindow.isDestroyed()) return false;
        if (!this.initializationComplete) await this.initialize();

        browserWindow.title = internalTitle;

        if (this.isAvailable()) {
            // Electron updates the compositor-facing title asynchronously.
            // Mutter's RegisterTitle matches on that title, so retry briefly
            // until Mutter has observed it. Calls after the first match are
            // harmless because Mutter has already replaced the internal title.
            for (const delay of [25, 75, 150]) {
                await new Promise((resolve) => setTimeout(resolve, delay));
                await this.registerTitle(internalTitle, displayTitle, browserWindow);
            }
            return true;
        }

        if (process.platform === "linux") {
            await this._registerTitleXprop(internalTitle, displayTitle, browserWindow);
            return true;
        }

        return false;
    }

    async unregisterTitle(internalTitle) {
        // UnregisterTitle functionality has been removed from the DBus interface
        // This method is kept for compatibility but does nothing
        this.registeredTitles.delete(internalTitle);
        return true;
    }

    async cleanup() {
        try {
            // Note: UnregisterTitle functionality has been removed from the DBus interface
            // We can only clear our local tracking
            this.registeredTitles.clear();
            
            if (this.bus) {
                this.bus.disconnect();
            }
        } catch (error) {
            console.warn('Error during SwaiRegistry cleanup:', error.message);
        }
    }

    isAvailable() {
        return this.initializationComplete && this.available;
    }

    /**
     * Generate a unique internal title for window registration
     * This will be set as the actual window title in Electron
     * Each window needs a unique internal title for proper registration
     */
    generateInternalTitle(appId, windowId) {
        return `io.stillhq.swai.${appId}.${windowId}`;
    }

    async _registerTitleXprop(internalTitle, displayTitle, browserWindow) {
        if (!browserWindow || browserWindow.isDestroyed()) return false;
        setTimeout(() => {
            const handle = browserWindow.getNativeWindowHandle();
            if (handle && handle.length >= 4) {
                const xid = handle.readUInt32LE(0);
                this._applyXpropById(xid, internalTitle, displayTitle);
            }
        }, 150);
        this.registeredTitles.add(internalTitle);
        return true;
    }

    _getWmClass(internalTitle) {
        return internalTitle?.replace(/\.\d+$/, '');
    }

    async _applyXpropById(xid, internalTitle, displayTitle) {
        const cls = this._getWmClass(internalTitle).replace(/"/g, '\\"');
        const tit = displayTitle.replace(/"/g, '\\"');
        const cmd = `xprop -id ${xid} -f WM_CLASS 8s -set WM_CLASS "${cls}" && xprop -id ${xid} -f _NET_WM_NAME 8s -set _NET_WM_NAME "${tit}" && xprop -id ${xid} -f WM_NAME 8s -set WM_NAME "${tit}"`;
        exec(cmd, (err) => { if (err) console.error(`[SWAI] xprop failed: ${err.message}`); });
    }
}

module.exports = SwaiRegistry; 
