<?php
/**
 * bentbox_upload.php
 * Steps 4 + 5: the upload page for a connected creator, and each video's status.
 *
 * The browser only ever calls THIS page. Tokens stay server-side; the page
 * never renders an access_token, connection_id, video_id or presigned URL.
 *
 * AJAX actions (all require an active connection):
 *   ?action=prepare   (direct mode)  JSON metadata in  -> {upload_url, ref} out
 *   ?action=upload    (proxy mode)   multipart in       -> streams to S3 server-side, {ref} out
 *   ?action=status&ref=…             -> the video's BentBox status
 *   ?action=disconnect               clears the stored connection
 *
 * "ref" is an encrypted, connection-bound reference to the video (see
 * bb_video_ref), so the browser can ask for a status without the video_id.
 */
require_once __DIR__ . '/bentbox_config.php';

// --------------------------------------------------------------------------
// AJAX ACTIONS (run and exit before any HTML)
// --------------------------------------------------------------------------
$action = $_GET['action'] ?? null;

if ($action === 'disconnect') {
    bb_clear_connection();
    bb_json(['ok' => true]);
}

if ($action !== null) {
    // Everything below requires a live connection.
    if (!bb_is_connected()) {
        http_response_code(401);
        bb_json(['ok' => false, 'error' => 'not_connected']);
    }
}

/** Normalise metadata from either a JSON body or POST fields. */
function read_meta(array $src): array {
    $tags = $src['tags'] ?? [];
    if (is_string($tags)) {
        $tags = array_filter(array_map('trim', explode(',', $tags)));
    }
    return [
        'title'         => trim($src['title'] ?? ''),
        'filename'      => trim($src['filename'] ?? ''),
        'filesize'      => (int)($src['filesize'] ?? 0),
        'duration'      => (float)($src['duration'] ?? 0),
        'description'   => trim($src['description'] ?? ''),
        'price'         => (float)($src['price'] ?? 0),
        'is_premium'    => filter_var($src['is_premium'] ?? false, FILTER_VALIDATE_BOOLEAN),
        'adult_content' => filter_var($src['adult_content'] ?? true, FILTER_VALIDATE_BOOLEAN),
        'tags'          => array_values($tags),
    ] + read_consent($src);
}

/**
 * Consent for POST /v1/content/video: exactly one of
 *   sole_performer = true          (the creator is the only person in the video)
 *   release_form_id = "<ProntoID>" (signed release covering the other performers)
 * Keys are only included when set, so they pass straight through to the API.
 */
function read_consent(array $src): array {
    $performers = $src['performers'] ?? '';
    if ($performers === 'solo') {
        return ['sole_performer' => true];
    }
    if ($performers === 'others') {
        $rid = trim((string)($src['release_form_id'] ?? ''));
        return ['sole_performer' => false, 'release_form_id' => $rid];
    }
    return [];
}

function validate_meta(array $m): ?string {
    if ($m['title'] === '')    return 'A title is required.';
    if ($m['filename'] === '') return 'A filename is required.';
    if ($m['filesize'] <= 0)   return 'File size could not be determined.';
    if ($m['duration'] <= 0)   return 'Video duration could not be determined.';
    if (!array_key_exists('sole_performer', $m)) {
        return 'Choose who appears in the video.';
    }
    if ($m['sole_performer'] === false) {
        $rid = $m['release_form_id'] ?? '';
        if ($rid === '') return 'Enter the ProntoID release form ID for the other performers.';
        if (strlen($rid) > 128 || !preg_match('/^[A-Za-z0-9_.:-]+$/', $rid)) {
            return 'That release form ID doesn\'t look right. Copy it exactly as ProntoID shows it.';
        }
    }
    return null;
}

