import subprocess
from pathlib import Path
from typing import Optional, Union, Iterator, List
from typing_extensions import Annotated
import gettext
import tempfile
import geler
import requests
import os
import hashlib
import platform
from bs4 import BeautifulSoup
import shutil
from functools import cache
import socket
from .eggs import get_yolk
from .model import MaXProjectConfig, db, MaxInstall
from .config import (
    WELCOME_PAGE,
    USER_MAX_DIR,
    CACHE_DIR,
    MAX_CONFIG_FILE,
    BASEX_DISTRO,
    SAXON_DISTRO,
    JAVA_DISTROS,
    WEB_PORT,
    WEB_HOST,
    BASEX_PORT,
    STOP_PORT,
    max_releases,
    max_release,
    latest_max_release,
)

locales_dir = Path(__file__).parent / "locales"
gettext.bindtextdomain("messages", str(locales_dir))
gettext.textdomain("messages")
_ = gettext.gettext


import typer  # noqa: E402
from rich import print  # noqa: E402
from rich.progress import track  # noqa: E402
from rich.console import Console  # noqa: E402
from rich.table import Table  # noqa: E402

climax_db = db()


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


def _closer_free_port_to(start_port: int) -> int:
    limit_iter = 0
    while _port_is_in_use(start_port) and limit_iter < 50:
        start_port = start_port + 1
        limit_iter = limit_iter + 1
    return start_port


def _free_port() -> int:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("localhost", 0))
    port = s.getsockname()[1]
    s.close()
    return port


def _scan_dir(start_path: Union[str, Path], extension: str = None) -> Iterator[Path]:
    for root, dirs, files in os.walk(start_path):
        for file in files:
            if extension:
                if file[(len(extension) * -1) :].lower() == extension.lower():
                    yield os.path.join(root, file)
            else:
                yield Path(os.path.join(root, file)).resolve()


@cache
def current_system() -> str:
    return f"{platform.system()}/{platform.machine()}".lower()


@cache
def current_os() -> str:
    return current_system().split("/")[0]


@cache
def class_path_separator() -> str:
    separator = ":"
    if current_os() == "windows":
        separator = ";"
    return separator


@cache
def cp_paths() -> str:
    basex_dir_path = Path(os.getcwd(), ".max", "basex")
    paths = class_path_separator().join(
        [
            str(Path(basex_dir_path, "BaseX.jar")),
            str(Path(basex_dir_path, "lib", "custom", "*")),
            str(Path(basex_dir_path, "lib", "*")),
        ]
    )
    return paths


@cache
def find_jdk(cur_sys: str = None) -> Optional[str]:
    if not cur_sys:
        cur_sys = current_system()
    return JAVA_DISTROS.get(cur_sys, None)


def dir_is_max(directory: Path = None) -> bool:
    if directory is None:
        directory = os.getcwd()
    directory = Path(directory)
    config_file = Path(directory, "config.xml")
    if config_file.exists():
        with open(config_file, "r") as f:
            soup = BeautifulSoup(f, "xml")
            conf = soup.find("configuration")
            if conf:
                ns = conf.get("xmlns")
                if ns == "http://certic.unicaen.fr/max/ns/1.0":
                    return True
    return False


def check_cwd_is_max():
    if not dir_is_max():
        print(
            "[red]{}[/red]".format(_("Le dossier n'est pas une installation de MaX."))
        )
        raise typer.Exit(code=1)


def max_config() -> MaXProjectConfig:
    return MaXProjectConfig(Path(os.getcwd(), MAX_CONFIG_FILE))


def unzip(source: Union[Path, str], destination: Union[Path, str]) -> bool:
    shutil.unpack_archive(source, destination)
    os.remove(source)
    return True


def cached_download(
    source: str, destination: Union[Path, str], chunk_size: int = 1024
) -> bool:
    cache_path = Path(CACHE_DIR, hashlib.sha1(source.encode("utf-8")).hexdigest())
    if cache_path.exists():
        shutil.copy(cache_path, destination)
    else:
        response = requests.get(source, stream=True)
        if response.status_code != 200:
            print("[red]{}[/red] {}".format(_("Impossible de télécharger"), source))
            raise typer.Exit(code=1)
        try:
            total_size = int(response.headers["Content-length"])
            seq_len = total_size // chunk_size
        except KeyError:
            # missing Content-length
            seq_len = 100
        with open(cache_path, "wb") as f:
            for data in track(
                response.iter_content(chunk_size=chunk_size),
                total=seq_len,
                description=os.path.basename(source),
            ):
                f.write(data)
        shutil.copy(cache_path, destination)
    return True


