package space.animefu.starfallhaven;

import java.io.Serializable;
import java.util.*;
import static space.animefu.starfallhaven.ColonySystems.ResourceType;

/** Platform-neutral, serializable contracts for trade, diplomacy, households, and dependent care. */
public final class SocietySystems {
    private SocietySystems() {}
    public static final int YEAR_DAYS=360;

    public enum RelationState { HOSTILE, GUARDED, NEUTRAL, FRIENDLY, ALLIED }
    public enum AgeStage { INFANT, CHILD, ADULT }

    public static final class Inventory implements Serializable {
        private static final long serialVersionUID=1L;
        public final EnumMap<ResourceType,Integer> amounts=new EnumMap<>(ResourceType.class);
        public int amount(ResourceType type){Integer n=amounts.get(type);return n==null?0:n;}
        public void add(ResourceType type,int quantity){if(type==null||quantity<=0)return;amounts.put(type,amount(type)+quantity);}
        public boolean remove(ResourceType type,int quantity){if(type==null||quantity<=0||amount(type)<quantity)return false;int left=amount(type)-quantity;if(left==0)amounts.remove(type);else amounts.put(type,left);return true;}
    }

    public static final class Faction implements Serializable {
        private static final long serialVersionUID=1L;
        public final String id,name;
        public int reputation;
        public Faction(String id,String name,int reputation){this.id=required(id);this.name=required(name);this.reputation=clamp(reputation,-100,100);}
        public int changeReputation(int delta){reputation=clamp(reputation+delta,-100,100);return reputation;}
        public RelationState relation(){return reputation<=-50?RelationState.HOSTILE:reputation<0?RelationState.GUARDED:reputation<35?RelationState.NEUTRAL:reputation<70?RelationState.FRIENDLY:RelationState.ALLIED;}
    }

    public static final class TradeOffer implements Serializable {
        private static final long serialVersionUID=1L;
        public final String id;
        public final ResourceType resource;
        public int quantity;
        public final int unitPrice;
        public TradeOffer(String id,ResourceType resource,int quantity,int unitPrice){this.id=required(id);this.resource=Objects.requireNonNull(resource);this.quantity=Math.max(0,quantity);this.unitPrice=Math.max(1,unitPrice);}
        @Override public boolean equals(Object value){if(this==value)return true;if(!(value instanceof TradeOffer))return false;TradeOffer o=(TradeOffer)value;return quantity==o.quantity&&unitPrice==o.unitPrice&&id.equals(o.id)&&resource==o.resource;}
        @Override public int hashCode(){return Objects.hash(id,resource,quantity,unitPrice);}
    }

    public static final class TradeReceipt implements Serializable {
        private static final long serialVersionUID=1L;
        public final boolean accepted;
        public final String reason,offerId;
        public final int quantity,totalPrice,reputationDelta;
        TradeReceipt(boolean accepted,String reason,String offerId,int quantity,int totalPrice,int reputationDelta){this.accepted=accepted;this.reason=reason;this.offerId=offerId;this.quantity=quantity;this.totalPrice=totalPrice;this.reputationDelta=reputationDelta;}
    }

    public static final class TradePricing {
        private TradePricing(){}
        /** Reputation changes purchase cost by at most 25 percent; all math is integral and deterministic. */
        public static int buyPrice(int basePrice,int reputation){int modifier=100-clamp(reputation,-100,100)/4;return Math.max(1,(Math.max(1,basePrice)*modifier+99)/100);}
    }

    public static final class Caravan implements Serializable {
        private static final long serialVersionUID=1L;
        public final String id,factionId;
        public final Inventory inventory=new Inventory();
        public final List<TradeOffer> offers=new ArrayList<>();
        public int arrivalDay,departureDay;
        private final Faction faction;
        private Caravan(String id,Faction faction,int arrivalDay){this.id=required(id);this.faction=Objects.requireNonNull(faction);this.factionId=faction.id;this.arrivalDay=arrivalDay;this.departureDay=arrivalDay+2;}
        public static Caravan create(String id,Faction faction,long seed,int arrivalDay){
            Caravan caravan=new Caravan(id,faction,arrivalDay);Random rng=new Random(seed^id.hashCode()^faction.id.hashCode());
            ResourceType[] goods={ResourceType.WOOD,ResourceType.STONE,ResourceType.FOOD,ResourceType.COMPONENT};
            int[] bases={3,4,2,16};
            for(int i=0;i<goods.length;i++){int quantity=3+rng.nextInt(8);caravan.inventory.add(goods[i],quantity);int price=TradePricing.buyPrice(bases[i]+rng.nextInt(3),faction.reputation);caravan.offers.add(new TradeOffer(id+"-"+goods[i].name().toLowerCase(Locale.US),goods[i],quantity,price));}
            return caravan;
        }
        public TradeReceipt buyFromCaravan(String offerId,int quantity,Inventory buyer,ResourceType currency){
            TradeOffer offer=find(offerId);if(offer==null)return rejected("offer missing",offerId);if(quantity<=0)return rejected("quantity must be positive",offerId);if(quantity>offer.quantity||quantity>inventory.amount(offer.resource))return rejected("insufficient caravan stock",offerId);
            int total=offer.unitPrice*quantity;if(buyer==null||!buyer.remove(currency,total))return rejected("insufficient payment",offerId);
            inventory.remove(offer.resource,quantity);buyer.add(offer.resource,quantity);offer.quantity-=quantity;int gain=Math.min(5,quantity);faction.changeReputation(gain);return new TradeReceipt(true,"accepted",offerId,quantity,total,gain);
        }
        private TradeOffer find(String offerId){for(TradeOffer offer:offers)if(offer.id.equals(offerId))return offer;return null;}
        private static TradeReceipt rejected(String reason,String offerId){return new TradeReceipt(false,reason,offerId,0,0,0);}
    }

