pragma Singleton
pragma ComponentBehavior: Bound

import QtQuick
import QtCore
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services

Singleton {
    id: root
    readonly property var log: Log.scoped("NotepadStorageService")

    property int refCount: 0

    readonly property string baseDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell")
    readonly property string filesDir: baseDir + "/notepad-files"
    readonly property string metadataPath: baseDir + "/notepad-session.json"

    property var tabs: []
    property int currentTabIndex: 0
    property var tabsBeingCreated: ({})
    property bool metadataLoaded: false

    // Shared live edit state across slideout and popout surfaces.
    property var sessionBuffers: ({})
    property int sessionBufferRevision: 0

    function setSessionBuffer(tabId, content, baseline) {
        if (tabId === undefined || tabId === null || tabId < 0)
            return;
        var next = Object.assign({}, sessionBuffers);
        if (content !== baseline) {
            next[tabId] = {
                content: content,
                baseline: baseline
            };
        } else {
            delete next[tabId];
        }
        sessionBuffers = next;
        sessionBufferRevision++;
    }

    function getSessionBuffer(tabId) {
        return sessionBuffers[tabId];
    }

    function clearSessionBuffer(tabId) {
        if (sessionBuffers[tabId] === undefined)
            return;
        var next = Object.assign({}, sessionBuffers);
        delete next[tabId];
        sessionBuffers = next;
        sessionBufferRevision++;
    }

    property var conflictTabId: -1
    property string conflictDiskContent: ""

    function flagConflict(tabId, diskContent) {
        conflictDiskContent = diskContent;
        conflictTabId = tabId;
    }

    function clearConflict() {
        conflictTabId = -1;
        conflictDiskContent = "";
    }

    Component.onCompleted: {
        ensureDirectories();
    }

    FileView {
        id: metadataFile
        path: root.refCount > 0 ? root.metadataPath : ""
        blockWrites: true
        atomicWrites: true

        onLoaded: {
            try {
                var data = JSON.parse(text());
                root.tabs = data.tabs || [];
                root.currentTabIndex = data.currentTabIndex || 0;
                root.metadataLoaded = true;
                root.validateTabs();
            } catch (e) {
                log.warn("Failed to parse notepad metadata:", e);
                root.createDefaultTab();
            }
        }

        onLoadFailed: {
            root.createDefaultTab();
        }
    }

    onRefCountChanged: {
        if (refCount === 1 && !metadataLoaded) {
            metadataFile.path = "";
            metadataFile.path = root.metadataPath;
        }
    }

    function ensureDirectories() {
        Proc.runCommand("", ["mkdir", "-p", root.baseDir, root.filesDir], null);
    }

    function loadMetadata() {
        metadataFile.path = "";
        metadataFile.path = root.metadataPath;
    }

    function createDefaultTab() {
        var id = Date.now();
        var filePath = "notepad-files/untitled-" + id + ".txt";
        var fullPath = baseDir + "/" + filePath;

        var newTabsBeingCreated = Object.assign({}, tabsBeingCreated);
        newTabsBeingCreated[id] = true;
        tabsBeingCreated = newTabsBeingCreated;

        root.createEmptyFile(fullPath, function () {
            root.tabs = [
                {
                    id: id,
                    title: I18n.tr("Untitled"),
                    filePath: filePath,
                    isTemporary: true,
                    lastModified: new Date().toISOString(),
                    cursorPosition: 0,
                    scrollPosition: 0
                }
            ];
            root.currentTabIndex = 0;

            var updatedTabsBeingCreated = Object.assign({}, tabsBeingCreated);
            delete updatedTabsBeingCreated[id];
            tabsBeingCreated = updatedTabsBeingCreated;
            root.saveMetadata();
        });
    }

    function saveMetadata() {
        var metadata = {
            version: 1,
            currentTabIndex: currentTabIndex,
            tabs: tabs
        };
        metadataFile.setText(JSON.stringify(metadata, null, 2));
    }

    function getTabById(tabId) {
        for (var i = 0; i < tabs.length; i++) {
            if (tabs[i].id === tabId)
                return tabs[i];
        }
        return null;
    }

    function loadTabContent(tabIndex, callback) {
        if (tabIndex < 0 || tabIndex >= tabs.length) {
            callback("");
            return;
        }

        var tab = tabs[tabIndex];
        var requestTabId = tab.id;
        var fullPath = tab.isTemporary ? baseDir + "/" + tab.filePath : tab.filePath;

        if (tabsBeingCreated[tab.id]) {
            Qt.callLater(() => {
                loadTabContent(tabIndex, callback);
            });
            return;
        }

        Proc.runCommand("", ["test", "-f", fullPath], (output, exitCode) => {
            var currentTab = root.getTabById(requestTabId);
            var currentPath = currentTab ? (currentTab.isTemporary ? baseDir + "/" + currentTab.filePath : currentTab.filePath) : "";

            if (!currentTab || currentPath !== fullPath) {
                callback("");
                return;
            }

            if (exitCode === 0) {
                tabFileLoaderComponent.createObject(root, {
                    path: fullPath,
                    callback: callback
                });
            } else {
                log.warn("Tab file does not exist:", fullPath);
                callback("");
            }
        });
    }

    function saveTabContent(tabIndex, content) {
        if (tabIndex < 0 || tabIndex >= tabs.length)
            return;
        var tab = tabs[tabIndex];
        var fullPath = tab.isTemporary ? baseDir + "/" + tab.filePath : tab.filePath;

        var saver = tabFileSaverComponent.createObject(root, {
            path: fullPath,
            content: content,
            tabIndex: tabIndex
        });
    }

    function createNewTab() {
        var id = Date.now();
        var filePath = "notepad-files/untitled-" + id + ".txt";
        var fullPath = baseDir + "/" + filePath;

        var newTab = {
            id: id,
            title: I18n.tr("Untitled"),
            filePath: filePath,
            isTemporary: true,
            lastModified: new Date().toISOString(),
            cursorPosition: 0,
            scrollPosition: 0
        };

        var newTabsBeingCreated = Object.assign({}, tabsBeingCreated);
        newTabsBeingCreated[id] = true;
        tabsBeingCreated = newTabsBeingCreated;
        createEmptyFile(fullPath, function () {
            var newTabs = tabs.slice();
            newTabs.push(newTab);
            tabs = newTabs;
            currentTabIndex = tabs.length - 1;

            var updatedTabsBeingCreated = Object.assign({}, tabsBeingCreated);
            delete updatedTabsBeingCreated[id];
            tabsBeingCreated = updatedTabsBeingCreated;
            saveMetadata();
        });

        return newTab;
    }

    function createTabForFile(path) {
        var id = Date.now();
        var fileName = path.split('/').pop();

        var newTab = {
            id: id,
            title: fileName,
            filePath: path,
            isTemporary: false,
            lastModified: new Date().toISOString(),
            cursorPosition: 0,
            scrollPosition: 0
        };

        var newTabs = tabs.slice();
        newTabs.push(newTab);
        tabs = newTabs;
        currentTabIndex = tabs.length - 1;
        saveMetadata();

        return newTab;
    }

    function closeTab(tabIndex) {
        if (tabIndex < 0 || tabIndex >= tabs.length)
            return;
        var newTabs = tabs.slice();
        var closedTabId = newTabs[tabIndex] ? newTabs[tabIndex].id : -1;
        clearSessionBuffer(closedTabId);
        if (conflictTabId === closedTabId)
            clearConflict();

        if (newTabs.length <= 1) {
            var id = Date.now();
            var filePath = "notepad-files/untitled-" + id + ".txt";

            var newTabsBeingCreated = Object.assign({}, tabsBeingCreated);
            newTabsBeingCreated[id] = true;
            tabsBeingCreated = newTabsBeingCreated;
            createEmptyFile(baseDir + "/" + filePath, function () {
                newTabs[0] = {
                    id: id,
                    title: I18n.tr("Untitled"),
                    filePath: filePath,
                    isTemporary: true,
                    lastModified: new Date().toISOString(),
                    cursorPosition: 0,
                    scrollPosition: 0
                };
                currentTabIndex = 0;
                tabs = newTabs;

                var updatedTabsBeingCreated = Object.assign({}, tabsBeingCreated);
                delete updatedTabsBeingCreated[id];
                tabsBeingCreated = updatedTabsBeingCreated;
                saveMetadata();
            });
            return;
        } else {
            var tabToDelete = newTabs[tabIndex];
            if (tabToDelete && tabToDelete.isTemporary) {
                deleteFile(baseDir + "/" + tabToDelete.filePath);
            }

            newTabs.splice(tabIndex, 1);
            if (currentTabIndex >= newTabs.length) {
                currentTabIndex = newTabs.length - 1;
            } else if (currentTabIndex > tabIndex) {
                currentTabIndex -= 1;
            }
        }

        tabs = newTabs;
        saveMetadata();
    }

    function switchToTab(tabIndex) {
        if (tabIndex < 0 || tabIndex >= tabs.length)
            return;
        currentTabIndex = tabIndex;
        saveMetadata();
    }

    function reorderTab(fromIndex, toIndex) {
        if (fromIndex < 0 || fromIndex >= tabs.length || toIndex < 0 || toIndex >= tabs.length)
            return;
        if (fromIndex === toIndex)
            return;
        var newTabs = tabs.slice();
        var moved = newTabs.splice(fromIndex, 1)[0];
        newTabs.splice(toIndex, 0, moved);
        tabs = newTabs;

        if (currentTabIndex === fromIndex) {
            currentTabIndex = toIndex;
        } else if (fromIndex < currentTabIndex && toIndex >= currentTabIndex) {
            currentTabIndex--;
        } else if (fromIndex > currentTabIndex && toIndex <= currentTabIndex) {
            currentTabIndex++;
        }

        saveMetadata();
    }

    function saveTabAs(tabIndex, userPath) {
        if (tabIndex < 0 || tabIndex >= tabs.length)
            return;
        var tab = tabs[tabIndex];
        var fileName = userPath.split('/').pop();

        if (tab.isTemporary) {
            var tempPath = baseDir + "/" + tab.filePath;
            copyFile(tempPath, userPath);
            deleteFile(tempPath);
        }

        var newTabs = tabs.slice();
        newTabs[tabIndex] = Object.assign({}, tab, {
            title: fileName,
            filePath: userPath,
            isTemporary: false,
            lastModified: new Date().toISOString()
        });
        tabs = newTabs;
        saveMetadata();
    }

    function renameTab(tabIndex, newTitle) {
        if (tabIndex < 0 || tabIndex >= tabs.length)
            return;
        var trimmed = (newTitle || "").trim();
        var tab = tabs[tabIndex];
        if (trimmed.length === 0 || trimmed === tab.title)
            return;
        if (tab.isTemporary) {
            updateTabMetadata(tabIndex, {
                title: trimmed
            });
            return;
        }

        var dir = tab.filePath.substring(0, tab.filePath.lastIndexOf('/') + 1);
        var newPath = dir + trimmed;
        moveFile(tab.filePath, newPath);
        updateTabMetadata(tabIndex, {
            title: trimmed,
            filePath: newPath
        });
    }

    function updateTabMetadata(tabIndex, properties) {
        if (tabIndex < 0 || tabIndex >= tabs.length)
            return;
        var newTabs = tabs.slice();
        var updatedTab = Object.assign({}, newTabs[tabIndex], properties);
        updatedTab.lastModified = new Date().toISOString();
        newTabs[tabIndex] = updatedTab;
        tabs = newTabs;
        saveMetadata();
    }

    function validateTabs() {
        var validTabs = [];
        for (var i = 0; i < tabs.length; i++) {
            var tab = tabs[i];
            validTabs.push(tab);
        }
        tabs = validTabs;

        if (tabs.length === 0) {
            root.createDefaultTab();
        }
    }

    Component {
        id: tabFileLoaderComponent
        FileView {
            property var callback
            blockLoading: true
            preload: true

            onLoaded: {
                callback(text());
                destroy();
            }

            onLoadFailed: {
                callback("");
                destroy();
            }
        }
    }

    Component {
        id: tabFileSaverComponent
        FileView {
            property string content
            property int tabIndex
            property var creationCallback

            blockWrites: false
            atomicWrites: true

            Component.onCompleted: setText(content)

            onSaved: {
                if (tabIndex >= 0) {
                    root.updateTabMetadata(tabIndex, {});
                }
                if (creationCallback) {
                    creationCallback();
                }
                destroy();
            }

            onSaveFailed: {
                log.error("Failed to save tab content");
                if (creationCallback) {
                    creationCallback();
                }
                destroy();
            }
        }
    }

    function createEmptyFile(path, callback) {
        var cleanPath = decodeURI(path.toString());

        if (!cleanPath.startsWith("/")) {
            cleanPath = baseDir + "/" + cleanPath;
        }

        Proc.runCommand("", ["touch", cleanPath], (output, exitCode) => {
            if (callback)
                callback();
        });
    }

    function copyFile(source, destination) {
        Proc.runCommand("", ["cp", source, destination], null);
    }

    function deleteFile(path) {
        Proc.runCommand("", ["rm", "-f", path], null);
    }

    function moveFile(source, destination) {
        Proc.runCommand("", ["mv", source, destination], null);
    }
}
