# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/Mkdocs.ipynb.

# %% auto 0
__all__ = ['new', 'nbdev_mkdocs_docs', 'prepare', 'preview']

# %% ../nbs/Mkdocs.ipynb 1
import collections
import datetime
import importlib
import itertools
import multiprocessing
import os
import pkgutil
import re
import shlex
import shutil
import subprocess  # nosec: B404
import sys
import textwrap
import types
from configparser import ConfigParser
from inspect import getmembers, isfunction, isclass, ismethod, getmodule, iscoroutine
from pathlib import Path
from typing import *

import nbdev
import typer
import yaml
import nbformat
from configupdater import ConfigUpdater, Section
from configupdater.option import Option
from fastcore.shutil import move
from nbdev.doclinks import nbdev_export, NbdevLookup
from nbdev.frontmatter import FrontmatterProc, _fm2dict
from nbdev.process import NBProcessor
from nbdev.quarto import nbdev_readme
from nbdev.quarto import prepare as nbdev_prepare
from nbdev.quarto import refresh_quarto_yml
from nbdev.serve import proc_nbs
from fastcore.basics import merge
from fastcore.foundation import L
from ._helpers.cli_doc import generate_cli_doc
from nbdev_mkdocs._helpers.utils import (
    get_value_from_config,
    set_cwd,
    raise_error_and_exit,
)
from ._package_data import get_root_data_path
from .social_image_generator import _update_social_image_in_mkdocs_yml
from ._helpers.doc_links_utils import fix_sym_links

# %% ../nbs/Mkdocs.ipynb 5
def _create_mkdocs_dir(root_path: str) -> None:
    """Create a mkdocs directory in the root path.

    Args:
        root_path: The root path of the project.

    Returns:
        None

    Raises:
        typer.Exit: If the mkdocs_template path does not exist.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    mkdocs_template_path = get_root_data_path() / "mkdocs_template"
    if not mkdocs_template_path.exists():
        raise_error_and_exit(
            f"Unexpected error: path {mkdocs_template_path.resolve()} does not exists!"
        )
    dst_path = Path(root_path) / "mkdocs"
    if dst_path.exists():
        typer.secho(
            f"Directory {dst_path.resolve()} already exist, skipping its creation.",
        )
    else:
        shutil.copytree(mkdocs_template_path, dst_path)
        #         shutil.move(dst_path.parent / "mkdocs_template", dst_path)
        typer.secho(
            f"Directory {dst_path.resolve()} created.",
        )

# %% ../nbs/Mkdocs.ipynb 8
_mkdocs_template_path = get_root_data_path() / "mkdocs_template.yml"

# %% ../nbs/Mkdocs.ipynb 10
with open(_mkdocs_template_path, "r") as f:
    _mkdocs_template = f.read()

# %% ../nbs/Mkdocs.ipynb 12
def _get_kwargs_from_settings(
    settings_path: Path, mkdocs_template: Optional[str] = None
) -> Dict[str, str]:
    """Get the values from the settings file

    Args:
        settings_path: The path to the settings file
        mkdocs_template: The mkdocs template to use

    Returns:
        A dictionary of the kwargs

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    config = ConfigParser()
    config.read(settings_path)
    if not mkdocs_template:
        mkdocs_template = _mkdocs_template
    keys = [s[1:-1] for s in re.findall("\{.*?\}", _mkdocs_template)]
    kwargs = {k: config["DEFAULT"][k] for k in keys}
    return kwargs

# %% ../nbs/Mkdocs.ipynb 14
def _create_mkdocs_yaml(root_path: str) -> None:
    """Create mkdocs.yml file

    Args:
        root_path: The root path of the project

    Raises:
        ValueError: If root_path is invalid or does not exists

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    try:
        # create mkdocs folder if necessary
        mkdocs_path = Path(root_path) / "mkdocs" / "mkdocs.yml"
        mkdocs_path.parent.mkdir(exist_ok=True)
        # mkdocs.yml already exists, just return
        if mkdocs_path.exists():
            typer.secho(
                f"Path '{mkdocs_path.resolve()}' exists, skipping generation of it."
            )
            return

        # get default values from settings.ini
        settings_path = Path(root_path) / "settings.ini"
        kwargs = _get_kwargs_from_settings(settings_path)
        mkdocs_yaml_str = _mkdocs_template.format(**kwargs)
        with open(mkdocs_path, "w") as f:
            f.write(mkdocs_yaml_str)
            typer.secho(f"File '{mkdocs_path.resolve()}' generated.")
            return
    except Exception as e:
        raise_error_and_exit(
            f"Unexpected Error while creating '{mkdocs_path.resolve()}': {e}"
        )

# %% ../nbs/Mkdocs.ipynb 17
_summary_template = """{sidebar}
- API
{api}
- CLI
{cli}
- [Releases]{changelog}
"""


def _create_summary_template(root_path: str) -> None:
    """Create a summary template file for mkdocs.

    Args:
        root_path: The root path of the project.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    try:
        # create mkdocs folder if necessary
        summary_template_path = Path(root_path) / "mkdocs" / "summary_template.txt"
        summary_template_path.parent.mkdir(exist_ok=True)
        # summary_template_path.yml already exists, just return
        if summary_template_path.exists():
            typer.secho(
                f"Path '{summary_template_path.resolve()}' exists, skipping generation of it."
            )
            return

        # generated a new summary_template_path.yml file
        with open(summary_template_path, "w") as f:
            f.write(_summary_template)
            typer.secho(f"File '{summary_template_path.resolve()}' generated.")
            return
    except Exception as e:
        raise_error_and_exit(
            f"Unexpected Error while creating '{summary_template_path.resolve()}': {e}"
        )