// ---- DIRECT MODE: return only the presigned URL to the browser -------------
if ($action === 'prepare') {
    $in  = json_decode(file_get_contents('php://input'), true) ?: [];
    $m   = read_meta($in);
    if ($err = validate_meta($m)) bb_json(['ok' => false, 'error' => $err]);

    $r = bb_create_and_sign($m);
    if (!$r['ok']) {
        bb_json(['ok' => false, 'error' => $r['error'], 'stage' => $r['stage'] ?? null]);
    }
    // The presigned URL is used by JS but not displayed anywhere.
    bb_json(['ok' => true, 'upload_url' => $r['upload_url'],
             'content_type' => $r['content_type'],
             'ref' => bb_video_ref($r['video_id'])]);
}

// ---- STATUS: where is this video on BentBox? --------------------------------
if ($action === 'status') {
    $videoId = bb_video_from_ref((string)($_GET['ref'] ?? ''));
    if ($videoId === null) {
        bb_json(['ok' => false, 'error' => 'unknown_video']);
    }
    $s = bb_video_status($videoId);
    if (!$s['ok'] && ($s['code'] ?? '') === 'not_found') {
        bb_json(['ok' => false, 'error' => 'unknown_video']);
    }
    bb_json($s);
}

// ---- PROXY MODE: browser -> this server -> S3 (nothing S3 reaches browser) --
if ($action === 'upload') {
    if (!isset($_FILES['file']['tmp_name']) || !is_uploaded_file($_FILES['file']['tmp_name'])) {
        bb_json(['ok' => false, 'error' => 'No file received.']);
    }
    $m = read_meta($_POST);
    if ($err = validate_meta($m)) bb_json(['ok' => false, 'error' => $err]);

    $r = bb_create_and_sign($m);
    if (!$r['ok']) {
        bb_json(['ok' => false, 'error' => $r['error'], 'stage' => $r['stage'] ?? null]);
    }

    // Stream the temp file straight to S3 (no full in-memory buffering).
    $tmp  = $_FILES['file']['tmp_name'];
    $fh   = fopen($tmp, 'rb');
    $ch   = curl_init($r['upload_url']);
    curl_setopt_array($ch, [
        CURLOPT_PUT            => true,
        CURLOPT_INFILE         => $fh,
        CURLOPT_INFILESIZE     => filesize($tmp),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 0,
        CURLOPT_HTTPHEADER     => ['Content-Type: ' . $r['content_type']],
    ]);
    curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    fclose($fh);

    $ok = ($code >= 200 && $code < 300);
    bb_json(['ok' => $ok, 'http' => $code] + ($ok ? ['ref' => bb_video_ref($r['video_id'])] : []));
}

