#!/usr/bin/env python3
"""
HP Unified CLI
Copyright © 2026 Roberto HP

Implements:
- HP CLI Contract v1.0 (Final)
- HP Target Contract v1.0 (Final)
"""

import sys
import subprocess
import platform
from pathlib import Path
from abc import ABC, abstractmethod
import argparse
import socket
import webbrowser
import shutil
import os
from colorama import init


# -------------------------------------------------
# Constants
# -------------------------------------------------


CLI_VERSION = "1.10.0 (March 2, 2026)"
DEFAULT_FILE = "TestLang.hp"
HP_DIR = ".hp"
CACHE_DIR = "cache"
TIME_DIR = "time"
THEMES = {
    "dark": {
        "compile": 33,  # azul suave
        "run": 40,  # verde suave
        "error": 196,  # rojo brillante
        "warning": 220,  # amarillo
        "info": 45,  # cyan
        "label_fg": 15,  # blanco
        "header": 186,  # amarillo pálido elegante
        "body": 220,  # verde suave
        "success": 40,  # amarillo
        "fail": 196,  # rojo brillante
    },
    "light": {
        "compile": 19,  # azul oscuro
        "run": 28,  # verde oscuro
        "error": 124,
        "warning": 130,
        "info": 25,
        "label_fg": 0,  # negro
        "header": 186,  # amarillo pálido elegante
        "body": 130,
        "success": 28,  # verde oscuro
        "fail": 124,
    },
    "hplang": {
        "compile": 67,  # azul gris (#627c91)
        "run": 79,  # turquesa (#4EC9B0)
        "error": 167,  # rojo suave armonizado
        "warning": 173,  # durazno (#d99378)
        "info": 108,  # verde gris comentario
        "header": 186,  # amarillo pálido elegante
        "body": 173,  # durazno (#d99378)
        "success": 79,  # turquesa (#4EC9B0)
        "fail": 167,  # rojo suave armonizado
    },
    "hplang2": {
        "compile": 75,  # azul gris elegante
        "run": 73,  # turquesa suave
        "error": 161,  # rojo armónico
        "warning": 179,  # durazno cálido
        "info": 114,  # verde comentario suave
        "header": 75,  # amarillo cálido elegante
        "body": 179,  # durazno cálido
        "success": 75,  # turquesa suave
        "fail": 161,  # rojo armónico
    },
}
GENERATED_FILES = [
    "TestLang.hp",
]
TEMP_EXTENSIONS = [".py", ".php", ".cs", ".java", ".class", ".o"]


# -------------------------------------------------
# Utilities
# -------------------------------------------------

init(autoreset=True)


def run(
    cmd: list[str],
    cwd=None,
    verbose=False,
    silent: bool = False,
    background: bool = False,
    interactive: bool = False,
):
    is_windows = platform.system() == "Windows"

    if background:
        if is_windows:
            return subprocess.Popen(
                " ".join(cmd),
                cwd=cwd,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
            )
        else:

            return subprocess.Popen(
                cmd,
                cwd=cwd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
            )

    if interactive:
        # self.cli.info(f"Process hp PID hp process:{os.getpid()}")
        # print("Process Python running script hp", "PID process process:", os.getpid())
        # print("Process shell", "PPID parent hp process:", os.getppid())
        # import psutil

        # # import os

        # p = psutil.Process(os.getpid())

        # print("PID:", p.pid)
        # print("PPID:", p.ppid())
        # print("Nombre:", p.name())

        # p = run(["ls"], background=True)

        # print("PID:", p.pid)

        # out, err = p.communicate()
        # print(out)

        # p = run(["bash", "-c", "echo PID: $$, PPID: $PPID"], background=True)

        # out, err = p.communicate()
        # print(out)

        # p = run(["ls"], background=True)

        # print("PID hijo:", p.pid())

        # p = run(["bash", "-c", "echo PID: $$, PPID: $PPID"], background=True)
        # out, err = p.communicate()
        # print(out)
        if is_windows:
            return subprocess.run(" ".join(cmd), cwd=cwd, shell=True)
        else:
            return subprocess.run(cmd, cwd=cwd)
    else:
        # print("[Process ]", "PID actual", os.getpid())
        # print("[Process ]", "PPID actual", os.getppid())
        # p = run(["bash", "-c", "echo PID: $$, PPID: $PPID"], background=True)
        # out, err = p.communicate()
        # print(out)
        if is_windows:
            return subprocess.run(
                " ".join(cmd), cwd=cwd, shell=True, capture_output=True, text=True
            )
        else:
            return subprocess.run(cmd, cwd=cwd, capture_output=True, text=True)

    # return result


