from collections.abc import Iterator, Callable
from pathlib import Path
from dataclasses import dataclass
import click
import os
from urllib.parse import quote
from string import capwords
import shutil
import tempfile

DRY = False

@dataclass
class Page:
    title: str
    url: str
    entries: list[Path]
    output: Path

    @staticmethod
    def create(source: Path, entries: list[Path], site_url: str, output_dir: Path):
        title = capwords(source.stem.replace("-", " "))
        url = f"{site_url}/{quote(source.stem)}"
        output = output_dir / source.stem / "index.html"
        return Page(title, url, entries, output)

def renderPageCallback(template_file: Path, stylesheet_url: str) -> Callable[[Page, str], None]:
    """ Callback to process the provided page metadata and output the final page to the filesystem """
    from jinja2 import Environment, FileSystemLoader, select_autoescape
    
    env = Environment(
            loader=FileSystemLoader(template_file.parent),
            autoescape=select_autoescape(), 
            extensions=["jinja_markdown.MarkdownExtension"]
    )
    template = env.get_template(template_file.name)

    def entries(page: Page) -> Iterator[str]:
        for entry in page.entries:
            with open(entry, "r") as f:
                yield f.read()

    def render(page: Page, site_title: str, navigation: list[dict[str, str]]) -> None:
        print(f"Rendering {page.output}")
        args = {"site_title": site_title, "stylesheet_url": stylesheet_url, "navigation": navigation, "entries": entries(page)}
        content = template.render(args)
        if DRY:
            print(content)
        else:
            os.makedirs(page.output.parent, exist_ok=True)
            with open(page.output, "w") as f:
                f.write(content)

    return render

def collectPages(site_root: Path, site_url: str, output_dir: Path) -> Iterator[Page]:
    """ Walk the site root collecting all the entries and organizing them into pages """
    for f in os.listdir(site_root):
        page_path = site_root / f
        
        # Directories are rendered as one page with multiple entries
        if os.path.isdir(page_path):
            yield Page.create(page_path, sorted(list(page_path.glob("**/*.md"))), site_url, output_dir)

        # Single .md files are rendered as single-entry pages
        elif page_path.suffix == ".md":
            yield Page.create(page_path, [page_path], site_url, output_dir)

def generateSite(site_root: Path, base_url: str, output_dir: Path, render_page: Callable[[Page, str], None]) -> None:
    """ Generate the site located at the specified root """
    print(f"Generating site rooted at {site_root}")

    site_url = f"{base_url}/{quote(site_root.stem)}"
    site_dir = output_dir / site_root.stem
    site_title = capwords(site_root.stem.replace("-", " "))
    
    pages = sorted([p for p in collectPages(site_root, site_url, site_dir)], key=lambda v: v.title)
    navigation = [{"text": p.title, "url": p.url} for p in pages]
    for page in pages:
        render_page(page, site_title, navigation)
    
    home = Page("Home", site_url, [], site_dir / "index.html")
    render_page(home, site_title, navigation)

def generateSites(config: dict[str, str]) -> None:
    """ Generate all sites according to the specified config """
    docs_root = Path(config["docs_root"])
    output_dir = Path(config["output_dir"])
    template_file = Path(config["template_file"])
    css_file = Path(config["css_file"])
    base_url = config["base_url"]
    stylesheet_url = f"{base_url}/{css_file.name}"

    render_page = renderPageCallback(template_file, stylesheet_url)

    with tempfile.TemporaryDirectory() as tmpdir:
        for f in os.listdir(docs_root):
            site = docs_root / f
            if os.path.isdir(site):
                generateSite(site, base_url, Path(tmpdir), render_page)

        shutil.copyfile(css_file, Path(tmpdir) / css_file.name)
       
        if output_dir.exists():
            shutil.rmtree(output_dir)
        shutil.move(tmpdir, output_dir)

@click.command()
@click.argument("config_file", nargs=1, type=click.Path())
@click.option("--dry/--no-dry", default=False, help="Instead of generating the sites, print every step to stdout")
def main(config_file, dry):
    global DRY
    DRY = dry

    with open(config_file, "rb") as f:
        import tomllib
        config = tomllib.load(f)

    generateSites(config)

if __name__ == '__main__':
    main()