import re
from enum import Enum
from typing import Self

from pydantic import BaseModel, Field

from .locs import Location


# from https://github.com/imandra-ai/imandrax-api/blob/1a4abb4eb59d9545c6f0af9698956437b5b5dcb8/src/internal/error_data/imandrax_errors.ml
class ErrorKind(str, Enum):
    APPLIED_SYMBOL_TYPE_ERR = 'AppliedSymbolTypeErr'
    CONFIG_ERROR = 'ConfigError'
    CPTR_GET_OTHER_ERR = 'CptrGetOtherErr'
    CPTR_NOT_FOUND_IN_STORAGE = 'CptrNotFoundInStorage'
    GENERIC_USER_ERROR = 'GenericUserError'
    INVALID_FUN_DEFINITION = 'InvalidFunDefinition'
    INVALID_QIDENT = 'InvalidQident'
    INVALID_TYPE_ALIAS = 'InvalidTypeAlias'
    INVALID_TYPE_DEFINITION = 'InvalidTypeDefinition'
    INVALID_CNAME = 'InvalidCname'
    LEVAL_ERROR = 'LevalError'
    LOWER_CIR_ERROR = 'LowerCirError'
    LOWER_MIR_ERROR = 'LowerMirError'
    LOWER_RIR_ERROR = 'LowerRirError'
    PARSER_NOT_REGISTERED = 'ParserNotRegistered'
    PATMATCH_ERROR = 'PatmatchError'
    SYN_TERM_CREATE_ERROR = 'SynTermCreateError'
    SYNTAX_ATTRIBUTE_ERR = 'SyntaxAttributeErr'
    SYNTAX_ERR = 'SyntaxErr'
    SYNTAX_UNSUGAR_ERR = 'SyntaxUnsugarErr'
    TACTIC_EVAL_ERR = 'TacticEvalErr'
    TERM_INVALID_SHAPE = 'TermInvalidShape'
    TERM_MODEL_FIERR = 'TermModelFIErr'
    THREAD_FUT_FAILURE = 'ThreadFutFailure'
    THREAD_TIMER_FAILURE = 'ThreadTimerFailure'
    TY_INFER_INTERNAL_ERROR = 'TyInferInternalError'
    TYPE_ARITY_MISMATCH = 'TypeArityMismatch'
    TYPE_CYCLE_DETECTED = 'TypeCycleDetected'
    TYPE_ERR = 'TypeErr'
    TYPE_VAR_ALREADY_BOUND = 'TypeVarAlreadyBound'
    TYPED_SYMBOL_NON_GROUND = 'TypedSymbolNonGround'
    UID_CONTENT_ADDRESSING_NAME_MISMATCH = 'UidContentAddressingNameMismatch'
    UID_CONTENT_ADDRESSING_NOT_TEMPORARY = 'UidContentAddressingNotTemporary'
    UID_NOT_CONTENT_ADDRESSED = 'UidNotContentAddressed'
    UNKNOWN_BUILTIN_SYMBOL_FOR_UID = 'UnknownBuiltinSymbolForUid'
    UNKNOWN_TYPE_DEFINITION = 'UnknownTypeDefinition'
    INTERACTIVE_PROOF_ERR = 'InteractiveProofErr'
    PROOF_DESER_ERROR = 'ProofDeserError'
    PROOF_CHECK_ERROR = 'ProofCheckError'
    INVALID_ANCHOR = 'InvalidAnchor'
    INDUCT_SCHEMA_ERROR = 'InductSchemaError'
    UNSUPPORTED = 'Unsupported'
    LOWER_FOL_ERROR = 'LowerFolError'
    VALIDATION_ERROR = 'ValidationError'
    LSP_ERROR = 'LspError'
    INTERRUPTED = 'Interrupted'
    REDIS_ERROR = 'RedisError'
    CIRDECL_NOT_FOUND_IN_STORAGE = 'CIRDeclNotFoundInStorage'
    SERIALIZATION_ERROR = 'SerializationError'
    DESERIALIZATION_ERROR = 'DeserializationError'
    DEBUG_MODE = 'DebugMode'
    IMPORT_ERROR = 'ImportError'
    GENERIC_IOERROR = 'GenericIOError'
    DUNE_ERROR = 'DuneError'
    RPC_ERROR = 'RpcError'
    RPC_DESER_ERROR = 'RpcDeserError'
    RPC_NETWORK_ERROR = 'RpcNetworkError'
    RPC_TIMEOUT = 'RpcTimeout'
    AUTH_ERROR = 'AuthError'
    FILE_EXISTS = 'FileExists'
    DIRECTORY_CREATION_ERROR = 'DirectoryCreationError'
    DECOMP_ERROR = 'DecompError'
    VERSION_MISMATCH_ERROR = 'VersionMismatchError'
    OH_NO_ERROR = 'OhNoError'
    DEBOUNCED = 'Debounced'

    @classmethod
    def from_proto_kind(cls, proto_kind: str) -> Self:
        kinds = re.findall(r'\{ Kind.name = "(.+)" \}', proto_kind)
        if len(kinds) != 1:
            raise ValueError('Unable to parse kind')
        return cls(kinds[0])


class ErrorMessage(BaseModel):
    """An error message"""

    msg: str
    locs: list[Location] = Field(
        default_factory=list, description='Locations for this message'
    )
    backtrace: str | None = Field(default=None, description='Captured backtrace')


class Error(BaseModel):
    msg: ErrorMessage | None = Field(
        default=None, description='The toplevel error message'
    )
    kind: str = Field(description='A string description of the kind of error')
    stack: list[ErrorMessage] = Field(
        default_factory=list, description='Context for the error'
    )
    process: str | None = Field(default=None)
