from __future__ import annotations
from dataclasses import dataclass
from math import sqrt
from .point import Point
from .line import Line


@dataclass(frozen=True)
class Parabola:
    """
    Standard parabolas with vertex at origin.

    orientation:
        'right' : y^2 =  4*a*x
        'left'  : y^2 = -4*a*x
        'up'    : x^2 =  4*a*y
        'down'  : x^2 = -4*a*y
    """
    a: float
    orientation: str = "right"

    # ---------- core geometry ----------

    @property
    def vertex(self) -> Point:
        """Vertex of the parabola (always origin in these standard forms)."""
        return Point(0.0, 0.0)

    @property
    def focus(self) -> Point:
        """
        Focus of the parabola:
            right : (a, 0)
            left  : (-a, 0)
            up    : (0, a)
            down  : (0, -a)
        """
        if self.orientation == "right":
            return Point(self.a, 0.0)
        if self.orientation == "left":
            return Point(-self.a, 0.0)
        if self.orientation == "up":
            return Point(0.0, self.a)
        if self.orientation == "down":
            return Point(0.0, -self.a)
        raise ValueError("Invalid orientation")

    @property
    def directrix(self) -> Line:
        """
        Directrix line:
            right : x = -a
            left  : x =  a
            up    : y = -a
            down  : y =  a
        """
        if self.orientation == "right":   # x = -a
            return Line(1.0, 0.0, self.a)
        if self.orientation == "left":    # x =  a
            return Line(1.0, 0.0, -self.a)
        if self.orientation == "up":      # y = -a
            return Line(0.0, 1.0, self.a)
        if self.orientation == "down":    # y =  a
            return Line(0.0, 1.0, -self.a)
        raise ValueError("Invalid orientation")

    @property
    def axis(self) -> Line:
        """
        Axis line (through vertex & focus):
            right/left : y = 0
            up/down   : x = 0
        """
        if self.orientation in ("right", "left"):
            return Line(0.0, 1.0, 0.0)   # y = 0
        return Line(1.0, 0.0, 0.0)       # x = 0

    @property
    def latus_rectum_length(self) -> float:
        """Length of latus rectum = 4a."""
        return 4.0 * self.a

    @property
    def latus_rectum_endpoints(self) -> tuple[Point, Point]:
        """
        Endpoints of the latus rectum.
        Uses standard formulas:
            right : (a, ±2a)
            left  : (-a, ±2a)
            up    : (±2a, a)
            down  : (±2a, -a)
        """
        a = self.a
        if self.orientation == "right":
            return (Point(a, 2 * a), Point(a, -2 * a))
        if self.orientation == "left":
            return (Point(-a, 2 * a), Point(-a, -2 * a))
        if self.orientation == "up":
            return (Point(2 * a, a), Point(-2 * a, a))
        if self.orientation == "down":
            return (Point(2 * a, -a), Point(-2 * a, -a))
        raise ValueError("Invalid orientation")

    # ---------- parametric form ----------

    def point_from_parameter(self, t: float) -> Point:
        """
        Parametric coordinates using parameter t:

            right : (a t^2,  2 a t)
            left  : (-a t^2, 2 a t)
            up    : (2 a t,  a t^2)
            down  : (2 a t, -a t^2)
        """
        a = self.a
        if self.orientation == "right":
            return Point(a * t * t, 2 * a * t)
        if self.orientation == "left":
            return Point(-a * t * t, 2 * a * t)
        if self.orientation == "up":
            return Point(2 * a * t, a * t * t)
        if self.orientation == "down":
            return Point(2 * a * t, -a * t * t)
        raise ValueError("Invalid orientation")

    # ---------- membership test ----------

    def contains(self, p: Point, *, tol: float = 1e-9) -> bool:
        """Check if a point lies on the parabola."""
        x, y = p.x, p.y
        a = self.a
        if self.orientation == "right":
            val = y * y - 4 * a * x
        elif self.orientation == "left":
            val = y * y + 4 * a * x
        elif self.orientation == "up":
            val = x * x - 4 * a * y
        elif self.orientation == "down":
            val = x * x + 4 * a * y
        else:
            raise ValueError("Invalid orientation")
        return abs(val) <= tol

    # ---------- slopes, tangents & normals ----------

    def tangent_slope_at(self, p: Point) -> float:
        """
        Slope of tangent at a point on the parabola.

        For:
            y^2 = ±4ax : dy/dx = ±2a / y
            x^2 = ±4ay : dy/dx =  x / (±2a)
        """
        a = self.a
        x, y = p.x, p.y

        if self.orientation == "right":      # y^2 =  4ax
            return (2 * a) / y
        if self.orientation == "left":       # y^2 = -4ax
            return (-2 * a) / y
        if self.orientation == "up":         # x^2 =  4ay
            return x / (2 * a)
        if self.orientation == "down":       # x^2 = -4ay
            return -x / (2 * a)

        raise ValueError("Invalid orientation")

    def normal_slope_at(self, p: Point) -> float:
        """Slope of normal at a point on the parabola."""
        m_tan = self.tangent_slope_at(p)
        return -1.0 / m_tan

    def tangent_line_at(self, p: Point) -> Line:
        """Line object for tangent at point p (assumes p lies on parabola)."""
        m = self.tangent_slope_at(p)
        # handle vertical tangent (very rare in these standard forms, but safe)
        if abs(m) > 1e15:
            return Line(1.0, 0.0, -p.x)
        # y - y1 = m(x - x1) => m x - y + (y1 - m x1) = 0
        c = p.y - m * p.x
        return Line(m, -1.0, c)

    def normal_line_at(self, p: Point) -> Line:
        """Line object for normal at point p."""
        m = self.normal_slope_at(p)
        if abs(m) > 1e15:
            return Line(1.0, 0.0, -p.x)
        c = p.y - m * p.x
        return Line(m, -1.0, c)

    # ---------- equation helpers ----------

    def equation(self) -> str:
        """Return the standard equation as a string."""
        a = self.a
        if self.orientation == "right":
            return f"y^2 = 4*{a}*x"
        if self.orientation == "left":
            return f"y^2 = -4*{a}*x"
        if self.orientation == "up":
            return f"x^2 = 4*{a}*y"
        if self.orientation == "down":
            return f"x^2 = -4*{a}*y"
        raise ValueError("Invalid orientation")

    def __repr__(self) -> str:
        return f"Parabola(a={self.a}, orientation='{self.orientation}')"
