# ============================================================
# File: _solve_ode_autonomous_scalar_phase_line.py
# Topic: Autonomous Scalar ODE / Phase-Line Equation (y' = f(y))
#
# Form:  dy/dt = f(y)   (autonomous, 1D)
#
# What this solver produces:
#   A) Phase-line analysis (equilibria + sign chart + stability)
#   B) Separation-of-variables solution (implicit):
#        ∫ dy / f(y) = t + C
#   C) Optional IC: y(t0)=y0  =>  C = ∫^{y0} dy/f(y) - t0  (symbolically)
#
# Notes:
#   - If SymPy can integrate 1/f(y), you get an explicit antiderivative.
#   - Otherwise it keeps an Integral(...) in the final implicit answer.
# ============================================================

from __future__ import annotations

from typing import Optional, Union, Dict, Any, List, Tuple

import sympy as sp


def solve_ode_autonomous_scalar_phase_line(
    f_of_y: Union[str, sp.Expr],
    *,
    t_symbol: str = "t",
    y_symbol: str = "y",
    C_symbol: str = "C",
    t0: Optional[Union[int, float, str, sp.Expr]] = None,
    y0: Optional[Union[int, float, str, sp.Expr]] = None,
    locals_map: Optional[Dict[str, Any]] = None,
    simplify: bool = True,
) -> dict:
    """
    Solve and analyze the autonomous ODE:
        dy/dt = f(y)

    Returns:
        { "problem_s": "...", "soln_s": "...", "answer_s": "..." }

    Output style:
      - HTML with \\(\\displaystyle ...\\) and aligned steps
      - "answer_s" is the final implicit solution (no "from (i)" tags)
    """

    # -----------------------------
    # 1) Symbols + parsing
    # -----------------------------
    t = sp.Symbol(t_symbol, real=True)
    y = sp.Function(y_symbol)
    yt = y(t)

    yy = sp.Symbol(y_symbol, real=True)  # treat f as function of symbol y
    C = sp.Symbol(C_symbol)

    base_locals = {
        t_symbol: t,
        y_symbol: yy,
        "pi": sp.pi,
        "E": sp.E,
        "exp": sp.exp,
        "log": sp.log,
        "ln": sp.log,
        "sqrt": sp.sqrt,
        "sin": sp.sin,
        "cos": sp.cos,
        "tan": sp.tan,
        "asin": sp.asin,
        "acos": sp.acos,
        "atan": sp.atan,
        "sinh": sp.sinh,
        "cosh": sp.cosh,
        "tanh": sp.tanh,
        "Abs": sp.Abs,
        "sec": sp.sec,
        "csc": sp.csc,
        "cot": sp.cot,
        # allow "Piecewise" etc
        "Piecewise": sp.Piecewise,
        "Heaviside": sp.Heaviside,
    }
    if locals_map:
        base_locals.update(locals_map)

    def L(expr: sp.Expr) -> str:
        return sp.latex(expr)

    try:
        f_raw = sp.sympify(f_of_y, locals=base_locals) if isinstance(f_of_y, str) else sp.sympify(f_of_y)
    except Exception as e:
        raise ValueError(f"Could not parse f(y)={f_of_y!r}. Error: {e}") from e

    f = sp.simplify(f_raw) if simplify else f_raw

    # -----------------------------
    # 2) Phase-line: equilibria + stability
    # -----------------------------
    # Equilibria: solve f(y)=0
    eq_solutions = []
    try:
        eq_solutions = sp.solve(sp.Eq(f, 0), yy)
    except Exception:
        eq_solutions = []

    # Filter for real-ish solutions, best-effort
    equilibria = []
    for s in eq_solutions:
        try:
            sr = sp.simplify(s)
            equilibria.append(sr)
        except Exception:
            equilibria.append(s)

    # If too many / unsolved, keep as raw
    equilibria_unique = []
    seen = set()
    for e in equilibria:
        key = sp.srepr(e)
        if key not in seen:
            equilibria_unique.append(e)
            seen.add(key)

    equilibria = equilibria_unique

    # Stability (standard quick test): f'(y*) < 0 stable, >0 unstable, =0 inconclusive
    fprime = sp.diff(f, yy)
    stability_rows: List[Tuple[sp.Expr, str]] = []
    for ystar in equilibria:
        try:
            val = sp.simplify(fprime.subs(yy, ystar)) if simplify else fprime.subs(yy, ystar)
            if val.is_negative is True:
                stab = "stable"
            elif val.is_positive is True:
                stab = "unstable"
            elif val.is_zero is True:
                stab = "inconclusive (f'(y*)=0)"
            else:
                # symbolic unknown sign
                stab = f"depends (f'(y*)={sp.latex(val)})"
        except Exception:
            stab = "inconclusive"
        stability_rows.append((ystar, stab))

    # -----------------------------
    # 3) Separation (implicit solution)
    # -----------------------------
    # dy/dt = f(y) => dy/f(y) = dt
    integrand = sp.simplify(1 / f) if simplify else (1 / f)
    Fint_raw = sp.integrate(integrand, yy)
    Fint = sp.simplify(Fint_raw) if simplify else Fint_raw

    # General implicit: ∫ dy/f(y) = t + C
    # Substitute y(t) into antiderivative:
    left_general = Fint.subs(yy, yt)
    implicit_general = sp.Eq(left_general, t + C)

    # Optional IC: y(t0)=y0 => C = F(y0) - t0
    use_ic = (t0 is not None) and (y0 is not None)
    parsed_t0 = parsed_y0 = None
    C_value = None
    if use_ic:
        parsed_t0 = sp.sympify(t0, locals=base_locals)
        parsed_y0 = sp.sympify(y0, locals=base_locals)
        C_value_raw = Fint.subs(yy, parsed_y0) - parsed_t0
        C_value = sp.simplify(C_value_raw) if simplify else C_value_raw

    note_parts = []
    if bool(Fint.has(sp.Integral)):
        note_parts.append("SymPy kept the integral \\(\\int \\frac{dy}{f(y)}\\) unevaluated.")
    anti_note = " ".join(note_parts)

    # -----------------------------
    # 4) Build HTML (steps)
    # -----------------------------
    ode_ltx = f"\\frac{{d {L(yt)}}}{{d {L(t)}}} = {L(f.subs(yy, yt))}"

    sep_ltx = f"\\frac{{d{L(yt)}}}{{{L(f.subs(yy, yt))}}} = d{L(t)}"
    int_ltx = f"\\int \\frac{{d{L(yt)}}}{{{L(f.subs(yy, yt))}}} = \\int d{L(t)}"
    antideriv_ltx = f"\\int \\frac{{d{L(yy)}}}{{{L(f)}}} = {L(Fint)}"
    implicit_ltx = f"{L(Fint.subs(yy, yt))} = {L(t)} + {L(C)} \\qquad \\cdots \\text{{(i)}}"

    # Equilibria + stability table (as aligned lines)
    if equilibria:
        eq_list_ltx = ",\\; ".join([L(e) for e in equilibria])
        eqs_ltx = f"\\text{{Equilibria: }} f({L(yy)})=0 \\Rightarrow {L(yy)} = {eq_list_ltx}"
    else:
        eqs_ltx = "\\text{Equilibria: solve } f(y)=0 \\text{ (no closed-form roots found)}"

    stab_lines = []
    if equilibria:
        for (ystar, stab) in stability_rows:
            stab_lines.append(f"y^*={L(ystar)}:\\; {stab}")
        stab_ltx = "\\\\\n".join([f"& \\displaystyle \\Rightarrow \\text{{{s}}}" for s in stab_lines])
    else:
        stab_ltx = "& \\displaystyle \\Rightarrow \\text{Stability test: } f'(y^*) \\text{ not evaluated (no equilibria)}"

    soln_stmts = []

    soln_stmt = f"""
    <div class="wrap">
      <div class="card">
        <div class="badge">Note: Autonomous Scalar ODE / Phase-Line (\\(y' = f(y)\\))<br/></div>

        \\(\\displaystyle
        \\begin{{aligned}}

        & \\displaystyle {ode_ltx} \\\\[8pt]

        & \\displaystyle \\Rightarrow \\text{{Phase-line (equilibria + stability):}} \\\\
        & \\displaystyle \\Rightarrow {eqs_ltx} \\\\
        {stab_ltx} \\\\[10pt]

        & \\displaystyle \\Rightarrow \\text{{Separation of variables:}} \\\\
        & \\displaystyle \\Rightarrow {sep_ltx} \\\\
        & \\displaystyle \\Rightarrow {int_ltx} \\\\
        & \\displaystyle \\Rightarrow {antideriv_ltx} \\\\
        & \\displaystyle \\Rightarrow {implicit_ltx} \\\\

        \\end{{aligned}}
        \\)
        <div class="note">{anti_note}</div>
      </div>
    </div>
    """
    soln_stmts.append(soln_stmt.replace("\n", ""))

    # IC block
    if use_ic and (C_value is not None):
        ic_ltx = f"{L(yt)}\\Big|_{{{L(t)}={L(parsed_t0)}}} = {L(parsed_y0)}"

        C_expr_ltx = f"{L(C)} = {L(Fint.subs(yy, parsed_y0))} - {L(parsed_t0)}"
        C_val_ltx = f"{L(C)} = {L(C_value)} \\qquad \\text{{Given }} {ic_ltx}"

        final_soln_ltx = f"{L(Fint.subs(yy, yt))} = {L(t)} + {L(C_value)} \\qquad \\cdots \\text{{from (i)}}"
        final_ans_ltx = f"{L(Fint.subs(yy, yt))} = {L(t)} + {L(C_value)}"

        ic_block = f"""
            <br/>
            \\(\\displaystyle
            \\begin{{aligned}}
            & \\displaystyle \\Rightarrow {C_expr_ltx} \\\\
            & \\displaystyle \\Rightarrow {C_val_ltx} \\\\
            & \\displaystyle \\Rightarrow {final_soln_ltx} \\\\
            \\end{{aligned}}
            \\)
        """
        soln_stmts.append(ic_block.replace("\n", ""))

        problem_s = f"Solve \\( {ode_ltx} \\) for \\( {ic_ltx} \\)"
        answer_s = f"\\( {final_ans_ltx} \\)"
    else:
        problem_s = f"Solve \\( {ode_ltx} \\)"
        answer_s = f"\\( {L(Fint.subs(yy, yt))} = {L(t)} + {L(C)} \\)"

    soln_s = "".join(soln_stmts)

    return {
        "problem_s": problem_s.strip(),
        "soln_s": soln_s.strip(),
        "answer_s": answer_s.strip(),
    }

