"""Solver interface for optimization and constraint programming.

This module provides a declarative Python API for building mathematical optimization
problems that can be solved using external solvers (MiniZinc, HiGHS, Ipopt, etc.).

## Quick Start

    from relationalai.semantics import Model, Float, Integer
    from relationalai.semantics.reasoners.solvers import SolverModel

    model = Model("my_problem")
    s = SolverModel(model, Float)

    # Define variables
    x = model.Relationship(f"{Float:x}")
    y = model.Relationship(f"{Float:y}")
    s.solve_for(x, name="x", lower=0, upper=10)
    s.solve_for(y, name="y", lower=0, upper=10)

    # Add objective
    s.minimize(x**2 + y**2)

    # Add constraints
    s.satisfy(model.require(x + y >= 5))

    # Display and solve
    s.display()

## Supported Features

**Variables:**
- Continuous (Float): Real-valued decision variables
- Integer: Discrete integer-valued variables
- Binary: Boolean (0/1) variables

**Objectives:**
- minimize(): Single minimization objective
- maximize(): Single maximization objective
- Note: Only one objective supported (use weighted sum for multi-objective)

**Constraints:**
- Linear and nonlinear expressions
- Aggregates: sum, min, max, count (over collections)
- Global constraints: all_different, implies, SOS2
- Note: Constraints must be grounded (no free variables)

**Operators:**
- Arithmetic: +, -, *, /, ^ (power), % (modulo), // (floor division)
- Comparison: =, !=, <, <=, >, >=
- Math functions: abs, exp, log (natural_log), sqrt, square, cbrt, power, floor, ceil

## Limitations

**Not Supported:**
- Multiple objectives (workaround: weighted sum)
- Trigonometric functions (sin, cos, tan)
- Truncation (trunc) - use floor or ceil instead
- Round to nearest integer (round) - use floor + 0.5 or appropriate solver features
- Distinct aggregates
- Symbolic rank aggregates
- avg aggregate (workaround: sum/count)

## Examples

See `example/solvers/` directory for complete examples:
- diet: Linear programming with bounds
- n_queens: Constraint satisfaction with global constraints
- sudoku: Discrete optimization with all_different
- portfolio: Quadratic programming with risk/return tradeoff
- traveling_salesman: Combinatorial optimization

## Exports

    SolverModel: Build optimization models with variables, objectives, constraints.
    make_name: Construct variable/constraint names.
    all_different: Global constraint for pairwise distinct values.
    implies: Logical implication constraint (left => right).
    special_ordered_set_type_2: SOS2 constraint for piecewise-linear modeling.
"""

from __future__ import annotations

from typing import Any

from relationalai.semantics import Integer, Any as CoreAny
from relationalai.semantics.frontend import base as b
from relationalai.semantics.frontend.core import TypeVar
from v0.relationalai.semantics.reasoners.optimization import Solver

from .solver_model import SolverModel, make_name

# Library for solver-specific aggregate relations.
library = b.Library("aggregates")

# Package version.
__version__ = "0.0.0"

__all__ = [
    "Solver",
    "SolverModel",
    "make_name",
    "all_different",
    "implies",
    "special_ordered_set_type_2",
]


# =============================================================================
# Constraint Constructors
# =============================================================================


_implies = library.Relation(
    "implies", fields=[b.Field.input("left", CoreAny), b.Field.input("right", CoreAny)]
)


def implies(left: Any, right: Any) -> b.Expression:
    """Create a logical implication constraint (left => right).

    Args:
        left: Antecedent of the implication.
        right: Consequent of the implication.

    Returns:
        Expression representing the implication constraint.
    """
    return _implies(left, right)


_all_different = library.Relation(
    "all_different", fields=[b.Field.input("over", TypeVar)]
)


def all_different(*args: b.Value) -> b.Aggregate:
    """Create an all-different global constraint.

    Requires all variables to take pairwise distinct values.

    Args:
        *args: Decision variables that must all differ.

    Returns:
        Aggregate expression for the all-different constraint.
    """
    return b.Aggregate(_all_different, *args)


_special_ordered_set_type_2 = library.Relation(
    "special_ordered_set_type_2",
    fields=[b.Field.input("index", Integer), b.Field.input("variables", CoreAny)],
)


def special_ordered_set_type_2(index: Any, variables: Any) -> b.Aggregate:
    """Create a special ordered set type 2 (SOS2) constraint.

    At most two variables can be non-zero, and they must be consecutive
    in the given order. Useful for piecewise-linear approximations.

    Args:
        index: Expression specifying variable order.
        variables: Decision variables forming the ordered set.

    Returns:
        Aggregate expression for the SOS2 constraint.
    """
    return b.Aggregate(_special_ordered_set_type_2, index, variables)
