import { ResearchPlan } from "simulation/ai/r28/queueplanResearch.js";
import { ResourcesManager } from "simulation/ai/common-api/resources.js";
import { Worker } from "simulation/ai/r28/worker.js";
import { aiWarn } from "simulation/ai/common-api/utils.js";
import * as filters from "simulation/ai/common-api/filters.js";

/**
 * Manage the research
 */
export function ResearchManager(Config)
{
	this.Config = Config;
	
	// Research cache system
	this.researchCacheByName = new Map();  // Cache by technology name
	this.researchCacheByEffect = new Map(); // Cache by technology effect
	this.cacheUpdateCount = 0;              // Counter for cache updates
	this.cacheUpdateReasons = {             // Track reasons for cache updates
		phaseChange: 0,
		buildingComplete: 0,
		researchComplete: 0,
		initialization: 0
	};
	
	// Performance metrics
	this.performanceMetrics = {
		loopSearches: 0,          // Times we scanned tech lists in loops
		loopSteps: 0,             // Iterations within those loops
		loopTimeMs: 0,            // Time spent in looped checks
		cacheLookups: 0,          // Number of cache lookups
		cacheLookupSteps: 0,      // Steps within cache lookups (Map fetches)
		cacheLookupTimeMs: 0,     // Time spent in cache lookups
		cacheUpdateTimeMs: 0,     // Time spent updating cache (overall)
		cacheAddTimeMs: 0,        // Time spent adding technologies into cache
		cacheAddCount: 0          // Number of technologies added to cache
	};
}

/**
 * Initialize the research cache with all available technologies
 */
ResearchManager.prototype.updateResearchCache = function(gameState, reason = "update")
{
	const cacheStartTime = Date.now();
	
	if (this.Config.debug > 1) {
		aiWarn("RESEARCH CACHE: Updating cache - reason: " + reason);
	}
	
	// Clear existing cache
	this.researchCacheByName.clear();
	this.researchCacheByEffect.clear();

	const techs = gameState.findAvailableTech();
	let addedByName = 0;
	let addedByEffect = 0;
	
	// if (this.Config.debug > 1) {
	// 	aiWarn("RESEARCH CACHE: Found " + techs.length + " available technologies");
	// }
	
	for (const tech of techs) {
		const addStartTime = Date.now();
		const techName = tech[0];
		const techTemplate = tech[1]._template;
		
		// Cache by name
		this.researchCacheByName.set(techName, tech);
		addedByName++;
		
		// Cache by effect (modifications)
		if (techTemplate.modifications) {
			for (const mod of techTemplate.modifications) {
				const effect = mod.value;
				if (!this.researchCacheByEffect.has(effect)) {
					this.researchCacheByEffect.set(effect, []);
				}
				this.researchCacheByEffect.get(effect).push(tech);
				addedByEffect++;
			}
		}

		// Track time spent adding technologies to cache
		const addEndTime = Date.now();
		this.performanceMetrics.cacheAddCount++;
		this.performanceMetrics.cacheAddTimeMs += (addEndTime - addStartTime);
		
		// if (this.Config.debug > 1) {
		// 	aiWarn("RESEARCH CACHE: Added tech '" + techName + "' with effects: " + 
		// 	           (techTemplate.modifications ? techTemplate.modifications.map(m => m.value).join(", ") : "none"));
		// }
	}
	
	// Update cache statistics
	this.cacheUpdateCount++;
	this.cacheUpdateReasons[reason] = (this.cacheUpdateReasons[reason] || 0) + 1;

	const cacheEndTime = Date.now();
	this.performanceMetrics.cacheUpdateTimeMs += (cacheEndTime - cacheStartTime);

	// if (this.Config.debug > 1) {
	// 	aiWarn("RESEARCH CACHE: Cache updated - added by name: " + addedByName + ", added by effect: " + addedByEffect + ", total updates: " + this.cacheUpdateCount);
	// }
};

/**
 * Find technology by name using cache
 */
