package ai.nektron.grownet;

import ai.nektron.grownet.debug.Trace;

import java.util.*;

/**
 * A mixed‑type population (excitatory, inhibitory, modulatory) that shares a lateral bus.
 */
public class Layer {
    private final List<Neuron> neurons = new ArrayList<>();
    private final LateralBus bus = new LateralBus();
    private final Random rng = new Random(1234);
    private Region region = null;      // backref for growth auto-wiring
    private int neuronLimit = -1;

    public Layer(int excitatoryCount, int inhibitoryCount, int modulatoryCount) {
        // Default slot policy for this demo layer.
        SlotConfig cfg = SlotConfig.fixed(10.0);   // 10% Δ bins
        int slotLimit = -1;                        // unlimited

        for (int i = 0; i < excitatoryCount; ++i) {
            Neuron neuron = new ExcitatoryNeuron("E" + i, bus, cfg, slotLimit);
            neuron.owner = this;
            neurons.add(neuron);
        }
        for (int i = 0; i < inhibitoryCount; ++i) {
            Neuron neuron = new InhibitoryNeuron("I" + i, bus, cfg, slotLimit);
            neuron.owner = this;
            neurons.add(neuron);
        }
        for (int i = 0; i < modulatoryCount; ++i) {
            Neuron neuron = new ModulatoryNeuron("M" + i, bus, cfg, slotLimit);
            neuron.owner = this;
            neurons.add(neuron);
        }
    }

    // --------------------------------------------- accessors ------------------------------------------------

    public List<Neuron> getNeurons() { return neurons; }
    public LateralBus getBus() { return bus; }
    public void setRegion(Region r) { this.region = r; }
    public void setNeuronLimit(int limit) { this.neuronLimit = limit; }

    // ------------------------------------------ intra‑layer wiring ------------------------------------------

    /** Layer‑local fanout (demo‑level). */
    public void wireRandomFeedforward(double probability) {
        for (Neuron srcNeuron : neurons) {
            for (Neuron dstNeuron : neurons) {
                if (srcNeuron == dstNeuron) continue;
                if (rng.nextDouble() < probability) {
                    srcNeuron.connect(dstNeuron, /*feedback=*/false);
                }
            }
        }
    }

    public void wireRandomFeedback(double probability) {
        for (Neuron srcNeuron : neurons) {
            for (Neuron dstNeuron : neurons) {
                if (srcNeuron == dstNeuron) continue;
                if (rng.nextDouble() < probability) {
                    srcNeuron.connect(dstNeuron, /*feedback=*/true);
                }
            }
        }
    }

    // ------------------------------------------------ forward ------------------------------------------------

    /** Drive all neurons with a scalar value for this tick. */
    public void forward(double value) {
        List<Neuron> neuronsSnapshot = new ArrayList<>(neurons);
        for (Neuron neuron : neuronsSnapshot) {
            boolean fired = neuron.onInput(value);
            if (fired) {
                neuron.onOutput(value);
            }
        }
    }

    /**
     * Optional helper used by shape‑aware layers (e.g., OutputLayer2D).
     * Default behaviour: route {@code value} to the N‑th neuron and apply the unified onInput/onOutput contract.
     * NOTE: The inter‑layer wiring (`Tract`) does NOT call this; it delivers directly to target neurons.
     * This method exists so specialized layers can @Override it for local bookkeeping.
     */
    public void propagateFrom(int sourceIndex, double value) {
        if (sourceIndex < 0 || sourceIndex >= neurons.size()) return;
        Neuron neuron = neurons.get(sourceIndex);
        boolean fired = neuron.onInput(value);
        if (fired) neuron.onOutput(value);
    }

    /** End‑of‑tick housekeeping: decay inhibition/modulation back toward neutral. */
    public void endTick() {
        bus.decay();
    }

    // ---- growth helper: add neuron like seed and autowire via Region
    public int tryGrowNeuron(Neuron seed) {
        if (neuronLimit >= 0 && neurons.size() >= neuronLimit) {
            SlotConfig seedSlotConfig = seed == null ? null : seed.getSlotConfig();
            if (region != null && seedSlotConfig != null && seedSlotConfig.isLayerGrowthEnabled()) {
                region.requestLayerGrowth(this);
            }
            return -1;
        }
        SlotConfig slotConfigCopy = (seed != null && seed.getSlotConfig() != null)
                ? seed.getSlotConfig().copy()
                : SlotConfig.fixed(10.0);
        int nextNeuronIndex = neurons.size();
        int seedSlotLimit = seed == null ? -1 : seed.getSlotLimit();
        Neuron newNeuron = (seed instanceof ModulatoryNeuron)
                ? new ModulatoryNeuron("M" + nextNeuronIndex, bus, slotConfigCopy, seedSlotLimit)
                : (seed instanceof InhibitoryNeuron)
                ? new InhibitoryNeuron("I" + nextNeuronIndex, bus, slotConfigCopy, seedSlotLimit)
                : new ExcitatoryNeuron("E" + nextNeuronIndex, bus, slotConfigCopy, seedSlotLimit);
        if (seed instanceof InhibitoryNeuron && newNeuron instanceof InhibitoryNeuron) {
            ((InhibitoryNeuron) newNeuron).setGamma(((InhibitoryNeuron) seed).getGamma());
        }
        if (seed instanceof ModulatoryNeuron && newNeuron instanceof ModulatoryNeuron) {
            ((ModulatoryNeuron) newNeuron).setKappa(((ModulatoryNeuron) seed).getKappa());
        }
        newNeuron.owner = this;
        neurons.add(newNeuron);
        Map<String, Object> eventFields = new LinkedHashMap<>();
        Trace.addLayerFields(eventFields, this);
        eventFields.put("neuron_id", newNeuron.getId());
        eventFields.put("created_neuron", true);
        eventFields.put("reason", "fallback_streak");
        Trace.emit("growth.neuron.create", eventFields);
        if (region != null) region.autowireNewNeuron(this, nextNeuronIndex);
        return nextNeuronIndex;
    }
}
