'use strict';

const express = require('express');
const cors = require('cors');
const { CJbot } = require('../model/minecraft');

class WebServer {
	constructor() {
		this.app = null;
		this.port = null;
		this.host = null;
		this.server = null;
		this.pluginRegistry = new Map(); // pluginName → { slot, webUI }
		this._pendingPlugins = [];       // plugins queued before start()
	}

	/**
	 * Queue a plugin class for web registration.
	 * Called from CJbot.pluginAdd() — may happen before start().
	 */
	queuePlugin(cls) {
		if (this.app) {
			// Server already running, register immediately
			this.registerPlugin(cls);
		} else {
			this._pendingPlugins.push(cls);
		}
	}

	/**
	 * Register a plugin's web support (router + UI descriptor).
	 * Creates a permanent middleware proxy slot so routes survive plugin reload.
	 */
	registerPlugin(cls) {
		if (this.pluginRegistry.has(cls.name)) return; // already registered

		const slot = { router: null };

		if (typeof cls.createRouter === 'function') {
			const resolver = this._makeInstanceResolver(cls.name);
			slot.router = cls.createRouter(resolver);
		}

		const webUI = cls.webUI || null;
		this.pluginRegistry.set(cls.name, { slot, webUI });

		// Permanent middleware proxy — delegates to slot.router at request time
		this.app.use((req, res, next) => {
			if (slot.router) return slot.router(req, res, next);
			next();
		});
	}

	/**
	 * Returns a resolver function: (botName?) => { plugin, bot }
	 * Searches CJbot.bots for any bot with the named plugin loaded.
	 * For on-demand bots, returns { plugin: null, bot } so callers can use ensureConnected.
	 */
	_makeInstanceResolver(pluginName) {
		return (botName) => {
			// Try specific bot first
			if (botName) {
				const bot = CJbot.bots[botName];
				if (bot && bot.plunginsLoaded[pluginName]) {
					return { plugin: bot.plunginsLoaded[pluginName], bot };
				}
				if (bot && bot.onDemand && bot.pluginsWanted[pluginName] !== undefined) {
					return { plugin: null, bot };
				}
				return { plugin: null, bot: null };
			}
			// Search all bots — prefer already-loaded
			for (const bot of Object.values(CJbot.bots)) {
				if (bot.plunginsLoaded[pluginName]) {
					return { plugin: bot.plunginsLoaded[pluginName], bot };
				}
			}
			// Fall back to on-demand bots that want this plugin
			for (const bot of Object.values(CJbot.bots)) {
				if (bot.onDemand && bot.pluginsWanted[pluginName] !== undefined) {
					return { plugin: null, bot };
				}
			}
			return { plugin: null, bot: null };
		};
	}

	async start() {
		const conf = require('../conf');

		this.port = conf.storage?.webPort || 3000;
		this.host = conf.storage?.webHost || '0.0.0.0';

		this.app = express();

		// Middleware
		this.app.use(express.json());
		this.app.use(cors());

		this.app.use((req, res, next) => {
			console.log(`WebServer: ${req.method} ${req.path}`);
			next();
		});

		// Flush any plugins that were queued before start()
		for (const cls of this._pendingPlugins) {
			this.registerPlugin(cls);
		}
		this._pendingPlugins = [];

		this.setupRoutes();

		return new Promise((resolve, reject) => {
			this.server = this.app.listen(this.port, this.host, () => {
				console.log(`WebServer: Running at http://${this.host}:${this.port}`);
				resolve();
			});
			this.server.on('error', (err) => {
				console.error('WebServer: Failed to start:', err);
				reject(err);
			});
		});
	}

