"""Python IR dataclasses that match the old lqp 0.2.x API.

These classes provide compatibility with the old lqp.ir module that was removed in
lqp 0.4.0. They are pure Python dataclasses that can be converted to lqp 0.4.0 protobuf
messages via the ir_to_proto function in proto_conversion.py.
"""
from __future__ import annotations

from abc import ABC
from dataclasses import dataclass, field
from enum import Enum
from typing import Any


# --- Abstract base classes for IR type hierarchies ---

class RelTerm(ABC):
    """Base class for relation terms (Var, Value, SpecializedValue)."""

class Term(RelTerm, ABC):
    """Base class for terms (Var, Value). All Terms are also RelTerms."""

class Formula(ABC):
    """Base class for formulas (Atom, Conjunction, Disjunction, etc.)."""

class Declaration(ABC):
    """Base class for declarations (Def, FunctionalDependency, Algorithm)."""

class Construct(ABC):
    """Base class for constructs in scripts (Loop, Assign, Upsert, etc.)."""

class Instruction(Construct, ABC):
    """Base class for instructions (Assign, Upsert, MonoidDef, MonusDef, Break). All Instructions are Constructs."""


# --- Source info (used as type hints only) ---

@dataclass(frozen=True)
class SourceInfo:
    meta: Any = None


@dataclass(frozen=True)
class LqpNode:
    meta: Any = None


# --- Type system ---

class TypeName(Enum):
    INT = "INT"
    INT128 = "INT128"
    UINT128 = "UINT128"
    FLOAT = "FLOAT"
    STRING = "STRING"
    DATE = "DATE"
    DATETIME = "DATETIME"
    DECIMAL = "DECIMAL"
    BOOLEAN = "BOOLEAN"
    MISSING = "MISSING"


@dataclass(frozen=True)
class Type:
    type_name: TypeName
    parameters: list = field(default_factory=list)
    meta: Any = None


# --- Relation ID ---

@dataclass(frozen=True, eq=False)
class RelationId:
    """128-bit relation identifier."""

    id: int  # noqa: A003
    meta: Any = field(default=None, compare=False, hash=False)

    def __hash__(self) -> int:
        return hash(self.id)

    def __eq__(self, other: object) -> bool:
        if isinstance(other, RelationId):
            return self.id == other.id
        return NotImplemented

    def __repr__(self) -> str:
        return f"RelationId(id={self.id!r})"


# --- Variable ---

@dataclass(frozen=True)
class Var(Term):
    name: str
    meta: Any = None


# --- Value types ---

@dataclass(frozen=True)
class Value(Term):
    """Wraps a raw Python value or a typed value wrapper (Int128Value, DateValue, etc.)."""
    value: Any
    meta: Any = None


@dataclass(frozen=True)
class Int128Value:
    value: int
    meta: Any = None


@dataclass(frozen=True)
class UInt128Value:
    value: int
    meta: Any = None


@dataclass(frozen=True)
class DecimalValue:
    precision: int
    scale: int
    value: Any  # decimal.Decimal
    meta: Any = None


@dataclass(frozen=True)
class DateValue:
    value: Any  # datetime.date
    meta: Any = None


@dataclass(frozen=True)
class DateTimeValue:
    value: Any  # datetime.datetime
    meta: Any = None


@dataclass(frozen=True)
class BooleanValue:
    value: bool
    meta: Any = None


@dataclass(frozen=True)
class SpecializedValue(RelTerm):
    """A specialized (typed) value used as a RelTerm in RelAtom/Primitive terms."""
    value: Value
    meta: Any = None


# --- Attributes ---

@dataclass(frozen=True)
class Attribute:
    name: str
    args: list = field(default_factory=list)  # list[Value]
    meta: Any = None


# --- Abstraction ---

@dataclass(frozen=True)
class Abstraction:
    """Lambda abstraction: vars is a list of (Var, Type) tuples, value is a Formula."""
    vars: list  # list[(Var, Type)]
    value: Any  # Formula
    meta: Any = None


# --- Formula types ---

@dataclass(frozen=True)
class Conjunction(Formula):
    args: list  # list[Formula]
    meta: Any = None


@dataclass(frozen=True)
class Disjunction(Formula):
    args: list  # list[Formula]
    meta: Any = None


