package space.animefu.starfallhaven;

import android.content.Context;
import android.graphics.*;
import android.os.SystemClock;
import android.view.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.*;

/** Touch-first Canvas colony console backed by packaged PixelLab pixel art. */
public final class HavenView extends View implements Runnable {
    public interface Host { void save(GameWorld world, boolean explicit); void openRegion(GameWorld world); boolean narrate(ChronicleClient.Request request,ChronicleClient.Callback callback); }

    private static final int INK=Color.rgb(225,241,236), MUTED=Color.rgb(147,174,177);
    private static final int PANEL=Color.rgb(16,31,42), PANEL_2=Color.rgb(25,47,58);
    private static final int CYAN=Color.rgb(105,239,219), AMBER=Color.rgb(250,188,80);
    private final Paint p=new Paint(Paint.ANTI_ALIAS_FLAG), pixel=new Paint();
    private final Map<String,Bitmap> sprites=new HashMap<>();
    private final ColonyUiState ui=new ColonyUiState();
    private final AreaDesignation area=new AreaDesignation();
    private final RectF[] pawnCards={new RectF(),new RectF(),new RectF()};
    private final RectF pauseButton=new RectF(), speedButton=new RectF(), saveButton=new RectF(), soundButton=new RectF();
    private final RectF ordersButton=new RectF(), buildButton=new RectF(), workButton=new RectF(), chronicleButton=new RectF(), regionButton=new RectF(), closePanel=new RectF();
    private final RectF narrateButton=new RectF();
    private final RectF panelFrame=new RectF();
    private final RectF[] actionButtons={new RectF(),new RectF(),new RectF(),new RectF(),new RectF()};
    private final RectF[] categoryButtons={new RectF(),new RectF(),new RectF(),new RectF()};
    private final RectF[] itemButtons={new RectF(),new RectF(),new RectF(),new RectF()};
    private final RectF draftButton=new RectF(), inspectClose=new RectF(), tutorialSkip=new RectF();
    private final RectF designateButton=new RectF(), confirmButton=new RectF(), cancelButton=new RectF();
    private final Rect terrainSource=new Rect();
    private GameWorld world;
    private final Host host; private final GameAudioManager audio; private final TutorialController tutorial;
    private final ScaleGestureDetector scale;
    private Thread loop; private volatile boolean running;
    private float camX=20,camY=14,tile=50,lastX,lastY,downX,downY,selectionPulse;
    private int safeLeft,safeTop,safeRight,safeBottom,selectedX=-1,selectedY=-1;
    private boolean dragging,moved,designating,chronicleOpen,chronicleBusy; private long lastAutosaveMs,lastNarrationSeed=Long.MIN_VALUE;
    private String chronicleTitle="The Haven Chronicle",chronicleStory="The first campfire burns beneath an unfamiliar sky.",chronicleSource="LOCAL",chronicleStatus="Ready to narrate";
    private float density; private HavenHudLayout hud;

    public HavenView(Context c){this(c,new GameWorld(84621L),null,new GameAudioManager(c),new TutorialController(c));}
    public HavenView(Context c,GameWorld initial,Host host,GameAudioManager audio,TutorialController tutorial){
        super(c);world=initial;this.host=host;this.audio=audio;this.tutorial=tutorial;
        density=1f;hud=HavenHudLayout.calculate(1280,570,0,0,0,0,getResources().getDisplayMetrics().density);setKeepScreenOn(true);setFocusable(true);
        pixel.setFilterBitmap(false);pixel.setAntiAlias(false);p.setDither(false);
        ChronicleHistory.Entry latest=world.latestChronicle();if(latest!=null){chronicleTitle=latest.headline;chronicleStory=latest.story;chronicleSource=latest.source;chronicleStatus="Saved "+latest.tone+" chronicle";lastNarrationSeed=latest.source.equals("GROK")?latest.selectionSeed:Long.MIN_VALUE;}
        scale=new ScaleGestureDetector(c,new ScaleGestureDetector.SimpleOnScaleGestureListener(){
            @Override public boolean onScale(ScaleGestureDetector d){float before=tile;tile=Math.max(dp(28),Math.min(dp(88),tile*d.getScaleFactor()));if(before!=tile)tutorial.onZoom();return true;}
        });
        loadSprites();lastAutosaveMs=SystemClock.uptimeMillis();
    }
    @Override public WindowInsets onApplyWindowInsets(WindowInsets insets){
        if(android.os.Build.VERSION.SDK_INT>=30){Insets bars=insets.getInsets(WindowInsets.Type.systemBars()|WindowInsets.Type.displayCutout());safeLeft=bars.left;safeTop=bars.top;safeRight=bars.right;safeBottom=bars.bottom;}
        else{safeLeft=insets.getSystemWindowInsetLeft();safeTop=insets.getSystemWindowInsetTop();safeRight=insets.getSystemWindowInsetRight();safeBottom=insets.getSystemWindowInsetBottom();}
        updateLayoutScale(getWidth(),getHeight());invalidate();return insets;
    }
    public GameWorld world(){return world;}
    public void resume(){if(running)return;running=true;loop=new Thread(this,"haven-loop");loop.start();}
    public void pause(){running=false;try{if(loop!=null)loop.join(250);}catch(InterruptedException ignored){Thread.currentThread().interrupt();}}
    @Override public void run(){long last=System.nanoTime();while(running){long now=System.nanoTime();float dt=(now-last)/1_000_000_000f;last=now;world.update(dt);ui.update(dt);selectionPulse+=dt;for(GameWorld.Event e:world.drainEvents())handleEvent(e);ChronicleHistory.Entry latest=world.latestChronicle();if(latest!=null&&latest.selectionSeed!=lastNarrationSeed&&!chronicleBusy){lastNarrationSeed=latest.selectionSeed;post(this::requestNarration);}long ms=SystemClock.uptimeMillis();if(host!=null&&ms-lastAutosaveMs>30000){host.save(world,false);lastAutosaveMs=ms;}postInvalidate();try{Thread.sleep(16);}catch(InterruptedException e){return;}}}
    private void handleEvent(GameWorld.Event e){if(e==GameWorld.Event.GATHERED){audio.play(GameAudioManager.Cue.GATHER);haptic();}else if(e==GameWorld.Event.BUILT){audio.play(GameAudioManager.Cue.BUILD);tutorial.onBuildPlaced();haptic();}else if(e==GameWorld.Event.RAID){audio.play(GameAudioManager.Cue.RAID);haptic();}else if(e==GameWorld.Event.BOND_ACTIVATED)audio.play(GameAudioManager.Cue.RESONANCE);}

