<?php
/**
 * Public Link Submission / Search
 * PHP 7.x compatible / MySQL (mysqli)
 *
 * - Lets anonymous, unregistered visitors submit a single valid URL or
 *   magnet link at a time.
 * - Submitted links are stored in `links_sent` (same column structure as
 *   the existing `links` table), never in `links` itself.
 * - Search results are a UNION of `links` and `links_sent`, merged and
 *   sorted together with no indication of which table a row came from.
 * - Anti-abuse: one accepted submission per IP every 10 minutes. The IP
 *   is never stored in the clear -- only sha256(ip . salt) is kept, in a
 *   `submission_records` table that is continuously pruned so that no
 *   row is ever older than 1 hour.
 * - No "Run Import Now" button/feature here on purpose -- this page only
 *   handles user-submitted single links plus searching.
 * - Visitors can also "like" any entry (from either table). Results are
 *   ordered by like count (highest first). Likes are stored in a new
 *   `likes` table keyed by the entry's link_hash (not by which physical
 *   table it lives in, so liking never reveals the entry's origin table
 *   either). One like per IP every 60 seconds; the whole `likes` table
 *   is continuously pruned so no row survives past 1 hour -- exactly
 *   like the submission rate-limit table.
 */

// ---------------------------------------------------------------------
// CONFIG / DB CONNECTION / SCHEMA / SESSION+AUTH HELPERS
// (shared with login.php and transactions.php)
// ---------------------------------------------------------------------
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/auth.php';

// ---------------------------------------------------------------------
// HELPERS (shared with the importer's logic)
// ---------------------------------------------------------------------

function get_domain_id(mysqli $mysqli, $domain)
{
    if ($domain === '' || $domain === null) {
        return null;
    }
    $domain = strtolower($domain);

    $stmt = $mysqli->prepare("SELECT id FROM domains WHERE domain = ? LIMIT 1");
    $stmt->bind_param('s', $domain);
    $stmt->execute();
    $stmt->bind_result($id);
    if ($stmt->fetch()) {
        $stmt->close();
        return $id;
    }
    $stmt->close();

    $stmt = $mysqli->prepare("INSERT IGNORE INTO domains (domain) VALUES (?)");
    $stmt->bind_param('s', $domain);
    $stmt->execute();
    $stmt->close();

    $stmt = $mysqli->prepare("SELECT id FROM domains WHERE domain = ? LIMIT 1");
    $stmt->bind_param('s', $domain);
    $stmt->execute();
    $stmt->bind_result($id);
    $stmt->fetch();
    $stmt->close();

    return $id;
}

function get_tracker_id(mysqli $mysqli, $tracker)
{
    if ($tracker === '' || $tracker === null) {
        return null;
    }

    $stmt = $mysqli->prepare("SELECT id FROM trackers WHERE tracker = ? LIMIT 1");
    $stmt->bind_param('s', $tracker);
    $stmt->execute();
    $stmt->bind_result($id);
    if ($stmt->fetch()) {
        $stmt->close();
        return $id;
    }
    $stmt->close();

    $stmt = $mysqli->prepare("INSERT IGNORE INTO trackers (tracker) VALUES (?)");
    $stmt->bind_param('s', $tracker);
    $stmt->execute();
    $stmt->close();

    $stmt = $mysqli->prepare("SELECT id FROM trackers WHERE tracker = ? LIMIT 1");
    $stmt->bind_param('s', $tracker);
    $stmt->execute();
    $stmt->bind_result($id);
    $stmt->fetch();
    $stmt->close();

    return $id;
}

function parse_magnet($line)
{
    $filename = '';
    $trackers = array();

    $query = '';
    if (strpos($line, '?') !== false) {
        $query = substr($line, strpos($line, '?') + 1);
    }

    $pairs = explode('&', $query);
    foreach ($pairs as $pair) {
        if ($pair === '') {
            continue;
        }
        $parts = explode('=', $pair, 2);
        $key = $parts[0];
        $value = isset($parts[1]) ? $parts[1] : '';

        if ($key === 'dn') {
            $filename = urldecode($value);
        } elseif ($key === 'tr') {
            $trackerUrl = urldecode($value);
            if ($trackerUrl !== '') {
                $trackers[] = $trackerUrl;
            }
        }
    }

    return array(
        'filename' => $filename,
        'domain'   => null,
        'trackers' => $trackers,
    );
}

function parse_plain_url($line)
{
    $filename = '';
    $domain = '';

    $parts = parse_url($line);
    if ($parts !== false) {
        if (!empty($parts['host'])) {
            $domain = $parts['host'];
        }
        if (!empty($parts['path'])) {
            $base = basename($parts['path']);
            if ($base !== '' && $base !== '/') {
                $filename = urldecode($base);
            }
        }
    }

    return array(
        'filename' => $filename,
        'domain'   => $domain,
        'trackers' => array(),
    );
}

/**
 * Checks whether a link_hash already exists in either `links` or
 * `links_sent`, so we never store the same link twice system-wide.
 */
function link_hash_exists(mysqli $mysqli, $linkHash)
{
    $stmt = $mysqli->prepare("SELECT id FROM links WHERE link_hash = ? LIMIT 1");
    $stmt->bind_param('s', $linkHash);
    $stmt->execute();
    $stmt->store_result();
    $exists = $stmt->num_rows > 0;
    $stmt->close();
    if ($exists) {
        return true;
    }

    $stmt = $mysqli->prepare("SELECT id FROM links_sent WHERE link_hash = ? LIMIT 1");
    $stmt->bind_param('s', $linkHash);
    $stmt->execute();
    $stmt->store_result();
    $exists = $stmt->num_rows > 0;
    $stmt->close();

    return $exists;
}