@cache
def ensure_java() -> Path:
    java_bin = shutil.which("java")
    if not java_bin:
        cur_sys = current_system()
        suitable_jdk = find_jdk(cur_sys)
        if suitable_jdk:
            destination_folder = Path(USER_MAX_DIR, "jdk")
            if not destination_folder.exists():
                destination_archive = Path(USER_MAX_DIR, os.path.basename(suitable_jdk))
                cached_download(suitable_jdk, destination_archive)
                unzip(destination_archive, destination_folder)
            if cur_sys in ["linux/x86_64", "linux/amd64"]:
                java_bin = Path(destination_folder, "jdk-22.0.2", "bin", "java")
            if cur_sys in ["windows/x86_64", "windows/amd64"]:
                java_bin = Path(destination_folder, "jdk-22.0.2", "bin", "java.exe")
            if cur_sys == "darwin/arm64":
                java_bin = Path(
                    destination_folder,
                    "jdk-22.0.2.jdk",
                    "Contents",
                    "Home",
                    "bin",
                    "java",
                )
    if not java_bin:
        print(
            "[red]"
            + _("Java est requis pour utiliser MaX: https://openjdk.org/install/")
            + "[/red]"
        )
        raise typer.Exit(code=1)
    return java_bin


def ensure_available_max_directory(directory: Optional[Path]) -> tuple[Path, bool]:
    if not directory:
        directory = os.getcwd()
    directory = Path(directory)
    if not directory.exists():
        directory.mkdir(parents=True, exist_ok=False)
    directory = directory.resolve()
    is_empty = not any(directory.iterdir())
    is_max = dir_is_max(directory)
    if not is_empty and not is_max:
        print(
            "[red]{}[/red]".format(
                _(
                    "Le dossier n'est pas vide et n'est pas un projet MaX valide, veuillez en choisir un autre."
                )
            )
        )
        raise typer.Exit(code=1)
    return directory, is_max


app = typer.Typer(
    help=_("Utilitaire en ligne de commande pour la gestion des projets MaX")
)


def _sync_bundles(root_directory: Path):
    ignore_file = ".ignore"
    dot_max_dir = Path(root_directory, ".max")
    bundles_dir = Path(dot_max_dir, "basex", "webapp", "max", "bundles")
    # first deactivate everything
    for item in os.listdir(bundles_dir):
        if os.path.isdir(Path(bundles_dir, item)):
            Path(bundles_dir, item, ignore_file).touch()
    config = MaXProjectConfig(Path(root_directory, "config.xml"))
    # then activate or install what is in config file
    for bundle_name, bundle in config.bundles.items():
        bundle_dir = Path(bundles_dir, bundle_name)
        if os.path.isdir(bundle_dir):
            Path(bundle_dir, ignore_file).unlink(missing_ok=True)
        else:
            if str(bundle["url"]).startswith("http://") or str(
                bundle["url"]
            ).startswith("https://"):
                bundle_archive_path = Path(bundles_dir, f"{bundle_name}.zip")
                cached_download(bundle["url"], bundle_archive_path)
                shutil.unpack_archive(bundle_archive_path, bundles_dir)
                dir_name, _ = os.path.splitext(os.path.basename(bundle["url"]))
                Path(bundles_dir, dir_name).rename(bundle_dir)
                bundle_archive_path.unlink()
            if str(bundle["url"]).startswith("local://"):
                zip_name = str(bundle["url"]).lstrip("local://")
                bundle_archive_path = Path(
                    dot_max_dir, "resources", "local_bundles", f"{zip_name}"
                )
                shutil.unpack_archive(bundle_archive_path, bundles_dir)
                dir_name, _ = os.path.splitext(os.path.basename(bundle["url"]))
                Path(bundles_dir, dir_name).rename(bundle_dir)
                bundle_archive_path.unlink()


