# ============================================================
# File: _solve_ode_first_order_homogeneous_yprime_f_y_over_x.py
# Topic: Homogeneous First-Order ODE  (y' = F(y/x))
# Method:
#   Let v = y/x  =>  y = v x  =>  dy/dx = v + x dv/dx
#   Then: v + x dv/dx = F(v)  =>  x dv/dx = F(v) - v
#   Separable: dv/(F(v)-v) = dx/x
#   Integrate: ∫ dv/(F(v)-v) = ln|x| + C
#   Substitute back v = y/x
#
# Optional IC: y(x0)=y0  (x0 != 0)
# ============================================================

from __future__ import annotations

from typing import Optional, Union, Dict, Any

import sympy as sp


def solve_ode_first_order_homogeneous_yprime_f_y_over_x(
    F: Union[str, sp.Expr],
    *,
    x_symbol: str = "x",
    y_symbol: str = "y",
    v_symbol: str = "v",
    C_symbol: str = "C",
    x0: 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 y' = F(y/x) using substitution v = y/x.

    Input F can be:
      - an expression in v (recommended), e.g. "v+1", "v**2", "sin(v)"
      - OR an expression in (y/x), e.g. "y/x + 1", "(y/x)**2" (auto-detected)

    Optional initial condition:
      y(x0) = y0   (requires x0 != 0)

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

    # -----------------------------
    # 1) Symbols + parsing
    # -----------------------------
    x = sp.Symbol(x_symbol, real=True)
    y = sp.Function(y_symbol)
    yx = y(x)
    v = sp.Symbol(v_symbol, real=True)
    C = sp.Symbol(C_symbol)

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

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

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

    # If F depends on x or y explicitly, interpret it as expression in (y/x)
    # and convert to F(v) by substituting (y/x)->v.
    y_over_x = sp.Symbol("_y_over_x_dummy_")
    F_in_v = None

    if (F_raw.has(x) or F_raw.has(yx) or (isinstance(F, str) and ("y" in F and "/" in F))):
        # Replace y/x with dummy then dummy->v
        # Best-effort: directly substitute (y/x)->v if present
        F_tmp = F_raw
        F_tmp = F_tmp.subs({yx / x: v})
        # Also handle plain symbol y (if user wrote y instead of y(x)), not recommended but try
        y_sym = sp.Symbol(y_symbol)
        F_tmp = F_tmp.subs({y_sym / x: v})
        F_in_v = F_tmp
    else:
        # Assume expression already in v
        F_in_v = F_raw

    if simplify:
        F_in_v = sp.simplify(F_in_v)

    # -----------------------------
    # 2) Core method
    # -----------------------------
    # v = y/x  => y = v x => y' = v + x v'
    # ODE: y' = F(v)  => v + x dv/dx = F(v)
    # => x dv/dx = F(v) - v
    denom = sp.simplify(F_in_v - v) if simplify else (F_in_v - v)

    if denom.equals(0):
        # F(v) - v = 0 => v constant => y = v x
        # This is a special degenerate case; still solvable.
        # v is any constant k; hence y = k x.
        k = sp.Symbol("k")
        y_general = k * x
        ode_ltx = f"\\frac{{d {L(yx)}}}{{d {L(x)}}} = {L(F_in_v.subs(v, yx/x))}"
        soln_s = f"""
        <div class="wrap">
          <div class="card">
            <div class="badge">Note: Homogeneous First-Order ODE (Degenerate case)<br/></div>
            \\(\\displaystyle
            \\begin{{aligned}}
            & \\displaystyle {ode_ltx} \\\\
            & \\displaystyle \\Rightarrow F(v)-v = 0 \\Rightarrow v=\\text{{constant}} \\\\
            & \\displaystyle \\Rightarrow {L(yx)} = v {L(x)} = {L(k)}{L(x)} \\\\
            \\end{{aligned}}
            \\)
          </div>
        </div>
        """.replace("\n", "")
        return {
            "problem_s": f"Solve \\( {ode_ltx} \\)",
            "soln_s": soln_s.strip(),
            "answer_s": f"\\( {L(yx)} = {L(y_general)} \\)",
        }

    integrand = sp.simplify(1 / denom) if simplify else (1 / denom)
    Iv_raw = sp.integrate(integrand, v)
    Iv = sp.simplify(Iv_raw) if simplify else Iv_raw

    # General implicit solution:
    # Iv(v) = ln|x| + C, with v = y/x
    # Optional IC: y(x0)=y0 => v0=y0/x0, C = Iv(v0) - ln|x0|
    use_ic = (x0 is not None) and (y0 is not None)
    parsed_x0 = None
    parsed_y0 = None
    v0 = None
    C_value = None

    if use_ic:
        parsed_x0 = sp.sympify(x0, locals=base_locals)
        parsed_y0 = sp.sympify(y0, locals=base_locals)
        if sp.simplify(parsed_x0).equals(0):
            raise ValueError("For v=y/x substitution, x0 must be non-zero.")
        v0 = sp.simplify(parsed_y0 / parsed_x0) if simplify else (parsed_y0 / parsed_x0)
        C_value_raw = Iv.subs(v, v0) - sp.log(sp.Abs(parsed_x0))
        C_value = sp.simplify(C_value_raw) if simplify else C_value_raw

    # Notes for unevaluated integrals
    note_parts = []
    if bool(Iv.has(sp.Integral)):
        note_parts.append("SymPy kept the v-integral unevaluated.")
    anti_note = " ".join(note_parts)

    # -----------------------------
    # 3) Build HTML (steps)
    # -----------------------------
    ode_ltx = f"\\frac{{d {L(yx)}}}{{d {L(x)}}} = {L(F_in_v.subs(v, yx/x))}"

    v_def_ltx = f"{L(v)} = \\frac{{{L(yx)}}}{{{L(x)}}}"
    y_as_v_ltx = f"{L(yx)} = {L(v)}\\,{L(x)}"
    dy_dx_ltx = f"\\frac{{d {L(yx)}}}{{d {L(x)}}} = {L(v)} + {L(x)}\\frac{{d{L(v)}}}{{d{L(x)}}}"

    sub_ltx = f"{L(v)} + {L(x)}\\frac{{d{L(v)}}}{{d{L(x)}}} = {L(F_in_v)}"
    sep_ltx = f"{L(x)}\\frac{{d{L(v)}}}{{d{L(x)}}} = {L(F_in_v)} - {L(v)}"
    sep2_ltx = f"\\frac{{d{L(v)}}}{{{L(F_in_v)}-{L(v)}}} = \\frac{{d{L(x)}}}{{{L(x)}}}"

    int_ltx = f"\\int \\frac{{d{L(v)}}}{{{L(F_in_v)}-{L(v)}}} = \\int \\frac{{d{L(x)}}}{{{L(x)}}}"
    iv_eval_ltx = f"{L(Iv)} = \\ln\\left|{L(x)}\\right| + {L(C)} \\qquad \\cdots \\text{{(i)}}"
    back_sub_ltx = f"{L(Iv.subs(v, yx/x))} = \\ln\\left|{L(x)}\\right| + {L(C)}"

    soln_stmts = []
    soln_stmt = f"""
    <div class="wrap">
      <div class="card">
        <div class="badge">Note: Homogeneous First-Order ODE (y' = F(y/x))<br/></div>

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

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

        & \\displaystyle \\Rightarrow \\text{{Let }} {v_def_ltx} \\;\\; (\\Rightarrow\\; {y_as_v_ltx}) \\\\
        & \\displaystyle \\Rightarrow {dy_dx_ltx} \\\\[8pt]

        & \\displaystyle \\Rightarrow \\text{{Substitute into the ODE:}} \\\\
        & \\displaystyle \\Rightarrow {sub_ltx} \\\\
        & \\displaystyle \\Rightarrow {sep_ltx} \\\\
        & \\displaystyle \\Rightarrow {sep2_ltx} \\\\[8pt]

        & \\displaystyle \\Rightarrow \\text{{Integrate both sides:}} \\\\
        & \\displaystyle \\Rightarrow {int_ltx} \\\\
        & \\displaystyle \\Rightarrow {iv_eval_ltx} \\\\[8pt]

        & \\displaystyle \\Rightarrow \\text{{Substitute back }} {L(v)}={L(yx)}/{L(x)}: \\\\
        & \\displaystyle \\Rightarrow {back_sub_ltx} \\\\

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

    # IC block: show extra "at x=x0" line; answer must NOT contain "from (i)"
    ans_ltx = back_sub_ltx  # default (no IC)

    if use_ic and (C_value is not None):
        ic_ltx = f"{L(yx)}\\Big|_{{{L(x)}={L(parsed_x0)}}} = {L(parsed_y0)}"
        v0_ltx = f"{L(v)}\\Big|_{{{L(x)}={L(parsed_x0)}}} = \\frac{{{L(parsed_y0)}}}{{{L(parsed_x0)}}} = {L(v0)}"

        C_expr_ltx = f"{L(C)} = {L(Iv)} - \\ln\\left|{L(x)}\\right|"
        C_at_x0_ltx = (
            f"{L(C)} = {L(Iv.subs(v, v0))} - \\ln\\left|{L(parsed_x0)}\\right|"
            f" \\qquad \\text{{at }} {L(x)}={L(parsed_x0)}"
        )
        C_val_ltx = f"{L(C)} = {L(C_value)} \\qquad \\text{{Given }} {ic_ltx}"

        final_soln_ltx = f"{L(Iv.subs(v, yx/x))} = \\ln\\left|{L(x)}\\right| + {L(C_value)} \\qquad \\cdots \\text{{ from (i)}}"
        final_ans_ltx = f"{L(Iv.subs(v, yx/x))} = \\ln\\left|{L(x)}\\right| + {L(C_value)}"

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

        ans_ltx = final_ans_ltx
        problem_s = f"Solve \\( {ode_ltx} \\) for \\( {ic_ltx} \\)"
    else:
        problem_s = f"Solve \\( {ode_ltx} \\)"

    soln_s = "".join(soln_stmts)

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