    @Override protected void onSizeChanged(int w,int h,int oldw,int oldh){super.onSizeChanged(w,h,oldw,oldh);updateLayoutScale(w,h);}
    private void updateLayoutScale(int w,int h){if(w<=0||h<=0)return;hud=HavenHudLayout.calculate(w,h,safeLeft,safeTop,safeRight,safeBottom,getResources().getDisplayMetrics().density);density=hud.scale;tile=dp(hud.compact?32:36);}
    @Override protected void onDraw(Canvas c){super.onDraw(c);synchronized(world){c.drawColor(Color.rgb(8,15,23));layoutControls();float top=worldTop(),bottom=worldBottom();clampCamera();c.save();c.clipRect(safeLeft,top,getWidth()-safeRight,bottom);drawWorld(c);drawNightTint(c,top,bottom);c.restore();drawTopBar(c);drawPawnRail(c);drawBottomBar(c);drawPanel(c);drawFeedback(c);drawTutorial(c);}}
    private void layoutControls(){
        float top=safeTop+dp(hud.compact?3:8),cardH=dp(hud.compact?44:58),left=safeLeft+dp(hud.compact?6:10),right=getWidth()-safeRight-dp(6);
        float controls=dp(hud.compact?184:202),gap=dp(hud.compact?4:6),cardW=Math.max(dp(68),Math.min(dp(hud.compact?132:154),(right-left-controls-gap*5)/3f)),x=left;
        for(int i=0;i<3;i++){pawnCards[i].set(x,top,x+cardW,top+cardH);x+=cardW+gap;}
        float r=right,h=dp(hud.compact?44:52);soundButton.set(r-dp(42),top,r,top+h);r-=dp(46);saveButton.set(r-dp(44),top,r,top+h);r-=dp(48);speedButton.set(r-dp(44),top,r,top+h);r-=dp(48);pauseButton.set(r-dp(44),top,r,top+h);
        float b=getHeight()-safeBottom-dp(6),y=b-dp(hud.compact?44:62),navLeft=safeLeft+dp(6),navGap=dp(hud.compact?4:6);float usable=getWidth()-safeRight-navLeft-dp(hud.compact?240:320),w=Math.max(dp(72),Math.min(dp(hud.compact?100:112),(usable-navGap*4)/5f));ordersButton.set(navLeft,y,navLeft+w,b);buildButton.set(ordersButton.right+navGap,y,ordersButton.right+navGap+w,b);workButton.set(buildButton.right+navGap,y,buildButton.right+navGap+w,b);chronicleButton.set(workButton.right+navGap,y,workButton.right+navGap+w,b);regionButton.set(chronicleButton.right+navGap,y,chronicleButton.right+navGap+w,b);
    }
    private float worldTop(){return safeTop+(hud==null?dp(74):hud.topBar);} private float worldBottom(){return getHeight()-safeBottom-(hud==null?dp(74):hud.bottomBar);}
    private void drawWorld(Canvas c){int minX=Math.max(0,(int)(camX-getWidth()/tile/2)-2),maxX=Math.min(GameWorld.WIDTH-1,(int)(camX+getWidth()/tile/2)+2),minY=Math.max(0,(int)(camY-(worldBottom()-worldTop())/tile/2)-2),maxY=Math.min(GameWorld.HEIGHT-1,(int)(camY+(worldBottom()-worldTop())/tile/2)+2);for(int x=minX;x<=maxX;x++)for(int y=minY;y<=maxY;y++)drawTile(c,x,y);drawCamp(c);drawColonyStructures(c);for(GameWorld.Enemy e:world.enemies)drawActor(c,"ENEMY",e.x,e.y,e.health/42f);long now=SystemClock.uptimeMillis();for(int i=0;i<world.colonists.size();i++)drawColonist(c,world.colonists.get(i),i,now);drawTileSelection(c);drawAreaPreview(c);drawFireParticles(c,now);}
    private void drawColonyStructures(Canvas c){
        for(ColonySystems.StockpileZone z:world.stockpiles){p.setColor(Color.argb(65,80,230,211));c.drawRect(wx(z.x),wy(z.y),wx(z.x+z.width),wy(z.y+z.height),p);p.setStyle(Paint.Style.STROKE);p.setStrokeWidth(dp(2));p.setColor(CYAN);c.drawRect(wx(z.x),wy(z.y),wx(z.x+z.width),wy(z.y+z.height),p);p.setStyle(Paint.Style.FILL);}
        for(ColonySystems.ResourcePile q:world.resourcePiles)drawObjectCentered(c,"RESOURCE_STACK",q.x,q.y,.62f);
        for(ColonySystems.Blueprint b:world.blueprints){if(!b.complete){drawObjectCentered(c,"BLUEPRINT",b.x+.5f,b.y+.5f,.85f);float f=Math.max(0,Math.min(1,b.workDone/b.workRequired));p.setColor(CYAN);c.drawRect(wx(b.x)+dp(4),wy(b.y+1)-dp(6),wx(b.x)+dp(4)+(tile-dp(8))*f,wy(b.y+1)-dp(3),p);}else if(b.type==ColonySystems.StructureType.BED)drawObjectCentered(c,"REST_POD",b.x+.5f,b.y+.5f,1f);else if(b.type==ColonySystems.StructureType.DOOR)drawObjectCentered(c,"DOOR",b.x+.5f,b.y+.5f,.9f);else if(b.type==ColonySystems.StructureType.FABRICATOR)drawObjectCentered(c,"FABRICATOR",b.x+.5f,b.y+.5f,1.15f);}
    }
    private void drawTile(Canvas c,int x,int y){float sx=wx(x),sy=wy(y);GameWorld.Tile type=world.tiles[x][y];Bitmap terrain=sprites.get(type==GameWorld.Tile.WATER?"ROCK_TERRAIN":"MEADOW_TERRAIN");if(terrain!=null){terrainSource.set(type==GameWorld.Tile.WATER?0:64,type==GameWorld.Tile.WATER?160:64,type==GameWorld.Tile.WATER?32:96,type==GameWorld.Tile.WATER?192:96);c.drawBitmap(terrain,terrainSource,new RectF(sx,sy,sx+tile+1,sy+tile+1),pixel);}else{p.setColor(type==GameWorld.Tile.WATER?Color.rgb(28,64,82):Color.rgb(50,87,67));c.drawRect(sx,sy,sx+tile+1,sy+tile+1,p);}if(type!=GameWorld.Tile.GRASS&&type!=GameWorld.Tile.WATER){String key=type==GameWorld.Tile.ROCK?"ORE":type==GameWorld.Tile.TREE||type==GameWorld.Tile.CRYSTAL?"CRYSTAL_TREE":type.name();drawObjectCentered(c,key,x+.5f,y+.5f,type==GameWorld.Tile.TREE?1.22f:type==GameWorld.Tile.CRYSTAL?1.05f:.82f);}if(world.walls[x][y])drawObjectCentered(c,"WALL",x+.5f,y+.5f,.92f);}
    private void drawCamp(Canvas c){drawObjectCentered(c,"CRASHED_POD",20,12,1.75f);drawObjectCentered(c,"SHELTER",17.7f,14.8f,1.7f);drawObjectCentered(c,"WORKBENCH",22.1f,15.2f,1.15f);drawObjectCentered(c,"CAMPFIRE",20.5f,15.7f,.85f);boolean young=false;for(GameWorld.Colonist colonist:world.colonists)young|=colonist.noncombatant();if(young)drawObjectCentered(c,"NURSERY",18.7f,16.2f,1.05f);if(world.beaconCharge>=0)drawObjectCentered(c,"BEACON",20.5f,14.2f,1.25f);}
    private void drawDesignation(Canvas c,String key,String id){String[] q=key.split(":");int x=Integer.parseInt(q[0]),y=Integer.parseInt(q[1]);p.setColor(Color.argb(110,85,237,215));c.drawRect(wx(x)+3,wy(y)+3,wx(x+1)-3,wy(y+1)-3,p);p.setStyle(Paint.Style.STROKE);p.setStrokeWidth(dp(2));p.setColor(CYAN);c.drawRect(wx(x)+3,wy(y)+3,wx(x+1)-3,wy(y+1)-3,p);p.setStyle(Paint.Style.FILL);text(c,id.substring(0,1).toUpperCase(Locale.US),wx(x+.5f),wy(y+.5f)+dp(6),dp(16),Color.WHITE,Paint.Align.CENTER,true);}
    private void drawObjectCentered(Canvas c,String key,float x,float y,float scale){Bitmap b=sprites.get(key);if(b==null)b=sprites.get("MISSING");float h=tile*scale,w=h*b.getWidth()/(float)b.getHeight();c.drawBitmap(b,null,new RectF(wx(x)-w/2,wy(y)-h/2,wx(x)+w/2,wy(y)+h/2),pixel);}
    private void drawColonist(Canvas c,GameWorld.Colonist a,int index,long now){if(!a.alive()){drawActor(c,"DOWN",a.x,a.y,0);return;}boolean melee=hasEnemyNear(a.x,a.y,1.65f);String action=melee?"melee":a.isWorking()?"using-tool":a.hasJob()?"walking":"idle",direction="south";if(a.hasJob()&&!action.equals("using-tool")){float dx=a.targetX()+.5f-a.x,dy=a.targetY()+.5f-a.y;if(Math.abs(dx)>Math.abs(dy))direction=dx>=0?"east":"west";else direction=dy>=0?"south":"north";}int count=action.equals("idle")?4:action.equals("walking")?8:9,frame=(int)(now/(action.equals("walking")?105:action.equals("idle")?240:90))%count;Bitmap b=sprites.get(AssetCatalog.colonistFrame(action,direction,frame));if(b==null)b=sprites.get("COLONIST");float s=tile*1.12f,l=wx(a.x)-s/2,t=wy(a.y)-s/2;if(index==ui.selectedColonist){p.setColor(Color.argb(135,105,239,219));c.drawCircle(wx(a.x),wy(a.y)+s*.27f,s*.46f+(float)Math.sin(selectionPulse*5)*2,p);}c.drawBitmap(b,null,new RectF(l,t,l+s,t+s),pixel);if(world.colonists.get(index).drafted){p.setStyle(Paint.Style.STROKE);p.setStrokeWidth(dp(2));p.setColor(AMBER);c.drawCircle(wx(a.x),wy(a.y),s*.55f,p);p.setStyle(Paint.Style.FILL);}drawHealth(c,l,t,s,a.health/100f);}
    private void drawTileSelection(Canvas c){if(selectedX<0)return;float in=3+(float)Math.sin(selectionPulse*6)*2;p.setStyle(Paint.Style.STROKE);p.setStrokeWidth(dp(3));p.setColor(ui.order==ColonyUiState.Order.ATTACK?Color.rgb(255,94,101):CYAN);c.drawRect(wx(selectedX)+in,wy(selectedY)+in,wx(selectedX+1)-in,wy(selectedY+1)-in,p);p.setStyle(Paint.Style.FILL);}
    private void drawAreaPreview(Canvas c){if(!area.active())return;int fill=ui.designateMode==ColonyUiState.DesignateMode.HAUL?Color.argb(92,250,188,80):Color.argb(92,105,239,219);int edge=ui.designateMode==ColonyUiState.DesignateMode.HAUL?AMBER:CYAN;for(AreaDesignation.TilePoint q:area.tiles()){p.setColor(fill);c.drawRect(wx(q.x)+dp(2),wy(q.y)+dp(2),wx(q.x+1)-dp(2),wy(q.y+1)-dp(2),p);p.setStyle(Paint.Style.STROKE);p.setStrokeWidth(dp(2));p.setColor(edge);c.drawRect(wx(q.x)+dp(2),wy(q.y)+dp(2),wx(q.x+1)-dp(2),wy(q.y+1)-dp(2),p);p.setStyle(Paint.Style.FILL);}drawObjectCentered(c,"MULTI",area.left()+.5f,area.top()+.5f,.72f);}
    private boolean hasEnemyNear(float x,float y,float r){for(GameWorld.Enemy e:world.enemies){float dx=x-e.x,dy=y-e.y;if(dx*dx+dy*dy<r*r)return true;}return false;}
    private void drawActor(Canvas c,String key,float x,float y,float hp){float s=tile*.78f,l=wx(x)-s/2,t=wy(y)-s/2;Bitmap b=sprites.get(key);if(b==null)b=sprites.get("MISSING");c.drawBitmap(b,null,new RectF(l,t,l+s,t+s),pixel);drawHealth(c,l,t,s,hp);}
    private void drawHealth(Canvas c,float l,float t,float s,float hp){if(hp<.99f){p.setColor(Color.rgb(55,20,25));c.drawRect(l,t-dp(6),l+s,t-dp(2),p);p.setColor(Color.rgb(107,230,137));c.drawRect(l,t-dp(6),l+s*Math.max(0,hp),t-dp(2),p);}}
    private void drawFireParticles(Canvas c,long now){for(int i=0;i<6;i++){float phase=((now/16+i*19)%90)/90f,x=wx(20.5f)+(float)Math.sin(i*3.7+phase*8)*tile*.12f,y=wy(15.55f)-phase*tile*.65f;p.setColor(Color.argb((int)(190*(1-phase)),255,i%2==0?176:103,36));c.drawCircle(x,y,Math.max(1,tile*.035f*(1-phase)),p);}}
    private void drawNightTint(Canvas c,float top,float bottom){if(!world.isNight())return;p.setColor(Color.argb(105,10,13,48));c.drawRect(0,top,getWidth(),bottom,p);}

