"""Render purchase_trade business docs for the MkDocs wiki.

The files in ``docs_source/business`` are the source of truth.  The generated
files are written back to ``docs/business`` because the wiki already points to
``docs``.
"""

from __future__ import annotations

import argparse
import html
import re
import sys
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
BUSINESS = ROOT / "business"
SOURCE = ROOT.parent / "docs_source" / "business"
MATERIAL_ICON = re.compile(r":material-[a-z0-9-]+:\s*")


def clean_inline(text: str) -> str:
    text = MATERIAL_ICON.sub("", text)
    text = re.sub(r"\s{2,}", " ", text)
    return text.strip()


def split_table_row(line: str) -> list[str]:
    return [cell.strip() for cell in line.strip().strip("|").split("|")]


def is_separator_row(line: str) -> bool:
    cells = split_table_row(line)
    return bool(cells) and all(re.fullmatch(r":?-{3,}:?", cell) for cell in cells)


def normalize_table(lines: list[str], index: int) -> tuple[list[str], int]:
    rows = []
    while index < len(lines) and lines[index].lstrip().startswith("|"):
        rows.append(lines[index])
        index += 1
    if len(rows) < 2 or not is_separator_row(rows[1]):
        return rows, index

    table = [split_table_row(row) for row in rows]
    header = [clean_inline(cell) for cell in table[0]]
    drop_first = header and header[0].lower() in {
        "repere",
        "repère",
        "marker",
        "etape",
        "étape",
        "step",
    }
    if drop_first:
        table = [row[1:] for row in table]

    cleaned = []
    for row_index, row in enumerate(table):
        if row_index == 1 and is_separator_row("| " + " | ".join(row) + " |"):
            row = ["---"] * len(table[0])
        cleaned.append("| " + " | ".join(clean_inline(cell) for cell in row) + " |")
    return cleaned, index


def normalize_source(text: str) -> str:
    lines = text.splitlines()
    out: list[str] = []
    i = 0
    in_code = False
    while i < len(lines):
        line = lines[i]

        if line.startswith("```"):
            in_code = not in_code
            out.append(line)
            i += 1
            continue

        if in_code:
            out.append(line)
            i += 1
            continue

        if line.startswith("!!! "):
            match = re.match(r"!!!\s+\w+\s+\"(.+)\"", line)
            title = clean_inline(match.group(1) if match else "Note")
            out.append(f"> **{title}**")
            i += 1
            while i < len(lines) and (
                lines[i].startswith("    ") or not lines[i].strip()
            ):
                if lines[i].strip():
                    out.append("> " + clean_inline(lines[i].strip()))
                else:
                    out.append(">")
                i += 1
            continue

        if line.lstrip().startswith("|"):
            table, i = normalize_table(lines, i)
            if len(table) > 1 and is_separator_row(table[1]):
                header_len = len(split_table_row(table[0]))
                table[1] = "| " + " | ".join(["---"] * header_len) + " |"
            out.extend(table)
            continue

        if not line.strip():
            out.append("")
        elif line.startswith("#"):
            out.append(clean_inline(line))
        else:
            leading = re.match(r"^\s*", line).group(0)
            out.append(leading + clean_inline(line.strip()))
        i += 1

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


def render_inline(text: str) -> str:
    placeholders: list[str] = []

    def keep_link(match: re.Match[str]) -> str:
        placeholders.append(
            f'<a href="{html.escape(match.group(2), quote=True)}">{html.escape(match.group(1))}</a>'
        )
        return f"@@LINK{len(placeholders) - 1}@@"

    text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", keep_link, text)
    text = html.escape(text)
    text = re.sub(r"`([^`]+)`", r"<code>\1</code>", text)
    for idx, value in enumerate(placeholders):
        text = text.replace(f"@@LINK{idx}@@", value)
    return text


def render_table(rows: list[str]) -> str:
    parsed = [split_table_row(row) for row in rows]
    header = parsed[0]
    body = parsed[2:]
    out = [
        '<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">',
        "<thead>",
        '<tr style="background:#eef3ff;">',
    ]
    for cell in header:
        out.append(
            '<th style="text-align:left; padding:0.55rem 0.7rem; '
            'border-bottom:2px solid #4051b5;">'
            f"{render_inline(cell)}</th>"
        )
    out.extend(["</tr>", "</thead>", "<tbody>"])
    for row in body:
        out.append('<tr style="border-bottom:1px solid #e0e0e0;">')
        for cell in row:
            out.append(
                '<td style="vertical-align:top; padding:0.5rem 0.7rem;">'
                f"{render_inline(cell)}</td>"
            )
        out.append("</tr>")
    out.extend(["</tbody>", "</table>"])
    return "\n".join(out)


