"""
plotting.py — Visualization helpers for coordgeo using matplotlib.

This module provides simple functions to draw:
- Points
- Lines
- Circles
- Triangles
- Parabolas
- Ellipses
- Hyperbolas

Usage pattern:

    from coordgeo import Point, Line, Circle, Triangle, Parabola, Ellipse, Hyperbola
    from coordgeo.plotting import create_axes, plot_point, plot_line, plot_circle, show

"""

from __future__ import annotations
from typing import Iterable, Tuple

import matplotlib.pyplot as plt
import numpy as np

from .point import Point
from .line import Line
from .circle import Circle
from .triangle import Triangle
from .parabola import Parabola
from .ellipse import Ellipse
from .hyperbola import Hyperbola


# ---------- global helpers ----------

def create_axes(
    xlim: Tuple[float, float] = (-10, 10),
    ylim: Tuple[float, float] = (-10, 10),
    grid: bool = True,
):
    """
    Create a matplotlib figure + axes with:
    - equal aspect ratio
    - x/y axes drawn
    - optional grid
    """
    fig, ax = plt.subplots()
    ax.set_aspect("equal", "box")

    ax.set_xlim(*xlim)
    ax.set_ylim(*ylim)

    # draw coordinate axes
    ax.axhline(0, linewidth=0.8)
    ax.axvline(0, linewidth=0.8)

    if grid:
        ax.grid(True, linestyle="--", linewidth=0.5, alpha=0.5)

    ax.set_xlabel("x")
    ax.set_ylabel("y")
    return fig, ax


def show():
    """Wrapper for plt.show(), just for consistency."""
    plt.show()


# ---------- points & collections ----------

def plot_point(ax, p: Point, **kwargs):
    """Plot a single point."""
    ax.scatter([p.x], [p.y], **kwargs)


def plot_points(ax, points: Iterable[Point], **kwargs):
    """Plot multiple points."""
    xs = [p.x for p in points]
    ys = [p.y for p in points]
    ax.scatter(xs, ys, **kwargs)


# ---------- line ----------

def plot_line(ax, line: Line, xlim: Tuple[float, float] | None = None, **kwargs):
    """
    Plot a line over the current axis range or over given xlim.

    Line is given in general form: a x + b y + c = 0
    """
    a, b, c = line.a, line.b, line.c

    if xlim is None:
        x_min, x_max = ax.get_xlim()
    else:
        x_min, x_max = xlim

    xs = np.linspace(x_min, x_max, 400)

    if abs(b) > 1e-12:
        # y = (-a x - c) / b
        ys = (-a * xs - c) / b
        ax.plot(xs, ys, **kwargs)
    else:
        # vertical line: x = -c / a
        x0 = -c / a
        ys = np.linspace(*ax.get_ylim(), 400)
        xs = np.full_like(ys, x0)
        ax.plot(xs, ys, **kwargs)


# ---------- circle ----------

def plot_circle(ax, circle: Circle, num: int = 400, **kwargs):
    """Plot a circle."""
    theta = np.linspace(0, 2 * np.pi, num)
    cx, cy = circle.center.x, circle.center.y
    r = circle.radius
    xs = cx + r * np.cos(theta)
    ys = cy + r * np.sin(theta)
    ax.plot(xs, ys, **kwargs)


# ---------- triangle ----------

def plot_triangle(ax, tri: Triangle, close: bool = True, **kwargs):
    """Plot the triangle edges."""
    pts = [tri.A, tri.B, tri.C]
    if close:
        pts.append(tri.A)

    xs = [p.x for p in pts]
    ys = [p.y for p in pts]
    ax.plot(xs, ys, **kwargs)


# ---------- parabola ----------

def plot_parabola(ax, parabola: Parabola, t_range: Tuple[float, float] = (-5, 5), num: int = 400, **kwargs):
    """
    Plot a standard parabola using its parametric form and parameter t.

    For most visualizations, t in [-5, 5] is enough.
    """
    ts = np.linspace(t_range[0], t_range[1], num)
    xs = []
    ys = []
    for t in ts:
        p = parabola.point_from_parameter(t)
        xs.append(p.x)
        ys.append(p.y)
    ax.plot(xs, ys, **kwargs)


# ---------- ellipse ----------

def plot_ellipse(ax, ellipse: Ellipse, num: int = 400, **kwargs):
    """Plot an ellipse using (a cos θ, b sin θ) parametric form."""
    thetas = np.linspace(0, 2 * np.pi, num)
    xs = []
    ys = []
    for th in thetas:
        p = ellipse.point_from_angle(th)
        xs.append(p.x)
        ys.append(p.y)
    ax.plot(xs, ys, **kwargs)


# ---------- hyperbola ----------

def plot_hyperbola(
    ax,
    hyperbola: Hyperbola,
    t_range: Tuple[float, float] = (-2, 2),
    num: int = 400,
    branches: int = 2,
    **kwargs,
):
    """
    Plot a standard hyperbola using parametric form:

        x = a cosh t
        y = b sinh t

    branches:
      1 -> only right branch
      2 -> both right (x>0) and left (x<0) branches (mirror)
    """
    ts = np.linspace(t_range[0], t_range[1], num)

    # right branch (x > 0)
    xs = []
    ys = []
    for t in ts:
        p = hyperbola.point_from_parameter(t)
        xs.append(p.x)
        ys.append(p.y)
    ax.plot(xs, ys, **kwargs)

    if branches == 2:
        # left branch: reflect across y-axis
        xs2 = [-x for x in xs]
        ys2 = ys
        ax.plot(xs2, ys2, **kwargs)
