from __future__ import annotations

import os
from pathlib import Path
from typing import Callable

from rimpack.config import RimpackConfig
from rimpack.constants import (
    RIMWORLD_FOLDER_ENV_VAR,
    RIMWORLD_STEAM_APP_ID,
    STEAM_LIBRARY_PATH_PATTERN,
    STEAM_WORKSHOP_FOLDER_ENV_VAR,
)


def configured_or_discovered_rimworld_folder(config: RimpackConfig | None) -> Path | None:
    if config is not None and config.rimworld_folder is not None:
        return config.rimworld_folder
    return discover_rimworld_folder()


def configured_or_discovered_steam_workshop_folder(
    config: RimpackConfig | None,
) -> Path | None:
    return configured_or_discovered_steam_workshop_folder_with_callback(config)


def configured_or_discovered_steam_workshop_folder_with_callback(
    config: RimpackConfig | None,
    *,
    on_inaccessible_root: Callable[[Path, OSError], None] | None = None,
) -> Path | None:
    if config is not None and config.steam_workshop_folder is not None:
        return config.steam_workshop_folder
    return discover_steam_workshop_folder_with_callback(
        on_inaccessible_root=on_inaccessible_root,
    )


def discover_rimworld_folder() -> Path | None:
    env_path = _existing_env_path(RIMWORLD_FOLDER_ENV_VAR)
    if env_path is not None:
        return env_path

    for steam_root in discover_steam_roots():
        rimworld_folder = steam_root / "steamapps" / "common" / "RimWorld"
        if rimworld_folder.is_dir():
            return rimworld_folder.resolve()
    return None


def discover_steam_workshop_folder() -> Path | None:
    return discover_steam_workshop_folder_with_callback()


def discover_steam_workshop_folder_with_callback(
    on_inaccessible_root: Callable[[Path, OSError], None] | None = None,
) -> Path | None:
    env_path = _existing_env_path(STEAM_WORKSHOP_FOLDER_ENV_VAR)
    if env_path is not None:
        return env_path

    for steam_root in discover_steam_roots(on_inaccessible_root=on_inaccessible_root):
        workshop_root = steam_root / "steamapps" / "workshop" / "content" / RIMWORLD_STEAM_APP_ID
        if workshop_root.is_dir():
            return workshop_root.resolve()
    return None


def discover_steam_roots(
    *,
    on_inaccessible_root: Callable[[Path, OSError], None] | None = None,
) -> list[Path]:
    roots: list[Path] = []
    seen: set[Path] = set()
    raw_candidates = [
        os.environ.get("STEAM_PATH"),
        _join_env_path("PROGRAMFILES(X86)", "Steam"),
        _join_env_path("PROGRAMFILES", "Steam"),
        r"C:\Steam",
    ]
    for candidate in raw_candidates:
        if candidate is None:
            continue
        steam_root = Path(candidate)
        if not steam_root.is_dir():
            continue
        resolved_root = steam_root.resolve()
        if resolved_root in seen:
            continue
        seen.add(resolved_root)
        roots.append(resolved_root)
        for library_root in parse_steam_library_roots(
            resolved_root,
            on_inaccessible_root=on_inaccessible_root,
        ):
            if library_root in seen:
                continue
            seen.add(library_root)
            roots.append(library_root)
    return roots


def parse_steam_library_roots(
    steam_root: Path,
    *,
    on_inaccessible_root: Callable[[Path, OSError], None] | None = None,
) -> tuple[Path, ...]:
    library_folders_path = steam_root / "steamapps" / "libraryfolders.vdf"
    if not library_folders_path.is_file():
        return ()

    library_folders_text = library_folders_path.read_text(encoding="utf-8")
    libraries: list[Path] = []
    for match in STEAM_LIBRARY_PATH_PATTERN.finditer(library_folders_text):
        library_path_text = match.group(1)
        library_path = Path(library_path_text.replace("\\\\", "\\")).resolve()
        try:
            if library_path.is_dir():
                libraries.append(library_path)
        except OSError as exc:
            if on_inaccessible_root is not None:
                on_inaccessible_root(library_path, exc)
            continue
    return tuple(libraries)


def _existing_env_path(env_name: str) -> Path | None:
    value = os.environ.get(env_name)
    if not value:
        return None
    path = Path(value)
    if not path.is_dir():
        return None
    return path.resolve()


def _join_env_path(env_name: str, child: str) -> str | None:
    value = os.environ.get(env_name)
    if not value:
        return None
    return str(Path(value) / child)