def _copy_max_tree(
    dot_max_dir: Union[str, Path], max_release_name: str, max_release_url: str
):
    with tempfile.TemporaryDirectory() as tmpdirname:
        zip_destination = Path(tmpdirname, "max.zip")
        cached_download(max_release_url, zip_destination)
        unzip(zip_destination, tmpdirname)
        shutil.copytree(
            Path(tmpdirname, f"max-v2-{max_release_name}", "src", "main", "core"),
            Path(dot_max_dir, "basex", "webapp", "max", "core"),
        )
        shutil.copytree(
            Path(tmpdirname, f"max-v2-{max_release_name}", "src", "main", "bundles"),
            Path(dot_max_dir, "basex", "webapp", "max", "bundles"),
        )
        shutil.copytree(
            Path(tmpdirname, f"max-v2-{max_release_name}", "src", "resources"),
            Path(dot_max_dir, "resources"),
        )


def _install_new_max_instance(root_directory: Path, init_values: dict = None):
    print(
        "[bold]"
        + _("Initialisation d'une nouvelle instance de MaX dans {}").format(
            root_directory
        )
        + "[/bold]"
    )
    config_file_path = Path(root_directory, "config.xml")

    with open(Path(root_directory, ".gitignore"), "w") as f:
        f.write(".max\n")

    conf = MaXProjectConfig(config_file_path)
    if init_values is not None:
        conf.title = init_values.get("title", "")
        conf.env = init_values.get("env", "prod")
        conf.max_version = {
            "name": init_values.get("max_version"),
            "url": init_values.get("max_url"),
        }
    else:
        conf.title = "inconnu"
        conf.env = "prod"
        latest_max = latest_max_release()
        conf.max_version = {
            "name": latest_max["name"],
            "url": latest_max["zip_url"],
        }
    conf.write()

    _sync_max_instance(root_directory, verbose=False)

    Path(root_directory, "content_html", "fr").mkdir(exist_ok=True, parents=True)
    welcome_page = Path(root_directory, "content_html", "fr", "index.html")
    with open(welcome_page, "w") as f:
        f.write(WELCOME_PAGE)
    climax_db.save(MaxInstall(None, str(root_directory.resolve())))
    climax_db.commit()
    print(
        "[bold]"
        + _("La nouvelle instance de MaX est prête dans {}").format(root_directory)
        + "[/bold]"
    )
    print(
        _(
            'Vous pouvez éventuellement installer un projet de démonstration avec "climax demo".'
        )
    )


def _sync_max_instance(root_directory: Path, verbose=True):
    if verbose:
        print(
            "[bold]"
            + _("Initialisation de l'instance de MaX existant dans {}").format(
                root_directory
            )
            + "[/bold]"
        )
    config = MaXProjectConfig(Path(root_directory, "config.xml"))
    dot_max_dir = Path(root_directory, ".max")
    if not os.path.isdir(dot_max_dir):
        dot_max_dir.mkdir(parents=True, exist_ok=True)
        basex_zip_destination = Path(dot_max_dir, "BaseX.zip")
        cached_download(BASEX_DISTRO, basex_zip_destination)
        unzip(basex_zip_destination, dot_max_dir)
        saxon_zip_destination = Path(
            dot_max_dir, "basex", "lib", "custom", "Saxon-HE-10.8.jar"
        )
        cached_download(SAXON_DISTRO, saxon_zip_destination)

        _copy_max_tree(
            Path(root_directory, ".max"),
            config.max_version.get("name"),
            config.max_version.get("url"),
        )

    _sync_bundles(root_directory)
    if verbose:
        print(
            "[bold]"
            + _("L'instance de MaX est prête dans {}").format(root_directory)
            + "[/bold]"
        )


@app.command(help=_("Initialisation d'une instance existante de MaX"))
def sync(
    directory: Annotated[
        Optional[Path], typer.Argument(help=_("chemin vers un dossier"))
    ] = os.getcwd(),
):
    ensure_java()
    root_directory, is_max = ensure_available_max_directory(directory)

    if is_max:
        _sync_max_instance(root_directory)
        climax_db.save(MaxInstall(None, str(root_directory.resolve())))
        climax_db.commit()
    else:
        print(
            _(
                "Le dossier ne contient pas une instance de MaX. Utiliser la commande new"
            )
        )
        raise typer.Exit()


