<?php
declare(strict_types=1);

require_once __DIR__ . '/lib/db.php';
require_once __DIR__ . '/lib/slug.php';
require_once __DIR__ . '/lib/auth.php';
require_once __DIR__ . '/lib/notebook.php';

header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: same-origin');
header('X-Frame-Options: SAMEORIGIN');

$route = route_target();
$slug = $route['slug'];
$routeTabId = $route['tab_id'];

if ($slug === null) {
    if (!empty($route['not_found'])) {
        $missingPath = isset($route['missing_path']) && is_string($route['missing_path'])
            ? $route['missing_path']
            : null;
        if ($missingPath !== null && is_create_missing_request()) {
            try {
                Notebook::create($missingPath);
                redirect_to_current_path();
            } catch (InvalidArgumentException $e) {
                http_response_code(400);
                render_404($missingPath, path_create_error($e->getMessage()));
                exit;
            }
        }
        http_response_code(404);
        render_404($missingPath);
        exit;
    }
    if (empty($_GET['new'])) {
        render_history_redirect();
        exit;
    }
    $newSlug = Notebook::create();
    header('Location: ' . base_path() . '/index.php/' . rawurlencode($newSlug), true, 302);
    exit;
}

if (!Slug::isValid($slug)) {
    http_response_code(404);
    render_404();
    exit;
}

$nb = Notebook::get($slug);
if ($nb === null) {
    if (is_create_missing_request()) {
        try {
            Notebook::createWithSlug($slug);
            redirect_to_current_path();
        } catch (InvalidArgumentException $e) {
            http_response_code(400);
            render_404($slug, path_create_error($e->getMessage()));
            exit;
        }
    }
    http_response_code(404);
    render_404($slug);
    exit;
}

Auth::startSession();
$locked = $nb['has_password'] && empty($_SESSION['unlocked'][$slug]);

$bootstrap = [
    'slug'        => $slug,
    'hasPassword' => (bool) $nb['has_password'],
    'locked'      => $locked,
    'csrf'        => Auth::csrfToken(),
    'tabs'        => [],
    'activeTab'   => null,
];

if (!$locked) {
    $tabs = Tab::listMeta($slug);
    $bootstrap['tabs'] = $tabs;
    if (!empty($tabs)) {
        $first = $tabs[0];
        $targetTabId = (int) ($_GET['tab'] ?? 0);
        if ($targetTabId <= 0 && $routeTabId !== null) {
            $targetTabId = $routeTabId;
        }
        $full = $targetTabId > 0 ? Tab::get($slug, $targetTabId) : null;
        if ($full === null) {
            $full  = Tab::get($slug, (int) $first['id']);
        }
        if ($full !== null) {
            $bootstrap['activeTab'] = $full;
        }
    }
}

render_shell($slug, $locked, $bootstrap);


// ---------- helpers ----------

/** @return array{slug: ?string, tab_id: ?int, not_found?: bool, missing_path?: string} */
function route_target(): array
{
    $pathInfo = $_SERVER['PATH_INFO'] ?? '';
    if ($pathInfo !== '') {
        $candidate = ltrim($pathInfo, '/');
        $candidate = explode('/', $candidate)[0];
        return route_candidate($candidate);
    }
    if (!empty($_GET['n']) && is_string($_GET['n'])) {
        return ['slug' => $_GET['n'], 'tab_id' => null];
    }
    $uri = $_SERVER['REQUEST_URI'] ?? '/';
    $path = parse_url($uri, PHP_URL_PATH) ?? '/';
    $path = preg_replace('#^' . preg_quote(base_path(), '#') . '#', '', $path) ?? $path;
    $path = ltrim($path, '/');
    if ($path === '') {
        return ['slug' => null, 'tab_id' => null];
    }
    if ($path === 'index.php') {
        return ['slug' => null, 'tab_id' => null];
    }
    if (strncmp($path, 'index.php/', 10) === 0) {
        $indexPath = substr($path, 10);
        if ($indexPath !== '') {
            return route_candidate(explode('/', $indexPath)[0]);
        }
        return ['slug' => null, 'tab_id' => null];
    }
    return route_candidate(explode('/', $path)[0]);
}

