from __future__ import annotations

import inspect
from dataclasses import dataclass
from pathlib import Path

from .builtin_modules import BuiltinFunction, builtin_module_alias_groups, load_builtin_module


@dataclass(frozen=True)
class DocBlock:
    file: Path
    line_start: int
    text_lines: list[str]
    code_line: str | None


def _is_comment_line(line: str) -> bool:
    s = line.lstrip()
    return s.startswith("#") or s.startswith("//")


def _comment_text(line: str) -> str:
    s = line.lstrip()
    if s.startswith("//"):
        return s[2:].lstrip()
    if s.startswith("#"):
        return s[1:].lstrip()
    return s


def _find_next_code_line(lines: list[str], start_index: int) -> str | None:
    i = start_index
    while i < len(lines):
        raw = lines[i]
        if raw.strip() == "":
            i += 1
            continue
        if _is_comment_line(raw):
            return None
        return raw.rstrip("\n")
    return None


def extract_doc_blocks(source: str, file: Path) -> list[DocBlock]:
    lines = source.splitlines()
    blocks: list[DocBlock] = []

    i = 0
    while i < len(lines):
        if not _is_comment_line(lines[i]):
            i += 1
            continue

        start = i
        text_lines: list[str] = []
        while i < len(lines) and _is_comment_line(lines[i]):
            text_lines.append(_comment_text(lines[i]))
            i += 1

        # If a comment block is immediately followed by code, attach that code line.
        code_line = _find_next_code_line(lines, i)
        blocks.append(
            DocBlock(
                file=file,
                line_start=start + 1,
                text_lines=text_lines,
                code_line=code_line,
            )
        )

    return blocks


def discover_prasad_files(*roots: str) -> list[Path]:
    files: list[Path] = []
    for root in roots:
        p = Path(root)
        if p.is_file() and p.suffix == ".prasad":
            files.append(p)
            continue
        if p.is_dir():
            files.extend(sorted(p.rglob("*.prasad")))
    # de-dup while preserving order
    seen: set[Path] = set()
    out: list[Path] = []
    for f in files:
        rf = f.resolve()
        if rf in seen:
            continue
        seen.add(rf)
        out.append(f)
    return out


def generate_markdown(*roots: str, title: str = "Prasad Language Docs") -> str:
    files = discover_prasad_files(*roots)
    parts: list[str] = [f"# {title}", ""]

    parts.append("## Standard Library")
    parts.append("")
    parts.append(generate_stdlib_reference().rstrip())
    parts.append("")

    if not files:
        parts.append("_No .prasad files found._")
        return "\n".join(parts).rstrip() + "\n"

    parts.append("## Files")
    for f in files:
        parts.append(f"- `{f.as_posix()}`")
    parts.append("")

    for f in files:
        try:
            source = f.read_text(encoding="utf-8")
        except Exception:
            continue

        blocks = extract_doc_blocks(source, f)
        if not blocks:
            continue

        parts.append(f"## `{f.as_posix()}`")
        parts.append("")

        for block in blocks:
            # Support a tiny directive syntax in comments:
            # - "@title X" becomes a subheading.
            directive = None
            if block.text_lines:
                first = block.text_lines[0].strip()
                if first.startswith("@title "):
                    directive = ("title", first[len("@title ") :].strip())
                elif first.startswith("@section "):
                    directive = ("section", first[len("@section ") :].strip())

            text_lines = block.text_lines[:]
            if directive is not None:
                # drop directive line
                text_lines = text_lines[1:]
                if directive[0] == "title":
                    parts.append(f"### {directive[1]}")
                    parts.append("")
                elif directive[0] == "section":
                    parts.append(f"### {directive[1]}")
                    parts.append("")

            paragraph = "\n".join(text_lines).strip()
            if paragraph:
                parts.append(paragraph)
                parts.append("")

            if block.code_line:
                parts.append("```prasad")
                parts.append(block.code_line)
                parts.append("```")
                parts.append("")

    return "\n".join(parts).rstrip() + "\n"


def _format_default(value) -> str:
    if value is inspect._empty:
        return ""
    if value is None:
        return "kahi"
    if value is True:
        return "ho"
    if value is False:
        return "khota"
    if isinstance(value, str):
        return f'"{value}"'
    return str(value)


def _format_signature(fn, *, drop_first: bool) -> str:
    sig = inspect.signature(fn)
    params = list(sig.parameters.values())
    if drop_first and params:
        params = params[1:]

    parts = []
    for p in params:
        if p.kind in (p.VAR_POSITIONAL, p.VAR_KEYWORD):
            continue
        if p.default is inspect._empty:
            parts.append(p.name)
        else:
            parts.append(f"{p.name}={_format_default(p.default)}")
    return "(" + ", ".join(parts) + ")"


def generate_stdlib_reference() -> str:
    groups = builtin_module_alias_groups()
    out: list[str] = []

    for canonical, aliases in groups.items():
        out.append(f"### `{canonical}`")
        if len(aliases) > 1:
            out.append(f"Aliases: {', '.join('`' + a + '`' for a in aliases)}")
        out.append("")

        exports = load_builtin_module(canonical)
        functions: list[tuple[str, str]] = []
        constants: list[tuple[str, str]] = []

        for name, value in sorted(exports.items(), key=lambda kv: kv[0]):
            if isinstance(value, BuiltinFunction):
                sig = _format_signature(value.func, drop_first=True)
                functions.append((name, sig))
            elif callable(value):
                sig = _format_signature(value, drop_first=False)
                functions.append((name, sig))
            else:
                constants.append((name, repr(value)))

        if functions:
            out.append("**Functions**")
            for fname, sig in functions:
                out.append(f"- `{canonical}.{fname}{sig}`")
            out.append("")

        if constants:
            out.append("**Constants**")
            for cname, cval in constants:
                out.append(f"- `{canonical}.{cname}` = `{cval}`")
            out.append("")

    return "\n".join(out).rstrip() + "\n"
