<?php

function template_project_root(): string {
	return realpath(__DIR__.'/../../../');
}

function template_allowed_roots(): array {
	$root = template_project_root();

	if (!$root) {
		return [];
	}

	return [
		rtrim(str_replace('\\', '/', $root.'/template'), '/').'/',
	];
}
function template_normalize_path(?string $path): string {
	$path = trim((string) $path);
	$path = str_replace('\\', '/', $path);
	$path = preg_replace('~/+~', '/', $path);

	return $path;
}

function template_resolve_path(?string $path): ?string {
	$path = template_normalize_path($path);

	if ($path === '') {
		return null;
	}

	if (str_starts_with($path, '/') || str_contains($path, '..') ||	!preg_match('~^[a-zA-Z0-9_\-/\.]+\.php$~', $path)) {
		return null;
	}

	$project_root = template_project_root();

	if (!$project_root) {
		return null;
	}

	$full_path = realpath($project_root.'/'.$path);

	if (!$full_path || !is_file($full_path)) {
		return null;
	}
	
	$full_path_normalized = str_replace('\\', '/', $full_path);
	
	foreach (template_allowed_roots() as $allowed_root) {
		if (str_starts_with($full_path_normalized, $allowed_root)) {
			return $full_path;
		}
	}

	return null;
}

function template_resolve_writable_path(?string $path): ?string {
	$path = template_normalize_path($path);

	if ($path === '') {
		return null;
	}

	if (str_starts_with($path, '/') ||	str_contains($path, '..') ||	!preg_match('~^[a-zA-Z0-9_\-/\.]+\.php$~', $path)) {
		return null;
	}

	$project_root = template_project_root();

	if (!$project_root) {
		return null;
	}

	$target_path = $project_root.'/'.$path;
	$target_path_normalized = str_replace('\\', '/', $target_path);
	$target_dir = dirname($target_path);

	foreach (template_allowed_roots() as $allowed_root) {
		$allowed_root = rtrim($allowed_root, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;

		if (is_dir($target_dir)) {
			$real_target_dir = realpath($target_dir);

			if ($real_target_dir) {
				$real_target_dir = rtrim($real_target_dir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;

				if (str_starts_with($real_target_dir, $allowed_root)) {
					return $target_path;
				}
			}
		}
		
		if (str_starts_with($target_path_normalized, $allowed_root)) {
			return $target_path;
		}
	}

	return null;
}

function template_read_source(string $path, ?string &$error = null): string {
	$full_path = template_resolve_path($path);

	if (!$full_path) {
		$error = 'Ungültiger Template-Pfad.';
		return '';
	}

	if (!is_readable($full_path)) {
		$error = 'Template-Datei ist nicht lesbar.';
		return '';
	}

	$source = file_get_contents($full_path);

	if ($source === false) {
		$error = 'Template-Datei konnte nicht gelesen werden.';
		return '';
	}

	return $source;
}

function template_find_php_cli(): ?string {
	if (!function_exists('shell_exec')) {
		return null;
	}

	$candidates = [];

	// Plesk-Versionen per Shell suchen, damit open_basedir nicht über is_file/glob meckert.
	$plesk_bins = shell_exec("ls -1 /opt/plesk/php/*/bin/php 2>/dev/null");

	if ($plesk_bins) {
		foreach (explode("\n", trim($plesk_bins)) as $bin) {
			$bin = trim($bin);

			if ($bin) {
				$candidates[] = $bin;
			}
		}
	}

	// Höchste Plesk-Version zuerst.
	usort($candidates, function($a, $b) {
		preg_match('~/opt/plesk/php/([^/]+)/bin/php$~', $a, $match_a);
		preg_match('~/opt/plesk/php/([^/]+)/bin/php$~', $b, $match_b);

		return version_compare($match_b[1] ?? '0', $match_a[1] ?? '0');
	});

	// Fallbacks ohne is_file(), weil open_basedir sonst Warnungen erzeugt.
	$candidates[] = 'php';
	$candidates[] = '/usr/bin/php';
	$candidates[] = '/usr/local/bin/php';

	foreach ($candidates as $candidate) {
		$cmd = escapeshellarg($candidate) . " -r " . escapeshellarg("echo PHP_SAPI;") . " 2>/dev/null";
		$output = trim((string) shell_exec($cmd));

		if ($output === 'cli') {
			return $candidate;
		}
	}

	return null;
}

function template_validate_php_source(string $source, ?string &$error = null): bool {
	if (!function_exists('shell_exec')) {
		return true;
	}

	$php_cli = template_find_php_cli();

	if (!$php_cli) {
		return true;
	}

	$tmp = tempnam(sys_get_temp_dir(), 'tpl_');

	if (!$tmp) {
		$error = 'Temporäre Datei konnte nicht erstellt werden.';
		return false;
	}

	file_put_contents($tmp, $source);

	$cmd = escapeshellarg($php_cli) . ' -l ' . escapeshellarg($tmp) . ' 2>&1';
	$output = shell_exec($cmd);

	@unlink($tmp);

	if ($output === null) {
		return true;
	}

	if (stripos($output, 'No syntax errors detected') !== false) {
		return true;
	}

	if (stripos($output, 'Errors parsing') !== false ||	stripos($output, 'Parse error') !== false || stripos($output, 'syntax error') !== false) {
		$error = 'PHP-Syntaxfehler: ' . trim($output);
		return false;
	}

	return true;
}


function template_save_source(string $path, string $source, ?string &$error = null): bool {
	$full_path = template_resolve_path($path);

	if (!$full_path) {
		$full_path = template_resolve_writable_path($path);
	}

	if (!$full_path) {
		$error = 'Ungültiger Template-Pfad.';
		return false;
	}

	$dir = dirname($full_path);

	if (!is_dir($dir)) {
		if (!mkdir($dir, 0755, true)) {
			$error = 'Template-Verzeichnis konnte nicht erstellt werden.';
			return false;
		}
	}

	if (file_exists($full_path) && !is_writable($full_path)) {
		$error = 'Template-Datei ist nicht beschreibbar.';
		return false;
	}

	if (!is_writable($dir)) {
		$error = 'Template-Verzeichnis ist nicht beschreibbar.';
		return false;
	}

	if (!template_validate_php_source($source, $error)) {
		return false;
	}

	if (file_exists($full_path)) {
		$backup_path = $full_path . '.bak';
		@copy($full_path, $backup_path);
	}

	$saved = file_put_contents($full_path, $source, LOCK_EX);

	if ($saved === false) {
		$error = 'Template-Datei konnte nicht gespeichert werden.';
		return false;
	}

	return true;
}