def render_callout(lines: list[str]) -> str:
    title = ""
    body = []
    for line in lines:
        content = line[1:].strip()
        if content.startswith("**") and content.endswith("**") and not title:
            title = content.strip("*")
        elif content:
            body.append(content)
    title_html = (
        f'<strong style="display:block; margin-bottom:0.35rem;">{render_inline(title)}</strong>'
        if title
        else ""
    )
    body_html = " ".join(render_inline(line) for line in body)
    return (
        '<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; '
        'padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;">'
        f"{title_html}<span>{body_html}</span></div>"
    )


def is_bullet_line(line: str) -> bool:
    return bool(re.match(r"^\s*-\s+", line))


def bullet_level(line: str) -> int:
    return len(re.match(r"^\s*", line).group(0)) // 2


def render_list(lines: list[str]) -> str:
    root: list[dict[str, object]] = []
    stack: list[tuple[int, list[dict[str, object]]]] = [(-1, root)]
    last_item: dict[str, object] | None = None

    for line in lines:
        if is_bullet_line(line):
            level = bullet_level(line)
            content = re.sub(r"^\s*-\s+", "", line).strip()
            while stack[-1][0] >= level:
                stack.pop()
            item: dict[str, object] = {
                "content": content,
                "children": [],
            }
            stack[-1][1].append(item)
            stack.append((level, item["children"]))
            last_item = item
        elif line.strip() and last_item is not None:
            last_item["content"] = str(last_item["content"]) + " " + line.strip()

    def render_items(items: list[dict[str, object]], level: int = 0) -> str:
        margin = "1.1rem" if level == 0 else "1.35rem"
        bullet = "disc" if level == 0 else "circle"
        parts = [
            f'<ul style="margin:0.65rem 0 1rem {margin}; padding-left:1rem; list-style-type:{bullet};">'
        ]
        for item in items:
            children = item["children"]
            parts.append(
                '<li style="margin:0.38rem 0;">'
                f"{render_inline(str(item['content']))}"
            )
            if children:
                parts.append(render_items(children, level + 1))
            parts.append("</li>")
        parts.append("</ul>")
        return "\n".join(parts)

    return render_items(root)


def render_source(text: str) -> str:
    lines = text.splitlines()
    out = [
        "<!-- Generated from docs_source/business by docs/tools/render_business_docs.py. -->",
        "",
    ]
    in_code = False
    code_lines: list[str] = []
    i = 0
    while i < len(lines):
        line = lines[i]

        if line.startswith("```"):
            if in_code:
                out.append(
                    '<pre style="background:#263238; color:#eef7ff; padding:1rem; '
                    'border-radius:0.35rem; overflow:auto;"><code>'
                    + html.escape("\n".join(code_lines))
                    + "</code></pre>"
                )
                code_lines = []
                in_code = False
            else:
                in_code = True
            i += 1
            continue
        if in_code:
            code_lines.append(line)
            i += 1
            continue

        if line.startswith(">"):
            block = []
            while i < len(lines) and lines[i].startswith(">"):
                if not lines[i][1:].strip():
                    i += 1
                    break
                block.append(lines[i])
                i += 1
            out.append(render_callout(block))
            out.append("")
            continue

        if line.lstrip().startswith("|"):
            table = []
            while i < len(lines) and lines[i].lstrip().startswith("|"):
                table.append(lines[i])
                i += 1
            if len(table) > 1 and is_separator_row(table[1]):
                out.append(render_table(table))
            else:
                out.extend(table)
            continue

        if is_bullet_line(line):
            list_lines = []
            while i < len(lines) and (
                is_bullet_line(lines[i])
                or (
                    lines[i].startswith(" ")
                    and lines[i].strip()
                    and not lines[i].lstrip().startswith("|")
                )
            ):
                list_lines.append(lines[i])
                i += 1
            out.append(render_list(list_lines))
            continue

        out.append(line)
        i += 1

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


def source_files() -> list[Path]:
    return sorted(path for path in SOURCE.rglob("*.md") if path.is_file())


def render_all(normalize: bool = False, check: bool = False) -> bool:
    SOURCE.mkdir(parents=True, exist_ok=True)
    ok = True
    for source in source_files():
        target = BUSINESS / source.relative_to(SOURCE)
        target.parent.mkdir(parents=True, exist_ok=True)
        if normalize:
            source.write_text(normalize_source(source.read_text(encoding="utf-8")), encoding="utf-8")
        rendered = render_source(source.read_text(encoding="utf-8"))
        if check:
            if not target.exists() or target.read_text(encoding="utf-8") != rendered:
                print(f"Outdated generated doc: {target.relative_to(ROOT.parent)}")
                ok = False
        else:
            target.write_text(rendered, encoding="utf-8")
    return ok


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--normalize-source",
        action="store_true",
        help="Clean existing source files from wiki-only syntax before rendering.",
    )
    parser.add_argument(
        "--check",
        action="store_true",
        help="Fail if generated wiki files are not up to date.",
    )
    args = parser.parse_args()
    if args.normalize_source and args.check:
        parser.error("--normalize-source cannot be used with --check")
    if not render_all(normalize=args.normalize_source, check=args.check):
        sys.exit(1)


if __name__ == "__main__":
    main()
