from __future__ import annotations

import re
from collections.abc import Callable
from collections.abc import Sized
from pathlib import Path
from typing import cast

import yaml
from loguru import logger

here = Path(__file__).resolve().parent


def _update_dict(
    target_dict: dict[str, object],
    new_dict: dict[str, object],
    validate_item: Callable[[str, object], None] | None = None,
) -> None:
    for key, value in new_dict.items():
        if validate_item:
            validate_item(key, value)
        if key not in target_dict:
            raise ValueError(f"Unexpected key in config: {key}")
        if isinstance(target_dict[key], dict) and isinstance(value, dict):
            _update_dict(
                cast(dict[str, object], target_dict[key]),
                cast(dict[str, object], value),
                validate_item=validate_item,
            )
        else:
            target_dict[key] = value


def _validate_config_item(key: str, value: object) -> None:
    if key == "validate_label" and value not in [None, "exact"]:
        raise ValueError(f"Unexpected value for config key 'validate_label': {value}")
    if key == "shape_color" and value not in [None, "auto", "manual"]:
        raise ValueError(f"Unexpected value for config key 'shape_color': {value}")
    if (
        key == "labels"
        and isinstance(value, Sized)
        and len(value) != len(set(cast(list[object], value)))
    ):
        raise ValueError(f"Duplicates are detected for config key 'labels': {value}")


def _migrate_config_from_file(config_from_yaml: dict) -> None:
    keep_prev_brightness: bool = config_from_yaml.pop("keep_prev_brightness", False)
    keep_prev_contrast: bool = config_from_yaml.pop("keep_prev_contrast", False)
    if keep_prev_brightness or keep_prev_contrast:
        logger.info(
            "Migrating old config: keep_prev_brightness={} or keep_prev_contrast={} "
            "-> keep_prev_brightness_contrast=True",
            keep_prev_brightness,
            keep_prev_contrast,
        )
        config_from_yaml["keep_prev_brightness_contrast"] = True

    if "store_data" in config_from_yaml:
        logger.info("Migrating old config: store_data -> with_image_data")
        config_from_yaml["with_image_data"] = config_from_yaml.pop("store_data")

    if config_from_yaml.get("shortcuts", {}).pop("add_point_to_edge", None):
        logger.info("Migrating old config: removing shortcuts.add_point_to_edge")

    if (model_name := config_from_yaml.get("ai", {}).get("default")) and (
        m := re.match(r"^SegmentAnything \((.*)\)$", model_name)
    ):
        model_name_new: str = f"Sam ({m.group(1)})"
        logger.info(
            "Migrating old config: ai.default={!r} -> ai.default={!r}",
            model_name,
            model_name_new,
        )
        config_from_yaml["ai"]["default"] = model_name_new

    # Migrate polygon shortcut keys to shape
    _POLYGON_TO_SHAPE_RENAMES = {
        "edit_polygon": "edit_shape",
        "delete_polygon": "delete_shape",
        "duplicate_polygon": "duplicate_shape",
        "copy_polygon": "copy_shape",
        "paste_polygon": "paste_shape",
        "show_all_polygons": "show_all_shapes",
        "hide_all_polygons": "hide_all_shapes",
        "toggle_all_polygons": "toggle_all_shapes",
    }
    shortcuts = config_from_yaml.get("shortcuts", {})
    for old_key, new_key in _POLYGON_TO_SHAPE_RENAMES.items():
        if old_key in shortcuts and new_key not in shortcuts:
            logger.info(
                "Migrating old config: shortcuts.{} -> shortcuts.{}",
                old_key,
                new_key,
            )
            shortcuts[new_key] = shortcuts.pop(old_key)


def get_user_config_file(create_if_missing: bool = True) -> str:
    user_config_path = Path("~/.labelmerc").expanduser()
    if not user_config_path.exists() and create_if_missing:
        try:
            with open(user_config_path, "w") as f:
                f.write(
                    "# Labelme config file.\n"
                    "# Only add settings you want to override.\n"
                    "# For all available options and defaults, see:\n"
                    "#   https://github.com/wkentaro/labelme/blob/main/labelme/config/default_config.yaml\n"
                    "#\n"
                    "# Example:\n"
                    "# with_image_data: true\n"
                    "# auto_save: false\n"
                    "# labels: [cat, dog]\n"
                )
        except Exception:
            logger.warning("Failed to save config: {!r}", str(user_config_path))
    return str(user_config_path)


def load_config(config_file: Path | None, config_overrides: dict) -> dict:
    config: dict
    with open(here / "default_config.yaml") as f:
        config = yaml.safe_load(f)

    if config_file is not None:
        with open(config_file) as f:
            config_from_yaml = yaml.safe_load(f)
        if isinstance(config_from_yaml, dict):
            _migrate_config_from_file(config_from_yaml=config_from_yaml)
            _update_dict(config, config_from_yaml, validate_item=_validate_config_item)

    _update_dict(config, config_overrides, validate_item=_validate_config_item)

    if not config["labels"] and config["validate_label"]:
        raise ValueError("labels must be specified when validate_label is enabled")

    return config