/**
 * Validate + parse a single submitted line without touching the DB.
 * Returns array('type' => ..., 'parsed' => ...) or false if invalid.
 */
function validate_submission($line)
{
    $line = trim($line);
    if ($line === '') {
        return false;
    }

    $isMagnet = (stripos($line, 'magnet:?') === 0);

    if ($isMagnet) {
        return array(
            'type'   => 'magnet',
            'parsed' => parse_magnet($line),
            'line'   => $line,
        );
    }

    if (!filter_var($line, FILTER_VALIDATE_URL)) {
        return false;
    }

    return array(
        'type'   => 'url',
        'parsed' => parse_plain_url($line),
        'line'   => $line,
    );
}

/**
 * Insert a validated submission into links_sent.
 * Returns 'inserted', 'duplicate', or 'error'.
 */
function insert_submission(mysqli $mysqli, $validated)
{
    $line = $validated['line'];
    $type = $validated['type'];
    $parsed = $validated['parsed'];

    $linkHash = sha1($line);

    if (link_hash_exists($mysqli, $linkHash)) {
        return 'duplicate';
    }

    $domainId = get_domain_id($mysqli, $parsed['domain']);

    $trackerIds = array();
    foreach ($parsed['trackers'] as $trackerUrl) {
        $tid = get_tracker_id($mysqli, $trackerUrl);
        if ($tid !== null) {
            $trackerIds[] = $tid;
        }
    }
    $idTrackers = empty($trackerIds) ? null : implode(',', $trackerIds);

    $filename = $parsed['filename'] !== '' ? $parsed['filename'] : null;
    $date = date('Y-m-d H:i:s');

    $stmt = $mysqli->prepare("INSERT INTO links_sent (link, link_hash, filename, domain_id, id_trackers, type, date) VALUES (?,?,?,?,?,?,?)");
    $stmt->bind_param('sssisss', $line, $linkHash, $filename, $domainId, $idTrackers, $type, $date);
    $ok = $stmt->execute();
    $stmt->close();

    return $ok ? 'inserted' : 'error';
}

/**
 * Returns the client IP as best as this simple setup can determine.
 * Adjust here if the app sits behind a trusted reverse proxy.
 */
function get_client_ip()
{
    return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
}

/**
 * Deletes rate-limit records older than SUBMIT_RECORD_TTL_SECONDS.
 * Called on every request, so the table is continuously kept to at most
 * ~1 hour of history (equivalent to clearing it out hourly).
 */
function prune_submission_records(mysqli $mysqli)
{
    $mysqli->query("DELETE FROM submission_records WHERE created_at < (NOW() - INTERVAL " . SUBMIT_RECORD_TTL_SECONDS . " SECOND)");
}

/**
 * Returns number of seconds the caller still has to wait, or 0 if they
 * are allowed to submit right now.
 */
function seconds_until_next_allowed(mysqli $mysqli, $ipHash)
{
    $stmt = $mysqli->prepare("SELECT created_at FROM submission_records WHERE ip_hash = ? ORDER BY created_at DESC LIMIT 1");
    $stmt->bind_param('s', $ipHash);
    $stmt->execute();
    $stmt->bind_result($lastCreatedAt);
    $found = $stmt->fetch();
    $stmt->close();

    if (!$found) {
        return 0;
    }

    $lastTs = strtotime($lastCreatedAt);
    $elapsed = time() - $lastTs;
    $remaining = SUBMIT_COOLDOWN_SECONDS - $elapsed;

    return $remaining > 0 ? $remaining : 0;
}

function record_submission_attempt(mysqli $mysqli, $ipHash)
{
    $now = date('Y-m-d H:i:s');
    $stmt = $mysqli->prepare("INSERT INTO submission_records (ip_hash, created_at) VALUES (?, ?)");
    $stmt->bind_param('ss', $ipHash, $now);
    $stmt->execute();
    $stmt->close();
}

/**
 * Deletes like records older than LIKE_RECORD_TTL_SECONDS. Called on
 * every request, keeping the table to a rolling ~1 hour window.
 */
function prune_likes(mysqli $mysqli)
{
    $mysqli->query("DELETE FROM likes WHERE created_at < (NOW() - INTERVAL " . LIKE_RECORD_TTL_SECONDS . " SECOND)");
}

/**
 * Returns number of seconds the caller still has to wait before their
 * next like counts, or 0 if they are allowed to like right now.
 */
function seconds_until_next_like_allowed(mysqli $mysqli, $ipHash)
{
    $stmt = $mysqli->prepare("SELECT created_at FROM likes WHERE ip_hash = ? ORDER BY created_at DESC LIMIT 1");
    $stmt->bind_param('s', $ipHash);
    $stmt->execute();
    $stmt->bind_result($lastCreatedAt);
    $found = $stmt->fetch();
    $stmt->close();

    if (!$found) {
        return 0;
    }

    $lastTs = strtotime($lastCreatedAt);
    $elapsed = time() - $lastTs;
    $remaining = LIKE_COOLDOWN_SECONDS - $elapsed;

    return $remaining > 0 ? $remaining : 0;
}

