# ============================================================
# File: _solve_pde_elliptic.py
# Topic: Elliptic PDEs (Laplace / Poisson) — App-Friendly Canonical Cases
#
# Elliptic PDE hallmark (2 variables): second-order with same-sign principal part
#   Example: Laplace equation  u_xx + u_yy = 0   (elliptic)
#            Poisson equation  u_xx + u_yy = f(x,y)
#
# We implement a small solver for a few classic elliptic PDE boundary-value cases:
#
# (1) Laplace in a rectangle (0<x<L, 0<y<H) with 3 sides zero and top boundary a single sine mode:
#       u_xx + u_yy = 0
#       u(0,y)=0, u(L,y)=0, u(x,0)=0
#       u(x,H) = sin(nπ x/L)
#     Solution (single mode):
#       u(x,y) = sin(nπ x/L) * sinh(nπ y/L) / sinh(nπ H/L)
#
# (2) Laplace in polar coordinates (radial symmetry, no θ-dependence):
#       u_rr + (1/r) u_r = 0     (for r>0)
#     Solution:
#       u(r) = A ln(r) + B
#     (If domain includes r=0, boundedness forces A=0 => u=B.)
#
# Notes:
#   - We return general-form constants/functions where appropriate.
#   - HTML output uses \\(\\displaystyle ...\\) and aligned steps.
# ============================================================

from __future__ import annotations

from typing import Optional, Union, Dict, Any, Literal

import sympy as sp


EllipticPdeKind = Literal[
    "laplace_rectangle_single_sine_top",
    "laplace_polar_radial",
]


