# Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# Copyright (c) 2026, Huawei Technologies Co., Ltd. All rights reserved.
#
# See LICENSE for license information.

from dataclasses import dataclass
from enum import Enum
from typing import NamedTuple, Optional

import torch


class FP8Format(NamedTuple):
    """FP8 format specification.

    Defines the properties of an FP8 format including range, exponent bits,
    mantissa bits, and PyTorch dtype.

    Attributes:
        max: Maximum representable value
        ebits: Number of exponent bits
        mbits: Number of mantissa bits
        dtype: PyTorch dtype for this format
    """

    max: float
    ebits: int
    mbits: int
    dtype: Optional[torch.dtype]

    @property
    def quant_dtype(self) -> torch.dtype:
        """Get quantization type.

        Returns:
            PyTorch dtype for quantization
        """
        if self.dtype is None:
            import torch_npu

            return torch_npu.hifloat8
        return self.dtype


class FormatEnum(Enum):
    """FP8 format enumeration.

    Defines standard FP8 formats with their specifications.
    """

    E4M3 = FP8Format(448, 4, 3, torch.float8_e4m3fn)
    E5M2 = FP8Format(57344, 5, 2, torch.float8_e5m2)
    HIF8 = FP8Format(57344, 5, 2, None)
    HIF8_224 = FP8Format(224, 5, 2, None)
    HIF8_15 = FP8Format(15, 5, 2, None)


class _FormatConfig(NamedTuple):
    """Format configuration for inputs, weights, and gradients.

    Attributes:
        fwd: Format for input/wegiht tensors
        bwd: Format for grad tensors
    """

    fwd: FormatEnum = FormatEnum.E4M3
    bwd: FormatEnum = FormatEnum.E4M3


class Format(Enum):
    """FP8 format configuration enumeration.

    Defines format configurations for different FP8 recipes.
    """

    E4M3 = _FormatConfig()
    E5M2 = _FormatConfig(fwd=FormatEnum.E5M2, bwd=FormatEnum.E5M2)
    HYBRID = _FormatConfig(bwd=FormatEnum.E5M2)
    HIF8 = _FormatConfig(fwd=FormatEnum.HIF8_15, bwd=FormatEnum.HIF8_224)

    @classmethod
    def from_str(cls, key: str) -> Optional["Format"]:
        """Get format from configuration key.

        Args:
            key: Configuration key string

        Returns:
            Format enum value or None
        """
        return getattr(cls, key.upper(), None)


class Recipe:
    """
    Base recipe class.
    """

    fp8_format: Format = Format.E4M3
    backward_override: Optional[str] = None  # TODO: os.getenv("NVTE_BACKWARD_OVERRIDE", None)

    @classmethod
    def nvfp4(cls):
        """Whether the given recipe is NVFP4 1D block scaling."""
        return False

    @classmethod
    def mxfp4(cls):
        """Whether the given recipe is MXFP4 block scaling."""
        return False

    @classmethod
    def mxfp8(cls):
        """Whether the given recipe is MXFP8 block scaling."""
        return False

    @classmethod
    def w4a8(cls):
        """Whether the given recipe is W4A8 mixed block scaling."""
        return False

    @classmethod
    def delayed(cls):
        """Whether the given recipe is delayed scaling."""
        return False

    @classmethod
    def float8_current_scaling(cls):
        """Whether the given recipe is (per-tensor) current scaling."""
        return False

    @classmethod
    def float8_per_tensor_scaling(cls):
        """Whether the given recipe is per-tensor scaling."""
        return False

    @classmethod
    def float8_block_scaling(cls):
        """Whether the given recipe is float8 blockwise scaling."""
        return False

    @classmethod
    def custom(cls):
        """Whether the given recipe is custom."""
        return False


@dataclass(frozen=True)
class MMParams:
    """Matrix multiplication options.

    Parameters
    ----------
    use_split_accumulator : bool, default = True
        Use FP8 fast accumulation on Hopper or Ada. For more details,
        see CUBLASLT_MATMUL_DESC_FAST_ACCUM option for cublasLtMatmul.
    """

    use_split_accumulator: bool = True


@dataclass(frozen=True)
class QParams:
    """Quantization parameters.
    power_2_scale: use power of 2 scale parameter
    amax_epsilon: optional minimum value of abs max
    random_hadamard_transform: whether to use random hadamard transform
    stochastic_rounding: whether to use stochastic rounding
    """

    power_2_scale: bool = False
    amax_epsilon: float = 0.0
    random_hadamard_transform: bool = False
    stochastic_rounding: bool = False
    fp4_2d_quantization: bool = False

    def __repr__(self) -> str:
        return (
            f"Qparams(\npower_2_scale={self.power_2_scale},\n"
            f"amax_epsilon={self.amax_epsilon},\n"
            f"random_hadamard_transform={self.random_hadamard_transform},\n"
            f"stochastic_rounding={self.stochastic_rounding},\n"
            f"fp4_2d_quantization={self.fp4_2d_quantization}\n)"
        )