ResearchManager.prototype.findTechByName = function(techName)
{
	this.performanceMetrics.cacheLookups++;
	this.performanceMetrics.cacheLookupSteps++; // One step to lookup in Map

	const startTime = Date.now();
	const result = this.researchCacheByName.get(techName);
	const endTime = Date.now();
	this.performanceMetrics.cacheLookupTimeMs += (endTime - startTime);
	
	return result;
};

/**
 * Find technologies by effect using cache
 */
ResearchManager.prototype.findTechsByEffect = function(effect)
{
	this.performanceMetrics.cacheLookups++;
	this.performanceMetrics.cacheLookupSteps++; // One step to lookup in Map

	const startTime = Date.now();
	const result = this.researchCacheByEffect.get(effect) || [];
	const endTime = Date.now();
	this.performanceMetrics.cacheLookupTimeMs += (endTime - startTime);
	
	return result;
};

/**
 * Fast lookup of a single technology by effect 
 */
ResearchManager.prototype.findTechByEffect = function(effect)
{
	const techs = this.findTechsByEffect(effect);
	return techs.length ? techs[0] : null;
};

/**
 * Get cache statistics for debugging
 */
ResearchManager.prototype.getCacheStats = function()
{
	return {
		cacheSize: this.researchCacheByName.size,
		effectTypes: this.researchCacheByEffect.size,
		updateCount: this.cacheUpdateCount,
		updateReasons: {...this.cacheUpdateReasons},
		performance: {...this.performanceMetrics}
	};
};

/**
 * Check if we can go to the next phase
 */
ResearchManager.prototype.checkPhase = function(gameState, queues)
{
	if (queues.majorTech.hasQueuedUnits())
		return;
	// Don't try to phase up if already trying to gather resources for a civil-centre or wonder
	if (queues.civilCentre.hasQueuedUnits() || queues.wonder.hasQueuedUnits())
		return;

	const currentPhaseIndex = gameState.currentPhase();
	const nextPhaseName = gameState.getPhaseName(currentPhaseIndex+1);
	if (!nextPhaseName)
		return;

	const r28Requirements =
		currentPhaseIndex == 1 && gameState.ai.HQ.getAccountedPopulation(gameState) >= this.Config.Economy.popPhase2 ||
		currentPhaseIndex == 2 && gameState.ai.HQ.getAccountedWorkers(gameState) > this.Config.Economy.workPhase3 ||
		currentPhaseIndex >= 3 && gameState.ai.HQ.getAccountedWorkers(gameState) > this.Config.Economy.workPhase4;
	if (r28Requirements && gameState.hasResearchers(nextPhaseName, true))
	{
		gameState.ai.HQ.phasing = currentPhaseIndex + 1;
		// Reset the queue priority in case it was changed during a previous phase update
		gameState.ai.queueManager.changePriority("majorTech", gameState.ai.Config.priorities.majorTech);
		queues.majorTech.addPlan(new ResearchPlan(gameState, nextPhaseName, true));
	}
};

ResearchManager.prototype.researchPopulationBonus = function(gameState, queues)
{
	if (queues.minorTech.hasQueuedUnits())
		return;

	const techs = gameState.findAvailableTech();
	for (const tech of techs)
	{
		if (!tech[1]._template.modifications)
			continue;
		// TODO may-be loop on all modifs and check if the effect if positive ?
		if (tech[1]._template.modifications[0].value !== "Population/Bonus")
			continue;
		queues.minorTech.addPlan(new ResearchPlan(gameState, tech[0]));
		break;
	}
};

ResearchManager.prototype.researchTradeBonus = function(gameState, queues)
{
	if (queues.minorTech.hasQueuedUnits())
		return;

	const techs = gameState.findAvailableTech();
	for (const tech of techs)
	{
		if (!tech[1]._template.modifications || !tech[1]._template.affects)
			continue;
		if (tech[1]._template.affects.indexOf("Trader") === -1)
			continue;
		// TODO may-be loop on all modifs and check if the effect if positive ?
		if (tech[1]._template.modifications[0].value !== "UnitMotion/WalkSpeed" &&
                    tech[1]._template.modifications[0].value !== "Trader/GainMultiplier")
			continue;
		queues.minorTech.addPlan(new ResearchPlan(gameState, tech[0]));
		break;
	}
};