def solve_pde_elliptic(
    *,
    kind: EllipticPdeKind,
    # ----- symbols -----
    x_symbol: str = "x",
    y_symbol: str = "y",
    r_symbol: str = "r",
    u_symbol: str = "u",
    # ----- rectangle params -----
    L_: Union[int, float, str, sp.Expr] = sp.pi,
    H_: Union[int, float, str, sp.Expr] = 1,
    n: Union[int, float, str, sp.Expr] = 1,
    # ----- locals -----
    locals_map: Optional[Dict[str, Any]] = None,
    simplify: bool = True,
) -> dict:
    """
    Returns:
        { "problem_s": "...", "soln_s": "...", "answer_s": "..." }

    Output style:
      - HTML with \\(\\displaystyle ...\\) and aligned steps
      - answer_s is the final solution statement (general/canonical)
    """
    # -----------------------------
    # 1) Symbols + parsing
    # -----------------------------
    x = sp.Symbol(x_symbol, real=True)
    y = sp.Symbol(y_symbol, real=True)
    r = sp.Symbol(r_symbol, positive=True, real=True)

    U = sp.Function(u_symbol)

    base_locals = {
        x_symbol: x,
        y_symbol: y,
        r_symbol: r,
        u_symbol: U,
        "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,
        "Piecewise": sp.Piecewise,
        "Heaviside": sp.Heaviside,
    }
    if locals_map:
        base_locals.update(locals_map)

    def P(v: Union[int, float, str, sp.Expr]) -> sp.Expr:
        return sp.sympify(v, locals=base_locals)

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

    L = P(L_)
    H = P(H_)
    N = P(n)

    if simplify:
        L = sp.simplify(L)
        H = sp.simplify(H)
        N = sp.simplify(N)

    # -----------------------------
    # 2) Cases
    # -----------------------------
    if kind == "laplace_rectangle_single_sine_top":
        # Domain: 0<x<L, 0<y<H
        uxy = U(x, y)
        pde_ltx = "u_{xx} + u_{yy} = 0"

        bc_ltx = (
            "u(0,y)=0,\\;u(" + Ltx(L) + ",y)=0,\\;u(x,0)=0,\\;u(x," + Ltx(H) + ")="
            + "\\sin\\!\\left(\\frac{" + Ltx(N) + "\\pi x}{" + Ltx(L) + "}\\right)"
        )

        lam = (N * sp.pi / L)
        if simplify:
            lam = sp.simplify(lam)

        sol_expr = sp.sin(lam * x) * sp.sinh(lam * y) / sp.sinh(lam * H)
        if simplify:
            sol_expr = sp.simplify(sol_expr)

        answer_ltx = Ltx(uxy) + " = " + Ltx(sol_expr)

        # FIX: Replaced literal '<' with '\\lt' in the domain definition below
        soln_s = (
            "<div class='wrap'><div class='card'>"
            "<div class='badge'>Elliptic PDE (Laplace in Rectangle) — Single Sine Boundary Mode<br/></div>"
            "\\(\\displaystyle \\begin{aligned}"
            "& \\displaystyle " + pde_ltx + ",\\; 0 \\lt x \\lt " + Ltx(L) + ",\\;0 \\lt y \\lt " + Ltx(H) + " \\\\[6pt]"
            "& \\displaystyle \\text{BC: } " + bc_ltx + " \\\\[10pt]"
            "& \\displaystyle \\Rightarrow \\text{Use separation of variables } u(x,y)=X(x)Y(y) \\\\"
            "& \\displaystyle \\Rightarrow X''+\\lambda^2 X=0,\\; Y''-\\lambda^2 Y=0 \\\\"
            "& \\displaystyle \\Rightarrow X(0)=X(" + Ltx(L) + ")=0 \\;\\Rightarrow\\; X=\\sin\\!\\left(\\frac{n\\pi x}{L}\\right),\\; \\lambda=\\frac{n\\pi}{L} \\\\"
            "& \\displaystyle \\Rightarrow u(x,y)=\\sin\\!\\left(\\frac{" + Ltx(N) + "\\pi x}{" + Ltx(L) + "}\\right) \\,"
            "\\frac{\\sinh\\!\\left(\\frac{" + Ltx(N) + "\\pi y}{" + Ltx(L) + "}\\right)}{\\sinh\\!\\left(\\frac{" + Ltx(N) + "\\pi " + Ltx(H) + "}{" + Ltx(L) + "}\\right)} \\\\[6pt]"
            "& \\displaystyle \\Rightarrow \\text{So } " + Ltx(uxy) + " = " + Ltx(sol_expr) + " \\\\"
            "\\end{aligned}\\)"
            "<div class='note'>This is the single-mode solution. For a general top boundary \\(f(x)\\), expand \\(f\\) in a sine series and superpose modes.</div>"
            "</div></div>"
        )

        return {
            "problem_s": ("Solve \\( " + pde_ltx + " \\) on \\(0 \\lt x \\lt " + Ltx(L) + ",\\;0 \\lt y \\lt " + Ltx(H) + "\\) with \\( " + bc_ltx + " \\)").strip(),
            "soln_s": soln_s.strip(),
            "answer_s": ("\\( " + answer_ltx + " \\)").strip(),
        }

    if kind == "laplace_polar_radial":
        # Radially symmetric Laplace: u_rr + (1/r) u_r = 0
        ur = U(r)
        pde_ltx = "u_{rr} + \\frac{1}{r}u_r = 0\\quad (r>0)"

        A = sp.Symbol("A")
        B = sp.Symbol("B")
        sol_expr = A * sp.log(r) + B

        answer_ltx = Ltx(ur) + " = " + Ltx(sol_expr)

        # NOTE: no f-strings here -> no brace parsing issues
        soln_s = (
            "<div class='wrap'><div class='card'>"
            "<div class='badge'>Elliptic PDE (Laplace, Radial Symmetry in Polar Coordinates)<br/></div>"
            "\\(\\displaystyle \\begin{aligned}"
            "& \\displaystyle " + pde_ltx + " \\\\[8pt]"
            "& \\displaystyle \\Rightarrow \\text{Multiply by } r:\\; r u_{rr} + u_r = 0 \\\\"
            "& \\displaystyle \\Rightarrow \\frac{d}{dr}(r u_r)=0 \\\\"
            "& \\displaystyle \\Rightarrow r u_r = A \\Rightarrow u_r = \\frac{A}{r} \\\\"
            "& \\displaystyle \\Rightarrow u(r) = A\\ln r + B \\\\[4pt]"
            "\\end{aligned}\\)"
            "<div class='note'>If the domain includes \\(r=0\\) and \\(u\\) must remain bounded, then \\(A=0\\) so \\(u=B\\).</div>"
            "</div></div>"
        )

        return {
            "problem_s": ("Solve \\( " + pde_ltx + " \\) assuming radial symmetry.").strip(),
            "soln_s": soln_s.strip(),
            "answer_s": ("\\( " + answer_ltx + " \\)").strip(),
        }

    raise ValueError(
        "Unknown kind={!r}. Expected one of: laplace_rectangle_single_sine_top, laplace_polar_radial.".format(kind)
    )