"""
Main cleaner module for processing Dartdoc-generated HTML documentation.

Processing stages:
1) Copy the original documentation tree into a working directory.
2) Detect and remove "stub" HTML files (very small or unimportant pages).
3) Iterate through all HTML files and:
   - disable dead or invalid links,
   - normalize links inside class sidebar files,
   - clean links in root and library index pages.
"""

from __future__ import annotations

import argparse
import shutil
from pathlib import Path

from bs4 import BeautifulSoup

from .stubs import is_deletable_stub, CLASS_SIDEBAR_SUFFIXES
from .links import is_anchor, is_external_or_js, is_trivially_bad_href

# Global context variables.
# OUT_ROOT: the root folder of the working documentation tree.
# stub_paths: absolute paths of stub HTML files that should be deleted.
OUT_ROOT: Path
stub_paths: set[Path] = set()


# ───────────────────────── Link helpers ─────────────────────────


def is_dead_target(href: str, html_file_set: set[Path]) -> bool:
    """
    Determine whether a given href points to a dead internal HTML target.

    Rules:
    - Ignore anchors (#...) and external URLs.
    - For relative .html files, the referenced file must exist in html_file_set.
    - If the target is one of the previously removed stub files, treat it as dead.

    Parameters:
        href: The link value extracted from a <a> tag.
        html_file_set: A set of absolute paths representing all valid HTML files.

    Returns:
        True if the link target is internal and invalid, False otherwise.
    """
    clean = href.split("#")[0].strip()
    if not clean or is_external_or_js(clean) or is_anchor(clean):
        return False

    target = (OUT_ROOT / clean).resolve()
    return target in stub_paths or (clean.endswith(".html") and target not in html_file_set)


# ───────────────────────── HTML rewriters ─────────────────────────


def _disable_link(soup: BeautifulSoup, a_tag) -> None:
    """
    Replace an <a> tag with a <span class="dead-link"> containing the same text.
    This visually disables the link without removing the text content.
    """
    span = soup.new_tag("span", **{"class": "dead-link"})
    span.string = a_tag.get_text()
    a_tag.replace_with(span)


def disable_dead_links_generic(html_path: Path, html_file_set: set[Path]) -> int:
    """
    Disable dead or invalid links in a generic HTML file.

    This applies to:
        - all non-root index files,
        - non-library index files,
        - non-class-sidebar files.

    Behavior:
        - Disable links with trivial issues (javascript:, empty, etc.).
        - Disable links whose internal HTML targets are missing or deleted.

    Returns:
        The number of modified links.
    """
    soup = BeautifulSoup(html_path.read_text(encoding="utf-8", errors="ignore"), "lxml")
    changed = 0

    for a in soup.find_all("a", href=True):
        href = a["href"]
        if is_dead_target(href, html_file_set) or is_trivially_bad_href(href):
            _disable_link(soup, a)
            changed += 1

    if changed:
        html_path.write_text(str(soup), encoding="utf-8")
    return changed


def disable_dead_links_root_index(html_path: Path, html_file_set: set[Path]) -> int:
    """
    Special-case handling for the root 'index.html' of the documentation set.

    Behavior:
        - Disable all external links (http, https, javascript).
        - For relative .html files, verify existence within OUT_ROOT.

    Returns:
        The number of modified links.
    """
    soup = BeautifulSoup(html_path.read_text(encoding="utf-8", errors="ignore"), "lxml")
    n = 0

    for a in soup.find_all("a", href=True):
        href = a["href"]

        if is_external_or_js(href):
            _disable_link(soup, a)
            n += 1
        else:
            clean = href.split("#")[0].strip()
            if clean and clean.endswith(".html") and (OUT_ROOT / clean).resolve() not in html_file_set:
                _disable_link(soup, a)
                n += 1

    if n:
        html_path.write_text(str(soup), encoding="utf-8")
    return n


def patch_root_index(fp: Path, html_file_set: set[Path]) -> int:
    """
    Thin wrapper emphasizing that root index files use stricter link rules.
    Delegates to disable_dead_links_root_index.
    """
    return disable_dead_links_root_index(fp, html_file_set)


def clean_library_index_links(fp: Path) -> int:
    """
    Clean links in library-level `index.html` files.

    These index files serve as grouping pages for library elements.
    Only trivial invalid links are disabled (javascript:, empty, localhost, etc.).
    Existence of target files is not validated here.

    Returns:
        The number of modified links.
    """
    soup = BeautifulSoup(fp.read_text("utf-8", errors="ignore"), "lxml")
    n = 0

    for a in soup.find_all("a", href=True):
        if is_trivially_bad_href(a["href"]):
            _disable_link(soup, a)
            n += 1

    if n:
        fp.write_text(str(soup), encoding="utf-8")
    return n


