"""Main GAMS MCP code generator.

This module orchestrates all emission components to generate a complete
GAMS MCP file from a KKT system.
"""

from src.config import Config
from src.emit.model import emit_model_mcp, emit_solve
from src.emit.original_symbols import (
    _compute_set_alias_phases,
    emit_computed_parameter_assignments,
    emit_original_aliases,
    emit_original_parameters,
    emit_original_sets,
)
from src.emit.templates import emit_equation_definitions, emit_equations, emit_variables
from src.kkt.kkt_system import KKTSystem


def emit_gams_mcp(
    kkt: KKTSystem,
    model_name: str = "mcp_model",
    add_comments: bool = True,
    config: Config | None = None,
) -> str:
    """Generate complete GAMS MCP code from KKT system.

    This function orchestrates all emission components to produce a complete,
    runnable GAMS file containing:
    1. Original model declarations (Sets, Aliases, Parameters)
    2. KKT-specific sets for multiplier indexing
    3. Variable declarations (primal + multipliers, grouped by kind)
    4. Equation declarations
    5. Equation definitions
    6. Model MCP declaration with complementarity pairs
    7. Solve statement

    Args:
        kkt: The KKT system to emit
        model_name: Name for the GAMS model (default: "mcp_model")
        add_comments: Whether to add explanatory comments (default: True)

    Returns:
        Complete GAMS MCP code as a string

    Example:
        ```python
        # After parsing and KKT assembly
        gams_code = emit_gams_mcp(kkt)
        Path("output.gms").write_text(gams_code)
        ```
    """
    sections = []

    # Header comment
    if add_comments:
        sections.append("$onText")
        sections.append("Generated by nlp2mcp")
        sections.append("")
        sections.append("This file contains the KKT (Karush-Kuhn-Tucker) conditions")
        sections.append("for the original NLP model, transformed into MCP format.")
        sections.append("")
        sections.append("KKT System Components:")
        sections.append("  - Stationarity: ∇f + J^T λ + J^T ν - π^L + π^U = 0")
        sections.append("  - Complementarity: g(x) ⊥ λ, h(x) = 0, bounds ⊥ π")
        sections.append("  - Dual feasibility: λ, π^L, π^U ≥ 0")
        sections.append("  - Primal feasibility: g(x) ≤ 0, h(x) = 0, lo ≤ x ≤ up")
        sections.append("$offText")
        sections.append("")

    # Original model symbols
    if add_comments:
        sections.append("* ============================================")
        sections.append("* Original Model Declarations")
        sections.append("* ============================================")
        sections.append("")

    # Sprint 17 Day 10 (Issue #621): Split sets and aliases into phases
    # Correct emit order ensures all dependencies are declared before use.
    # For each phase p: emit phase p sets, then phase p aliases.
    # Supports N phases dynamically based on dependency depth.
    # Compute phases once and pass to both emitters to avoid redundant work.
    phases = _compute_set_alias_phases(kkt.model_ir)
    sets_by_phase = emit_original_sets(kkt.model_ir, precomputed_phases=phases)
    aliases_by_phase = emit_original_aliases(kkt.model_ir, precomputed_phases=phases)

    # Ensure both lists have the same length by padding with empty strings
    if sets_by_phase or aliases_by_phase:
        max_phases = max(len(sets_by_phase), len(aliases_by_phase))
    else:
        max_phases = 0
    while len(sets_by_phase) < max_phases:
        sets_by_phase.append("")
    while len(aliases_by_phase) < max_phases:
        aliases_by_phase.append("")

    # Emit each phase: sets first, then aliases
    for phase_idx in range(max_phases):
        if sets_by_phase[phase_idx]:
            sections.append(sets_by_phase[phase_idx])
            sections.append("")
        if aliases_by_phase[phase_idx]:
            sections.append(aliases_by_phase[phase_idx])
            sections.append("")

    params_code = emit_original_parameters(kkt.model_ir)
    if params_code:
        sections.append(params_code)
        sections.append("")

    # Sprint 17 Day 4: Emit computed parameter assignments
    computed_params_code = emit_computed_parameter_assignments(kkt.model_ir)
    if computed_params_code:
        sections.append(computed_params_code)
        sections.append("")

    # Variables (primal + multipliers)
    if add_comments:
        sections.append("* ============================================")
        sections.append("* Variables (Primal + Multipliers)")
        sections.append("* ============================================")
        sections.append("")
        sections.append("* Primal variables: Original decision variables from the NLP")
        sections.append("* Multipliers:")
        sections.append("*   ν (nu_*): Free multipliers for equality constraints")
        sections.append("*   λ (lam_*): Positive multipliers for inequality constraints")
        sections.append("*   π^L (piL_*): Positive multipliers for lower bounds")
        sections.append("*   π^U (piU_*): Positive multipliers for upper bounds")
        sections.append("")

    variables_code = emit_variables(kkt)
    sections.append(variables_code)
    sections.append("")

    # Variable initialization (if smooth_abs is enabled)
    if config and config.smooth_abs:
        if add_comments:
            sections.append("* ============================================")
            sections.append("* Variable Initialization")
            sections.append("* ============================================")
            sections.append("")
            sections.append("* Initialize variables to avoid domain errors with smooth abs()")
            sections.append("* The smooth approximation sqrt(x^2 + ε) can cause GAMS")
            sections.append("* domain errors during model generation if variables default to")
            sections.append("* values that make expressions negative under power operations.")
            sections.append("")

        # Initialize all primal variables to a safe non-zero value
        for var_name in kkt.model_ir.variables:
            sections.append(f"{var_name}.l = 1;")

        sections.append("")

    # Equations
    if add_comments:
        sections.append("* ============================================")
        sections.append("* Equations")
        sections.append("* ============================================")
        sections.append("")
        sections.append("* Stationarity: One equation per primal variable (except objvar)")
        sections.append("* Complementarity: Equations for inequalities and bounds")
        sections.append("* Equality constraints: Original equality constraints")
        sections.append("")

    equations_code = emit_equations(kkt)
    sections.append(equations_code)
    sections.append("")

    # Equation definitions
    if add_comments:
        sections.append("* ============================================")
        sections.append("* Equation Definitions")
        sections.append("* ============================================")
        sections.append("")

    eq_defs_code, index_aliases = emit_equation_definitions(kkt)

    # Emit index aliases if any are needed (to avoid GAMS Error 125)
    # These must be declared before the equation definitions that use them
    if index_aliases:
        if add_comments:
            sections.append("* Index aliases to avoid 'Set is under control already' error")
            sections.append("* (GAMS Error 125 when equation domain index is reused in sum)")
        for idx in sorted(index_aliases):
            alias_name = f"{idx}__"
            sections.append(f"Alias({idx}, {alias_name});")
        sections.append("")

    sections.append(eq_defs_code)
    sections.append("")

    # Model MCP
    if add_comments:
        sections.append("* ============================================")
        sections.append("* Model MCP Declaration")
        sections.append("* ============================================")
        sections.append("")
        sections.append("* Each line pairs an equation with a variable:")
        sections.append("*   equation.variable")
        sections.append("*")
        sections.append("* This defines the complementarity problem:")
        sections.append("*   equation ⊥ variable")
        sections.append("*")
        sections.append("* Meaning: equation = 0 if variable > 0")
        sections.append("*          equation ≥ 0 if variable = 0")
        sections.append("")

    model_code = emit_model_mcp(kkt, model_name)
    sections.append(model_code)
    sections.append("")

    # Solve statement
    if add_comments:
        sections.append("* ============================================")
        sections.append("* Solve Statement")
        sections.append("* ============================================")
        sections.append("")

    solve_code = emit_solve(model_name)
    sections.append(solve_code)

    return "\n".join(sections)