# %% ../nbs/Mkdocs.ipynb 19
def _replace_ghp_deploy_action(root_path: str) -> None:
    """Replace the default deploy action file in the .github/workflows directory with a custom one.

    Args:
        root_path: The root path of the project.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    src_path = get_root_data_path() / "ghp_deploy_action_template.yml"
    if not src_path.exists():
        raise_error_and_exit(
            f"Unexpected error: path {src_path.resolve()} does not exists!",
        )

    workflows_path = Path(root_path) / ".github" / "workflows"
    workflows_path.mkdir(exist_ok=True, parents=True)

    dst_path = Path(workflows_path) / "deploy.yaml"
    shutil.copyfile(src_path, dst_path)

# %% ../nbs/Mkdocs.ipynb 22
def _update_gitignore_file(root_path: str) -> None:
    """Add the autogenerated mkdocs directories to the .gitignore file.

    Args:
        root_path: The root path of the project

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    _mkdocs_gitignore_path = get_root_data_path() / "gitignore.txt"
    with open(_mkdocs_gitignore_path, "r") as f:
        _new_paths_to_ignore = f.read()
        _new_paths_to_ignore = "\n\n" + _new_paths_to_ignore

    gitignore_path = Path(root_path) / ".gitignore"
    if not gitignore_path.exists():
        raise_error_and_exit(
            f"Unexpected error: path {gitignore_path.resolve()} does not exists!"
        )

    with open(gitignore_path, "a") as f:
        f.write(_new_paths_to_ignore)

# %% ../nbs/Mkdocs.ipynb 24
def _generate_default_social_image_link(root_path: str) -> None:
    """Add default social sharing image link to the mkdocs yaml file

    Args:
        root_path: The root path of the project.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    with set_cwd(root_path):
        repo = get_value_from_config(root_path, "repo")
        user = get_value_from_config(root_path, "user")

        timestamp = datetime.datetime.now().timestamp()
        img_url = f"https://opengraph.githubassets.com/{timestamp}/{user}/{repo}"

        _update_social_image_in_mkdocs_yml(root_path, img_url)

# %% ../nbs/Mkdocs.ipynb 27
def new(root_path: str) -> None:
    """Initialize mkdocs project files

    Creates **mkdocs** directory in the **root_path** directory and populates
    it with initial values. You should edit mkdocs.yml file to customize it if
    needed.

    Args:
        root_path: The path to the root of the project

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    _create_mkdocs_dir(root_path)
    _create_mkdocs_yaml(root_path)
    _create_summary_template(root_path)
    _replace_ghp_deploy_action(root_path)
    _update_gitignore_file(root_path)
    _generate_default_social_image_link(root_path)

# %% ../nbs/Mkdocs.ipynb 31
def _get_files_to_convert_to_markdown(cache: Path) -> List[Path]:
    """Get a list of notebooks and qmd files that require conversion to markdown format.

    Args:
        cache: The cache directory path

    Returns:
        A list of files to convert to markdown

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    exts = [".ipynb", ".qmd"]
    files = [
        f
        for f in cache.rglob("*")
        if f.suffix in exts and not any(p.startswith(".") for p in f.parts)
    ]

    return files

# %% ../nbs/Mkdocs.ipynb 33
def _update_conditional_content_tags(text: str) -> str:
    """Update conditional content tags.

    Args:
        text: The text to update the conditional content tags in.

    Returns:
        The updated text with the conditional content tags modified.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    pattern = r":::\s*{(?:\s*.*\.content-visible|\s*\.content-hidden)\s*(when|unless)-format\s*=\\\s*(\"|\')\s*(html|markdown)\s*\\(\"|\')\s*.*}"
    text = re.sub(
        pattern,
        lambda m: m.group(0).replace(
            m.group(1), "when" if m.group(1) == "unless" else "unless"
        ),
        text,
    )
    return text

# %% ../nbs/Mkdocs.ipynb 36
def _update_mermaid_chart_tags(text: str) -> str:
    """Convert the mermaid chart tags from quarto format to markdown format.

    Args:
        text: The text to update the mermaid chart tags in.

    Returns:
        The updated text with the mermaid chart tags modified.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    pattern = r"```\s*{mermaid\s*}"
    text = re.sub(pattern, "``` mermaid", text)
    return text

# %% ../nbs/Mkdocs.ipynb 38
def _add_markdown_attribute_to_enable_md_in_html(text: str) -> str:
    """Add markdown attribute to enable markdown in html.

    Args:
        text: The text to add the markdown attribute to

    Returns:
        The text with the markdown attribute added

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    pattern = r":::\s*{\s*(markdown=1)?\s*"
    text = re.sub(pattern, r"::: {markdown=1 ", text)
    return text

