"""Isolated project overlays for parallel candidate evaluation.

Creates lightweight temporary directories that mirror the
project root using symlinks.  Only the target module file is a
real file containing candidate code; everything else resolves
through symlinks to the original project.

Using the overlay as the subprocess ``cwd`` ensures the
candidate code is found on ``sys.path[0]`` while all other
imports resolve to the originals.
"""

from __future__ import annotations

import shutil
import tempfile
from pathlib import Path


def create_project_overlay(
    module_path: Path,
    project_root: Path,
    candidate_code: str,
) -> Path:
    """Create a project-root overlay with one module replaced.

    Mirrors the directory structure from *project_root* down to
    *module_path* using symlinks for siblings at each level.
    Only the target module file contains *candidate_code*;
    everything else is a symlink to the original.

    Returns the overlay root (to use as subprocess ``cwd``).
    The caller must clean up via :func:`cleanup_overlay`.
    """
    overlay = Path(
        tempfile.mkdtemp(prefix="codeflash_eval_"),
    )

    try:
        rel = module_path.relative_to(project_root)
    except ValueError:
        shutil.rmtree(overlay)
        raise

    parts = rel.parts
    current_orig = project_root
    current_over = overlay

    for i, part in enumerate(parts):
        is_last = i == len(parts) - 1

        if is_last:
            (current_over / part).write_text(
                candidate_code,
                encoding="utf-8",
            )
            for item in current_orig.iterdir():
                if item.name == part:
                    continue
                link = current_over / item.name
                if not link.exists() and not link.is_symlink():
                    link.symlink_to(item)
        else:
            next_orig = current_orig / part
            next_over = current_over / part
            next_over.mkdir()

            for item in current_orig.iterdir():
                if item.name == part:
                    continue
                link = current_over / item.name
                if not link.exists() and not link.is_symlink():
                    link.symlink_to(item)

            current_orig = next_orig
            current_over = next_over

    return overlay


def cleanup_overlay(overlay: Path) -> None:
    """Remove an overlay created by :func:`create_project_overlay`."""
    shutil.rmtree(overlay, ignore_errors=True)