    public static final class DiplomacyResult implements Serializable {
        private static final long serialVersionUID=1L;
        public final boolean accepted;public final String reason;public final int reputationDelta;public final RelationState before,after;
        DiplomacyResult(boolean accepted,String reason,int delta,RelationState before,RelationState after){this.accepted=accepted;this.reason=reason;this.reputationDelta=delta;this.before=before;this.after=after;}
    }
    public static final class Diplomacy {
        private Diplomacy(){}
        public static DiplomacyResult offerAid(Faction faction,int aidValue){RelationState before=faction.relation();if(aidValue<=0)return new DiplomacyResult(false,"aid must have value",0,before,before);int delta=Math.min(20,2+aidValue);faction.changeReputation(delta);return new DiplomacyResult(true,"aid welcomed",delta,before,faction.relation());}
        public static DiplomacyResult signAccord(Faction faction){RelationState before=faction.relation();if(faction.reputation<50)return new DiplomacyResult(false,"more trust required",0,before,before);int delta=70-faction.reputation;if(delta>0)faction.changeReputation(delta);return new DiplomacyResult(true,"accord signed",Math.max(0,delta),before,faction.relation());}
        public static DiplomacyResult breakPromise(Faction faction){RelationState before=faction.relation();faction.changeReputation(-30);return new DiplomacyResult(true,"trust damaged",-30,before,faction.relation());}
    }

    public static final class CareNeeds implements Serializable {
        private static final long serialVersionUID=1L;
        public float foodNeed,comfortNeed,restNeed;
        public CareNeeds(AgeStage stage){foodNeed=stage==AgeStage.INFANT?65:stage==AgeStage.CHILD?30:0;comfortNeed=stage==AgeStage.INFANT?60:stage==AgeStage.CHILD?25:0;restNeed=stage==AgeStage.INFANT?55:stage==AgeStage.CHILD?25:0;}
        public float urgency(){return Math.max(foodNeed,Math.max(comfortNeed,restNeed));}
    }
    public static final class Person implements Serializable {
        private static final long serialVersionUID=1L;
        public final String id,name;public int ageDays;public AgeStage stage;public String partnerId,householdId;public final List<String> parentIds=new ArrayList<>();public CareNeeds care;public boolean availableForCare=true;
        public Person(String id,String name,AgeStage stage,int ageDays){this.id=required(id);this.name=required(name);this.stage=Objects.requireNonNull(stage);this.ageDays=Math.max(0,ageDays);this.care=new CareNeeds(stage);}
        public boolean isAdult(){return stage==AgeStage.ADULT&&ageDays>=18*YEAR_DAYS;}
        public boolean canWorkOrFight(){return isAdult();}
    }
    public static final class Pregnancy implements Serializable {
        private static final long serialVersionUID=1L;
        public final String id,parentAId,parentBId;public final int dueDay;
        Pregnancy(String id,String a,String b,int dueDay){this.id=id;this.parentAId=a;this.parentBId=b;this.dueDay=dueDay;}
    }
    public static final class FamilyEvent implements Serializable {
        private static final long serialVersionUID=1L;public enum Type { PARTNERSHIP, EXPECTING_CHILD, BIRTH, AGE_STAGE_CHANGED }
        public final Type type;public final String subjectId,detail;public final int day;
        FamilyEvent(Type type,String subjectId,String detail,int day){this.type=type;this.subjectId=subjectId;this.detail=detail;this.day=day;}
    }
    public static final class FamilyResult implements Serializable {
        private static final long serialVersionUID=1L;public final boolean accepted;public final String reason,id;
        FamilyResult(boolean accepted,String reason,String id){this.accepted=accepted;this.reason=reason;this.id=id;}
    }

