# ============================================================
# File: _solve_ode_second_order_cauchy_euler.py
# Topic: Cauchy–Euler (Equidimensional) ODE (full-steps HTML)
# Form:  x^2 y'' + A x y' + B y = g(x)   (this solver: homogeneous only)
# Here we implement the common homogeneous form:
#        x^2 y'' + A x y' + B y = 0
# Method:
#   Try y = x^m  => characteristic: m(m-1) + A m + B = 0
#   Solve for m and write y(x) based on root type (distinct, repeated, complex)
# Optional IC: y(x0)=y0, y'(x0)=dy0  (x0 != 0 typically)
# Output: dict {problem_s, soln_s, answer_s}
# ============================================================

from __future__ import annotations

from typing import Optional, Union, Dict, Any

import sympy as sp


def solve_ode_second_order_cauchy_euler_homogeneous(
    A: Union[str, sp.Expr],
    B: Union[str, sp.Expr],
    *,
    x_symbol: str = "x",
    y_symbol: str = "y",
    m_symbol: str = "m",
    C1_symbol: str = "C_1",
    C2_symbol: str = "C_2",
    x0: Optional[Union[int, float, str, sp.Expr]] = None,
    y0: Optional[Union[int, float, str, sp.Expr]] = None,
    dy0: Optional[Union[int, float, str, sp.Expr]] = None,
    locals_map: Optional[Dict[str, Any]] = None,
    simplify: bool = True,
) -> dict:
    """
    Full-steps HTML solution for homogeneous Cauchy–Euler ODE:
        x^2 y'' + A x y' + B y = 0

    Optional initial conditions:
        y(x0)=y0,  y'(x0)=dy0

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

    # -----------------------------
    # 1) Setup symbols + parsing
    # -----------------------------
    x = sp.Symbol(x_symbol, real=True)
    y = sp.Function(y_symbol)
    yx = y(x)

    m = sp.Symbol(m_symbol)
    C1 = sp.Symbol(C1_symbol)
    C2 = sp.Symbol(C2_symbol)

    base_locals = {
        x_symbol: x,
        m_symbol: m,
        "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,
    }
    if locals_map:
        base_locals.update(locals_map)

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

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

    if simplify:
        A_, B_ = sp.simplify(A_), sp.simplify(B_)

    # -----------------------------
    # 2) Characteristic in m
    # -----------------------------
    # y = x^m, y' = m x^(m-1), y'' = m(m-1) x^(m-2)
    char_poly = sp.expand(m*(m - 1) + A_*m + B_)
    if simplify:
        char_poly = sp.simplify(char_poly)

    # Solve roots
    roots = sp.solve(sp.Eq(char_poly, 0), m)
    # For quadratic, roots usually length 2 (could be repeated)
    # Compute discriminant of m^2 + (A-1)m + B = 0
    # since: m(m-1)+Am+B = m^2 + (A-1)m + B
    disc = sp.simplify((A_ - 1)**2 - 4*B_) if simplify else ((A_ - 1)**2 - 4*B_)

    # Ensure r1,r2 symbols
    if len(roots) >= 2:
        m1 = sp.simplify(roots[0]) if simplify else roots[0]
        m2 = sp.simplify(roots[1]) if simplify else roots[1]
    elif len(roots) == 1:
        m1 = sp.simplify(roots[0]) if simplify else roots[0]
        m2 = m1
    else:
        # fallback quadratic formula
        m1 = sp.simplify((-(A_ - 1) + sp.sqrt(disc)) / 2) if simplify else ((-(A_ - 1) + sp.sqrt(disc)) / 2)
        m2 = sp.simplify((-(A_ - 1) - sp.sqrt(disc)) / 2) if simplify else ((-(A_ - 1) - sp.sqrt(disc)) / 2)

    case = "unknown"
    if disc.is_zero is True:
        case = "repeated"
    elif disc.is_positive is True:
        case = "distinct_real"
    elif disc.is_negative is True:
        case = "complex"

    # -----------------------------
    # 3) General solution
    # -----------------------------
    if case == "repeated":
        # y = x^m (C1 + C2 ln x)
        y_general = (x**m1) * (C1 + C2*sp.log(x))
        form_note = "D=0 (repeated root)"
    elif case == "complex":
        # m = alpha ± i beta
        alpha = sp.simplify(sp.re(m1)) if simplify else sp.re(m1)
        beta = sp.simplify(sp.im(m1)) if simplify else sp.im(m1)
        # Typical form: x^alpha (C1 cos(beta ln x) + C2 sin(beta ln x))
        y_general = (x**alpha) * (C1*sp.cos(beta*sp.log(x)) + C2*sp.sin(beta*sp.log(x)))
        form_note = "D<0 (complex roots)"
    else:
        # distinct or unknown: y = C1 x^m1 + C2 x^m2
        y_general = C1*(x**m1) + C2*(x**m2)
        form_note = "use m1, m2"

    if simplify:
        y_general = sp.simplify(y_general)

    # -----------------------------
    # 4) Optional initial conditions
    # -----------------------------
    use_ic = (x0 is not None) and (y0 is not None) and (dy0 is not None)

    y_particular = None
    C1_value = None
    C2_value = None

    if use_ic:
        x0_ = sp.sympify(x0, locals=base_locals)
        y0_ = sp.sympify(y0, locals=base_locals)
        dy0_ = sp.sympify(dy0, locals=base_locals)

        yprime = sp.diff(y_general, x)

        eq1 = sp.Eq(y_general.subs(x, x0_), y0_)
        eq2 = sp.Eq(yprime.subs(x, x0_), dy0_)

        solC = sp.solve((eq1, eq2), (C1, C2), dict=True)
        if solC:
            C1_value = solC[0].get(C1, None)
            C2_value = solC[0].get(C2, None)
            y_particular = y_general.subs({C1: C1_value, C2: C2_value})
            if simplify:
                C1_value = sp.simplify(C1_value) if C1_value is not None else None
                C2_value = sp.simplify(C2_value) if C2_value is not None else None
                y_particular = sp.simplify(y_particular)

    # -----------------------------
    # 5) Build step-by-step HTML
    # -----------------------------
    y1 = sp.diff(yx, x)
    y2 = sp.diff(yx, x, 2)

    ode_ltx = f"{L(x)}^2\\,{L(y2)} + ({L(A_)}){L(x)}\\,{L(y1)} + ({L(B_)}){L(yx)} = 0"

    trial_ltx = f"{L(yx)} = {L(x)}^{{{L(m)}}}"
    yprime_trial_ltx = f"{L(y1)} = {L(m)}{L(x)}^{{{L(m)}-1}}"
    y2_trial_ltx = f"{L(y2)} = {L(m)}({L(m)}-1){L(x)}^{{{L(m)}-2}}"

    sub_ltx = (
        f"{L(x)}^2\\left({L(m)}({L(m)}-1){L(x)}^{{{L(m)}-2}}\\right)"
        f" + ({L(A_)}){L(x)}\\left({L(m)}{L(x)}^{{{L(m)}-1}}\\right)"
        f" + ({L(B_)})\\left({L(x)}^{{{L(m)}}}\\right) = 0"
    )
    simp_ltx = f"\\left({L(char_poly)}\\right){L(x)}^{{{L(m)}}} = 0"
    char_ltx = f"{L(char_poly)} = 0 \\qquad \\cdots \\text{{(Characteristic)}}"
    disc_ltx = f"D = ({L(A_)}-1)^2 - 4({L(B_)}) = {L(disc)}"
    m1_ltx = f"{L(m)}_1 = {L(m1)}"
    m2_ltx = f"{L(m)}_2 = {L(m2)}"
    ygen_ltx = f"{L(yx)} = {L(y_general)} \\qquad \\cdots \\text{{(i)}}"

    soln_stmts = []

    soln_stmt = f"""
    <div class="wrap">
      <div class="card">
        <div class="badge">Note: Cauchy–Euler ODE (homogeneous)<br/></div>

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

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

        & \\displaystyle \\Rightarrow \\text{{Assume a trial solution: }} {trial_ltx} \\\\
        & \\displaystyle \\Rightarrow {yprime_trial_ltx},\\; {y2_trial_ltx} \\\\[6pt]

        & \\displaystyle \\Rightarrow \\text{{Substitute into the ODE:}} \\\\
        & \\displaystyle \\Rightarrow {sub_ltx} \\\\
        & \\displaystyle \\Rightarrow {simp_ltx} \\\\
        & \\displaystyle \\Rightarrow {char_ltx} \\\\[6pt]

        & \\displaystyle \\Rightarrow {disc_ltx} \\\\
        & \\displaystyle \\Rightarrow {m1_ltx},\\; {m2_ltx} \\\\[6pt]

        & \\displaystyle \\Rightarrow \\text{{Therefore, {form_note}}} \\\\
        & \\displaystyle \\Rightarrow {ygen_ltx} \\\\

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

    # IC block
    if use_ic and (y_particular is not None) and (C1_value is not None) and (C2_value is not None):
        x0_ = sp.sympify(x0, locals=base_locals)
        y0_ = sp.sympify(y0, locals=base_locals)
        dy0_ = sp.sympify(dy0, locals=base_locals)

        yprime_general = sp.diff(y_general, x)

        ic1_ltx = f"{L(yx)}\\Big|_{{{L(x)}={L(x0_)}}} = {L(y0_)}"
        ic2_ltx = f"{L(sp.diff(yx, x))}\\Big|_{{{L(x)}={L(x0_)}}} = {L(dy0_)}"

        y_at_x0_ltx = f"{L(y_general.subs(x, x0_))} = {L(y0_)} \\qquad \\text{{at }} {L(x)}={L(x0_)}"
        yp_at_x0_ltx = f"{L(yprime_general.subs(x, x0_))} = {L(dy0_)} \\qquad \\text{{at }} {L(x)}={L(x0_)}"

        C1_ltx = f"{L(C1)} = {L(C1_value)}"
        C2_ltx = f"{L(C2)} = {L(C2_value)}"

        final_ic_soln_ltx = f"{L(yx)} = {L(y_particular)} \\qquad \\cdots \\text{{from (i)}}"
        final_ic_ans_ltx = f"{L(yx)} = {L(y_particular)}"

        ic_block = f"""
            <br/>
            \\(\\displaystyle
            \\begin{{aligned}}
            & \\displaystyle \\Rightarrow \\text{{Given }} {ic1_ltx},\\; {ic2_ltx} \\\\
            & \\displaystyle \\Rightarrow {y_at_x0_ltx} \\\\
            & \\displaystyle \\Rightarrow {yp_at_x0_ltx} \\\\
            & \\displaystyle \\Rightarrow {C1_ltx},\\; {C2_ltx} \\\\
            & \\displaystyle \\Rightarrow {final_ic_soln_ltx} \\\\
            \\end{{aligned}}
            \\)
        """
        soln_stmts.append(ic_block.replace("\n", ""))

        problem_s = f"Solve \\( {ode_ltx} \\) for \\( {ic1_ltx},\\; {ic2_ltx} \\)"
        answer_s = f"\\( {final_ic_ans_ltx} \\)"
    else:
        problem_s = f"Solve \\( {ode_ltx} \\)"
        answer_s = f"\\( {L(yx)} = {L(y_general)} \\)"

    soln_s = "".join(soln_stmts)

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


