"""Build script for pySurprise C++ extension + algorithm binaries."""

import os
import shutil
import subprocess
import sys
from pathlib import Path

from pybind11.setup_helpers import Pybind11Extension
from pybind11.setup_helpers import build_ext as _pybind_build_ext
from setuptools import setup

VERSION = "1.1.0"

# ── Standalone C++ executables to compile and ship in the wheel ──────
# Each maps a flat binary name to a list of source files (repo-relative).
BINARIES = {
    "computeSurprise": [
        "src/Surprise/computeSurprise.cpp",
    ],
    "jerarca": [
        "src/Jerarca/src/main.cpp",
        "src/Jerarca/src/Upgma.cpp",
        "src/Jerarca/src/SCluster.cpp",
        "src/Jerarca/src/UVCluster.cpp",
        "src/Jerarca/src/Graph.cpp",
        "src/Jerarca/src/Surprise.cpp",
    ],
    "cpm_community": [
        "src/CPM/src/Community/main_community.cpp",
        "src/CPM/src/Base/community.cpp",
        "src/CPM/src/Base/graph.cpp",
        "src/CPM/src/Base/greedy_louvain.cpp",
        "src/CPM/src/Base/info.cpp",
        "src/CPM/src/MTRand/MersenneTwister.cpp",
    ],
    "cpm_slicer": [
        "src/CPM/src/Slicer/main_slicer.cpp",
        "src/CPM/src/Slicer/slicer.cpp",
        "src/CPM/src/Base/graph.cpp",
    ],
    "rb_community": [
        "src/RB/src/Community/main_community.cpp",
        "src/RB/src/Base/community.cpp",
        "src/RB/src/Base/graph.cpp",
        "src/RB/src/Base/greedy_louvain.cpp",
        "src/RB/src/Base/info.cpp",
        "src/RB/src/MTRand/MersenneTwister.cpp",
    ],
    "rb_slicer": [
        "src/RB/src/Slicer/main_slicer.cpp",
        "src/RB/src/Slicer/slicer.cpp",
        "src/RB/src/Base/graph.cpp",
    ],
    "rnsc": [
        "src/RNSC/source/rnsc/main.cpp",
        "src/RNSC/source/rnsc/linkedList.cpp",
        "src/RNSC/source/rnsc/linkedList2.cpp",
        "src/RNSC/source/rnsc/linkedList3.cpp",
        "src/RNSC/source/rnsc/graph.cpp",
        "src/RNSC/source/rnsc/graph2.cpp",
        "src/RNSC/source/rnsc/graph3.cpp",
        "src/RNSC/source/rnsc/graph4.cpp",
        "src/RNSC/source/rnsc/graph5.cpp",
        "src/RNSC/source/rnsc/graph6.cpp",
        "src/RNSC/source/rnsc/printing.cpp",
        "src/RNSC/source/rnsc/experiment.cpp",
        "src/RNSC/source/rnsc/miscFunctions.cpp",
    ],
    "rnscconvert": [
        "src/RNSC/source/convert/main.cpp",
        "src/RNSC/source/convert/linkedList.cpp",
        "src/RNSC/source/convert/linkedList2.cpp",
        "src/RNSC/source/convert/linkedList3.cpp",
        "src/RNSC/source/convert/graph.cpp",
        "src/RNSC/source/convert/graph2.cpp",
        "src/RNSC/source/convert/graph3.cpp",
        "src/RNSC/source/convert/graph4.cpp",
        "src/RNSC/source/convert/graph5.cpp",
        "src/RNSC/source/convert/graph6.cpp",
        "src/RNSC/source/convert/printing.cpp",
        "src/RNSC/source/convert/experiment.cpp",
        "src/RNSC/source/convert/miscFunctions.cpp",
    ],
    "rnscfilter": [
        "src/RNSC/source/filter/graph.cpp",
        "src/RNSC/source/filter/linkedList.cpp",
        "src/RNSC/source/filter/form.cpp",
    ],
}


def _find_cxx():
    """Find a working C++ compiler."""
    for name in ["g++", "c++", "clang++"]:
        if shutil.which(name):
            return name
    return None


class BuildExtWithBinaries(_pybind_build_ext):
    """Extended build_ext that also compiles standalone C++ executables."""

    def run(self):
        super().run()
        self._compile_binaries()

    def _compile_binaries(self):
        cxx = _find_cxx()
        if cxx is None:
            print("WARNING: No C++ compiler found, skipping algorithm binaries")
            return

        bin_dir = Path(self.build_lib) / "pysurprise" / "bin"
        bin_dir.mkdir(parents=True, exist_ok=True)

        ext = ".exe" if sys.platform == "win32" else ""
        compiled = 0

        for name, sources in BINARIES.items():
            output = bin_dir / f"{name}{ext}"

            missing = [s for s in sources if not os.path.exists(s)]
            if missing:
                print(f"  SKIP {name}: missing sources {missing}")
                continue

            cmd = [cxx, "-O3", "-Wall", "-o", str(output)] + sources
            if sys.platform != "win32":
                cmd.append("-lpthread")

            print(f"  Compiling {name} ...")
            try:
                result = subprocess.run(
                    cmd, capture_output=True, text=True, check=True
                )
                os.chmod(str(output), 0o755)
                compiled += 1
            except subprocess.CalledProcessError as e:
                stderr = (e.stderr or "")[:300]
                print(f"  WARN: {name} failed: {stderr}")
            except Exception as e:
                print(f"  WARN: {name} error: {e}")

        print(f"  Compiled {compiled}/{len(BINARIES)} algorithm binaries")


# ── pybind11 extension ───────────────────────────────────────────────
ext_modules = [
    Pybind11Extension(
        "pysurprise._core",
        sources=[
            "pysurprise/csrc/bindings.cpp",
            "pysurprise/csrc/surprise.cpp",
        ],
        include_dirs=["pysurprise/csrc"],
        cxx_std=11,
        define_macros=[("VERSION_INFO", f'"{VERSION}"')],
    ),
]

setup(
    ext_modules=ext_modules,
    cmdclass={"build_ext": BuildExtWithBinaries},
)