# %% ../nbs/Mkdocs.ipynb 40
def _update_quarto_tags_to_markdown_format(nb_path: Path) -> None:
    """Update Quarto tags to Markdown format

    Args:
        nb_path: Path to the notebook

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    with open(nb_path, "r") as f:
        contents = f.read()

    contents = _update_conditional_content_tags(contents)
    contents = _update_mermaid_chart_tags(contents)
    contents = _add_markdown_attribute_to_enable_md_in_html(contents)

    with open(nb_path, "w") as f:
        f.write(contents)

# %% ../nbs/Mkdocs.ipynb 43
def _sprun(cmd: str) -> None:
    """Run a command via subprocess.check_output

    Args:
        cmd: The command to run

    Raises:
        subprocess.CalledProcessError: If the command fails

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    try:
        # nosemgrep: python.lang.security.audit.subprocess-shell-true.subprocess-shell-true
        subprocess.check_output(
            cmd, shell=True  # nosec: B602:subprocess_popen_with_shell_equals_true
        )

    except subprocess.CalledProcessError as e:
        sys.exit(
            f"CMD Failed: e={e}\n e.returncode={e.returncode}\n e.output={e.output}\n e.stderr={e.stderr}\n cmd={cmd}"
        )

# %% ../nbs/Mkdocs.ipynb 44
def _generate_markdown_from_files(root_path: str, cache_path: Path) -> None:
    """Generate markdown files from notebook files.

    Args:
        root_path: The root path of the project.
        cache_path: The path to the _proc directory.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    doc_path = Path(root_path) / "mkdocs" / "docs"
    doc_path.mkdir(exist_ok=True, parents=True)

    with set_cwd(root_path):
        files = _get_files_to_convert_to_markdown(cache_path)

        for f in files:
            dir_prefix = str(f.parent)[len(str(cache_path)) + 1 :]
            dst_md = doc_path / f"{dir_prefix}" / f"{f.stem}.md"
            dst_md.parent.mkdir(parents=True, exist_ok=True)

            _update_quarto_tags_to_markdown_format(f)

            cmd = f'cd "{cache_path}" && quarto render "{f}" -o "{f.stem}.md" -t gfm --no-execute'
            _sprun(cmd)

            src_md = cache_path / "_docs" / f"{f.stem}.md"
            shutil.move(str(src_md), dst_md)

# %% ../nbs/Mkdocs.ipynb 46
def _replace_all(text: str, dir_prefix: str) -> str:
    """Replace the images relative path in the markdown string

    Args:
        text: The markdown string
        dir_prefix: Sub directory prefix to append to the image's relative path

    Returns:
        The text with the updated images relative path

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    _replace = {}

    image_patterns = [
        (
            re.compile(r"!\[[^\]]*\]\(([^https?:\/\/].*?)\s*(\"(?:.*[^\"])\")?\s*\)"),
            "../images/nbs/",
        ),
        (
            re.compile(r"<img\s*src\s*=\s*\"([^http|https][^\"]*)\""),
            "../../images/nbs/",
        ),
    ]

    for pattern, image_path in image_patterns:
        matches = [match.groups()[0] for match in pattern.finditer(text)]
        if len(matches) > 0:
            for m in matches:
                _replace[m] = (
                    os.path.normpath(Path(image_path).joinpath(f"{dir_prefix}/{m}"))
                    if len(dir_prefix) > 0
                    else f"images/nbs/{m}"
                )

    for k, v in _replace.items():
        text = text.replace(k, v)

    return text

# %% ../nbs/Mkdocs.ipynb 48
def _update_path_in_markdown(cache_path: Path, doc_path: Path) -> None:
    """Update guide images relative path in the markdown files

    Args:
        cache_path: The path to the _proc directory.
        doc_path: Path to the mkdocs/docs directory.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    files = _get_files_to_convert_to_markdown(cache_path)

    for file in files:
        dir_prefix = str(file.parent)[len(str(cache_path)) + 1 :]
        md = doc_path / f"{dir_prefix}" / f"{file.stem}.md"

        with open(Path(md), "r") as f:
            _new_text = f.read()
            _new_text = _replace_all(_new_text, dir_prefix)
        with open(Path(md), "w") as f:
            f.write(_new_text)


def _copy_images_to_docs_dir(root_path: str, cache_path: Path) -> None:
    # Reference: https://github.com/quarto-dev/quarto-cli/blob/main/src/core/image.ts#L38
    """Copy images from nbs to docs directory.

    Args:
        root_path: The root path of the project.
        cache_path: The path to the _proc directory.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    image_extensions = [
        ".apng",
        ".avif",
        ".gif",
        ".jpg",
        ".jpeg",
        ".jfif",
        ".pjpeg",
        ".pjp",
        ".png",
        ".svg",
        ".webp",
    ]

    nbs_images_path = [
        p for p in Path(cache_path).glob(r"**/*") if p.suffix in image_extensions
    ]

    if len(nbs_images_path) > 0:
        doc_path = Path(root_path) / "mkdocs" / "docs"
        img_path = Path(doc_path) / "images" / "nbs"
        for src_path in nbs_images_path:
            dir_prefix = str(src_path.parent)[len(str(cache_path)) + 1 :]
            dst_path = Path(img_path) / f"{dir_prefix}"
            dst_path.mkdir(exist_ok=True, parents=True)
            shutil.copy(src_path, dst_path)

        _update_path_in_markdown(cache_path, doc_path)