	setupRoutes() {
		// Index page — assembled dynamically from plugin descriptors
		this.app.get('/', (req, res) => {
			res.send(this.getIndexHTML());
		});

		// Health check
		this.app.get('/health', (req, res) => {
			res.json({ status: 'ok', server: `${this.host}:${this.port}` });
		});

		// ========================================
		// Bot management API routes (core)
		// ========================================

		this.app.get('/api/bots', (req, res) => {
			try {
				const bots = {};
				for (const [name, bot] of Object.entries(CJbot.bots)) {
					const info = {
						name,
						connected: bot.isReady,
						autoReConnect: bot.autoReConnect,
						autoConnect: bot.autoConnect,
						onDemand: bot.onDemand || false,
						pluginsWanted: Object.keys(bot.pluginsWanted || {}),
						pluginsLoaded: Object.keys(bot.plunginsLoaded || {}),
					};
					if (bot.isReady && bot.bot && bot.bot.entity) {
						info.health = bot.bot.health;
						info.food = bot.bot.food;
						info.position = {
							x: Math.round(bot.bot.entity.position.x),
							y: Math.round(bot.bot.entity.position.y),
							z: Math.round(bot.bot.entity.position.z),
						};
					}
					bots[name] = info;
				}
				res.json({ bots });
			} catch (error) {
				console.error('API Error /api/bots:', error);
				res.status(500).json({ error: error.message });
			}
		});

		this.app.get('/api/plugins', (req, res) => {
			try {
				res.json({ plugins: Object.keys(CJbot.plungins) });
			} catch (error) {
				console.error('API Error /api/plugins:', error);
				res.status(500).json({ error: error.message });
			}
		});

		this.app.post('/api/bots/:name/connect', async (req, res) => {
			try {
				const bot = CJbot.bots[req.params.name];
				if (!bot) return res.status(404).json({ error: 'Bot not found' });
				if (bot.isReady) return res.status(400).json({ error: 'Bot already connected' });

				bot.autoReConnect = req.body?.autoReConnect ?? true;
				bot.connect().catch(err => console.error(`Web connect error for ${req.params.name}:`, err));

				res.json({ status: 'connecting' });
			} catch (error) {
				console.error('API Error /api/bots/:name/connect:', error);
				res.status(500).json({ error: error.message });
			}
		});

		this.app.post('/api/bots/:name/disconnect', async (req, res) => {
			try {
				const bot = CJbot.bots[req.params.name];
				if (!bot) return res.status(404).json({ error: 'Bot not found' });
				if (!bot.isReady) return res.status(400).json({ error: 'Bot not connected' });

				bot.autoReConnect = false;
				bot.quit(true);

				res.json({ status: 'disconnecting' });
			} catch (error) {
				console.error('API Error /api/bots/:name/disconnect:', error);
				res.status(500).json({ error: error.message });
			}
		});

		this.app.post('/api/bots/:name/plugins/:plugin/load', async (req, res) => {
			try {
				const bot = CJbot.bots[req.params.name];
				if (!bot) return res.status(404).json({ error: 'Bot not found' });
				if (!bot.isReady) return res.status(400).json({ error: 'Bot not connected' });

				const pluginName = req.params.plugin;
				if (!CJbot.plungins[pluginName]) return res.status(404).json({ error: 'Plugin not registered' });
				if (bot.plunginsLoaded[pluginName]) return res.status(400).json({ error: 'Plugin already loaded' });

				bot.pluginLoad(pluginName, req.body || {}).catch(err => console.error(`Web plugin load error:`, err));

				res.json({ status: 'loading', plugin: pluginName });
			} catch (error) {
				console.error('API Error plugin load:', error);
				res.status(500).json({ error: error.message });
			}
		});

		this.app.post('/api/bots/:name/plugins/:plugin/unload', async (req, res) => {
			try {
				const bot = CJbot.bots[req.params.name];
				if (!bot) return res.status(404).json({ error: 'Bot not found' });

				const pluginName = req.params.plugin;
				if (!bot.plunginsLoaded[pluginName]) return res.status(400).json({ error: 'Plugin not loaded' });

				bot.pluginUnload(pluginName).catch(err => console.error(`Web plugin unload error:`, err));

				res.json({ status: 'unloading', plugin: pluginName });
			} catch (error) {
				console.error('API Error plugin unload:', error);
				res.status(500).json({ error: error.message });
			}
		});

		this.app.post('/api/bots/:name/command', async (req, res) => {
			try {
				const bot = CJbot.bots[req.params.name];
				if (!bot) return res.status(404).json({ error: 'Bot not found' });
				if (!bot.isReady) return res.status(400).json({ error: 'Bot not connected' });

				const { command, args, plugin } = req.body || {};
				if (!command) return res.status(400).json({ error: 'Missing command' });

				// Find plugin with handleCommand — check specified plugin first, then search
				let targetPlugin = null;
				if (plugin && bot.plunginsLoaded[plugin]) {
					targetPlugin = bot.plunginsLoaded[plugin];
				} else {
					for (const p of Object.values(bot.plunginsLoaded)) {
						if (typeof p.handleCommand === 'function') {
							targetPlugin = p;
							break;
						}
					}
				}

				if (!targetPlugin || typeof targetPlugin.handleCommand !== 'function') {
					return res.status(503).json({ error: 'No plugin with handleCommand loaded on this bot' });
				}

				targetPlugin.handleCommand('web-ui', command, ...(args || []))
					.catch(err => console.error('Web command error:', err));

				res.json({ status: 'queued', command });
			} catch (error) {
				console.error('API Error /api/bots/:name/command:', error);
				res.status(500).json({ error: error.message });
			}
		});
	}

