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


@dataclass(frozen=True)
class Hyperbola:
    """
    Standard hyperbola:

        x^2 / a^2 - y^2 / b^2 = 1

    All formulas assume this standard form, centred at origin,
    transverse axis along the x-axis.
    """
    a: float
    b: float

    # ---------- basic geometry ----------

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

            x = a cosh(t)
            y = b sinh(t)
        """
        return Point(self.a * cosh(t), self.b * sinh(t))

    def contains(self, p: Point, *, tol: float = 1e-9) -> bool:
        """
        Check if a point lies on the hyperbola:

            x^2/a^2 - y^2/b^2 = 1
        """
        val = (p.x * p.x) / (self.a * self.a) - (p.y * p.y) / (self.b * self.b)
        return abs(val - 1.0) <= tol

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

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

        From implicit differentiation:

            x^2/a^2 - y^2/b^2 = 1
         =>  2x/a^2 - 2y/(b^2) * dy/dx = 0
         =>  dy/dx = (b^2 x) / (a^2 y)
        """
        return (self.b * self.b * p.x) / (self.a * self.a * p.y)

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

    # ---------- eccentricity, foci, directrices ----------

    @property
    def eccentricity(self) -> float:
        """
        Eccentricity of the hyperbola:

            e = sqrt(1 + b^2 / a^2)
        """
        return sqrt(1.0 + (self.b * self.b) / (self.a * self.a))

    @property
    def focus_points(self) -> tuple[Point, Point]:
        """
        Foci of the hyperbola:

            F1 = (ae, 0)
            F2 = (-ae, 0)
        """
        e = self.eccentricity
        return (Point(self.a * e, 0.0), Point(-self.a * e, 0.0))

    @property
    def directrices(self) -> tuple[float, float]:
        """
        x-coordinates of the two directrices:

            x =  a/e  and  x = -a/e

        Returns the pair (a/e, -a/e). You can interpret them as lines:
            x = directrices[0], x = directrices[1]
        """
        e = self.eccentricity
        return (self.a / e, -self.a / e)

    # ---------- asymptotes ----------

    @property
    def asymptote_slopes(self) -> tuple[float, float]:
        """
        Slopes of asymptotes:

            y = (b/a)x  and  y = -(b/a)x
        """
        m = self.b / self.a
        return (m, -m)

    def asymptotes(self) -> tuple[Line, Line]:
        """
        Asymptotes as Line objects:

            y = (b/a)x
            y = -(b/a)x
        """
        m1, m2 = self.asymptote_slopes
        origin = Point(0.0, 0.0)
        return (
            Line.from_point_slope(origin, m1),
            Line.from_point_slope(origin, m2),
        )

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

    def equation(self) -> str:
        """
        Returns the standard equation as a human-readable string:

            (x^2)/(a^2) - (y^2)/(b^2) = 1
        """
        return f"(x^2)/({self.a**2}) - (y^2)/({self.b**2}) = 1"

    def general_equation_coeffs(self) -> tuple[float, float, float]:
        """
        Returns coefficients (A, B, C) of the general form:

            A x^2 + B y^2 = C

        For x^2/a^2 - y^2/b^2 = 1 we get:

            b^2 x^2 - a^2 y^2 = a^2 b^2
        """
        A = self.b * self.b
        B = -self.a * self.a
        C = self.a * self.a * self.b * self.b
        return (A, B, C)

    # ---------- representation ----------

    def __repr__(self) -> str:
        return (
            f"Hyperbola(a={self.a}, b={self.b}, "
            f"e={self.eccentricity:.4f})"
        )
