#
#   Imandra Inc.
#
#   speclogician/modeling/component.py
#

import uuid
import re
from datetime import datetime, timezone
from pydantic import BaseModel, Field, field_validator, ConfigDict
from ..utils import IMLValidity

_NAME_RE = re.compile(r"^[a-z][a-z0-9_]*$")

# Conservative reserved set (OCaml-ish + common IML surface syntax).
# Add more as you run into them.
_IML_RESERVED: set[str] = {
    # core keywords / syntax
    "let", "in", "and", "rec",
    "type", "match", "with", "function",
    "if", "then", "else",
    "try", "raise",
    "module", "open", "include",
    "begin", "end",
    "as", "of", "when",

    # booleans / common literals
    "true", "false",

    # common types / constructors that are often special-cased
    "some", "none",
}

def validate_iml_identifier(name: str) -> str:
    if not _NAME_RE.fullmatch(name):
        raise ValueError(
            f"Invalid name '{name}'. Must start with a lowercase letter and contain only "
            "lowercase letters, digits, and underscores."
        )
    if name in _IML_RESERVED:
        raise ValueError(f"Invalid name '{name}': reserved keyword in IML/OCaml surface syntax.")
    return name

class ModelComponent(BaseModel):
    """
    Base class for all model components (Predicates, transitions and scenarios)
    """
    model_config = ConfigDict(validate_assignment=True)  # optional but recommended

    comp_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    name: str

    # Track last update time (UTC)
    last_updated: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))

    @field_validator("name")
    @classmethod
    def validate_name(cls, v: str) -> str:
        return validate_iml_identifier(v)

    def touch(self) -> None:
        self.last_updated = datetime.now(timezone.utc)
    
class SrcComponent(ModelComponent):
    src_code: str = ""
    is_iml_valid : IMLValidity = IMLValidity.UNKNOWN
    iml_error: str | None = None

    def set_src_code(self, src_code: str) -> None:
        self.src_code = src_code
        self.touch()