# -------------------------------------------------
# Theme manager.
# -------------------------------------------------


class CLIStyle:
    def __init__(self, theme_name="dark"):
        if theme_name not in THEMES:
            theme_name = "dark"

        self.theme = THEMES[theme_name]

    def fg256(self, n):
        return f"\033[38;5;{n}m"

    def reset(self):
        return "\033[0m"

    def color(self, text, color_code):
        if not sys.stdout.isatty():
            return text
        return self.fg256(color_code) + text + self.reset()

    def header(self, text):
        print()
        print(self.color(f"{text}", self.theme["header"]))

    def body(self, text):
        print(self.color(f"{text}", self.theme["body"]))

    def success(self, text):
        print(self.color(f"{text}", self.theme["success"]))

    def fail(self, text):
        print(self.color(f"{text}", self.theme["fail"]))

    def compile(self, text):
        print()
        print(self.color(f"Compiling {text}", self.theme["compile"]))

    def run(self, text):
        print()
        print(self.color(f"{text}", self.theme["run"]))
        print()

    def error(self, text):
        print(self.color(f"Error     {text}", self.theme["error"]))

    def warning(self, text):
        print(self.color(f"Warning   {text}", self.theme["warning"]))

    def warning2(self, text):
        print(self.color(f"Warning   {text}", self.theme["warning"]), end=" ")

    def info(self, text):
        print(self.color(f"Info      {text}", self.theme["info"]))


# -------------------------------------------------
# Project Context
# -------------------------------------------------


