<?php
/**
 * flow.php — visual walkthrough of the integration demo, plus source downloads.
 * Styled to match the BentBox API docs. Lives inside the demo folder so it can
 * serve each file's raw source as a download (rather than executing the .php).
 */

// The files that make up the demo package (name => [description, tag]).
$FILES = [
    'config.php'   => ['Your settings: credentials, redirect URI, scopes, cookie key.', 'setup'],
    'bentbox.php'  => ['The library: HTTP, OAuth, encrypted-cookie token store, upload calls.', 'core'],
    'partials.php' => ['Shared, unbranded page chrome and CSS.', 'ui'],
    'index.php'    => ['Landing page: explains the flow, shows connection status.', 'overview'],
    'connect.php'  => ['Starts OAuth: sets the state cookie, redirects to BentBox.', 'step 1'],
    'callback.php' => ['Redirect target: verifies state, exchanges the code, stores tokens.', 'steps 2–3'],
    'upload.php'   => ['Upload UI + server actions (create → sign → PUT).', 'step 4'],
    'README.md'    => ['Full setup walkthrough and API reference.', 'docs'],
    'flow.php'     => ['This page.', 'docs'],
];

// ── Downloads (must run before any HTML) ─────────────────────────────────────
if (isset($_GET['download'])) {
    $f = basename($_GET['download']);
    if (isset($FILES[$f]) && is_file(__DIR__ . '/' . $f)) {
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename="' . $f . '"');
        header('Content-Length: ' . filesize(__DIR__ . '/' . $f));
        readfile(__DIR__ . '/' . $f);
        exit;
    }
    http_response_code(404);
    exit('Not found');
}
if (($_GET['zip'] ?? '') === '1' && class_exists('ZipArchive')) {
    $tmp = tempnam(sys_get_temp_dir(), 'bbz');
    $zip = new ZipArchive();
    $zip->open($tmp, ZipArchive::OVERWRITE);
    foreach (array_keys($FILES) as $f) {
        if (is_file(__DIR__ . '/' . $f)) $zip->addFile(__DIR__ . '/' . $f, 'bentbox-integration-demo/' . $f);
    }
    $zip->close();
    header('Content-Type: application/zip');
    header('Content-Disposition: attachment; filename="bentbox-integration-demo.zip"');
    header('Content-Length: ' . filesize($tmp));
    readfile($tmp);
    unlink($tmp);
    exit;
}
$hasZip = class_exists('ZipArchive');

// ── Sequence-diagram renderer ────────────────────────────────────────────────
function seq_svg(array $actors, array $steps): string {
    $gap = 250; $left = 100; $top = 96; $rowH = 46; $headY = 30; $headH = 38;
    $n = count($actors);
    $w = $left * 2 + ($n - 1) * $gap;
    $h = $top + count($steps) * $rowH + 30;
    $xi = fn($i) => $left + $i * $gap;

    $ink = '#0A0F1E'; $signal = '#2E54FF'; $muted = '#59617A'; $line = '#E7E9F0'; $text = '#0C1222';
    $s  = "<svg viewBox='0 0 $w $h' xmlns='http://www.w3.org/2000/svg' style='width:100%;height:auto;min-width:560px;'>";
    $s .= "<defs><marker id='ah' markerWidth='10' markerHeight='10' refX='7' refY='3' orient='auto'>"
        . "<path d='M0,0 L7,3 L0,6 Z' fill='$muted'/></marker></defs>";

    foreach ($actors as $i => $name) {
        $x = $xi($i);
        $fill   = $i == 0 ? '#FFFFFF' : ($i == 1 ? '#EEF2FF' : $ink);
        $stroke = $i == 0 ? $line    : ($i == 1 ? $signal   : $ink);
        $tcol   = $i == 2 ? '#FFFFFF' : ($i == 1 ? $signal   : $text);
        $bw = 156; $bx = $x - $bw / 2;
        $s .= "<rect x='$bx' y='$headY' width='$bw' height='$headH' rx='9' fill='$fill' stroke='$stroke'/>";
        $s .= "<text x='$x' y='" . ($headY + $headH / 2 + 4.5) . "' text-anchor='middle' "
            . "font-family=\"'Space Grotesk',sans-serif\" font-size='13' font-weight='600' fill='$tcol'>"
            . htmlspecialchars($name) . "</text>";
        $s .= "<line x1='$x' y1='" . ($headY + $headH) . "' x2='$x' y2='" . ($h - 16)
            . "' stroke='$line' stroke-dasharray='4 5'/>";
    }

    foreach ($steps as $k => [$from, $to, $num, $label]) {
        $y  = $top + ($k + 0.5) * $rowH;
        $x1 = $xi($from); $x2 = $xi($to);
        $dir = $x2 > $x1 ? 1 : -1;
        $ax1 = $x1 + $dir * 5; $ax2 = $x2 - $dir * 9;
        $mx = ($x1 + $x2) / 2;
        $s .= "<text x='$mx' y='" . ($y - 8) . "' text-anchor='middle' "
            . "font-family=\"'JetBrains Mono',monospace\" font-size='10.5' fill='$text'>"
            . htmlspecialchars($label) . "</text>";
        $s .= "<line x1='$ax1' y1='$y' x2='$ax2' y2='$y' stroke='$muted' stroke-width='1.5' marker-end='url(#ah)'/>";
        $s .= "<circle cx='$x1' cy='$y' r='9' fill='$signal'/>";
        $s .= "<text x='$x1' y='" . ($y + 3.6) . "' text-anchor='middle' "
            . "font-family=\"'JetBrains Mono',monospace\" font-size='10' font-weight='700' fill='#fff'>$num</text>";
    }
    return $s . "</svg>";
}

