"""
Parser error types and codes for highflame-policy.

This module provides standardized error codes that are consistent
across all language implementations (Rust, Go, TypeScript, Python).
"""

from dataclasses import dataclass, field
from typing import Optional


class ErrorCodes:
    """
    Error codes for parser errors.

    These codes are stable and consistent across all language implementations.
    Format: HFP-<CATEGORY>-<NUMBER>
    - HFP = HighFlame Policy
    - CATEGORY = SCOPE | ACTION | COND | PARSE
    - NUMBER = 3-digit incremental
    """

    # Scope constraint errors (HFP-SCOPE-xxx)
    SCOPE_MISSING_ENTITY = "HFP-SCOPE-001"
    """Scope constraint is missing an entity (for == operator)"""

    SCOPE_MISSING_ENTITY_TYPE = "HFP-SCOPE-002"
    """Scope constraint is missing an entity type (for is operator)"""

    SCOPE_MISSING_ENTITY_LIST = "HFP-SCOPE-003"
    """Scope constraint is missing entity list (for in operator)"""

    SCOPE_SLOT_NOT_SUPPORTED = "HFP-SCOPE-004"
    """Slot constraints are not supported in PolicyRule"""

    SCOPE_UNSUPPORTED_OP = "HFP-SCOPE-005"
    """Unsupported scope operator"""

    # Action constraint errors (HFP-ACTION-xxx)
    ACTION_MISSING_ENTITY = "HFP-ACTION-001"
    """Action constraint is missing an entity (for == operator)"""

    ACTION_MISSING_ENTITIES = "HFP-ACTION-002"
    """Action constraint is missing entities (for in operator)"""

    ACTION_UNSUPPORTED_OP = "HFP-ACTION-003"
    """Unsupported action operator"""

    ACTION_SCOPE_NIL = "HFP-ACTION-004"
    """Action scope is null/nil"""

    # Condition errors (HFP-COND-xxx)
    COND_UNLESS_NOT_SUPPORTED = "HFP-COND-001"
    """Unless clauses are not supported in PolicyRule"""

    COND_COMPLEX_EXPRESSION = "HFP-COND-002"
    """Complex condition cannot be parsed"""

    # Parse errors (HFP-PARSE-xxx)
    PARSE_INVALID_SYNTAX = "HFP-PARSE-001"
    """Invalid Cedar syntax"""

    PARSE_JSON_CONVERSION = "HFP-PARSE-002"
    """Failed to convert policy to JSON"""

    PARSE_JSON_STRUCTURE = "HFP-PARSE-003"
    """Failed to parse Cedar JSON structure"""

    PARSE_UNKNOWN_EFFECT = "HFP-PARSE-004"
    """Unknown policy effect (not permit or forbid)"""

    PARSE_DUPLICATE_ID = "HFP-PARSE-005"
    """Duplicate policy ID found"""


@dataclass
class ErrorContext:
    """Context information for parser errors."""

    operator: Optional[str] = None
    """The operator that caused the error (e.g., "==", "in", "is")"""

    field: Optional[str] = None
    """The field that caused the error (e.g., "principal", "action", "resource")"""

    policy_id: Optional[str] = None
    """The policy ID if available"""

    def to_dict(self) -> dict:
        """Convert to dictionary, excluding None values."""
        result = {}
        if self.operator is not None:
            result["operator"] = self.operator
        if self.field is not None:
            result["field"] = self.field
        if self.policy_id is not None:
            result["policyId"] = self.policy_id
        return result


class ParserError(ValueError):
    """
    A structured parser error with code, message, and optional context.

    This error type is consistent across all language implementations
    (Rust, Go, TypeScript, Python) and provides machine-readable error codes.
    """

    def __init__(
        self,
        code: str,
        message: str,
        context: Optional[ErrorContext] = None,
    ):
        """
        Create a new ParserError.

        Args:
            code: Machine-readable error code (e.g., "HFP-SCOPE-001")
            message: Human-readable error message
            context: Optional context for debugging
        """
        super().__init__(message)
        self.code = code
        self.message = message
        self.context = context

    def __str__(self) -> str:
        """Returns a string representation including the error code."""
        return f"[{self.code}] {self.message}"

    def __repr__(self) -> str:
        return f"ParserError(code={self.code!r}, message={self.message!r}, context={self.context!r})"

    def to_dict(self) -> dict:
        """Serialize the error to a dictionary for JSON serialization."""
        result = {
            "code": self.code,
            "message": self.message,
        }
        if self.context is not None:
            result["context"] = self.context.to_dict()
        return result

    # Convenience static factory methods for common errors

    @staticmethod
    def scope_missing_entity(operator: str, field: str) -> "ParserError":
        """Scope constraint is missing an entity."""
        return ParserError(
            ErrorCodes.SCOPE_MISSING_ENTITY,
            f"'{operator}' constraint is missing an entity",
            ErrorContext(operator=operator, field=field),
        )

    @staticmethod
    def scope_missing_entity_type(field: str) -> "ParserError":
        """Scope constraint is missing an entity type."""
        return ParserError(
            ErrorCodes.SCOPE_MISSING_ENTITY_TYPE,
            "'is' constraint is missing an entity_type",
            ErrorContext(operator="is", field=field),
        )

    @staticmethod
    def scope_missing_entity_list(field: str) -> "ParserError":
        """Scope constraint is missing entity list."""
        return ParserError(
            ErrorCodes.SCOPE_MISSING_ENTITY_LIST,
            "'in' constraint is missing a single entity or entities list",
            ErrorContext(operator="in", field=field),
        )

    @staticmethod
    def scope_slot_not_supported(operator: str, field: str) -> "ParserError":
        """Slot constraints are not supported."""
        return ParserError(
            ErrorCodes.SCOPE_SLOT_NOT_SUPPORTED,
            f"'{operator}' constraint with slot cannot be represented",
            ErrorContext(operator=operator, field=field),
        )

    @staticmethod
    def scope_unsupported_op(operator: str, field: str) -> "ParserError":
        """Unsupported scope operator."""
        return ParserError(
            ErrorCodes.SCOPE_UNSUPPORTED_OP,
            f"Unsupported scope operator: {operator}",
            ErrorContext(operator=operator, field=field),
        )

    @staticmethod
    def action_missing_entity(operator: str) -> "ParserError":
        """Action constraint is missing an entity."""
        return ParserError(
            ErrorCodes.ACTION_MISSING_ENTITY,
            f"Action '{operator}' constraint is missing an entity",
            ErrorContext(operator=operator, field="action"),
        )

    @staticmethod
    def action_missing_entities() -> "ParserError":
        """Action constraint is missing entities."""
        return ParserError(
            ErrorCodes.ACTION_MISSING_ENTITIES,
            "Action 'in' constraint is missing entities",
            ErrorContext(operator="in", field="action"),
        )

    @staticmethod
    def action_unsupported_op(operator: str) -> "ParserError":
        """Unsupported action operator."""
        return ParserError(
            ErrorCodes.ACTION_UNSUPPORTED_OP,
            f"Unsupported action operator: {operator}",
            ErrorContext(operator=operator, field="action"),
        )

    @staticmethod
    def action_scope_nil() -> "ParserError":
        """Action scope is nil."""
        return ParserError(
            ErrorCodes.ACTION_SCOPE_NIL,
            "Action scope is nil",
            ErrorContext(field="action"),
        )