/**
 * Has this ip_hash already liked this exact link_hash within the current
 * retention window? Keeps a single visitor from repeatedly re-liking the
 * same entry every 60 seconds to inflate its rank.
 */
function already_liked(mysqli $mysqli, $ipHash, $linkHash)
{
    $stmt = $mysqli->prepare("SELECT id FROM likes WHERE ip_hash = ? AND link_hash = ? LIMIT 1");
    $stmt->bind_param('ss', $ipHash, $linkHash);
    $stmt->execute();
    $stmt->store_result();
    $exists = $stmt->num_rows > 0;
    $stmt->close();

    return $exists;
}

function record_like(mysqli $mysqli, $ipHash, $linkHash)
{
    $now = date('Y-m-d H:i:s');
    $stmt = $mysqli->prepare("INSERT INTO likes (link_hash, ip_hash, created_at) VALUES (?, ?, ?)");
    $stmt->bind_param('sss', $linkHash, $ipHash, $now);
    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function h($s)
{
    return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8');
}

// ---------------------------------------------------------------------
// HANDLE SUBMISSION (POST -> redirect -> GET, to avoid resubmission)
// ---------------------------------------------------------------------
prune_submission_records($mysqli);
prune_likes($mysqli);

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['like_hash'])) {
    $linkHash = $_POST['like_hash'];

    $redirectParams = array();
    if (isset($_GET['q']) && $_GET['q'] !== '') {
        $redirectParams['q'] = $_GET['q'];
    }
    if (isset($_GET['page']) && $_GET['page'] !== '') {
        $redirectParams['page'] = $_GET['page'];
    }

    if (!preg_match('/^[a-f0-9]{40}$/i', $linkHash) || !link_hash_exists($mysqli, $linkHash)) {
        $redirectParams['status'] = 'like_invalid';
    } else {
        $ipHash = hash('sha256', get_client_ip() . LIKE_SALT);

        if (already_liked($mysqli, $ipHash, $linkHash)) {
            $redirectParams['status'] = 'already_liked';
        } else {
            $remaining = seconds_until_next_like_allowed($mysqli, $ipHash);
            if ($remaining > 0) {
                $redirectParams['status'] = 'like_limited';
                $redirectParams['wait'] = (int) ceil($remaining);
            } else {
                $ok = record_like($mysqli, $ipHash, $linkHash);
                $redirectParams['status'] = $ok ? 'liked' : 'like_error';
            }
        }
    }

    header('Location: ?' . http_build_query($redirectParams));
    exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submitted_link'])) {
    $ipHash = hash('sha256', get_client_ip() . IP_SALT);
    $remaining = seconds_until_next_allowed($mysqli, $ipHash);

    $redirectParams = array();
    if (isset($_GET['q']) && $_GET['q'] !== '') {
        $redirectParams['q'] = $_GET['q'];
    }

    if ($remaining > 0) {
        $redirectParams['status'] = 'limited';
        $redirectParams['wait'] = (int) ceil($remaining / 60);
    } else {
        $validated = validate_submission($_POST['submitted_link']);
        if ($validated === false) {
            $redirectParams['status'] = 'invalid';
        } else {
            // Count this as a used slot for the cooldown window regardless
            // of whether it turns out to be a duplicate.
            record_submission_attempt($mysqli, $ipHash);

            $result = insert_submission($mysqli, $validated);
            $redirectParams['status'] = $result; // inserted | duplicate | error

            if ($result === 'inserted') {
                reward_user_for_submission($mysqli);
            }
        }
    }

    header('Location: ?' . http_build_query($redirectParams));
    exit;
}

$statusMessage = isset($_GET['status']) ? $_GET['status'] : null;
$statusWait = isset($_GET['wait']) ? (int) $_GET['wait'] : null;

// ---------------------------------------------------------------------
// SEARCH + PAGINATION (unified across `links` and `links_sent`)
// ---------------------------------------------------------------------
$search = isset($_GET['q']) ? trim($_GET['q']) : '';
$page = isset($_GET['page']) ? max(1, (int) $_GET['page']) : 1;
$offset = ($page - 1) * PER_PAGE;

$where1 = '';
$where2 = '';
$params = array();
$types = '';

if ($search !== '') {
    $where1 = "WHERE l.link LIKE ? OR l.filename LIKE ?";
    $where2 = "WHERE ls.link LIKE ? OR ls.filename LIKE ?";
    $like = '%' . $search . '%';
    // needed twice: once for each side of the UNION
    $params = array($like, $like, $like, $like);
    $types = 'ssss';
}

// total count across both tables
$countSql = "
    SELECT COUNT(*) FROM (
        SELECT l.id FROM links l $where1
        UNION ALL
        SELECT ls.id FROM links_sent ls $where2
    ) t
";
$stmt = $mysqli->prepare($countSql);
if ($types !== '') {
    $stmt->bind_param($types, ...$params);
}
$stmt->execute();
$stmt->bind_result($totalRows);
$stmt->fetch();
$stmt->close();

$totalPages = max(1, (int) ceil($totalRows / PER_PAGE));
if ($page > $totalPages) {
    $page = $totalPages;
    $offset = ($page - 1) * PER_PAGE;
}

