pragma Singleton
pragma ComponentBehavior: Bound

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

Singleton {
    id: root

    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

    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) {
                console.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() {
        mkdirProcess.running = true
    }

    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
        }

        var fileChecker = fileExistsComponent.createObject(root, {
            path: fullPath,
            callback: (exists) => {
                var currentTab = root.getTabById(requestTabId)
                var currentPath = currentTab
                    ? (currentTab.isTemporary ? baseDir + "/" + currentTab.filePath : currentTab.filePath)
                    : ""

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

                if (exists) {
                    var loader = tabFileLoaderComponent.createObject(root, {
                        path: fullPath,
                        callback: callback
                    })
                } else {
                    console.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 closeTab(tabIndex) {
        if (tabIndex < 0 || tabIndex >= tabs.length) return

        var newTabs = tabs.slice()

        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 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: fileExistsComponent
        Process {
            property string path
            property var callback
            command: ["test", "-f", path]

            Component.onCompleted: running = true

            onExited: (exitCode) => {
                callback(exitCode === 0)
                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: {
                console.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
        }

        var creator = fileCreatorComponent.createObject(root, {
            filePath: cleanPath,
            creationCallback: callback
        })
    }

    function copyFile(source, destination) {
        copyProcess.source = source
        copyProcess.destination = destination
        copyProcess.running = true
    }

    function deleteFile(path) {
        deleteProcess.filePath = path
        deleteProcess.running = true
    }

    Component {
        id: fileCreatorComponent
        QtObject {
            property string filePath
            property var creationCallback

            Component.onCompleted: {
                var touchProcess = touchProcessComponent.createObject(this, {
                    filePath: filePath,
                    callback: creationCallback
                })
            }
        }
    }

    Component {
        id: touchProcessComponent
        Process {
            property string filePath
            property var callback
            command: ["touch", filePath]

            Component.onCompleted: running = true

            onExited: (exitCode) => {
                if (callback) callback()
                destroy()
            }
        }
    }

    Process {
        id: copyProcess
        property string source
        property string destination
        command: ["cp", source, destination]
    }

    Process {
        id: deleteProcess
        property string filePath
        command: ["rm", "-f", filePath]
    }

    Process {
        id: mkdirProcess
        command: ["mkdir", "-p", root.baseDir, root.filesDir]
    }
}
