# SPDX-License-Identifier: AGPL-3.0-or-later | Commercial license available
# © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
# © Code 2020–2026 Miroslav Šotek. All rights reserved.
# ORCID: 0009-0009-3560-0851
# Contact: www.anulum.li | protoscience@anulum.li
# SC-NeuroCore — SCPN L1 Quantum Biological Layer (Stochastic Implementation)

"""SCPN L1: Quantum Biological Layer (Stochastic Implementation)."""

from __future__ import annotations

from dataclasses import dataclass
import logging
from typing import Any, Optional

import numpy as np

try:
    from sc_neurocore.quantum.hybrid import QuantumStochasticLayer
    from sc_neurocore.quantum.hardware_bridge import QuantumHardwareLayer

    _HAS_QUANTUM = True
except ImportError:
    _HAS_QUANTUM = False

logger = logging.getLogger(__name__)


@dataclass
class L1_StochasticParameters:
    """Parameters for the Stochastic L1 Layer."""

    n_qubits: int = 1000  # Number of simulated microtubules/dipoles
    bitstream_length: int = 1024

    # Biological Enhancements (from Legacy RAG)
    F_non_Markov: float = 1e4  # Protection factor
    temperature: float = 310.0  # human body temperature (K)

    # Quantum Dynamics
    coupling_strength: float = 0.1
    decoherence_rate: float = 0.05

    # Execution Backend
    # "simulated", "qiskit.aer_simulator", "pennylane.default.qubit"
    backend: str = "simulated"


class L1_QuantumLayer:
    """
    Stochastic implementation of the Quantum Cellular Field.
    """

    def __init__(self, params: Optional[L1_StochasticParameters] = None) -> None:
        self.params = params or L1_StochasticParameters()

        # The Core Engine
        if self.params.backend == "simulated":
            self.quantum_core: Any = QuantumStochasticLayer(
                n_qubits=self.params.n_qubits, length=self.params.bitstream_length
            )
        else:
            self.quantum_core = QuantumHardwareLayer(
                n_qubits=self.params.n_qubits,
                length=self.params.bitstream_length,
                backend_type=self.params.backend,
            )

        # State: Coherence represented as probabilities (0.0 to 1.0)
        # In SC, this will be encoded into bitstreams.
        # Initialize with max coherence (pure state)
        self.coherence_probs = np.ones(self.params.n_qubits) * 0.95

        # History for non-Markovian effects
        self.history: list[float] = []

    def step(
        self, dt: float, external_field: Optional[np.ndarray[Any, Any]] = None
    ) -> np.ndarray[Any, Any]:
        """
        Advance the layer by one time step.

        Args:
            dt: Time step in seconds.
            external_field: Optional coupling input from other layers (normalized 0-1).

        Returns:
            output_bitstreams: The stochastic state of the field.
        """
        # 1. Apply Decoherence (Classical Decay)
        # Adjusted by Non-Markovian factor
        effective_decay = self.params.decoherence_rate * dt / np.log10(self.params.F_non_Markov)
        self.coherence_probs *= 1.0 - effective_decay

        # 2. Apply External Coupling (e.g. from L2 Neurochemical)
        if external_field is not None:
            # Mix the field: coherence is modulated by external input
            # Simple convex combination for now
            self.coherence_probs = (
                1 - self.params.coupling_strength
            ) * self.coherence_probs + self.params.coupling_strength * external_field

        # 3. Quantum Rotation via Stochastic Core
        # The core takes the probabilities, rotates them (simulating evolution),
        # and returns collapsed bitstreams.
        # We assume the 'probability' maps to the quantum phase/amplitude.

        # Generate input bitstreams from current probabilities
        # (This is a simplified interface; ideally we keep state in bitstreams)
        # Using a simple generator for now:
        rands = np.random.random((self.params.n_qubits, self.params.bitstream_length))
        input_bits = (rands < self.coherence_probs[:, None]).astype(np.uint8)

        # Pass through Quantum Hybrid Layer
        output_bits = self.quantum_core.forward(input_bits)

        # 4. Update State from Measurement (Collapse/Update)
        # The output bits represent the measured state.
        # We update our internal probabilities based on the measurement (Bayesian update or similar)
        # For this simulation, we'll take the mean as the new base, but add some "Quantum Zeno" recovery
        measured_probs = np.mean(output_bits, axis=1)

        # "Zeno" effect: frequent measurement can freeze evolution or reset it.
        # Here we just blend it back.
        self.coherence_probs = 0.9 * self.coherence_probs + 0.1 * measured_probs

        res: np.ndarray[Any, Any] = output_bits
        return res

    def get_global_metric(self) -> float:
        """Return the global coherence metric (Phi-like)."""
        return float(np.mean(self.coherence_probs))