// --------------------------------------------------------------------------
// PAGE RENDER
// --------------------------------------------------------------------------
$connected = bb_is_connected();
$conn      = bb_get_connection();
?><!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Upload to BentBox</title>
    <style>
        :root{--bl:#2563eb;--ink:#0f172a;--mut:#64748b;--brd:#e2e8f0;--ok:#10b981;--err:#ef4444;}
        *{box-sizing:border-box;}
        body{font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;background:#f1f5f9;
             color:var(--ink);margin:0;padding:40px 20px;line-height:1.6;}
        .wrap{max-width:600px;margin:0 auto;}
        .topbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;}
        .brand{font-weight:800;font-size:1.25rem;}
        .conn{font-size:.82rem;color:var(--mut);}
        .conn a{color:var(--mut);}
        .card{background:#fff;border-radius:16px;padding:32px;box-shadow:0 8px 30px rgba(0,0,0,.06);}
        h1{font-size:1.5rem;margin:0 0 6px;}
        .sub{color:var(--mut);margin:0 0 24px;}
        label{display:block;font-weight:600;font-size:.85rem;margin:16px 0 6px;}
        input[type=text],input[type=number],textarea{width:100%;padding:12px 14px;border:1px solid var(--brd);
             border-radius:10px;font:inherit;font-size:.95rem;}
        textarea{min-height:80px;resize:vertical;}
        .row{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
        .drop{border:2px dashed var(--brd);border-radius:12px;padding:28px;text-align:center;
              color:var(--mut);cursor:pointer;transition:.15s;background:#f8fafc;}
        .drop.hl{border-color:var(--bl);background:#eff6ff;color:var(--bl);}
        .drop strong{color:var(--ink);}
        .fileinfo{font-size:.82rem;color:var(--mut);margin-top:10px;}
        .chk{display:flex;align-items:center;gap:8px;margin-top:14px;font-size:.9rem;font-weight:600;}
        .chk input{width:auto;}
        .btn{width:100%;margin-top:24px;background:var(--bl);color:#fff;border:none;border-radius:11px;
             padding:15px;font:inherit;font-weight:700;font-size:1rem;cursor:pointer;transition:.15s;}
        .btn:hover{background:#1d4ed8;}
        .btn:disabled{opacity:.5;cursor:not-allowed;}
        .progress{height:10px;background:var(--brd);border-radius:6px;overflow:hidden;margin-top:20px;display:none;}
        .progress>div{height:100%;width:0;background:var(--bl);transition:width .2s;}
        .msg{margin-top:18px;padding:14px 16px;border-radius:10px;font-size:.92rem;display:none;}
        .msg.ok{background:#ecfdf5;color:#065f46;display:block;}
        .msg.err{background:#fef2f2;color:#991b1b;display:block;}
        fieldset{border:0;padding:0;margin:20px 0 0;}
        legend{font-weight:600;font-size:.85rem;margin-bottom:8px;padding:0;}
        .opt{display:flex;gap:10px;align-items:flex-start;padding:12px 14px;border:1px solid var(--brd);
             border-radius:10px;margin-top:8px;cursor:pointer;font-size:.92rem;}
        .opt:has(input:checked){border-color:var(--bl);background:#eff6ff;}
        .opt input{margin-top:4px;}
        .opt span{display:block;color:var(--mut);font-size:.82rem;font-weight:400;}
        .release{margin:10px 0 0 30px;display:none;}
        .release.show{display:block;}
        .release input{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;}
        .hint{font-size:.8rem;color:var(--mut);margin-top:6px;}
        /* Your uploads */
        .uploads{margin-top:24px;}
        .uploads h2{font-size:1.05rem;margin:0 0 4px;}
        .uploads .sub{margin-bottom:14px;font-size:.88rem;}
        .vid{border:1px solid var(--brd);border-radius:12px;padding:16px 18px;margin-top:10px;}
        .vid.new{border-color:var(--bl);box-shadow:0 0 0 3px #dbeafe;}
        .vid-head{display:flex;justify-content:space-between;gap:12px;align-items:baseline;}
        .vid-title{font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
        .chip{flex-shrink:0;font-size:.76rem;font-weight:700;padding:3px 9px;border-radius:999px;background:#f1f5f9;color:var(--mut);}
        .chip.wait{background:#eff6ff;color:#1d4ed8;}
        .chip.act{background:#fffbeb;color:#b45309;}
        .chip.live{background:#ecfdf5;color:#047857;}
        .chip.stop{background:#fef2f2;color:#b91c1c;}
        .track{display:grid;grid-template-columns:repeat(4,1fr);gap:4px;margin:12px 0 6px;list-style:none;padding:0;}
        .track li{font-size:.72rem;color:var(--mut);padding-top:8px;border-top:3px solid var(--brd);}
        .track li.done{border-top-color:var(--bl);color:var(--ink);}
        .track li.now{border-top-color:var(--bl);color:var(--ink);font-weight:700;}
        .track.act li.now{border-top-color:#f59e0b;}
        .track.live li{border-top-color:#10b981;color:var(--ink);}
        .track.stop li.now{border-top-color:var(--err);}
        .vid-desc{font-size:.88rem;color:var(--mut);margin:6px 0 0;}
        .vid-foot{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-top:12px;flex-wrap:wrap;}
        .vid-link{display:inline-block;font-size:.88rem;font-weight:700;color:#fff;background:var(--bl);text-decoration:none;padding:8px 14px;border-radius:9px;}
        .vid-link.quiet{background:#fff;color:var(--ink);border:1px solid var(--brd);}
        .vid-meta{font-size:.76rem;color:var(--mut);}
        .linkbtn{background:none;border:0;color:var(--bl);font:inherit;font-size:.76rem;cursor:pointer;padding:0;text-decoration:underline;}
        .connect-card{text-align:center;padding:48px 32px;}
        .connect-card p{color:var(--mut);}
        .connect-btn{display:inline-block;background:var(--bl);color:#fff;text-decoration:none;
             padding:14px 30px;border-radius:11px;font-weight:700;margin-top:12px;}
    </style>
</head>
<body>
<div class="wrap">

    <div class="topbar">
        <div class="brand">Your site</div>
        <?php if ($connected): ?>
            <div class="conn">Connected to BentBox · <a href="#" id="disconnect">Disconnect</a></div>
        <?php endif; ?>
    </div>

    <?php if (!$connected): ?>

        <div class="card connect-card">
            <h1>Connect your BentBox account</h1>
            <p>To publish videos to BentBox, connect your account once. You'll be asked to approve access, then brought back here to upload.</p>
            <a class="connect-btn" href="bentbox_connect.php">Connect BentBox Account</a>
        </div>

    <?php else: ?>

        <div class="card">
            <h1>Upload a video</h1>
            <p class="sub">Your video will be published to your BentBox profile.</p>

            <div class="drop" id="drop">
                <strong>Choose a video</strong> or drag it here
                <div class="fileinfo" id="fileinfo">MP4, MOV — up to 2&nbsp;GB</div>
                <input type="file" id="file" accept="video/*" hidden>
            </div>

            <label>Title</label>
            <input type="text" id="title" maxlength="200" placeholder="Give your video a title">

            <label>Description</label>
            <textarea id="description" maxlength="5000" placeholder="Optional"></textarea>

            <div class="row">
                <div>
                    <label>Price (USD)</label>
                    <input type="number" id="price" min="0" max="9999.99" step="0.01" value="0">
                </div>
                <div>
                    <label>Tags</label>
                    <input type="text" id="tags" placeholder="comma, separated">
                </div>
            </div>

            <fieldset id="performers">
                <legend>Who's in this video?</legend>
                <label class="opt"><input type="radio" name="performers" value="solo">
                    <div>Only me<span>You're the only person who appears in the video.</span></div></label>
                <label class="opt"><input type="radio" name="performers" value="others">
                    <div>Me and other performers<span>Everyone else must have signed a release on ProntoID.</span></div></label>
                <div class="release" id="release">
                    <label for="release_form_id" style="margin-top:6px">ProntoID release form ID</label>
                    <input type="text" id="release_form_id" maxlength="128" autocomplete="off" spellcheck="false" placeholder="e.g. pid_rel_a1b2c3">
                    <div class="hint">Copy it from the signed release in ProntoID. One release can cover several performers.</div>
                </div>
            </fieldset>

            <label class="chk"><input type="checkbox" id="is_premium"> Premium content</label>
            <label class="chk"><input type="checkbox" id="adult_content" checked> Adult content (18+)</label>

            <button class="btn" id="submit">Publish to BentBox</button>

            <div class="progress" id="progress"><div id="bar"></div></div>
            <div class="msg" id="msg"></div>
        </div>

        <div class="card uploads" id="uploads" hidden>
            <h2>Your uploads</h2>
            <p class="sub">Videos you've sent to BentBox from here, and where each one is. This updates by itself.</p>
            <div id="vids"></div>
        </div>

    <?php endif; ?>
</div>

<script>
const PROXY = <?= BB_PROXY_UPLOAD ? 'true' : 'false' ?>;
</script>
<script>
(function(){
    const $ = id => document.getElementById(id);
    if (!$('submit')) {
        // Not connected — only wire up disconnect if present.
        if ($('disconnect')) wireDisconnect();
        return;
    }

    let meta = { filename:'', filesize:0, duration:0 };
    const drop = $('drop'), fileInput = $('file');

    drop.addEventListener('click', () => fileInput.click());
    ['dragover','dragenter'].forEach(e => drop.addEventListener(e, ev => {
        ev.preventDefault(); drop.classList.add('hl');
    }));
    ['dragleave','drop'].forEach(e => drop.addEventListener(e, ev => {
        ev.preventDefault(); drop.classList.remove('hl');
    }));
    drop.addEventListener('drop', ev => { if (ev.dataTransfer.files[0]) setFile(ev.dataTransfer.files[0]); });
    fileInput.addEventListener('change', () => { if (fileInput.files[0]) setFile(fileInput.files[0]); });

    function setFile(f){
        fileInput._file = f;
        meta.filename = f.name; meta.filesize = f.size; meta.duration = 0;
        if (!$('title').value) $('title').value = f.name.replace(/\.[^.]+$/, '');
        const v = document.createElement('video');
        v.preload = 'metadata';
        v.onloadedmetadata = () => { URL.revokeObjectURL(v.src); meta.duration = Math.round((v.duration||0)*10)/10; info(); };
        v.onerror = info;
        v.src = URL.createObjectURL(f);
        info();
    }
    function info(){
        const mb = (meta.filesize/1048576).toFixed(1);
        $('fileinfo').innerHTML = '<strong>'+meta.filename+'</strong> · '+mb+' MB · '+(meta.duration||'…')+'s';
    }

    function showMsg(text, ok){ const m=$('msg'); m.className='msg '+(ok?'ok':'err'); m.textContent=text; }

    // Show the release field only for "Me and other performers"
    const performers = () => (document.querySelector('input[name="performers"]:checked') || {}).value || '';
    document.querySelectorAll('input[name="performers"]').forEach(r => r.addEventListener('change', () => {
        const others = performers() === 'others';
        $('release').classList.toggle('show', others);
        if (others) $('release_form_id').focus();
    }));

    $('submit').addEventListener('click', async function(){
        const f = fileInput._file;
        if (!f) return showMsg('Please choose a video file first.', false);
        if (!$('title').value.trim()) return showMsg('Please add a title.', false);
        if (!performers()) return showMsg('Choose who appears in the video.', false);
        if (performers() === 'others' && !$('release_form_id').value.trim()) {
            $('release_form_id').focus();
            return showMsg('Enter the ProntoID release form ID for the other performers.', false);
        }

        this.disabled = true;
        $('progress').style.display = 'block';
        $('bar').style.width = '0%';
        $('msg').className = 'msg';

        const md = {
            title: $('title').value.trim(),
            filename: meta.filename, filesize: meta.filesize, duration: meta.duration,
            description: $('description').value.trim(),
            price: parseFloat($('price').value)||0,
            is_premium: $('is_premium').checked,
            adult_content: $('adult_content').checked,
            tags: $('tags').value.split(',').map(t=>t.trim()).filter(Boolean).slice(0,20),
            performers: performers(),
            release_form_id: performers() === 'others' ? $('release_form_id').value.trim() : '',
        };

        try {
            const ref = PROXY ? await proxyUpload(f, md) : await directUpload(f, md);
            $('bar').style.width = '100%';
            showMsg('Uploaded. BentBox is processing your video; follow it under “Your uploads” below.', true);
            this.disabled = false;
            if (ref) Uploads.add(ref, md.title);
        } catch (e) {
            this.disabled = false;
            showMsg('Upload failed: ' + e.message, false);
        }
    });

    // Direct: ask our server for a presigned URL, then PUT straight to S3.
    async function directUpload(f, md){
        const r = await fetch('bentbox_upload.php?action=prepare', {
            method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(md)
        });
        const j = await r.json();
        if (!j.ok) throw new Error(j.error || 'preparation failed');
        await putWithProgress(j.upload_url, f, f.type || j.content_type || 'video/mp4');
        return j.ref;
    }
    function putWithProgress(url, f, ct){
        return new Promise((res, rej) => {
            const x = new XMLHttpRequest();
            x.open('PUT', url, true);
            x.setRequestHeader('Content-Type', ct);
            x.upload.onprogress = e => { if (e.lengthComputable) $('bar').style.width = Math.round(e.loaded/e.total*100)+'%'; };
            x.onload = () => (x.status>=200 && x.status<300) ? res() : rej(new Error('storage returned '+x.status));
            x.onerror = () => rej(new Error('network error (if this is CORS, enable proxy mode)'));
            x.send(f);
        });
    }
    // Proxy: send the file to our server, which relays it to S3.
    function proxyUpload(f, md){
        return new Promise((res, rej) => {
            const fd = new FormData();
            Object.entries(md).forEach(([k,v]) => fd.append(k, Array.isArray(v)?v.join(','):v));
            fd.append('file', f);
            const x = new XMLHttpRequest();
            x.open('POST', 'bentbox_upload.php?action=upload', true);
            x.upload.onprogress = e => { if (e.lengthComputable) $('bar').style.width = Math.round(e.loaded/e.total*100)+'%'; };
            x.onload = () => { let j={}; try{j=JSON.parse(x.responseText);}catch(_){}
                               j.ok ? res(j.ref) : rej(new Error(j.error || ('server returned '+x.status))); };
            x.onerror = () => rej(new Error('network error'));
            x.send(fd);
        });
    }

    // ------------------------------------------------------------------
    // Your uploads: status from GET /v1/content/video/{id}, via ?action=status
    // ------------------------------------------------------------------
    const Uploads = (function(){
        const KEY = 'bb_uploads', MAX = 5;
        const STEPS = ['Uploaded', 'Processing', 'In review', 'Live'];
        // status -> [chip label, tone, current step index, label for that step, what it means for you]
        const VIEW = {
            awaiting_upload:   ['Waiting for file', 'wait', 0, null, 'BentBox hasn’t received the file yet.'],
            processing:        ['Processing',       'wait', 1, null, 'BentBox is preparing your video. This usually takes a few minutes.'],
            draft:             ['Draft',            'act',  2, 'Publish', 'Your video is ready but not published. Publish it on BentBox when you’re ready.'],
            pending_review:    ['In review',        'wait', 2, null, 'Your video is published and waiting for BentBox review. It goes live once approved.'],
            action_required:   ['Needs your input', 'act',  2, 'Finish settings', 'Approved! Finish your video’s settings on BentBox so it can go live.'],
            live:              ['Live',             'live', 3, null, 'Your video is live on BentBox.'],
            upload_expired:    ['Upload expired',   'stop', 0, null, 'The file didn’t arrive within 24 hours. Upload the video again.'],
            processing_failed: ['Processing failed','stop', 1, null, 'BentBox couldn’t process this file. Check it plays correctly, then upload it again.'],
            removed:           ['Removed',          'stop', 3, null, 'This video has been removed from BentBox.'],
        };
        const DONE = ['live', 'removed', 'upload_expired', 'processing_failed'];
        let items = load(), timer = null, startedAt = Date.now(), newest = null;

        function load(){ try { return JSON.parse(localStorage.getItem(KEY)) || []; } catch(_) { return []; } }
        function save(){ try { localStorage.setItem(KEY, JSON.stringify(items.map(({ref,title,at,s}) => ({ref,title,at,s})))); } catch(_){} }
        function esc(s){ return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
        function safeUrl(u){ return /^https:\/\/bentbox\.co\//.test(u || '') ? u : null; }

        function add(ref, title){
            items = [{ref, title, at: Date.now(), s: {status:'processing'}}].concat(items).slice(0, MAX);
            newest = ref; startedAt = Date.now(); save(); render(); refresh();
        }

        function render(){
            $('uploads').hidden = items.length === 0;
            $('vids').innerHTML = items.map(it => {
                const s = it.s || {}, v = VIEW[s.status] || ['Checking…', '', 0, null, null];
                const steps = STEPS.map((name, i) => {
                    const cls = s.status === 'live' ? 'done' : (i < v[2] ? 'done' : (i === v[2] ? 'now' : ''));
                    return '<li class="'+cls+'">'+esc(i === v[2] && v[3] ? v[3] : name)+'</li>';
                }).join('');
                const page = safeUrl(s.video_page_url), settings = safeUrl(s.video_settings_url);
                let action = '';
                if (s.status === 'live' && page)
                    action = '<a class="vid-link" href="'+esc(page)+'" target="_blank" rel="noopener">View on BentBox</a>';
                else if ((s.status === 'draft' || s.status === 'action_required') && settings)
                    action = '<a class="vid-link" href="'+esc(settings)+'" target="_blank" rel="noopener">'+(s.status === 'draft' ? 'Publish on BentBox' : 'Finish on BentBox')+'</a>';
                else if (settings && !DONE.includes(s.status))
                    action = '<a class="vid-link quiet" href="'+esc(settings)+'" target="_blank" rel="noopener">Edit on BentBox</a>';
                const checked = it.checked ? 'Checked ' + new Date(it.checked).toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}) : '';
                return '<div class="vid'+(it.ref === newest ? ' new' : '')+'">'
                    + '<div class="vid-head"><span class="vid-title">'+esc(s.title || it.title || 'Untitled video')+'</span>'
                    + '<span class="chip '+v[1]+'">'+esc(v[0])+'</span></div>'
                    + '<ol class="track '+v[1]+'">'+steps+'</ol>'
                    + '<p class="vid-desc">'+esc(it.err || v[4] || s.status_description || 'Checking with BentBox…')+'</p>'
                    + '<div class="vid-foot"><span>'+action+'</span>'
                    + '<span class="vid-meta">'+checked+(checked ? ' · ' : '')+'<button class="linkbtn" data-check>Check now</button></span></div>'
                    + '</div>';
            }).join('');
        }

        async function check(it){
            try {
                const r = await fetch('bentbox_upload.php?action=status&ref=' + encodeURIComponent(it.ref));
                const j = await r.json();
                if (j.ok) { it.s = j; it.err = null; }
                else if (j.error === 'unknown_video' || j.error === 'not_connected') { it.gone = true; }
                else { it.err = 'Couldn’t check right now. We’ll try again shortly.'; }
            } catch(_) { it.err = 'Couldn’t reach the server. We’ll try again shortly.'; }
            it.checked = Date.now();
        }

        async function refresh(){
            clearTimeout(timer);
            const open = items.filter(it => !DONE.includes((it.s || {}).status) || !it.checked);
            await Promise.all(open.map(check));
            items = items.filter(it => !it.gone);   // drop videos from another connection or deleted refs
            save(); render(); schedule();
        }

        // Poll quickly while processing, slowly during review, never once everything is final.
        function schedule(){
            const st = items.map(it => (it.s || {}).status);
            if (st.every(s => DONE.includes(s))) return;
            if (Date.now() - startedAt > 30 * 60 * 1000) return;      // stop after 30 min; "Check now" restarts
            const busy = st.some(s => s === 'processing' || s === 'awaiting_upload' || !s);
            timer = setTimeout(() => { if (!document.hidden) refresh(); else schedule(); }, busy ? 10000 : 60000);
        }

        $('vids').addEventListener('click', e => {
            if (e.target.matches('[data-check]')) { startedAt = Date.now(); refresh(); }
        });
        document.addEventListener('visibilitychange', () => { if (!document.hidden && items.length) refresh(); });

        if (items.length) { render(); refresh(); }
        return { add, clear(){ items = []; save(); } };
    })();

    wireDisconnect();
    function wireDisconnect(){
        const d = $('disconnect'); if (!d) return;
        d.addEventListener('click', async e => {
            e.preventDefault();
            try { localStorage.removeItem('bb_uploads'); } catch(_){}
            await fetch('bentbox_upload.php?action=disconnect');
            location.href = 'bentbox_upload.php';
        });
    }
})();
</script>
</body>
</html>