def _choose_between(values: List[str], label: str = "") -> str:
    try:
        from simple_term_menu import TerminalMenu

        terminal_menu = TerminalMenu(values, title=label)
        menu_entry_index = terminal_menu.show()
        return values[menu_entry_index]
    except NotImplementedError:  # mainly windows
        choice = None
        while choice not in values:
            choice = typer.prompt("{} ({})".format(_(label), ", ".join(values)))
        return choice


@app.command(help=_("Création d'une nouvelle instance de MaX"))
def new(
    directory: Annotated[
        Optional[Path], typer.Argument(help=_("chemin vers un dossier"))
    ] = os.getcwd(),
    interactive: bool = False,
):
    ensure_java()
    root_directory, is_max = ensure_available_max_directory(directory)
    if is_max:
        print(_("Le dossier contient une instance de MaX. Utilisez la commande sync"))
        raise typer.Exit()
    init_values = None
    if interactive:
        init_values = {}
        init_values["max_version"] = _choose_between(
            [x["name"] for x in max_releases()], _("Version de max")
        )
        init_values["max_url"] = max_release(init_values["max_version"]).get("zip_url")
        init_values["env"] = _choose_between(["dev", "prod"], _("Environnement"))
        init_values["title"] = typer.prompt(_("Titre du projet"))
    _install_new_max_instance(root_directory, init_values)


@app.command(
    help=_("Installe une édition de démonstration dans l'instance de MaX en cours")
)
def demo():
    check_cwd_is_max()
    if typer.confirm(
        _("Voulez-vous installer une édition de démonstration ?"), default=False
    ):
        cur_dir = os.getcwd()
        config = MaXProjectConfig(Path(cur_dir, "config.xml"))
        with tempfile.TemporaryDirectory() as tmpdirname:
            zip_destination = Path(tmpdirname, "max.zip")
            cached_download(config.max_version["url"], zip_destination)
            unzip(zip_destination, tmpdirname)
            tmp_max_release_dir = Path(
                tmpdirname, f"max-v2-{config.max_version['name']}"
            )

            shutil.copytree(  ## BaseX databases
                Path(tmp_max_release_dir, "fixtures", "max"),
                Path(cur_dir, ".max", "basex", "data", "max"),
                dirs_exist_ok=True,
            )

            shutil.copytree(
                Path(tmp_max_release_dir, "fixtures"),
                Path(cur_dir),
                dirs_exist_ok=True,
            )
            shutil.rmtree(Path(cur_dir, "max"), ignore_errors=True)
            _sync_max_instance(cur_dir, verbose=False)

        print("[green]" + _("édition de démonstration installée") + "[/green]")
        print(_("Vous pouvez démarrer MaX avec la commande [bold]climax start[/bold]"))


@app.command(help=_("Arrête l'instance de MaX du dossier en cours"))
def stop(http_stop_port: int = STOP_PORT):
    check_cwd_is_max()
    java_bin_path = ensure_java()
    process_args = [
        str(java_bin_path),
        "-cp",
        cp_paths(),
        "-Xmx2g",
        "org.basex.BaseXHTTP",
        f"-s{http_stop_port}",
        "stop",
    ]
    subprocess.run(process_args)


@app.command(help=_("Démarre l'instance de MaX du dossier en cours"))
def start(
    http_host: str = WEB_HOST,
    http_port: int = WEB_PORT,
    basex_port: int = BASEX_PORT,
    http_stop_port: int = STOP_PORT,
    service: Annotated[
        bool,
        typer.Option(
            help=_(
                'Démarrer MaX en tant que service. Utilisez "climax stop" pour arrêter le service.'
            )
        ),
    ] = False,
):
    basex_port = _closer_free_port_to(basex_port)
    http_port = _closer_free_port_to(http_port)
    if http_stop_port <= http_port:
        http_stop_port = http_port + 1
    http_stop_port = _closer_free_port_to(http_stop_port)
    check_cwd_is_max()
    java_bin_path = ensure_java()
    process_args = [
        str(java_bin_path),
        "-cp",
        cp_paths(),
        "-Xmx2g",
        "org.basex.BaseXHTTP",
        f"-p{basex_port}",
        f"-h{http_port}",
        f"-n{http_host}",
        f"-s{http_stop_port}",
    ]
    if service:
        process_args.append("-S")
    print(
        "[green]"
        + _("Démarrage de MaX sur http://{}:{}").format(http_host, http_port)
        + "[/green]"
    )
    subprocess.run(process_args)


