from aisi.__system__.imports import *


class index:
    ####################################################################################// Load
    def __init__(self, app="", cwd="", args=[]):
        self.app, self.cwd, self.args = app, cwd, args

        self.folder = ""
        self.collectors_path = ""
        self.model = ""
        self.key = ""
        self.collectors = None
        self.provider = None
        pass

    def __exit__(self):
        self.provider = None
        self.collectors = None
        self.key = ""
        self.model = ""
        self.folder = ""
        self.collectors_path = ""
        pass

    ####################################################################################// Main
    def init(self, folder="", collectors="", model="", key=""):  # (folder, collectors, model, key) - Initialize system
        self.folder, self.collectors_path, self.model, self.key = folder, collectors, model, key
        if not os.path.exists(collectors):
            cli.error("Invalid collectors file path")
            sys.exit()

        self.provider = self.__provider()
        if not self.provider:
            cli.error("Invalid provider")
            sys.exit()

        spec = importlib.util.spec_from_file_location("Collectors", collectors)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
        sys.modules["Collectors"] = module

        if not hasattr(module, "Collectors"):
            cli.error("Class named 'Collectors' not found")
            sys.exit()
        else:
            self.collectors = getattr(module, "Collectors", None)
        pass

    def run(self, message="", skill=""):  # (name, prompt) - Run skill from code
        if not skill:
            return ""

        if isinstance(message, str) and os.path.isfile(message) and os.path.exists(message):
            cli.trace("Loading file: " + message)
            message = cli.read(message)

        skill_class = self.__importSkill(skill)
        if not skill_class:
            return ""

        return self.__runMethod(skill, skill_class, "run", message)

    def prompt(self, message="", skill=""):  # (message, skill) - Builds a prompt for AI request
        if not skill:
            return ""

        if isinstance(message, str) and os.path.isfile(message) and os.path.exists(message):
            cli.trace("Loading file: " + message)
            message = cli.read(message)

        file = self.src("prompt.md", skill)
        if not file:
            return ""

        prompt = cli.read(file)
        if not prompt.strip():
            return ""

        skill_class = self.__importSkill(skill)
        if not skill_class:
            return ""

        methods = []
        if skill_class:
            methods = [m for m in dir(self.collectors) if not m.startswith("_") and m != "run" and callable(getattr(self.collectors, m))]

        detected_methods = re.findall(r'\[\[(\w+)\]\]', prompt)
        for method in methods:
            if method in ["MESSAGE"]:
                continue
            if method not in detected_methods:
                continue
            try:
                result = self.__runMethod(skill, self.collectors, method, message)
                prompt = prompt.replace("[[" + method + "]]", str(result))
            except Exception:
                prompt = prompt.replace("[[" + method + "]]", "")

        return prompt.replace("[[MESSAGE]]", message)

    def src(self, path="", skill=""):  # (path, skill) - Returns relative path of skill resources
        if not skill:
            return ""

        path = os.path.join(self.folder, skill.replace(".", "/"), path)
        if not os.path.exists(path):
            return ""

        return path

    def format(self, type="", content="", default=""):  # (type, content, default) - Returns right content/code block
        if content.strip() == "":
            return default

        if type.strip() == "":
            return content

        return f"\n```{type}\n{content}\n```\n"

    def add(self, name=""):  # (name) - Add new skill to project with name e.g "Other.Skill"
        if not name:
            return False

        folder = os.path.join(self.folder, name.replace(".", "/"))
        os.makedirs(folder, exist_ok=True)

        frame = os.path.dirname(__file__) + "/skill"
        if not os.path.exists(frame):
            return False

        media_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.mp3', '.mp4', '.wav', '.avi', '.mov', '.bin'}
        replacers = {
            "name": name.replace(".", ""),
        }

        for root, dirs, files in os.walk(frame):
            rel_path = os.path.relpath(root, frame)
            target_dir = folder if rel_path == "." else os.path.join(folder, rel_path)
            os.makedirs(target_dir, exist_ok=True)
            
            for file in files:
                src_file = os.path.join(root, file)
                dst_file = os.path.join(target_dir, file)
                
                if os.path.splitext(file)[1].lower() in media_extensions:
                    shutil.copy2(src_file, dst_file)
                else:
                    with open(src_file, 'r', encoding='utf-8', errors='ignore') as f:
                        content = f.read()
                    for key, value in replacers.items():
                        content = content.replace("{{" + key + "}}", value)
                    with open(dst_file, 'w', encoding='utf-8') as f:
                        f.write(content)

        return True

    def generate(self, skill="", message=""):  # (skill, prompt) - Generate new skill for project
        if not skill or not message:
            return False

        if isinstance(message, str) and os.path.isfile(message) and os.path.exists(message):
            cli.trace("Loading file: " + message)
            message = cli.read(message)

        file = os.path.dirname(__file__) + "/sources/prompt.skill.md"
        if not os.path.exists(file):
            return False

        skills = "\n".join(self.__skills())

        prompt = cli.read(file)
        colls = cli.read(self.collectors_path)

        prompt = prompt.replace("[[skills]]", self.format("yaml", skills))
        prompt = prompt.replace("[[MESSAGE]]", f"Create new skill named '{skill}';\n{message}")
        prompt = prompt.replace("[[collectors]]", f"\n```py\n{colls}\n```\n")

        files = self.FILES(prompt)
        collectors = files.get("collectors.py", "").strip()
        if collectors and cli.isFile(self.collectors_path):
            cli.write(self.collectors_path, collectors)

        config = json.loads(files.get("config.json", "{}"))
        skill = config.get("skill-name", skill)
        modules = config.get("import-modules", [])

        for each in files:
            if each in ["config.json"]:
                continue
            path = os.path.join(self.folder, skill.replace(".", "/"), each)
            os.makedirs(os.path.dirname(path), exist_ok=True)

            if each.strip() == "prompt.md":
                 files[each] = files[each].replace("{{MESSAGE}}", "[[MESSAGE]]")
            cli.write(path, files[each])

        if len(modules) > 0:
            imports_file = self.folder + "/.system/imports.py"
            if not os.path.exists(imports_file):
                return False
            for module in modules:
                cli.append(imports_file, module)

        return True

    def skills(self, must=False, skip=[]):  # (must, skip) - Displays skill selection in terminal
        skills = self.__skills(skip)
        if not skills:
            cli.error("No skills detected")
            return

        selected = ""
        build_path = ""
        while selected == "":
            items = []
            for item in os.listdir(self.folder + build_path) if build_path != "" else []:
                if build_path and build_path[1:].replace("/", ".") + "." + item in skip:
                    continue
                items.append(self.__depascal(item))

            if "skill.py" in items:
                continue

            parent = ""
            if build_path == "":
                for skill in skills:
                    parts = self.__pascal(skill).split(".")
                    first = self.__depascal(parts[0])
                    if first in items:
                        continue
                    items.append(first)
                if not must:
                    items.append("[Exit]")
            else:
                parent = " from '" + build_path[1:].replace("/", ".") + "'"
                items.append("[Back]")

            chosen = self.__pascal(cli.selection("Please select" + parent, items, True))
            if chosen == "[Back]":
                build_path = "/".join(build_path.split("/")[:-1])
                continue
            if chosen == "[Exit]":
                return ""

            path = os.path.join(self.folder + build_path, chosen, "skill.py")
            if os.path.exists(path):
                if build_path:
                    selected = build_path + "/" + chosen
                else:
                    selected = chosen
            else:
                build_path += "/" + chosen

        if selected[0] == "/":
            selected = selected[1:]

        return selected.replace("/", ".")

    def models(self, must=False):  # (must) - Displays model selection in terminal
        return cli.selection(
            "Set AI Model",
            [
                "openai/gpt-5.2",
                "openai/gpt-5.1",
                "openai/gpt-5-mini",
                "openai/gpt-5-nano",
                "openai/gpt-4.1",
                "openai/gpt-4.1-mini",
                #
                "vertex/gemini-2.5-pro",
                "vertex/gemini-2.5-flash",
                #
                # DEV - Test and adjust claude API
                # "claude/claude-opus-4-6",
                # "claude/claude-sonnet-4-6",
                # "claude/claude-haiku-4-5",
                #
                # DEV - Test and adjust grok API
                # "grok/grok-4-1-fast-reasoning",
                # "grok/grok-4-1-fast-non-reasoning",
                # "grok/grok-4",
                # "grok/grok-code-fast-1",
                # "grok/grok-beta",
                #
                # "",
            ],
            must,
        )

    ####################################################################################// AI Actions
    def REPLY(self, prompt=""):  # (prompt) - Generates reply, returns message or ""
        return self.provider.reply(prompt)

    def FILES(self, prompt=""):  # (prompt) - Generates files with contents, returns files dict or {}
        return self.provider.files(prompt)

    def IMAGE(self, prompt="", path=""):  # (prompt, path) - Generates image file, returns path or ""
        return self.provider.image(prompt, path)

    def VIDEO(self, prompt="", path=""):  # (prompt, path) - Generates video file, returns path or ""
        return self.provider.video(prompt, path)

    def AUDIO(self, prompt="", path=""):  # (prompt, path) - Generates audio file, returns path or ""
        return self.provider.audio(prompt, path)

    ####################################################################################// Helpers
    def __importSkill(self, skill=""):
        file = self.src("skill.py", skill)
        if not file:
            return None

        class_name = skill.replace(".", "").strip()
        module_name = f"{class_name}_{skill.replace('.', '_')}"
        module = sys.modules.get(module_name)

        if module is None:
            spec = importlib.util.spec_from_file_location(module_name, file)
            module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(module)
            sys.modules[module_name] = module

        return getattr(module, class_name, None)

    def __runMethod(self, skill="", instance=None, method="", message=""):
        if not skill or not instance or not method:
            return ""

        skill_instance = instance()
        method_func = getattr(skill_instance, method)

        if not callable(method_func):
            return ""

        return method_func(message, self.cwd, skill)

    def __provider(self):
        if "/" in self.model:
            provider, model = self.model.split("/", 1)
        else:
            cli.error("Invalid model format. Expected provider/model")
            return None

        try:
            ProviderClass = globals()[provider.capitalize()]
            return ProviderClass(self.key, model, self.cwd).__class__
        except KeyError:
            cli.error(f"Provider class '{provider}' not found")
            return None

        cli.error(f"Could not load '{provider}' provider")
        return None

    def __skills(self, skip=[]):
        skills = []
        if not os.path.exists(self.folder):
            return skills

        for root, dirs, files in os.walk(self.folder):
            skill = root.replace(self.folder + "\\", "").replace("\\", ".")
            skipit = False
            for item in skip:
                if skill == item or skill.startswith(item + "."):
                    skipit = True
            if not skipit and "skill.py" in files:
                skills.append(self.__depascal(skill))

        return skills

    def __pascal(self, text=""):
        return text.replace(" ", "").strip()

    def __depascal(self, text=""):
        return re.sub(r'(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])', ' ', text).strip()
