"""
Backend の共通インターフェース。

設計方針
--------
- backend と device を分離する。
- xp は基本的な vector/tensor 配列演算に使う。
- FFT、転送、同期、device 情報など、実装差が出やすい部分は Backend が吸収する。
- Backend インスタンス生成後に backend/device を書き換えない。
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any

import numpy as np


class Backend(ABC):
    """数値計算 backend の抽象基底クラス。"""

    name: str = "abstract"

    @property
    @abstractmethod
    def xp(self):
        """NumPy-like array namespace を返す。"""
        raise NotImplementedError

    @property
    def is_accelerator(self) -> bool:
        """GPU/accelerator backend なら True。"""
        return False

    @abstractmethod
    def asarray(self, x: Any, dtype=None, order=None):
        """入力を backend 側の配列にする。"""
        raise NotImplementedError

    @abstractmethod
    def to_numpy(self, x) -> np.ndarray:
        """backend 配列を NumPy ndarray に戻す。"""
        raise NotImplementedError

    @abstractmethod
    def synchronize(self) -> None:
        """backend 上の非同期処理の完了を待つ。CPU backend では no-op。"""
        raise NotImplementedError

    @abstractmethod
    def fftn(self, x, axes=None):
        """N 次元 FFT。"""
        raise NotImplementedError

    @abstractmethod
    def ifftn(self, x, axes=None):
        """N 次元 inverse FFT。"""
        raise NotImplementedError

    def fft(self, x, axis=-1):
        """1 次元 FFT。既定実装は fftn() に委譲する。"""
        return self.fftn(x, axes=(axis,))

    def ifft(self, x, axis=-1):
        """1 次元 inverse FFT。既定実装は ifftn() に委譲する。"""
        return self.ifftn(x, axes=(axis,))

    def fft2(self, x, axes=(-2, -1)):
        """2 次元 FFT。"""
        return self.fftn(x, axes=axes)

    def ifft2(self, x, axes=(-2, -1)):
        """2 次元 inverse FFT。"""
        return self.ifftn(x, axes=axes)

    @abstractmethod
    def get_device_name(self) -> str:
        """実際に計算を行う device 名を返す。"""
        raise NotImplementedError

    def get_device_info(self) -> dict[str, object]:
        """ログ出力用の基本 device 情報。backend ごとに拡張してよい。"""
        return {
            "backend": self.name,
            "device": self.get_device_name(),
            "is_accelerator": self.is_accelerator,
        }

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}("
            f"backend={self.name!r}, device={self.get_device_name()!r})"
        )