def fix_class_sidebar_links(html_path: Path, html_file_set: set[Path]) -> int:
    """
    Normalize links in class sidebar files (`*-class-sidebar.html`).

    Corrections include:
        - Ignore anchors and external URLs.
        - If a link starts with '{folder}/', remove that folder prefix once.
        - For .html targets:
            * If the file exists, normalize the href value.
            * If the file does not exist and the href is trivial, disable the link.
        - For non-HTML targets (directories or asset paths), only trivial hrefs are disabled.

    Returns:
        The number of modified links.
    """
    soup = BeautifulSoup(html_path.read_text(encoding="utf-8", errors="ignore"), "lxml")
    n = 0
    folder = html_path.parent.name

    for a in soup.find_all("a", href=True):
        href = a["href"]
        if not href:
            continue

        # Skip anchors and external links.
        if is_anchor(href) or href.startswith(("http://", "https://")):
            continue

        # Normalize the path and remove duplicated folder prefix.
        if not is_external_or_js(href):
            hnorm = href.replace("\\", "/")
            prefix = f"{folder}/"
            if hnorm.startswith(prefix):
                hnorm = hnorm[len(prefix):]
                # Preserve anchors if present.
                if "#" in href:
                    frag = href.split("#", 1)[1]
                    hnorm = f"{hnorm.split('#', 1)[0]}#{frag}"
                href = hnorm

        clean = href.split("#")[0].strip()
        if clean and clean.endswith(".html"):
            target = (OUT_ROOT / clean).resolve()
            if target in html_file_set:
                if a["href"] != href:
                    a["href"] = href
                    n += 1
            else:
                if is_trivially_bad_href(a["href"]):
                    _disable_link(soup, a)
                    n += 1
        else:
            if is_trivially_bad_href(a["href"]):
                _disable_link(soup, a)
                n += 1

    if n:
        html_path.write_text(str(soup), encoding="utf-8")
    return n


# ───────────────────────── Copying / orchestration ─────────────────────────


def copy_tree(src: Path, dst: Path) -> None:
    """
    Create a fresh copy of the source directory at the destination path.

    Behavior:
        - If the destination exists, it is fully removed first.
        - The entire source tree is then copied recursively.

    This ensures the cleaner always operates on a clean working directory.
    """
    if dst.exists():
        shutil.rmtree(dst)
    shutil.copytree(src, dst)


def clean_docs(src: Path, out_root: Path, min_chars: int, tiny_chars: int) -> None:
    """
    Main entry point for cleaning a Dartdoc documentation tree.

    Steps performed:
      1) Copy the documentation folder `src` to `out_root / src.name`.
      2) Collect all HTML files.
      3) Determine which HTML files are stubs using is_deletable_stub, and delete them.
      4) Process all remaining HTML files:
         - root index.html -> patch_root_index
         - library index.html -> clean_library_index_links
         - *-class-sidebar.html -> fix_class_sidebar_links
         - everything else -> disable_dead_links_generic

    Parameters:
        src: Path to the original Dartdoc HTML directory (usually doc/api/).
        out_root: Directory where the cleaned copy will be written.
        min_chars: Threshold for sidebar and library index stub detection.
        tiny_chars: Threshold for detecting other very small stubs.
    """
    if not src.is_dir():
        raise FileNotFoundError(src)

    dst = out_root / src.name
    copy_tree(src, dst)

    global OUT_ROOT, stub_paths
    OUT_ROOT = dst.resolve()

    # Collect all HTML files before removing stubs.
    html_paths = list(dst.rglob("*.html"))

    # Identify and remove stub files.
    stub_paths = {p for p in html_paths if is_deletable_stub(p, min_chars, tiny_chars)}
    for p in stub_paths:
        p.unlink(missing_ok=True)

    # HTML files after removing stubs.
    html_paths = [p for p in html_paths if p not in stub_paths]
    html_file_set = {p.resolve() for p in html_paths}

    # Process each HTML file according to its type or location.
    for html_path in html_paths:
        html_abs = html_path.resolve()
        rel = html_abs.relative_to(OUT_ROOT)
        name = html_path.name

        if rel == Path("index.html"):
            patch_root_index(html_path, html_file_set)
        elif name == "index.html":
            clean_library_index_links(html_path)
        elif name.endswith("-class-sidebar.html"):
            fix_class_sidebar_links(html_path, html_file_set)
        else:
            disable_dead_links_generic(html_path, html_file_set)


# ───────────────────────── CLI ─────────────────────────


def main() -> None:
    """
    Minimal command-line interface for the cleaner.
    This does not create ZIP files, it only performs the cleaning workflow.

    Example:
        python -m dartdoc_advanced.cleaner.core path/to/doc/api \
            --out-root tmp_out \
            --min-chars 350 --tiny-chars 150
    """
    ap = argparse.ArgumentParser("Clean dartdoc stub & dead links")
    ap.add_argument("src", type=Path, help="path to doc/api (dartdoc output)")
    ap.add_argument(
        "--out-root",
        type=Path,
        default=Path("out"),
        help="working root for the copy (default: ./out)",
    )
    ap.add_argument(
        "--min-chars",
        type=int,
        default=350,
        help="threshold for *-sidebar.html & *-library.html (default: 350)",
    )
    ap.add_argument(
        "--tiny-chars",
        type=int,
        default=150,
        help="threshold for other micro-stubs (default: 150)",
    )
    args = ap.parse_args()
    clean_docs(args.src, args.out_root, args.min_chars, args.tiny_chars)


if __name__ == "__main__":
    main()