/**
 * Research resource gathering speed technologies using cache system
 * Adds plan immediately, logs reason and priority
 */
ResearchManager.prototype.researchGatheringSpeedTechs = function(gameState, queues)
{
	if (queues.minorTech.hasQueuedUnits())
		return null;

	const loopStartTime = Date.now();
	const currentPhase = gameState.currentPhase();
	const numSupports = gameState.getOwnUnits().filter(filters.byClass("Support")).length;
	const numWorkers = gameState.countOwnEntitiesAndQueuedWithRole(Worker.ROLE_WORKER);
	const numFarms = gameState.getOwnStructures().filter(filters.byClass("Field")).length;
	const currentGatherRates = gameState.ai.HQ.GetCurrentGatherRates(gameState);
	const wantedGatherRates = gameState.ai.HQ.GetWantedGatherRates(gameState);

	const fishTech = this.findTechByEffect("ResourceGatherer/Rates/food.fish");
	const fruitTech = this.findTechByEffect("ResourceGatherer/Rates/food.fruit");
	const woodTech = this.findTechByEffect("ResourceGatherer/Rates/wood.tree");
	const grainTech = this.findTechByEffect("ResourceGatherer/Rates/food.grain");
	const metalTech = this.findTechByEffect("ResourceGatherer/Rates/metal.ore");
	const stoneTech = this.findTechByEffect("ResourceGatherer/Rates/stone.rock");

	const candidates = [];
	const pushCandidate = (tech, priority, reason) => {
		if (!tech)
			return;
		candidates.push({ name: tech[0], priority, reason });
	};

	if (currentPhase === 1)
	{
		if (gameState.ai.HQ.navalMap)
			pushCandidate(fishTech, 2000, "phase1 naval map: fish");
		pushCandidate(fruitTech, 2000, "phase1: fruit");
		if (numSupports > 30)
			pushCandidate(woodTech, 2000, "phase1: >30 support, wood");
		if (numFarms > 1)
			pushCandidate(grainTech, 2000, "phase1: >1 farms, grain");
		if (currentGatherRates.metal < wantedGatherRates.metal)
			pushCandidate(metalTech, 2000, "phase1: metal below target");
		if (currentGatherRates.stone < wantedGatherRates.stone)
			pushCandidate(stoneTech, 2000, "phase1: stone below target");
	}
	else if (currentPhase === 2)
	{
		if (gameState.ai.HQ.navalMap)
			pushCandidate(fishTech, 1000, "phase2 naval map: fish");
		//if (currentGatherRates.wood < wantedGatherRates.wood || wantedGatherRates.wood > 0)
		pushCandidate(woodTech, 2500, "phase2: need wood");
		//if (numFarms > 2 || wantedGatherRates.food > 0)
		pushCandidate(grainTech, 3500, "phase2: >2 farms, grain");
		//if (currentGatherRates.metal < wantedGatherRates.metal || wantedGatherRates.metal > 0)
		pushCandidate(metalTech, 2000, "phase2: need metal");
		//if (currentGatherRates.stone < wantedGatherRates.stone || wantedGatherRates.stone > 0)
		pushCandidate(stoneTech, 1600, "phase2: need stone");
	}
	else if (currentPhase >= 3)
	{
		//if (currentGatherRates.wood < wantedGatherRates.wood || wantedGatherRates.wood > 0)
		pushCandidate(woodTech, 2500, "phase3+: need wood");
		//if (numFarms > 3 || wantedGatherRates.food > 0)
		pushCandidate(grainTech, 3500, "phase3+: >3 farms, grain");
		//if (currentGatherRates.metal < wantedGatherRates.metal || wantedGatherRates.metal > 0)
		pushCandidate(metalTech, 2000, "phase3+: need metal");
		//if (currentGatherRates.stone < wantedGatherRates.stone || wantedGatherRates.stone > 0)
		pushCandidate(stoneTech, 1600, "phase3+: need stone");
	}

	if (!candidates.length)
	{
		const loopEndTime = Date.now();
		this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
		if (this.Config.debug > 0)
			aiWarn("RESEARCH: no candidates for research | phase=" + currentPhase +
				" supports=" + numSupports +
				" workers=" + numWorkers +
				" farms=" + numFarms +
				" rates=" + JSON.stringify(currentGatherRates) +
				" wanted=" + JSON.stringify(wantedGatherRates));
		return null;
	}

	candidates.sort((a, b) => b.priority - a.priority);
	const pick = candidates[0];

	gameState.ai.queueManager.changePriority("minorTech", pick.priority);
	const plan = new ResearchPlan(gameState, pick.name);
	plan.queueToReset = "minorTech";
	queues.minorTech.addPlan(plan);

	const loopEndTime = Date.now();
	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);

	if (this.Config.debug > 0)
	{
		aiWarn("RESEARCH: added research plan " + pick.name +
			" (priority " + pick.priority + ")" +
			" reason: " + pick.reason +
			" | phase=" + currentPhase +
			" supports=" + numSupports +
			" workers=" + numWorkers +
			" farms=" + numFarms +
			" rates=" + JSON.stringify(currentGatherRates) +
			" wanted=" + JSON.stringify(wantedGatherRates));
	}

	return pick;
};