# %% ../nbs/Mkdocs.ipynb 52
def _get_title_from_notebook(cache_path: Path, file_path: Path) -> str:
    """Get the title of a notebook or markdown file.

    Args:
        cache_path: The path to the _proc directory.
        file_path: The path to the notebook file.

    Returns:
        The title of the file.

    Raises:
        ValueError: If the file does not exist.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    title: str
    _file_path = cache_path / file_path

    if not _file_path.exists():
        raise_error_and_exit(
            f"Unexpected error: path {_file_path.resolve()} does not exists!"
        )

    if _file_path.suffix == ".ipynb":
        nbp = NBProcessor(_file_path, procs=FrontmatterProc)
        nbp.process()

        if "title" in nbp.nb.frontmatter_:
            title = nbp.nb.frontmatter_["title"]
        else:
            headers = [
                cell["source"]
                for cell in nbp.nb["cells"]
                if cell["cell_type"] == "markdown" and cell["source"].startswith("#")
            ]
            title = (
                f"{_file_path.stem}.html"
                if len(headers) == 0
                else headers[0].replace("#", "").strip()
            )
    else:
        with open(_file_path) as f:
            contents = f.read()
        metadata = _fm2dict(contents, nb=False)
        metadata = {k.lower(): v for k, v in metadata.items()}
        title = metadata["title"]

    return title

# %% ../nbs/Mkdocs.ipynb 54
def _get_sidebar_from_config(file_path: Path) -> List[Any]:
    """Get the sidebar contents from the sidebar.yml or _quarto.yml file.

    Args:
        file_path: Path to the sidebar.yml or _quarto.yml file.

    Returns:
        The sidebar contents.

    Raises:
        KeyError: If the sidebar is not defined in the config file.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    if not file_path.exists():
        raise_error_and_exit(f"Path '{file_path.resolve()}' does not exists!")

    try:
        with open(file_path) as f:
            config = yaml.safe_load(f)
        sidebar: List[Any] = config["website"]["sidebar"]["contents"]
    except KeyError as e:
        raise_error_and_exit(
            f"Key Error: Contents of the sidebar are not defined in the files sidebar.yml or _quarto.yml."
        )

    return sidebar


def _read_sidebar_from_yml(root_path: str) -> List[Union[str, Any]]:
    """Get the sidebar contents from the sidebar.yml or _quarto.yml file.

    Args:
        root_path: The root path of the project.

    Returns:
        A list of strings and objects.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    _proc_dir = Path(root_path) / "_proc"
    sidebar_yml_path = _proc_dir / "sidebar.yml"
    _quarto_yml_path = _proc_dir / "_quarto.yml"

    custom_sidebar = get_value_from_config(root_path, "custom_sidebar")
    if custom_sidebar == "False":
        cmd = f'cd "{root_path}" && nbdev_docs'
        _sprun(cmd)

    return (
        _get_sidebar_from_config(sidebar_yml_path)
        if sidebar_yml_path.exists()
        else _get_sidebar_from_config(_quarto_yml_path)
    )

# %% ../nbs/Mkdocs.ipynb 57
def _flattern_sidebar_items(items: List[Union[str, Any]]) -> List[Union[str, Any]]:
    """Flatten a list of items.

    Args:
        items: A list of items.

    Returns:
        A flattened list of items.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    return [i for item in items if isinstance(item, list) for i in item] + [
        item for item in items if not isinstance(item, list)
    ]


def _expand_sidebar_if_needed(
    root_path: str, sidebar: List[Union[str, Any]]
) -> List[Union[str, Any]]:
    """Expand the sidebar if needed.

    Args:
        root_path: The root path of the project
        sidebar: The sidebar to expand

    Returns:
        The expanded sidebar

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    _proc_dir = Path(root_path) / "_proc"
    exts = [".ipynb", ".qmd"]

    for index, item in enumerate(sidebar):
        if "auto" in item:
            files = list(_proc_dir.glob("".join(item["auto"].split("/")[1:])))  # type: ignore
            files = sorted([str(f.relative_to(_proc_dir)) for f in files if f.suffix in exts])  # type: ignore
            sidebar[index] = files

        if isinstance(item, dict) and "contents" in item:
            _contents = item["contents"]
            if isinstance(_contents, str) and bool(re.search(r"[*?\[\]]", _contents)):
                files = list(_proc_dir.glob(item["contents"]))
                files = sorted([str(f.relative_to(_proc_dir)) for f in files if f.suffix in exts])  # type: ignore
                item["contents"] = files

    flat_sidebar = _flattern_sidebar_items(sidebar)
    return flat_sidebar

# %% ../nbs/Mkdocs.ipynb 59
def _generate_nav_from_sidebar(
    sidebar_items: List[Union[str, Dict[str, Any]]], cache_path: Path, level: int = 0
) -> str:
    """Generate a navigation string for mkdocs from a sidebar list.

    Args:
        sidebar_items: A list of strings or dictionaries.
        cache_path: The path to the _proc directory.
        level: The level of indentation to use.

    Returns:
        str: The navigation string.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    output = ""
    links = [
        "{}- [{}]({}.md)\n".format(
            "    " * level,
            _get_title_from_notebook(cache_path, Path(item)),
            Path(item).with_suffix(""),
        )
        if isinstance(item, str)
        else "{}- {}\n".format("    " * level, item["section"])
        + _generate_nav_from_sidebar(item["contents"], cache_path, level + 1)
        for item in sidebar_items
    ]
    output += "".join(links)
    return output