@dataclass(frozen=True)
class Not(Formula):
    arg: Any  # Formula
    meta: Any = None


@dataclass(frozen=True)
class Exists(Formula):
    body: Abstraction
    meta: Any = None


@dataclass(frozen=True)
class Reduce(Formula):
    op: Abstraction
    body: Abstraction
    terms: list  # list[Term]
    meta: Any = None


@dataclass(frozen=True)
class FFI(Formula):
    name: str
    args: list  # list[Abstraction]
    terms: list  # list[Term]
    meta: Any = None


@dataclass(frozen=True)
class Atom(Formula):
    name: RelationId
    terms: list  # list[Term]
    meta: Any = None


@dataclass(frozen=True)
class Pragma(Formula):
    name: str
    terms: list  # list[Var]
    meta: Any = None


@dataclass(frozen=True)
class Primitive(Formula):
    name: str
    terms: list  # list[RelTerm]
    meta: Any = None


@dataclass(frozen=True)
class RelAtom(Formula):
    name: str
    terms: list  # list[RelTerm]
    meta: Any = None


@dataclass(frozen=True)
class Cast(Formula):
    input: Any   # Term
    result: Any  # Term
    meta: Any = None


# --- Declaration types ---

@dataclass(frozen=True)
class Def(Declaration):
    name: RelationId
    body: Abstraction
    attrs: list = field(default_factory=list)  # list[Attribute]
    meta: Any = None


class Constraint:
    """Base class for constraint-type declarations."""
    pass


@dataclass(frozen=True)
class FunctionalDependency(Declaration, Constraint):
    name: RelationId
    guard: Abstraction
    keys: list  # list[Var]
    values: list  # list[Var]
    meta: Any = None


# --- Algorithm / Script / Loopy constructs ---

@dataclass(frozen=True)
class OrMonoid:
    meta: Any = None


@dataclass(frozen=True)
class SumMonoid:
    type: Any = None  # Type
    meta: Any = None


@dataclass(frozen=True)
class MinMonoid:
    type: Any = None  # Type
    meta: Any = None


@dataclass(frozen=True)
class MaxMonoid:
    type: Any = None  # Type
    meta: Any = None


@dataclass(frozen=True)
class Assign(Instruction):
    name: RelationId
    body: Abstraction
    attrs: list = field(default_factory=list)  # list[Attribute]
    meta: Any = None


@dataclass(frozen=True)
class Upsert(Instruction):
    name: RelationId
    body: Abstraction
    attrs: list = field(default_factory=list)  # list[Attribute]
    value_arity: int = 0
    meta: Any = None


@dataclass(frozen=True)
class MonoidDef(Instruction):
    monoid: Any  # OrMonoid | SumMonoid | MinMonoid | MaxMonoid
    name: Any = None  # RelationId
    body: Any = None  # Abstraction
    attrs: list = field(default_factory=list)  # list[Attribute]
    value_arity: int = 0
    meta: Any = None


@dataclass(frozen=True)
class MonusDef(Instruction):
    monoid: Any  # OrMonoid | SumMonoid | MinMonoid | MaxMonoid
    name: Any = None  # RelationId
    body: Any = None  # Abstraction
    attrs: list = field(default_factory=list)  # list[Attribute]
    value_arity: int = 0
    meta: Any = None


@dataclass(frozen=True)
class Break(Instruction):
    """Break condition in a loopy script."""
    name: RelationId
    body: Abstraction
    attrs: list = field(default_factory=list)  # list[Attribute]
    meta: Any = None


@dataclass(frozen=True)
class Loop(Construct):
    init: list = field(default_factory=list)  # list[Instruction]
    body: Any = None  # Script
    meta: Any = None


@dataclass(frozen=True)
class Script:
    constructs: list = field(default_factory=list)  # list[Construct]
    meta: Any = None


@dataclass(frozen=True)
class Algorithm(Declaration):
    global_: list = field(default_factory=list)  # list[RelationId]
    body: Any = None  # Script
    meta: Any = None


# --- Transaction types ---

