from datetime import datetime

from .settings_scene import registerProjectPathUpdatedCallback

from .utils_debug import debug_log
from .blender_windows import setStatus
import bpy
import os

from .utils_web_project import getGeneratedDirectory, getScriptsDirectory, stopProcesses
from . import component_types
from . import external_process
from . import utils

debugLog = True

class schemeCompilerProcess(bpy.types.Operator):
    bl_idname = 'scene.needle_regenerate_component_panels'
    bl_label = 'Regenerate component panels'
    bl_options = {"REGISTER"}

    def execute(self, context):
        print("REGENERATE")
        component_types.generateComponentTypesFromSchemes()
        return {"FINISHED"}


# this is a list of all watcher processes
watcherProcess = []
# this is where we store the path of changed files before we re-build the panels
# TODO: only re-build the panels for the changed files
filesChanged = []
filesDeleted:int = 0
didReceiveReload : bool = False

def onMessage(msg: any):
    if not msg.startswith( "CMD:"):
        return
    command = msg[4:]
    global filesChanged
    global filesDeleted
    if command == "reload":
        ignoreFirstReload = False
        # when running the watcher for the first time it will send a reload command
        # we ignore this command (at least during development)
        global didReceiveReload
        firstReload = not didReceiveReload
        didReceiveReload = True
        filesChangedCount = len(filesChanged)
        if filesChangedCount > 0 or filesDeleted > 0:
            stringOfChangedFiles = ""
            for file in filesChanged:
                # get file name
                fileName = os.path.basename(file)
                stringOfChangedFiles = stringOfChangedFiles + fileName + ", "
            timeNow = datetime.now().strftime("%H:%M:%S")
            setStatus("🌵 Needle: Building components at " + timeNow + " → " + stringOfChangedFiles + " (" + str(filesChangedCount) + " files, " + str(filesDeleted) + " deleted)")
            filesChanged = []
            filesDeleted = 0
            if not ignoreFirstReload or not firstReload:
                if debugLog:
                    print("RELOAD, changed files:", filesChangedCount)
                scheduleRegenerateTypes()

    # event invoked when a file has been written to disk
    # the command contains the path to the file e.g. CMD:writefile my/file/path.ts
    elif command.startswith("writefile"):
        filePath = command[10:]
        if debugLog:
            print("File changed event", filePath)
        if not utils.isSchemeFile(filePath):
            print("Not a component file")
            return
        fileExists = os.path.isfile(filePath)
        if not fileExists:
            print("FILE DOES NOT EXIST", filePath)
            return
        if filePath not in filesChanged:
            filesChanged.append(filePath)
    elif command.startswith("deletedfile"):
        filesDeleted = filesDeleted + 1
    else:
        print("Unknown command", command)

typeRegenerationIndex: int = 0
lastTypeCallback = None
def scheduleRegenerateTypes():
    global lastTypeCallback
    if lastTypeCallback is not None:
        bpy.app.timers.unregister(lastTypeCallback)
    global typeRegenerationIndex
    typeRegenerationIndex = typeRegenerationIndex + 1
    currentIndex = typeRegenerationIndex
    lastTypeCallback = lambda: handleScheduledRegenerateTypes(currentIndex)
    bpy.app.timers.register(lastTypeCallback, first_interval=0.5)

def handleScheduledRegenerateTypes(index: int):
    # check if we are still the latest scheduled type regeneration
    global typeRegenerationIndex
    if index != typeRegenerationIndex:
        return
    # unregister cb
    global lastTypeCallback
    bpy.app.timers.unregister(lastTypeCallback)
    lastTypeCallback = None
    if debugLog:
        print("Regenerate types")
    component_types.generateComponentTypesFromSchemes()

def startFileWatcher():
    stopFileWatchers()
    # if utils.isTempProject():
    #     return
    webProjectPath = utils.getWebProjectPath()
    if webProjectPath is None or not os.path.exists(webProjectPath):
        print("No web project found", webProjectPath)
        return
    # by default use the compiler path that is installed in the current web project
    compilerPath = webProjectPath + "/node_modules/@needle-tools/needle-component-compiler/src"
    # check if the compiler path exists
    if not os.path.exists(compilerPath):
        print("COMPILER PATH DOES NOT EXIST", compilerPath + "\nInstall @needle-tools/needle-component-compiler 2.x in your web project")
        return
    elif not os.path.exists(compilerPath + "/watcher.js"):
        debug_log("Compiler watcher script does not exist, this means you have a wrong package installed: Please install @needle-tools/needle-component-compiler 2.x in your web project")
        return
    
    srcDirectory = getScriptsDirectory(webProjectPath)
    if srcDirectory is None:
        srcDirectory = os.path.join(webProjectPath, "src")
    schemesDirectory = utils.getSchemesDirectory(webProjectPath)

    if not os.path.exists(srcDirectory):
        return
    
    codegenDirectory = getGeneratedDirectory(webProjectPath)
    registerTypesPath = os.path.join(codegenDirectory, "register-types.js")
    if debugLog:
        print("START WATCHER", srcDirectory)
    setStatus("🌵 Needle: watching for changes in " + srcDirectory)
    debug_log("START WATCHER", srcDirectory)
    command = "node watcher.js \"" + srcDirectory + "\" \"" + schemesDirectory + "\" \"" + registerTypesPath + "\""
    bpy.app.timers.register(lambda: startFileWatcherDelayed(command, compilerPath), first_interval=2)

def startFileWatcherDelayed(command, compilerPath):
    p = external_process.exec_command(command, cwd=compilerPath, debug=False, onMessage=onMessage)
    watcherProcess.append(p) 
    

def stopFileWatchers():
    stopProcesses("node *watcher.js")

    global watcherProcess
    if debugLog:
        print("Kill started processes", len(watcherProcess))
    for p in watcherProcess:
        p.kill()
    watcherProcess = []

    
def onProjectPathChangedOrSceneLoaded(self, context):
    component_types.generateComponentTypesFromSchemes()
    pass

def register():
    registerProjectPathUpdatedCallback(onProjectPathChangedOrSceneLoaded)