"""FastAPI routes for the Village Simulation API.""" from fastapi import APIRouter, HTTPException from backend.core.engine import get_engine, SimulationMode from backend.core.world import WorldConfig from backend.config import get_config, set_config, SimulationConfig, reset_config from .schemas import ( WorldStateResponse, ControlResponse, SetModeRequest, InitializeRequest, ErrorResponse, ) router = APIRouter() # ============== State Endpoints ============== @router.get( "/state", response_model=WorldStateResponse, summary="Get full simulation state", description="Returns the complete state of the simulation including agents, market, and world info.", ) def get_state(): """Get the full simulation state.""" engine = get_engine() return engine.get_state() @router.get( "/agents", summary="Get all agents", description="Returns a list of all agents in the simulation.", ) def get_agents(): """Get all agents.""" engine = get_engine() return { "agents": [a.to_dict() for a in engine.world.agents], "count": len(engine.world.agents), "alive_count": len(engine.world.get_living_agents()), } @router.get( "/agents/{agent_id}", summary="Get a specific agent", description="Returns detailed information about a specific agent.", ) def get_agent(agent_id: str): """Get a specific agent by ID.""" engine = get_engine() agent = engine.world.get_agent(agent_id) if agent is None: raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found") return agent.to_dict() # ============== Market Endpoints ============== @router.get( "/market/orders", summary="Get market orders", description="Returns all active market orders.", ) def get_market_orders(): """Get all active market orders.""" engine = get_engine() return engine.market.get_state_snapshot() @router.get( "/market/prices", summary="Get market prices", description="Returns current market prices for all resource types.", ) def get_market_prices(): """Get market price summary.""" engine = get_engine() return engine.market.get_market_prices() # ============== Control Endpoints ============== @router.post( "/control/initialize", response_model=ControlResponse, summary="Initialize simulation", description="Initialize or reset the simulation with the specified parameters.", ) def initialize_simulation(request: InitializeRequest): """Initialize or reset the simulation.""" engine = get_engine() config = WorldConfig( width=request.world_width, height=request.world_height, initial_agents=request.num_agents, ) engine.reset(config) return ControlResponse( success=True, message=f"Simulation initialized with {request.num_agents} agents", turn=engine.world.current_turn, mode=engine.mode.value, ) @router.post( "/control/next_step", response_model=ControlResponse, summary="Advance simulation", description="Advance the simulation by one step. Only works in MANUAL mode.", ) def next_step(): """Advance the simulation by one step.""" engine = get_engine() if engine.mode == SimulationMode.AUTO: raise HTTPException( status_code=400, detail="Cannot manually advance while in AUTO mode. Switch to MANUAL first.", ) if not engine.is_running: raise HTTPException( status_code=400, detail="Simulation is not running. Initialize first.", ) turn_log = engine.next_step() return ControlResponse( success=True, message=f"Advanced to turn {engine.world.current_turn}", turn=engine.world.current_turn, mode=engine.mode.value, ) @router.post( "/control/mode", response_model=ControlResponse, summary="Set simulation mode", description="Set the simulation mode to MANUAL or AUTO.", ) def set_mode(request: SetModeRequest): """Set the simulation mode.""" engine = get_engine() try: mode = SimulationMode(request.mode) engine.set_mode(mode) return ControlResponse( success=True, message=f"Mode set to {mode.value}", turn=engine.world.current_turn, mode=mode.value, ) except ValueError: raise HTTPException( status_code=400, detail=f"Invalid mode: {request.mode}. Use 'manual' or 'auto'.", ) @router.get( "/control/status", summary="Get simulation status", description="Returns the current simulation status.", ) def get_status(): """Get simulation status.""" engine = get_engine() return { "is_running": engine.is_running, "mode": engine.mode.value, "current_turn": engine.world.current_turn, "current_day": engine.world.current_day, "time_of_day": engine.world.time_of_day.value, "living_agents": len(engine.world.get_living_agents()), } # ============== Logs Endpoints ============== @router.get( "/logs", summary="Get turn logs", description="Returns the most recent turn logs.", ) def get_logs(limit: int = 10): """Get recent turn logs.""" engine = get_engine() logs = engine.turn_logs[-limit:] if engine.turn_logs else [] return { "logs": [log.to_dict() for log in logs], "total_turns": len(engine.turn_logs), } @router.get( "/logs/{turn}", summary="Get specific turn log", description="Returns the log for a specific turn.", ) def get_turn_log(turn: int): """Get log for a specific turn.""" engine = get_engine() for log in engine.turn_logs: if log.turn == turn: return log.to_dict() raise HTTPException(status_code=404, detail=f"Log for turn {turn} not found") # ============== Config Endpoints ============== @router.get( "/config", summary="Get simulation configuration", description="Returns all configurable simulation parameters.", ) def get_simulation_config(): """Get current simulation configuration.""" config = get_config() return config.to_dict() @router.post( "/config", response_model=ControlResponse, summary="Update simulation configuration", description="Update simulation parameters. Requires simulation restart to take effect.", ) def update_simulation_config(config_data: dict): """Update simulation configuration.""" try: new_config = SimulationConfig.from_dict(config_data) set_config(new_config) return ControlResponse( success=True, message="Configuration updated. Restart simulation to apply changes.", ) except Exception as e: raise HTTPException(status_code=400, detail=f"Invalid configuration: {str(e)}") @router.post( "/config/reset", response_model=ControlResponse, summary="Reset configuration to defaults", description="Reset all configuration parameters to their default values.", ) def reset_simulation_config(): """Reset configuration to defaults.""" reset_config() return ControlResponse( success=True, message="Configuration reset to defaults.", ) @router.post( "/config/save", response_model=ControlResponse, summary="Save configuration to file", description="Save current configuration to config.json file.", ) def save_config_to_file(): """Save configuration to file.""" try: config = get_config() config.save("config.json") return ControlResponse( success=True, message="Configuration saved to config.json", ) except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to save config: {str(e)}") @router.post( "/config/load", response_model=ControlResponse, summary="Load configuration from file", description="Load configuration from config.json file.", ) def load_config_from_file(): """Load configuration from file.""" try: config = SimulationConfig.load("config.json") set_config(config) return ControlResponse( success=True, message="Configuration loaded from config.json", ) except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to load config: {str(e)}")