"""
CP-SAT model builder for deterministic scheduling.
Constructs decision variables and calls constraint/objective builders.
"""
from typing import Dict, List, Any, Tuple
from ortools.sat.python import cp_model
from constraint_builder import build_constraints
from objective_builder import build_objective


def build_model(problem_data: Dict[str, Any]) -> Tuple[cp_model.CpModel, Dict[Tuple[int, int], cp_model.IntVar]]:
    """
    Build CP-SAT model from problem data.

    Args:
        problem_data: Problem input JSON

    Returns:
        Tuple of (model, decision_variables_dict)
        Decision variables: x[(employee_id, shift_id)] = BoolVar
    """
    model = cp_model.CpModel()

    employees = problem_data["employees"]
    shifts = problem_data["shifts"]
    locked_shifts = problem_data.get("lockedShifts", [])

    # Build locked assignments lookup
    locked_assignments = {ls["shiftId"]: ls["employeeId"] for ls in locked_shifts}

    # Decision variables: x[employee_id, shift_id] = 1 if employee assigned to shift
    x: Dict[Tuple[int, int], cp_model.IntVar] = {}

    for emp in employees:
        emp_id = emp["userId"]

        for shift in shifts:
            shift_id = shift["shiftId"]

            # Existing assignments are immutable context, not decisions. Keeping
            # them out of x prevents any hard constraint from rewriting or
            # invalidating a manager's already-published schedule.
            if shift_id in locked_assignments or shift.get("isLocked", False):
                continue

            # Create boolean variable
            var_name = f"x_e{emp_id}_s{shift_id}"
            x[(emp_id, shift_id)] = model.NewBoolVar(var_name)

            # Note: Locked shift handling is done in C2 constraint (constraint_builder.py)

    # Build constraints
    constraints_metadata = build_constraints(model, problem_data, x)

    # Build objective function
    build_objective(model, problem_data, x)

    return model, x