// /home/kostya/dev/r28-mod/simulation/data/technologies/soldier_attack_melee_01.json

// /home/kostya/dev/r28-mod/simulation/data/technologies/soldier_attack_ranged_01.json

// /home/kostya/dev/r28-mod/simulation/data/technologies/soldier_resistance_hack_01.json

// /home/kostya/dev/r28-mod/simulation/data/technologies/soldier_resistance_pierce_01.json



ResearchManager.prototype.researchForgeTechs = function(gameState, queues)
{
	if (queues.minorTech.hasQueuedUnits())
		return null;

	const loopStartTime = Date.now();
	const currentPhase = gameState.currentPhase();
	const numSupports = gameState.getOwnUnits().filter(filters.byClass("Support")).length;
	const numWorkers = gameState.countOwnEntitiesAndQueuedWithRole(Worker.ROLE_WORKER);
	//const numFarms = gameState.getOwnStructures().filter(filters.byClass("Field")).length;
	//const currentGatherRates = gameState.ai.HQ.GetCurrentGatherRates(gameState);
	//const wantedGatherRates = gameState.ai.HQ.GetWantedGatherRates(gameState);

	//
	// 
	const attack_melee = this.findTechByEffect("Attack/Melee/Damage/Hack");
	
	const attack_ranged = this.findTechByEffect("Attack/Ranged/Damage/Pierce");
		
	const resistance_hack = this.findTechByEffect("Resistance/Entity/Damage/Hack");
	const resistance_pierce = this.findTechByEffect("Resistance/Entity/Damage/Pierce");
	

	


	const candidates = [];
	const pushCandidate = (tech, priority, reason) => {
		if (!tech)
			return;
		candidates.push({ name: tech[0], priority, reason });
	};

	if (currentPhase === 1 && gameState.getOwnEntitiesByClass("Forge", true).hasEntities())
	{
		if (numSupports > 30)
			pushCandidate(attack_melee, 2000, "...");
		if (numSupports > 30)
			pushCandidate(attack_ranged, 2000, "...");
		if (numSupports > 30)
			pushCandidate(resistance_hack, 2000, "...");
		if (numSupports > 30)
			pushCandidate(resistance_pierce, 2000, "...");


	}

	else if (currentPhase === 2 && gameState.getOwnEntitiesByClass("Forge", true).hasEntities())
	{
		if (numSupports > 30)
			pushCandidate(attack_melee, 2000, "...");
		if (numSupports > 30)
			pushCandidate(attack_ranged, 2000, "...");
		if (numSupports > 30)
			pushCandidate(resistance_hack, 2000, "...");
		if (numSupports > 30)
			pushCandidate(resistance_pierce, 2000, "...");

	}

	else if (currentPhase >= 3)
	{
		if (numSupports > 30)
			pushCandidate(attack_melee, 2000, "...");
		if (numSupports > 30)
			pushCandidate(attack_ranged, 2000, "...");
		if (numSupports > 30)
			pushCandidate(resistance_hack, 2000, "...");
		if (numSupports > 30)
			pushCandidate(resistance_pierce, 2000, "...");


	}

	if (!candidates.length)
	{
		const loopEndTime = Date.now();
		this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
		if (this.Config.debug > 0)
			aiWarn("RESEARCH: no candidates for research | phase=" + currentPhase +
				" supports=" + numSupports);
		return null;
	}

	candidates.sort((a, b) => b.priority - a.priority);
	const pick = candidates[0];

	gameState.ai.queueManager.changePriority("minorTech", pick.priority);
	const plan = new ResearchPlan(gameState, pick.name);
	plan.queueToReset = "minorTech";
	queues.minorTech.addPlan(plan);

	const loopEndTime = Date.now();
	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);

	if (this.Config.debug > 1)
	{
		aiWarn("RESEARCH: added research plan " + pick.name +
			" (priority " + pick.priority + ")" +
			" reason: " + pick.reason +
			" | phase=" + currentPhase +
			" supports=" + numSupports);
	}

	return pick;
};