    private void drawTopBar(Canvas c){p.setColor(Color.rgb(9,23,32));c.drawRect(0,0,getWidth(),worldTop(),p);for(int i=0;i<3;i++)drawPawnCard(c,i);drawIconButton(c,pauseButton,world.paused?"▶":"Ⅱ",world.paused);drawIconButton(c,speedButton,"×"+(int)world.speed,false);drawIconButton(c,saveButton,"SAVE",false);drawIconButton(c,soundButton,audio.isMuted()?"OFF":"SND",audio.isMuted());float y=worldTop()-dp(5),meta=dp(hud.compact?11:13);text(c,"DAY "+world.day+(world.isNight()?"  NIGHTWATCH":"  SUNWARD"),safeLeft+dp(10),y,meta,MUTED,Paint.Align.LEFT,true);text(c,"WOOD "+world.wood+"   STONE "+world.stone+"   FOOD "+world.food+"   CRYSTAL "+world.crystal,getWidth()-safeRight-dp(10),y,meta,INK,Paint.Align.RIGHT,true);}
    private void drawPawnRail(Canvas c){}
    private void drawPawnCard(Canvas c,int i){GameWorld.Colonist a=world.colonists.get(Math.min(i,world.colonists.size()-1));boolean on=i==ui.selectedColonist;p.setColor(on?Color.rgb(31,78,82):PANEL_2);c.drawRoundRect(pawnCards[i],dp(7),dp(7),p);p.setStyle(Paint.Style.STROKE);p.setStrokeWidth(dp(on?2:1));p.setColor(on?CYAN:Color.rgb(68,101,110));c.drawRoundRect(pawnCards[i],dp(7),dp(7),p);p.setStyle(Paint.Style.FILL);float inset=dp(hud.compact?5:6),portrait=pawnCards[i].height()-inset*2;Bitmap b=sprites.get("COLONIST");c.drawBitmap(b,null,new RectF(pawnCards[i].left+inset,pawnCards[i].top+inset,pawnCards[i].left+inset+portrait,pawnCards[i].bottom-inset),pixel);float x=pawnCards[i].left+portrait+dp(hud.compact?9:11);text(c,a.name,x,pawnCards[i].top+dp(hud.compact?18:21),dp(hud.compact?13:15),INK,Paint.Align.LEFT,true);if(hud.compact){text(c,ui.profile(i).role,x,pawnCards[i].top+dp(32),dp(9),MUTED,Paint.Align.LEFT,false);meter(c,x,pawnCards[i].bottom-dp(5),pawnCards[i].right-x-dp(6),a.health/100f,Color.rgb(104,221,132));}else{text(c,ui.profile(i).role,x,pawnCards[i].top+dp(38),dp(11),MUTED,Paint.Align.LEFT,false);meter(c,x,pawnCards[i].bottom-dp(10),pawnCards[i].right-x-dp(8),a.health/100f,Color.rgb(104,221,132));}if(world.colonists.get(i).drafted)text(c,"D",pawnCards[i].right-dp(10),pawnCards[i].top+dp(16),dp(11),AMBER,Paint.Align.CENTER,true);}
    private void drawBottomBar(Canvas c){float top=worldBottom();p.setColor(Color.rgb(9,23,32));c.drawRect(0,top,getWidth(),getHeight(),p);drawNav(c,ordersButton,"ORDERS",ui.panel==ColonyUiState.Panel.ORDERS&&!chronicleOpen);drawNav(c,buildButton,"ARCHITECT",ui.panel==ColonyUiState.Panel.ARCHITECT&&!chronicleOpen);drawNav(c,workButton,"WORK",ui.panel==ColonyUiState.Panel.WORK&&!chronicleOpen);drawNav(c,chronicleButton,"CHRONICLE",chronicleOpen);drawNav(c,regionButton,"REGION",false);if(ui.designateMode!=ColonyUiState.DesignateMode.NONE){float r=getWidth()-safeRight-dp(8),b=getHeight()-safeBottom-dp(8),t=b-dp(hud.compact?52:62);cancelButton.set(r-dp(112),t,r,b);confirmButton.set(r-dp(232),t,r-dp(120),b);drawNav(c,cancelButton,"CANCEL",false);drawNav(c,confirmButton,"CONFIRM",area.active());String label=designationLabel()+"  "+validDesignationCount()+" / "+area.tiles().size();text(c,label,confirmButton.left-dp(10),b-dp(30),dp(12),CYAN,Paint.Align.RIGHT,true);text(c,"Drag map • camera locked",confirmButton.left-dp(10),b-dp(13),dp(10),MUTED,Paint.Align.RIGHT,false);}else{confirmButton.setEmpty();cancelButton.setEmpty();String hint=ui.selectedBuild!=null?"DESIGNATE "+ui.selectedBuild.name.toUpperCase(Locale.US):ui.order==ColonyUiState.Order.INSPECT?"TAP SETTLER OR TILE":"ORDER: "+ui.order.name();float laneLeft=regionButton.right+dp(12);if(getWidth()-safeRight-laneLeft>dp(140))text(c,hint,getWidth()-safeRight-dp(10),ordersButton.centerY()+dp(5),dp(hud.compact?11:13),ui.selectedBuild!=null?AMBER:CYAN,Paint.Align.RIGHT,true);}}
    private void drawNav(Canvas c,RectF r,String label,boolean active){p.setColor(active?Color.rgb(35,123,119):PANEL_2);c.drawRoundRect(r,dp(9),dp(9),p);p.setStyle(Paint.Style.STROKE);p.setStrokeWidth(dp(2));p.setColor(active?CYAN:Color.rgb(70,105,113));c.drawRoundRect(r,dp(9),dp(9),p);p.setStyle(Paint.Style.FILL);float size=dp(hud!=null&&hud.compact?(label.length()>9?11:13):15);text(c,label,r.centerX(),r.centerY()+size*.34f,size,Color.WHITE,Paint.Align.CENTER,true);}
    private void drawIconButton(Canvas c,RectF r,String label,boolean active){p.setColor(active?Color.rgb(102,69,36):PANEL_2);c.drawRoundRect(r,dp(9),dp(9),p);text(c,label,r.centerX(),r.centerY()+dp(5),dp(label.length()>3?12:18),active?AMBER:INK,Paint.Align.CENTER,true);}

