class ConfigValidationError(Exception):
    """
    Class extending the Exception to define a structured custom validation error
    This class has a method to serialize the returned message into a json message.
    """
    ERROR_MESSAGES = {
        "type_mismatch": "type mismatch",
        "invalid_enum": "invalid value",
        "missing_required": "missing required configuration key",
        "unknown_keys": "unknown configuration keys",
        "validator_failed": "validation failed",
    }

    def __init__(
        self,
        *,
        code: str,
        path: list[str],
        value: any = None,
        expected: any = None,
        allowed: any = None,
        details: str | None = None,
    ):
        self.code = code
        self.path = path.copy()
        self.value = value
        self.expected = expected
        self.allowed = allowed
        self.details = details

        super().__init__(self.__str__())

    @property
    def location(self) -> str:
        result = ""
        for p in self.path:
            if p.startswith("["):
                result += p
            else:
                if result:
                    result += "."
                result += p
        return result or "root"

    def __str__(self) -> str:
        base = self.ERROR_MESSAGES.get(self.code, self.code)
        base += f" at '{self.location}'"

        if self.value is not None:
            base += f". Got: {self.value!r}"

        if self.expected is not None:
            base += f". Expected: {self.expected}"

        if self.allowed is not None:
            # sort the set of allowed values for the correct error matching
            allowed_str = "{" + ", ".join(sorted(self.allowed)) + "}"
            base += f". Allowed: {allowed_str}"
            #base += f". Allowed: {self.allowed}"

        if self.details:
            base += f". {self.details}"

        return base
    
    def to_dict(self) -> dict:
        """
        Serialize error to dict suitable for JSON, logging, or API responses.
        """
        return {
            "code": self.code,
            "message": str(self),
            "location": self.location,
            "path": self.path,
            "value": self.value,
            "expected": self.expected,
            "allowed": self.allowed,
            "details": self.details,
        }