package space.animefu.starfallhaven;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

/** Deterministic, authoritative incident selector. Remote prose may describe, but never alter, an outcome. */
public final class ChronicleDirector {
    public enum Incident {
        AURORA_BLOOM("aurora-bloom", 5, 16, 1, 0, 0, 0, 0, 0, 5, -4, "Aurora Bloom", "Colored currents kindle a calm harvest over the haven."),
        SALVAGE_POD("salvage-pod", 4, 14, 1, 0, 0, 0, 0, 3, 0, 1, "Salvage Pod", "A weathered cargo shell settles beyond the perimeter."),
        CROP_BLIGHT("crop-blight", 5, 10, 3, 8, 0, 0, 0, 0, -5, 6, "Crop Blight", "A silver mold reaches the stored produce."),
        CRYSTAL_STORM("crystal-storm", 6, 8, 4, 0, 0, 0, 0, 2, -2, 10, "Crystal Storm", "Sharp luminous weather scours the haven rim."),
        REFUGEES("refugees", 8, 8, 5, 12, 3, 0, 0, 0, -4, 2, "Wayfarers at the Gate", "Displaced wayfarers ask to share the haven."),
        TRADE_OPPORTUNITY("trade-opportunity", 4, 12, 3, 0, 0, 2, -24, 2, 2, -3, "Passing Exchange", "A neighbor offers a brief exchange window."),
        SICKNESS("sickness", 6, 9, 4, 0, 2, 0, 0, 0, -6, 7, "Haven Fever", "A brief fever moves through the sleeping quarters."),
        HOSTILE_PROBE("hostile-probe", 7, 7, 5, 0, 2, 12, -100, 0, -4, 12, "Distant Probe", "Unknown watchers test the haven's response."),
        RELATIONSHIP_DELEGATION("relationship-delegation", 7, 9, 4, 0, 2, 1, -14, 0, 4, -5, "Neighbor Delegation", "A neighboring delegation arrives to renew ties."),
        QUIET_DAY("quiet-day", 2, 11, 1, 0, 1, 0, -100, 0, 3, -8, "Quiet Orbit", "Routine work and shared meals steady the haven.");

        public final String id;
        public final int cooldownDays, baseWeight, minimumDay, minimumFood, minimumSettlers, minimumRelations, minimumStanding;
        public final int componentDelta, moodDelta, tensionDelta;
        public final String headline, synopsis;

        Incident(String id, int cooldownDays, int baseWeight, int minimumDay, int minimumFood,
                 int minimumSettlers, int minimumRelations, int minimumStanding, int componentDelta,
                 int moodDelta, int tensionDelta, String headline, String synopsis) {
            this.id=id; this.cooldownDays=cooldownDays; this.baseWeight=baseWeight; this.minimumDay=minimumDay;
            this.minimumFood=minimumFood; this.minimumSettlers=minimumSettlers; this.minimumRelations=minimumRelations;
            this.minimumStanding=minimumStanding; this.componentDelta=componentDelta; this.moodDelta=moodDelta;
            this.tensionDelta=tensionDelta; this.headline=headline; this.synopsis=synopsis;
        }
    }

    /** Immutable selection input, suitable for constructing from a world or a restored save. */
    public static final class Snapshot {
        public final long worldSeed;
        public final int day, wood, stone, food, crystal, components, settlers, livingSettlers;
        public final int knownRelations, friendlyRelations, hostileRelations, bestStanding, threat, tension;
        public Snapshot(long worldSeed, int day, int wood, int stone, int food, int crystal, int components,
                        int settlers, int livingSettlers, int knownRelations, int friendlyRelations,
                        int hostileRelations, int bestStanding, int threat, int tension) {
            this.worldSeed=worldSeed; this.day=Math.max(1,day); this.wood=Math.max(0,wood); this.stone=Math.max(0,stone);
            this.food=Math.max(0,food); this.crystal=Math.max(0,crystal); this.components=Math.max(0,components);
            this.settlers=Math.max(0,settlers); this.livingSettlers=Math.max(0,livingSettlers);
            this.knownRelations=Math.max(0,knownRelations); this.friendlyRelations=Math.max(0,friendlyRelations);
            this.hostileRelations=Math.max(0,hostileRelations); this.bestStanding=clamp(bestStanding,-100,100);
            this.threat=clamp(threat,0,100); this.tension=clamp(tension,0,100);
        }
    }

