package ai.nektron.grownet.growth;

import ai.nektron.grownet.debug.Trace;
import ai.nektron.grownet.Layer;
import ai.nektron.grownet.Neuron;
import ai.nektron.grownet.Region;
import ai.nektron.grownet.SlotConfig;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Growth heuristics. Conservative and best‑effort: methods are no‑ops unless
 * the GrowthPolicy enables them and pressure/cooldowns are satisfied.
 */
public final class GrowthEngine {

    private GrowthEngine() { }

    /**
     * If average slots per neuron is high and we are below max layer count,
     * add a spillover layer using the policy sizing rule. Best‑effort only.
     */
    public static void maybeGrow(Region region, GrowthPolicy policy) {
        if (region == null || policy == null) return;
        if (!policy.isEnableLayerGrowth()) return;

        // Compute avg slots/neuron and optional OR-trigger (% neurons at cap & fallback)
        double totalSlots = 0.0;
        int totalNeurons = 0;
        int atCapAndFallback = 0;
        final List<Layer> layers = region.getLayers();
        for (Layer layer : layers) {
            for (Neuron neuron : layer.getNeurons()) {
                totalSlots += neuron.getSlots().size();
                totalNeurons += 1;
                SlotConfig slotConfig = neuron.getSlotConfig();
                int cap = neuron.getSlotLimit() >= 0
                        ? neuron.getSlotLimit()
                        : (slotConfig == null ? -1 : slotConfig.getSlotLimit());
                boolean atCap = (cap >= 0) && (neuron.getSlots().size() >= cap);
                if (atCap && neuron.lastSlotUsedFallback) atCapAndFallback += 1;
            }
        }
        if (totalNeurons <= 0) return;
        final double avgSlots = totalSlots / Math.max(1.0, totalNeurons);
        final double pctAtCapFallback = 100.0 * ((double) atCapAndFallback) / Math.max(1.0, totalNeurons);

        boolean avgOk = avgSlots >= policy.getAvgSlotsThreshold();
        boolean pctOk = policy.getPercentAtCapFallbackThreshold() > 0.0
                && pctAtCapFallback >= policy.getPercentAtCapFallbackThreshold();
        if (!avgOk && !pctOk) return;
        if (layers.size() >= policy.getMaxLayers()) return;

        // cooldown via first layer's bus tick (if available); fallback to 0
        long tick = 0L;
        if (!layers.isEmpty()) {
            try { tick = layers.get(0).getBus().getCurrentStep(); } catch (Throwable ignored) { }
        }
        if ((tick - policy.getLastLayerGrowthTick()) < policy.getLayerCooldownTicks()) return;

        int donorLayerIndex = -1;
        for (int candidateLayerIndex = layers.size() - 1; candidateLayerIndex >= 0; candidateLayerIndex -= 1) {
            donorLayerIndex = candidateLayerIndex;
            break;
        }
        if (donorLayerIndex >= 0) {
            Layer donorLayer = layers.get(donorLayerIndex);
            Map<String, Object> regionGrowthEvent = new LinkedHashMap<>();
            Trace.addLayerFields(regionGrowthEvent, donorLayer);
            regionGrowthEvent.put("created_layer", true);
            regionGrowthEvent.put("reason", avgOk ? "avg_slots_threshold" : "percent_at_cap_fallback");
            Trace.emit("growth.region.create", regionGrowthEvent);
            int newLayerIndex = region.requestLayerGrowth(
                    donorLayer,
                    policy.getWireProbability(),
                    policy.getNewLayerExcitatoryCount()
            );
            if (newLayerIndex >= 0) {
                policy.setLastLayerGrowthTick(tick);
            }
        }
    }

    /**
     * Scan for temporal‑focus outliers vs. per‑neuron anchor. If a large delta%% is
     * observed, mark the neuron by setting a short focus lock. Conservative: no graph
     * mutation by default; callers can use this as a hook point.
     */
    public static void applyFocusOutlierHints(Region region, GrowthPolicy policy) {
        if (region == null || policy == null) return;
        if (!policy.isEnableNeuronGrowth()) return;

        final List<Layer> layers = region.getLayers();
        long tick = 0L;
        if (!layers.isEmpty()) {
            try { tick = layers.get(0).getBus().getCurrentStep(); } catch (Throwable ignored) { }
        }
        if ((tick - policy.getLastNeuronGrowthTick()) < policy.getNeuronCooldownTicks()) return;

        for (Layer layer : layers) {
            for (Neuron neuron : layer.getNeurons()) {
                try {
                    // Access temporal focus fields via public/protected API
                    double anchor = 0.0;
                    boolean set = false;
                    try { anchor = neuron.getFocusAnchor(); set = (neuron.getSlots() != null); } catch (Throwable ignored) { }

                    // derive epsilon and bin width from a default SlotConfig; best‑effort
                    SlotConfig cfg = new SlotConfig();
                    double denom = Math.max(Math.abs(anchor), cfg.getEpsilonScale());
                    double last = 0.0;
                    boolean haveLast = false;
                    try { last = neuron.getLastInputValue(); haveLast = true; } catch (Throwable ignored) { }
                    if (!set || !haveLast) continue;
                    double deltaPct = Math.abs(last - anchor) / (denom <= 0.0 ? 1e-6 : denom) * 100.0;
                    if (deltaPct >= policy.getNeuronOutlierThresholdPct()) {
                        // Lock briefly to avoid repeated triggers; reflection‑safe attempt
                        try {
                            java.lang.reflect.Field f = neuron.getClass().getDeclaredField("focusLockUntilTick");
                            f.setAccessible(true);
                            long lockUntil = tick + Math.max(1, policy.getNeuronCooldownTicks() / 4);
                            f.setLong(neuron, lockUntil);
                        } catch (Throwable ignored) { /* optional field */ }
                    }
                } catch (Throwable ignored) { /* best‑effort only */ }
            }
        }
        policy.setLastNeuronGrowthTick(tick);
    }
}
