"""Build manifest for U-Net segmenter combining REFUGE and Papila annotations."""

from __future__ import annotations

import argparse
import random
from pathlib import Path
from typing import Optional

import sys

import pandas as pd

ROOT = Path(__file__).resolve().parents[1]
sys.path.append(str(ROOT))

from classes.refuge_preprocessing import RefugePreprocessing

REFUGE_ROOT = Path("REFUGE")
PAPILA_IMAGES = Path("FundusImages")
PAPILA_CONTOURS = Path("Papila/ExpertsSegmentations/Contours")
DEFAULT_OUTPUT = Path("Papila/analysis_data/unet_manifest.csv")


def pick_contour(base: str, kind: str) -> Optional[Path]:
    """Return contour path for Papila image (disc/cup)."""
    candidates = [
        PAPILA_CONTOURS / f"{base}_{kind}_exp2.txt",
        PAPILA_CONTOURS / f"{base}_{kind}_exp1.txt",
    ]
    for path in candidates:
        if path.exists():
            return path
    return None


def collect_refuge() -> pd.DataFrame:
    pre = RefugePreprocessing(REFUGE_ROOT)
    samples = []
    for sample in pre.build_manifest(refresh=True):
        if sample.mask_path is None:
            continue
        split = sample.split
        if split == "test":
            split = "holdout"
        samples.append(
            {
                "sample_id": sample.sample_id,
                "dataset": "refuge",
                "image_path": sample.image_path.resolve(),
                "annotation_disc": sample.mask_path.resolve(),
                "annotation_cup": sample.mask_path.resolve(),
                "annotation_type_disc": "mask",
                "annotation_type_cup": "mask",
                "split": split,
            }
        )
    return pd.DataFrame(samples)


def collect_papila() -> pd.DataFrame:
    samples = []
    if not PAPILA_IMAGES.exists():
        return pd.DataFrame(samples)
    for img_path in sorted(PAPILA_IMAGES.glob("RET*")):
        base = img_path.stem
        disc = pick_contour(base, "disc")
        cup = pick_contour(base, "cup")
        if disc is None or cup is None:
            continue
        samples.append(
            {
                "sample_id": f"papila_{base}",
                "dataset": "papila",
                "image_path": img_path.resolve(),
                "annotation_disc": disc.resolve(),
                "annotation_cup": cup.resolve(),
                "annotation_type_disc": "contour",
                "annotation_type_cup": "contour",
            }
        )
    return pd.DataFrame(samples)


def assign_splits(df: pd.DataFrame, holdout_ratio: float, seed: int) -> pd.DataFrame:
    rng = random.Random(seed)
    df = df.copy()
    if "split" not in df.columns:
        df["split"] = None
    for dataset, group in df.groupby("dataset"):
        indices = list(group.index)

        # Preserve provided splits (e.g., REFUGE train/val/test); only populate
        # missing entries with "train" so downstream code has a default.
        split_series = df.loc[indices, "split"]
        missing = split_series.isna() | (split_series.astype(str).str.strip() == "")
        if missing.any():
            df.loc[missing[missing].index, "split"] = "train"
            split_series = df.loc[indices, "split"]

        if dataset != "papila":
            continue

        if holdout_ratio <= 0:
            continue

        desired_holdout = max(1, int(len(indices) * holdout_ratio))
        split_series = df.loc[indices, "split"]
        current_holdout_mask = split_series == "holdout"
        current_holdout = int(current_holdout_mask.sum())
        remaining = desired_holdout - current_holdout
        if remaining <= 0:
            continue

        candidate_indices = list(split_series[split_series == "train"].index)
        rng.shuffle(candidate_indices)
        selected = candidate_indices[:remaining]
        df.loc[selected, "split"] = "holdout"
    return df


def main() -> None:
    parser = argparse.ArgumentParser(description="Build U-Net manifest")
    parser.add_argument("--holdout", type=float, default=0.05)
    parser.add_argument("--seed", type=int, default=42)
    parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
    args = parser.parse_args()

    refuge_df = collect_refuge()
    papila_df = collect_papila()
    combined = pd.concat([refuge_df, papila_df], ignore_index=True)
    combined = assign_splits(combined, holdout_ratio=args.holdout, seed=args.seed)
    combined.to_csv(args.output, index=False)
    print(f"Manifest saved to {args.output} with {len(combined)} entries")


if __name__ == "__main__":
    main()