ResearchManager.prototype.unlockChampions = function(gameState, queues)
{
	if (queues.minorTech.hasQueuedUnits())
		return null;

	const loopStartTime = Date.now();
	const currentPhase = gameState.currentPhase();
	const numSupports = gameState.getOwnUnits().filter(filters.byClass("Support")).length;
	const numWorkers = gameState.countOwnEntitiesAndQueuedWithRole(Worker.ROLE_WORKER);
	
	// 

	const unlock_champion_infantry = this.findTechByName("unlock_champion_infantry");
	
	const unlock_champion_cavalry = this.findTechByName("unlock_champion_cavalry");
		
	const unlock_champion_chariot = this.findTechByName("unlock_champion_chariots");
	
	const candidates = [];
	const pushCandidate = (tech, priority, reason) => {
		if (!tech)
			return;
		candidates.push({ name: tech[0], priority, reason });
	};
	
	if (numSupports > 30)
		pushCandidate(unlock_champion_infantry, 5000, "...");
	if (numSupports > 30)
		pushCandidate(unlock_champion_cavalry, 3000, "...");
	if (numSupports > 30)
		pushCandidate(unlock_champion_chariot, 2000, "...");

	if (!candidates.length)
	{
		const loopEndTime = Date.now();
		this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
		if (this.Config.debug > 0)
			aiWarn("RESEARCH: no candidates for research | phase=" + currentPhase +
				" supports=" + numSupports);
		return null;
	}

	candidates.sort((a, b) => b.priority - a.priority);
	const pick = candidates[0];

	gameState.ai.queueManager.changePriority("minorTech", pick.priority);
	const plan = new ResearchPlan(gameState, pick.name);
	plan.queueToReset = "minorTech";
	queues.minorTech.addPlan(plan);

	const loopEndTime = Date.now();
	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);

	if (this.Config.debug > 1)
	{
		aiWarn("RESEARCH: added research plan " + pick.name +
			" (priority " + pick.priority + ")" +
			" reason: " + pick.reason +
			" | phase=" + currentPhase +
			" supports=" + numSupports);
	}

	return pick;
};





