"""
Sine wave plot factory for the mikeshello library.

This module defines the SinePlot class, responsible for generating a clean,
mathematically precise sine-wave curve of the form:

    y(x) = A * sin(omega * x + phase) + C

All parameters are given strictly in radians and natural units.
The plot itself contains no styling (color, linewidth, etc.).
"""

from __future__ import annotations
from typing import Tuple, Optional
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.axes import Axes

from ._base import _PlotBase

PI2 = 2 * np.pi


class SinePlot(_PlotBase):
    """
    Internal class for generating sine wave plots.

    Generates a sine function of the form:
        y(x) = A * sin(omega * x + phase) + C

    Parameters:
        amplitude: Amplitude A of the sine wave.
        angular_frequency: Angular frequency omega (rad/unit).
        phase: Phase shift phi (rad).
        vertical_shift: Vertical offset C.
        domain: Explicit domain as (xmin, xmax) tuple. If given, overrides periods.
        periods: Number of periods to plot. Ignored if angular_frequency is 0.
        points: Number of sample points for the plot.

    Raises:
        ValueError: If any of the following conditions are met:
            - points is not positive
            - domain is specified with xmin >= xmax
            - periods is not positive
            - Both domain and periods are specified
            - periods is specified when angular_frequency is 0
    """

    def __init__(
            self,
            *,
            amplitude: float,
            angular_frequency: float,
            phase: float,
            vertical_shift: float,
            domain: Optional[Tuple[float, float]],
            periods: Optional[float],
            points: int,
    ) -> None:
        if points <= 0:
            raise ValueError(f"points must be positive, got {points}.")

        if domain is not None:
            xmin, xmax = domain
            if xmin >= xmax:
                raise ValueError(
                    f"domain must have xmin < xmax, got ({xmin}, {xmax})."
                )

        if periods is not None:
            if periods <= 0:
                raise ValueError(f"periods must be positive, got {periods}.")
            if angular_frequency == 0:
                raise ValueError(
                    "Cannot specify periods when angular_frequency is 0."
                )

        if domain is not None and periods is not None:
            raise ValueError("Specify either domain or periods, not both.")

        self.amplitude = amplitude
        self.angular_frequency = angular_frequency
        self.phase = phase
        self.vertical_shift = vertical_shift
        self.domain = domain
        self.periods = periods
        self.points = points

    def _resolve_domain(self) -> Tuple[float, float]:
        """Resolve the plotting domain from domain or periods parameter."""
        omega = self.angular_frequency
        domain = self.domain
        periods = self.periods

        # Explicit domain takes precedence
        if domain is not None:
            xmin, xmax = domain
            return float(xmin), float(xmax)

        # Use periods if specified
        if periods is not None:
            return 0.0, float(periods * PI2 / omega)

        # Defaults: one period, or [0, 1] for constant function
        if omega == 0:
            return 0.0, 1.0
        return 0.0, PI2 / omega

    def create(self) -> Tuple[Figure, Axes]:
        """
        Create and return a sine wave plot.

        Returns:
            A tuple of (Figure, Axes) containing the plot.
        """
        xmin, xmax = self._resolve_domain()
        x = np.linspace(xmin, xmax, self.points)

        y = (
                self.amplitude
                * np.sin(self.angular_frequency * x + self.phase)
                + self.vertical_shift
        )

        fig, ax = plt.subplots()
        ax.plot(x, y)

        self.fig = fig
        self.ax = ax
        return fig, ax


def sine(
        *,
        amplitude: float = 1.0,
        angular_frequency: float = 1.0,
        phase: float = 0.0,
        vertical_shift: float = 0.0,
        domain: Optional[Tuple[float, float]] = None,
        periods: Optional[float] = None,
        points: int = 500,
) -> Tuple[Figure, Axes]:
    """
    Generates a mathematically precise sine-wave curve of the form:
        y(x) = A * sin(omega * x + phase) + C

    All parameters are given strictly in radians and natural units.
    The plot contains no styling - users should customize the resulting
    Matplotlib Axes as needed.

    Args:
        amplitude: Amplitude A of the sine wave. Defaults to 1.0.
        angular_frequency: Angular frequency omega (rad/unit). Defaults to 1.0.
        phase: Phase shift phi (rad). Defaults to 0.0.
        vertical_shift: Vertical offset C. Defaults to 0.0.
        domain: Explicit domain as (xmin, xmax) tuple. If given, overrides
            periods. Defaults to None.
        periods: Number of periods to plot. Ignored if angular_frequency is 0.
            Defaults to None (plots one period).
        points: Number of sample points for the plot. Defaults to 500.

    Returns:
        A tuple of (Figure, Axes) containing the plot.

    Raises:
        ValueError: If any of the following conditions are met:
            - points is not positive
            - domain is specified with xmin >= xmax
            - periods is not positive
            - Both domain and periods are specified
            - periods is specified when angular_frequency is 0

    Example:
        >>> fig, ax = sine_plot(amplitude=2.0, periods=2)
        >>> ax.set_xlabel('x')
        >>> ax.set_ylabel('y')
        >>> plt.show()
    """
    return SinePlot(
        amplitude=amplitude,
        angular_frequency=angular_frequency,
        phase=phase,
        vertical_shift=vertical_shift,
        domain=domain,
        periods=periods,
        points=points,
    ).create()
