<?php
declare(strict_types=1);

header('Content-Type: application/json; charset=UTF-8');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');

$storageFile = __DIR__ . DIRECTORY_SEPARATOR . 'konstellationen.json';
$requestMethod = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
$allowedLayoutPresets = ['focus', 'poster', 'compact'];
$allowedViewPresets = ['default', 'digital-deadline', 'countdown', 'clock-duo', 'digital-only', 'analog-only', 'digital-comment'];
$allowedAnalogDesigns = ['classic', 'readable', 'transparent', 'chalk', 'minimal'];
$allowedAnalogDialStyles = ['none', 'balanced', 'school', 'ring', 'quarters'];
$allowedTimeTextEffects = ['plain', 'glow', 'outline', 'neon', 'shadow'];
$allowedStageThemes = ['lagoon', 'daylight', 'chalkboard', 'sunset', 'transparent', 'forest', 'berry', 'mono', 'ocean', 'candy', 'glacier', 'citrus', 'ember', 'midnight', 'terracotta', 'obsidian', 'aurora', 'plum', 'forge'];
$allowedWindowIds = ['deadline', 'time', 'analog', 'hourglass', 'message', 'logo'];
$allowedTextScaleWindowIds = ['deadline', 'time', 'hourglass', 'message'];
$maxPayloadBytes = 128000;