    public static final class Consequence {
        public final int woodDelta, stoneDelta, foodDelta, crystalDelta, componentDelta, moodDelta, threatDelta, tensionDelta;
        public Consequence(int woodDelta,int stoneDelta,int foodDelta,int crystalDelta,int componentDelta,int moodDelta,int threatDelta,int tensionDelta) {
            this.woodDelta=woodDelta;this.stoneDelta=stoneDelta;this.foodDelta=foodDelta;this.crystalDelta=crystalDelta;
            this.componentDelta=componentDelta;this.moodDelta=moodDelta;this.threatDelta=threatDelta;this.tensionDelta=tensionDelta;
        }
    }

    /** Data-only request for an optional narrator. No response field can become a simulation command. */
    public static final class NarrativePrompt {
        public final String promptVersion, incidentId, headline, synopsis, tone;
        public final int day, settlers, tension;
        public final List<String> permittedFacts;
        NarrativePrompt(Incident incident, Snapshot snapshot, Consequence consequence) {
            promptVersion="starfall-chronicle-1"; incidentId=incident.id; headline=incident.headline; synopsis=incident.synopsis;
            tone="hopeful frontier chronicle; concise; original setting terms only"; day=snapshot.day;
            settlers=snapshot.livingSettlers; tension=snapshot.tension;
            List<String> facts=new ArrayList<>(); facts.add("day="+day); facts.add("livingSettlers="+settlers);
            facts.add("foodDelta="+consequence.foodDelta); facts.add("componentDelta="+consequence.componentDelta);
            facts.add("moodDelta="+consequence.moodDelta); facts.add("tensionDelta="+consequence.tensionDelta);
            permittedFacts=Collections.unmodifiableList(facts);
        }
    }

    public static final class Outcome {
        public final Incident incident;
        public final long selectionSeed;
        public final Consequence consequence;
        public final NarrativePrompt prompt;
        public final Map<Incident,Integer> eligibleWeights;
        Outcome(Incident incident,long selectionSeed,Consequence consequence,NarrativePrompt prompt,Map<Incident,Integer> weights) {
            this.incident=incident;this.selectionSeed=selectionSeed;this.consequence=consequence;this.prompt=prompt;
            this.eligibleWeights=Collections.unmodifiableMap(new EnumMap<>(weights));
        }
    }

    public Outcome select(Snapshot snapshot, ChronicleHistory history) {
        if (snapshot == null) throw new IllegalArgumentException("snapshot required");
        if (history == null) history = new ChronicleHistory();
        EnumMap<Incident,Integer> weights=new EnumMap<>(Incident.class);
        int total=0;
        for(Incident incident:Incident.values()) if(eligible(incident,snapshot,history)) {
            int weight=weight(incident,snapshot); if(weight>0){weights.put(incident,weight);total+=weight;}
        }
        // Quiet day has deliberately broad eligibility and is the local/offline fail-safe.
        if(total<=0){weights.clear();weights.put(Incident.QUIET_DAY,1);total=1;}
        long seed=mix(snapshot.worldSeed ^ ((long)snapshot.day*0x9e3779b97f4a7c15L) ^ history.size());
        int roll=(int)Long.remainderUnsigned(mix(seed),total), cursor=0; Incident selected=Incident.QUIET_DAY;
        for(Incident incident:Incident.values()){Integer value=weights.get(incident);if(value!=null&&(roll<(cursor+=value))){selected=incident;break;}}
        Consequence consequence=consequence(selected,snapshot,seed);
        return new Outcome(selected,seed,consequence,new NarrativePrompt(selected,snapshot,consequence),weights);
    }