/** Techs to be searched for as soon as they are available */
ResearchManager.prototype.researchWantedTechs = function(gameState, techs)
{
	// Track loop performance
	this.performanceMetrics.loopSearches++;
	const loopStartTime = Date.now();
	
	// Use cache if available, otherwise fall back to provided techs
	let techsToSearch = techs;
	if (this.researchCacheByName.size > 0) {
		techsToSearch = Array.from(this.researchCacheByName.values());
	}
	
	for (const tech of techsToSearch)
	{
		this.performanceMetrics.loopSteps++; // Track each iteration
		
		if (tech[0].indexOf("unlock_champion") == 0)
		{
			const loopEndTime = Date.now();
			this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
			return { "name": tech[0], "increasePriority": true };
		}
		if (tech[0] == "traditional_army_sele" || tech[0] == "reformed_army_sele")
		{
			const loopEndTime = Date.now();
			this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
			return { "name": pickRandom(["traditional_army_sele", "reformed_army_sele"]), "increasePriority": true };
		}

		if (!tech[1]._template.modifications)
			continue;
		const template = tech[1]._template;
		if (phase1)
		{
			const cost = template.cost;
			let costMax = 0;
			for (const res in cost)
				costMax = Math.max(costMax, Math.max(cost[res]-available[res], 0));
			if (10*numWorkers < costMax)
				continue;
		}
		for (const i in template.modifications)
		{
			this.performanceMetrics.loopSteps++; // Additional step for modification check
			// if (gameState.ai.HQ.navalMap && template.modifications[i].value === "ResourceGatherer/Rates/food.fish")
			// {
			// 	const loopEndTime = Date.now();
			// 	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
			// 	return { "name": tech[0], "increasePriority": this.CostSum(template.cost) < 400 };
			// }
			// else if (template.modifications[i].value === "ResourceGatherer/Rates/food.fruit")
			// {
			// 	const loopEndTime = Date.now();
			// 	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
			// 	return { "name": tech[0], "increasePriority": this.CostSum(template.cost) < 400 };
			// }
			// else if (template.modifications[i].value === "ResourceGatherer/Rates/food.grain")
			// {
			// 	const loopEndTime = Date.now();
			// 	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
			// 	return { "name": tech[0], "increasePriority": false };
			// }
			// else if (template.modifications[i].value === "ResourceGatherer/Rates/wood.tree")
			// {
			// 	const loopEndTime = Date.now();
			// 	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
			// 	return { "name": tech[0], "increasePriority": this.CostSum(template.cost) < 400 };
			// }
			// else if (template.modifications[i].value === "ResourceGatherer/Rates/stone.rock")
			// {
			// 	const loopEndTime = Date.now();
			// 	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
			// 	return { "name": tech[0], "increasePriority": false };
			// }
			// else if (template.modifications[i].value === "ResourceGatherer/Rates/metal.ore")
			// {
			// 	const loopEndTime = Date.now();
			// 	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
			// 	return { "name": tech[0], "increasePriority": false };
			// }
			//else if (template.modifications[i].value.startsWith("ResourceGatherer/Capacities") && numWorkers > 85)
			// if (template.modifications[i].value.startsWith("ResourceGatherer/Capacities"))
			// {
			// 	const loopEndTime = Date.now();
			// 	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
			// 	return { "name": tech[0], "increasePriority": false };
			// }
			if (template.modifications[i].value === "Health/Max")
			{
				const loopEndTime = Date.now();
				this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
				return { "name": tech[0], "increasePriority": false };
			}
			else if (template.modifications[i].value === "UnitMotion/WalkSpeed")
			{
				const loopEndTime = Date.now();
				this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
				return { "name": tech[0], "increasePriority": false };
			}
			else if (template.modifications[i].value === "BuildingAI/DefaultArrowCount")
			{
				const loopEndTime = Date.now();
				this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
				return { "name": tech[0], "increasePriority": this.CostSum(template.cost) < 400 };
			}
			else if (template.modifications[i].value === "Health/RegenRate")
			{
				const loopEndTime = Date.now();
				this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
				return { "name": tech[0], "increasePriority": false };
			}
			else if (template.modifications[i].value === "Health/IdleRegenRate")
			{
				const loopEndTime = Date.now();
				this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
				return { "name": tech[0], "increasePriority": false };
			}
		}
	}
	
	const loopEndTime = Date.now();
	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
	return null;
};

