"""Compilation utilities for Mira."""

from __future__ import annotations

import os
import py_compile
import shutil
from typing import Iterable

NATIVE_EXTENSIONS: tuple[str, ...] = (".dll", ".pyd", ".so", ".dylib")
SKIP_DIRS: set[str] = {"__pycache__", ".git", ".venv", "venv"}


def compile_file(src: str, dst: str) -> None:
    """Compile a single .py file into a .pyc file.

    Args:
        src: Source .py file path.
        dst: Destination .pyc file path.

    Raises:
        RuntimeError: If compilation fails.
    """
    os.makedirs(os.path.dirname(dst), exist_ok=True)
    try:
        py_compile.compile(src, cfile=dst, dfile=src, doraise=True)
    except py_compile.PyCompileError as exc:
        raise RuntimeError(f"Failed to compile {src}: {exc.msg}") from exc


def compile_directory(src_dir: str, out_dir: str, dlls_dir: str) -> None:
    """Recursively compile a directory of Python sources.

    - .py files are compiled into .pyc and saved into out_dir.
    - Native binaries are copied into dlls_dir.
    - Other files are copied as-is into out_dir.

    Args:
        src_dir: Source directory to compile.
        out_dir: Output directory for compiled bytecode and data files.
        dlls_dir: Output directory for native binaries.

    Raises:
        FileNotFoundError: If src_dir does not exist.
        RuntimeError: If compilation fails.
    """
    if not os.path.isdir(src_dir):
        raise FileNotFoundError(f"Source directory not found: {src_dir}")

    os.makedirs(out_dir, exist_ok=True)
    os.makedirs(dlls_dir, exist_ok=True)

    for root, dirs, files in os.walk(src_dir):
        dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
        for filename in files:
            src_path = os.path.join(root, filename)
            rel_path = os.path.relpath(src_path, src_dir)
            lower_name = filename.lower()

            if lower_name.endswith(".py"):
                rel_no_ext = os.path.splitext(rel_path)[0] + ".pyc"
                dst_path = os.path.join(out_dir, rel_no_ext)
                compile_file(src_path, dst_path)
                continue

            if lower_name.endswith(NATIVE_EXTENSIONS):
                dst_path = os.path.join(dlls_dir, rel_path)
                os.makedirs(os.path.dirname(dst_path), exist_ok=True)
                shutil.copy2(src_path, dst_path)
                continue

            dst_path = os.path.join(out_dir, rel_path)
            os.makedirs(os.path.dirname(dst_path), exist_ok=True)
            shutil.copy2(src_path, dst_path)
