#!/usr/bin/env python3

"""Updates files in preparation for a jekyll build.

Used from .github/workflows/gh_pages.yaml. This updates the file and directory
structure prior to the jekyll build.
"""

import dataclasses
import os
from pathlib import Path
import re
from typing import Optional

__copyright__ = """
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""


@dataclasses.dataclass
class ChildDir:
    """Tracks whether a child directory has grandchildren."""

    title: str
    has_grandchildren: bool = False


def get_title(f: Path, content: str) -> str:
    """Returns a file's title according to markdown.

    Replacements are for YAML compatibility in `add_frontmatter`.
    """
    m = re.search("^# (.*)$", content, re.MULTILINE)
    assert m, f
    title = m[1]
    title = title.replace("\\", "\\\\")
    title = title.replace('"', '\\"')
    title = re.sub("`([^`]+)`", r"<code>\1</code>", title)
    return title


def add_frontmatter(
    f: Path,
    orig_content: str,
    titles: list[str],
    nav_order: Optional[int],
    has_children: bool,
) -> None:
    """Adds frontmatter to a file."""
    content = "---\n"

    assert len(titles) <= 3
    content += f'title: "{titles[-1]}"\n'
    if len(titles) >= 2:
        content += f'parent: "{titles[-2]}"\n'
    if len(titles) == 3:
        content += f'grand_parent: "{titles[-3]}"\n'

    if nav_order is not None:
        content += f"nav_order: {nav_order}\n"
    if has_children:
        # has_toc is disabling the list of child files in a separate table of
        # contents.
        content += "has_children: true\nhas_toc: false\n"
    content += "---\n\n" + orig_content
    f.write_text(content)


def label_subdir(
    subdir_str: str,
    top_nav_order: int,
    parent_title: Optional[str] = None,
    grandchild_dirs: bool = False,
) -> None:
    """Automatically adds child information to a subdirectory's markdown files.

    This is in support of navigation.
    """
    assert not (parent_title and grandchild_dirs)

    subdir = Path(subdir_str)
    readme = subdir / "README.md"
    readme_content = readme.read_text()

    def include_child(md_file: Path) -> bool:
        """Returns whether md_file is a child of this label."""
        # The top-level README.md isn't a child.
        if md_file == readme:
            return False
        # Skip directories that aren't part of the repository.
        if "/node_modules/" in str(md_file):
            return False
        if "/dist/" in str(md_file):
            return False
        return True

    readme_title = get_title(readme, readme_content)
    readme_titles = [readme_title]
    if parent_title:
        readme_titles.insert(0, parent_title)
    children = [x for x in subdir.glob("**/*.md") if include_child(x)]
    add_frontmatter(
        readme, readme_content, readme_titles, top_nav_order, bool(children)
    )

    if grandchild_dirs:
        # When adding grandchildren, we cluster files by child directory.
        child_dirs = {}
        for child in children:
            if child.name == "README.md":
                title = get_title(child, child.read_text())
                child_dirs[child.parent] = ChildDir(title)
        for child in children:
            if child.name != "README.md" and child.parent in child_dirs:
                child_dirs[child.parent].has_grandchildren = True

    for child in children:
        child_content = child.read_text()
        child_title = get_title(child, child_content)
        child_nav_order = None

        if subdir_str == "proposals":
            # Use proposal numbers as part of the title and ordering.
            m = re.match(r"p(\d+).md", child.name)
            # Skip files that aren't proposals.
            if not m:
                continue
            child_title = f"#{m[1]}: {child_title}"
            child_nav_order = int(m[1])

        titles = [readme_title, child_title]
        has_children = False
        if parent_title:
            titles.insert(0, parent_title)
        elif grandchild_dirs and child.parent in child_dirs:
            if child.name == "README.md":
                has_children = child_dirs[child.parent].has_grandchildren
            else:
                dir_title = child_dirs[child.parent].title
                titles.insert(1, dir_title)

        add_frontmatter(
            child, child_content, titles, child_nav_order, has_children
        )


def label_root_file(
    name: str, title: str, top_nav_order: int, has_children: bool = False
) -> None:
    """Adds frontmatter to a root file, like CONTRIBUTING.md."""
    f = Path(name)
    add_frontmatter(f, f.read_text(), [title], top_nav_order, has_children)


def main() -> None:
    # Ensure this runs from the repo root.
    os.chdir(Path(__file__).parents[1])

    # The external symlink is created by scripts/create_compdb.py, and can
    # interfere with local execution.
    external = Path("external")
    if external.exists():
        external.unlink()

    # Move files to the repo root.
    for f in Path("website").iterdir():
        if f.name in ["README.md", ".jekyll-cache", "_site"]:
            continue
        f.rename(f.name)

    # Use an object for a reference.
    nav_order = [0]

    # Returns an incrementing value for ordering.
    def next(nav_order: list[int]) -> int:
        nav_order[0] += 1
        return nav_order[0]

    label_root_file("README.md", "Home", next(nav_order))
    label_root_file("CONTRIBUTING.md", "Contributing", next(nav_order))
    label_subdir("docs/design", next(nav_order), grandchild_dirs=True)
    label_subdir("docs/guides", next(nav_order))
    label_subdir("docs/project", next(nav_order), grandchild_dirs=True)
    label_subdir("docs/spec", next(nav_order))
    # Provide a small file to cluster implementation-related directories.
    label_root_file(
        "implementation.md",
        "Implementation",
        next(nav_order),
        has_children=True,
    )
    label_subdir("utils", next(nav_order))
    label_subdir("proposals", next(nav_order))
    label_root_file("CODE_OF_CONDUCT.md", "Code of conduct", next(nav_order))
    label_root_file("SECURITY.md", "Security policy", next(nav_order))

    # Reset the order for the implementation children.
    nav_order[0] = 0
    label_subdir(
        "toolchain/docs", next(nav_order), parent_title="Implementation"
    )
    label_subdir("testing", next(nav_order), parent_title="Implementation")


if __name__ == "__main__":
    main()