    private RectF calculatePanelFrame(ColonyUiState.Panel panel){float l=safeLeft+dp(8),worldT=worldTop()+dp(6),maxBottom=worldBottom()-dp(6),available=maxBottom-worldT,wanted=dp(panel==ColonyUiState.Panel.WORK?(hud.compact?182:270):(hud.compact?176:190)),height=Math.min(wanted,available),t=worldT+(available-height)/2f,r=Math.min(getWidth()-safeRight-dp(8),l+dp(panel==ColonyUiState.Panel.WORK?650:720));return new RectF(l,t,r,t+height);}
    private void drawPanel(Canvas c){if(chronicleOpen){panelFrame.setEmpty();drawChronicle(c);return;}if(ui.panel==ColonyUiState.Panel.NONE){panelFrame.setEmpty();drawInspector(c);return;}if(ui.selectedColonist<0)ui.selectedColonist=0;panelFrame.set(calculatePanelFrame(ui.panel));float l=panelFrame.left,t=panelFrame.top,r=panelFrame.right,b=panelFrame.bottom;p.setColor(Color.argb(247,13,30,41));c.drawRoundRect(panelFrame,dp(12),dp(12),p);closePanel.set(r-dp(44),t+dp(6),r-dp(5),t+dp(45));drawIconButton(c,closePanel,"×",false);if(ui.panel==ColonyUiState.Panel.ORDERS)drawOrders(c,l,t,r,b);else if(ui.panel==ColonyUiState.Panel.ARCHITECT)drawArchitect(c,l,t,r,b);else drawWork(c,l,t,r,b);}
    private void drawChronicle(Canvas c){
        int tension=world.chronicleTension;float l=safeLeft+dp(12),r=getWidth()-safeRight-dp(12),t=worldTop()+dp(8),b=worldBottom()-dp(8);p.setColor(Color.argb(250,13,30,41));c.drawRoundRect(l,t,r,b,dp(14),dp(14),p);
        closePanel.set(r-dp(50),t+dp(7),r-dp(6),t+dp(51));drawIconButton(c,closePanel,"×",false);narrateButton.set(r-dp(184),b-dp(58),r-dp(16),b-dp(10));drawNav(c,narrateButton,chronicleBusy?"NARRATING…":"REFRESH STORY",chronicleBusy);
        text(c,"HAVEN CHRONICLE",l+dp(18),t+dp(28),dp(17),CYAN,Paint.Align.LEFT,true);text(c,"TENSION  "+tension+" / 100",l+dp(18),t+dp(52),dp(13),tension>65?AMBER:INK,Paint.Align.LEFT,true);meter(c,l+dp(146),t+dp(44),dp(170),tension/100f,tension>65?Color.rgb(246,108,91):CYAN);
        float split=l+(r-l)*.66f;String source="SOURCE  "+chronicleSource;text(c,source,split-dp(12),t+dp(28),dp(12),chronicleSource.equals("GROK")?AMBER:MUTED,Paint.Align.RIGHT,true);
        text(c,chronicleTitle,l+dp(18),t+dp(82),dp(17),INK,Paint.Align.LEFT,true);drawWrapped(c,chronicleStory,l+dp(18),t+dp(105),split-l-dp(34),dp(14),INK,6);
        text(c,"EVENT HISTORY",split+dp(10),t+dp(58),dp(13),CYAN,Paint.Align.LEFT,true);float y=t+dp(82);List<ChronicleHistory.Entry> entries=world.chronicleHistory.entries();int shown=0;for(int i=entries.size()-1;i>=0&&shown<6;i--){ChronicleHistory.Entry entry=entries.get(i);drawWrapped(c,"• DAY "+entry.day+"  "+entry.headline,split+dp(10),y,r-split-dp(28),dp(11),INK,2);y+=dp(35);shown++;}if(entries.isEmpty())text(c,"No incidents recorded",split+dp(10),y,dp(11),MUTED,Paint.Align.LEFT,false);
        text(c,chronicleStatus,l+dp(18),b-dp(24),dp(11),MUTED,Paint.Align.LEFT,false);
    }
    private void drawOrders(Canvas c,float l,float t,float r,float b){text(c,"CONTEXT ORDERS",l+dp(18),t+dp(30),dp(16),CYAN,Paint.Align.LEFT,true);String[] names={"INSPECT","HARVEST","HAUL","MOVE","ATTACK"};float step=Math.min(dp(118),(r-l-dp(32))/5f);for(int i=0;i<5;i++){float x=l+dp(16)+i*step;actionButtons[i].set(x,t+dp(48),x+step-dp(7),t+dp(102));drawNav(c,actionButtons[i],names[i],ui.order.ordinal()==i);}GameWorld.Colonist colonist=world.colonists.get(ui.selectedColonist);draftButton.set(l+dp(16),t+dp(116),l+dp(178),b-dp(14));drawNav(c,draftButton,colonist.drafted?"UNDRAFT":"DRAFT",colonist.drafted);text(c,colonist.drafted?"Direct movement and attacks are armed.":"Harvest and haul support area designation.",l+dp(196),b-dp(32),dp(13),MUTED,Paint.Align.LEFT,false);}
    private void drawArchitect(Canvas c,float l,float t,float r,float b){text(c,"ARCHITECT",l+dp(18),t+dp(28),dp(16),CYAN,Paint.Align.LEFT,true);String[] cats={"STRUCTURE","FURNITURE","ZONES","PRODUCTION"};for(int i=0;i<4;i++){float x=l+dp(16)+i*dp(144);categoryButtons[i].set(x,t+dp(40),x+dp(136),t+dp(88));drawNav(c,categoryButtons[i],cats[i],ui.category.ordinal()==i);}ColonyUiState.BuildItem[] items=ui.items(ui.category);for(int i=0;i<itemButtons.length;i++)itemButtons[i].setEmpty();for(int i=0;i<items.length;i++){float x=l+dp(16)+i*dp(206);itemButtons[i].set(x,t+dp(102),Math.min(x+dp(196),r-dp(12)),b-dp(12));boolean active=ui.selectedBuild==items[i];p.setColor(active?Color.rgb(40,108,104):PANEL_2);c.drawRoundRect(itemButtons[i],dp(9),dp(9),p);text(c,items[i].name,itemButtons[i].left+dp(10),itemButtons[i].top+dp(21),dp(14),INK,Paint.Align.LEFT,true);text(c,items[i].detail,itemButtons[i].left+dp(10),itemButtons[i].top+dp(39),dp(10),MUTED,Paint.Align.LEFT,false);text(c,items[i].cost().isEmpty()?"ZONE":items[i].cost(),itemButtons[i].right-dp(9),itemButtons[i].bottom-dp(9),dp(11),AMBER,Paint.Align.RIGHT,true);}}
    private void drawWork(Canvas c,float l,float t,float r,float b){text(c,"WORK PRIORITY  •  TAP TO CYCLE 0–4",l+dp(18),t+dp(18),dp(13),CYAN,Paint.Align.LEFT,true);String[] jobs={"BUILD","HARVEST","CRAFT","GUARD"};ColonySystems.WorkType[] types={ColonySystems.WorkType.CONSTRUCT,ColonySystems.WorkType.GATHER,ColonySystems.WorkType.CRAFT,ColonySystems.WorkType.EMERGENCY};float nameW=dp(126),cell=dp(92);for(int j=0;j<4;j++)text(c,jobs[j],l+nameW+j*cell+cell/2,t+dp(38),dp(10),MUTED,Paint.Align.CENTER,true);for(int i=0;i<3;i++){float y=t+dp(44)+i*dp(44);GameWorld.Colonist colonist=world.colonists.get(i);text(c,colonist.name,l+dp(18),y+dp(28),dp(13),i==ui.selectedColonist?CYAN:INK,Paint.Align.LEFT,true);for(int j=0;j<4;j++){RectF q=workCell(i,j,l,t);int v=colonist.priority(types[j]);p.setColor(v==0?Color.rgb(43,49,54):v==1?Color.rgb(45,128,94):Color.rgb(38,72,83));c.drawRoundRect(q,dp(7),dp(7),p);text(c,v==0?"—":String.valueOf(v),q.centerX(),q.centerY()+dp(5),dp(15),v==1?Color.WHITE:INK,Paint.Align.CENTER,true);}}}
    private RectF workCell(int row,int col,float l,float t){float nameW=dp(126),cell=dp(92),y=t+dp(44)+row*dp(44);return new RectF(l+nameW+col*cell+dp(4),y,l+nameW+(col+1)*cell-dp(4),y+dp(44));}
    private void drawInspector(Canvas c){if(ui.selectedColonist<0||ui.selectedColonist>=world.colonists.size())return;GameWorld.Colonist a=world.colonists.get(ui.selectedColonist);ColonyUiState.Profile q=ui.profile(ui.selectedColonist);float r=getWidth()-safeRight-dp(8),l=r-dp(hud.compact?260:310),t=worldTop()+dp(6),b=hud.compact?Math.min(worldBottom()-dp(6),t+dp(150)):worldBottom()-dp(8);p.setColor(Color.argb(238,13,30,41));c.drawRoundRect(l,t,r,b,dp(11),dp(11),p);inspectClose.set(r-dp(44),t+dp(4),r-dp(4),t+dp(44));drawIconButton(c,inspectClose,"×",false);if(hud.compact){text(c,a.name,l+dp(11),t+dp(20),dp(15),CYAN,Paint.Align.LEFT,true);text(c,q.role+(a.drafted?" • DRAFTED":""),l+dp(11),t+dp(36),dp(9),MUTED,Paint.Align.LEFT,false);float mid=l+(r-l)/2;statBar(c,l+dp(11),t+dp(52),mid-l-dp(17),"HP",a.health/100f,Color.rgb(108,225,136));statBar(c,mid+dp(3),t+dp(52),r-mid-dp(13),"FOCUS",a.mood/100f,AMBER);text(c,"Food "+(int)a.hunger+"  Rest "+(int)a.energy+"  Comfort "+(int)a.comfort,l+dp(11),t+dp(80),dp(10),INK,Paint.Align.LEFT,false);String trait=a.traits.isEmpty()?"Unmarked":a.traits.iterator().next().name().replace('_',' ');text(c,trait,l+dp(11),t+dp(101),dp(10),INK,Paint.Align.LEFT,true);ColonySystems.Skill[] shown={ColonySystems.Skill.BUILD,ColonySystems.Skill.GATHER,ColonySystems.Skill.CRAFT,ColonySystems.Skill.COMBAT};for(int i=0;i<shown.length;i++){float x=l+dp(11)+(i%2)*dp(120),y=t+dp(122)+(i/2)*dp(18);text(c,shown[i].name()+" "+a.skill(shown[i]),x,y,dp(10),i==0?AMBER:INK,Paint.Align.LEFT,true);}}else{text(c,a.name,l+dp(14),t+dp(24),dp(18),CYAN,Paint.Align.LEFT,true);text(c,q.role+(a.drafted?"  •  DRAFTED":""),l+dp(14),t+dp(43),dp(11),MUTED,Paint.Align.LEFT,false);float mid=l+(r-l)/2;statBar(c,l+dp(14),t+dp(62),mid-l-dp(24),"HEALTH",a.health/100f,Color.rgb(108,225,136));statBar(c,mid+dp(4),t+dp(62),r-mid-dp(18),"FOCUS",a.mood/100f,AMBER);text(c,"NEEDS  Food "+(int)a.hunger+"  Rest "+(int)a.energy+"  Comfort "+(int)a.comfort,l+dp(14),t+dp(96),dp(11),INK,Paint.Align.LEFT,false);String trait=a.traits.isEmpty()?"Unmarked":a.traits.iterator().next().name().replace('_',' ');String thought=a.thoughts.isEmpty()?"Steady at the haven":a.thoughts.get(0).label;text(c,trait+"  •  "+thought,l+dp(14),t+dp(119),dp(11),INK,Paint.Align.LEFT,false);ColonySystems.Skill[] shown={ColonySystems.Skill.BUILD,ColonySystems.Skill.GATHER,ColonySystems.Skill.CRAFT,ColonySystems.Skill.COMBAT};for(int i=0;i<shown.length;i++){float x=l+dp(14)+(i%2)*dp(145),y=t+dp(143)+(i/2)*dp(22);text(c,shown[i].name()+" "+a.skill(shown[i]),x,y,dp(11),i==0?AMBER:INK,Paint.Align.LEFT,true);}}}
    private void statBar(Canvas c,float x,float y,float w,String label,float value,int color){text(c,label,x,y,dp(11),MUTED,Paint.Align.LEFT,true);meter(c,x,y+dp(9),w,value,color);text(c,String.valueOf((int)(value*100)),x+w,y,dp(11),INK,Paint.Align.RIGHT,true);}
    private void meter(Canvas c,float x,float y,float w,float v,int color){p.setColor(Color.rgb(41,55,61));c.drawRoundRect(x,y,x+w,y+dp(6),dp(3),dp(3),p);p.setColor(color);c.drawRoundRect(x,y,x+w*Math.max(0,Math.min(1,v)),y+dp(6),dp(3),dp(3),p);}

