package wtf.beatrice.retrorender.engine.render;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.environment.DirectionalShadowLight;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;

public class DayNightCycle {

    private final DirectionalShadowLight sun;
    private final ColorAttribute ambientLight;

    // base colors
    private final Color dayAmbient    = new Color(0.6f, 0.7f, 0.7f, 1f);
    private final Color nightAmbient  = new Color(0.2f, 0.15f, 0.2f, 1f);

    private final Color daySunColor   = new Color(1.0f, 0.9f, 1f, 1f);
    private final Color nightSunColor = new Color(0.4f, 0.4f, 0.6f, 1f);

    private final Color daySkyColor   = new Color(0.8f, 0.9f, 1.0f, 1f);
    private final Color nightSkyColor = new Color(0.1f, 0.01f, 0.2f, 1f);

    private final Color   currentSkyColor = new Color();
    private final Color   tmpSunColor     = new Color();
    private final Vector3 tmpDir          = new Vector3();
    private final Vector3 sunDirection    = new Vector3();

    // Rotate around Z so the sun path is tilted relative to world up.
    private final Vector3 tiltAxis = new Vector3(0f, 0f, 1f).nor();
    private float axialTilt = 30f; // in degrees

    /**
     * If false (default), simple equator-style model:
     *  timeOfDay=0   -> midnight
     *  timeOfDay=0.25-> sunrise
     *  timeOfDay=0.5 -> noon
     *  timeOfDay=0.75-> sunset
     *
     * Brightness is driven purely by this ideal curve; Z-tilt only affects
     * visual direction (shadow slant), not timing of day/night.
     *
     * If true, brightness and day length depend on the final tilted direction,
     * so axialTiltDeg affects how long days are and how high the sun gets.
     */
    private boolean seasonal = false;

    /**
     * 0–1: 0 = midnight, 0.25 = sunrise, 0.5 = noon, 0.75 = sunset.
     */
    private float timeOfDay = 0.25f;

    /** Seconds for one full 24h cycle. */
    private float dayLengthSeconds = 60 * 5f;

    /**
     * Daylight / altitude factor in [0, 1].
     * 0 = "sun below horizon / no direct light"
     * 1 = "sun as high/bright as it gets for current configuration".
     */
    private float sunAltitude = 0f;

    public DayNightCycle(DirectionalShadowLight sun, ColorAttribute ambientLight) {
        this.sun = sun;
        this.ambientLight = ambientLight;
    }

    public void update(float delta) {
        if (dayLengthSeconds <= 0f) return;

        // Advance time of day
        timeOfDay = (timeOfDay + delta / dayLengthSeconds) % 1f;

        // 0..2π over a day
        float angle = timeOfDay * MathUtils.PI2;

        // Ideal equator curve:
        //  t=0   -> midnight (height=-1)
        //  t=0.25-> sunrise  (height=0)
        //  t=0.5 -> noon     (height=1)
        //  t=0.75-> sunset   (height=0)
        float orbitAngle = angle - MathUtils.PI / 2f;
        float height     = MathUtils.sin(orbitAngle);   // -1..1
        float horizontal = MathUtils.cos(orbitAngle);   // -1..1

        // Base direction before Z-tilt, as if on equator.
        float baseX = -horizontal * 0.7f;
        float baseY = -height;              // negative when sun is above
        float baseZ = -horizontal * 0.3f;

        if (!seasonal) {
            // === Simple "equator-style" mode ===

            float sunFactor = MathUtils.clamp(height, 0f, 1f); // 0..1
            sunAltitude = sunFactor; // 👈 always 0..1

            float ambientFactor = 0.25f + 0.75f * sunFactor;

            // Visual direction with axial tilt
            tmpDir.set(baseX, baseY, baseZ).nor();
            tmpDir.rotate(tiltAxis, axialTilt);
            sunDirection.set(tmpDir);

            // Sun color / intensity
            tmpSunColor.set(nightSunColor).lerp(daySunColor, sunFactor);
            tmpSunColor.mul(sunFactor);

            sun.set(tmpSunColor.r, tmpSunColor.g, tmpSunColor.b,
                tmpDir.x, tmpDir.y, tmpDir.z);

            // Ambient + sky
            ambientLight.color.set(nightAmbient).lerp(dayAmbient, ambientFactor);
            currentSkyColor.set(nightSkyColor).lerp(daySkyColor, ambientFactor);

        } else {
            // === Seasonal / physical mode ===

            tmpDir.set(baseX, baseY, baseZ).nor();
            tmpDir.rotate(tiltAxis, axialTilt);
            sunDirection.set(tmpDir);

            // True altitude from final direction: 0..1
            float altitude = Math.max(-tmpDir.y, 0f);
            sunAltitude = altitude; // 👈 also 0..1

            float sunFactor     = altitude;
            float ambientFactor = 0.25f + 0.75f * altitude;

            tmpSunColor.set(nightSunColor).lerp(daySunColor, sunFactor);
            tmpSunColor.mul(sunFactor);

            sun.set(tmpSunColor.r, tmpSunColor.g, tmpSunColor.b,
                tmpDir.x, tmpDir.y, tmpDir.z);

            ambientLight.color.set(nightAmbient).lerp(dayAmbient, ambientFactor);
            currentSkyColor.set(nightSkyColor).lerp(daySkyColor, ambientFactor);
        }
    }

    public Color getSkyColor() {
        return currentSkyColor;
    }

    public float getTimeOfDay() {
        return timeOfDay;
    }

    /**
     * Daylight / altitude factor in [0, 1].
     * 0 = no direct sunlight, 1 = max noon brightness.
     */
    public float getSunAltitude() {
        return sunAltitude;
    }

    public void setTimeOfDay(float t) {
        timeOfDay = MathUtils.clamp(t, 0f, 1f);
    }

    /** World-space direction the sun is shining *from*. */
    public Vector3 getSunDirection() {
        return sunDirection;
    }

    /** True if we consider the sun above the horizon (i.e. there should be direct light / shadows). */
    public boolean isSunAboveHorizon() {
        return sunAltitude > 0f; // strictly > 0; you can use >= 0.01f if you want a tiny cutoff
    }

    public float getAxialTilt()
    {
        return axialTilt;
    }

    public void setAxialTilt(float degrees)
    {
        axialTilt = degrees;
    }

    public float getDayLength()
    {
        return dayLengthSeconds;
    }

    public void setDayLength(float seconds)
    {
        dayLengthSeconds = seconds;
    }
}