    public boolean eligible(Incident incident, Snapshot s, ChronicleHistory history) {
        if(incident==null||s==null)return false;
        int last=history==null?Integer.MIN_VALUE:history.lastDay(incident.id);
        if(last!=Integer.MIN_VALUE&&s.day-last<incident.cooldownDays)return false;
        if(s.day<incident.minimumDay||s.food<incident.minimumFood||s.livingSettlers<incident.minimumSettlers||s.knownRelations<incident.minimumRelations)return false;
        if(incident==Incident.TRADE_OPPORTUNITY&&s.hostileRelations>=s.knownRelations)return false;
        if((incident==Incident.TRADE_OPPORTUNITY||incident==Incident.RELATIONSHIP_DELEGATION)&&s.bestStanding<incident.minimumStanding)return false;
        if(incident==Incident.HOSTILE_PROBE&&s.hostileRelations==0&&s.threat<45)return false;
        if(incident==Incident.CROP_BLIGHT&&s.food<Math.max(incident.minimumFood,s.livingSettlers*2))return false;
        if(incident==Incident.REFUGEES&&s.food<s.livingSettlers*3)return false;
        if(incident==Incident.CRYSTAL_STORM&&s.crystal==0&&s.day<8)return false;
        return true;
    }

    private int weight(Incident i,Snapshot s){
        int w=i.baseWeight;
        switch(i){
            case AURORA_BLOOM:w+=Math.max(0,45-s.tension)/10;break;
            case SALVAGE_POD:w+=s.components<4?8:0;break;
            case CROP_BLIGHT:w+=Math.min(8,s.food/10);break;
            case CRYSTAL_STORM:w+=s.crystal/5+s.threat/25;break;
            case REFUGEES:w+=Math.max(0,s.food-s.livingSettlers*3)/8;break;
            case TRADE_OPPORTUNITY:w+=s.friendlyRelations*4+s.crystal/8;break;
            case SICKNESS:w+=Math.max(0,s.livingSettlers-4)*2;break;
            case HOSTILE_PROBE:w+=s.hostileRelations*6+s.threat/8;break;
            case RELATIONSHIP_DELEGATION:w+=s.friendlyRelations*5+Math.max(0,s.bestStanding)/15;break;
            case QUIET_DAY:w+=s.tension/9+(s.threat>60?6:0);break;
        }
        return clamp(w,1,40);
    }

    private Consequence consequence(Incident i,Snapshot s,long seed){
        int variance=(int)Long.remainderUnsigned(mix(seed^i.ordinal()),3);
        switch(i){
            case AURORA_BLOOM:return new Consequence(0,0,2+variance,1,0,i.moodDelta,0,i.tensionDelta);
            case SALVAGE_POD:return new Consequence(2+variance,1,0,0,Math.min(5,i.componentDelta+variance),i.moodDelta,1,i.tensionDelta);
            case CROP_BLIGHT:return new Consequence(0,0,-Math.min(s.food,4+variance*2),0,0,i.moodDelta,0,i.tensionDelta);
            case CRYSTAL_STORM:return new Consequence(0,-Math.min(s.stone,1+variance),0,Math.min(4,1+variance),0,i.moodDelta,5,i.tensionDelta);
            case REFUGEES:return new Consequence(0,0,-Math.min(s.food,3+variance),0,0,i.moodDelta,0,i.tensionDelta);
            case TRADE_OPPORTUNITY:return new Consequence(1,0,2+variance,-Math.min(s.crystal,1),i.componentDelta,i.moodDelta,-2,i.tensionDelta);
            case SICKNESS:return new Consequence(0,0,-Math.min(s.food,1+variance),0,0,i.moodDelta,1,i.tensionDelta);
            case HOSTILE_PROBE:return new Consequence(0,-Math.min(s.stone,variance),0,0,0,i.moodDelta,8,i.tensionDelta);
            case RELATIONSHIP_DELEGATION:return new Consequence(0,0,-Math.min(s.food,1),0,0,i.moodDelta,-3,i.tensionDelta);
            default:return new Consequence(0,0,0,0,0,i.moodDelta,-2,i.tensionDelta);
        }
    }

    public String offlineNarrative(Outcome outcome) {
        if(outcome==null)return "The chronicle remains quiet.";
        return outcome.incident.headline+" — "+outcome.incident.synopsis;
    }

    static int clamp(int value,int low,int high){return Math.max(low,Math.min(high,value));}
    static long mix(long z){z=(z^(z>>>30))*0xbf58476d1ce4e5b9L;z=(z^(z>>>27))*0x94d049bb133111ebL;return z^(z>>>31);}
}