    private void drawFeedback(Canvas c){if(ui.toastSeconds>0){float w=Math.min(dp(460),getWidth()-dp(40)),y=worldBottom()-dp(46);p.setColor(Color.argb(232,15,32,41));c.drawRoundRect(getWidth()/2-w/2,y-dp(26),getWidth()/2+w/2,y+dp(14),dp(10),dp(10),p);text(c,ui.toast,getWidth()/2,y,dp(14),INK,Paint.Align.CENTER,true);}if(world.messageTime>0){text(c,world.message,getWidth()/2,worldTop()+dp(28),dp(14),Color.WHITE,Paint.Align.CENTER,true);}if(world.beaconCharge>=0&&world.result==GameWorld.Result.PLAYING)text(c,"BEACON HOLD  "+Math.max(0,(int)Math.ceil(world.beaconCharge))+"s",getWidth()/2,worldTop()+dp(53),dp(18),CYAN,Paint.Align.CENTER,true);if(world.result!=GameWorld.Result.PLAYING){p.setColor(Color.argb(225,4,10,17));c.drawRect(0,0,getWidth(),getHeight(),p);text(c,world.result==GameWorld.Result.WON?"HAVEN SECURED":"COLONY LOST",getWidth()/2,getHeight()/2,dp(42),world.result==GameWorld.Result.WON?CYAN:Color.rgb(255,100,100),Paint.Align.CENTER,true);text(c,"Tap to begin a new landing",getWidth()/2,getHeight()/2+dp(42),dp(20),INK,Paint.Align.CENTER,false);}}
    private void drawTutorial(Canvas c){if(!tutorial.visible()||chronicleOpen)return;float l=safeLeft+dp(8),t=worldTop()+dp(6),r=Math.min(getWidth()-safeRight-dp(8),l+dp(hud.compact?600:630)),b=t+dp(hud.compact?58:80);p.setColor(Color.argb(245,12,29,39));c.drawRoundRect(l,t,r,b,dp(10),dp(10),p);if(hud.compact){text(c,tutorial.title(),l+dp(12),t+dp(20),dp(12),CYAN,Paint.Align.LEFT,true);text(c,tutorial.instruction(),l+dp(12),t+dp(41),dp(11),Color.WHITE,Paint.Align.LEFT,false);tutorialSkip.set(r-dp(78),t+dp(7),r-dp(7),b-dp(7));drawNav(c,tutorialSkip,"SKIP",false);}else{text(c,tutorial.title(),l+dp(16),t+dp(25),dp(14),CYAN,Paint.Align.LEFT,true);text(c,tutorial.instruction(),l+dp(16),t+dp(54),dp(14),Color.WHITE,Paint.Align.LEFT,false);tutorialSkip.set(r-dp(88),t+dp(11),r-dp(10),t+dp(57));drawNav(c,tutorialSkip,"SKIP",false);}}