# %% ../nbs/Mkdocs.ipynb 61
def _generate_summary_for_sidebar(root_path: str, cache_path: Path) -> str:
    """Generate a summary for the sidebar

    Args:
        root_path: The root path of the project.
        cache_path: The path to the _proc directory.

    Returns:
        The summary for the sidebar

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    with set_cwd(root_path):
        sidebar = _read_sidebar_from_yml(root_path)
        expanded_sidebar = _expand_sidebar_if_needed(root_path, sidebar)
        sidebar_nav = _generate_nav_from_sidebar(expanded_sidebar, cache_path)

        return sidebar_nav

# %% ../nbs/Mkdocs.ipynb 63
def _copy_not_found_file_and_get_path(root_path: str, file_prefix: str) -> str:
    """Copy the CLI command found file to the docs directory and return the path to the file.

    Args:
        root_path: The root path of the project
        file_prefix: The prefix of the file to be copied

    Returns:
        The path to the copied file

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    src_path = get_root_data_path() / f"{file_prefix}_not_found.md"
    if not src_path.exists():
        raise_error_and_exit(
            f"Unexpected error: path {src_path.resolve()} does not exists!"
        )

    docs_path = Path(root_path) / "mkdocs" / "docs"
    docs_path.mkdir(exist_ok=True, parents=True)
    dst_path = docs_path / f"{file_prefix}_not_found.md"
    shutil.copyfile(src_path, dst_path)

    return (
        f"({dst_path.name})"
        if file_prefix == "changelog"
        else " " * 4 + f"- [Not found]({dst_path.name})"
    )

# %% ../nbs/Mkdocs.ipynb 66
def _get_submodules(package_name: str) -> List[str]:
    """Get all submodules of a package.

    Args:
        package_name: The name of the package.

    Returns:
        A list of submodules.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    try:
        # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import
        m = importlib.import_module(package_name)
    except ModuleNotFoundError as e:
        #         if (
        #             "NBDEV_MKDOCS_PATCH_IMPORTLIB" in os.environ
        #             and os.environ["NBDEV_MKDOCS_PATCH_IMPORTLIB"] != "false"
        #         ):
        #             # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import
        #             m = importlib.import_module("nbdev_mkdocs")
        #         else:
        raise e
    submodules = [
        info.name
        for info in pkgutil.walk_packages(m.__path__, prefix=f"{package_name}.")
    ]
    submodules = [
        x
        for x in submodules
        if not any([name.startswith("_") for name in x.split(".")])
    ]
    return [package_name] + submodules

# %% ../nbs/Mkdocs.ipynb 69
def _import_submodules(module_name: str) -> List[types.ModuleType]:
    def import_module(name: str) -> Optional[types.ModuleType]:
        try:
            # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import
            return importlib.import_module(name)
        except Exception:
            return None

    package_names = _get_submodules(module_name)
    modules = [import_module(n) for n in package_names]
    return [m for m in modules if m is not None]

# %% ../nbs/Mkdocs.ipynb 72
def _import_functions_and_classes(
    m: types.ModuleType,
) -> List[Tuple[str, Union[types.FunctionType, Type[Any]]]]:
    return [(x, y) for x, y in getmembers(m) if isfunction(y) or isclass(y)]

# %% ../nbs/Mkdocs.ipynb 74
def _is_private(name: str) -> bool:
    parts = name.split(".")
    return any([part.startswith("_") for part in parts])

# %% ../nbs/Mkdocs.ipynb 76
def _import_all_members(module_name: str) -> List[str]:
    submodules = _import_submodules(module_name)
    members: List[Tuple[str, Union[types.FunctionType, Type[Any]]]] = list(
        itertools.chain(*[_import_functions_and_classes(m) for m in submodules])
    )

    names = [f"{y.__module__}.{y.__name__}" for x, y in members]
    names = [
        name for name in names if not _is_private(name) and name.startswith(module_name)
    ]
    return names

# %% ../nbs/Mkdocs.ipynb 78
def _add_all_submodules(members: List[str]) -> List[str]:
    def _f(x: str) -> List[str]:
        xs = x.split(".")
        return [".".join(xs[:i]) + "." for i in range(1, len(xs))]

    submodules = list(set(itertools.chain(*[_f(x) for x in members])))
    members = members + submodules
    members = sorted(set(members))
    return members

# %% ../nbs/Mkdocs.ipynb 81
def _get_api_summary_item(x: str) -> str:
    xs = x.split(".")
    if x.endswith("."):
        indent = " " * (4 * (len(xs) - 1))
        return f"{indent}- {xs[-2]}"
    else:
        indent = " " * (4 * (len(xs)))
        return f"{indent}- [{xs[-1]}](api/{'/'.join(xs)}.md)"

# %% ../nbs/Mkdocs.ipynb 83
def _get_api_summary(members: List[str]) -> str:
    return "\n".join([_get_api_summary_item(x) for x in members]) + "\n"

# %% ../nbs/Mkdocs.ipynb 85
def _generate_api_doc(name: str, docs_path: Path) -> Path:
    xs = name.split(".")
    module_name = ".".join(xs[:-1])
    member_name = xs[-1]
    path = docs_path / f"{('/').join(xs)}.md"
    content = f"::: {module_name}.{member_name}\n"

    path.parent.mkdir(exist_ok=True, parents=True)
    with open(path, "w") as f:
        f.write(content)

    return path

# %% ../nbs/Mkdocs.ipynb 87
def _generate_api_docs(members: List[str], docs_path: Path) -> List[Path]:
    return [_generate_api_doc(x, docs_path) for x in members if not x.endswith(".")]

# %% ../nbs/Mkdocs.ipynb 89
def _generate_api_docs_for_module(root_path: str, module_name: str) -> str:
    """Generate API documentation for a module.

    Args:
        root_path: The root path of the project.
        module_name: The name of the module.

    Returns:
        A string containing the API documentation for the module.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    members = _import_all_members(module_name)
    members_with_submodules = _add_all_submodules(members)

    api_summary = _get_api_summary(members_with_submodules)

    _generate_api_docs(
        members_with_submodules, Path(root_path) / "mkdocs" / "docs" / "api"
    )

    return api_summary


