"""
SuperCollider UGen platform implementation.

Generates cross-platform SuperCollider UGens (.scx on macOS, .so on Linux)
using CMake and the SC plugin interface headers (fetched at configure time
via CMake FetchContent from the SC source tarball).

Output artifacts:
  - <name>.scx/.so     (compiled UGen binary)
  - <UgenName>.sc       (SuperCollider class file for sclang)
"""

import re
import shutil
import sys
from pathlib import Path
from string import Template
from typing import Optional

from gen_dsp.core.builder import BuildResult
from gen_dsp.core.manifest import Manifest, ParamInfo
from gen_dsp.core.project import ProjectConfig
from gen_dsp.errors import BuildError, ProjectError
from gen_dsp.platforms.base import Platform
from gen_dsp.templates import get_sc_templates_dir


class SuperColliderPlatform(Platform):
    """SuperCollider UGen platform implementation using CMake."""

    name = "sc"

    @property
    def extension(self) -> str:
        """Get the extension for SC UGens."""
        if sys.platform == "darwin" or sys.platform == "win32":
            return ".scx"
        return ".so"

    def get_build_instructions(self) -> list[str]:
        """Get build instructions for SuperCollider UGens."""
        return [
            "cmake -B build && cmake --build build",
        ]

    def generate_project(
        self,
        manifest: Manifest,
        output_dir: Path,
        lib_name: str,
        config: Optional[ProjectConfig] = None,
    ) -> None:
        """Generate SuperCollider UGen project files."""
        templates_dir = get_sc_templates_dir()
        if not templates_dir.is_dir():
            raise ProjectError(f"SC templates not found at {templates_dir}")

        # Copy static files
        static_files = [
            "gen_ext_sc.cpp",
            "gen_ext_common_sc.h",
            "_ext_sc.cpp",
            "_ext_sc.h",
            "sc_buffer.h",
        ]
        for filename in static_files:
            src = templates_dir / filename
            if src.exists():
                shutil.copy2(src, output_dir / filename)

        # UGen name (first letter capitalized, required by SC)
        ugen_name = self._capitalize_name(lib_name)

        # Generate .sc class file
        self._generate_sc_class(
            output_dir,
            lib_name,
            ugen_name,
            manifest.num_inputs,
            manifest.num_outputs,
            manifest.num_params,
            manifest.params,
        )

        # Resolve shared cache settings
        shared_cache = config is not None and config.shared_cache
        if shared_cache:
            from gen_dsp.core.cache import get_cache_dir

            cache_dir = get_cache_dir().as_posix()
        else:
            cache_dir = ""

        # Generate CMakeLists.txt
        self._generate_cmakelists(
            templates_dir / "CMakeLists.txt.template",
            output_dir / "CMakeLists.txt",
            manifest.gen_name,
            lib_name,
            ugen_name,
            manifest.num_inputs,
            manifest.num_outputs,
            manifest.num_params,
            use_shared_cache="ON" if shared_cache else "OFF",
            cache_dir=cache_dir,
        )

        # Generate gen_buffer.h using base class method
        self.generate_buffer_header(
            templates_dir / "gen_buffer.h.template",
            output_dir / "gen_buffer.h",
            manifest.buffers,
            header_comment="Buffer configuration for gen_dsp SuperCollider wrapper",
        )

        # Create build directory
        (output_dir / "build").mkdir(exist_ok=True)

    def _detect_plugin_type(self, num_inputs: int) -> str:
        """Detect SC UGen type from number of inputs.

        Returns 'effect' if inputs > 0, 'generator' if inputs == 0.
        """
        return "effect" if num_inputs > 0 else "generator"

    @staticmethod
    def _capitalize_name(name: str) -> str:
        """Capitalize first letter for SC class name.

        SuperCollider class names must start with an uppercase letter.
        """
        if not name:
            return name
        return name[0].upper() + name[1:]

    def _generate_sc_class(
        self,
        output_dir: Path,
        lib_name: str,
        ugen_name: str,
        num_inputs: int,
        num_outputs: int,
        num_params: int,
        params: list[ParamInfo],
    ) -> None:
        """Generate SuperCollider class file (.sc).

        The class file tells sclang about the UGen's interface:
        argument names, default values, number of outputs, and
        input rate validation.
        """
        base_class = "MultiOutUGen" if num_outputs > 1 else "UGen"

        lines = [
            f"// {ugen_name}.sc - SuperCollider class for {lib_name}",
            "// Generated by gen-dsp",
            "",
            f"{ugen_name} : {base_class} {{",
        ]

        # Build argument list for *ar method
        args = []
        arg_names = []
        for i in range(num_inputs):
            args.append(f"in{i}")
            arg_names.append(f"in{i}")
        for i in range(num_params):
            if i < len(params):
                p = params[i]
                pname = self._sanitize_sc_arg(p.name)
                default = p.default
            else:
                pname = f"param{i}"
                default = 0.0
            default_str = self._format_sc_number(default)
            args.append(f"{pname}={default_str}")
            arg_names.append(pname)

        # *ar method
        if args:
            args_str = ", ".join(args)
            arg_names_str = ", ".join(arg_names)
            lines.append(f"    *ar {{ |{args_str}|")
            lines.append(f"        ^this.multiNew('audio', {arg_names_str})")
            lines.append("    }")
        else:
            lines.append("    *ar {")
            lines.append("        ^this.multiNew('audio')")
            lines.append("    }")

        # init method (only for MultiOutUGen)
        if num_outputs > 1:
            lines.append("")
            lines.append("    init { |... theInputs|")
            lines.append("        inputs = theInputs;")
            lines.append(f"        ^this.initOutputs({num_outputs}, rate);")
            lines.append("    }")

        # checkInputs (validate audio-rate inputs)
        if num_inputs > 0:
            lines.append("")
            lines.append("    checkInputs {")
            lines.append(f"        {num_inputs}.do {{ |i|")
            lines.append("            if(inputs[i].rate != 'audio') {")
            lines.append('                ^("input " ++ i ++ " is not audio rate");')
            lines.append("            };")
            lines.append("        };")
            lines.append("        ^this.checkValidInputs;")
            lines.append("    }")

        lines.append("}")

        content = "\n".join(lines) + "\n"
        (output_dir / f"{ugen_name}.sc").write_text(content, encoding="utf-8")

    @staticmethod
    def _sanitize_sc_arg(name: str) -> str:
        """Sanitize a parameter name for use as an SC method argument.

        SC identifiers must start with a lowercase letter and contain
        only alphanumeric characters and underscores.
        """
        sanitized = re.sub(r"[^a-zA-Z0-9_]", "_", name)
        if sanitized and sanitized[0].isupper():
            sanitized = sanitized[0].lower() + sanitized[1:]
        if sanitized and sanitized[0].isdigit():
            sanitized = "p_" + sanitized
        return sanitized or "param"

    @staticmethod
    def _format_sc_number(value: float) -> str:
        """Format a float for SC source (drop trailing .0 for integers)."""
        if value == int(value):
            return str(int(value))
        return str(value)

    def _generate_cmakelists(
        self,
        template_path: Path,
        output_path: Path,
        gen_name: str,
        lib_name: str,
        ugen_name: str,
        num_inputs: int,
        num_outputs: int,
        num_params: int,
        use_shared_cache: str = "OFF",
        cache_dir: str = "",
    ) -> None:
        """Generate CMakeLists.txt from template."""
        if not template_path.exists():
            raise ProjectError(f"CMakeLists.txt template not found at {template_path}")

        template_content = template_path.read_text(encoding="utf-8")
        template = Template(template_content)
        content = template.safe_substitute(
            gen_name=gen_name,
            lib_name=lib_name,
            ugen_name=ugen_name,
            genext_version=self.GENEXT_VERSION,
            num_inputs=num_inputs,
            num_outputs=num_outputs,
            num_params=num_params,
            use_shared_cache=use_shared_cache,
            cache_dir=cache_dir,
        )
        output_path.write_text(content, encoding="utf-8")

    def build(
        self,
        project_dir: Path,
        clean: bool = False,
        verbose: bool = False,
    ) -> BuildResult:
        """Build SuperCollider UGen using CMake."""
        cmakelists = project_dir / "CMakeLists.txt"
        if not cmakelists.exists():
            raise BuildError(f"CMakeLists.txt not found in {project_dir}")

        build_dir = project_dir / "build"

        # Clean if requested
        if clean and build_dir.exists():
            shutil.rmtree(build_dir)

        build_dir.mkdir(exist_ok=True)

        # Configure with CMake
        configure_result = self.run_command(["cmake", ".."], build_dir, verbose=verbose)
        if configure_result.returncode != 0:
            return BuildResult(
                success=False,
                platform="sc",
                output_file=None,
                stdout=configure_result.stdout,
                stderr=configure_result.stderr,
                return_code=configure_result.returncode,
            )

        # Build
        build_result = self.run_command(
            ["cmake", "--build", "."], build_dir, verbose=verbose
        )

        # Find output
        output_file = self.find_output(project_dir)

        return BuildResult(
            success=build_result.returncode == 0,
            platform="sc",
            output_file=output_file,
            stdout=build_result.stdout,
            stderr=build_result.stderr,
            return_code=build_result.returncode,
        )

    def clean(self, project_dir: Path) -> None:
        """Clean build artifacts."""
        build_dir = project_dir / "build"
        if build_dir.exists():
            shutil.rmtree(build_dir)

    def find_output(self, project_dir: Path) -> Optional[Path]:
        """Find the built SC UGen binary."""
        build_dir = project_dir / "build"
        if build_dir.is_dir():
            for ext in [".scx", ".so", ".dll"]:
                for f in build_dir.glob(f"**/*{ext}"):
                    if f.is_file():
                        return f
        return None