    @Override public boolean onTouchEvent(MotionEvent e){
        if(ui.designateMode==ColonyUiState.DesignateMode.NONE)scale.onTouchEvent(e);
        float x=e.getX(),y=e.getY();int act=e.getActionMasked();
        if(act==MotionEvent.ACTION_DOWN&&tutorial.visible()&&tutorialSkip.contains(x,y)){tutorial.skip();tap();return true;}
        if(world.result!=GameWorld.Result.PLAYING&&act==MotionEvent.ACTION_UP){performClick();world=new GameWorld(84621L);selectedX=selectedY=-1;cancelDesignation(false);if(host!=null)host.save(world,false);return true;}
        if(act==MotionEvent.ACTION_DOWN){
            if(handleUiDown(x,y))return true;
            lastX=downX=x;lastY=downY=y;dragging=true;moved=false;
            if(ui.designateMode!=ColonyUiState.DesignateMode.NONE&&inWorld(y)){int[] q=tileAt(x,y);area.begin(q[0],q[1],designationShape());designating=true;invalidate();}
            return true;
        }
        if(act==MotionEvent.ACTION_MOVE&&dragging&&!scale.isInProgress()){
            if(designating){int[] q=tileAt(x,y);area.update(q[0],q[1]);moved=true;invalidate();return true;}
            float dx=x-lastX,dy=y-lastY;if(Math.abs(x-downX)+Math.abs(y-downY)>dp(10))moved=true;if(moved){camX-=dx/tile;camY-=dy/tile;tutorial.onPan();}lastX=x;lastY=y;return true;
        }
        if((act==MotionEvent.ACTION_UP||act==MotionEvent.ACTION_CANCEL)&&dragging){dragging=false;if(designating){designating=false;if(act==MotionEvent.ACTION_CANCEL)area.clear();else{int[] q=tileAt(x,y);area.update(q[0],q[1]);performClick();haptic();}invalidate();return true;}if(!moved&&inWorld(y)){performClick();handleWorldTap(x,y);}return true;}
        return true;
    }
    @Override public boolean performClick(){super.performClick();return true;}
    private boolean handleUiDown(float x,float y){
        if(chronicleOpen){
            if(closePanel.contains(x,y)){chronicleOpen=false;tap();return true;}
            if(narrateButton.contains(x,y)){requestNarration();tap();return true;}
            return true;
        }
        if(confirmButton.contains(x,y)){confirmDesignation();tap();return true;}
        if(cancelButton.contains(x,y)){cancelDesignation(true);return true;}
        for(int i=0;i<3;i++)if(pawnCards[i].contains(x,y)){selectColonist(i);return true;}
        if(pauseButton.contains(x,y)){world.paused=!world.paused;tap();return true;}
        if(speedButton.contains(x,y)){world.speed=world.speed==1?2:world.speed==2?4:1;tap();return true;}
        if(saveButton.contains(x,y)){if(host!=null)host.save(world,true);tap();return true;}
        if(soundButton.contains(x,y)){audio.toggleMuted();tap();return true;}
        if(ordersButton.contains(x,y)){togglePanel(ColonyUiState.Panel.ORDERS);return true;}
        if(buildButton.contains(x,y)){togglePanel(ColonyUiState.Panel.ARCHITECT);tutorial.onArchitectOpened();return true;}
        if(workButton.contains(x,y)){togglePanel(ColonyUiState.Panel.WORK);tutorial.onWorkOpened();return true;}
        if(chronicleButton.contains(x,y)){chronicleOpen=true;ui.panel=ColonyUiState.Panel.NONE;ui.selectedColonist=-1;cancelDesignation(false);tap();return true;}
        if(regionButton.contains(x,y)){cancelDesignation(false);if(host!=null)host.openRegion(world);tap();return true;}
        if(closePanel.contains(x,y)||inspectClose.contains(x,y)){ui.panel=ColonyUiState.Panel.NONE;ui.selectedColonist=-1;tap();return true;}
        if(ui.panel==ColonyUiState.Panel.ORDERS){
            for(int i=0;i<5;i++)if(actionButtons[i].contains(x,y)){ui.order=ColonyUiState.Order.values()[i];ui.selectedBuild=null;if(i==1)startDesignation(ColonyUiState.DesignateMode.HARVEST);else if(i==2)startDesignation(ColonyUiState.DesignateMode.HAUL);else cancelDesignation(false);ui.announce(namesForOrder(i));tap();return true;}
            if(draftButton.contains(x,y)){GameWorld.Colonist q=world.colonists.get(ui.selectedColonist);boolean drafted=!q.drafted;world.setDrafted(ui.selectedColonist,drafted);tutorial.onDraft();ui.announce(drafted?q.name+" ready for direct orders":q.name+" returned to colony duty");tap();return true;}
        }
        if(ui.panel==ColonyUiState.Panel.ARCHITECT){
            for(int i=0;i<4;i++)if(categoryButtons[i].contains(x,y)){ui.category=ColonyUiState.BuildCategory.values()[i];ui.selectedBuild=null;cancelDesignation(false);tap();return true;}
            ColonyUiState.BuildItem[] items=ui.items(ui.category);for(int i=0;i<items.length;i++)if(itemButtons[i].contains(x,y)){ui.selectedBuild=items[i];ui.order=ColonyUiState.Order.INSPECT;startDesignation(ColonyUiState.DesignateMode.BUILD);ui.announce("Drag on the map to preview "+items[i].name);tap();return true;}
        }
        if(ui.panel==ColonyUiState.Panel.WORK){RectF frame=calculatePanelFrame(ui.panel);float l=frame.left,t=frame.top;ColonySystems.WorkType[] types={ColonySystems.WorkType.CONSTRUCT,ColonySystems.WorkType.GATHER,ColonySystems.WorkType.CRAFT,ColonySystems.WorkType.EMERGENCY};String[] labels={"BUILD","HARVEST","CRAFT","GUARD"};for(int i=0;i<3;i++)for(int j=0;j<4;j++)if(workCell(i,j,l,t).contains(x,y)){GameWorld.Colonist c=world.colonists.get(i);int value=c.priority(types[j]);value=value>=4?0:value+1;if(world.setWorkPriority(i,j,value)){ui.announce(c.name+" "+labels[j]+" priority "+value);if(host!=null)host.save(world,false);}tutorial.onWorkPriority();tap();return true;}}
        return false;
    }
    private String namesForOrder(int i){return i==0?"Inspect mode":i==1?"Drag a harvest area":i==2?"Drag over loose supplies to haul":i==3?"Tap terrain to move":"Tap a hostile to focus attack";}
    private void togglePanel(ColonyUiState.Panel panel){chronicleOpen=false;ui.panel=ui.panel==panel?ColonyUiState.Panel.NONE:panel;tap();}
    private void selectColonist(int i){ui.selectedColonist=i;ui.panel=ColonyUiState.Panel.NONE;tutorial.onColonistSelected();ui.announce(world.colonists.get(i).name+" selected");tap();}
    private void handleWorldTap(float x,float y){int tx=(int)((x-getWidth()/2f)/tile+camX),ty=(int)((y-worldTop()-(worldBottom()-worldTop())/2f)/tile+camY);if(tx<0||ty<0||tx>=GameWorld.WIDTH||ty>=GameWorld.HEIGHT)return;selectedX=tx;selectedY=ty;selectionPulse=0;if(ui.selectedBuild!=null){placeBuild(tx,ty);return;}int pawn=colonistAt(tx,ty);if(pawn>=0&&ui.order==ColonyUiState.Order.INSPECT){selectColonist(pawn);return;}if(ui.order==ColonyUiState.Order.GATHER){world.setTool(GameWorld.Tool.GATHER);boolean valid=world.canGather(tx,ty);world.command(tx,ty);if(valid)tutorial.onGather();}else if(ui.order==ColonyUiState.Order.MOVE){if(!world.colonists.get(ui.selectedColonist).drafted){ui.announce("Draft "+world.colonists.get(ui.selectedColonist).name+" before direct movement");return;}boolean invoked=world.commandColonist(ui.selectedColonist,"move",tx,ty);if(!invoked)ui.announce("Move marker set — direct movement awaits simulation support");else ui.announce("Move order confirmed");tutorial.onOrder();}else if(ui.order==ColonyUiState.Order.ATTACK){if(!world.colonists.get(ui.selectedColonist).drafted){ui.announce("Draft a settler before issuing attack orders");return;}boolean invoked=world.commandColonist(ui.selectedColonist,"attack",tx,ty);ui.announce(invoked?"Hostile marked":"Focus marker set — settlers engage nearby threats");tutorial.onOrder();}else{ColonySystems.Blueprint structure=world.structureAt(tx,ty);if(structure!=null&&structure.complete&&structure.type==ColonySystems.StructureType.FABRICATOR){ColonySystems.ProductionJob job=world.queueProduction(structure.id,ColonySystems.Recipe.NUTRIENT_PACK,1);ui.announce(job==null?"Need 5 food for a nutrient pack":"Nutrient pack queued at fabricator");}else ui.announce("Terrain "+tx+", "+ty+" selected");}}
    private int colonistAt(int tx,int ty){for(int i=0;i<world.colonists.size();i++){GameWorld.Colonist a=world.colonists.get(i);if((int)a.x==tx&&(int)a.y==ty)return i;}return -1;}
    private void startDesignation(ColonyUiState.DesignateMode mode){ui.designateMode=mode;area.clear();ui.panel=ColonyUiState.Panel.NONE;selectedX=selectedY=-1;}
    private void cancelDesignation(boolean announce){ui.designateMode=ColonyUiState.DesignateMode.NONE;area.clear();designating=false;if(announce){ui.selectedBuild=null;ui.announce("Designation cancelled");tap();}}
    private boolean inWorld(float y){return y>worldTop()&&y<worldBottom();}
    private int[] tileAt(float x,float y){return new int[]{Math.max(0,Math.min(GameWorld.WIDTH-1,(int)((x-getWidth()/2f)/tile+camX))),Math.max(0,Math.min(GameWorld.HEIGHT-1,(int)((y-worldTop()-(worldBottom()-worldTop())/2f)/tile+camY)))};}
    private AreaDesignation.Shape designationShape(){if(ui.designateMode==ColonyUiState.DesignateMode.BUILD&&ui.selectedBuild!=null&&(ui.selectedBuild.id.equals("barrier")||ui.selectedBuild.id.equals("door")))return AreaDesignation.Shape.LINE;return AreaDesignation.Shape.RECTANGLE;}
    private String designationLabel(){if(ui.designateMode==ColonyUiState.DesignateMode.HARVEST)return "HARVEST";if(ui.designateMode==ColonyUiState.DesignateMode.HAUL)return "PICKUP";return ui.selectedBuild==null?"BUILD":ui.selectedBuild.name.toUpperCase(Locale.US);}
    private boolean loosePileAt(int x,int y){for(ColonySystems.ResourcePile pile:world.resourcePiles)if(!pile.stored&&(int)pile.x==x&&(int)pile.y==y)return true;return false;}
    private boolean buildTileValid(AreaDesignation.TilePoint q){if(ui.selectedBuild==null)return false;if(ui.selectedBuild.id.equals("storage"))return world.tiles[q.x][q.y]!=GameWorld.Tile.WATER;if(ui.selectedBuild.id.equals("beacon"))return area.tiles().size()==1&&world.tiles[q.x][q.y]==GameWorld.Tile.GRASS&&!world.walls[q.x][q.y];return world.tiles[q.x][q.y]==GameWorld.Tile.GRASS&&!world.walls[q.x][q.y]&&world.structureAt(q.x,q.y)==null;}
    private int validDesignationCount(){int count=0;for(AreaDesignation.TilePoint q:area.tiles())if(ui.designateMode==ColonyUiState.DesignateMode.HARVEST?world.canGather(q.x,q.y):ui.designateMode==ColonyUiState.DesignateMode.HAUL?loosePileAt(q.x,q.y):buildTileValid(q))count++;return count;}
    private void confirmDesignation(){
        if(!area.active()){ui.announce("Drag across one or more map tiles first");return;}
        int done=0;
        if(ui.designateMode==ColonyUiState.DesignateMode.BUILD&&ui.selectedBuild!=null&&ui.selectedBuild.id.equals("storage")){ColonySystems.StockpileZone zone=world.addStockpile(area.left(),area.top(),area.right()-area.left()+1,area.bottom()-area.top()+1,EnumSet.allOf(ColonySystems.ResourceType.class));done=zone==null?0:(area.right()-area.left()+1)*(area.bottom()-area.top()+1);}
        else for(AreaDesignation.TilePoint q:area.tiles()){
            if(ui.designateMode==ColonyUiState.DesignateMode.HARVEST&&world.canGather(q.x,q.y)){boolean accepted=invokeOptional("designateHarvest",new Class<?>[]{int.class,int.class},q.x,q.y);if(!accepted){world.setTool(GameWorld.Tool.GATHER);world.command(q.x,q.y);accepted=true;}if(accepted)done++;}
            else if(ui.designateMode==ColonyUiState.DesignateMode.HAUL){for(ColonySystems.ResourcePile pile:world.resourcePiles)if(!pile.stored&&(int)pile.x==q.x&&(int)pile.y==q.y&&invokeOptional("designateHaul",new Class<?>[]{long.class},pile.id)){done++;break;}}
            else if(ui.designateMode==ColonyUiState.DesignateMode.BUILD&&buildTileValid(q)){if(placeBuildTile(ui.selectedBuild,q.x,q.y))done++;}
        }
        if(done>0){ui.announce(done+" "+designationLabel().toLowerCase(Locale.US)+" tile"+(done==1?"":"s")+" confirmed");if(ui.designateMode==ColonyUiState.DesignateMode.HARVEST)tutorial.onGather();else if(ui.designateMode==ColonyUiState.DesignateMode.BUILD)tutorial.onBuildPlaced();area.clear();}
        else ui.announce("No valid tiles were accepted by the colony");
    }
    private boolean placeBuildTile(ColonyUiState.BuildItem item,int x,int y){if(item.id.equals("beacon")){int before=world.wood+world.stone+world.crystal;world.setTool(GameWorld.Tool.BEACON);world.command(x,y);return world.beaconCharge>=0&&world.wood+world.stone+world.crystal<before;}return world.placeBlueprint(item.id,x,y);}
    private void placeBuild(int x,int y){ColonyUiState.BuildItem item=ui.selectedBuild;if(world.tiles[x][y]!=GameWorld.Tile.GRASS||world.walls[x][y]){ui.announce("Choose clear meadow terrain");return;}boolean placed;if(item.id.equals("beacon")){int before=world.wood+world.stone+world.crystal;world.setTool(GameWorld.Tool.BEACON);world.command(x,y);placed=world.beaconCharge>=0&&world.wood+world.stone+world.crystal<before;}else placed=world.placeBlueprint(item.id,x,y);if(placed){ui.announce(item.name+" designated");tutorial.onBuildPlaced();}else ui.announce("Cannot place "+item.name+" here or materials are missing");}
    private boolean invokeOptional(String name,Class<?>[] types,Object...args){try{Method m=world.getClass().getMethod(name,types);Object out=m.invoke(world,args);return !(out instanceof Boolean)||((Boolean)out);}catch(Exception ignored){return false;}}
    private void requestNarration(){
        if(chronicleBusy||host==null)return;ChronicleHistory.Entry entry=world.latestChronicle();
        if(entry==null){chronicleStatus="The Director is watching • first incident due by day "+world.chronicleNextDay;return;}
        chronicleBusy=true;chronicleStatus="Contacting the Chronicle…";chronicleTitle=entry.headline;chronicleStory=entry.story;chronicleSource=entry.source;
        final long selectionSeed=entry.selectionSeed;final int eventDay=entry.day;final String incidentId=entry.incidentId;ChronicleClient.Request request=new ChronicleClient.Request(entry,world.colonists.size(),world.chronicleTension);
        boolean accepted=host.narrate(request,new ChronicleClient.Callback(){
            @Override public void onSuccess(ChronicleClient.Narrative narrative){post(()->{chronicleBusy=false;if(world.acceptChronicleNarrative(eventDay,incidentId,selectionSeed,narrative.title,narrative.story,narrative.tone)){chronicleTitle=narrative.title;chronicleStory=narrative.story;chronicleSource="GROK";chronicleStatus="Tone: "+narrative.tone;host.save(world,false);}else chronicleStatus="Incident changed • keeping local chronicle";invalidate();});}
            @Override public void onFailure(String message){post(()->{chronicleBusy=false;chronicleSource=entry.source;chronicleStatus=message+" • showing saved chronicle";invalidate();});}
        });
        if(!accepted){chronicleBusy=false;chronicleStatus="A narration request is already running";}
    }
    private void drawWrapped(Canvas c,String value,float x,float y,float maxWidth,float size,int color,int maxLines){
        if(value==null)return;p.setTextSize(size);p.setTypeface(Typeface.DEFAULT);String[] words=value.replace('\n',' ').split("\\s+");StringBuilder line=new StringBuilder();int lines=0;
        for(String word:words){String candidate=line.length()==0?word:line+" "+word;if(p.measureText(candidate)>maxWidth&&line.length()>0){text(c,line.toString(),x,y+lines*size*1.35f,size,color,Paint.Align.LEFT,false);lines++;line.setLength(0);line.append(word);if(lines>=maxLines)return;}else{if(line.length()>0)line.append(' ');line.append(word);}}
        if(lines<maxLines&&line.length()>0)text(c,line.toString(),x,y+lines*size*1.35f,size,color,Paint.Align.LEFT,false);
    }
    private void tap(){audio.play(GameAudioManager.Cue.TAP);haptic();invalidate();}private void haptic(){performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);}
    private float wx(float x){return getWidth()/2f+(x-camX)*tile;}private float wy(float y){return worldTop()+(worldBottom()-worldTop())/2f+(y-camY)*tile;}private void clampCamera(){camX=Math.max(1,Math.min(GameWorld.WIDTH-1,camX));camY=Math.max(1,Math.min(GameWorld.HEIGHT-1,camY));}
    private float dp(float v){return v*density;}private void text(Canvas c,String s,float x,float y,float size,int color,Paint.Align align,boolean bold){p.setTextAlign(align);p.setTypeface(bold?Typeface.DEFAULT_BOLD:Typeface.DEFAULT);p.setTextSize(size);p.setColor(color);c.drawText(s,x,y,p);p.setTextAlign(Paint.Align.LEFT);}

    private void loadSprites(){sprites.put("MISSING",placeholder(0xff542a5d,0xffff74e8,"missing"));sprites.put("BERRY",placeholder(0xff315f46,0xffed5b86,"berry"));sprites.put("WALL",placeholder(0xff5e4433,0xffb89765,"wall"));sprites.put("COLONIST",placeholder(0xff1c3447,0xffffd486,"person"));sprites.put("DOWN",placeholder(0xff322c33,0xff77545a,"person"));sprites.put("ENEMY",placeholder(0xff341b45,0xffdc5de8,"enemy"));load("MEADOW_TERRAIN",AssetCatalog.MEADOW_SOIL);load("ROCK_TERRAIN",AssetCatalog.SOIL_ROCK);load("CRYSTAL_TREE",AssetCatalog.CRYSTAL_TREE);load("ORE",AssetCatalog.ORE_BOULDER);load("CRASHED_POD",AssetCatalog.CRASHED_POD);load("CAMPFIRE",AssetCatalog.CAMPFIRE);load("SHELTER",AssetCatalog.SHELTER);load("WORKBENCH",AssetCatalog.WORKBENCH);load("BERRY","art/local/berry_bush.png");load("WALL","art/local/barrier.png");load("ENEMY","art/local/void_stalker.png");load("BEACON","art/local/haven_beacon.png");load("BLUEPRINT","art/local/v02/blueprint_hologram.png");load("FABRICATOR","art/local/v02/fabricator.png");load("RESOURCE_STACK","art/local/v02/resource_stack.png");load("REST_POD","art/local/v02/rest_pod.png");load("DOOR","art/local/v02/sealed_door.png");load("MULTI",AssetCatalog.DESIGNATION_MULTI);load("NURSERY",AssetCatalog.NURSERY_CRADLE);load("GROW_BED",AssetCatalog.GROW_BED);for(String action:new String[]{"idle","walking","using-tool","melee"})for(String direction:new String[]{"north","south","east","west"}){int count=action.equals("idle")?4:action.equals("walking")?8:9;for(int frame=0;frame<count;frame++){String path=AssetCatalog.colonistFrame(action,direction,frame);load(path,path);}}}
    private void load(String key,String path){try(InputStream in=getContext().getAssets().open(path)){Bitmap b=BitmapFactory.decodeStream(in);if(b!=null){b.setDensity(Bitmap.DENSITY_NONE);sprites.put(key,b);}}catch(IOException ignored){}}
    private Bitmap placeholder(int bg,int fg,String kind){Bitmap b=Bitmap.createBitmap(16,16,Bitmap.Config.ARGB_8888);Canvas c=new Canvas(b);Paint q=new Paint();q.setColor(bg);c.drawRect(0,0,16,16,q);q.setColor(fg);if(kind.equals("berry")){c.drawRect(7,5,9,15,q);c.drawCircle(5,6,3,q);c.drawCircle(11,7,3,q);}else if(kind.equals("wall")){c.drawRect(1,4,15,13,q);}else if(kind.equals("person")){c.drawCircle(8,4,3,q);c.drawRect(5,7,11,14,q);}else if(kind.equals("enemy")){c.drawCircle(8,8,6,q);q.setColor(Color.WHITE);c.drawRect(4,6,6,8,q);c.drawRect(10,6,12,8,q);}else{q.setStrokeWidth(2);c.drawLine(2,2,14,14,q);c.drawLine(14,2,2,14,q);}return b;}
}
