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


@dataclass(frozen=True)
class Triangle:
    """
    Triangle defined by three non-collinear vertices A, B, C.
    Provides:
      - area
      - centroid
      - incenter
      - circumcenter
      - orthocenter
    """
    A: Point
    B: Point
    C: Point

    # ---------- basic helpers ----------

    @property
    def side_lengths(self) -> tuple[float, float, float]:
        """
        Returns (a, b, c) where:
          a = |BC| (opposite A)
          b = |CA| (opposite B)
          c = |AB| (opposite C)
        """
        a = self.B.distance_to(self.C)
        b = self.C.distance_to(self.A)
        c = self.A.distance_to(self.B)
        return (a, b, c)

    @property
    def perimeter(self) -> float:
        a, b, c = self.side_lengths
        return a + b + c

    @property
    def semi_perimeter(self) -> float:
        return self.perimeter / 2.0

    @property
    def is_degenerate(self) -> bool:
        """True if area is (numerically) zero."""
        return self.area <= 1e-12

    # ---------- area ----------

    @property
    def area(self) -> float:
        """
        Area using the shoelace (determinant) formula:

          Area = 1/2 | x1(y2 - y3) + x2(y3 - y1) + x3(y1 - y2) |
        """
        x1, y1 = self.A.x, self.A.y
        x2, y2 = self.B.x, self.B.y
        x3, y3 = self.C.x, self.C.y

        det = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)
        return abs(det) / 2.0

    # ---------- centroid ----------

    @property
    def centroid(self) -> Point:
        """
        Centroid G is intersection of medians:

          G = ((x1 + x2 + x3)/3, (y1 + y2 + y3)/3)
        """
        gx = (self.A.x + self.B.x + self.C.x) / 3.0
        gy = (self.A.y + self.B.y + self.C.y) / 3.0
        return Point(gx, gy)

    # ---------- incenter ----------

    @property
    def incenter(self) -> Point:
        """
        Incenter I is intersection of internal angle bisectors.

        Formula (a = |BC|, b = |CA|, c = |AB|):

          I = ( (a*x1 + b*x2 + c*x3) / (a + b + c),
                (a*y1 + b*y2 + c*y3) / (a + b + c) )
        """
        if self.is_degenerate:
            raise ValueError("Incenter undefined for degenerate triangle.")

        a, b, c = self.side_lengths
        x1, y1 = self.A.x, self.A.y
        x2, y2 = self.B.x, self.B.y
        x3, y3 = self.C.x, self.C.y
        p = a + b + c

        ix = (a * x1 + b * x2 + c * x3) / p
        iy = (a * y1 + b * y2 + c * y3) / p
        return Point(ix, iy)

    # ---------- circumcenter ----------

    @property
    def circumcenter(self) -> Point:
        """
        Circumcenter O is the center of the unique circle through A, B, C.
        Uses your Circle.from_three_points().
        """
        if self.is_degenerate:
            raise ValueError("Circumcenter undefined for degenerate triangle.")
        circle = Circle.from_three_points(self.A, self.B, self.C)
        return circle.center

    @property
    def circumcircle(self) -> Circle:
        """Return the circumcircle itself."""
        if self.is_degenerate:
            raise ValueError("Circumcircle undefined for degenerate triangle.")
        return Circle.from_three_points(self.A, self.B, self.C)

    # ---------- orthocenter ----------

    @property
    def orthocenter(self) -> Point:
        """
        Orthocenter H: intersection of the three altitudes.

        We construct two altitudes and intersect them:
          - altitude from A is perpendicular to BC
          - altitude from B is perpendicular to CA
        """
        if self.is_degenerate:
            raise ValueError("Orthocenter undefined for degenerate triangle.")

        # Side lines
        line_BC = Line.from_points(self.B, self.C)
        line_CA = Line.from_points(self.C, self.A)

        # Altitudes
        altitude_A = line_BC.perpendicular_through(self.A)
        altitude_B = line_CA.perpendicular_through(self.B)

        H = altitude_A.intersection(altitude_B)
        if H is None:
            # Should not happen for non-degenerate triangle, but safe check
            raise RuntimeError("Failed to compute orthocenter (altitudes parallel?).")
        return H

    # ---------- nice representation ----------

    def __repr__(self) -> str:
        return (
            f"Triangle(A={self.A}, B={self.B}, C={self.C}, "
            f"area={self.area:.4f})"
        )
