from __future__ import annotations

import sys
from pathlib import Path
from typing import Dict, Iterable, List, Optional, Tuple


def resolve_repo_root(
    start: Optional[Path] = None,
    required_subdirs: Tuple[str, ...] = ("functions", "data_helpers"),
) -> Path:
    """Resolve repository root by searching upward from `start` or current cwd."""

    cwd = Path.cwd().resolve() if start is None else Path(start).resolve()
    for candidate in [cwd, *cwd.parents]:
        if all((candidate / subdir).exists() for subdir in required_subdirs):
            return candidate
    raise FileNotFoundError(
        f"Could not resolve repository root from {cwd}. "
        f"Missing required subdirs: {required_subdirs}"
    )


def ensure_repo_on_syspath(repo_root: Path) -> None:
    """Insert repository root into `sys.path` if needed."""

    repo_str = str(Path(repo_root).resolve())
    if repo_str not in sys.path:
        sys.path.insert(0, repo_str)


def bootstrap_notebook_env(
    start: Optional[Path] = None,
    required_subdirs: Tuple[str, ...] = ("functions", "data_helpers"),
) -> Path:
    """Resolve repo root and ensure it is importable from notebook environments."""

    repo_root = resolve_repo_root(start=start, required_subdirs=required_subdirs)
    ensure_repo_on_syspath(repo_root)
    return repo_root


def first_existing_path(
    candidates: Iterable[Path],
    missing_message: Optional[str] = None,
) -> Path:
    """Return first existing path from candidates, or raise FileNotFoundError."""

    tried: List[Path] = []
    for candidate in candidates:
        p = Path(candidate)
        tried.append(p)
        if p.exists():
            return p

    if missing_message is None:
        missing_message = "Could not find any candidate path:\n" + "\n".join(str(p) for p in tried)
    raise FileNotFoundError(missing_message)


def _processed_data_dir(repo_root: Path) -> Path:
    """Return `<repo>/processed_data`, creating it if needed."""

    out_dir = Path(repo_root).resolve() / "processed_data"
    out_dir.mkdir(parents=True, exist_ok=True)
    return out_dir


def build_default_decoding_paths(
    repo_root: Path,
    psth_filename: str = "PSTH_10ms.npz",
    area_list_filename: str = "Area_list.npz",
    cluster_candidates: Tuple[str, ...] = ("Data_Clustering.npz", "Data_Clustering.pkl"),
) -> Dict[str, Path]:
    """Build default input/output paths for decoding notebooks."""

    repo_root = Path(repo_root).resolve()
    out_dir = _processed_data_dir(repo_root)

    psth_path = out_dir / psth_filename
    area_list_path = repo_root / "data_helpers" / area_list_filename
    cluster_path = first_existing_path(
        [out_dir / name for name in cluster_candidates],
        missing_message=(
            "Could not find Data_Clustering source. Expected one of:\n"
            + "\n".join(str(out_dir / name) for name in cluster_candidates)
        ),
    )

    return {
        "psth_path": psth_path,
        "area_list_path": area_list_path,
        "cluster_path": cluster_path,
        "out_dir": out_dir,
        "out_dropout": out_dir / "Decoding_clusters_dropout_mndropout.npz",
        "out_random": out_dir / "Decoding_clusters_random_dropout.npz",
    }


def build_default_psth_area_output_paths(
    repo_root: Path,
    out_filename: str,
    psth_filename: str = "PSTH_10ms.npz",
    area_list_filename: str = "Area_list.npz",
) -> Dict[str, Path]:
    """Build default input/output paths for PSTH + Area_list based notebooks."""

    repo_root = Path(repo_root).resolve()
    out_dir = _processed_data_dir(repo_root)

    return {
        "psth_path": out_dir / psth_filename,
        "area_list_path": repo_root / "data_helpers" / area_list_filename,
        "out_dir": out_dir,
        "out_path": out_dir / out_filename,
    }


def build_default_neuronal_data_paths(
    repo_root: Path,
    out_filename: str,
    psth_filename: str = "PSTH_10ms.npz",
    area_list_filename: str = "Area_list.npz",
) -> Dict[str, Path]:
    """Backward-compatible alias for neuronal-data notebooks."""

    return build_default_psth_area_output_paths(
        repo_root=repo_root,
        out_filename=out_filename,
        psth_filename=psth_filename,
        area_list_filename=area_list_filename,
    )


def build_default_optoinhibition_paths(
    repo_root: Path,
    data_dir_name: str = "data_optogenetics",
    out_filename: str = "Optoinhibition.npz",
) -> Dict[str, Path]:
    """Build default input/output paths for optoinhibition processing."""

    repo_root = Path(repo_root).resolve()
    out_dir = _processed_data_dir(repo_root)

    return {
        "data_dir": repo_root / data_dir_name,
        "out_dir": out_dir,
        "out_path": out_dir / out_filename,
    }


def build_default_ephys_psth_paths(
    repo_root: Path,
    out_filename: str,
    data_dir_name: str = "data_electrophysiology",
) -> Dict[str, Path]:
    """Build default input/output paths for ephys PSTH notebooks."""

    repo_root = Path(repo_root).resolve()
    out_dir = _processed_data_dir(repo_root)

    return {
        "data_dir": repo_root / data_dir_name,
        "out_dir": out_dir,
        "out_path": out_dir / out_filename,
    }