#     return textwrap.indent(submodule_summary, prefix=" " * 4)

# %% ../nbs/Mkdocs.ipynb 93
def _restrict_line_length(s: str, width: int = 80) -> str:
    """Restrict the line length of a string.

    Args:
        s: The string to be processed.
        width: The maximum line length.

    Returns:
        A new string in which each line is less than the specified width.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    _s = ""

    for blocks in s.split("\n\n"):
        sub_block = blocks.split("\n  ")
        for line in sub_block:
            line = line.replace("\n", " ")
            line = "\n".join(textwrap.wrap(line, width=width, replace_whitespace=False))
            if len(sub_block) == 1:
                _s += line + "\n\n"
            else:
                _s += "\n" + line + "\n" if line.endswith(":") else " " + line + "\n"
    return _s

# %% ../nbs/Mkdocs.ipynb 95
def _generate_cli_doc_for_submodule(
    root_path: str, docs_dir_name: str, cmd: str
) -> str:
    """Generate CLI documentation for a submodule.

    Args:
        root_path: The root path of the project.
        docs_dir_name: The name of the directory where the documentation will be stored.
        cmd: The command to generate documentation for.

    Returns:
        The generated documentation.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    cli_app_name = cmd.split("=")[0]
    module_name = cmd.split("=")[1].split(":")[0]
    method_name = cmd.split("=")[1].split(":")[1]

    subpath = f"{docs_dir_name}/{cli_app_name}.md"
    path = Path(root_path) / "mkdocs" / "docs" / subpath
    path.parent.mkdir(exist_ok=True, parents=True)

    try:
        # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import
        m = importlib.import_module(module_name)
        if isinstance(getattr(m, method_name), typer.Typer):
            cli_doc = generate_cli_doc(module_name, cli_app_name)
        else:
            cmd = f"{cli_app_name} --help"
            cli_doc = (
                # nosemgrep: python.lang.security.audit.subprocess-shell-true.subprocess-shell-true
                subprocess.run(  # nosec: B602:subprocess_popen_with_shell_equals_true
                    cmd,
                    shell=True,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                ).stdout.decode("utf-8")
            )

            cli_doc = _restrict_line_length(cli_doc)
            cli_doc = "\n```\n" + cli_doc + "\n```\n"

    except AttributeError as e:
        cli_doc = f"Unable to generate documentation for command. Execution of `{cli_app_name} --help` command failed."

    with open(path, "w") as f:
        f.write(cli_doc)

    return f"- [{cli_app_name}]({subpath})"


