/**
 * Condition-based Waiting Utility
 * Replaces hardcoded delays with intelligent condition-based waiting
 */

export interface WaitCondition {
	check: () => Promise<boolean> | boolean;
	description?: string;
}

export interface WaitOptions {
	timeout?: number; // Maximum time to wait in milliseconds
	interval?: number; // Polling interval in milliseconds
	retries?: number; // Maximum number of retries
	exponentialBackoff?: boolean; // Use exponential backoff for intervals
	minInterval?: number; // Minimum interval for exponential backoff
	maxInterval?: number; // Maximum interval for exponential backoff
}

export interface WaitResult {
	success: boolean;
	duration: number;
	attempts: number;
	reason?: string;
}

export class ConditionWaiter {
	private static readonly DEFAULT_TIMEOUT = 120000; // 2 minutes for Claude operations
	private static readonly DEFAULT_INTERVAL = 500; // 500ms
	private static readonly DEFAULT_MIN_INTERVAL = 100; // 100ms
	private static readonly DEFAULT_MAX_INTERVAL = 5000; // 5 seconds
	private static readonly DEFAULT_RETRIES = 60;

	/**
	 * Wait for a condition to be met
	 */
	public static async waitForCondition(
		condition: WaitCondition,
		options: WaitOptions = {}
	): Promise<WaitResult> {
		const startTime = Date.now();
		const timeout = options.timeout || this.DEFAULT_TIMEOUT;
		const baseInterval = options.interval || this.DEFAULT_INTERVAL;
		const maxRetries = options.retries || this.DEFAULT_RETRIES;
		const useExponentialBackoff = options.exponentialBackoff || false;
		const minInterval = options.minInterval || this.DEFAULT_MIN_INTERVAL;
		const maxInterval = options.maxInterval || this.DEFAULT_MAX_INTERVAL;

		let attempts = 0;
		let currentInterval = baseInterval;

		while (attempts < maxRetries && (Date.now() - startTime) < timeout) {
			attempts++;

			try {
				const result = await condition.check();
				if (result) {
					return {
						success: true,
						duration: Date.now() - startTime,
						attempts,
						reason: 'Condition met'
					};
				}
			} catch (error) {
				console.warn(`Condition check failed on attempt ${attempts}: ${error.message}`);
			}

			// Calculate next interval
			if (useExponentialBackoff) {
				currentInterval = Math.min(
					Math.max(currentInterval * 1.5, minInterval),
					maxInterval
				);
			} else {
				currentInterval = baseInterval;
			}

			// Wait before next attempt
			await new Promise(resolve => setTimeout(resolve, currentInterval));
		}

		return {
			success: false,
			duration: Date.now() - startTime,
			attempts,
			reason: `Timeout after ${timeout}ms or max retries (${maxRetries}) reached`
		};
	}

	/**
	 * Wait for tmux window to be ready (output contains expected content)
	 */
	public static async waitForTmuxWindow(
		bridge: any,
		sessionName: string,
		windowIndex: number,
		expectedContent: string | RegExp,
		options: WaitOptions = {}
	): Promise<WaitResult> {
		const condition: WaitCondition = {
			check: async () => {
				try {
					const content = await bridge.captureWindowContent(sessionName, windowIndex, 20);
					if (typeof expectedContent === 'string') {
						return content.includes(expectedContent);
					} else {
						return expectedContent.test(content);
					}
				} catch (error) {
					return false;
				}
			},
			description: `Waiting for tmux window ${sessionName}:${windowIndex} to contain: ${expectedContent}`
		};

		return this.waitForCondition(condition, {
			timeout: 30000,
			interval: 1000,
			exponentialBackoff: true,
			...options
		});
	}