    public static final class FamilyRegistry implements Serializable {
        private static final long serialVersionUID=1L;
        public final List<Person> people=new ArrayList<>();public final List<Pregnancy> pregnancies=new ArrayList<>();public int currentDay;private long nextId=1;
        public boolean add(Person person){if(person==null||person(person.id)!=null)return false;people.add(person);return true;}
        public Person person(String id){for(Person p:people)if(p.id.equals(id))return p;return null;}
        public FamilyResult formPartnership(String aId,String bId){Person a=person(aId),b=person(bId);if(a==null||b==null||a==b)return fail("people unavailable");if(!a.isAdult()||!b.isAdult())return fail("partners must both be adults");if(a.partnerId!=null||b.partnerId!=null)return fail("existing partnership");String household="household-"+(nextId++);a.partnerId=b.id;b.partnerId=a.id;a.householdId=b.householdId=household;return ok(household);}
        /** Records a planned child and expected day without representing conception or sexual activity. */
        public FamilyResult beginPregnancy(String aId,String bId,int dueDay){Person a=person(aId),b=person(bId);if(a==null||b==null||!a.isAdult()||!b.isAdult())return fail("parents must both be adults");if(dueDay<=currentDay)return fail("due day must be in the future");String id="expecting-"+(nextId++);pregnancies.add(new Pregnancy(id,a.id,b.id,dueDay));return ok(id);}
        public List<FamilyEvent> advanceDay(int newDay){if(newDay<currentDay)throw new IllegalArgumentException("day cannot move backward");List<FamilyEvent> events=new ArrayList<>();int elapsed=newDay-currentDay;currentDay=newDay;for(Person p:people){p.ageDays+=elapsed;AgeStage next=stageForAge(p.ageDays);if(next!=p.stage){p.stage=next;p.care=new CareNeeds(next);events.add(new FamilyEvent(FamilyEvent.Type.AGE_STAGE_CHANGED,p.id,next.name(),newDay));}}
            for(Iterator<Pregnancy> it=pregnancies.iterator();it.hasNext();){Pregnancy expected=it.next();if(expected.dueDay<=newDay){Person a=person(expected.parentAId),b=person(expected.parentBId);String id="child-"+(nextId++);Person infant=new Person(id,"Haven Child "+id.substring(6),AgeStage.INFANT,0);infant.parentIds.add(a.id);infant.parentIds.add(b.id);infant.householdId=a.householdId!=null?a.householdId:b.householdId;people.add(infant);events.add(new FamilyEvent(FamilyEvent.Type.BIRTH,id,"child welcomed safely",newDay));it.remove();}}return events;}
        private static AgeStage stageForAge(int ageDays){return ageDays<2*YEAR_DAYS?AgeStage.INFANT:ageDays<18*YEAR_DAYS?AgeStage.CHILD:AgeStage.ADULT;}
        private static FamilyResult ok(String id){return new FamilyResult(true,"accepted",id);}private static FamilyResult fail(String reason){return new FamilyResult(false,reason,null);}
    }

    public static final class CareAssignment implements Serializable {
        private static final long serialVersionUID=1L;public final String dependentId,caregiverId;public final float urgency;
        CareAssignment(String dependentId,String caregiverId,float urgency){this.dependentId=dependentId;this.caregiverId=caregiverId;this.urgency=urgency;}
    }
    public static final class Caregiving {
        private Caregiving(){}
        /** Parents sort before other adults, then IDs sort lexically, so replay and save reload choose identically. */
        public static CareAssignment chooseCaregiver(FamilyRegistry registry,String dependentId){Person child=registry.person(dependentId);if(child==null||child.stage==AgeStage.ADULT)return null;List<Person> candidates=new ArrayList<>();for(Person p:registry.people)if(p.isAdult()&&p.availableForCare)candidates.add(p);Collections.sort(candidates,(a,b)->{int ap=child.parentIds.contains(a.id)?0:1,bp=child.parentIds.contains(b.id)?0:1;return ap!=bp?ap-bp:a.id.compareTo(b.id);});return candidates.isEmpty()?null:new CareAssignment(child.id,candidates.get(0).id,child.care.urgency());}
        public static FamilyResult provideCare(FamilyRegistry registry,CareAssignment assignment,float amount){if(assignment==null||amount<=0)return FamilyRegistry.fail("invalid care assignment");Person child=registry.person(assignment.dependentId),adult=registry.person(assignment.caregiverId);if(child==null||adult==null||child.stage==AgeStage.ADULT||!adult.isAdult()||!adult.availableForCare)return FamilyRegistry.fail("caregiver unavailable");child.care.foodNeed=Math.max(0,child.care.foodNeed-amount);child.care.comfortNeed=Math.max(0,child.care.comfortNeed-amount*.8f);child.care.restNeed=Math.max(0,child.care.restNeed-amount*.6f);return FamilyRegistry.ok(assignment.dependentId);}
    }

    public static final class SocietyState implements Serializable {
        private static final long serialVersionUID=1L;public final List<Faction> factions=new ArrayList<>();public final List<Caravan> caravans=new ArrayList<>();public final FamilyRegistry families=new FamilyRegistry();
    }

    private static int clamp(int value,int min,int max){return Math.max(min,Math.min(max,value));}
    private static String required(String value){if(value==null||value.trim().isEmpty())throw new IllegalArgumentException("value required");return value;}
}