if ($requestMethod !== 'POST') {
    http_response_code(405);
    echo json_encode([
        'error' => 'method not allowed'
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

if (!isAllowedOriginRequest()) {
    http_response_code(403);
    echo json_encode([
        'error' => 'origin not allowed'
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

$contentType = strtolower(trim((string) ($_SERVER['CONTENT_TYPE'] ?? '')));
if ($contentType !== '' && !str_starts_with($contentType, 'application/json')) {
    http_response_code(415);
    echo json_encode([
        'error' => 'content type must be application/json'
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

$rawBody = file_get_contents('php://input');
if ($rawBody === false || strlen($rawBody) > $maxPayloadBytes) {
    http_response_code(413);
    echo json_encode([
        'error' => 'payload too large'
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

try {
    $decoded = json_decode((string) $rawBody, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
    http_response_code(400);
    echo json_encode([
        'error' => 'invalid json payload'
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

if (!is_array($decoded) || !isset($decoded['konstellation']) || !is_array($decoded['konstellation'])) {
    http_response_code(400);
    echo json_encode([
        'error' => 'missing konstellation payload'
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

$entry = normalizeEntry(
    $decoded['konstellation'],
    $allowedWindowIds,
    $allowedTextScaleWindowIds,
    $allowedLayoutPresets,
    $allowedViewPresets,
    $allowedAnalogDesigns,
    $allowedAnalogDialStyles,
    $allowedTimeTextEffects,
    $allowedStageThemes
);

if ($entry === null) {
    http_response_code(400);
    echo json_encode([
        'error' => 'invalid konstellation payload'
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

$payload = readPayload($storageFile);
$entries = $payload['konstellationen'];
$updated = false;

foreach ($entries as $index => $existingEntry) {
    if (!is_array($existingEntry)) {
        continue;
    }

    $existingId = sanitizeId($existingEntry['id'] ?? $existingEntry['name'] ?? '');
    if ($existingId !== $entry['id']) {
        continue;
    }

    $entries[$index] = $entry;
    $updated = true;
    break;
}

if (!$updated) {
    http_response_code(404);
    echo json_encode([
        'error' => 'base konstellation not found'
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

$json = json_encode([
    'konstellationen' => array_values($entries)
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

if ($json === false || file_put_contents($storageFile, $json . PHP_EOL, LOCK_EX) === false) {
    http_response_code(500);
    echo json_encode([
        'error' => 'storage file could not be written'
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

echo json_encode([
    'ok' => true,
    'id' => $entry['id'],
    'name' => $entry['name']
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

function readPayload(string $storageFile): array
{
    if (!is_file($storageFile)) {
        return ['konstellationen' => []];
    }

    $raw = file_get_contents($storageFile);
    if ($raw === false || trim($raw) === '') {
        return ['konstellationen' => []];
    }

    try {
        $decoded = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
    } catch (JsonException $exception) {
        return ['konstellationen' => []];
    }

    $entries = [];
    if (is_array($decoded) && isset($decoded['konstellationen']) && is_array($decoded['konstellationen'])) {
        $entries = $decoded['konstellationen'];
    }

    return ['konstellationen' => $entries];
}

function normalizeEntry(
    array $entry,
    array $allowedWindowIds,
    array $allowedTextScaleWindowIds,
    array $allowedLayoutPresets,
    array $allowedViewPresets,
    array $allowedAnalogDesigns,
    array $allowedAnalogDialStyles,
    array $allowedTimeTextEffects,
    array $allowedStageThemes
): ?array {
    $id = sanitizeId($entry['id'] ?? $entry['name'] ?? '');
    if ($id === '') {
        return null;
    }

    $name = sanitizeShortText($entry['name'] ?? $id, 120);
    $normalizedEntry = [
        'id' => $id,
        'name' => $name !== '' ? $name : $id
    ];

    copyAllowedEnum($entry, $normalizedEntry, 'layoutPreset', $allowedLayoutPresets);
    copyAllowedEnum($entry, $normalizedEntry, 'viewPreset', $allowedViewPresets);
    copyAllowedBool($entry, $normalizedEntry, 'showLogo');
    copyAllowedBool($entry, $normalizedEntry, 'showWindowTitles');
    copyAllowedColor($entry, $normalizedEntry, 'textColor');
    copyAllowedEnum($entry, $normalizedEntry, 'analogDesign', $allowedAnalogDesigns);
    copyAllowedEnum($entry, $normalizedEntry, 'analogDialStyle', $allowedAnalogDialStyles);
    copyAllowedEnum($entry, $normalizedEntry, 'timeTextEffect', $allowedTimeTextEffects);
    copyAllowedEnum($entry, $normalizedEntry, 'messageTextEffect', $allowedTimeTextEffects);
    copyAllowedBool($entry, $normalizedEntry, 'showSeconds');
    copyAllowedEnum($entry, $normalizedEntry, 'stageTheme', $allowedStageThemes);
    copyAllowedString($entry, $normalizedEntry, 'backgroundImage', 260);
    copyAllowedString($entry, $normalizedEntry, 'backgroundMode', 40);
    copyAllowedString($entry, $normalizedEntry, 'backgroundSlideshowGroup', 80);
    copyAllowedString($entry, $normalizedEntry, 'logoImage', 260);
    copyAllowedNumber($entry, $normalizedEntry, 'backgroundScale', 100, 170);
    copyAllowedNumber($entry, $normalizedEntry, 'backgroundPositionX', 0, 100);
    copyAllowedNumber($entry, $normalizedEntry, 'backgroundPositionY', 0, 100);

    $visible = normalizeBooleanMap($entry['visible'] ?? null, $allowedWindowIds);
    if ($visible !== []) {
        $normalizedEntry['visible'] = $visible;
    }

    $transparent = normalizeBooleanMap($entry['transparentWindows'] ?? null, $allowedWindowIds);
    if ($transparent !== []) {
        $normalizedEntry['transparentWindows'] = $transparent;
    }

    $blurred = normalizeBooleanMap($entry['blurredTransparentWindows'] ?? null, $allowedWindowIds);
    if ($blurred !== []) {
        $normalizedEntry['blurredTransparentWindows'] = $blurred;
    }

    $fontScale = normalizeNumberMap($entry['windowFontScale'] ?? null, $allowedTextScaleWindowIds, 0.7, 4);
    if ($fontScale !== []) {
        $normalizedEntry['windowFontScale'] = $fontScale;
    }

    $positions = normalizePositions($entry['positions'] ?? null, $allowedWindowIds);
    if ($positions !== []) {
        $normalizedEntry['positions'] = $positions;
    }

    return $normalizedEntry;
}

function sanitizeId($value): string
{
    $value = strtolower(trim((string) $value));
    $value = preg_replace('/[^a-z0-9]+/', '-', $value);
    return trim((string) $value, '-');
}

function sanitizeShortText($value, int $maxLength): string
{
    $value = trim(preg_replace('/\s+/', ' ', (string) $value) ?? '');
    if ($value === '') {
        return '';
    }

    return mb_substr($value, 0, $maxLength);
}

function copyAllowedEnum(array $source, array &$target, string $key, array $allowedValues): void
{
    if (!array_key_exists($key, $source) || !is_string($source[$key])) {
        return;
    }

    if (!in_array($source[$key], $allowedValues, true)) {
        return;
    }

    $target[$key] = $source[$key];
}

function copyAllowedBool(array $source, array &$target, string $key): void
{
    if (array_key_exists($key, $source) && is_bool($source[$key])) {
        $target[$key] = $source[$key];
    }
}

function copyAllowedString(array $source, array &$target, string $key, int $maxLength): void
{
    if (!array_key_exists($key, $source) || !is_string($source[$key])) {
        return;
    }

    $target[$key] = mb_substr(trim($source[$key]), 0, $maxLength);
}

function copyAllowedColor(array $source, array &$target, string $key): void
{
    if (!array_key_exists($key, $source) || !is_string($source[$key])) {
        return;
    }

    $value = trim($source[$key]);
    if (!preg_match('/^#[0-9a-fA-F]{6}$/', $value)) {
        return;
    }

    $target[$key] = strtoupper($value);
}

function copyAllowedNumber(array $source, array &$target, string $key, float $min, float $max): void
{
    if (!array_key_exists($key, $source) || !is_numeric($source[$key])) {
        return;
    }

    $value = (float) $source[$key];
    $target[$key] = max($min, min($max, $value));
}

function normalizeBooleanMap($value, array $allowedKeys): array
{
    if (!is_array($value)) {
        return [];
    }

    $normalized = [];
    foreach ($allowedKeys as $key) {
        if (array_key_exists($key, $value) && is_bool($value[$key])) {
            $normalized[$key] = $value[$key];
        }
    }

    return $normalized;
}

function normalizeNumberMap($value, array $allowedKeys, float $min, float $max): array
{
    if (!is_array($value)) {
        return [];
    }

    $normalized = [];
    foreach ($allowedKeys as $key) {
        if (array_key_exists($key, $value) && is_numeric($value[$key])) {
            $normalized[$key] = max($min, min($max, (float) $value[$key]));
        }
    }

    return $normalized;
}

function normalizePositions($value, array $allowedKeys): array
{
    if (!is_array($value)) {
        return [];
    }

    $normalized = [];
    foreach ($allowedKeys as $key) {
        if (!array_key_exists($key, $value) || !is_array($value[$key])) {
            continue;
        }

        $position = $value[$key];
        if (!isset($position['x'], $position['y'], $position['w']) || !is_numeric($position['x']) || !is_numeric($position['y']) || !is_numeric($position['w'])) {
            continue;
        }

        $normalizedPosition = [
            'x' => max(0, min(0.96, (float) $position['x'])),
            'y' => max(0, min(0.98, (float) $position['y'])),
            'w' => max(0.14, min(0.95, (float) $position['w']))
        ];

        if (array_key_exists('h', $position) && is_numeric($position['h'])) {
            $normalizedPosition['h'] = max(0.08, min(0.95, (float) $position['h']));
        }

        $normalized[$key] = $normalizedPosition;
    }

    return $normalized;
}

function isAllowedOriginRequest(): bool
{
    $secFetchSite = strtolower((string) ($_SERVER['HTTP_SEC_FETCH_SITE'] ?? ''));
    if ($secFetchSite !== '' && !in_array($secFetchSite, ['same-origin', 'same-site', 'none'], true)) {
        return false;
    }

    $host = strtolower((string) ($_SERVER['HTTP_HOST'] ?? ''));
    if ($host === '') {
        return true;
    }

    foreach (['HTTP_ORIGIN', 'HTTP_REFERER'] as $header) {
        $value = (string) ($_SERVER[$header] ?? '');
        if ($value === '') {
            continue;
        }

        $parts = parse_url($value);
        $headerHost = strtolower((string) ($parts['host'] ?? ''));
        if ($headerHost !== '' && $headerHost !== strtolower(preg_replace('/:\d+$/', '', $host) ?? $host)) {
            return false;
        }
    }

    return true;
}
