"""Command-line interface for panel-bie."""

from __future__ import annotations

import argparse
import json
import sys

import numpy as np


def _compute_beta(p: float, user_beta: float | None = None) -> float:
    """Compute appropriate beta for panel grading.

    Beta must be < n where p = 2n. For stability, use min(user_beta, n-0.5).
    """
    n = p / 2
    max_beta = n - 0.5
    if user_beta is not None:
        return min(user_beta, max_beta)
    # Default: use 1.5 or max_beta, whichever is smaller
    return min(1.5, max_beta)


def cmd_solve(args):
    """Solve a Dirichlet problem."""
    from panel_bie import DoubleLayerLaplace

    # Load geometry
    try:
        from superellipse import Superellipse
        with open(args.geometry) as f:
            data = json.load(f)
        p = data.get("p", 4.0)
        curve = Superellipse(
            a=data.get("a", 1.0),
            b=data.get("b", 1.0),
            p=p,
            q=data.get("q"),
        )
        beta = _compute_beta(p, getattr(args, "beta", None))
        disc = curve.panel_discretization(
            panels_per_quadrant=args.panels,
            nodes_per_panel=args.nodes,
            beta=beta,
        )
    except ImportError:
        print("Error: superellipse package required for geometry loading")
        sys.exit(1)

    # Parse boundary condition
    # Simple expression evaluation (for demo; production should use safer parsing)
    def g(pt):
        x, y = pt[0], pt[1]
        return eval(args.bc)  # noqa: S307

    # Solve
    solver = DoubleLayerLaplace(disc)
    mu = solver.solve(g)

    # Evaluate
    eval_pt = [float(x) for x in args.eval.split(",")]
    u = solver.evaluate(mu, eval_pt)

    print(f"u({eval_pt[0]}, {eval_pt[1]}) = {u:.10f}")


def cmd_condition(args):
    """Compute condition number of the discretized operator."""
    from panel_bie import DoubleLayerLaplace

    try:
        from superellipse import Superellipse
        curve = Superellipse(a=args.a, b=args.b, p=args.p)
        beta = _compute_beta(args.p, getattr(args, "beta", None))
        disc = curve.panel_discretization(
            panels_per_quadrant=args.panels,
            nodes_per_panel=args.nodes,
            beta=beta,
        )
    except ImportError:
        print("Error: superellipse package required")
        sys.exit(1)

    solver = DoubleLayerLaplace(disc)
    A = solver.assemble()

    cond = np.linalg.cond(A)
    print(f"Discretization: {disc.n_nodes} nodes (beta={beta:.2f})")
    print(f"Condition number: {cond:.6e}")


def cmd_convergence(args):
    """Test convergence with increasing panel count."""
    from panel_bie import DoubleLayerLaplace

    try:
        from superellipse import Superellipse
        curve = Superellipse(a=args.a, b=args.b, p=args.p)
    except ImportError:
        print("Error: superellipse package required")
        sys.exit(1)

    beta = _compute_beta(args.p)

    # Reference value with high resolution
    disc_ref = curve.panel_discretization(
        panels_per_quadrant=16, nodes_per_panel=16, beta=beta
    )
    solver_ref = DoubleLayerLaplace(disc_ref)

    def g(pt):
        return pt[0] ** 2 - pt[1] ** 2  # Harmonic

    mu_ref = solver_ref.solve(g)
    u_ref = solver_ref.evaluate(mu_ref, [0.0, 0.0])

    print(f"Superellipse p={args.p}, beta={beta:.2f}")
    print(f"Reference value: u(0,0) = {u_ref:.12f}")
    print()
    print("Panels/quad  Nodes   u(0,0)           Error")
    print("-" * 50)

    for ppq in [2, 4, 8, 12]:
        disc = curve.panel_discretization(
            panels_per_quadrant=ppq, nodes_per_panel=16, beta=beta
        )
        solver = DoubleLayerLaplace(disc)
        mu = solver.solve(g)
        u = solver.evaluate(mu, [0.0, 0.0])
        err = abs(u - u_ref)
        print(f"{ppq:5d}        {disc.n_nodes:5d}   {u:.12f}   {err:.2e}")


def main():
    parser = argparse.ArgumentParser(
        prog="panel-bie",
        description="Panel-based boundary integral equation solvers",
    )
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # solve command
    p_solve = subparsers.add_parser("solve", help="Solve Dirichlet problem")
    p_solve.add_argument("--geometry", "-g", required=True, help="Geometry JSON file")
    p_solve.add_argument("--bc", required=True, help="Boundary condition expression (Python)")
    p_solve.add_argument("--eval", required=True, help="Evaluation point (x,y)")
    p_solve.add_argument("--panels", type=int, default=4, help="Panels per quadrant")
    p_solve.add_argument("--nodes", type=int, default=16, help="Nodes per panel")
    p_solve.add_argument("--beta", type=float, help="Panel grading parameter (default: auto)")
    p_solve.set_defaults(func=cmd_solve)

    # condition command
    p_cond = subparsers.add_parser("condition", help="Compute condition number")
    p_cond.add_argument("--a", type=float, default=1.0)
    p_cond.add_argument("--b", type=float, default=1.0)
    p_cond.add_argument("--p", type=float, default=4.0)
    p_cond.add_argument("--panels", type=int, default=4)
    p_cond.add_argument("--nodes", type=int, default=16)
    p_cond.add_argument("--beta", type=float, help="Panel grading parameter (default: auto)")
    p_cond.set_defaults(func=cmd_condition)

    # convergence command
    p_conv = subparsers.add_parser("convergence", help="Test convergence")
    p_conv.add_argument("--a", type=float, default=1.0)
    p_conv.add_argument("--b", type=float, default=1.0)
    p_conv.add_argument("--p", type=float, default=4.0)
    p_conv.set_defaults(func=cmd_convergence)

    args = parser.parse_args()
    if args.command is None:
        parser.print_help()
        sys.exit(1)

    args.func(args)


if __name__ == "__main__":
    main()
