// 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

/**
 * un.js - Unsandbox.com Browser SDK
 * Browser-compatible version of the official SDK (lib/un.js)
 * Uses Web Crypto API for HMAC-SHA256 signing
 *
 * Update Node.js SDK: make update-unsandbox-sdk
 */

const API_BASE = "https://api.unsandbox.com";
const POLL_DELAYS_MS = [300, 450, 700, 900, 650, 1600, 2000];

/**
 * Get credentials from vault (preferred) or localStorage (fallback)
 */
export function getCredentials() {
	// Try to get from vault first
	if (typeof window !== 'undefined' && window.UncloseVault && window.UncloseVault.isUnlocked()) {
		const useUnsandbox = window.UncloseVault.get("useUnsandbox", false);
		if (!useUnsandbox) return null;

		const publicKey = window.UncloseVault.get("unsandboxPublicKey");
		const secretKey = window.UncloseVault.get("unsandboxSecretKey");

		if (publicKey && secretKey) {
			return { publicKey, secretKey };
		}
		return null;
	}

	// Fallback to localStorage (for migration or if vault not available)
	const useUnsandbox = localStorage.getItem("useUnsandbox") === "true";
	if (!useUnsandbox) return null;

	const publicKey = localStorage.getItem("unsandboxPublicKey");
	const secretKey = localStorage.getItem("unsandboxSecretKey");

	if (publicKey && secretKey) {
		return { publicKey, secretKey };
	}
	return null;
}

/**
 * Check if unsandbox.com API is configured
 */
export function isConfigured() {
	return getCredentials() !== null;
}

/**
 * Sign a request using HMAC-SHA256 (Web Crypto API)
 * Message format: "timestamp:METHOD:path:body"
 */
async function signRequest(secretKey, timestamp, method, path, body = "") {
	const message = `${timestamp}:${method}:${path}:${body}`;
	const encoder = new TextEncoder();
	const keyData = encoder.encode(secretKey);
	const messageData = encoder.encode(message);

	const cryptoKey = await crypto.subtle.importKey(
		"raw",
		keyData,
		{ name: "HMAC", hash: "SHA-256" },
		false,
		["sign"]
	);

	const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
	const hashArray = Array.from(new Uint8Array(signature));
	return hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
}

/**
 * Make an authenticated request to the unsandbox.com API
 */
async function makeRequest(method, path, publicKey, secretKey, data = null) {
	const timestamp = Math.floor(Date.now() / 1000);
	const body = data ? JSON.stringify(data) : "";
	const signature = await signRequest(secretKey, timestamp, method, path, data ? body : "");

	const headers = {
		"Authorization": `Bearer ${publicKey}`,
		"X-Timestamp": timestamp.toString(),
		"X-Signature": signature,
		"Content-Type": "application/json",
	};

	const options = {
		method,
		headers,
	};

	if (data) {
		options.body = body;
	}

	const response = await fetch(`${API_BASE}${path}`, options);

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

	return response.json();
}

/**
 * Execute code and wait for completion
 */
export async function executeCode(language, code, publicKey = null, secretKey = null) {
	// Use provided credentials or get from localStorage
	if (!publicKey || !secretKey) {
		const creds = getCredentials();
		if (!creds) {
			throw new Error("No unsandbox.com credentials configured");
		}
		publicKey = creds.publicKey;
		secretKey = creds.secretKey;
	}

	const response = await makeRequest("POST", "/execute", publicKey, secretKey, {
		language,
		code,
	});

	// Check if we got immediate result or need to poll
	const status = response.status;
	if (status === "completed" || status === "failed" || status === "timeout" || status === "cancelled") {
		return response;
	}

	// Need to poll
	const jobId = response.job_id;
	if (!jobId) {
		throw new Error("No job_id returned from execute endpoint");
	}

	return waitForJob(jobId, publicKey, secretKey);
}

/**
 * Execute code asynchronously (returns job_id immediately)
 */
export async function executeAsync(language, code, publicKey = null, secretKey = null) {
	if (!publicKey || !secretKey) {
		const creds = getCredentials();
		if (!creds) {
			throw new Error("No unsandbox.com credentials configured");
		}
		publicKey = creds.publicKey;
		secretKey = creds.secretKey;
	}

	const response = await makeRequest("POST", "/execute", publicKey, secretKey, {
		language,
		code,
	});

	return response.job_id;
}

/**
 * Get job status/result
 */
export async function getJob(jobId, publicKey = null, secretKey = null) {
	if (!publicKey || !secretKey) {
		const creds = getCredentials();
		if (!creds) {
			throw new Error("No unsandbox.com credentials configured");
		}
		publicKey = creds.publicKey;
		secretKey = creds.secretKey;
	}

	return makeRequest("GET", `/jobs/${jobId}`, publicKey, secretKey);
}

/**
 * Wait for job completion with exponential backoff polling
 */
export async function waitForJob(jobId, publicKey = null, secretKey = null, onPoll = null) {
	if (!publicKey || !secretKey) {
		const creds = getCredentials();
		if (!creds) {
			throw new Error("No unsandbox.com credentials configured");
		}
		publicKey = creds.publicKey;
		secretKey = creds.secretKey;
	}

	let pollCount = 0;

	while (true) {
		// Sleep before polling
		const delayIdx = Math.min(pollCount, POLL_DELAYS_MS.length - 1);
		await new Promise(resolve => setTimeout(resolve, POLL_DELAYS_MS[delayIdx]));
		pollCount++;

		const response = await getJob(jobId, publicKey, secretKey);

		// Callback for progress updates
		if (onPoll) {
			onPoll(pollCount, response);
		}

		const status = response.status;
		if (status === "completed" || status === "failed" || status === "timeout" || status === "cancelled") {
			return response;
		}
	}
}

/**
 * Cancel a running job
 */
export async function cancelJob(jobId, publicKey = null, secretKey = null) {
	if (!publicKey || !secretKey) {
		const creds = getCredentials();
		if (!creds) {
			throw new Error("No unsandbox.com credentials configured");
		}
		publicKey = creds.publicKey;
		secretKey = creds.secretKey;
	}

	return makeRequest("DELETE", `/jobs/${jobId}`, publicKey, secretKey);
}

/**
 * Get supported languages
 */
export async function getLanguages(publicKey = null, secretKey = null) {
	if (!publicKey || !secretKey) {
		const creds = getCredentials();
		if (!creds) {
			throw new Error("No unsandbox.com credentials configured");
		}
		publicKey = creds.publicKey;
		secretKey = creds.secretKey;
	}

	const response = await makeRequest("GET", "/languages", publicKey, secretKey);
	return response.languages || [];
}
