<?php

declare(strict_types=1);

/**
 * Request-to-request redirect cycle protection.
 *
 * DentalCare has several legitimate one-hop redirects (login, update gates,
 * Task View alignment, and patient portal reauthentication). This guard only
 * interrupts a repeated two-page or self-redirect cycle. Normal navigation,
 * refreshes, and a single redirect are not blocked.
 */

function dental_navigation_guard_normalize_target(string $target): string
{
    $target = trim($target);
    if ($target === '') return '';

    $parts = parse_url($target);
    if ($parts === false) return '';

    $path = (string)($parts['path'] ?? '');
    if ($path === '') $path = '/';
    $path = '/' . ltrim((string)preg_replace('#/+#', '/', $path), '/');

    $query = (string)($parts['query'] ?? '');
    if ($query !== '') {
        parse_str($query, $queryValues);
        foreach (['csrf', '_', 'navigation_guard_reset'] as $volatileKey) unset($queryValues[$volatileKey]);
        ksort($queryValues);
        $query = http_build_query($queryValues);
    }

    return $path . ($query !== '' ? '?' . $query : '');
}

function dental_navigation_guard_current_target(): string
{
    return dental_navigation_guard_normalize_target((string)($_SERVER['REQUEST_URI'] ?? '/'));
}

function dental_navigation_guard_reset(): void
{
    unset(
        $_SESSION['dental_redirect_edges'],
        $_SESSION['dental_redirect_loop_detected_at'],
        $_SESSION['dental_redirect_loop_pair']
    );
}

function dental_navigation_guard_trim_edges(array $edges, float $now): array
{
    $edges = array_values(array_filter($edges, static function ($edge) use ($now): bool {
        return is_array($edge)
            && isset($edge['from'], $edge['to'], $edge['at'])
            && ($now - (float)$edge['at']) <= 15.0;
    }));
    return array_slice($edges, -8);
}

function dental_navigation_guard_cycle(array $edges, string $current): ?array
{
    $count = count($edges);
    if ($count >= 3) {
        $last = array_slice($edges, -3);
        $selfLoop = true;
        foreach ($last as $edge) {
            if (($edge['from'] ?? '') !== $current || ($edge['to'] ?? '') !== $current) {
                $selfLoop = false;
                break;
            }
        }
        if ($selfLoop) return ['type'=>'self', 'from'=>$current, 'to'=>$current];
    }

    if ($count < 4) return null;
    $last = array_slice($edges, -4);
    [$a, $b, $c, $d] = $last;
    $alternating = ($a['from'] ?? '') !== ''
        && ($a['to'] ?? '') !== ''
        && ($a['from'] ?? '') === ($c['from'] ?? '')
        && ($a['to'] ?? '') === ($c['to'] ?? '')
        && ($b['from'] ?? '') === ($d['from'] ?? '')
        && ($b['to'] ?? '') === ($d['to'] ?? '')
        && ($a['to'] ?? '') === ($b['from'] ?? '')
        && ($b['to'] ?? '') === ($a['from'] ?? '')
        && $current === ($a['from'] ?? '');

    return $alternating
        ? ['type'=>'two_page', 'from'=>(string)$a['from'], 'to'=>(string)$a['to']]
        : null;
}

function dental_navigation_guard_clear_stale_context(): void
{
    foreach ([
        'dental_patient_context', 'active_patient_context', 'cashier_patient_context',
        'dental_active_clinical_context', 'dental_viewed_patient_id', 'dental_selected_visit_id',
        'task_dentist_id', 'dental_update_pending_cache', 'portal_redirect_target',
    ] as $key) {
        unset($_SESSION[$key]);
    }

    $actualRole = (string)($_SESSION['user']['role'] ?? '');
    if ($actualRole === 'super_admin') {
        unset($_SESSION['task_role'], $_SESSION['active_role'], $_SESSION['task_context_role']);
    }
}