/** Techs to be searched for as soon as they are available, but only after phase 2 */
ResearchManager.prototype.researchPreferredTechs = function(gameState, techs)
{
	// Track loop performance
	this.performanceMetrics.loopSearches++;
	const loopStartTime = Date.now();
	
	const phase2 = gameState.currentPhase() === 2;
	const available = phase2 ? gameState.ai.queueManager.getAvailableResources(gameState) : null;
	const numWorkers = phase2 ? gameState.getOwnEntitiesByRole(Worker.ROLE_WORKER, true).length : 0;
	
	// Use cache if available, otherwise fall back to provided techs
	let techsToSearch = techs;
	if (this.researchCacheByName.size > 0) {
		techsToSearch = Array.from(this.researchCacheByName.values());
	}
	
	for (const tech of techsToSearch)
	{
		this.performanceMetrics.loopSteps++; // Track each iteration
		
		if (!tech[1]._template.modifications)
			continue;
		const template = tech[1]._template;
		if (phase2)
		{
			const cost = template.cost;
			let costMax = 0;
			for (const res in cost)
				costMax = Math.max(costMax, Math.max(cost[res]-available[res], 0));
			if (10*numWorkers < costMax)
				continue;
		}
		for (const i in template.modifications)
		{
			if (template.modifications[i].value === "ResourceGatherer/Rates/stone.rock")
				return { "name": tech[0], "increasePriority": this.CostSum(template.cost) < 400 };
			else if (template.modifications[i].value === "ResourceGatherer/Rates/metal.ore")
				return { "name": tech[0], "increasePriority": this.CostSum(template.cost) < 400 };
			else if (template.modifications[i].value === "BuildingAI/DefaultArrowCount")
				return { "name": tech[0], "increasePriority": this.CostSum(template.cost) < 400 };
			else if (template.modifications[i].value === "Health/RegenRate")
				return { "name": tech[0], "increasePriority": false };
			else if (template.modifications[i].value === "Health/IdleRegenRate")
				return { "name": tech[0], "increasePriority": false };
		}
	}

	const loopEndTime = Date.now();
	this.performanceMetrics.loopTimeMs += (loopEndTime - loopStartTime);
	return null;
};

ResearchManager.prototype.update = function(gameState, queues)
{
	// Initialize cache on first run
	if (this.cacheUpdateCount === 0) {
		this.updateResearchCache(gameState, "initialization");
	}

	// Periodic refresh every 2 turns, guarded by HQ turn cache to run once per turn
	// const hqTurnCache = gameState.ai.HQ?.turnCache;
	// if (hqTurnCache && gameState.ai.playedTurn % 2 === 0 && !hqTurnCache.researchCacheRefreshed)
	// {
	// 	if (this.Config.debug > 1)
	// 		aiWarn("RESEARCH CACHE: Periodic refresh (every 2 turns)");
	// 	this.updateResearchCache(gameState, "periodic");
	// 	hqTurnCache.researchCacheRefreshed = true;
	// }

	
	// if (gameState.ai.playedTurn % 2 === 0)
	// {
	// 	if (this.Config.debug > 1)
	// 		aiWarn("RESEARCH CACHE: Periodic refresh (every 2 turns)");
	// 	this.updateResearchCache(gameState, "periodic");
		
	// }
	
	// Log performance metrics every 4 turns
	if (gameState.ai.playedTurn % 4 === 0 && this.Config.debug > 1) {
		this.logPerformanceMetrics();
	}
	if (this.Config.debug > 1) {
    	aiWarn("RESEARCH: minorTech has queued units: " + queues.minorTech.hasQueuedUnits());
    	aiWarn("RESEARCH: majorTech has queued units: " + queues.majorTech.hasQueuedUnits());
	}

	const currentPhase = gameState.currentPhase();
	
	// Allow minor research technologies even if major phase is in progress
	if (currentPhase <=2 && queues.minorTech.hasQueuedUnits())
		return;

	// 

	const techs = gameState.findAvailableTech();
	this.researchPopulationBonus(gameState, queues);




	this.researchGatheringSpeedTechs(gameState, queues);


	// researchForgeTechs

	this.researchForgeTechs(gameState, queues);



	if (currentPhase >= 3)
		this.unlockChampions(gameState, queues);



	// Priority try resource gathering upgrades
	// const gatherPlan = this.researchGatheringSpeedTechs(gameState, queues);
	// if (gatherPlan)
	// 	return;
 //
	// // researchForgeTechs
 //
	// const forgePlan = this.researchForgeTechs(gameState, queues);
	// if (forgePlan)
	// 	return;
 //
	// const currentPhase = gameState.currentPhase();
	// if (currentPhase >= 3)
	// {
	// 	const championPlan = this.unlockChampions(gameState, queues);
	// 	if (championPlan)
	// 		return;
	// }

};