	getIndexHTML() {
		// Collect plugin UI descriptors sorted by tabOrder
		const plugins = [];
		for (const [name, reg] of this.pluginRegistry) {
			if (reg.webUI) plugins.push(reg.webUI);
		}
		plugins.sort((a, b) => (a.tabOrder || 100) - (b.tabOrder || 100));

		// Build tab buttons, content panels, CSS, JS, sidebar
		const tabButtons = plugins.map(p =>
			`<div class="tab" onclick="switchTab('${p.tabId}')">${p.tabLabel}</div>`
		).join('\n\t\t\t');

		const tabPanels = plugins.map(p =>
			`<div id="tab-${p.tabId}" class="tab-content">${p.html || ''}</div>`
		).join('\n\t\t');

		const pluginCSS = plugins.map(p => p.css || '').join('\n');
		const pluginJS = plugins.map(p => p.js || '').join('\n');

		const sidebarHtml = plugins.map(p => p.sidebarHtml || '').join('\n');
		const sidebarJs = plugins.map(p => p.sidebarJs || '').join('\n');

		// Build ALL_TABS array for switchTab
		const allTabIds = plugins.map(p => `'${p.tabId}'`).concat("'bots'");
		const onTabActiveMap = plugins
			.filter(p => p.onTabActive)
			.map(p => `'${p.tabId}': ${p.onTabActive}`)
			.join(', ');

		return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MC Bot Town</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Segoe UI',Tahoma,sans-serif;background:#111827;color:#e5e7eb;min-height:100vh}
.header{background:#1f2937;padding:16px 24px;border-bottom:1px solid #374151;display:flex;align-items:center;justify-content:space-between}
.header h1{font-size:1.4em;color:#60a5fa}
.header button{background:#2563eb;color:#fff;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;font-size:.85em}
.header button:hover{background:#1d4ed8}
.layout{display:flex;height:calc(100vh - 57px)}
.sidebar{width:320px;background:#1f2937;border-right:1px solid #374151;display:flex;flex-direction:column;flex-shrink:0}
.main{flex:1;overflow:auto;padding:20px}
.tabs{display:flex;border-bottom:1px solid #374151}
.tab{padding:10px 16px;cursor:pointer;color:#9ca3af;font-size:.9em;border-bottom:2px solid transparent}
.tab.active{color:#60a5fa;border-bottom-color:#60a5fa}
.tab:hover{color:#e5e7eb}
.tab-content{display:none}
.tab-content.active{display:block}
/* Bot management styles */
.bot-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:16px}
.bot-card{background:#111827;border:1px solid #374151;border-radius:8px;padding:16px;transition:border-color .2s}
.bot-card:hover{border-color:#60a5fa}
.bot-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.bot-card-header h3{font-size:1em;display:flex;align-items:center;gap:8px}
.bot-status{width:10px;height:10px;border-radius:50%;display:inline-block}
.bot-status.online{background:#10b981}
.bot-status.offline{background:#ef4444}
.bot-info{font-size:.8em;color:#9ca3af;margin-bottom:12px}
.bot-info span{display:block;margin:2px 0}
.plugin-tags{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:12px}
.plugin-tag{background:#1e3a5f;color:#60a5fa;padding:3px 8px;border-radius:4px;font-size:.75em;display:flex;align-items:center;gap:4px}
.plugin-tag .unload-btn{background:none;border:none;color:#ef4444;cursor:pointer;font-size:.9em;padding:0 2px;line-height:1}
.plugin-tag .unload-btn:hover{color:#f87171}
.bot-actions{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
.bot-actions select{padding:6px;border:1px solid #374151;border-radius:4px;background:#1f2937;color:#e5e7eb;font-size:.8em}
.btn-connect{background:#059669;color:#fff;border:none;padding:6px 14px;border-radius:4px;cursor:pointer;font-size:.8em}
.btn-connect:hover{background:#047857}
.btn-disconnect{background:#dc2626;color:#fff;border:none;padding:6px 14px;border-radius:4px;cursor:pointer;font-size:.8em}
.btn-disconnect:hover{background:#b91c1c}
.btn-action{background:#2563eb;color:#fff;border:none;padding:6px 10px;border-radius:4px;cursor:pointer;font-size:.75em}
.btn-action:hover{background:#1d4ed8}
.btn-load{background:#7c3aed;color:#fff;border:none;padding:6px 10px;border-radius:4px;cursor:pointer;font-size:.8em}
.btn-load:hover{background:#6d28d9}
.bot-commands{display:flex;gap:4px;flex-wrap:wrap;margin-top:8px}
${pluginCSS}
/* Toast notifications */
#toastContainer{position:fixed;top:16px;right:16px;z-index:200;display:flex;flex-direction:column;gap:8px;pointer-events:none}
.toast{pointer-events:auto;padding:12px 20px;border-radius:8px;font-size:.85em;color:#fff;box-shadow:0 4px 12px rgba(0,0,0,.4);animation:toastIn .3s ease;max-width:380px;word-wrap:break-word}
.toast.removing{animation:toastOut .3s ease forwards}
.toast.info{background:#2563eb}
.toast.success{background:#059669}
.toast.error{background:#dc2626}
.toast.warning{background:#d97706}
@keyframes toastIn{from{transform:translateX(100%);opacity:0}to{transform:translateX(0);opacity:1}}
@keyframes toastOut{from{transform:translateX(0);opacity:1}to{transform:translateX(100%);opacity:0}}
/* Timestamps */
.last-updated{font-size:.75em;color:#6b7280;margin-left:8px}
/* Online players dropdown */
.players-dropdown{position:relative;display:inline-block}
.players-btn{background:#374151;color:#e5e7eb;border:none;padding:8px 14px;border-radius:6px;cursor:pointer;font-size:.85em}
.players-btn:hover{background:#4b5563}
.players-list{display:none;position:absolute;top:100%;right:0;margin-top:4px;background:#1f2937;border:1px solid #374151;border-radius:6px;min-width:180px;max-height:300px;overflow:auto;z-index:100;box-shadow:0 4px 12px rgba(0,0,0,.4)}
.players-list.open{display:block}
.players-list .pl-item{padding:8px 14px;font-size:.85em;color:#e5e7eb;border-bottom:1px solid #374151}
.players-list .pl-item:last-child{border-bottom:none}
.players-list .pl-empty{padding:12px 14px;color:#6b7280;font-size:.85em;text-align:center}
</style>
</head>
<body>
<div id="toastContainer"></div>
<div class="header">
	<h1>MC Bot Town</h1>
	<div style="display:flex;gap:8px;align-items:center">
		<div class="players-dropdown" id="playersDropdown">
			<button class="players-btn" onclick="togglePlayersDropdown()">Players: <span id="playersCount">?</span></button>
			<div class="players-list" id="playersList"></div>
		</div>
		<button onclick="loadAll()">Refresh</button>
	</div>
</div>
<div class="layout">
	<div class="sidebar" id="sidebarArea">
		${sidebarHtml}
	</div>
	<div class="main">
		<div class="tabs" id="mainTabs">
			${tabButtons}
			<div class="tab" onclick="switchTab('bots')">Bots</div>
		</div>
		${tabPanels}
		<div id="tab-bots" class="tab-content" style="margin-top:16px">
			<div style="margin-bottom:8px"><span class="last-updated" id="ts-bots"></span></div>
			<div id="botsArea"><div style="padding:20px;color:#6b7280;text-align:center">Loading bots...</div></div>
		</div>
	</div>
</div>
<script>
// === Toast notifications ===
function showToast(message, type='info', duration=4000) {
	const container = document.getElementById('toastContainer');
	const el = document.createElement('div');
	el.className = 'toast ' + type;
	el.textContent = message;
	container.appendChild(el);
	setTimeout(() => {
		el.classList.add('removing');
		el.addEventListener('animationend', () => el.remove());
	}, duration);
}

// === Timestamps ===
const tsMap = {};
function updateTimestamp(elId) {
	tsMap[elId] = Date.now();
	tickTimestamp(elId);
}
function tickTimestamp(elId) {
	const el = document.getElementById(elId);
	if (!el || !tsMap[elId]) return;
	const sec = Math.round((Date.now() - tsMap[elId]) / 1000);
	el.textContent = sec < 5 ? 'just now' : sec + 's ago';
}
setInterval(() => { for (const id of Object.keys(tsMap)) tickTimestamp(id); }, 5000);

// === Online players ===
let onlinePlayers = [];
async function pollOnlinePlayers() {
	try {
		const r = await fetch('/api/invite/online-players');
		if (!r.ok) return;
		const d = await r.json();
		onlinePlayers = d.players || [];
		document.getElementById('playersCount').textContent = onlinePlayers.length;
	} catch(e) {}
}
function togglePlayersDropdown() {
	const list = document.getElementById('playersList');
	const isOpen = list.classList.contains('open');
	list.classList.toggle('open');
	if (!isOpen) {
		if (onlinePlayers.length === 0) {
			list.innerHTML = '<div class="pl-empty">No players online</div>';
		} else {
			list.innerHTML = onlinePlayers.map(p => '<div class="pl-item">' + escHtml(p) + '</div>').join('');
		}
	}
}
document.addEventListener('click', function(e) {
	const dd = document.getElementById('playersDropdown');
	if (dd && !dd.contains(e.target)) document.getElementById('playersList').classList.remove('open');
});
pollOnlinePlayers();
setInterval(pollOnlinePlayers, 15000);

const ALL_TABS = [${allTabIds.join(',')}];
const TAB_ACTIVE_HANDLERS = {${onTabActiveMap}};
let currentTab = ALL_TABS[0] || 'bots';
let allPlugins=[], botsLoaded=false, botsInterval=null;

function fmtName(n){return n?n.replace(/_/g,' ').replace(/\\b\\w/g,c=>c.toUpperCase()):''}
function fmt(n){return n>=1e6?(n/1e6).toFixed(1)+'M':n>=1e3?(n/1e3).toFixed(1)+'K':n}
function escHtml(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')}

function switchTab(name) {
	currentTab = name;
	document.querySelectorAll('#mainTabs .tab').forEach(t => t.classList.remove('active'));
	document.querySelectorAll('.main > .tab-content').forEach(t => t.classList.remove('active'));
	const panel = document.getElementById('tab-'+name);
	if (panel) panel.classList.add('active');
	// Activate the correct tab button
	const allTabEls = document.querySelectorAll('#mainTabs .tab');
	const tabOrder = [...ALL_TABS.map(id=>id), 'bots'];
	// Remove the quotes from ALL_TABS ids for comparison
	const idx = tabOrder.indexOf(name);
	if (idx >= 0 && allTabEls[idx]) allTabEls[idx].classList.add('active');
	// Show/hide sidebar based on whether active plugin has sidebar content
	const sidebar = document.getElementById('sidebarArea');
	const hasSidebar = ${JSON.stringify(plugins.filter(p => p.sidebarHtml).map(p => p.tabId))}.includes(name);
	sidebar.style.display = hasSidebar ? '' : 'none';
	// Call plugin's onTabActive handler
	if (TAB_ACTIVE_HANDLERS[name]) TAB_ACTIVE_HANDLERS[name]();
	// Bots tab auto-refresh
	if (name === 'bots') {
		loadBots();
		if (!botsInterval) botsInterval = setInterval(() => { if (currentTab === 'bots') loadBots(); }, 5000);
	}
}

function setupAC(inputId, listId, getOptions, onSelect) {
	const input=document.getElementById(inputId);
	const list=document.getElementById(listId);
	if (!input || !list) return;
	let activeIdx=-1;

	function show(opts) {
		if (!opts.length){list.classList.remove('open');return}
		const q=input.value.toLowerCase();
		list.innerHTML=opts.slice(0,15).map((o,i)=>{
			const label=typeof o==='string'?o:o.label;
			const extra=typeof o==='string'?'':o.extra||'';
			const hl=highlightMatch(label,q);
			return '<div class="ac-opt'+(i===activeIdx?' active':'')+'" data-idx="'+i+'" data-val="'+(typeof o==='string'?o:o.value)+'">'+
				'<span>'+hl+'</span>'+(extra?'<span class="ac-count">'+extra+'</span>':'')+'</div>';
		}).join('');
		list.classList.add('open');

		list.querySelectorAll('.ac-opt').forEach(el=>{
			el.addEventListener('mousedown',e=>{
				e.preventDefault();
				input.value=el.dataset.val;
				list.classList.remove('open');
				if(onSelect)onSelect(el.dataset.val);
			});
		});
	}

	function highlightMatch(text, q) {
		if(!q)return fmtName(text);
		const name=fmtName(text);
		const idx=name.toLowerCase().indexOf(q);
		if(idx===-1)return name;
		return name.substring(0,idx)+'<span class="ac-match">'+name.substring(idx,idx+q.length)+'</span>'+name.substring(idx+q.length);
	}

	input.addEventListener('input',()=>{
		activeIdx=-1;
		const opts=getOptions(input.value);
		show(opts);
		if(onSelect)onSelect(null);
	});

	input.addEventListener('focus',()=>{
		if(input.value||getOptions('').length<=20){
			const opts=getOptions(input.value);
			show(opts);
		}
	});

	input.addEventListener('blur',()=>{
		setTimeout(()=>list.classList.remove('open'),150);
	});

	input.addEventListener('keydown',e=>{
		const opts=list.querySelectorAll('.ac-opt');
		if(!opts.length)return;
		if(e.key==='ArrowDown'){
			e.preventDefault();
			activeIdx=Math.min(activeIdx+1,opts.length-1);
			opts.forEach((o,i)=>o.classList.toggle('active',i===activeIdx));
			opts[activeIdx]?.scrollIntoView({block:'nearest'});
		}else if(e.key==='ArrowUp'){
			e.preventDefault();
			activeIdx=Math.max(activeIdx-1,0);
			opts.forEach((o,i)=>o.classList.toggle('active',i===activeIdx));
			opts[activeIdx]?.scrollIntoView({block:'nearest'});
		}else if(e.key==='Enter'&&activeIdx>=0){
			e.preventDefault();
			input.value=opts[activeIdx].dataset.val;
			list.classList.remove('open');
			if(onSelect)onSelect(opts[activeIdx].dataset.val);
		}else if(e.key==='Escape'){
			list.classList.remove('open');
		}
	});
}

// === Plugin JS ===
${pluginJS}

// === BOTS ===
async function loadBots() {
	try {
		const [botsRes, pluginsRes] = await Promise.all([
			fetch('/api/bots'),
			fetch('/api/plugins')
		]);
		const botsData = await botsRes.json();
		const pluginsData = await pluginsRes.json();
		allPlugins = pluginsData.plugins || [];
		renderBots(botsData.bots || {});
		botsLoaded = true;
		updateTimestamp('ts-bots');
	} catch(e) {
		document.getElementById('botsArea').innerHTML='<div style="padding:20px;color:#ef4444">Failed to load bots</div>';
	}
}

function renderBots(bots) {
	const area = document.getElementById('botsArea');
	const names = Object.keys(bots);
	if (names.length === 0) {
		area.innerHTML='<div style="padding:20px;color:#6b7280">No bots configured</div>';
		return;
	}

	area.innerHTML = '<div class="bot-grid">' + names.map(name => {
		const b = bots[name];
		const online = b.connected;
		const statusCls = online ? 'online' : 'offline';
		const statusText = online ? 'Online' : 'Offline';

		let infoHtml = '';
		if (online && b.position) {
			infoHtml = '<div class="bot-info">' +
				'<span>Health: ' + (b.health != null ? b.health + '/20' : '?') + ' | Food: ' + (b.food != null ? b.food + '/20' : '?') + '</span>' +
				'<span>Position: ' + b.position.x + ', ' + b.position.y + ', ' + b.position.z + '</span>' +
			'</div>';
		}

		// Plugin tags
		let pluginHtml = '';
		if (b.pluginsLoaded && b.pluginsLoaded.length > 0) {
			pluginHtml = '<div class="plugin-tags">' +
				b.pluginsLoaded.map(p =>
					'<span class="plugin-tag">' + escHtml(p) +
					' <button class="unload-btn" onclick="unloadPlugin(\\'' + escHtml(name) + '\\',\\'' + escHtml(p) + '\\')" title="Unload">&times;</button>' +
					'</span>'
				).join('') +
			'</div>';
		}

		// Available plugins to load (not already loaded)
		const loadable = allPlugins.filter(p => !(b.pluginsLoaded || []).includes(p));
		let loadSelect = '';
		if (online && loadable.length > 0) {
			loadSelect = '<select id="plugin-select-' + name + '">' +
				loadable.map(p => '<option value="' + escHtml(p) + '">' + escHtml(p) + '</option>').join('') +
			'</select>' +
			'<button class="btn-load" onclick="loadPlugin(\\'' + escHtml(name) + '\\')">Load</button>';
		}

		// Connect/disconnect button
		const connBtn = online
			? '<button class="btn-disconnect" onclick="disconnectBot(\\'' + escHtml(name) + '\\')">Disconnect</button>'
			: '<button class="btn-connect" onclick="connectBot(\\'' + escHtml(name) + '\\')">Connect</button>';

		// Command buttons for plugins with handleCommand
		let cmdHtml = '';
		if (online && (b.pluginsLoaded || []).includes('Storage')) {
			cmdHtml = '<div class="bot-commands">' +
				'<button class="btn-action" onclick="runCommand(\\'' + escHtml(name) + '\\',\\'scan\\')">Scan</button>' +
				'<button class="btn-action" onclick="runCommand(\\'' + escHtml(name) + '\\',\\'organize\\')">Organize</button>' +
				'<button class="btn-action" onclick="runCommand(\\'' + escHtml(name) + '\\',\\'consolidate\\')">Consolidate</button>' +
			'</div>';
		}

		return '<div class="bot-card">' +
			'<div class="bot-card-header">' +
				'<h3><span class="bot-status ' + statusCls + '"></span> ' + escHtml(name) + '</h3>' +
				'<span style="font-size:.8em;color:#9ca3af">' + statusText + '</span>' +
			'</div>' +
			infoHtml +
			pluginHtml +
			'<div class="bot-actions">' + connBtn + ' ' + loadSelect + '</div>' +
			cmdHtml +
		'</div>';
	}).join('') + '</div>';
}

async function connectBot(name) {
	try {
		const r = await fetch('/api/bots/' + name + '/connect', { method: 'POST', headers: {'Content-Type':'application/json'}, body: '{}' });
		const d = await r.json();
		if (!r.ok) showToast(d.error || 'Failed', 'error');
		else showToast(name + ' connecting...', 'info');
		setTimeout(loadBots, 2000);
	} catch(e) { showToast('Network error', 'error'); }
}

async function disconnectBot(name) {
	try {
		const r = await fetch('/api/bots/' + name + '/disconnect', { method: 'POST' });
		const d = await r.json();
		if (!r.ok) showToast(d.error || 'Failed', 'error');
		else showToast(name + ' disconnecting...', 'info');
		setTimeout(loadBots, 1000);
	} catch(e) { showToast('Network error', 'error'); }
}

async function loadPlugin(botName) {
	const sel = document.getElementById('plugin-select-' + botName);
	if (!sel) return;
	const pluginName = sel.value;
	try {
		const r = await fetch('/api/bots/' + botName + '/plugins/' + pluginName + '/load', { method: 'POST', headers: {'Content-Type':'application/json'}, body: '{}' });
		const d = await r.json();
		if (!r.ok) showToast(d.error || 'Failed', 'error');
		else showToast('Loading ' + pluginName + '...', 'success');
		setTimeout(loadBots, 2000);
	} catch(e) { showToast('Network error', 'error'); }
}

async function unloadPlugin(botName, pluginName) {
	try {
		const r = await fetch('/api/bots/' + botName + '/plugins/' + pluginName + '/unload', { method: 'POST' });
		const d = await r.json();
		if (!r.ok) showToast(d.error || 'Failed', 'error');
		else showToast('Unloading ' + pluginName + '...', 'success');
		setTimeout(loadBots, 1000);
	} catch(e) { showToast('Network error', 'error'); }
}

async function runCommand(botName, command) {
	// Find and disable the button that was clicked
	const btns = document.querySelectorAll('.bot-commands .btn-action');
	let clickedBtn = null;
	btns.forEach(b => { if (b.textContent.toLowerCase() === command) clickedBtn = b; });
	const origText = clickedBtn ? clickedBtn.textContent : '';
	if (clickedBtn) { clickedBtn.disabled = true; clickedBtn.textContent = command.charAt(0).toUpperCase() + command.slice(1) + '...'; }

	try {
		const r = await fetch('/api/bots/' + botName + '/command', {
			method: 'POST',
			headers: {'Content-Type':'application/json'},
			body: JSON.stringify({ command, args: [] })
		});
		const d = await r.json();
		if (!r.ok) {
			showToast(d.error || 'Failed', 'error');
			if (clickedBtn) { clickedBtn.disabled = false; clickedBtn.textContent = origText; }
			return;
		}
		showToast(command.charAt(0).toUpperCase() + command.slice(1) + ' started...', 'info');

		// Poll storage status until not busy
		const poll = async () => {
			try {
				const sr = await fetch('/api/storage/status?bot=' + encodeURIComponent(botName));
				if (!sr.ok) return finish();
				const sd = await sr.json();
				if (sd.busy) {
					if (clickedBtn) clickedBtn.textContent = (sd.command || command).charAt(0).toUpperCase() + (sd.command || command).slice(1) + '...';
					setTimeout(poll, 1500);
				} else {
					finish();
				}
			} catch(e) { finish(); }
		};
		const finish = () => {
			if (clickedBtn) { clickedBtn.disabled = false; clickedBtn.textContent = origText; }
			showToast(command.charAt(0).toUpperCase() + command.slice(1) + ' complete!', 'success');
			if (typeof storageLoadAll === 'function') storageLoadAll();
			loadBots();
		};
		setTimeout(poll, 1500);
	} catch(e) {
		showToast('Network error', 'error');
		if (clickedBtn) { clickedBtn.disabled = false; clickedBtn.textContent = origText; }
	}
}

function loadAll(){
	if (typeof storageLoadAll === 'function') storageLoadAll();
	if(currentTab==='bots') loadBots();
	if(currentTab==='activity' && typeof loadActivity === 'function') loadActivity();
	if(currentTab==='ai' && typeof loadAiStatus === 'function') loadAiStatus();
}

// Activate first tab on load
switchTab(ALL_TABS[0] || 'bots');
setInterval(loadAll,30000);
</script>
${sidebarJs ? '<script>' + sidebarJs + '</script>' : ''}
</body>
</html>`;
	}
}

module.exports = new WebServer();
