from typing import Any

from clan_lib.errors import ClanError
from clan_lib.persist.path_utils import (
    PathTuple,
    find_duplicates,
    path_starts_with,
    path_to_string,
)
from clan_lib.persist.write_rules import WriteMap, is_writeable_path


def validate_no_static_deletion(
    path: PathTuple, new_value: Any, static_data: dict[PathTuple, Any]
) -> None:
    """Validate that we're not trying to delete static data."""
    # Check if we're trying to delete a path that exists in static data
    if path in static_data and new_value is None:
        msg = f"Path '{path_to_string(path)}' is readonly - since its defined via a .nix file"
        raise ClanError(msg)

    # For lists, check if we're trying to remove static items
    if isinstance(new_value, list) and path in static_data:
        static_items = static_data[path]
        if isinstance(static_items, list):
            missing_static = [item for item in static_items if item not in new_value]
            if missing_static:
                msg = f"Path '{path_to_string(path)}' doesn't contain static items {missing_static} - They are readonly - since they are defined via a .nix file"
                raise ClanError(msg)


def validate_writeability(path: PathTuple, writeables: WriteMap) -> None:
    """Validate that a path is writeable."""
    if not is_writeable_path(path, writeables):
        msg = f"Path '{path_to_string(path)}' is readonly. - It seems its value is statically defined in nix."
        raise ClanError(msg)


def validate_type_compatibility(path: tuple, old_value: Any, new_value: Any) -> None:
    """Validate that type changes are allowed."""
    if old_value is not None and type(old_value) is not type(new_value):
        if new_value is None:
            return  # Deletion is handled separately

        path_str = path_to_string(path)
        msg = f"Type mismatch for path '{path_str}'. Cannot update {type(old_value)} with {type(new_value)}"
        description = f"""
Previous value is of type '{type(old_value)}' this operation would change it to '{type(new_value)}'.
Prev: {old_value}
->
After: {new_value}
        """
        raise ClanError(msg, description=description)


def validate_list_uniqueness(path: tuple, value: Any) -> None:
    """Validate that lists don't contain duplicates."""
    if isinstance(value, list):
        duplicates = find_duplicates(value)
        if duplicates:
            msg = f"Path '{path_to_string(path)}' contains list duplicates: {duplicates} - List values must be unique."
            raise ClanError(msg)


def validate_patch_conflicts(
    patches: set[PathTuple], delete_paths: set[PathTuple]
) -> None:
    """Ensure patches don't conflict with deletions."""
    conflicts = {
        path
        for delete_path in delete_paths
        for path in patches
        if path_starts_with(path, delete_path)
    }

    if conflicts:
        conflict_list = ", ".join(path_to_string(path) for path in sorted(conflicts))
        msg = f"The following paths are marked for deletion but also have update values: {conflict_list}. - You cannot delete and patch the same path and its subpaths."
        raise ClanError(msg)
