# Barcode.Ruby gem - Easy 2 Steps
#
# QR code generation using barcode_pao (FFI) or barcode_pao_wasm (WASM).
#
#   bundle exec ruby app.rb
#   -> http://localhost:5742

require "sinatra"
require "json"

# Load FFI gem
$LOAD_PATH.unshift(File.join(__dir__, "..", "barcode_pao_ffi", "lib"))
require "barcode_pao"

# Load WASM gem
$LOAD_PATH.unshift(File.join(__dir__, "..", "barcode_pao_wasm", "lib"))
require "barcode_pao_wasm"

set :port, 5742
set :bind, "0.0.0.0"

post "/draw-base64" do
  content_type :json
  code = params[:code]
  code = "https://www.pao.ac/" if code.nil? || code.empty?
  engine = params[:engine] || "ffi"
  begin
    if engine == "wasm"
      qr = BarcodePaoWasm::QRCode.new
      qr.set_output_format("png")
      result = qr.draw(code, 300)
      { ok: true, base64: result }.to_json
    else
      qr = BarcodePao::QRCode.new("png")
      qr.draw(code, 300)
      { ok: true, base64: qr.get_image_base64 }.to_json
    end
  rescue => e
    { ok: false, error: e.message }.to_json
  end
end

post "/draw-svg" do
  content_type :json
  code = params[:code]
  code = "https://www.pao.ac/" if code.nil? || code.empty?
  engine = params[:engine] || "ffi"
  begin
    if engine == "wasm"
      qr = BarcodePaoWasm::QRCode.new
      qr.set_output_format("svg")
      result = qr.draw(code, 300)
      { ok: true, svg: result }.to_json
    else
      qr = BarcodePao::QRCode.new("svg")
      qr.draw(code, 300)
      { ok: true, svg: qr.get_svg }.to_json
    end
  rescue => e
    { ok: false, error: e.message }.to_json
  end
end

get "/" do
  erb :index
end

__END__

