# type: ignore
"""Tests for the pass_visualizer debugging tool.

Covers:
- Core helpers: build_module, build_pass_stages, inspect_structure
- Structure-tree capture and per-pass diffing (build_pass_data via viewer)
- HTML / text emission (emit_html, emit_txt)

These run the CUDA lowering prologue on a small kernel. An explicit cuda target
is passed so the pipeline does not depend on auto target detection.
"""

import tilelang
import tilelang.testing
import tilelang.language as T
from tilelang import tvm

from tilelang.tools.pass_visualizer import (
    build_module,
    build_pass_stages,
    inspect_structure,
)
from tilelang.tools.pass_visualizer.viewer import (
    _capture_tree,
    build_pass_data,
    emit_html,
    emit_txt,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _gemm_relu_kernel():
    """A small fused GEMM + bias + ReLU @tilelang.jit kernel."""

    @tilelang.jit(out_idx=[-1])
    def gemm_relu(M, N, K, block_M, block_N, block_K, dtype="float16", accum_dtype="float32"):

        @T.prim_func
        def main(
            A: T.Tensor((M, K), dtype),
            B: T.Tensor((K, N), dtype),
            bias: T.Tensor((N,), dtype),
            C: T.Tensor((M, N), dtype),
        ):
            with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M), threads=128) as (bx, by):
                A_shared = T.alloc_shared((block_M, block_K), dtype)
                B_shared = T.alloc_shared((block_K, block_N), dtype)
                C_local = T.alloc_fragment((block_M, block_N), accum_dtype)

                T.clear(C_local)
                for k in T.Pipelined(T.ceildiv(K, block_K), num_stages=3):
                    T.copy(A[by * block_M, k * block_K], A_shared)
                    T.copy(B[k * block_K, bx * block_N], B_shared)
                    T.gemm(A_shared, B_shared, C_local)

                for i, j in T.Parallel(block_M, block_N):
                    C_local[i, j] = T.max(C_local[i, j] + bias[bx * block_N + j], 0)

                T.copy(C_local, C[by * block_M, bx * block_N])

        return main

    return gemm_relu


def _build_small_module():
    """Build an IRModule + resolved cuda target for the small kernel."""
    kernel = _gemm_relu_kernel()
    func = kernel.get_tir(M=128, N=128, K=128, block_M=64, block_N=64, block_K=32)
    cuda_target = tvm.target.Target("cuda", host="llvm")
    return build_module(func, target=cuda_target)


# ---------------------------------------------------------------------------
# Core helpers
# ---------------------------------------------------------------------------


@tilelang.testing.requires_cuda
def test_build_pass_stages_nonempty():
    """build_pass_stages returns an ordered list of (name, transform) pairs."""
    _mod, target = _build_small_module()
    stages = build_pass_stages(target)

    assert len(stages) > 0
    names = [name for name, _ in stages]
    # The prologue must run LayoutInference and LowerTileOp.
    assert "LayoutInference" in names
    assert "LowerTileOp" in names
    for name, transform in stages:
        assert isinstance(name, str)
        assert callable(transform)


@tilelang.testing.requires_cuda
def test_inspect_structure_renders_tree(capsys):
    """inspect_structure prints the SBlock tree with tile ops expanded by field."""
    mod, _target = _build_small_module()
    inspect_structure(mod)
    out = capsys.readouterr().out

    assert "PrimFunc" in out
    assert "SBlock" in out
    # gemm is expanded by field name, not printed as one positional line.
    assert "T.gemm" in out
    assert "a_region" in out


@tilelang.testing.requires_cuda
def test_capture_tree_returns_lines():
    """_capture_tree turns inspect_structure output into a list of text lines."""
    mod, _target = _build_small_module()
    lines = _capture_tree(mod)

    assert isinstance(lines, list)
    assert len(lines) > 0
    assert any("PrimFunc" in ln for ln in lines)


# ---------------------------------------------------------------------------
# build_pass_data + emission (file-driven)
# ---------------------------------------------------------------------------


@tilelang.testing.requires_cuda
def test_build_pass_data_and_emit(tmp_path):
    """End-to-end: run the example kernel through the pipeline and emit HTML/txt."""
    import os

    kernel_path = os.path.join(
        os.path.dirname(tilelang.tools.pass_visualizer.__file__),
        "examples",
        "gemm_relu.py",
    )
    with open(kernel_path) as f:
        source = f.read()

    kwargs = {"M": 128, "N": 128, "K": 128, "block_M": 64, "block_N": 64, "block_K": 32}
    name, stages = build_pass_data(kernel_path, None, "auto", kwargs, source)

    assert name == "gemm_relu"
    # source + (input) + at least one pass.
    assert len(stages) >= 3
    for st in stages:
        assert "name" in st and "flag" in st and "rows" in st
    flags = {st["flag"] for st in stages}
    assert "source" in flags
    assert "input" in flags

    html = emit_html(name, stages)
    assert "Pass browser" in html
    assert "gemm_relu" in html

    txt = emit_txt(name, stages)
    assert "kernel: gemm_relu" in txt
    assert "T.gemm" in txt


if __name__ == "__main__":
    tilelang.testing.main()
