// AGPL-3.0-only
//
// This is free software for the public good of a permacomputer
// hosted at permacomputer.com, an always-on computer by the people, for the
// people. One which is durable, easy to repair, and distributed like tap water
// for machine learning intelligence.
//
// The permacomputer is community-owned infrastructure optimized around
// four values:
//
//   TRUTH    - First principles, math & science, open source code
//   FREEDOM  - Voluntary partnerships, freedom from tyranny & corporate control
//   HARMONY  - Minimal waste, self-renewing systems with diverse connections
//   LOVE     - Be yourself without hurting others, cooperation through natural law
//
// This software contributes to that vision by making machine learning
// accessible to everyone through a free, open, embeddable chat interface.
// Code is seeds to sprout on any abandoned technology.
//
// Learn more: https://www.permacomputer.com
//
// NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
//
// Copyright 2025-2026 TimeHexOn & foxhop & russell@unturf
// https://www.permacomputer.com

// Code execution sandbox functionality
import { CODE_EXEC_URL } from "./config.js";
import * as un from "./un.js";

// Helper function to sleep/wait
function sleep(ms) {
	return new Promise(resolve => setTimeout(resolve, ms));
}

// Helper function to escape HTML for safe display
export function escapeHtml(text) {
	const div = document.createElement('div');
	div.textContent = text;
	return div.innerHTML;
}

// Function to execute code in sandbox
export async function executeCode(code, language, resultsContainer, playButton) {
	// Update button state
	playButton.textContent = 'Running...';
	playButton.disabled = true;

	// Clear previous results
	resultsContainer.innerHTML = '<div style="color: var(--uncloseai-secondary-text);">Executing code...</div>';

	try {
		// If no language specified, default to Python
		if (!language) {
			language = 'python';
		}

		// Check if un.com API is configured
		if (un.isConfigured()) {
			await executeWithUnsandbox(code, language, resultsContainer);
		} else {
			await executeWithFallback(code, language, resultsContainer);
		}

	} catch (error) {
		console.error('Error executing code:', error);
		resultsContainer.innerHTML = `<div style="color: #dc3545;">Error: ${escapeHtml(error.message)}</div>`;
	} finally {
		// Reset button state
		playButton.textContent = '▶ Run';
		playButton.disabled = false;
	}
}

// Execute using un.com SDK (authenticated)
async function executeWithUnsandbox(code, language, resultsContainer) {
	let pollCount = 0;
	let cancelButton = null;
	let currentJobId = null;

	try {
		// Start async execution to get job_id for cancel button
		currentJobId = await un.executeAsync(language, code);

		// Create cancel button (shown after 3 seconds)
		cancelButton = createCancelButton(resultsContainer, async () => {
			if (currentJobId) {
				try {
					await un.cancelJob(currentJobId);
					cancelButton.disabled = true;
					cancelButton.textContent = 'Cancelling...';
				} catch (error) {
					console.error('Error cancelling job:', error);
				}
			}
		});

		// Wait for job with progress callback
		const result = await un.waitForJob(currentJobId, null, null, (count) => {
			pollCount = count;
			// Show cancel button after poll #5 (around 3 seconds)
			if (count >= 5 && cancelButton) {
				cancelButton.style.display = 'inline-block';
			}
		});

		// Hide cancel button
		if (cancelButton) {
			cancelButton.style.display = 'none';
		}

		// Display result
		if (result.status === 'completed') {
			displayExecutionResults(result, resultsContainer, language);
		} else {
			displayErrorResult(result, resultsContainer);
		}

	} catch (error) {
		if (cancelButton) {
			cancelButton.style.display = 'none';
		}
		throw error;
	}
}

// Execute using fallback endpoint (unauthenticated)
async function executeWithFallback(code, language, resultsContainer) {
	// Use /execute/async endpoint with polling
	const asyncResponse = await fetch(`${CODE_EXEC_URL}/execute/async`, {
		method: 'POST',
		headers: {
			'Content-Type': 'application/json',
		},
		body: JSON.stringify({
			language: language,
			code: code
		})
	});

	if (!asyncResponse.ok) {
		throw new Error(`HTTP error! status: ${asyncResponse.status}`);
	}

	const { job_id } = await asyncResponse.json();

	// Poll for results using same delays as SDK
	const delays = [300, 450, 700, 900, 650, 1600, 2000];
	let pollCount = 0;

	// Create cancel button
	const cancelButton = createCancelButton(resultsContainer, async () => {
		try {
			await fetch(`${CODE_EXEC_URL}/jobs/${job_id}`, { method: 'DELETE' });
			cancelButton.disabled = true;
			cancelButton.textContent = 'Cancelling...';
		} catch (error) {
			console.error('Error cancelling job:', error);
		}
	});

	while (true) {
		await sleep(delays[Math.min(pollCount, delays.length - 1)]);
		pollCount++;

		const jobResponse = await fetch(`${CODE_EXEC_URL}/jobs/${job_id}`);
		if (!jobResponse.ok) {
			throw new Error(`Failed to fetch job status: ${jobResponse.status}`);
		}

		const job = await jobResponse.json();

		if (job.status !== 'pending' && job.status !== 'running') {
			// Job finished - hide cancel button
			cancelButton.style.display = 'none';

			if (job.status === 'completed') {
				const result = job.result || job;
				displayExecutionResults(result, resultsContainer, language);
			} else {
				displayErrorResult(job.result || job, resultsContainer);
			}
			break;
		}

		// Show cancel button after poll #5 (around 3 seconds)
		if (pollCount === 5) {
			cancelButton.style.display = 'inline-block';
		}
	}
}