def _generate_cli_docs_for_module(root_path: str, module_name: str) -> str:
    """Generate CLI docs for a module.

    Args:
        root_path: The root path of the module
        module_name: The name of the module

    Returns:
        The generated CLI docs

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    docs_dir_name = "cli"
    shutil.rmtree(
        Path(root_path) / "mkdocs" / "docs" / f"{docs_dir_name}", ignore_errors=True
    )
    console_scripts = get_value_from_config(root_path, "console_scripts")

    if not console_scripts:
        ret_val = _copy_not_found_file_and_get_path(
            root_path=root_path, file_prefix="cli_commands"
        )
        return ret_val

    submodule_summary = "\n".join(
        [
            _generate_cli_doc_for_submodule(
                root_path=root_path, docs_dir_name=docs_dir_name, cmd=cmd
            )
            for cmd in console_scripts.split("\n")
            if cmd != ""
        ]
    )

    return textwrap.indent(submodule_summary, prefix=" " * 4)

# %% ../nbs/Mkdocs.ipynb 99
def _copy_change_log_if_exists(root_path: str, docs_path: Union[Path, str]) -> str:
    """Copy the CHANGELOG.md file to the docs folder if it's not already present.

    Args:
        root_path: The root path of the project.
        docs_path: The path to the docs folder.

    Returns:
        The path to the copied CHANGELOG.md file.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    source_change_log_path = Path(root_path) / "CHANGELOG.md"
    dst_change_log_path = Path(docs_path) / "CHANGELOG.md"

    if source_change_log_path.exists():
        shutil.copy(source_change_log_path, dst_change_log_path)
        changelog = "(CHANGELOG.md)"
    else:
        changelog = _copy_not_found_file_and_get_path(
            root_path=root_path, file_prefix="changelog"
        )

    return changelog

# %% ../nbs/Mkdocs.ipynb 102
def _build_summary(
    root_path: str,
    module: str,
    cache_path: Path,
) -> None:
    # create docs_path if needed
    """Create a summary navigation file for generating navigation that is compatible with mkdocs.

    Args:
        root_path: The root path of the project.
        module: The module to generate the API documentation for.
        cache_path: The path to the _proc directory.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    docs_path = Path(root_path) / "mkdocs" / "docs"
    docs_path.mkdir(exist_ok=True)

    # copy README.md as index.md
    shutil.copy(Path(root_path) / "README.md", docs_path / "index.md")

    # generate markdown files
    _generate_markdown_from_files(root_path, cache_path)

    # copy images to docs dir and update path in generated markdown files
    _copy_images_to_docs_dir(root_path, cache_path)

    # generates sidebar navigation
    sidebar = _generate_summary_for_sidebar(root_path, cache_path)

    # generate API
    api = _generate_api_docs_for_module(root_path, module)

    # generate CLI
    cli = _generate_cli_docs_for_module(root_path, module)

    # copy CHANGELOG.md as CHANGELOG.md is exists
    changelog = _copy_change_log_if_exists(root_path, docs_path)

    # read summary template from file
    with open(Path(root_path) / "mkdocs" / "summary_template.txt") as f:
        summary_template = f.read()

    summary = summary_template.format(
        sidebar=sidebar, api=api, cli=cli, changelog=changelog
    )
    summary = "\n".join(
        [l for l in [l.rstrip() for l in summary.split("\n")] if l != ""]
    )

    with open(docs_path / "SUMMARY.md", mode="w") as f:
        f.write(summary)

# %% ../nbs/Mkdocs.ipynb 105
def _copy_cname_if_needed(root_path: str) -> None:
    """Copy the CNAME file to mkdocs/docs/CNAME if it's not already present.

    Args:
        root_path: The root path of the project

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    cname_path = Path(root_path) / "CNAME"
    dst_path = Path(root_path) / "mkdocs" / "docs" / "CNAME"
    if cname_path.exists():
        dst_path.parent.mkdir(exist_ok=True, parents=True)
        shutil.copyfile(cname_path, dst_path)
        typer.secho(
            f"File '{cname_path.resolve()}' copied to '{dst_path.resolve()}'.",
        )
    else:
        typer.secho(
            f"File '{cname_path.resolve()}' not found, skipping copying..",
        )