	/**
	 * Wait for command completion (prompt appears)
	 */
	public static async waitForPrompt(
		bridge: any,
		sessionName: string,
		windowIndex: number,
		promptPattern: RegExp = /[\$#%>]\s*$/m,
		options: WaitOptions = {}
	): Promise<WaitResult> {
		const condition: WaitCondition = {
			check: async () => {
				try {
					const content = await bridge.captureWindowContent(sessionName, windowIndex, 5);
					return promptPattern.test(content);
				} catch (error) {
					return false;
				}
			},
			description: `Waiting for command prompt in ${sessionName}:${windowIndex}`
		};

		return this.waitForCondition(condition, {
			timeout: 10000,
			interval: 500,
			exponentialBackoff: true,
			...options
		});
	}

	/**
	 * Wait for Claude agent to be ready (specific output patterns)
	 */
	public static async waitForClaudeReady(
		bridge: any,
		sessionName: string,
		windowIndex: number,
		options: WaitOptions = {}
	): Promise<WaitResult> {
		const readyPatterns = [
			/Claude\s+Code/i,
			/Model:\s+Claude/i,
			/Ready\s+to\s+help/i,
			/How\s+can\s+I\s+help/i,
			/What\s+would\s+you\s+like/i
		];

		const condition: WaitCondition = {
			check: async () => {
				try {
					const content = await bridge.captureWindowContent(sessionName, windowIndex, 10);
					return readyPatterns.some(pattern => pattern.test(content));
				} catch (error) {
					return false;
				}
			},
			description: `Waiting for Claude agent to be ready in ${sessionName}:${windowIndex}`
		};

		return this.waitForCondition(condition, {
			timeout: 60000, // Increased to 60 seconds for Claude startup
			interval: 2000, // Check every 2 seconds
			exponentialBackoff: true,
			...options
		});
	}

	/**
	 * Wait for git operation completion
	 */
	public static async waitForGitOperation(
		bridge: any,
		sessionName: string,
		windowIndex: number,
		operation: 'push' | 'pull' | 'commit' | 'branch',
		options: WaitOptions = {}
	): Promise<WaitResult> {
		const operationPatterns = {
			push: [/branch.*up.to.date/i, /everything.*up.to.date/i, /\d+\s+objects/i, /Total\s+\d+/i],
			pull: [/Already.*up.to.date/i, /Fast.forward/i, /files?\s+changed/i],
			commit: [/\d+\s+files?\s+changed/i, /create\s+mode/i, /\[\w+.*\]/i],
			branch: [/Switched\s+to/i, /already\s+on/i, /branch.*set\s+up/i]
		};

		const errorPatterns = [
			/error:/i,
			/fatal:/i,
			/failed/i,
			/rejected/i,
			/permission\s+denied/i,
			/authentication\s+failed/i
		];

		const condition: WaitCondition = {
			check: async () => {
				try {
					const content = await bridge.captureWindowContent(sessionName, windowIndex, 10);
					
					// Check for errors first
					if (errorPatterns.some(pattern => pattern.test(content))) {
						throw new Error(`Git operation failed: ${content.substring(0, 200)}`);
					}

					// Check for success patterns
					return operationPatterns[operation].some(pattern => pattern.test(content));
				} catch (error) {
					if (error.message.includes('Git operation failed')) {
						throw error;
					}
					return false;
				}
			},
			description: `Waiting for git ${operation} operation to complete in ${sessionName}:${windowIndex}`
		};

		return this.waitForCondition(condition, {
			timeout: 30000,
			interval: 1000,
			exponentialBackoff: true,
			...options
		});
	}

	/**
	 * Wait for file or directory to exist
	 */
	public static async waitForPath(
		pathToCheck: string,
		options: WaitOptions = {}
	): Promise<WaitResult> {
		const fs = require('fs').promises;

		const condition: WaitCondition = {
			check: async () => {
				try {
					await fs.access(pathToCheck);
					return true;
				} catch {
					return false;
				}
			},
			description: `Waiting for path to exist: ${pathToCheck}`
		};

		return this.waitForCondition(condition, {
			timeout: 10000,
			interval: 200,
			...options
		});
	}

	/**
	 * Wait for Claude to complete a potentially long-running operation
	 */
	public static async waitForClaudeOperation(
		bridge: any,
		sessionName: string,
		windowIndex: number,
		operationDescription: string,
		options: WaitOptions = {}
	): Promise<WaitResult> {
		const completionPatterns = [
			/✅/,                    // Success emoji
			/completed/i,           // "completed" text
			/finished/i,            // "finished" text  
			/done/i,               // "done" text
			/ready/i,              // "ready" text
			/[>$#%]\s*$/m,         // Command prompt returned
		];

		const condition: WaitCondition = {
			check: async () => {
				try {
					const content = await bridge.captureWindowContent(sessionName, windowIndex, 20);
					return completionPatterns.some(pattern => pattern.test(content));
				} catch (error) {
					return false;
				}
			},
			description: `Waiting for Claude to complete: ${operationDescription} in ${sessionName}:${windowIndex}`
		};

		return this.waitForCondition(condition, {
			timeout: 600000, // 10 minutes for long operations
			interval: 10000, // Check every 10 seconds
			exponentialBackoff: false,
			...options
		});
	}

	/**
	 * Wait for agent to be responsive (can execute simple commands)
	 */
	public static async waitForAgentResponsive(
		bridge: any,
		sessionName: string,
		windowIndex: number,
		options: WaitOptions = {}
	): Promise<WaitResult> {
		const condition: WaitCondition = {
			check: async () => {
				try {
					// Send a simple test command
					await bridge.sendCommandToWindow(sessionName, windowIndex, 'echo "agent_test_ready"');
					
					// Wait a bit for response
					await new Promise(resolve => setTimeout(resolve, 500));
					
					// Check if we got the expected response
					const content = await bridge.captureWindowContent(sessionName, windowIndex, 5);
					return content.includes('agent_test_ready');
				} catch (error) {
					return false;
				}
			},
			description: `Waiting for agent to be responsive in ${sessionName}:${windowIndex}`
		};

		return this.waitForCondition(condition, {
			timeout: 10000,
			interval: 2000,
			exponentialBackoff: false,
			...options
		});
	}

	/**
	 * Wait for process to complete (no active processes in tmux pane)
	 */
	public static async waitForProcessCompletion(
		bridge: any,
		sessionName: string,
		windowIndex: number,
		options: WaitOptions = {}
	): Promise<WaitResult> {
		const condition: WaitCondition = {
			check: async () => {
				try {
					// Check if there are any running processes
					await bridge.sendCommandToWindow(sessionName, windowIndex, 'ps aux | grep -v grep | grep -c "[^]]$" || echo "0"');
					
					await new Promise(resolve => setTimeout(resolve, 300));
					
					const content = await bridge.captureWindowContent(sessionName, windowIndex, 3);
					
					// Look for process count of 0 or 1 (just the shell)
					const lines = content.trim().split('\n');
					const lastLine = lines[lines.length - 1];
					const processCount = parseInt(lastLine.trim());
					
					return processCount <= 1;
				} catch (error) {
					return false;
				}
			},
			description: `Waiting for processes to complete in ${sessionName}:${windowIndex}`
		};

		return this.waitForCondition(condition, {
			timeout: 60000,
			interval: 2000,
			exponentialBackoff: true,
			...options
		});
	}

	/**
	 * Wait for network connectivity
	 */
	public static async waitForNetwork(
		host: string = 'google.com',
		options: WaitOptions = {}
	): Promise<WaitResult> {
		const { exec } = require('child_process');
		const { promisify } = require('util');
		const execAsync = promisify(exec);

		const condition: WaitCondition = {
			check: async () => {
				try {
					await execAsync(`ping -c 1 -W 2 ${host}`, { timeout: 3000 });
					return true;
				} catch {
					return false;
				}
			},
			description: `Waiting for network connectivity to ${host}`
		};

		return this.waitForCondition(condition, {
			timeout: 30000,
			interval: 2000,
			exponentialBackoff: true,
			...options
		});
	}

	/**
	 * Create a combined condition (AND logic)
	 */
	public static combineConditions(...conditions: WaitCondition[]): WaitCondition {
		return {
			check: async () => {
				for (const condition of conditions) {
					const result = await condition.check();
					if (!result) return false;
				}
				return true;
			},
			description: `Combined conditions: ${conditions.map(c => c.description).join(' AND ')}`
		};
	}

	/**
	 * Create an any condition (OR logic)
	 */
	public static anyCondition(...conditions: WaitCondition[]): WaitCondition {
		return {
			check: async () => {
				for (const condition of conditions) {
					const result = await condition.check();
					if (result) return true;
				}
				return false;
			},
			description: `Any condition: ${conditions.map(c => c.description).join(' OR ')}`
		};
	}
}