// Create cancel button helper
function createCancelButton(resultsContainer, onCancel) {
	let cancelButton = resultsContainer.querySelector('.uncloseai-cancel-execution-btn');
	if (!cancelButton) {
		cancelButton = document.createElement('button');
		cancelButton.textContent = 'Cancel';
		cancelButton.className = 'uncloseai-cancel-execution-btn uncloseai-btn-small';
		cancelButton.style.display = 'none';
		cancelButton.style.marginTop = '8px';
		cancelButton.onclick = onCancel;
		resultsContainer.appendChild(cancelButton);
	} else {
		cancelButton.disabled = false;
		cancelButton.textContent = 'Cancel';
		cancelButton.style.display = 'none';
		cancelButton.onclick = onCancel;
	}
	return cancelButton;
}

// Display error result
function displayErrorResult(result, resultsContainer) {
	const errorMsg = result.error || result.stderr || result.message || `Execution ${result.status || 'failed'}`;
	const partialOutput = result.stdout || result.partial_output;

	let outputHtml = `<div style="color: #dc3545; font-weight: bold;">${escapeHtml(errorMsg)}</div>`;
	if (partialOutput) {
		outputHtml += '<div style="color: var(--uncloseai-secondary-text); margin-top: 8px;">Partial output:</div>';
		outputHtml += `<div style="color: var(--uncloseai-primary-text); margin-left: 10px;">${escapeHtml(partialOutput)}</div>`;
	}
	resultsContainer.innerHTML = outputHtml;
}

// Helper function to display execution results
export function displayExecutionResults(result, resultsContainer, language) {
	// Format and display results
	let outputHtml = '';

	// Handle nested response structure - check if stdout is an object with nested data
	let actualStdout = result.stdout;
	let actualStderr = result.stderr;

	// If stdout is an object (nested response), extract the actual stdout/stderr
	if (typeof result.stdout === 'object' && result.stdout !== null) {
		actualStdout = result.stdout.stdout || '';
		actualStderr = result.stdout.stderr || '';
	}

	// Show language
	if (language) {
		outputHtml += `<div style="color: var(--uncloseai-primary-text); margin-bottom: 8px; font-weight: bold;">Language: ${language}</div>`;
	}

	// Show stdout
	if (actualStdout) {
		outputHtml += '<div style="color: #28a745; font-weight: bold;">Output:</div>';
		outputHtml += `<div style="color: var(--uncloseai-primary-text); margin-left: 10px;">${escapeHtml(actualStdout)}</div>`;
	}

	// Show stderr if present
	if (actualStderr) {
		outputHtml += '<div style="color: #dc3545; font-weight: bold; margin-top: 8px;">Errors/Warnings:</div>';
		outputHtml += `<div style="color: #dc3545; margin-left: 10px;">${escapeHtml(actualStderr)}</div>`;
	}

	// If no output at all
	if (!actualStdout && !actualStderr) {
		outputHtml += '<div style="color: var(--uncloseai-secondary-text);">(No output produced)</div>';
	}

	resultsContainer.innerHTML = outputHtml;
}

// Function to add code execution buttons to code blocks
export function addCodeExecutionButton(codeBlock) {
	// Check if we already have an execution button
	const pre = codeBlock.parentElement;
	if (!pre || pre.tagName.toLowerCase() !== 'pre') return;
	if (pre.querySelector('.uncloseai-run-code-btn')) return; // Already has button

	// Try to detect language from the code block's class
	let language = null;
	const classes = codeBlock.className.split(' ');
	for (const cls of classes) {
		if (cls.startsWith('language-')) {
			language = cls.replace('language-', '');
			break;
		}
	}

	// Default to Python if no language specified
	if (!language) {
		language = 'python';
	}

	// Get the code content
	const code = codeBlock.dataset.fullContent || codeBlock.textContent;

	// Create run button
	const runButton = document.createElement('button');
	runButton.textContent = '▶ Run';
	runButton.className = 'uncloseai-run-code-btn uncloseai-code-copy-btn'; // Reuse copy button styling
	runButton.title = `Run ${language} code in sandbox`;

	// Create results container
	let resultsContainer = pre.parentElement.querySelector('.uncloseai-code-execution-results');
	if (!resultsContainer) {
		resultsContainer = document.createElement('div');
		resultsContainer.className = 'uncloseai-code-execution-results';
		resultsContainer.style.marginTop = '10px';
		resultsContainer.style.padding = '10px';
		resultsContainer.style.backgroundColor = 'var(--uncloseai-modal-background)';
		resultsContainer.style.borderRadius = '5px';
		resultsContainer.style.fontFamily = 'monospace';
		resultsContainer.style.fontSize = '14px';
		resultsContainer.style.whiteSpace = 'pre-wrap';
		resultsContainer.style.wordWrap = 'break-word';
		resultsContainer.style.display = 'none'; // Hidden initially

		// Insert after the pre element
		pre.parentElement.insertBefore(resultsContainer, pre.nextSibling);
	}

	runButton.onclick = async () => {
		// Show results container
		resultsContainer.style.display = 'block';
		await executeCode(code, language, resultsContainer, runButton);
	};

	// Find existing copy button container or create one
	let buttonContainer = pre.querySelector('.uncloseai-code-copy-btn')?.parentElement;
	if (!buttonContainer) {
		// Create button container if it doesn't exist
		buttonContainer = document.createElement('div');
		buttonContainer.style.position = 'absolute';
		buttonContainer.style.top = '8px';
		buttonContainer.style.right = '8px';
		buttonContainer.style.display = 'flex';
		buttonContainer.style.gap = '4px';
		pre.style.position = 'relative';
		pre.appendChild(buttonContainer);
	}

	// Add run button to container
	buttonContainer.appendChild(runButton);
}
