package com.livingworld.debug;

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

/**
 * Measures simulation cycle and module cost using a monotonic clock.
 */
public final class SimulationProfiler
        implements com.livingworld.core.simulation.SimulationProfiler {

    private long cycleStartNanos = -1L;
    private long totalCycleNanos;
    private final Map<String, Long> moduleStartNanos = new LinkedHashMap<>();
    private final Map<String, Long> moduleTimings = new LinkedHashMap<>();
    private int eventsPublished;
    private int regionsUpdated;
    private int savesPerformed;
    private int budgetOverruns;

    public synchronized void beginCycle() {
        if (cycleStartNanos >= 0L) {
            throw new IllegalStateException("a profiling cycle is already active");
        }
        totalCycleNanos = 0L;
        moduleStartNanos.clear();
        moduleTimings.clear();
        eventsPublished = 0;
        regionsUpdated = 0;
        savesPerformed = 0;
        budgetOverruns = 0;
        cycleStartNanos = System.nanoTime();
    }

    public synchronized void endCycle() {
        ensureCycleActive();
        if (!moduleStartNanos.isEmpty()) {
            throw new IllegalStateException(
                    "cannot end cycle while modules are still active: " + moduleStartNanos.keySet());
        }
        totalCycleNanos = System.nanoTime() - cycleStartNanos;
        cycleStartNanos = -1L;
    }

    @Override
    public synchronized void startCycle(long simulationTick) {
        beginCycle();
    }

    @Override
    public synchronized void endCycle(long durationMs) {
        endCycle();
    }

    @Override
    public synchronized void beginModule(String moduleId) {
        ensureCycleActive();
        validateModuleId(moduleId);
        if (moduleStartNanos.putIfAbsent(moduleId, System.nanoTime()) != null) {
            throw new IllegalStateException("module is already being profiled: " + moduleId);
        }
    }

    @Override
    public synchronized void endModule(String moduleId) {
        ensureCycleActive();
        validateModuleId(moduleId);
        Long startedAt = moduleStartNanos.remove(moduleId);
        if (startedAt == null) {
            throw new IllegalStateException("module profiling was not started: " + moduleId);
        }
        long elapsed = System.nanoTime() - startedAt;
        moduleTimings.merge(moduleId, elapsed, Long::sum);
    }

    @Override
    public synchronized void recordEvent() {
        ensureCycleActive();
        eventsPublished++;
    }

    @Override
    public synchronized void recordRegionUpdated() {
        ensureCycleActive();
        regionsUpdated++;
    }

    @Override
    public synchronized void recordSave() {
        ensureCycleActive();
        savesPerformed++;
    }

    @Override
    public synchronized void recordBudgetOverrun() {
        ensureCycleActive();
        budgetOverruns++;
    }

    public synchronized SimulationProfileSnapshot createSnapshot() {
        long cycleNanos = cycleStartNanos >= 0L
                ? System.nanoTime() - cycleStartNanos
                : totalCycleNanos;
        return new SimulationProfileSnapshot(
                cycleNanos,
                moduleTimings,
                eventsPublished,
                regionsUpdated,
                savesPerformed,
                budgetOverruns > 0);
    }

    public synchronized int getBudgetOverrunCount() {
        return budgetOverruns;
    }

    private void ensureCycleActive() {
        if (cycleStartNanos < 0L) {
            throw new IllegalStateException("no profiling cycle is active");
        }
    }

    private static void validateModuleId(String moduleId) {
        if (moduleId == null || moduleId.isBlank()) {
            throw new IllegalArgumentException("moduleId must not be null or blank");
        }
    }
}