@@ index
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Barcode.Ruby gem &#x2014; Easy 2 Steps</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Segoe UI',system-ui,sans-serif;background:#f0f4f8;color:#1e293b}
header{background:linear-gradient(135deg,#CC342D,#8B0000);color:#fff;padding:24px 32px;position:relative;overflow:hidden}
header h1{font-size:22px;font-weight:700;letter-spacing:.5px}
header p{font-size:13px;opacity:.85;margin-top:4px}
.badge{display:inline-block;background:rgba(255,255,255,.2);border-radius:12px;padding:3px 12px;font-size:11px;margin-top:6px}
.container{max-width:960px;margin:0 auto;padding:24px}
.steps{display:flex;gap:16px;margin-bottom:20px}
.step{flex:1;background:#fff;border-radius:10px;padding:16px;box-shadow:0 1px 3px rgba(0,0,0,.08);border-left:4px solid #CC342D}
.step-num{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;background:#CC342D;color:#fff;font-size:13px;font-weight:700;margin-right:8px}
.step-title{font-weight:600;font-size:14px;color:#CC342D}
.step-desc{font-size:12px;color:#64748b;margin-top:4px}
.main-row{display:flex;gap:20px}
.left-panel{flex:1;min-width:0}
.right-panel{width:360px;flex-shrink:0}
.card{background:#fff;border-radius:10px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,.08);margin-bottom:16px}
label{display:block;font-size:13px;font-weight:600;color:#475569;margin-bottom:6px}
input[type=text]{width:100%;padding:10px 14px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:14px;transition:border .2s}
input[type=text]:focus{outline:none;border-color:#CC342D;box-shadow:0 0 0 3px rgba(204,52,45,.15)}
.engine-row{display:flex;gap:12px;margin:12px 0;align-items:center}
.engine-row label{display:inline;font-size:13px;font-weight:500;margin-bottom:0;cursor:pointer}
.engine-row input[type=radio]{margin-right:4px}
.mode-row{display:flex;gap:8px;margin:16px 0 12px}
.mode-btn{flex:1;padding:10px 8px;border:1.5px solid #e2e8f0;border-radius:8px;background:#fff;cursor:pointer;font-size:12px;font-weight:600;text-align:center;transition:all .15s;color:#64748b}
.mode-btn.active{border-color:#CC342D;background:#fef2f2;color:#CC342D}
.mode-btn:hover{border-color:#f87171}
.code-panel{background:#1e293b;border-radius:8px;padding:14px 16px;margin-top:12px}
.code-panel pre{font-family:'Consolas','Courier New',monospace;font-size:12px;color:#e2e8f0;line-height:1.7;white-space:pre-wrap;overflow-x:auto}
.code-panel .kw{color:#CC342D}
.code-panel .fn{color:#38bdf8}
.code-panel .str{color:#86efac}
.code-panel .cm{color:#64748b}
.preview-card{background:#fff;border-radius:10px;box-shadow:0 1px 3px rgba(0,0,0,.08);position:sticky;top:20px}
.preview-header{padding:14px 16px;border-bottom:1px solid #f1f5f9;display:flex;align-items:center;justify-content:space-between}
.preview-header h3{font-size:14px;color:#CC342D}
.preview-body{padding:20px;display:flex;align-items:center;justify-content:center;min-height:360px}
.preview-body img{max-width:100%;image-rendering:pixelated}
.preview-body .svg-container{max-width:100%;overflow:auto}
.preview-body .svg-container svg{max-width:100%;height:auto}
.empty-preview{color:#94a3b8;font-size:13px;text-align:center}
footer{text-align:center;padding:20px;font-size:12px;color:#94a3b8}
@media(max-width:768px){.main-row{flex-direction:column}.right-panel{width:100%}.steps{flex-direction:column}}
</style>
</head>
<body>
<header>
  <h1>Barcode.Ruby gem &#x2014; Easy 2 Steps</h1>
  <p>Native FFI / WASM barcode library &#x2014; Sinatra REST API</p>
  <span class="badge">Ruby + Sinatra + FFI/WASM</span>
</header>

<div class="container">
  <div class="steps">
    <div class="step">
      <span class="step-num">1</span>
      <span class="step-title">Create barcode</span>
      <div class="step-desc">qr = BarcodePao::QRCode.new("png")</div>
    </div>
    <div class="step">
      <span class="step-num">2</span>
      <span class="step-title">Draw!</span>
      <div class="step-desc">qr.draw(code, 300)</div>
    </div>
  </div>

  <div class="main-row">
    <div class="left-panel">
      <div class="card">
        <label for="code-input">Data</label>
        <input type="text" id="code-input" value="https://www.pao.ac/" placeholder="Enter data...">

        <div class="engine-row">
          <label><input type="radio" name="engine" value="ffi" checked onchange="onEngineChange()"> Native (FFI)</label>
          <label><input type="radio" name="engine" value="wasm" onchange="onEngineChange()"> WASM</label>
        </div>

        <div class="mode-row">
          <button class="mode-btn active" data-mode="base64" onclick="setMode('base64')">PNG (Base64)</button>
          <button class="mode-btn" data-mode="svg" onclick="setMode('svg')">SVG</button>
        </div>

        <div class="code-panel" id="code-panel-ffi-base64">
<pre><span class="cm"># Step 1: Create barcode (FFI)</span>
qr = <span class="kw">BarcodePao</span>::<span class="fn">QRCode</span>.new(<span class="str">"png"</span>)
<span class="cm"># Step 2: Draw!</span>
qr.<span class="fn">draw</span>(code, <span class="str">300</span>)
base64 = qr.<span class="fn">get_image_base64</span></pre>
        </div>
        <div class="code-panel" id="code-panel-ffi-svg" style="display:none">
<pre><span class="cm"># Step 1: Create barcode (FFI, SVG)</span>
qr = <span class="kw">BarcodePao</span>::<span class="fn">QRCode</span>.new(<span class="str">"svg"</span>)
<span class="cm"># Step 2: Draw!</span>
qr.<span class="fn">draw</span>(code, <span class="str">300</span>)
svg = qr.<span class="fn">get_svg</span></pre>
        </div>
        <div class="code-panel" id="code-panel-wasm-base64" style="display:none">
<pre><span class="cm"># Step 1: Create barcode (WASM)</span>
qr = <span class="kw">BarcodePaoWasm</span>::<span class="fn">QRCode</span>.new
qr.<span class="fn">set_output_format</span>(<span class="str">"png"</span>)
<span class="cm"># Step 2: Draw!</span>
result = qr.<span class="fn">draw</span>(code, <span class="str">300</span>)</pre>
        </div>
        <div class="code-panel" id="code-panel-wasm-svg" style="display:none">
<pre><span class="cm"># Step 1: Create barcode (WASM, SVG)</span>
qr = <span class="kw">BarcodePaoWasm</span>::<span class="fn">QRCode</span>.new
qr.<span class="fn">set_output_format</span>(<span class="str">"svg"</span>)
<span class="cm"># Step 2: Draw!</span>
result = qr.<span class="fn">draw</span>(code, <span class="str">300</span>)</pre>
        </div>
      </div>
    </div>

    <div class="right-panel">
      <div class="preview-card">
        <div class="preview-header">
          <h3>Preview</h3>
          <span id="mode-badge" style="font-size:11px;color:#64748b">PNG (FFI)</span>
        </div>
        <div class="preview-body" id="preview-body">
          <div class="empty-preview">Enter data and it will render automatically</div>
        </div>
      </div>
    </div>
  </div>
</div>

<footer>Barcode.Ruby gem &#x2014; Native FFI / WASM barcode library &#x2014; pao.ac</footer>

<script>
let currentMode = 'base64';
let currentEngine = 'ffi';
let debounceTimer = null;

const codeInput = document.getElementById('code-input');
const previewBody = document.getElementById('preview-body');
const modeBadge = document.getElementById('mode-badge');

function updateCodePanels() {
  ['ffi-base64','ffi-svg','wasm-base64','wasm-svg'].forEach(id => {
    document.getElementById('code-panel-' + id).style.display = 'none';
  });
  document.getElementById('code-panel-' + currentEngine + '-' + currentMode).style.display = '';
}

function updateBadge() {
  const modeStr = currentMode === 'base64' ? 'PNG' : 'SVG';
  const engineStr = currentEngine === 'ffi' ? 'FFI' : 'WASM';
  modeBadge.textContent = modeStr + ' (' + engineStr + ')';
}

function onEngineChange() {
  currentEngine = document.querySelector('input[name=engine]:checked').value;
  updateCodePanels();
  updateBadge();
  generate();
}

function setMode(mode) {
  currentMode = mode;
  document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
  document.querySelector(`[data-mode="${mode}"]`).classList.add('active');
  updateCodePanels();
  updateBadge();
  generate();
}

function generate() {
  const code = codeInput.value.trim();
  if (!code) {
    previewBody.innerHTML = '<div class="empty-preview">Enter data to generate</div>';
    return;
  }
  const fd = new FormData();
  fd.append('code', code);
  fd.append('engine', currentEngine);

  const endpoint = currentMode === 'svg' ? '/draw-svg' : '/draw-base64';
  fetch(endpoint, {method: 'POST', body: fd})
    .then(r => r.json())
    .then(data => {
      if (!data.ok) {
        previewBody.innerHTML = `<div class="empty-preview" style="color:#ef4444">${data.error}</div>`;
        return;
      }
      if (currentMode === 'svg') {
        previewBody.innerHTML = `<div class="svg-container">${data.svg}</div>`;
      } else {
        previewBody.innerHTML = `<img src="${data.base64}" alt="QR Code">`;
      }
    })
    .catch(err => {
      previewBody.innerHTML = `<div class="empty-preview" style="color:#ef4444">${err}</div>`;
    });
}

codeInput.addEventListener('input', () => {
  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(generate, 400);
});

generate();
</script>
</body>
</html>