// fetch merged page; note we deliberately do NOT select/display any
// column that would reveal which of the two tables a row came from.
// link_hash is exposed only as the target identifier for the Like
// button -- it's a hash of the link text itself (shown right next to
// it anyway), not a table-origin id, so it doesn't leak anything.
// Results are ordered by like count (highest first), then by date.
$listSql = "
    SELECT combined.link AS link, combined.filename AS filename, combined.type AS type,
           combined.id_trackers AS id_trackers, combined.date AS date, combined.link_hash AS link_hash,
           COALESCE(lk.likes_count, 0) AS likes_count
    FROM (
        SELECT l.link AS link, l.filename AS filename, l.type AS type, l.id_trackers AS id_trackers, l.date AS date, l.link_hash AS link_hash
        FROM links l
        $where1
        UNION ALL
        SELECT ls.link AS link, ls.filename AS filename, ls.type AS type, ls.id_trackers AS id_trackers, ls.date AS date, ls.link_hash AS link_hash
        FROM links_sent ls
        $where2
    ) combined
    LEFT JOIN (
        SELECT link_hash, COUNT(*) AS likes_count FROM likes GROUP BY link_hash
    ) lk ON lk.link_hash = combined.link_hash
    ORDER BY likes_count DESC, combined.date DESC
    LIMIT ? OFFSET ?
";
$stmt = $mysqli->prepare($listSql);
$allTypes = $types . 'ii';
$allParams = $params;
$allParams[] = PER_PAGE;
$allParams[] = $offset;
$stmt->bind_param($allTypes, ...$allParams);
$stmt->execute();
$result = $stmt->get_result();
$rows = array();
while ($row = $result->fetch_assoc()) {
    $rows[] = $row;
}
$stmt->close();

