
import numpy as np

def basis_encoding(data: list, n_qubits: int = None) -> dict:
    """
    Basis encoding: maps integer index to computational basis state.
    data: list of integers (each < 2^n_qubits).
    """
    max_val  = max(data)
    n_qubits = n_qubits or int(np.ceil(np.log2(max_val + 2)))
    dim      = 2 ** n_qubits
    states   = []
    for val in data:
        s      = np.zeros(dim, dtype=complex)
        s[int(val) % dim] = 1.0
        states.append(s)
    return {
        "states":   states,
        "n_qubits": n_qubits,
        "encoding": "basis",
        "data":     data,
    }

def amplitude_encoding(data: np.ndarray) -> dict:
    """
    Amplitude encoding: normalizes classical vector into quantum amplitudes.
    len(data) must be a power of 2.
    """
    data   = np.array(data, dtype=complex)
    norm   = np.linalg.norm(data)
    if norm < 1e-12:
        raise ValueError("Data vector has zero norm — cannot amplitude encode.")
    state  = data / norm
    n_qubits = int(np.ceil(np.log2(len(state))))
    dim      = 2 ** n_qubits
    if len(state) < dim:
        state = np.pad(state, (0, dim - len(state)))
        state = state / np.linalg.norm(state)
    return {
        "state":    state,
        "n_qubits": n_qubits,
        "encoding": "amplitude",
        "norm_factor": float(norm),
    }

def angle_encoding(data: np.ndarray, rotation: str = "ry") -> dict:
    """
    Angle encoding: encodes each feature as a rotation angle on a qubit.
    Returns the resulting product state vector.
    """
    data     = np.array(data, dtype=float)
    n_qubits = len(data)
    dim      = 2 ** n_qubits
    state    = np.array([1.0 + 0j])
    for theta in data:
        if rotation == "ry":
            gate = np.array([[np.cos(theta/2), -np.sin(theta/2)],
                             [np.sin(theta/2),  np.cos(theta/2)]])
        elif rotation == "rx":
            gate = np.array([[np.cos(theta/2), -1j*np.sin(theta/2)],
                             [-1j*np.sin(theta/2), np.cos(theta/2)]])
        else:  # rz
            gate = np.array([[np.exp(-1j*theta/2), 0],
                             [0, np.exp(1j*theta/2)]])
        qubit_state = gate @ np.array([1.0, 0.0])
        state       = np.kron(state, qubit_state)
    return {
        "state":    state,
        "n_qubits": n_qubits,
        "encoding": f"angle ({rotation})",
        "data":     data.tolist(),
    }
