import re
import sys
import uuid

from .utils_web_project import tryOpenWorkspace
from .blender_windows import setStatus
from .extensions.NEEDLE_gameobject_data import guidKey
from .utils import getAttrInsensitive, getWebProjectPath, showMessage
from .panels_components import getBlenderObjectPropertyKey, getComponentKey, setIsActive, getIsActive, componentPrefix
import bpy
import os
import subprocess

def isEnabled(component):
    if hasattr(component, "enabled"):
        return component.enabled
    return False

# combining a component name with a method name
# to resolve e.g. EventList method assignments
def toMethodInfo(componentFullName, methodName):
    return componentFullName + "::" + methodName

def fromMethodInfo(methodInfo):
    return methodInfo.split("::")

def getGuid(componentOrBlenderObject):
    if componentOrBlenderObject is None:
        return None

    if isinstance(componentOrBlenderObject, bpy.types.Object):
        guid_key = guidKey()
        if hasattr(componentOrBlenderObject, guid_key):
            return componentOrBlenderObject.guid_key
        pass

    # components are expected to have a guid string property
    if not hasattr(componentOrBlenderObject, "guid"):
        return str(id(componentOrBlenderObject)) # fallback to generating an id - this might happen when its called using a blender object
    # first try if we already have a guid for the thing
    if hasattr(componentOrBlenderObject, "guid") and componentOrBlenderObject.guid != "":
        return componentOrBlenderObject.guid
    # if not, generate one
    # the guid is saved in the blender scene since its a StringProperty on the object
    # so we only need to generate it once
    guid = str(id(componentOrBlenderObject))
    print("GENERATED GUID", componentOrBlenderObject, guid)
    componentOrBlenderObject.guid = guid
    return guid


def getComponentGuid(blender_object : bpy.types.Object, component_name : str) -> str or None:
    if blender_object is None:
        return None
    component = None
    if hasattr(blender_object, component_name):
        component = getattr(blender_object, component_name)
    # if we dont have a component we generate one from the object id and the component name
    # this is the case when we create a component on export in code (e.g. for the Camera or the Renderer)
    if component is None:
        # generate a guid for the component
        return generateGuid(blender_object, component_name)
    return getGuid(component)

def generateGuid(blender_object : bpy.types.Object or None, salt:str) -> str: 
    # if the blender object is provided we use its id to salt the uuid
    if blender_object is not None:
        salt = str(id(blender_object)) + salt
    # https://docs.python.org/3/library/uuid.html#example
    return str(uuid.uuid3(uuid.NAMESPACE_DNS, salt))

def hasComponent(object) -> bool:
    """
    Checks if an object has a component.
    If no component name is provided, it checks if any component is present.
    """
    return hasComponent(object, None)

def hasComponent(object, componentName : str = None, caseSensitive:bool = True) -> bool:
    """
    Checks if an object has a component.
    If no component name is provided, it checks if any component is present.
    """
    # if no name is provided check if any component is present
    if componentName is None or len(componentName) == 0:
        prefix = componentPrefix()
        for key in object.keys():
            if key.startswith(prefix) and hasattr(object, key):
                comp = getattr(object, key)
                if comp is not None and getIsActive(comp):
                    return True
        return False

    componentName = getComponentKey(componentName)
    comp = None
    if caseSensitive == True:
        comp = getattr(object, componentName, None)
    else:
        comp = getAttrInsensitive(object, componentName)
    return getIsActive(comp)

def getComponent(object, componentFullName):
    return getattr(object, componentFullName, None)


def addComponent(context, componentName):
    selectedObjects = context.selected_objects
    for obj in selectedObjects:
        addComponentToObject(obj, componentName)
    # addComponentToObject(context.object, componentName)
    context.area.tag_redraw()

def addComponentToObject(obj, componentName) -> str or None:
    key =  getComponentKey(componentName)
    if key is not None:
        comp = getattr(obj, key, None)
        if comp is not None:
            comp[getBlenderObjectPropertyKey()] = obj
            setIsActive(comp, True)
            return key
    return None


def findByGuid(objectsArray: list[bpy.types.Object] , componentGuid:str) -> None or any:
    for obj in objectsArray:
        comp = getComponentByGuid(obj, componentGuid)
        if comp is not None:
            return comp
    return None

def getComponentByGuid(object : bpy.types.Object, guid:str) -> None or any:
    if object is None: return None
    prefix = componentPrefix()
    for key in object.keys():
        if key.startswith(prefix) and hasattr(object, key):
            comp = getattr(object, key)
            if comp is not None:
                if hasattr(comp, "guid") and comp.guid == guid:
                    return comp
    return None


def createNewComponent(name:str):
    webProjectPath = getWebProjectPath()
    if webProjectPath is None:
        return None
    scriptsPath = webProjectPath + "/src/scripts"
    if not os.path.exists(scriptsPath):
        return None
    # create a new component
    templatePath = os.path.join(os.path.dirname(__file__), "templates", "component.template.ts")
    if not os.path.exists(templatePath):
        return None
    if name is None or len(name) == 0:
        name = "NewComponent"
    # make sure the name doesnt have an extension
    if name.endswith(".ts"):
        name = name[:-3]
    # remove invalid characters (the following regex only allows alphanumeric characters and underscores)
    name = re.sub('[^0-9a-zA-Z_]', '', name)
    targetPath = os.path.join(scriptsPath, name + ".ts")
    index = 0
    while os.path.exists(targetPath):
        if index > 1000: break
        index += 1
        targetPath = os.path.join(scriptsPath, name + "_" + str(index) + ".ts")
    # copy the template to the target path
    with open(templatePath, "r") as templateFile:
        # create the target folder if it doesnt exist
        targetFolder = os.path.dirname(targetPath)
        if not os.path.exists(targetFolder):
            os.makedirs(targetFolder)
        with open(targetPath, "w") as targetFile:
            code = templateFile.read()
            code = code.replace("$ComponentName$", name)
            targetFile.write(code)
    showMessage("New component created at " + targetPath, "New Component")
    # open with default app
    if tryOpenWorkspace(scriptsPath):
        bpy.app.timers.register(lambda: openFile(targetPath), first_interval=1.0)
    else:
        openFile(targetPath)
    pass


def openFile(path:str):
    if sys.platform == "win32":
        os.startfile(path)
    else:
        opener = "open" if sys.platform == "darwin" else "xdg-open"
        subprocess.call([opener, path])






__lastTimeComponentSearchDict: dict[str, float] = {}

def findComponentInScene(componentName:str, force:bool = False):
    import time
    if not force:
        if componentName in __lastTimeComponentSearchDict:
            if (time.time() - __lastTimeComponentSearchDict[componentName]) < 2:
                return __lastTimeComponentSearchDict[componentName]
    for obj in bpy.context.scene.objects:
        if hasComponent(obj, componentName, False):
            
            return obj
        if obj.name == componentName:
            return obj
    return None