/** @return array{slug: ?string, tab_id: ?int, not_found?: bool, missing_path?: string} */
function route_candidate(string $candidate): array
{
    $candidate = rawurldecode($candidate);
    if ($candidate === '') {
        return ['slug' => null, 'tab_id' => null];
    }
    if (Slug::isValid($candidate)) {
        return ['slug' => $candidate, 'tab_id' => null];
    }

    $tab = Tab::findByPathSlug($candidate);
    if ($tab !== null) {
        return [
            'slug' => (string) $tab['notebook_slug'],
            'tab_id' => (int) $tab['id'],
        ];
    }
    return ['slug' => null, 'tab_id' => null, 'not_found' => true, 'missing_path' => $candidate];
}

function base_path(): string
{
    $script = $_SERVER['SCRIPT_NAME'] ?? '/index.php';
    $base   = rtrim(str_replace('\\', '/', dirname($script)), '/');
    return $base;
}

function asset_url(string $rel): string
{
    $path = __DIR__ . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $rel);
    $v    = is_file($path) ? filemtime($path) : time();
    return base_path() . '/' . ltrim($rel, '/') . '?v=' . $v;
}

function h(string $s): string
{
    return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function is_create_missing_request(): bool
{
    return ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST'
        && isset($_POST['create_missing'])
        && hash_equals('1', (string) $_POST['create_missing']);
}

function redirect_to_current_path(): void
{
    $uri = $_SERVER['REQUEST_URI'] ?? (base_path() . '/index.php');
    $path = parse_url($uri, PHP_URL_PATH);
    header('Location: ' . ($path ?: base_path() . '/index.php'), true, 303);
    exit;
}

function path_create_error(string $code): string
{
    return [
        'bad_path'      => '路径只能使用 2-64 位字母、数字、下划线或短横线。',
        'reserved_path' => '这个路径与系统入口、随机地址或现有文件冲突。',
        'path_taken'    => '这个路径已经被其他 Tab 使用。',
        'bad_slug'      => '这个地址格式无效。',
        'slug_taken'    => '这个地址已经被创建。',
    ][$code] ?? '创建失败，请换一个路径再试。';
}

function render_404(?string $slug = null, string $error = ''): void
{
    header('Content-Type: text/html; charset=utf-8');
    $slugDisplay = $slug !== null ? h($slug) : '';
    $canCreate = $slug !== null
        && (Slug::isValid($slug) || Tab::validatePathSlug($slug) === null);
    $createForm = $canCreate ? <<<HTML
  <form method="post">
    <input type="hidden" name="create_missing" value="1">
    <button type="submit">用此路径创建笔记本</button>
  </form>
HTML : '';
    $errorHtml = $error !== '' ? '<p class="err">' . h($error) . '</p>' : '';
    echo <<<HTML
<!doctype html>
<html lang="zh-CN"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>未找到 · PHPNotePad</title>
<style>
  body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
       display:flex;align-items:center;justify-content:center;height:100vh;margin:0;color:#1d2129;background:#f7f8fa}
  .box{text-align:center;padding:32px;border-radius:8px;background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.06);max-width:420px}
  h1{margin:0 0 8px;font-size:24px}
  a{color:#165dff;text-decoration:none}
  code{background:#f2f3f5;padding:2px 6px;border-radius:4px}
  form{margin:18px 0 10px}
  button{border:0;border-radius:4px;background:#165dff;color:#fff;padding:8px 16px;font:inherit;cursor:pointer}
  button:hover{background:#0e42d2}
  .err{color:#f53f3f}
  .muted{color:#86909c;font-size:13px}
</style></head><body>
<div class="box">
  <h1>笔记本不存在</h1>
  <p>没有找到 <code>{$slugDisplay}</code>。</p>
{$errorHtml}
{$createForm}
  <p class="muted"><a href="./">创建随机地址的新笔记本</a></p>
</div></body></html>
HTML;
}

function render_history_redirect(): void
{
    header('Content-Type: text/html; charset=utf-8');
    $base = h(base_path());
    $newUrl = h(base_path() . '/index.php?new=1');
    echo <<<HTML
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>PHPNotePad</title>
<style>
  body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;color:#1d2129;background:#f7f8fa}
  .box{text-align:center;padding:32px;border-radius:8px;background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.06)}
  h1{margin:0 0 8px;font-size:22px}
  a{color:#165dff;text-decoration:none}
</style>
</head>
<body>
<div class="box">
  <h1>正在打开最近的笔记本...</h1>
  <p><a href="{$newUrl}">创建新笔记本</a></p>
</div>
<script>
(function(){
  var basePath = "{$base}";
  var history = [];
  try { history = JSON.parse(localStorage.getItem('phpnp.history') || '[]'); } catch (_) {}
  if (Array.isArray(history) && history.length && history[0] && history[0].slug) {
    location.replace(basePath + '/index.php/' + encodeURIComponent(history[0].slug));
    return;
  }
  location.replace("{$newUrl}");
})();
</script>
</body>
</html>
HTML;
}

function render_shell(string $slug, bool $locked, array $bootstrap): void
{
    header('Content-Type: text/html; charset=utf-8');
    $cdn = 'https://cdn.jsdelivr.net';
    // Cloudflare Web Analytics beacon (auto-injected when site is behind CF with RUM on)
    $cf  = 'https://static.cloudflareinsights.com';
    $cfApi = 'https://cloudflareinsights.com';
    $csp = "default-src 'self'; "
         . "script-src 'self' {$cdn} {$cf} 'unsafe-eval' 'unsafe-inline'; "
         . "style-src 'self' {$cdn} 'unsafe-inline'; "
         . "font-src {$cdn} data:; "
         . "img-src 'self' data: blob:; "
         . "connect-src 'self' {$cdn} {$cfApi}; "
         . "worker-src blob:;";
    header('Content-Security-Policy: ' . $csp);

    $appJs    = asset_url('public/app.js');
    $appCss   = asset_url('public/app.css');
    $baseHref = base_path() . '/';
    $bootstrap['basePath'] = base_path();
    $bootJson = json_encode($bootstrap, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP);
    $csrfMeta = h($bootstrap['csrf']);
    $baseAttr = h($baseHref);
    $title    = $locked ? '解锁笔记本' : 'PHPNotePad · ' . h($slug);

    echo <<<HTML
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<base href="{$baseAttr}">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="csrf-token" content="{$csrfMeta}">
<meta name="color-scheme" content="light dark">
<title>{$title}</title>
<link rel="preconnect" href="{$cdn}" crossorigin>
<link rel="dns-prefetch" href="{$cdn}">
<style>
  *,*::before,*::after{box-sizing:border-box}
  html,body{margin:0;height:100%;background:#f7f8fa;color:#1d2129;
    font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif}
  #app{height:100%;display:flex;flex-direction:column}
  .skeleton{display:flex;flex-direction:column;height:100%}
  .skeleton-bar{height:48px;background:#fff;border-bottom:1px solid #e5e6eb;
    display:flex;align-items:center;padding:0 16px;gap:12px}
  .skeleton-tabs{height:36px;background:#fff;border-bottom:1px solid #e5e6eb;
    display:flex;align-items:center;padding:0 8px;gap:8px}
  .skeleton-pulse{background:linear-gradient(90deg,#eef0f3 0%,#f7f8fa 50%,#eef0f3 100%);
    background-size:200% 100%;animation:pulse 1.4s ease-in-out infinite;border-radius:4px}
  .skeleton-pulse.title{width:120px;height:20px}
  .skeleton-pulse.tab{width:80px;height:24px}
  .skeleton-pulse.body{flex:1;margin:16px}
  @keyframes pulse{0%{background-position:200% 0}100%{background-position:-200% 0}}
  .lock-screen{max-width:360px;margin:120px auto 0;background:#fff;padding:32px;
    border-radius:8px;box-shadow:0 2px 8px rgba(0,0,0,.06)}
  .lock-screen h1{margin:0 0 16px;font-size:20px}
  .lock-screen input{width:100%;padding:8px 12px;border:1px solid #c9cdd4;border-radius:4px;
    font-size:14px;outline:none;margin-bottom:12px}
  .lock-screen input:focus{border-color:#165dff}
  .lock-screen button{width:100%;padding:8px 12px;background:#165dff;color:#fff;border:0;
    border-radius:4px;font-size:14px;cursor:pointer}
  .lock-screen button:hover{background:#0e42d2}
  .lock-screen .err{color:#f53f3f;margin:0 0 12px;min-height:20px;font-size:13px}
  @media (prefers-color-scheme: dark){
    html,body{background:#17171a;color:#f7f8fa}
    .skeleton-bar,.skeleton-tabs,.lock-screen{background:#232324;border-color:#2e2e30}
    .skeleton-pulse{background:linear-gradient(90deg,#2a2a2c 0%,#3a3a3d 50%,#2a2a2c 100%)}
    .lock-screen input{background:#17171a;color:#f7f8fa;border-color:#3a3a3d}
  }
</style>
<link rel="stylesheet" href="{$cdn}/npm/@arco-design/web-vue@2/dist/arco.min.css">
<link rel="stylesheet" href="{$appCss}">
</head>
<body>
HTML;

    if ($locked) {
        echo <<<HTML
<div id="app">
  <div class="lock-screen">
    <h1>🔒 笔记本已加密</h1>
    <p style="color:#86909c;font-size:13px;margin:0 0 16px">输入密码解锁此笔记本</p>
    <p class="err" id="lockErr"></p>
    <form id="lockForm">
      <input id="lockPwd" type="password" placeholder="密码" autocomplete="current-password" autofocus required>
      <button type="submit">解锁</button>
    </form>
  </div>
</div>
<script>
(function(){
  var BOOT = {$bootJson};
  var slug = BOOT.slug;
  var csrf = BOOT.csrf;
  var form = document.getElementById('lockForm');
  var err  = document.getElementById('lockErr');
  var pwd  = document.getElementById('lockPwd');
  var btn  = form.querySelector('button');
  form.addEventListener('submit', async function(e){
    e.preventDefault();
    err.textContent = '';
    btn.disabled = true; btn.textContent = '验证中…';
    try {
      var r = await fetch('api.php?a=unlock&slug=' + encodeURIComponent(slug), {
        method: 'POST',
        headers: {'Content-Type':'application/json','X-CSRF-Token':csrf},
        body: JSON.stringify({password: pwd.value})
      });
      var data = await r.json();
      if (r.ok && data.ok) { location.reload(); return; }
      if (data.error === 'rate_limited') err.textContent = '尝试次数过多，请 1 分钟后再试。';
      else if (data.error === 'bad_password') err.textContent = '密码错误。';
      else err.textContent = '解锁失败。';
    } catch(_) { err.textContent = '网络错误。'; }
    btn.disabled = false; btn.textContent = '解锁';
    pwd.select();
  });
})();
</script>
</body></html>
HTML;
        return;
    }

    echo <<<HTML
<div id="app">
  <div class="skeleton">
    <div class="skeleton-bar">
      <div class="skeleton-pulse title"></div>
      <div style="flex:1"></div>
      <div class="skeleton-pulse" style="width:80px;height:24px"></div>
    </div>
    <div class="skeleton-tabs">
      <div class="skeleton-pulse tab"></div>
      <div class="skeleton-pulse tab"></div>
    </div>
    <div class="skeleton-pulse body"></div>
  </div>
</div>
<script id="phpnp-bootstrap" type="application/json">{$bootJson}</script>
<script src="{$cdn}/npm/vue@3/dist/vue.global.prod.js"></script>
<script src="{$cdn}/npm/@arco-design/web-vue@2/dist/arco-vue.min.js"></script>
<script src="{$appJs}" defer></script>
</body></html>
HTML;
}