@dataclass(frozen=True, eq=False)
class FragmentId:
    """Fragment identifier (bytes)."""

    id: bytes  # noqa: A003
    meta: Any = field(default=None, compare=False, hash=False)

    def __hash__(self) -> int:
        return hash(self.id)

    def __eq__(self, other: object) -> bool:
        if isinstance(other, FragmentId):
            return self.id == other.id
        return NotImplemented

    def __repr__(self) -> str:
        return f"FragmentId(id={self.id!r})"


@dataclass(frozen=True)
class DebugInfo:
    """Maps RelationId → original name for debugging."""
    id_to_orig_name: dict = field(default_factory=dict)  # RelationId -> str
    meta: Any = None


@dataclass(frozen=True)
class Fragment:
    """A compilation fragment containing declarations."""
    id: FragmentId
    declarations: list  # list[Declaration]
    debug_info: DebugInfo
    meta: Any = None


@dataclass(frozen=True)
class Define:
    fragment: Fragment
    meta: Any = None


@dataclass(frozen=True)
class Undefine:
    fragment_id: FragmentId
    meta: Any = None


@dataclass(frozen=True)
class Write:
    """A write operation. write_type is either Define or Undefine."""
    write_type: Any  # Define | Undefine
    meta: Any = None


@dataclass(frozen=True)
class Output:
    name: str
    relation_id: RelationId
    meta: Any = None


@dataclass(frozen=True)
class ExportCSVColumn:
    column_name: str
    column_data: RelationId
    meta: Any = None


@dataclass(frozen=True)
class ExportCSVConfig:
    path: str
    data_columns: list = field(default_factory=list)  # list[ExportCSVColumn]
    compression: str = ""
    partition_size: int = 0
    syntax_header_row: bool = True
    syntax_missing_string: str = ""
    syntax_delim: str = ","
    syntax_quotechar: str = '"'
    syntax_escapechar: str = ""
    meta: Any = None


@dataclass(frozen=True)
class Export:
    """CSV export read operation. config is an ExportCSVConfig."""
    config: ExportCSVConfig
    meta: Any = None




# --- Inline CSV data (data-in-LQP) ---

@dataclass(frozen=True)
class CSVLocator:
    paths: list = field(default_factory=list)
    inline_data: bytes = b""
    meta: Any = None


@dataclass(frozen=True)
class CSVConfig:
    header_row: int = 0
    skip: int = 0
    new_line: str = "\n"
    delimiter: str = ","
    quotechar: str = '"'
    escapechar: str = '"'
    comment: str = ""
    missing_strings: list = field(default_factory=list)
    decimal_separator: str = "."
    encoding: str = "utf-8"
    compression: str = "none"
    meta: Any = None


@dataclass(frozen=True)
class GNFColumn:
    column_name: str = ""
    target_id: RelationId = None  # type: ignore[assignment]
    types: list = field(default_factory=list)  # list[Type]
    meta: Any = None


@dataclass(frozen=True)
class CSVData(Declaration):
    """Inline CSV data declaration."""
    locator: CSVLocator = None  # type: ignore[assignment]
    config: CSVConfig = None  # type: ignore[assignment]
    columns: list = field(default_factory=list)  # list[GNFColumn]
    asof: str = ""
    meta: Any = None

@dataclass(frozen=True)
class Read:
    """A read operation. read_type is either Output or Export."""
    read_type: Any  # Output | Export
    meta: Any = None


@dataclass(frozen=True)
class Sync:
    fragments: list = field(default_factory=list)  # list[FragmentId]
    meta: Any = None


@dataclass(frozen=True)
class Epoch:
    writes: list = field(default_factory=list)   # list[Write]
    reads: list = field(default_factory=list)    # list[Read]
    meta: Any = None


@dataclass(frozen=True)
class IVMConfig:
    level: Any = None  # MaintenanceLevel value
    meta: Any = None


@dataclass(frozen=True)
class Configure:
    semantics_version: int = 0
    ivm_config: Any = None  # IVMConfig | None
    meta: Any = None


@dataclass(frozen=True)
class Transaction:
    epochs: list = field(default_factory=list)  # list[Epoch]
    configure: Any = None  # Configure
    sync: Any = None       # Sync | None
    meta: Any = None


class MaintenanceLevel(Enum):
    UNSPECIFIED = 0
    OFF = 1
    AUTO = 2
    ALL = 3
