from __future__ import annotations

import argparse
import re
from os import PathLike
from pathlib import Path

from pdm.core import Core
from pdm.project.config import Config

ROOT = Path(__file__).resolve().parent.parent
REFERENCE_DIR = ROOT / "docs" / "reference"
TEMPLATE_DIR = ROOT / "tasks" / "doc_templates"
CLI_TEMPLATE = TEMPLATE_DIR / "cli.md.in"
CONFIGURATION_TEMPLATE = TEMPLATE_DIR / "configuration.md.in"
CLI_OUTPUT = REFERENCE_DIR / "cli.md"
CONFIGURATION_OUTPUT = REFERENCE_DIR / "configuration.md"
GENERATED_COMMENT = "<!-- Generated by tasks/render_reference_docs.py. -->"
MONOSPACED = ("pyproject.toml", "pdm.lock", ".pdm-python", ":pre", ":post", ":all")
ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]")


def clean_help(help_text: str | None) -> str:
    if not help_text:
        return ""
    help_text = strip_ansi_sequences(help_text)
    help_text = help_text.replace(r"\[", "[").replace(r"\]", "]")
    help_text = re.sub(r"__([\w\d_]+)__", r"`__\1__`", help_text)
    help_text = re.sub(r"env var: ([A-Z_]+)", r"env var: `\1`", help_text)
    help_text = re.sub(r"\[([^\]]+)\]", r"\\[\1\\]", help_text)
    for monospaced in MONOSPACED:
        help_text = re.sub(rf"\s(['\"]?{re.escape(monospaced)}['\"]?)", f" `{monospaced}`", help_text)
    return help_text


def render_parser(parser: argparse.ArgumentParser, title: str, heading_level: int = 2) -> str:
    result = [f"{'#' * heading_level} {title}\n"]
    if parser.description and title != "pdm":
        result.append("> " + clean_help(parser.description) + "\n")

    for group in sorted(parser._action_groups, key=lambda item: item.title.lower(), reverse=True):
        if not any(
            bool(action.option_strings or action.dest) or isinstance(action, argparse._SubParsersAction)
            for action in group._group_actions
        ):
            continue

        result.append(f"{group.title.title()}:\n")
        for action in group._group_actions:
            if isinstance(action, argparse._SubParsersAction):
                for name, subparser in action._name_parser_map.items():
                    result.append(render_parser(subparser, name, heading_level + 1))
                continue

            options = [f"`{option}`" for option in action.option_strings]
            line = f"- `{action.dest}`" if not options else f"- {', '.join(options)}"
            if action.metavar:
                line += f" `{action.metavar}`"
            line += f": {clean_help(action.help)}"

            if action.default and action.default != argparse.SUPPRESS:
                default = action.default
                if any(option.startswith("--no-") for option in action.option_strings) and default is True:
                    default = False
                line += f" (default: `{default}`)"

            result.append(line)
        result.append("")

    return "\n".join(result).rstrip()


def render_cli_reference() -> str:
    return render_parser(Core().parser, "pdm")


def render_configuration_table() -> str:
    rows = [
        "| Config Item | Description | Default Value | Available in Project | Env var |",
        "| --- | --- | --- | --- | --- |",
    ]

    for key, value in Config._config_map.items():
        default = f"`{format_default(value.default)}`" if value.should_show() else ""
        env_var = f"`{value.env_var}`" if value.env_var else ""
        available_in_project = "No" if value.global_only else "Yes"
        rows.append(f"| `{key}` | {value.description} | {default} | {available_in_project} | {env_var} |")

    rows.extend(
        [
            "| `pypi.<name>.url` | The URL of custom package source | `https://pypi.org/simple` | Yes | |",
            "| `pypi.<name>.username` | The username to access custom source | | Yes | |",
            "| `pypi.<name>.password` | The password to access custom source | | Yes | |",
            "| `pypi.<name>.type` | `index` or `find_links` | `index` | Yes | |",
            "| `pypi.<name>.verify_ssl` | Verify SSL certificate when query custom source | `True` | Yes | |",
            "| `repository.<name>.url` | The URL of custom package source | `https://pypi.org/simple` | Yes | |",
            "| `repository.<name>.username` | The username to access custom repository | | Yes | |",
            "| `repository.<name>.password` | The password to access custom repository | | Yes | |",
            (
                "| `repository.<name>.ca_certs` | Path to a PEM-encoded CA cert bundle "
                "(used for server cert verification) | The CA certificates from "
                "[certifi](https://pypi.org/project/certifi/) | Yes | |"
            ),
            "| `repository.<name>.verify_ssl` | Verify SSL certificate when uploading to repository | `True` | Yes | |",
        ]
    )
    return "\n".join(rows)


def format_default(value: object) -> str:
    if isinstance(value, PathLike):
        return normalize_home_path(Path(value))
    if isinstance(value, str):
        return normalize_home_path(strip_ansi_sequences(value))
    return str(value)


def normalize_home_path(value: str | Path) -> str:
    text = str(value)
    home = str(Path.home())
    if text.startswith(home):
        return text.replace(home, "~", 1)
    return text


def strip_ansi_sequences(value: str) -> str:
    return ANSI_ESCAPE_RE.sub("", value)


def render_template(path: Path, **replacements: str) -> str:
    content = path.read_text(encoding="utf-8")
    for key, value in replacements.items():
        content = content.replace(f"{{{{ {key} }}}}", value)
    return content


def write_if_changed(path: Path, content: str) -> None:
    normalized = content.rstrip() + "\n"
    if path.exists() and path.read_text(encoding="utf-8") == normalized:
        return
    path.write_text(normalized, encoding="utf-8")


def main() -> None:
    write_if_changed(
        CLI_OUTPUT,
        render_template(
            CLI_TEMPLATE,
            generated_comment=GENERATED_COMMENT,
            cli_reference=render_cli_reference(),
        ),
    )
    write_if_changed(
        CONFIGURATION_OUTPUT,
        render_template(
            CONFIGURATION_TEMPLATE,
            generated_comment=GENERATED_COMMENT,
            available_configurations=render_configuration_table(),
        ),
    )


if __name__ == "__main__":
    main()
