import subprocess
from pathlib import Path
from typing import Optional, Union, Iterator
from typing_extensions import Annotated
import gettext

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

from bs4 import BeautifulSoup  # noqa: E402
from .eggs import get_yolk  # noqa: E402
from .model import MaXProjectConfig  # noqa: E402
from .config import (  # noqa: E402
    WELCOME_PAGE,
    USER_MAX_DIR,
    CACHE_DIR,
    MAX_CONFIG_FILE,
    BASEX_DISTRO,
    SAXON_DISTRO,
    MAX_DISTRO,
    CURRENT_MAX_DISTRO_DIR,
    JAVA_DISTROS,
    WEB_PORT,
    WEB_HOST,
    BASEX_PORT,
    STOP_PORT,
)
import geler  # noqa: E402
import requests  # noqa: E402
import os  # noqa: E402
import hashlib  # noqa: E402
import platform  # noqa: E402
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
import shutil  # noqa: E402
from functools import lru_cache  # noqa: E402
import socket  # noqa: E402
from .model import db, MaxInstall  # noqa: E402
# from simple_term_menu import TerminalMenu  # 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()


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


@lru_cache
def class_path_separator() -> str:
    separator = ":"
    current_os = current_system().split("/")[0]
    if current_os == "windows":
        separator = ";"
    return separator


@lru_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


@lru_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


@lru_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_max_distro() -> Path:
    destination_folder = Path(USER_MAX_DIR, "max")
    if not destination_folder.exists():
        destination_archive = Path(USER_MAX_DIR, "max.zip")
        cached_download(MAX_DISTRO, destination_archive)
        unzip(destination_archive, destination_folder)
    return destination_folder


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 _install_new_max_instance(root_directory: Path):
    print(
        "[bold]"
        + _("Initialisation d'une nouvelle instance de MaX dans {}").format(
            root_directory
        )
        + "[/bold]"
    )
    config_file_path = Path(root_directory, "config.xml")
    dot_max_dir = Path(root_directory, ".max")
    dot_max_dir.mkdir(parents=True, exist_ok=False)

    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)

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

    shutil.copytree(
        Path(USER_MAX_DIR, "max", CURRENT_MAX_DISTRO_DIR, "src", "main", "core"),
        Path(dot_max_dir, "basex", "webapp", "max", "core"),
    )
    shutil.copytree(
        Path(USER_MAX_DIR, "max", CURRENT_MAX_DISTRO_DIR, "src", "main", "bundles"),
        Path(dot_max_dir, "basex", "webapp", "max", "bundles"),
    )

    shutil.copytree(
        Path(USER_MAX_DIR, "max", CURRENT_MAX_DISTRO_DIR, "src", "resources"),
        Path(dot_max_dir, "resources"),
    )

    shutil.copy(Path(dot_max_dir, "resources", "config.xml.sample"), config_file_path)

    _sync_bundles(root_directory)

    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]"
        )
    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)

        shutil.copytree(
            Path(USER_MAX_DIR, "max", CURRENT_MAX_DISTRO_DIR, "src", "main", "core"),
            Path(dot_max_dir, "basex", "webapp", "max", "core"),
        )
        shutil.copytree(
            Path(USER_MAX_DIR, "max", CURRENT_MAX_DISTRO_DIR, "src", "main", "bundles"),
            Path(dot_max_dir, "basex", "webapp", "max", "bundles"),
        )

        shutil.copytree(
            Path(USER_MAX_DIR, "max", CURRENT_MAX_DISTRO_DIR, "src", "resources"),
            Path(dot_max_dir, "resources"),
        )

    _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()
    ensure_max_distro()
    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()


@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(),
):
    ensure_java()
    ensure_max_distro()
    root_directory, is_max = ensure_available_max_directory(directory)
    if is_max:
        print(_("Le dossier contient une instance de MaX. Utiliser la commande sync"))
        raise typer.Exit()
    else:
        _install_new_max_instance(root_directory)


@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()

        shutil.copytree(  ## BaseX databases
            Path(USER_MAX_DIR, "max", CURRENT_MAX_DISTRO_DIR, "fixtures", "max"),
            Path(cur_dir, ".max", "basex", "data", "max"),
            dirs_exist_ok=True,
        )
        shutil.copytree(
            Path(
                USER_MAX_DIR, "max", CURRENT_MAX_DISTRO_DIR, "fixtures", "content_html"
            ),
            Path(cur_dir, "content_html"),
            dirs_exist_ok=True,
        )
        shutil.copytree(
            Path(USER_MAX_DIR, "max", CURRENT_MAX_DISTRO_DIR, "fixtures", "templates"),
            Path(cur_dir, "templates"),
            dirs_exist_ok=True,
        )
        shutil.copytree(
            Path(USER_MAX_DIR, "max", CURRENT_MAX_DISTRO_DIR, "fixtures", "autoroute"),
            Path(cur_dir, "autoroute"),
            dirs_exist_ok=True,
        )

        shutil.copy(
            Path(USER_MAX_DIR, "max", CURRENT_MAX_DISTRO_DIR, "fixtures", "config.xml"),
            Path(cur_dir, "config.xml"),
        )

        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)
    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:
        # for lang in config.languages:
        # start_url = f"http://localhost:{port_number}/{lang}/tdm.html"
        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:  # todo install from archive
        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)
    # options = []
    # for max_install in climax_db.projects():
    #    options.append(max_install.path)
    # terminal_menu = TerminalMenu(options)
    # menu_entry_index = terminal_menu.show()
    # subprocess.run(["cd", options[menu_entry_index]], shell=True)


# @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()