function dental_navigation_guard_render_recovery(array $cycle): never
{
    dental_navigation_guard_clear_stale_context();
    dental_navigation_guard_reset();
    $_SESSION['dental_redirect_loop_detected_at'] = date(DATE_ATOM);
    $_SESSION['dental_redirect_loop_pair'] = $cycle;

    $reference = function_exists('dental_request_reference') ? dental_request_reference() : strtoupper(substr(hash('sha256', microtime(true)), 0, 12));
    error_log('DentalCare redirect cycle interrupted [' . $reference . ']: ' . json_encode($cycle, JSON_UNESCAPED_SLASHES));

    http_response_code(409);
    if (!headers_sent()) header('Content-Type: text/html; charset=utf-8');

    $isPatientRoute = str_contains((string)($cycle['from'] ?? ''), 'patient-')
        || str_contains((string)($cycle['to'] ?? ''), 'patient-');
    $signedIn = !empty($_SESSION['user']['id']);
    $primaryAction = $isPatientRoute
        ? '<a class="primary" href="patient-login?portal_reauth=1&navigation_guard_reset=1">Return to patient sign in</a>'
        : ($signedIn
            ? '<a class="primary" href="navigation-recovery?navigation_guard_reset=1">Open workspace recovery</a>'
            : '<a class="primary" href="login.php?navigation_guard_reset=1">Return to staff sign in</a>');

    $safeReference = htmlspecialchars($reference, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    echo '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Navigation loop stopped</title><style>body{font-family:system-ui,-apple-system,sans-serif;background:#f4f7f8;color:#17333a;margin:0}.wrap{max-width:680px;margin:8vh auto;padding:24px}.card{background:#fff;border:1px solid #d8e2e5;border-radius:18px;padding:28px;box-shadow:0 12px 35px rgba(16,52,60,.08)}h1{margin-top:0}.notice{background:#fff7dd;border:1px solid #efd889;border-radius:12px;padding:14px}.actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:22px}a{display:inline-block;padding:11px 16px;border-radius:10px;text-decoration:none;border:1px solid #97abb0;color:#17333a}.primary{background:#0b6f73;color:#fff;border-color:#0b6f73}small{color:#60777d}</style></head><body><main class="wrap"><section class="card"><h1>Navigation loop stopped</h1><div class="notice">DentalCare stopped a repeated redirect before it could trap this account again.</div><p>Temporary patient, Task View, and update-navigation context was cleared. No patient, appointment, treatment, billing, or clinical record was changed.</p><div class="actions">' . $primaryAction . '<a href="index.php?navigation_guard_reset=1">Public home</a></div><p><small>Reference: ' . $safeReference . '</small></p></section></main></body></html>';
    exit;
}

function dental_navigation_guard_boot(): void
{
    if (PHP_SAPI === 'cli' || session_status() !== PHP_SESSION_ACTIVE) return;

    if (isset($_GET['navigation_guard_reset'])) {
        dental_navigation_guard_reset();
        return;
    }

    $current = dental_navigation_guard_current_target();
    if ($current === '' || str_contains($current, 'navigation-recovery')) return;

    $now = microtime(true);
    $edges = dental_navigation_guard_trim_edges((array)($_SESSION['dental_redirect_edges'] ?? []), $now);
    $_SESSION['dental_redirect_edges'] = $edges;
    $cycle = dental_navigation_guard_cycle($edges, $current);
    if ($cycle !== null) dental_navigation_guard_render_recovery($cycle);

    register_shutdown_function(static function () use ($current): void {
        if (session_status() !== PHP_SESSION_ACTIVE) return;
        $status = (int)http_response_code();
        $location = '';
        foreach (headers_list() as $headerLine) {
            if (stripos($headerLine, 'Location:') === 0) {
                $location = trim(substr($headerLine, strlen('Location:')));
            }
        }

        if ($location !== '' && $status >= 300 && $status < 400) {
            $to = dental_navigation_guard_normalize_target($location);
            if ($to !== '') {
                $edges = dental_navigation_guard_trim_edges((array)($_SESSION['dental_redirect_edges'] ?? []), microtime(true));
                $edges[] = ['from'=>$current, 'to'=>$to, 'at'=>microtime(true), 'status'=>$status];
                $_SESSION['dental_redirect_edges'] = array_slice($edges, -8);
            }
            return;
        }

        if ($status >= 200 && $status < 300) {
            unset($_SESSION['dental_redirect_edges']);
        }
    });
}