# %% ../nbs/Mkdocs.ipynb 107
def _copy_docs_overrides(root_path: str) -> None:
    """Copy the docs_overrides directory to the mkdocs/docs/overrides directory.

    Args:
        root_path: The root path of the project.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    src_path = Path(root_path) / "mkdocs" / "docs_overrides"
    dst_path = Path(root_path) / "mkdocs" / "docs" / "overrides"

    if not src_path.exists():
        raise_error_and_exit(
            f"Unexpected error: path {src_path.resolve()} does not exists!"
        )

    shutil.rmtree(dst_path, ignore_errors=True)
    shutil.copytree(src_path, dst_path)

# %% ../nbs/Mkdocs.ipynb 109
def _fix_sym_links_in_nbs(root_path: str, cache_path: Path, nbdev_lookup: NbdevLookup, docs_versioning: str, lib_version: str, use_relative_doc_links: bool) -> None:  # type: ignore
    """Fix the default sym links generated by nbdev in the notebooks

    Args:
        root_path: The root path of the project.
        cache_path: The path to the _proc directory.
        nbdev_lookup: Instance of NbdevLookup.
        docs_versioning: The value set for docs_versioning flag in settings.ini file.
        lib_version: The current version of the library.
        use_relative_doc_links: If set to True, relative links are added to symbol references in generated
            documentation. Else, the value set in doc_host in settings.ini is added to symbol references in generated documentation.


    !!! note

        The above docstring is autogenerated by docstring-gen library (https://docstring-gen.airt.ai)
    """
    files = [
        f
        for f in cache_path.rglob("*")
        if f.suffix == ".ipynb"
        and not any(cache_path.startswith(".") for cache_path in f.parts)
        and not f.name.startswith("_")
    ]

    for file in files:
        _f = nbformat.read(file, as_version=4)
        for cell in _f.cells:
            if cell.cell_type == "markdown":
                updated_src = fix_sym_links(
                    cell["source"],
                    nbdev_lookup,
                    docs_versioning,
                    lib_version,
                    use_relative_doc_links,
                )
                cell["source"] = updated_src

        nbformat.write(_f, file)

# %% ../nbs/Mkdocs.ipynb 112
def nbdev_mkdocs_docs(
    root_path: str,
    refresh_quarto_settings: bool = False,
    use_relative_doc_links: bool = False,
    no_mkdocs_build: bool = False,
) -> None:
    """Prepare mkdocs documentation

    Args:
        root_path: The root path of the project
        refresh_quarto_settings: Flag to refresh quarto yml file. This flag should be set to `True`
            if this function is called directly without calling prepare.
        use_relative_doc_links: If set to True, relative links are added to symbol references in generated
            documentation. Else, the value set in doc_host in settings.ini is added to symbol references in
            generated documentation. This flag should be set to `False` if this function is called directly
            without calling preview.
        no_mkdocs_build: If set to True, then the mkdocs build will be skipped. This flag should be set to
            `False` if this function is called directly without calling preview.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    with set_cwd(root_path):
        if refresh_quarto_settings:
            refresh_quarto_yml()

        _copy_cname_if_needed(root_path)

        _copy_docs_overrides(root_path)

        lib_name = get_value_from_config(root_path, "lib_name")
        lib_path = get_value_from_config(root_path, "lib_path")

        cache_path = proc_nbs(force=True)
        nbdev_lookup = NbdevLookup(incl_libs=lib_name.replace("_", "-"))
        docs_versioning = get_value_from_config(root_path, "docs_versioning")
        lib_version = get_value_from_config(root_path, "version")
        _fix_sym_links_in_nbs(
            root_path,
            cache_path,
            nbdev_lookup,
            docs_versioning,
            lib_version,
            use_relative_doc_links,
        )

        _build_summary(root_path, lib_path, cache_path)

        if not no_mkdocs_build:
            cmd = f"mkdocs build -f \"{(Path(root_path) / 'mkdocs' / 'mkdocs.yml').resolve()}\""
            _sprun(cmd)


def prepare(
    root_path: str,
    use_relative_doc_links: bool = False,
    no_test: bool = False,
    no_mkdocs_build: bool = False,
) -> None:
    """Prepare mkdocs for serving

    Args:
        root_path: The root path of the project
        use_relative_doc_links: If set to True, relative links are added to symbol references in generated
            documentation. Else, the value set in doc_host in settings.ini is added to symbol references in
            generated documentation. This flag should be set to `False` if this function is called directly
            without calling preview.
        no_test: If set to False, the unit tests will be run, else they will be skipped
        no_mkdocs_build: If set to True, then the mkdocs build will be skipped. This flag
            should be set to `False` if this function is called directly without calling preview

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    with set_cwd(root_path):
        if no_test:
            nbdev_export.__wrapped__()
            refresh_quarto_yml()
            nbdev_readme.__wrapped__(chk_time=True)
        else:
            cmd = "nbdev_prepare"
            _sprun(cmd)

    nbdev_mkdocs_docs(
        root_path=root_path,
        use_relative_doc_links=use_relative_doc_links,
        no_mkdocs_build=no_mkdocs_build,
    )

# %% ../nbs/Mkdocs.ipynb 116
def preview(
    root_path: str, use_relative_doc_links: bool, port: Optional[int] = None
) -> None:
    """Preview the mkdocs documentation.

    Args:
        root_path: The root path of the documentation.
        use_relative_doc_links: If set to True, relative links are added to symbol references in generated
            documentation. Else, the value set in doc_host in settings.ini is added to symbol references in
            generated documentation.
        port: The port to serve the documentation on.

    !!! note

        The above docstring is autogenerated by docstring-gen library (https://github.com/airtai/docstring-gen)
    """
    with set_cwd(root_path):
        prepare(
            root_path=root_path,
            use_relative_doc_links=use_relative_doc_links,
            no_test=True,
            no_mkdocs_build=True,
        )

        cmd = f"mkdocs serve -f {root_path}/mkdocs/mkdocs.yml -a 0.0.0.0"
        if port:
            cmd = cmd + f":{port}"

        with subprocess.Popen(  # nosec B603:subprocess_without_shell_equals_true
            shlex.split(cmd),
            stdout=subprocess.PIPE,
            bufsize=1,
            text=True,
            universal_newlines=True,
        ) as p:
            for line in p.stdout:  # type: ignore
                print(line, end="")

        if p.returncode != 0:
            typer.secho(
                f"Command cmd='{cmd}' failed!",
                err=True,
                fg=typer.colors.RED,
            )
            raise typer.Exit(6)
