from __future__ import annotations

"""
High-level pipeline for cleaning Dartdoc output and packaging it into a ZIP.

Stages:
1) Create a temporary working directory.
2) Run the cleaner (stub removal + link fixes) on the Dartdoc tree.
3) Apply additional sidebar fixes for '*-class-sidebar.html' files.
4) Package the cleaned documentation tree into a ZIP archive.

This module serves as the public-facing pipeline API:
- It is used by the CLI.
"""

from pathlib import Path
import argparse
import tempfile
import zipfile

# Cleaner module - internally performs all HTML cleaning operations.
from dartdoc_advanced.cleaner.core import clean_docs
from dartdoc_advanced.cleaner.sidebar_fix import fix_class_sidebar_file


# ────────────────────────── helpers ──────────────────────────


def make_zip(out_dir: Path, zip_base: Path | None = None) -> Path:
    """
    Create a ZIP archive containing the contents of `out_dir`.

    Parameters
    ----------
    out_dir:
        The directory containing cleaned documentation (typically tmp_root/src.name).
    zip_base:
        Base path WITHOUT the '.zip' extension. If None, out_dir is used.

    Behavior:
    - Ensures the parent folder for the ZIP file exists.
    - Recursively includes all files under out_dir.
    - Preserves the root folder name inside the archive.

    Returns:
        Full path to the created ZIP archive.
    """
    out_dir = out_dir.resolve()
    base = zip_base.resolve() if zip_base else out_dir
    zip_path = base.with_suffix(".zip")
    zip_path.parent.mkdir(parents=True, exist_ok=True)

    with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
        for file_path in out_dir.rglob("*"):
            if file_path.is_file():
                # Preserve the top-level directory name inside the ZIP.
                arcname = Path(out_dir.name) / file_path.relative_to(out_dir)
                zf.write(file_path, arcname)

    return zip_path


def _run_sidebar_fix(tmp_out_dir: Path) -> int:
    """
    Apply class-sidebar fixes to all '*-class-sidebar.html' files in the tree.

    This step exists because some sidebar files require additional normalization
    beyond what the main cleaner performs.

    Returns:
        Number of modified files. This is mostly useful for debugging.
    """
    changed = 0
    for fp in tmp_out_dir.rglob("*-class-sidebar.html"):
        changed += fix_class_sidebar_file(fp)
    return changed


# ────────────────────────── pipeline core ──────────────────────────


def run_pipeline(
    src: Path,
    zip_base: Path,
    min_chars: int,
    tiny_chars: int,
) -> Path:
    """
    Run the full pipeline: clean -> fix sidebars -> zip.

    Parameters
    ----------
    src:
        The original doc/api directory generated by Dartdoc.
    zip_base:
        Base path WITHOUT the '.zip' extension for the resulting archive.
    min_chars, tiny_chars:
        Text-length thresholds for detecting stub pages. Passed directly to the cleaner.

    Returns
    -------
    Path
        The path to the generated ZIP archive.

    Raises
    ------
    FileNotFoundError:
        If the source directory does not exist.
    RuntimeError:
        If the expected output folder is missing or if ZIP creation fails.
    """
    src = src.resolve()
    if not src.exists():
        raise FileNotFoundError(f"Source not found: {src}")

    # A temporary root directory is used to avoid polluting the real filesystem.
    with tempfile.TemporaryDirectory(prefix="docsy_") as tmp_root_str:
        tmp_root = Path(tmp_root_str)

        # 1) Main cleaning - output is written into tmp_root/<src.name>.
        clean_docs(src, tmp_root, min_chars, tiny_chars)

        tmp_out_dir = (tmp_root / src.name).resolve()
        if not tmp_out_dir.exists():
            raise RuntimeError(f"Expected output folder not created: {tmp_out_dir}")

        # 2) Apply additional sidebar corrections.
        _run_sidebar_fix(tmp_out_dir)

        # 3) Create ZIP archive.
        zip_path = make_zip(tmp_out_dir, zip_base)

    # After leaving the temporary directory context, only the ZIP remains.
    if not zip_path.exists():
        raise RuntimeError(f"ZIP was reported created but is missing: {zip_path}")

    return zip_path


# ────────────────────────── CLI entry-point ──────────────────────────


def main() -> None:
    """
    Allow the pipeline to be executed directly from the command line:

        python -m dartdoc_advanced.pipeline.runner \\
            path/to/doc/api \\
            --zip-name /full/output/path \\
            --min-chars 350 --tiny-chars 150

    This serves as a convenience interface around run_pipeline().
    """
    parser = argparse.ArgumentParser(
        description="Run pipeline (clean docs -> fix sidebars -> zip) in one go."
    )
    parser.add_argument(
        "src",
        type=Path,
        help="Directory produced by dartdoc (e.g. path/to/doc/api)",
    )
    parser.add_argument(
        "--min",
        "--min-chars",
        dest="min_chars",
        type=int,
        default=350,
        help="Threshold for *-sidebar.html & *-library.html stubs (default: 350)",
    )
    parser.add_argument(
        "--tiny",
        "--tiny-chars",
        dest="tiny_chars",
        type=int,
        default=150,
        help="Threshold for other micro-stubs (default: 150)",
    )
    parser.add_argument(
        "--zip-name",
        type=Path,
        required=True,
        help="Destination archive WITHOUT '.zip' (e.g. /tmp/MyApiDocs)",
    )
    args = parser.parse_args()

    zip_path = run_pipeline(args.src, args.zip_name, args.min_chars, args.tiny_chars)
    print(f"ZIP created: {zip_path}")


if __name__ == "__main__":
    main()
