
import numpy as np

def _qft_matrix(n_qubits: int) -> np.ndarray:
    N   = 2 ** n_qubits
    w   = np.exp(2j * np.pi / N)
    idx = np.arange(N)
    return np.power(w, np.outer(idx, idx)) / np.sqrt(N)

def qft(n_qubits: int, state: np.ndarray = None) -> dict:
    """
    Forward Quantum Fourier Transform.
    If state is None, returns the QFT unitary matrix.
    """
    F = _qft_matrix(n_qubits)
    if state is None:
        return {"unitary": F, "n_qubits": n_qubits, "type": "QFT"}
    state   = np.array(state, dtype=complex)
    out     = F @ state
    return {
        "output_state": out,
        "input_state":  state,
        "unitary":      F,
        "n_qubits":     n_qubits,
        "type":         "QFT",
    }

def inverse_qft(n_qubits: int, state: np.ndarray = None) -> dict:
    """
    Inverse Quantum Fourier Transform.
    """
    F_dag = np.conj(_qft_matrix(n_qubits)).T
    if state is None:
        return {"unitary": F_dag, "n_qubits": n_qubits, "type": "IQFT"}
    state = np.array(state, dtype=complex)
    out   = F_dag @ state
    return {
        "output_state": out,
        "input_state":  state,
        "unitary":      F_dag,
        "n_qubits":     n_qubits,
        "type":         "IQFT",
    }