@app.command(help=_("Efface le cache de climax"))
def cache_clear():
    for p in CACHE_DIR.iterdir():
        if p.is_file():
            p.unlink(missing_ok=True)
    shutil.rmtree(Path(USER_MAX_DIR, "max"), ignore_errors=True)


@app.command(help=_("Affiche la configuration de MaX"))
def info():
    check_cwd_is_max()
    config = max_config()
    console = Console()
    table = Table(_("Nom"), _("Valeur"), show_lines=True)
    table.add_row(_("Version de MaX"), config.max_version.get("name"))
    table.add_row(_("Titre"), config.title)
    table.add_row(_("Environnement"), config.env)
    table.add_row(_("Langues"), ", ".join(config.languages))
    table.add_row(_("Bundles installés"), ", ".join(config.bundles.keys()))
    table.add_row(_("Bundle de vocabulaire XML"), config.vocabulary_bundle)
    console.print(table)


@app.command(help=_("Fait une copie HTML statique du projet dans le dossier"))
def freeze(
    directory: Annotated[
        Optional[Path], typer.Argument(help=_("chemin vers un dossier"))
    ] = Path(os.getcwd(), "output"),
    debug: bool = False,
):
    max_config()
    # start server on specific port
    port_number = _free_port()
    stop_port = port_number + 1
    stop_port = _closer_free_port_to(stop_port)
    start(WEB_HOST, port_number, http_stop_port=stop_port, service=True)
    # copy website
    try:
        start_url = f"http://localhost:{port_number}/"
        geler.freeze(start_url, directory)
    except Exception as e:
        stop(stop_port)
        raise e
    # stop server
    stop(stop_port)
    print("[green]" + _("Site copié dans {}").format(str(directory)) + "[/green]")


@app.command(help=_("Supprime un bundle pour l'instance de Max en cours"))
def bundles_remove(bundle_name: str):
    check_cwd_is_max()
    config = max_config()
    keep_bundles = {}
    bundle_name = bundle_name.lower().strip()
    if bundle_name not in config.bundles.keys():
        print(_("[red]{} n'est pas un bundle actif[/red]").format(bundle_name))
        raise typer.Exit(code=1)
    for active_bundle_name, active_bundle_url in config.bundles.items():
        if active_bundle_name.strip() != bundle_name.strip():
            keep_bundles[active_bundle_name] = active_bundle_url
    config.bundles = keep_bundles
    config.write()
    bundles_list()


@app.command(help=_("Liste les bundles disponibles"))
def bundles_list():
    check_cwd_is_max()
    config = max_config()
    console = Console()
    table = Table(_("Nom"), _("Installé"), _("Description"), show_lines=True)
    bundles_done = {}

    for bundle_name, bundle in config.available_bundles.items():
        bundles_done[bundle_name] = [
            "[bold]{}[/bold]".format(bundle["name"]),
            "[green]{}[/green]".format(_("oui"))
            if bundle["active"]
            else "[red]{}[/red]".format(_("non")),
            bundle["description"],
        ]

    for bundle_name, bundle in config.bundles.items():
        if bundle_name not in bundles_done.keys():
            bundles_done[bundle_name] = [
                "[bold]{}[/bold]".format(bundle_name),
                "[green]{}[/green]".format(_("oui")),
                _("(bundle local sans description)"),
            ]
    for k, row in dict(sorted(bundles_done.items())).items():
        table.add_row(*row)
    console.print(table)