class ProjectContext:
    def __init__(self, workdir: Path):
        self.workdir = workdir
        self.config_file = workdir / "config.hp"
        self.hp_dir = workdir / HP_DIR
        self.time_dir = self.hp_dir / CACHE_DIR / TIME_DIR
        self.verbose = False

    def ensure_initialized(self):
        if not self.config_file.exists():
            # print("Initializing this directory as a workspace ...")
            result = run(["hpcompiler"])
            # if result.stdout:
            #     self.cli.info(result.stdout)

            # if result.stderr:
            #     self.cli.error(result.stderr)

            # if result.returncode != 0:
            #     return False

        self.time_dir.mkdir(parents=True, exist_ok=True)

    def set_flag(self, key: str, value: bool):
        text = self.config_file.read_text(encoding="utf-8")
        text = text.replace(f"{key}=True", f"{key}=False")
        text = text.replace(f"{key}=False", f"{key}={str(value)}")
        self.config_file.write_text(text, encoding="utf-8")

    def get_flag(self, key: str) -> bool | None:
        if not self.config_file.exists():
            return None

        with self.config_file.open(encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if line.startswith(f"{key}="):
                    value = line.split("=", 1)[1].strip()
                    return value == "True"

        return None

    def read_config(self) -> dict:
        config = {}
        if not self.config_file.exists():
            return config

        with self.config_file.open(encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if "=" in line:
                    k, v = line.split("=", 1)
                    config[k.strip()] = v.strip()
        return config

    def write_config(self, config: dict):
        lines = [f"{k}={v}" for k, v in config.items()]
        self.config_file.write_text("\n".join(lines), encoding="utf-8")

    def set_value(self, key: str, value: str):
        config = self.read_config()
        config[key] = value
        self.write_config(config)

    def get_value(self, key: str, default: str):
        config = self.read_config()
        return config.get(key, default)

    def binary_name(self, base_name):
        system = platform.system()

        if system == "Windows":
            return base_name + ".bat"
        return base_name

    @property
    def versions_dir(self):
        return os.path.join(os.path.expanduser("~"), "hpcompiler", "versions")

    def current_path(self):
        return os.path.join(self.versions_dir, "current")

    def current_version(self):
        current = self.current_path()
        if os.path.islink(current):
            return os.path.basename(os.readlink(current))
        elif os.path.exists(current):
            # Windows case
            return "unknown (Windows copy)"
        return None


# -------------------------------------------------
# Time Cache
# -------------------------------------------------


class TimeCache:
    def __init__(self, context: ProjectContext, hp_file: Path, target: str):
        self.dir = context.time_dir / hp_file.stem
        self.dir.mkdir(parents=True, exist_ok=True)
        self.path = self.dir / f"{target}.time"

    def read(self) -> int | None:
        if not self.path.exists():
            return None
        try:
            return int(self.path.read_text().strip())
        except ValueError:
            return None

    def write(self, timestamp: int):
        self.path.write_text(str(timestamp))


# -------------------------------------------------
# Target Base
# -------------------------------------------------


class TargetBase(ABC):
    name: str
    requires_runtime = False

    def __init__(
        self, context: ProjectContext, cli: CLIStyle, hp_file: Path, action_word: str
    ):
        self.context = context
        self.cli = cli
        self.hp_file = hp_file
        self.action_word = action_word
        self.cache = TimeCache(context, hp_file, self.name)

    def needs_compile(self, force: bool, compile: bool) -> bool:
        if force or compile:
            return True

        last = self.cache.read()
        current = int(self.hp_file.stat().st_mtime)

        if last is None:
            return True

        return current > last

    def compile(self, console: bool):
        self.cli.compile(f"{self.hp_file.name} for {self.name} ...")

        result = run(["hpcompiler", str(self.hp_file)])

        if result.stdout:
            self.cli.body(result.stdout)  # Nothing, a blank line.

        if result.stderr:
            self.cli.error(result.stderr)

        # if isinstance(result.stderr, str):
        #     for line in result.stderr.splitlines():
        #         if "warning" in line.lower():
        #             self.cli.warning(line)
        #         else:
        #             self.cli.error(line)

        if result.returncode != 0:
            return result

        if not console:
            self.cache.write(int(self.hp_file.stat().st_mtime))

        # self.cli.body("Compiled successfully.")
        return result

    def build(self):
        pass

    @property
    def verbose(self):
        return self.context.verbose

    @abstractmethod
    def run(self):
        pass

    @abstractmethod
    def activate_flags(self, console: bool):
        pass

    def reset_flags(self, console: bool):
        pass


# -------------------------------------------------
# Python Target
# -------------------------------------------------


class PythonTarget(TargetBase):
    name = "python"  # Use in TimeCache() inside TargetBase class.

    def activate_flags(self, console: bool):
        if console:
            self.context.set_flag("pythonCodeConsole", True)
        else:
            self.context.set_flag("pythonCodeFile", True)

    def reset_flags(self, console: bool):
        if console:
            self.context.set_flag("pythonCodeConsole", False)
        else:
            self.context.set_flag("pythonCodeFile", False)

    def activate_flag(self, show_PIDs: bool):
        if show_PIDs:
            self.context.set_flag("pythonShowPIDs", True)

    def reset_flag(self, show_PIDs: bool):
        if show_PIDs:
            self.context.set_flag("pythonShowPIDs", False)

    # def run(self, interactive: bool = False):
    def run(self):
        py_file = self.hp_file.with_suffix(".py")
        self.cli.run(f"{self.action_word} with Python ...")
        # return run([sys.executable, str(py_file)])
        return run([sys.executable, str(py_file)], interactive=True)


# -------------------------------------------------
# C# Target
# -------------------------------------------------


class CsTarget(TargetBase):
    name = "cs"  # Use in TimeCache() inside TargetBase class.

    def activate_flags(self, console: bool):
        if console:
            self.context.set_flag("csCodeConsole", True)
        else:
            self.context.set_flag("csCodeFile", True)

    def reset_flags(self, console: bool):
        if console:
            self.context.set_flag("csCodeConsole", False)
        else:
            self.context.set_flag("csCodeFile", False)

    def build(self):
        project = self.hp_file.stem
        workdir = self.context.workdir
        project_dir = workdir / project
        src_dir = project_dir / project
        program_cs = workdir / "Program.cs"
        main_cs = workdir / f"{project}.cs"

        # -------------------------------------------------
        # Create solution/project if not exists
        # -------------------------------------------------
        if not project_dir.exists():
            project_dir.mkdir(parents=True)

            result = run(
                ["dotnet", "new", "sln", "-n", f"{project}"],
                cwd=project_dir,
                verbose=self.verbose,
            )
            result = run(
                ["dotnet", "new", "console", "-n", f"{project}", "-f", "net7.0"],
                cwd=project_dir,
                verbose=self.verbose,
            )
            result = run(
                ["dotnet", "new", "globaljson", "--sdk-version", "7.0.317"],
                cwd=project_dir,
                verbose=self.verbose,
            )
            result = run(
                ["dotnet", "sln", "add", f"{project}/{project}.csproj"],
                cwd=project_dir,
                verbose=self.verbose,
            )

        # -------------------------------------------------
        # Move generated .cs files into project
        # -------------------------------------------------

        src_dir.mkdir(exist_ok=True)
        if program_cs.exists():
            program_cs.replace(src_dir / "Program.cs")
        if main_cs.exists():
            main_cs.replace(src_dir / f"{project}.cs")

        # -------------------------------------------------
        # Build & run
        # -------------------------------------------------

        return run(
            ["dotnet", "build", project],
            cwd=project_dir,
            verbose=self.verbose,
            silent=True,
        )

    def run(self):
        project = self.hp_file.stem
        workdir = self.context.workdir
        project_dir = workdir / project

        self.cli.run(f"{self.action_word} with C# ...")
        return run(
            ["dotnet", "run", "--no-build", "--project", project],
            cwd=project_dir,
            verbose=self.verbose,
            silent=False,
            interactive=True,
        )


# -------------------------------------------------
# PHP Target
# -------------------------------------------------


class PhpTarget(TargetBase):
    name = "php"  # Use in TimeCache() inside TargetBase class.

    PORT = 8000
    requires_runtime = True

    def activate_flags(self, console: bool):
        if console:
            self.context.set_flag("phpCodeConsole", True)
        else:
            self.context.set_flag("phpCodeFile", True)

    def reset_flags(self, console: bool):
        if console:
            self.context.set_flag("phpCodeConsole", False)
        else:
            self.context.set_flag("phpCodeFile", False)

    def start_runtime(self):
        if not self._is_port_open(self.PORT):
            print(f"Starting PHP server on port {self.PORT} ...")
            return run(
                ["php", "-S", f"localhost:{self.PORT}"],
                background=True,
                silent=True,
            )
        else:
            print(f"PHP server already running on port {self.PORT}.")
            return True

    def open_in_browser(self, url: str):
        browser = self.context.get_value("browser", "Google browser")
        system = platform.system()

        if browser:
            if system == "Darwin":  # macOS
                return run(["open", "-a", browser, url], background=True)
            elif system == "Windows":
                return run(["start", browser, url], background=True)
            else:  # Linux
                return run([browser, url], background=True)
        else:
            webbrowser.open(url)
            return True

    def run(self):
        self.start_runtime()
        php_file = self.hp_file.with_suffix(".php")
        url = f"http://localhost:{self.PORT}/{php_file.name}"

        # print("Running with PHP ...\n")
        self.cli.run(f"{self.action_word} with PHP ...")
        print(f"Opening {url}")

        self.open_in_browser(url)

        print("Program is running as a web page ...")

    @staticmethod
    def _is_port_open(port: int) -> bool:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            return s.connect_ex(("localhost", port)) == 0


# -------------------------------------------------
# Java Target
# -------------------------------------------------


class JavaTarget(TargetBase):
    name = "java"  # Use in TimeCache() inside TargetBase class.

    def activate_flags(self, console: bool):
        if console:
            self.context.set_flag("javaCodeConsole", True)
        else:
            self.context.set_flag("javaCodeFile", True)

    def reset_flags(self, console: bool):
        if console:
            self.context.set_flag("javaCodeConsole", False)
        else:
            self.context.set_flag("javaCodeFile", False)

    def build(self):
        java_file = self.hp_file.with_suffix(".java")

        result = run(
            ["javac", java_file.name],
            cwd=java_file.parent,
        )

    def run(self):
        class_name = self.hp_file.stem

        self.cli.run(f"{self.action_word} with Java ...")
        return run(["java", class_name], cwd=self.hp_file.parent, interactive=True)


# -------------------------------------------------
# Target Manager
# -------------------------------------------------


class TargetManager:
    TARGETS = {
        "python": PythonTarget,
        "cs": CsTarget,
        "php": PhpTarget,
        "java": JavaTarget,
    }

    def __init__(
        self,
        context: ProjectContext,
        cli: CLIStyle,
        hp_file: Path,
        action_word: str,
    ):
        self.context = context
        self.cli = cli
        self.hp_file = hp_file
        self.action_word = action_word
        # self.browser = browser

    def run_targets(self, targets, force, console, compile_only, show_pids):
        # def run_targets(self, targets, force, console, compile_only):

        result = None

        for name in targets:

            if name not in self.TARGETS:
                self.cli.error(f"Unknown target '{name}'")
                return None

            target_cls = self.TARGETS[name]
            target = target_cls(self.context, self.cli, self.hp_file, self.action_word)

            try:
                target.activate_flag(show_pids)
                target.activate_flags(console)

                # COMPILACIÓN
                if target.needs_compile(force, compile_only) or console:
                    result = target.compile(console)

                    if result.returncode != 0:
                        return result

                #  BUILD + RUN
                if not console and not compile_only:

                    target.build()

                    # result = target.run(interactive=True)
                    result = target.run()

            finally:
                target.reset_flags(console)
                target.reset_flag(show_pids)

        return result


# -------------------------------------------------
# Config handler
# -------------------------------------------------
class ConfigHander:
    def __init__(self, context, cli):
        self.context = context
        self.cli = cli

    def show_config_help(self):
        self.cli.header("HP Configuration manager.")
        self.cli.body("Usage:")
        self.cli.body("  hp config set <key> <value>")
        self.cli.body("  hp config get <key>")
        self.cli.body("  hp config list")
        # self.cli.body("")
        self.cli.header("Examples:")
        self.cli.body("  hp config set theme light")
        self.cli.body("  hp config get theme")

    def run(self, args):
        action = args.action
        key = args.key
        value = args.value

        if not action:
            self.show_config_help()
            return

        if action == "get":
            self.get(key)

        elif action == "set":
            self.set(key, value)

        elif action == "list":
            self.list_all()

        else:
            self.cli.error(f"Invalid config command: {action}")

    def get(self, key):
        if not key:
            self.cli.error("Missing <key>.")
            self.show_config_help()
            return

        val = self.context.get_value(key)
        if val is not None:
            self.cli.info(f"{key} = {val}")
        else:
            self.cli.warning("Key not found.")
        return

    def set(self, key, value):
        validKeys = ["theme", "browser"]
        validThemes = ["dark", "light", "hplang", "hplang2"]
        validBrowsers = ["Google Chrome", "Safari", "Opera", "Firefox"]
        if not key and not value:
            self.cli.error("Missing <key> and <value>.")
            self.show_config_help()
            return
        elif not key in validKeys:
            self.cli.error(f"{key} is unknown.")
            self.cli.info(f"Whatever of this: {', '.join(validKeys)} is valid.")
            # self.show_config_help()
            return
        elif key == "browser" and not value in validBrowsers:
            if value == None:
                self.cli.error(f"A <value> is require.")
            else:
                self.cli.error(f"{value} is unknown.")
            self.cli.info(f"Whatever of this: {', '.join(validBrowsers)} is valid.")
            # self.show_config_help()
            return
        elif key == "theme" and not value in validThemes:
            if value == None:
                self.cli.error(f"A <value> is require.")
            else:
                self.cli.error(f"{value} is unknown.")
            self.cli.info(f"Whatever of this: {', '.join(validThemes)} is valid.")
            # self.show_config_help()
            return
        self.context.set_value(key, value)
        self.cli.success(f"{key} updated to {value}")
        return

    def list_all(self):
        config = self.context.read_config()
        if not config:
            self.cli.warning("No configuration values set.")
            return

        for k, v in config.items():
            self.cli.info(f"{k} = {v}")
        return


# -------------------------------------------------
# Clean
# -------------------------------------------------


class Cleaner:
    def __init__(self, context, cli):
        self.context = context
        self.cli = cli

    def run(self, args):
        self.cli.header("Cleaning the workspace ...\n")

        removed = 0

        for file in os.listdir():
            if file in GENERATED_FILES:
                os.remove(file)
                self.cli.success(f"Removed {file}")
                removed += 1

            elif any(file.endswith(ext) for ext in TEMP_EXTENSIONS):
                os.remove(file)
                self.cli.success(f"Removed {file}")
                removed += 1

        self._reset_flags()

        if removed == 0:
            self.cli.fail("No generated files found.")
        else:
            self.cli.success("\nClean completly.")

    def _reset_flags(self):
        config = self.context.read_config()

        for key in config.keys():
            if key.endswith("CodeConsole") or key.endswith("CodeFile"):
                config[key] = False

        self.context.write_config(config)


# -------------------------------------------------
# Doctor
# -------------------------------------------------


class Doctor:
    MIN_PYTHON = (3, 10)

    def __init__(self, context, cli):
        self.context = context
        self.cli = cli

    def check_binary(self, name):
        return shutil.which(name) is not None

    def run(self, args):
        self.cli.header("Running environment diagnostics ...\n")

        self._check_python()
        self._check("CS", self.check_binary("cs"))
        self._check("PHP", self.check_binary("php"))
        self._check("Java", self.check_binary("java"))
        self._check("hpcompiler", self.context.binary_name("hpcompiler"))
        # self._check("hpcompiler", self.check_binary("hpcompiler"))

        self._check_config()

        self.cli.success("\nDiagnostic finished.")

    # 🔥 Python inteligente multiplataforma
    def _check_python(self):
        system = platform.system()

        if system == "Windows":
            candidates = ["py", "python"]
        else:
            candidates = ["python3", "python"]

        interpreter = None
        for cmd in candidates:
            if self.check_binary(cmd):
                interpreter = cmd
                break

        if not interpreter:
            self.cli.fail("[FAIL] Python not found")
            return

        # Verificar versión real
        version = sys.version_info
        version_tuple = (version.major, version.minor)

        if version_tuple < self.MIN_PYTHON:
            self.cli.fail(
                f"[FAIL] Python {version.major}.{version.minor} "
                f"(Requires {self.MIN_PYTHON[0]}.{self.MIN_PYTHON[1]}+)"
            )
        else:
            self.cli.body(
                f"[OK] Python {version.major}.{version.minor}.{version.micro} ({interpreter})"
            )

    def _check(self, label, condition):
        if condition:
            self.cli.body(f"[OK] {label}")
        else:
            self.cli.fail(f"[FAIL] {label}")

    def _check_config(self):
        if os.path.exists("config.hp"):
            self.cli.body("[OK] config.hp")
        else:
            self.cli.fail("[FAIL] config.hp")


# -------------------------------------------------
# Init
# -------------------------------------------------


class Initializer:
    def __init__(self, context, cli):
        self.context = context
        self.cli = cli

    def run(self, args):
        self.cli.header("Initializing this workspace ...\n")

        for file in ["config.hp", "TestLang.hp"]:
            if os.path.exists(file):
                os.remove(file)
                self.cli.warning(f"Removed {file}")

        result = subprocess.run(["hpcompiler"], capture_output=True)

        if result.returncode == 0:
            self.cli.success("Workspace initialized successfully.")
        else:
            self.cli.error("Initialization failed.")


# -------------------------------------------------
# Reset
# -------------------------------------------------


class Resetter:
    def __init__(self, context, cli):
        self.context = context
        self.cli = cli

    def run(self, args):
        self.cli.info("Resetting the workspace ...\n")

        Cleaner(self.context, self.cli).run(args)
        Initializer(self.context, self.cli).run(args)

        self.cli.success("\nWorkspace reseted completly.")


# -------------------------------------------------
# RunHandler
# -------------------------------------------------


class RunHandler:
    def __init__(self, context, cli):
        self.context = context
        self.cli = cli

    def run(self, args):
        self.cli.info("Resetting the workspace ...\n")

        Cleaner(self.context, self.cli).run(args)
        Initializer(self.context, self.cli).run(args)

        self.cli.success("\nWorkspace reseted completly.")


# -------------------------------------------------
# ListVersions
# -------------------------------------------------


class ListVersions:

    def __init__(self, context, cli):
        self.context = context
        self.cli = cli

    def run(self, args):
        versions_dir = self.context.versions_dir
        current = self.context.current_version()

        if not os.path.exists(versions_dir):
            self.cli.fail("No versions installed.")
            return

        versions = sorted(os.listdir(versions_dir))

        self.cli.header("Installed versions:\n")

        for version in versions:
            if version == current:
                self.cli.body(f"  * {version} (current)")
            else:
                self.cli.body(f"    {version}")
        print()


# -------------------------------------------------
# UseVersion
# -------------------------------------------------


class UseVersion:

    def __init__(self, context, cli):
        self.context = context
        self.cli = cli

    def run(self, args):
        if not args.action:
            self.cli.error(f"It requieres a <version>.")
            self.cli.info("Usage: hp use [x.y.z] or `hp list` to see possible version.")
            return

        version = f"hpcompiler-v{args.action}"
        target = os.path.join(self.context.versions_dir, version)

        if not os.path.exists(target):
            self.cli.error(f"Version {args.action} not installed.")
            self.cli.info("Usage: hp use [x.y.z] or `hp list` to see possible version.")
            return

        current = self.context.current_path()

        system = platform.system()

        try:
            if system == "Windows":
                # Borrar carpeta current si existe
                if os.path.exists(current):
                    shutil.rmtree(current)
                shutil.copytree(target, current)
            else:
                if os.path.islink(current) or os.path.exists(current):
                    os.remove(current)
                os.symlink(target, current)

            self.cli.success(f"Now, hpcompiler is using {version}")

        except Exception as e:
            self.cli.error(f"Failed to switch version: {e}")


# -------------------------------------------------
# UninstallVersion
# -------------------------------------------------


class UninstallVersion:

    def __init__(self, context, cli):
        self.context = context
        self.cli = cli

    def run(self, args):
        if not args.action:
            self.cli.error(f"It requieres a <version>.")
            self.cli.info(
                "Usage: hp uninstall [x.y.z] or `hp list` to see possible version."
            )
            return

        version = f"hpcompiler-v{args.action}"

        target = os.path.join(self.context.versions_dir, version)
        current = self.context.current_version()

        if version == current:
            self.cli.error("Cannot uninstall the current active version.")
            return

        if not os.path.exists(target):
            self.cli.error(f"Version {args.action} not installed.")
            self.cli.info(
                "Usage: hp uninstall [x.y.z] or `hp list` to see possible version."
            )
            return

        confirm = input(
            self.cli.color(
                f"Warning   Are you sure you want to uninstall {args.action}? (y/N) ",
                self.cli.theme["warning"],
            )
        )

        # confirm = input(
        #     self.cli.warning2(
        #         f"Are you sure you want to uninstall {args.action}? (y/N) "
        #     )
        # )

        if confirm.lower() != "y":
            self.cli.info("Aborted.")
            return

        try:
            shutil.rmtree(target)
            self.cli.success(f"Version hpcompiler-v{args.action} uninstalled.")
        except Exception as e:
            self.cli.error(f"Uninstall failed: {e}")


# -------------------------------------------------
# CLI
# -------------------------------------------------


def main():

    parser = argparse.ArgumentParser(prog="hp")

    parser.add_argument(
        "command",
        nargs="?",
        help="Valid commands: doctor, clean, run, reset, init and config or input file",
    )
    # parser.add_argument("file", nargs="?", default=DEFAULT_FILE)
    parser.add_argument("action", nargs="?", default=None)
    parser.add_argument("key", nargs="?", default=None)
    parser.add_argument("value", nargs="?", default=None)
    parser.add_argument(
        "--targets",
        choices=["python", "cs", "php", "java", "js"],
        help="Comma-separated targets (python,cs,php,java)",
        default="python",
    )

    parser.add_argument(
        "--compile",
        action="store_true",
        help="Just compile to target.",
    )
    parser.add_argument(
        "--force",
        action="store_true",
        help="Force to compile, even thought it was compiled previously.",
    )
    parser.add_argument(
        "--console",
        action="store_true",
        help="Show generated code in console.",
    )
    parser.add_argument("--version", action="store_true", help="Show version hp CLI.")
    parser.add_argument(
        "--verbose", action="store_true", help="Show detailed execution output"
    )
    parser.add_argument(
        "--pid",
        action="store_true",
        help="Show pid, ppid, process name of processes generated.",
    )
    parser.add_argument(
        "--theme",
        choices=list(THEMES.keys()),
        help="Override CLI theme",
    )
    parser.add_argument(
        "--browser",
        choices=["Google Chrome", "Safari", "Opera", "Firefox"],
        help="Override configured browser",
    )

    args = parser.parse_args()

    if args.version:
        print(f"hp {CLI_VERSION}")
        sys.exit(0)

    targets = [t.strip() for t in args.targets.split(",")]

    context = ProjectContext(Path.cwd())

    context.verbose = args.verbose

    theme = args.theme or context.get_value("theme", "dark")
    cli = CLIStyle(theme)

    context.ensure_initialized()

    # It is not used for the moment.
    browser = args.browser or context.get_value("browser", "Google Chrome")

    COMMANDS = {
        "doctor": Doctor,
        "clean": Cleaner,
        "init": Initializer,
        "reset": Resetter,
        "config": ConfigHander,
        "list": ListVersions,
        "use": UseVersion,
        "uninstall": UninstallVersion,
    }
    RUN_ALIASES = {"run": "Running", "start": "Starting", "execute": "Executing"}

    command = args.command

    action_word = "Running"
    if not command:
        file = DEFAULT_FILE  # implicit run without a file.

    elif command in RUN_ALIASES:
        action_word = RUN_ALIASES[command]

        if args.action:  # There is a file after run.
            file = args.action
        else:
            file = DEFAULT_FILE  # explicit run without a file.

    elif command in COMMANDS:
        COMMANDS[command](context, cli).run(args)
        return

    else:
        file = command  # implicit run with a file.

    hp_file = Path.cwd() / file
    if not hp_file.is_file():
        cli.error(f"File '{file}' does not exist or is not a valid file.")
        cli.info(f"Verify filename is correct and ends with '.hp'.")
        sys.exit(1)

    manager = TargetManager(context, cli, hp_file, action_word)

    success = manager.run_targets(
        targets, args.force, args.console, args.compile, args.pid
    )
    # success = manager.run_targets(targets, args.force, args.console, args.compile)

    if not success:
        sys.exit(1)

    # if not args.command:
    #     parser.print_help()
    #     sys.exit(1)

    sys.exit(0)


# -------------------------------------------------
# Entry Point
# -------------------------------------------------

if __name__ == "__main__":
    main()