ResearchManager.prototype.CostSum = function(cost)
{
	let costSum = 0;
	for (const res in cost)
		costSum += cost[res];
	return costSum;
};

/**
 * Trigger cache update when phase changes
 */
ResearchManager.prototype.onPhaseChange = function(gameState, newPhase)
{
	if (this.Config.debug > 1) {
		aiWarn("RESEARCH CACHE: Phase change detected - updating cache");
	}
	this.updateResearchCache(gameState, "phaseChange");
};

/**
 * Trigger cache update when building construction is completed
 */
ResearchManager.prototype.onBuildingComplete = function(gameState, buildingTemplate)
{
	if (this.Config.debug > 1) {
		aiWarn("RESEARCH CACHE: Building completed - updating cache: " + buildingTemplate);
	}
	this.updateResearchCache(gameState, "buildingComplete");
};

/**
 * Trigger cache update when research is completed
 */
// ResearchManager.prototype.onResearchComplete = function(gameState, techName)
// {
// 	if (this.Config.debug > 1) {
// 		aiWarn("RESEARCH CACHE: Research completed - updating cache: " + techName);
// 	}
// 	this.updateResearchCache(gameState, "researchComplete");
// };

/**
 * Log performance metrics comparing old vs new logic
 */
ResearchManager.prototype.logPerformanceMetrics = function()
{
	if (this.Config.debug > 1) {
		const stats = this.getCacheStats();
		aiWarn("RESEARCH CACHE PERFORMANCE:");
		aiWarn("- Cache size: " + stats.cacheSize + " technologies");
		aiWarn("- Effect types: " + stats.effectTypes);
		aiWarn("- Cache updates: " + stats.updateCount);
		aiWarn("- Update reasons: " + JSON.stringify(stats.updateReasons));
		aiWarn("- Loop searches: " + stats.performance.loopSearches + ", steps: " + stats.performance.loopSteps + ", time: " + stats.performance.loopTimeMs.toFixed(3) + "ms");
		aiWarn("- Cache lookups: " + stats.performance.cacheLookups + ", steps: " + stats.performance.cacheLookupSteps + ", time: " + stats.performance.cacheLookupTimeMs.toFixed(3) + "ms");
		aiWarn("- Cache updates: " + stats.updateCount + " times, total time: " + stats.performance.cacheUpdateTimeMs.toFixed(3) + "ms (adds: " + stats.performance.cacheAddCount + " techs, " + stats.performance.cacheAddTimeMs.toFixed(3) + "ms)");
		
		if (stats.performance.loopSteps > 0) {
			const avgLoopTime = (stats.performance.loopTimeMs / stats.performance.loopSteps).toFixed(6);
			aiWarn("- Avg loop step time: " + avgLoopTime + "ms");
		}
		
		if (stats.performance.cacheLookups > 0) {
			const avgLookupTime = (stats.performance.cacheLookupTimeMs / stats.performance.cacheLookups).toFixed(6);
			aiWarn("- Avg cache lookup time: " + avgLookupTime + "ms per search");
		}
		
		if (stats.updateCount > 0) {
			const avgCacheTime = (stats.performance.cacheUpdateTimeMs / stats.updateCount).toFixed(3);
			aiWarn("- Avg cache update time: " + avgCacheTime + "ms per update");
		}
	}
};

ResearchManager.prototype.Serialize = function()
{
	return {};
};

ResearchManager.prototype.Deserialize = function(data)
{
};