@app.command(help=_("Ajoute un bundle pour l'instance de Max en cours"))
def bundles_add(bundle_name: str):
    from_archive = None
    check_cwd_is_max()
    config = max_config()
    current_bundles_config = config.bundles
    # bundle_name is a local archive
    if bundle_name.endswith(".zip") and Path(bundle_name).is_file():
        from_archive = bundle_name
        bundle_name = os.path.splitext(os.path.basename(bundle_name))[0]
    if from_archive is not None:
        if type(from_archive) is str:
            from_archive = Path(from_archive)
        if not from_archive.is_file():
            print(
                _("[red][bold]{}[/bold] n'est pas un chemin valide[/red]").format(
                    from_archive
                )
            )
            raise typer.Exit(code=1)
        fname, extension = os.path.splitext(from_archive)
        if extension != ".zip":
            print(_("[red]{} n'est pas un bundle[/red]").format(from_archive))
            raise typer.Exit(code=1)
        local_destination = Path(
            os.getcwd(),
            ".max",
            "resources",
            "local_bundles",
            os.path.basename(from_archive),
        )
        Path(os.path.dirname(local_destination)).mkdir(exist_ok=True, parents=True)
        shutil.copy(from_archive, local_destination)
        current_bundles_config[bundle_name] = {
            "name": bundle_name,
            "url": f"local://{os.path.basename(local_destination)}",
        }
        config.bundles = current_bundles_config
        config.write()
        _sync_max_instance(os.getcwd(), verbose=False)
        bundles_list()
    else:
        if bundle_name not in config.available_bundles.keys():
            print(_("[red]{} n'est pas un bundle disponible[/red]").format(bundle_name))
            raise typer.Exit(code=1)
        else:
            for (
                available_bundle_name,
                available_bundle,
            ) in config.available_bundles.items():
                if available_bundle_name == bundle_name:
                    if available_bundle["vocabulary"]:
                        config.vocabulary_bundle = bundle_name
                    current_bundles_config[bundle_name] = {
                        "name": bundle_name,
                        "url": available_bundle["url"],
                    }
                    config.bundles = current_bundles_config
                    config.write()
                    _sync_max_instance(os.getcwd(), verbose=False)
                    bundles_list()


@app.command(help=_("Ajoute un fichier ou un dossier XML à l'instance de Max en cours"))
def feed(feed_path: Path):
    check_cwd_is_max()
    java_bin_path = ensure_java()
    feed_path = Path(feed_path).resolve()
    if not feed_path.is_file() and not feed_path.is_dir():
        print(_("[red]{} n'existe pas[/red]").format(feed_path))
        raise typer.Exit(code=1)
    process_args = [
        str(java_bin_path),
        "-cp",
        cp_paths(),
        "-Xmx2g",
        "org.basex.BaseX",
        "if(not(db:exists('max'))) then db:create('max') else ()",
    ]
    subprocess.run(process_args)

    if feed_path.is_file():
        process_args = [
            str(java_bin_path),
            "-cp",
            cp_paths(),
            "-Xmx2g",
            "org.basex.BaseX",
            f"db:put('max', '{feed_path}', '{feed_path.name}')",
            # f"-cOPEN max; REPLACE {feed_path.resolve()}",
        ]
        subprocess.run(process_args)
    elif feed_path.is_dir():
        offset = len(str(feed_path))
        for f in _scan_dir(feed_path, ".xml"):
            process_args = [
                str(java_bin_path),
                "-cp",
                cp_paths(),
                "-Xmx2g",
                "org.basex.BaseX",
                f"db:put('max', '{f}', '{str(f)[offset:]}')",
                # f"-cOPEN max; REPLACE {f}",
            ]
        subprocess.run(process_args)


@app.command(help=_("Lance le client de BaseX"))
def basex():
    check_cwd_is_max()
    java_bin_path = ensure_java()
    process_args = [
        str(java_bin_path),
        "-cp",
        cp_paths(),
        "-Xmx2g",
        "org.basex.BaseX",
    ]
    subprocess.run(process_args)


@app.command(help=_("Liste les templates du bundle de vocabulaire en cours"))
def templates_list():
    check_cwd_is_max()
    config = max_config()
    vocab_bundle = config.vocabulary_bundle
    tpl_dir = Path(
        os.getcwd(),
        ".max",
        "basex",
        "webapp",
        "max",
        "bundles",
        vocab_bundle,
        "templates",
    )
    tpls_list = []
    offset = len(str(tpl_dir))
    for f in _scan_dir(tpl_dir):
        if f.is_file():
            tpls_list.append(str(f)[offset:])
    for item in sorted(tpls_list):
        print(item)


@app.command(help=_("Liste les projets gérés par climax"))
def projects():
    console = Console()
    table = Table(_("Projet"), _("Chemin"), show_lines=True)
    for max_install in climax_db.projects():
        if max_install.exists():
            config = max_install.config()
            table.add_row(config.title, max_install.path)
    console.print(table)


# @app.command(help=_("Application web (test)"))
# def ui(max_dir: str = None):
#    from .gui import app

#    app.max_directory = max_dir
#    app.run(port=8888, reloader=True, debug=True)


get_yolk()

if __name__ == "__main__":
    app()