$actors = ["User's Browser", "Your Server", "BentBox API"];
$phaseA = [
    [0,1,'1','GET /connect.php'],
    [1,0,'2','302 → /oauth/authorize'],
    [0,2,'3','sign in + approve'],
    [2,0,'4','302 → /callback.php?code'],
    [0,1,'5','GET /callback.php?code'],
    [1,2,'6','POST /oauth/token'],
    [2,1,'7','access_token + connection_id'],
    [1,0,'8','Set bb_conn cookie (encrypted)'],
];
$phaseB = [
    [0,1,'1','POST ?action=prepare'],
    [1,2,'2','POST /v1/content/video'],
    [2,1,'3','video_id'],
    [1,2,'4','POST /v1/content/upload-url'],
    [2,1,'5','presigned URL'],
    [1,0,'6','presigned URL'],
    [0,2,'7','PUT file → presigned storage'],
];
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Integration Flow — BentBox API</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=Instrument+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
<style>
    :root{
        --paper:#FAFAFB; --paper-2:#FFFFFF; --ink:#0A0F1E; --text:#0C1222; --muted:#59617A;
        --line:#E7E9F0; --line-soft:#F0F1F5; --signal:#2E54FF; --violet:#8B5CF6; --cyan:#22D3EE;
        --green:#34D399; --amber:#E08A00; --red:#E11D48;
        --spectrum:linear-gradient(90deg,var(--signal),var(--violet),var(--cyan));
        --shadow-lg:0 40px 80px -30px rgba(10,15,30,0.45);
        --shadow-sm:0 1px 2px rgba(12,18,34,0.04),0 12px 34px -22px rgba(12,18,34,0.22);
    }
    *{margin:0;padding:0;box-sizing:border-box;}
    html{scroll-behavior:smooth;}
    body{font-family:'Instrument Sans',sans-serif;background:var(--paper);color:var(--text);
         line-height:1.6;-webkit-font-smoothing:antialiased;overflow-x:hidden;}
    a{color:inherit;}
    ::selection{background:rgba(46,84,255,0.16);}
    .glow{position:fixed;top:-10%;left:50%;transform:translateX(-50%);width:900px;height:420px;
          pointer-events:none;background:radial-gradient(60% 60% at 50% 0%,rgba(46,84,255,0.06),transparent 70%);z-index:-1;}

    header{position:sticky;top:0;z-index:100;border-bottom:1px solid var(--line);
           background:rgba(250,250,251,0.72);backdrop-filter:blur(14px);}
    .header-inner{max-width:1200px;margin:0 auto;padding:0 5%;height:68px;display:flex;
                  align-items:center;justify-content:space-between;}
    .brand{display:flex;align-items:center;gap:10px;}
    .brand img{height:26px;display:block;}
    .tag{font-family:'JetBrains Mono',monospace;font-size:0.66rem;letter-spacing:0.08em;color:var(--muted);
         border:1px solid var(--line);padding:2px 7px;border-radius:5px;text-transform:uppercase;}
    nav{display:flex;align-items:center;gap:26px;}
    nav a.link{color:var(--muted);text-decoration:none;font-size:0.9rem;font-weight:500;transition:color .18s;}
    nav a.link:hover{color:var(--text);}
    nav a.site{text-decoration:none;font-size:0.9rem;font-weight:600;color:var(--text);padding:9px 16px;
               border:1px solid var(--line);border-radius:9px;background:var(--paper-2);transition:.18s;}
    nav a.site:hover{border-color:#cfd3e0;box-shadow:var(--shadow-sm);}

    .hero{padding:70px 5% 30px;max-width:1200px;margin:0 auto;text-align:center;}
    .hero h1{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:clamp(2.2rem,4.5vw,3.1rem);
             line-height:1.08;letter-spacing:-0.025em;margin-bottom:1rem;}
    .hero h1 span{color:var(--signal);}
    .hero p{font-size:1.12rem;color:var(--muted);max-width:680px;margin:0 auto;}

    .content{max-width:1200px;margin:0 auto;padding:44px 5% 90px;display:grid;
             grid-template-columns:236px 1fr;gap:64px;}
    .sidebar{position:sticky;top:96px;height:fit-content;}
    .sidebar h3{font-family:'JetBrains Mono',monospace;font-size:0.72rem;text-transform:uppercase;
                letter-spacing:0.1em;color:var(--muted);margin-bottom:0.9rem;padding-left:16px;}
    .sidebar ul{list-style:none;}
    .sidebar li a{position:relative;display:block;padding:6px 0 6px 16px;color:var(--muted);
                  text-decoration:none;font-size:0.9rem;transition:color .16s;}
    .sidebar li a::before{content:"";position:absolute;left:0;top:5px;bottom:5px;width:2px;border-radius:2px;
                          background:transparent;transition:background .18s;}
    .sidebar li a:hover{color:var(--text);}
    .sidebar li a:hover::before{background:var(--line);}
    .sidebar li a.active{color:var(--text);font-weight:600;}
    .sidebar li a.active::before{background:var(--spectrum);}

    .main-content section{margin-bottom:70px;scroll-margin-top:96px;}
    .main-content h2{font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:1.7rem;
                     letter-spacing:-0.02em;margin-bottom:1rem;padding-bottom:0.6rem;
                     border-bottom:1px solid var(--line);position:relative;}
    .main-content h2::after{content:"";position:absolute;left:0;bottom:-1px;width:52px;height:2px;
                            border-radius:2px;background:var(--spectrum);}
    .main-content h3{font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:1.28rem;
                     letter-spacing:-0.01em;margin:2rem 0 0.9rem;}
    .main-content p{color:var(--muted);margin-bottom:1rem;line-height:1.72;}
    .main-content strong{color:var(--text);font-weight:600;}
    .main-content code{font-family:'JetBrains Mono',monospace;font-size:0.85em;background:#EEF1F7;
                       border:1px solid #E2E6F0;color:#33405C;padding:0.1em 0.42em;border-radius:6px;}

    .seq{background:var(--paper-2);border:1px solid var(--line);border-radius:16px;padding:1.6rem;
         margin:1.6rem 0;box-shadow:var(--shadow-sm);overflow-x:auto;}
    .seq-cap{font-family:'JetBrains Mono',monospace;font-size:0.72rem;text-transform:uppercase;
             letter-spacing:0.08em;color:var(--muted);margin-bottom:1rem;}

    .flow-diagram{background:var(--paper-2);border:1px solid var(--line);border-radius:16px;padding:1.9rem;margin:2rem 0;}
    .flow-step{display:flex;align-items:flex-start;gap:1rem;margin-bottom:1.4rem;padding-bottom:1.4rem;
               border-bottom:1px solid var(--line-soft);}
    .flow-step:last-child{border-bottom:none;margin-bottom:0;padding-bottom:0;}
    .flow-number{flex-shrink:0;width:30px;height:30px;border-radius:9px;background:var(--ink);color:#fff;
                 display:flex;align-items:center;justify-content:center;font-family:'JetBrains Mono',monospace;
                 font-weight:600;font-size:0.85rem;}
    .flow-content h4{margin:0 0 0.35rem;font-size:1.05rem;}
    .flow-content p{margin:0;color:var(--muted);}

    .badge{display:inline-block;padding:3px 9px;border-radius:6px;font-family:'JetBrains Mono',monospace;
           font-size:0.7rem;font-weight:700;letter-spacing:0.04em;text-transform:uppercase;}
    .badge.post{background:#E7ECFF;color:#2440CC;} .badge.get{background:#E3F7EC;color:#0B7A44;}
    .badge.put{background:#F3EBFF;color:#6B21A8;}

    .info-box{background:#F4F6FB;border:1px solid var(--line);border-left:3px solid var(--signal);
              padding:1.25rem 1.4rem;border-radius:12px;margin:1.5rem 0;}
    .info-box.warning{border-left-color:var(--amber);background:#FFF8EC;border-color:#FBE4BE;}
    .info-box h4{margin:0 0 0.4rem;font-size:0.98rem;font-weight:700;}
    .info-box p{margin:0;color:var(--muted);font-size:0.92rem;}

    .terminal{background:var(--ink);border:1px solid rgba(255,255,255,0.08);border-radius:14px;
              padding:16px 20px;margin:1.4rem 0;font-family:'JetBrains Mono',monospace;font-size:0.85rem;
              color:#cdd4e6;box-shadow:var(--shadow-lg);overflow-x:auto;}
    .terminal .c{color:#5F6A86;}

    .dl-all{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;
            background:var(--paper-2);border:1px solid var(--line);border-radius:14px;padding:1.1rem 1.4rem;margin:1.5rem 0;}
    .dl-all p{margin:0;color:var(--muted);font-size:0.9rem;}
    .btn{display:inline-flex;align-items:center;gap:8px;font-family:'Instrument Sans',sans-serif;
         font-weight:600;font-size:0.88rem;text-decoration:none;padding:10px 18px;border-radius:10px;
         background:var(--signal);color:#fff;border:none;cursor:pointer;transition:.15s;}
    .btn:hover{background:#1f42e6;}
    .btn.small{padding:7px 13px;font-size:0.82rem;background:var(--paper-2);color:var(--text);border:1px solid var(--line);}
    .btn.small:hover{border-color:#cfd3e0;box-shadow:var(--shadow-sm);}

    .file-list{border:1px solid var(--line);border-radius:14px;overflow:hidden;margin:1.5rem 0;}
    .file-row{display:flex;align-items:center;gap:14px;padding:14px 18px;border-bottom:1px solid var(--line-soft);}
    .file-row:last-child{border-bottom:none;}
    .file-row:hover{background:#FAFBFE;}
    .file-meta{flex:1;min-width:0;}
    .file-name{font-family:'JetBrains Mono',monospace;font-size:0.9rem;font-weight:600;color:var(--text);}
    .file-name .ftag{font-size:0.62rem;color:var(--muted);border:1px solid var(--line);border-radius:20px;
                     padding:1px 8px;margin-left:8px;text-transform:uppercase;letter-spacing:0.04em;vertical-align:1px;}
    .file-desc{font-size:0.85rem;color:var(--muted);margin-top:2px;}

    footer{text-align:center;padding:46px 5%;border-top:1px solid var(--line);color:var(--muted);font-size:0.9rem;}
    footer a{color:var(--signal);text-decoration:none;}

    @media (max-width:968px){
        .content{grid-template-columns:1fr;gap:8px;}
        .sidebar{position:relative;top:0;margin-bottom:24px;border:1px solid var(--line);
                 border-radius:12px;padding:16px;background:var(--paper-2);}
    }
    @media (max-width:640px){ nav a.link{display:none;} .hero{padding:48px 5% 20px;} }
    @media (prefers-reduced-motion:reduce){ *{transition:none!important;scroll-behavior:auto!important;} }
</style>
</head>
<body>
<div class="glow"></div>

<header>
    <div class="header-inner">
        <div class="brand">
            <img src="https://bentapi-public.s3.us-east-1.amazonaws.com/images/Logo_BentAPI_2026.png" alt="BentAPI">
            <span class="tag">Flow</span>
        </div>
        <nav>
            <a class="link" href="#overview">Overview</a>
            <a class="link" href="#oauth">OAuth</a>
            <a class="link" href="#upload">Upload</a>
            <a class="link" href="#files">Files</a>
            <a class="site" href="docs.php">Full docs</a>
        </nav>
    </div>
</header>

<section class="hero">
    <h1>Integration <span>Flow</span></h1>
    <p>A visual walkthrough of the reference implementation — how a third-party site connects a BentBox account and uploads a video — with the source files to download.</p>
</section>

<div class="content">
    <aside class="sidebar">
        <h3>On this page</h3>
        <ul>
            <li><a href="#overview">Overview</a></li>
            <li><a href="#oauth">Phase A · Connect</a></li>
            <li><a href="#upload">Phase B · Upload</a></li>
            <li><a href="#files">The files</a></li>
            <li><a href="#setup">Setup</a></li>
        </ul>
    </aside>

    <main class="main-content">
        <section id="overview">
            <h2>Overview</h2>
            <p>The demo is a small, unbranded PHP app that a partner drops into their own site. Everything runs server-side: the browser never sees a token. The whole integration is four steps.</p>
            <div class="flow-diagram">
                <div class="flow-step"><div class="flow-number">1</div><div class="flow-content">
                    <h4>Connect</h4><p>Send the user to BentBox's consent screen with your <code>client_id</code>, requested scopes, and a CSRF <code>state</code>.</p></div></div>
                <div class="flow-step"><div class="flow-number">2</div><div class="flow-content">
                    <h4>Approve</h4><p>The user signs in to BentBox and approves. BentBox redirects back to your <code>redirect_uri</code> with a short-lived <code>code</code>.</p></div></div>
                <div class="flow-step"><div class="flow-number">3</div><div class="flow-content">
                    <h4>Exchange</h4><p>Your server swaps the <code>code</code> plus your <code>client_secret</code> for an <code>access_token</code> and <code>connection_id</code>, and stores them encrypted.</p></div></div>
                <div class="flow-step"><div class="flow-number">4</div><div class="flow-content">
                    <h4>Upload</h4><p>Create video metadata, request a presigned URL, and upload the file bytes.</p></div></div>
            </div>
        </section>

        <section id="oauth">
            <h2>Phase A · Connecting an account</h2>
            <p>The OAuth handshake. Steps 1–5 are browser redirects; the sensitive exchange in step 6 happens server-to-server, so your <code>client_secret</code> is never exposed.</p>
            <div class="seq">
                <div class="seq-cap">Sequence · OAuth authorization code flow</div>
                <?= seq_svg($actors, $phaseA) ?>
            </div>
            <p><span class="badge get">GET</span> <code>connect.php</code> kicks it off and <span class="badge get">GET</span> <code>callback.php</code> receives the return; the token exchange is a <span class="badge post">POST</span> to <code>/oauth/token</code>.</p>
            <div class="info-box warning">
                <h4>⚠ redirect_uri must match exactly</h4>
                <p>The <code>redirect_uri</code> you send must be registered on your BentBox account character-for-character — same scheme, host, and path, no trailing slash. A mismatch returns <code>invalid_client</code>.</p>
            </div>
        </section>

        <section id="upload">
            <h2>Phase B · Uploading a video</h2>
            <p>With the stored token, uploading is two API calls plus the file transfer. The token rides in both the request body and an <code>Authorization: Bearer</code> header.</p>
            <div class="seq">
                <div class="seq-cap">Sequence · Content upload</div>
                <?= seq_svg($actors, $phaseB) ?>
            </div>
            <p><span class="badge post">POST</span> <code>/v1/content/video</code> reserves a <code>video_id</code>, <span class="badge post">POST</span> <code>/v1/content/upload-url</code> returns a presigned URL, and a <span class="badge put">PUT</span> sends the bytes. In proxy mode the <span class="badge put">PUT</span> runs on your server instead of the browser.</p>
            <p>The <code>POST /v1/content/video</code> body also carries the consent attestation — <code>sole_performer: true</code> or a ProntoID <code>release_form_id</code> covering additional performer(s) — plus an optional <code>publish_on_ready</code> flag (default <code>true</code>) controlling whether BentBox publishes the video once processing and approval complete. These are fields on the existing call, so the sequence above doesn't change.</p>
        </section>

        <section id="files">
            <h2>The files</h2>
            <p>Nine files, no dependencies. Download individually below, or grab everything at once.</p>
            <div class="dl-all">
                <p><strong>Whole package</strong> — ready to drop into your web root.</p>
                <?php if ($hasZip): ?>
                    <a class="btn" href="?zip=1">↓ Download all (.zip)</a>
                <?php else: ?>
                    <span style="color:var(--muted);font-size:0.85rem;">Zip unavailable on this server — download files individually.</span>
                <?php endif; ?>
            </div>
            <div class="file-list">
                <?php foreach ($FILES as $name => [$desc, $tag]): ?>
                    <div class="file-row">
                        <div class="file-meta">
                            <div class="file-name"><?= htmlspecialchars($name) ?><span class="ftag"><?= htmlspecialchars($tag) ?></span></div>
                            <div class="file-desc"><?= htmlspecialchars($desc) ?></div>
                        </div>
                        <a class="btn small" href="?download=<?= urlencode($name) ?>">↓ Download</a>
                    </div>
                <?php endforeach; ?>
            </div>
        </section>

        <section id="setup">
            <h2>Setup</h2>
            <div class="flow-diagram">
                <div class="flow-step"><div class="flow-number">1</div><div class="flow-content">
                    <h4>Copy the folder into your web root</h4><p>Serve it over HTTPS — OAuth and secure cookies require it.</p></div></div>
                <div class="flow-step"><div class="flow-number">2</div><div class="flow-content">
                    <h4>Register your redirect URI</h4><p>Add the exact URL of <code>callback.php</code> to your BentBox <code>redirect_uris</code>.</p></div></div>
                <div class="flow-step"><div class="flow-number">3</div><div class="flow-content">
                    <h4>Generate a cookie key</h4><p>Use the same value on every server.</p></div></div>
                <div class="flow-step"><div class="flow-number">4</div><div class="flow-content">
                    <h4>Fill in <code>config.php</code></h4><p>Credentials, redirect URI, scopes, and the key — then open <code>index.php</code>.</p></div></div>
            </div>
            <div class="terminal">
                <span class="c"># generate the 32-byte cookie key</span><br>
                php -r "echo base64_encode(random_bytes(32)).PHP_EOL;"
            </div>
            <div class="info-box">
                <h4>🔑 One key, every instance</h4>
                <p>The cookie key encrypts the stored tokens. Behind a load balancer, all nodes must share the identical key, or a connection written by one node won't decrypt on another.</p>
            </div>
        </section>
    </main>
</div>

<footer>
    Reference implementation · <a href="docs.php">Full API docs</a> · <a href="README.md">README</a>
</footer>

<script>
// Lightweight scrollspy for the sidebar.
const links = [...document.querySelectorAll('.sidebar a')];
const map = new Map(links.map(a => [a.getAttribute('href').slice(1), a]));
const obs = new IntersectionObserver(es => {
    es.forEach(e => { if (e.isIntersecting) {
        links.forEach(l => l.classList.remove('active'));
        map.get(e.target.id)?.classList.add('active');
    }});
}, { rootMargin: '-40% 0px -55% 0px' });
document.querySelectorAll('main section[id]').forEach(s => obs.observe(s));
</script>
</body>
</html>