$currentUser = current_user($mysqli);
$theme = current_theme();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Link Board</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
    :root {
        --bg: #12161d;
        --panel: #1a212b;
        --panel-alt: #0e1218;
        --line: #2b3440;
        --text: #e9edf2;
        --text-dim: #8a94a3;
        --amber: #f2a93b;
        --amber-soft: rgba(242,169,59,0.14);
        --teal: #49b3bd;
        --teal-soft: rgba(73,179,189,0.14);
        --ok: #4caf82;
        --ok-soft: rgba(76,175,130,0.14);
        --warn: #e0a83f;
        --warn-soft: rgba(224,168,63,0.14);
        --err: #e2646a;
        --err-soft: rgba(226,100,106,0.14);
        --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
        --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        --radius: 10px;
        --gap: clamp(14px, 2.5vw, 22px);
    }

    body.theme-light {
        --bg: #f4f5f7;
        --panel: #ffffff;
        --panel-alt: #eef0f3;
        --line: #dbe0e6;
        --text: #1b2027;
        --text-dim: #5b6472;
        --amber-soft: rgba(242,169,59,0.18);
        --teal-soft: rgba(73,179,189,0.18);
    }

    * { box-sizing: border-box; }

    body {
        margin: 0;
        background: var(--bg);
        background-image:
            radial-gradient(circle at 15% 0%, rgba(242,169,59,0.06), transparent 45%),
            radial-gradient(circle at 85% 15%, rgba(73,179,189,0.05), transparent 40%);
        color: var(--text);
        font-family: var(--font-sans);
        line-height: 1.45;
        padding: clamp(16px, 4vw, 40px) 16px 60px;
    }

    .shell { max-width: 920px; margin: 0 auto; }

    /* ---------- Header / tally ---------- */
    .board-header {
        display: flex;
        flex-wrap: wrap;
        align-items: flex-end;
        justify-content: space-between;
        gap: var(--gap);
        margin-bottom: var(--gap);
    }
    .brand-mark {
        display: inline-block;
        color: var(--amber);
        font-family: var(--font-mono);
        font-size: 13px;
        letter-spacing: 0.18em;
        text-transform: uppercase;
        margin-bottom: 6px;
    }
    .board-header h1 {
        margin: 0;
        font-family: var(--font-mono);
        font-weight: 700;
        font-size: clamp(26px, 4.5vw, 34px);
        letter-spacing: -0.01em;
    }
    .tagline { margin: 6px 0 0; color: var(--text-dim); font-size: 14px; }
    .tally {
        display: flex;
        align-items: center;
        gap: 10px;
        background: var(--panel);
        border: 1px solid var(--line);
        border-radius: var(--radius);
        padding: 10px 16px;
    }
    .tally-tile {
        position: relative;
        font-family: var(--font-mono);
        font-weight: 700;
        font-variant-numeric: tabular-nums;
        color: var(--amber);
        background: var(--panel-alt);
        border: 1px solid var(--line);
        border-radius: 6px;
        padding: 4px 10px;
        font-size: 18px;
        min-width: 2.6em;
        text-align: center;
    }
    .tally-tile::after {
        content: '';
        position: absolute;
        left: 0; right: 0; top: 50%;
        height: 1px;
        background: rgba(0,0,0,0.4);
    }
    .tally-label { font-size: 12px; color: var(--text-dim); }

    /* ---------- Panels ---------- */
    .panel {
        background: var(--panel);
        border: 1px solid var(--line);
        border-radius: var(--radius);
        padding: clamp(14px, 3vw, 20px);
        margin-bottom: var(--gap);
    }
    .panel-title {
        margin: 0 0 12px;
        font-size: 13px;
        font-weight: 600;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--text-dim);
    }
    .panel-row { display: flex; flex-wrap: wrap; gap: 10px; }

    /* ---------- Form controls ---------- */
    input[type=text] {
        flex: 1 1 220px;
        min-width: 0;
        padding: 11px 13px;
        background: var(--panel-alt);
        border: 1px solid var(--line);
        border-radius: 6px;
        color: var(--text);
        font-family: var(--font-mono);
        font-size: 14px;
    }
    input[type=text]::placeholder { color: var(--text-dim); }
    input[type=text]:focus,
    button:focus-visible,
    .btn:focus-visible,
    .like-btn:focus-visible {
        outline: 2px solid var(--amber);
        outline-offset: 2px;
    }

    button, .btn {
        appearance: none;
        border: 1px solid var(--amber);
        background: var(--amber);
        color: #14181f;
        font-family: var(--font-sans);
        font-weight: 600;
        font-size: 14px;
        padding: 11px 18px;
        border-radius: 6px;
        cursor: pointer;
        text-decoration: none;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        white-space: nowrap;
        transition: background 0.15s ease, transform 0.15s ease;
    }
    button:hover, .btn:hover { background: #ffbf5c; }
    button:active, .btn:active { transform: translateY(1px); }
    .btn.btn-ghost {
        background: transparent;
        color: var(--text-dim);
        border-color: var(--line);
    }
    .btn.btn-ghost:hover { color: var(--text); border-color: var(--text-dim); background: transparent; }

    .hint { font-size: 12px; color: var(--text-dim); margin: 10px 0 0; }

    /* ---------- Notices ---------- */
    .notice {
        padding: 11px 14px;
        border-radius: 8px;
        margin-bottom: var(--gap);
        font-size: 14px;
        border: 1px solid;
    }
    .notice-ok    { background: var(--ok-soft);   color: var(--ok);   border-color: rgba(76,175,130,0.4); }
    .notice-warn  { background: var(--warn-soft); color: var(--warn); border-color: rgba(224,168,63,0.4); }
    .notice-error { background: var(--err-soft);  color: var(--err);  border-color: rgba(226,100,106,0.4); }

    /* ---------- Board list header (desktop only) ---------- */
    .board-list-head {
        display: none;
        gap: 14px;
        padding: 0 4px 10px;
        font-size: 11px;
        font-weight: 600;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--text-dim);
        border-bottom: 1px solid var(--line);
        margin-bottom: 6px;
    }
    .board-list { list-style: none; margin: 0; padding: 0; }

    .entry {
        display: flex;
        align-items: center;
        gap: 14px;
        padding: 14px 4px;
        border-bottom: 1px solid var(--line);
    }
    .entry:last-child { border-bottom: none; }

    .entry-rank {
        font-family: var(--font-mono);
        font-variant-numeric: tabular-nums;
        color: var(--text-dim);
        font-size: 13px;
        min-width: 2ch;
        text-align: right;
        flex: 0 0 auto;
    }

    .badge {
        display: inline-block;
        font-family: var(--font-mono);
        font-size: 11px;
        font-weight: 600;
        letter-spacing: 0.05em;
        text-transform: uppercase;
        padding: 3px 8px;
        border-radius: 999px;
        border: 1px solid transparent;
        flex: 0 0 auto;
    }
    .badge.type-url    { color: var(--teal);  background: var(--teal-soft);  border-color: rgba(73,179,189,0.35); }
    .badge.type-magnet { color: var(--amber); background: var(--amber-soft); border-color: rgba(242,169,59,0.35); }

    .entry-main { flex: 1 1 auto; min-width: 0; }
    .entry-top { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
    .entry-filename {
        font-size: 14px;
        font-weight: 600;
        color: var(--text);
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .entry-link {
        display: block;
        font-family: var(--font-mono);
        font-size: 12.5px;
        color: var(--text-dim);
        text-decoration: none;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .entry-link:hover { color: var(--teal); text-decoration: underline; }

    .entry-date {
        font-family: var(--font-mono);
        font-size: 12px;
        color: var(--text-dim);
        flex: 0 0 auto;
        white-space: nowrap;
    }

    .like-form { margin: 0; flex: 0 0 auto; }
    .like-btn {
        display: inline-flex;
        align-items: center;
        gap: 7px;
        background: var(--panel-alt);
        border: 1px solid var(--line);
        color: var(--text);
        font-family: var(--font-mono);
        font-size: 13px;
        font-weight: 600;
        padding: 8px 12px;
        border-radius: 6px;
        cursor: pointer;
        transition: border-color 0.15s ease, color 0.15s ease, transform 0.1s ease;
    }
    .like-btn:hover { border-color: var(--amber); color: var(--amber); }
    .like-btn:active { transform: scale(0.96); }
    .like-btn .arrow { font-size: 11px; line-height: 1; }
    .like-btn .count { font-variant-numeric: tabular-nums; }

    /* ---------- Invest ("$") button -- same visual style as like-btn ---------- */
    .invest-form { margin: 0; flex: 0 0 auto; }
    .invest-btn {
        display: inline-flex;
        align-items: center;
        gap: 7px;
        background: var(--panel-alt);
        border: 1px solid var(--line);
        color: var(--text);
        font-family: var(--font-mono);
        font-size: 13px;
        font-weight: 600;
        padding: 8px 12px;
        border-radius: 6px;
        cursor: pointer;
        transition: border-color 0.15s ease, color 0.15s ease, transform 0.1s ease;
    }
    .invest-btn:hover { border-color: var(--teal); color: var(--teal); }
    .invest-btn:active { transform: scale(0.96); }

    /* ---------- Top-right account link ---------- */
    .account-link {
        position: fixed;
        top: 16px;
        right: 16px;
        display: inline-flex;
        align-items: center;
        gap: 8px;
        background: var(--panel);
        border: 1px solid var(--line);
        color: var(--text);
        font-family: var(--font-mono);
        font-size: 13px;
        font-weight: 600;
        padding: 9px 14px;
        border-radius: 999px;
        text-decoration: none;
        z-index: 50;
        box-shadow: 0 2px 10px rgba(0,0,0,0.25);
    }
    .account-link:hover { border-color: var(--amber); color: var(--amber); }
    .account-link .balance-pill {
        background: var(--amber-soft);
        color: var(--amber);
        border-radius: 999px;
        padding: 2px 9px;
        font-size: 12px;
    }
    @media (max-width: 699px) {
        .account-link { position: static; display: inline-flex; margin-bottom: 14px; }
    }

    /* ---------- Invest modal ---------- */
    .modal-overlay {
        display: none;
        position: fixed;
        inset: 0;
        background: rgba(8,10,14,0.6);
        align-items: center;
        justify-content: center;
        z-index: 200;
        padding: 16px;
    }
    .modal-overlay.open { display: flex; }
    .modal-box {
        background: var(--panel);
        border: 1px solid var(--line);
        border-radius: var(--radius);
        padding: 22px;
        width: 100%;
        max-width: 380px;
    }
    .modal-box h3 {
        margin: 0 0 14px;
        font-family: var(--font-mono);
        font-size: 17px;
    }
    .modal-row {
        display: flex;
        justify-content: space-between;
        font-size: 13px;
        color: var(--text-dim);
        margin-bottom: 8px;
    }
    .modal-row strong { color: var(--text); }
    .modal-box input[type=number] {
        width: 100%;
        padding: 11px 13px;
        background: var(--panel-alt);
        border: 1px solid var(--line);
        border-radius: 6px;
        color: var(--text);
        font-family: var(--font-mono);
        font-size: 14px;
        margin: 12px 0 6px;
    }
    .modal-error {
        color: var(--err);
        font-size: 13px;
        min-height: 18px;
        margin-bottom: 8px;
    }
    .modal-actions { display: flex; gap: 10px; margin-top: 10px; }
    .modal-actions button { flex: 1 1 0; }

    .confirm-box p { font-size: 14px; margin: 0 0 18px; }

    @media (prefers-reduced-motion: reduce) {
        button, .btn, .like-btn { transition: none; }
        button:active, .btn:active, .like-btn:active { transform: none; }
    }

    .empty-state {
        padding: 30px 10px;
        text-align: center;
        color: var(--text-dim);
        font-size: 14px;
    }

    /* ---------- Pagination ---------- */
    .pagination { margin-top: 16px; display: flex; gap: 6px; flex-wrap: wrap; }
    .pagination a, .pagination span {
        font-family: var(--font-mono);
        font-size: 13px;
        padding: 6px 11px;
        border: 1px solid var(--line);
        border-radius: 6px;
        text-decoration: none;
        color: var(--text-dim);
    }
    .pagination a:hover { border-color: var(--amber); color: var(--amber); }
    .pagination .current { background: var(--amber); color: #14181f; border-color: var(--amber); font-weight: 700; }
    .summary { font-size: 13px; color: var(--text-dim); margin: 12px 0 0; }

    /* ---------- Desktop layout (>= 700px): columned rows ---------- */
    @media (min-width: 700px) {
        .board-list-head { display: flex; }
        .board-list-head span:nth-child(1) { flex: 0 0 auto; min-width: 2ch; }
        .board-list-head span:nth-child(2) { flex: 0 0 90px; }
        .board-list-head span:nth-child(3) { flex: 1 1 auto; }
        .board-list-head span:nth-child(4) { flex: 0 0 150px; }
        .board-list-head span:nth-child(5) { flex: 0 0 110px; text-align: right; }

        .entry-top .badge { flex: 0 0 90px; }
        .entry-date { flex: 0 0 150px; }
    }

    /* ---------- Mobile layout (< 700px): stacked cards ---------- */
    @media (max-width: 699px) {
        .entry {
            flex-wrap: wrap;
            padding: 14px 12px;
            background: var(--panel-alt);
            border: 1px solid var(--line);
            border-radius: 8px;
            margin-bottom: 10px;
        }
        .entry:last-child { margin-bottom: 0; }
        .entry-rank { order: 0; }
        .entry-top { order: 1; flex: 1 1 auto; justify-content: space-between; }
        .entry-main { order: 2; flex: 1 1 100%; }
        .like-form { order: 3; flex: 1 1 100%; }
        .like-btn { width: 100%; justify-content: center; padding: 10px; }
        .board-header { align-items: flex-start; }
        .tally { width: 100%; justify-content: space-between; }
    }
</style>
</head>
<body class="theme-<?php echo h($theme); ?>">

<a class="account-link" href="login.php">
    <?php if ($currentUser): ?>
        &#9881; <?php echo h($currentUser['username']); ?>
        <span class="balance-pill"><?php echo h(number_format((float) $currentUser['balance'], 2)); ?> pts</span>
    <?php else: ?>
        &#128100; Login / Style
    <?php endif; ?>
</a>

<div class="shell">

    <header class="board-header">
        <div>
            <span class="brand-mark">
<a class="brand-mark" href="buy.php">buy</a>
<a class="brand-mark" href="sell.php">sell</a>
<a class="brand-mark" href="transfer.php">transfer</a>
<a class="brand-mark" href="comments.php">comments</a>
<a class="brand-mark" href="messages.php">messages</a>
<a class="brand-mark" href="upload.php">upload</a>
<a class="brand-mark" href="json/">JSON</a>

// anonymous &middot; no signup</span>
            <h1>Link Board</h1>
            <p class="tagline">Drop a URL or magnet link. The board ranks entries by community likes.</p>
        </div>
        <div class="tally">
            <span class="tally-tile"><?php echo (int) $totalRows; ?></span>
            <span class="tally-label">entries<br>logged</span>
        </div>
    </header>

    <?php if ($statusMessage !== null): ?>
    <?php if ($statusMessage === 'inserted'): ?>
        <div class="notice notice-ok">Thanks! Your link was added.</div>
    <?php elseif ($statusMessage === 'duplicate'): ?>
        <div class="notice notice-warn">That link is already in the database.</div>
    <?php elseif ($statusMessage === 'invalid'): ?>
        <div class="notice notice-error">That doesn't look like a valid URL or magnet link. Please check it and try again.</div>
    <?php elseif ($statusMessage === 'limited'): ?>
        <div class="notice notice-warn">You can only submit one link every 10 minutes. Please try again in about <?php echo (int) $statusWait; ?> minute(s).</div>
    <?php elseif ($statusMessage === 'error'): ?>
        <div class="notice notice-error">Something went wrong saving your link. Please try again.</div>
    <?php elseif ($statusMessage === 'liked'): ?>
        <div class="notice notice-ok">Thanks for the like!</div>
    <?php elseif ($statusMessage === 'already_liked'): ?>
        <div class="notice notice-warn">You've already liked that entry recently.</div>
    <?php elseif ($statusMessage === 'like_limited'): ?>
        <div class="notice notice-warn">You can only like one entry per minute. Please try again in about <?php echo (int) $statusWait; ?> second(s).</div>
    <?php elseif ($statusMessage === 'like_invalid' || $statusMessage === 'like_error'): ?>
        <div class="notice notice-error">Couldn't register that like. Please try again.</div>
    <?php endif; ?>
    <?php endif; ?>

    <section class="panel">
        <h2 class="panel-title">Drop a link</h2>
        <form class="panel-row" method="post" action="<?php echo $search !== '' ? '?q=' . urlencode($search) : '?'; ?>">
            <input type="text" name="submitted_link" placeholder="Paste a URL or magnet link..." required>
            <button type="submit">Submit</button>
        </form>
        <p class="hint">Anonymous, no registration required. Limit: one submission per 10 minutes.</p>
    </section>

    <section class="panel">
        <h2 class="panel-title">Search the board</h2>
        <form class="panel-row" method="get" action="">
            <input type="text" name="q" placeholder="Search by link or filename..." value="<?php echo h($search); ?>">
            <button type="submit">Search</button>
            <?php if ($search !== ''): ?>
                <a class="btn btn-ghost" href="?">Clear</a>
            <?php endif; ?>
        </form>
    </section>

    <?php
    $baseQuery = array();
    if ($search !== '') { $baseQuery['q'] = $search; }

    function page_url($p, $baseQuery)
    {
        $q = $baseQuery;
        $q['page'] = $p;
        return '?' . http_build_query($q);
    }
    ?>

    <section class="panel">
        <div class="board-list-head">
            <span>#</span>
            <span>Type</span>
            <span>Entry</span>
            <span>Date</span>
            <span>Like</span>
        </div>

        <?php if (empty($rows)): ?>
            <div class="empty-state">No results found.</div>
        <?php else: ?>
            <ol class="board-list">
                <?php foreach ($rows as $i => $row): ?>
                    <li class="entry">
                        <span class="entry-rank"><?php echo (int) ($offset + $i + 1); ?></span>
                        <div class="entry-main">
                            <div class="entry-top">
                                <span class="badge type-<?php echo h($row['type']); ?>"><?php echo h($row['type']); ?></span>
                            </div>
                            <?php if ($row['filename']): ?>
                                <div class="entry-filename"><?php echo h($row['filename']); ?></div>
                            <?php endif; ?>
                            <a class="entry-link" href="<?php echo h($row['link']); ?>" target="_blank" rel="noopener noreferrer nofollow" title="<?php echo h($row['link']); ?>">
                                <?php echo h($row['link']); ?>
                            </a>
                        </div>
                        <span class="entry-date"><?php echo h($row['date']); ?></span>
                        <form class="like-form" method="post" action="<?php echo h(page_url($page, $baseQuery)); ?>">
                            <input type="hidden" name="like_hash" value="<?php echo h($row['link_hash']); ?>">
                            <button type="submit" class="like-btn">
                                <span class="arrow">&#9650;</span>
                                <span class="count"><?php echo (int) $row['likes_count']; ?></span>
                            </button>
                        </form>
                        <form class="invest-form" onsubmit="return false;">
                            <button type="button" class="invest-btn"
                                onclick="openInvestModal('<?php echo h($row['link_hash']); ?>', '<?php echo h(addslashes($row['filename'] !== null ? $row['filename'] : $row['link'])); ?>')">
                                $
                            </button>
                        </form>
                    </li>
                <?php endforeach; ?>
            </ol>
        <?php endif; ?>

        <div class="pagination">
            <?php
            if ($page > 1) {
                echo '<a href="' . h(page_url($page - 1, $baseQuery)) . '">&laquo; Prev</a>';
            }

            $startP = max(1, $page - 3);
            $endP = min($totalPages, $page + 3);

            for ($p = $startP; $p <= $endP; $p++) {
                if ($p === $page) {
                    echo '<span class="current">' . $p . '</span>';
                } else {
                    echo '<a href="' . h(page_url($p, $baseQuery)) . '">' . $p . '</a>';
                }
            }

            if ($page < $totalPages) {
                echo '<a href="' . h(page_url($page + 1, $baseQuery)) . '">Next &raquo;</a>';
            }
            ?>
        </div>
        <p class="summary">Page <?php echo $page; ?> of <?php echo $totalPages; ?> (<?php echo (int) $totalRows; ?> results)</p>
    </section>

</div>

<!-- Invest / deposit modal -->
<div class="modal-overlay" id="investOverlay">
    <div class="modal-box">
        <h3>Invest in this entry</h3>
        <div class="modal-row"><span>Balance</span><strong id="investBalance">0.00 pts</strong></div>
        <div class="modal-row"><span>File</span><strong id="investFilename">-</strong></div>
        <label for="investAmount" class="hint" style="display:block;margin:4px 0 0;">Amount to deposit / invest</label>
        <input type="number" id="investAmount" min="0.01" step="0.01" placeholder="0.00">
        <div class="modal-error" id="investError"></div>
        <div class="modal-actions">
            <button type="button" class="btn btn-ghost" onclick="closeInvestModal()">Cancel</button>
            <button type="button" onclick="requestInvestConfirm()">Confirm</button>
        </div>
    </div>
</div>

<!-- Confirm / cancel step -->
<div class="modal-overlay" id="confirmOverlay">
    <div class="modal-box confirm-box">
        <h3>Are you sure?</h3>
        <p>Confirm investing <strong id="confirmAmount">0.00</strong> pts into <strong id="confirmFilename">-</strong>?</p>
        <div class="modal-error" id="confirmError"></div>
        <div class="modal-actions">
            <button type="button" class="btn btn-ghost" onclick="closeConfirmModal()">Cancel</button>
            <button type="button" onclick="submitInvest()">Yes, confirm</button>
        </div>
    </div>
</div>

<script>
    var CURRENT_BALANCE = <?php echo json_encode($currentUser ? (float) $currentUser['balance'] : 0); ?>;
    var IS_LOGGED_IN = <?php echo $currentUser ? 'true' : 'false'; ?>;
    var pendingInvest = { linkHash: null, filename: null };

    function openInvestModal(linkHash, filename) {
        if (!IS_LOGGED_IN) {
            window.location.href = 'login.php';
            return;
        }
        pendingInvest.linkHash = linkHash;
        pendingInvest.filename = filename;

        document.getElementById('investBalance').textContent = CURRENT_BALANCE.toFixed(2) + ' pts';
        document.getElementById('investFilename').textContent = filename;
        document.getElementById('investAmount').value = '';
        document.getElementById('investError').textContent = '';
        document.getElementById('investOverlay').classList.add('open');
    }

    function closeInvestModal() {
        document.getElementById('investOverlay').classList.remove('open');
    }

    function requestInvestConfirm() {
        var amountInput = document.getElementById('investAmount');
        var amount = parseFloat(amountInput.value);
        var errorEl = document.getElementById('investError');

        if (isNaN(amount) || amount <= 0) {
            errorEl.textContent = 'Enter a valid amount greater than zero.';
            return;
        }
        if (CURRENT_BALANCE <= 0 || amount > CURRENT_BALANCE) {
            errorEl.textContent = 'Insufficient balance for that amount.';
            return;
        }

        pendingInvest.amount = amount;
        document.getElementById('confirmAmount').textContent = amount.toFixed(2);
        document.getElementById('confirmFilename').textContent = pendingInvest.filename;
        document.getElementById('confirmError').textContent = '';
        document.getElementById('confirmOverlay').classList.add('open');
    }

    function closeConfirmModal() {
        document.getElementById('confirmOverlay').classList.remove('open');
    }

    function submitInvest() {
        var confirmError = document.getElementById('confirmError');
        confirmError.textContent = '';

        var body = new URLSearchParams();
        body.set('link_hash', pendingInvest.linkHash);
        body.set('filename', pendingInvest.filename);
        body.set('amount', pendingInvest.amount);

        fetch('transactions.php', {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: body.toString()
        })
        .then(function (r) { return r.json(); })
        .then(function (data) {
            if (data.ok) {
                CURRENT_BALANCE = parseFloat(data.balance);
                closeConfirmModal();
                closeInvestModal();
                document.querySelector('.account-link .balance-pill') &&
                    (document.querySelector('.account-link .balance-pill').textContent = CURRENT_BALANCE.toFixed(2) + ' pts');
                alert('Investment confirmed! New balance: ' + CURRENT_BALANCE.toFixed(2) + ' pts.');
            } else {
                confirmError.textContent = data.error || 'Transaction failed.';
            }
        })
        .catch(function () {
            confirmError.textContent = 'Network error. Please try again.';
        });
    }

    // Click outside the modal box closes it
    document.getElementById('investOverlay').addEventListener('click', function (e) {
        if (e.target === this) { closeInvestModal(); }
    });
    document.getElementById('confirmOverlay').addEventListener('click', function (e) {
        if (e.target === this) { closeConfirmModal(); }
    });
</script>

</body>
</html>