# Barcode.Ruby gem - All-in-One
#
# Full 18-type barcode demo using barcode_pao (FFI) or barcode_pao_wasm (WASM).
#
#   bundle exec ruby app.rb
#   -> http://localhost:5743

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, 5743
set :bind, "0.0.0.0"

BARCODE_TYPES = [
  { id: "QR", label: "QR Code", group: "2D Barcode", dim: "2d", default: "https://www.pao.ac/", w: 200, h: 200 },
  { id: "DataMatrix", label: "DataMatrix", group: "2D Barcode", dim: "2d", default: "Hello DataMatrix", w: 200, h: 200 },
  { id: "PDF417", label: "PDF417", group: "2D Barcode", dim: "2d", default: "Hello PDF417", w: 200, h: 100 },
  { id: "GS1_128", label: "GS1-128", group: "Special", dim: "1d", default: "[01]04912345123459", w: 400, h: 100 },
  { id: "YubinCustomer", label: "Yubin Customer", group: "Special", dim: "postal", default: "1060032", w: 400, h: 60 },
  { id: "Code128", label: "Code 128", group: "1D Barcode", dim: "1d", default: "Hello-2026", w: 400, h: 100 },
  { id: "Code39", label: "Code 39", group: "1D Barcode", dim: "1d", default: "HELLO-123", w: 400, h: 100 },
  { id: "Code93", label: "Code 93", group: "1D Barcode", dim: "1d", default: "CODE93", w: 400, h: 100 },
  { id: "NW7", label: "NW-7 / Codabar", group: "1D Barcode", dim: "1d", default: "A123456B", w: 400, h: 100 },
  { id: "ITF", label: "ITF", group: "1D Barcode", dim: "1d", default: "123456", w: 400, h: 100 },
  { id: "Matrix2of5", label: "Matrix 2 of 5", group: "1D Barcode", dim: "1d", default: "1234", w: 400, h: 100 },
  { id: "NEC2of5", label: "NEC 2 of 5", group: "1D Barcode", dim: "1d", default: "1234", w: 400, h: 100 },
  { id: "GS1DataBar14", label: "GS1 DataBar 14", group: "GS1 DataBar", dim: "1d", default: "0123456789012", w: 300, h: 80 },
  { id: "GS1DataBarLimited", label: "GS1 DataBar Limited", group: "GS1 DataBar", dim: "1d", default: "0123456789012", w: 300, h: 80 },
  { id: "GS1DataBarExpanded", label: "GS1 DataBar Expanded", group: "GS1 DataBar", dim: "1d", default: "[01]90012345678908", w: 400, h: 80 },
  { id: "JAN13", label: "JAN-13 / EAN-13", group: "JAN / UPC", dim: "1d", default: "490123456789", w: 300, h: 100 },
  { id: "JAN8", label: "JAN-8 / EAN-8", group: "JAN / UPC", dim: "1d", default: "1234567", w: 250, h: 100 },
  { id: "UPCA", label: "UPC-A", group: "JAN / UPC", dim: "1d", default: "01234567890", w: 300, h: 100 },
  { id: "UPCE", label: "UPC-E", group: "JAN / UPC", dim: "1d", default: "0123456", w: 250, h: 100 },
].freeze

BARCODE_MAP = BARCODE_TYPES.each_with_object({}) { |bt, h| h[bt[:id]] = bt }.freeze

# FFI class mapping
FFI_CLASSES = {
  "QR" => BarcodePao::QRCode, "DataMatrix" => BarcodePao::DataMatrix,
  "PDF417" => BarcodePao::PDF417, "Code128" => BarcodePao::Code128,
  "Code39" => BarcodePao::Code39, "Code93" => BarcodePao::Code93,
  "NW7" => BarcodePao::NW7, "ITF" => BarcodePao::ITF,
  "Matrix2of5" => BarcodePao::Matrix2of5, "NEC2of5" => BarcodePao::NEC2of5,
  "JAN13" => BarcodePao::JAN13, "JAN8" => BarcodePao::JAN8,
  "UPCA" => BarcodePao::UPCA, "UPCE" => BarcodePao::UPCE,
  "GS1_128" => BarcodePao::GS1128,
  "GS1DataBar14" => BarcodePao::GS1DataBar14,
  "GS1DataBarLimited" => BarcodePao::GS1DataBarLimited,
  "GS1DataBarExpanded" => BarcodePao::GS1DataBarExpanded,
  "YubinCustomer" => BarcodePao::YubinCustomer,
}.freeze

# WASM class mapping
WASM_CLASSES = {
  "QR" => BarcodePaoWasm::QRCode, "DataMatrix" => BarcodePaoWasm::DataMatrix,
  "PDF417" => BarcodePaoWasm::PDF417, "Code128" => BarcodePaoWasm::Code128,
  "Code39" => BarcodePaoWasm::Code39, "Code93" => BarcodePaoWasm::Code93,
  "NW7" => BarcodePaoWasm::NW7, "ITF" => BarcodePaoWasm::ITF,
  "Matrix2of5" => BarcodePaoWasm::Matrix2of5, "NEC2of5" => BarcodePaoWasm::NEC2of5,
  "JAN13" => BarcodePaoWasm::JAN13, "JAN8" => BarcodePaoWasm::JAN8,
  "UPCA" => BarcodePaoWasm::UPCA, "UPCE" => BarcodePaoWasm::UPCE,
  "GS1_128" => BarcodePaoWasm::GS1128,
  "GS1DataBar14" => BarcodePaoWasm::GS1DataBar14,
  "GS1DataBarLimited" => BarcodePaoWasm::GS1DataBarLimited,
  "GS1DataBarExpanded" => BarcodePaoWasm::GS1DataBarExpanded,
  "YubinCustomer" => BarcodePaoWasm::YubinCustomer,
}.freeze

def draw_ffi(type_id, code, format, width, height, params)
  klass = FFI_CLASSES[type_id] || raise("Unknown type: #{type_id}")
  bc = klass.new(format)

  # Apply type-specific settings
  case type_id
  when "QR"
    ecc_map = { "0" => "L", "1" => "M", "2" => "Q", "3" => "H" }
    ecc = params[:qr_error_level] || "1"
    bc.set_error_correction_level(ecc_map[ecc] || "M")
    ver = (params[:qr_version] || "0").to_i
    bc.set_version(ver) if ver > 0
    bc.draw(code, width)
  when "DataMatrix"
    bc.draw(code, width)
  when "PDF417"
    el = (params[:pdf417_error_level] || "-1").to_i
    bc.set_error_level(el) if el >= 0 && el <= 8
    cols = (params[:pdf417_cols] || "0").to_i
    bc.set_columns(cols) if cols > 0
    bc.draw(code, width, height)
  when "YubinCustomer"
    bc.draw(code, height)
  when "GS1DataBar14"
    st = params[:databar14_type]
    bc.set_symbol_type(st) if st && st != "Omnidirectional"
    bc.draw(code, width, height)
  when "GS1DataBarExpanded"
    st = params[:databar_expanded_type]
    bc.set_symbol_type(st) if st && st != "Unstacked"
    cols = (params[:databar_expanded_cols] || "2").to_i
    bc.set_no_of_columns(cols)
    bc.draw(code, width, height)
  else
    # Standard 1D types
    if bc.respond_to?(:set_show_text)
      bc.set_show_text(params[:show_text] != "0")
      bc.set_text_even_spacing(params[:even_spacing] != "0")
    end
    bc.draw(code, width, height)
  end
  bc
end

def draw_wasm(type_id, code, format, width, height, params)
  klass = WASM_CLASSES[type_id] || raise("Unknown type: #{type_id}")
  bc = klass.new
  bc.set_output_format(format)

  # Apply type-specific settings
  case type_id
  when "QR"
    ecc_map = { "0" => "L", "1" => "M", "2" => "Q", "3" => "H" }
    ecc = params[:qr_error_level] || "1"
    bc.set_error_correction_level(ecc_map[ecc] || "M")
    ver = (params[:qr_version] || "0").to_i
    bc.set_version(ver) if ver > 0
    bc.draw(code, width)
  when "DataMatrix"
    bc.draw(code, width)
  when "PDF417"
    el = (params[:pdf417_error_level] || "-1").to_i
    bc.set_error_level(el) if el >= 0 && el <= 8
    cols = (params[:pdf417_cols] || "0").to_i
    bc.set_columns(cols) if cols > 0
    bc.draw(code, width, height)
  when "YubinCustomer"
    bc.draw(code, height)
  when "GS1DataBar14"
    st = params[:databar14_type]
    bc.set_symbol_type(st) if st && st != "Omnidirectional"
    bc.draw(code, width, height)
  when "GS1DataBarExpanded"
    st = params[:databar_expanded_type]
    bc.set_symbol_type(st) if st && st != "Unstacked"
    cols = (params[:databar_expanded_cols] || "2").to_i
    bc.set_no_of_columns(cols)
    bc.draw(code, width, height)
  else
    # Standard 1D types
    if bc.respond_to?(:set_show_text)
      bc.set_show_text(params[:show_text] != "0")
      bc.set_text_even_spacing(params[:even_spacing] != "0")
    end
    bc.draw(code, width, height)
  end
end

get "/" do
  @barcode_types_json = BARCODE_TYPES.to_json
  erb :index
end

post "/draw-base64" do
  content_type :json
  type_id = params[:type] || "QR"
  info = BARCODE_MAP[type_id] || BARCODE_MAP["QR"]
  type_id = info[:id]
  code = params[:code]
  code = info[:default] if code.nil? || code.empty?
  width = (params[:width] || info[:w]).to_i
  height = (params[:height] || info[:h]).to_i
  engine = params[:engine] || "ffi"
  begin
    if engine == "wasm"
      result = draw_wasm(type_id, code, "png", width, height, params)
      { ok: true, base64: result }.to_json
    else
      bc = draw_ffi(type_id, code, "png", width, height, params)
      { ok: true, base64: bc.get_image_base64 }.to_json
    end
  rescue => e
    { ok: false, error: e.message }.to_json
  end
end

post "/draw-svg" do
  content_type :json
  type_id = params[:type] || "QR"
  info = BARCODE_MAP[type_id] || BARCODE_MAP["QR"]
  type_id = info[:id]
  code = params[:code]
  code = info[:default] if code.nil? || code.empty?
  width = (params[:width] || info[:w]).to_i
  height = (params[:height] || info[:h]).to_i
  engine = params[:engine] || "ffi"
  begin
    if engine == "wasm"
      result = draw_wasm(type_id, code, "svg", width, height, params)
      { ok: true, svg: result }.to_json
    else
      bc = draw_ffi(type_id, code, "svg", width, height, params)
      { ok: true, svg: bc.get_svg }.to_json
    end
  rescue => e
    { ok: false, error: e.message }.to_json
  end
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; All-in-One</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}
header h1{font-size:22px;font-weight:700}
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:1100px;margin:0 auto;padding:24px}
.main-row{display:flex;gap:20px}
.left-panel{flex:1;min-width:0}
.right-panel{width:400px;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],input[type=number],select{width:100%;padding:10px 14px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:14px;transition:border .2s}
input:focus,select:focus{outline:none;border-color:#CC342D;box-shadow:0 0 0 3px rgba(204,52,45,.15)}
.row2{display:flex;gap:12px}
.row2>div{flex:1}
.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}
.check-row{display:flex;gap:16px;margin:10px 0}
.check-row label{display:inline-flex;align-items:center;gap:4px;font-size:13px;font-weight:500;cursor:pointer}
.mode-row{display:flex;gap:8px;margin:12px 0}
.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}
.btn-generate{width:100%;padding:12px;border:none;border-radius:8px;background:#CC342D;color:#fff;font-size:14px;font-weight:600;cursor:pointer;margin-top:8px}
.btn-generate:hover{background:#a52920}
.type-frame{display:none;margin-top:12px;padding:12px;background:#fafafa;border-radius:8px;border:1px solid #e2e8f0}
.type-frame.active{display:block}
.type-frame label{font-size:12px}
.type-frame select,.type-frame input{font-size:13px;padding:8px 10px}
.code-panel{background:#1e293b;border-radius:8px;padding:14px 16px;margin-top:12px}
.code-panel pre{font-family:'Consolas','Courier New',monospace;font-size:11.5px;color:#e2e8f0;line-height:1.6;white-space:pre-wrap}
.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:400px}
.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%}}
</style>
</head>
<body>
<header>
  <h1>Barcode.Ruby gem &#x2014; All-in-One</h1>
  <p>Native FFI / WASM barcode library &#x2014; 18 barcode types</p>
  <span class="badge">Ruby + Sinatra + FFI/WASM</span>
</header>

<div class="container">
<div class="main-row">
  <div class="left-panel">
    <div class="card">
      <label for="bc-type">Barcode Type</label>
      <select id="bc-type" onchange="onTypeChange()"></select>

      <div class="row2" style="margin-top:12px">
        <div><label for="bc-width">Width</label><input type="number" id="bc-width" value="200"></div>
        <div><label for="bc-height">Height</label><input type="number" id="bc-height" value="200"></div>
      </div>

      <div style="margin-top:12px">
        <label for="bc-code">Data</label>
        <input type="text" id="bc-code" value="https://www.pao.ac/">
      </div>

      <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="check-row" id="check-row-1d">
        <label><input type="checkbox" id="chk-show-text" checked> Show Text</label>
        <label><input type="checkbox" id="chk-even-spacing" checked> Even Spacing</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>

      <!-- Type-specific settings -->
      <div class="type-frame" id="frame-qr">
        <label>Error Correction</label>
        <select id="qr-ecc">
          <option value="0">L (Low ~7%)</option>
          <option value="1" selected>M (Medium ~15%)</option>
          <option value="2">Q (Quartile ~25%)</option>
          <option value="3">H (High ~30%)</option>
        </select>
        <label style="margin-top:8px">Version (0=Auto)</label>
        <input type="number" id="qr-version" value="0" min="0" max="40">
      </div>

      <div class="type-frame" id="frame-pdf417">
        <label>Error Level (-1=Auto)</label>
        <input type="number" id="pdf417-el" value="-1" min="-1" max="8">
        <label style="margin-top:8px">Columns (0=Auto)</label>
        <input type="number" id="pdf417-cols" value="0" min="0" max="30">
      </div>

      <div class="type-frame" id="frame-databar14">
        <label>Symbol Type</label>
        <select id="databar14-type">
          <option value="Omnidirectional">Omnidirectional</option>
          <option value="Stacked">Stacked</option>
          <option value="StackedOmni">Stacked Omnidirectional</option>
        </select>
      </div>

      <div class="type-frame" id="frame-databar-exp">
        <label>Symbol Type</label>
        <select id="databar-exp-type">
          <option value="Unstacked">Unstacked</option>
          <option value="Stacked">Stacked</option>
        </select>
        <label style="margin-top:8px">Columns</label>
        <input type="number" id="databar-exp-cols" value="2" min="1" max="11">
      </div>

      <button class="btn-generate" onclick="generate()">Generate</button>

      <div class="code-panel">
        <pre id="code-panel"></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">Select a barcode type and click Generate</div>
      </div>
    </div>
  </div>
</div>
</div>

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

<script>
const barcodeTypes = <%= @barcode_types_json %>;
const typeMap = {};
barcodeTypes.forEach(t => typeMap[t.id] = t);

let currentMode = 'base64';
let currentEngine = 'ffi';

function populateTypes() {
  const sel = document.getElementById('bc-type');
  const groups = {};
  barcodeTypes.forEach(t => {
    if (!groups[t.group]) groups[t.group] = [];
    groups[t.group].push(t);
  });
  for (const [group, types] of Object.entries(groups)) {
    const og = document.createElement('optgroup');
    og.label = group;
    types.forEach(t => {
      const opt = document.createElement('option');
      opt.value = t.id;
      opt.textContent = t.label;
      og.appendChild(opt);
    });
    sel.appendChild(og);
  }
}

function onTypeChange() {
  const info = typeMap[document.getElementById('bc-type').value];
  if (!info) return;
  document.getElementById('bc-width').value = info.w;
  document.getElementById('bc-height').value = info.h;
  document.getElementById('bc-code').value = info.default;

  // Show/hide 1D options
  const is1d = info.dim === '1d';
  document.getElementById('check-row-1d').style.display = is1d ? '' : 'none';

  // Type-specific frames
  document.querySelectorAll('.type-frame').forEach(f => f.classList.remove('active'));
  if (info.id === 'QR') document.getElementById('frame-qr').classList.add('active');
  if (info.id === 'PDF417') document.getElementById('frame-pdf417').classList.add('active');
  if (info.id === 'GS1DataBar14') document.getElementById('frame-databar14').classList.add('active');
  if (info.id === 'GS1DataBarExpanded') document.getElementById('frame-databar-exp').classList.add('active');

  updateCodePanel();
}

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

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

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

function updateCodePanel() {
  const info = typeMap[document.getElementById('bc-type').value];
  if (!info) return;
  const fmt = currentMode === 'base64' ? 'png' : 'svg';
  const panel = document.getElementById('code-panel');
  const ffiMap = {
    QR: 'QRCode', DataMatrix: 'DataMatrix', PDF417: 'PDF417',
    Code128: 'Code128', Code39: 'Code39', Code93: 'Code93',
    'NW7': 'NW7', ITF: 'ITF', Matrix2of5: 'Matrix2of5', NEC2of5: 'NEC2of5',
    JAN13: 'JAN13', JAN8: 'JAN8', UPCA: 'UPCA', UPCE: 'UPCE',
    'GS1_128': 'GS1128', GS1DataBar14: 'GS1DataBar14',
    GS1DataBarLimited: 'GS1DataBarLimited', GS1DataBarExpanded: 'GS1DataBarExpanded',
    YubinCustomer: 'YubinCustomer'
  };
  const cn = ffiMap[info.id] || info.id;

  if (currentEngine === 'ffi') {
    let drawCall;
    if (info.dim === '2d' && info.id !== 'PDF417') drawCall = `bc.draw(code, ${info.w})`;
    else if (info.dim === 'postal') drawCall = `bc.draw(code, ${info.h})`;
    else drawCall = `bc.draw(code, ${info.w}, ${info.h})`;
    const getResult = currentMode === 'svg' ? 'bc.get_svg' : 'bc.get_image_base64';
    panel.innerHTML =
      `<span class="cm"># FFI</span>\n` +
      `bc = <span class="kw">BarcodePao</span>::<span class="fn">${cn}</span>.new(<span class="str">"${fmt}"</span>)\n` +
      `${drawCall}\n` +
      `result = ${getResult}`;
  } else {
    let drawCall;
    if (info.dim === '2d' && info.id !== 'PDF417') drawCall = `result = bc.draw(code, ${info.w})`;
    else if (info.dim === 'postal') drawCall = `result = bc.draw(code, ${info.h})`;
    else drawCall = `result = bc.draw(code, ${info.w}, ${info.h})`;
    panel.innerHTML =
      `<span class="cm"># WASM</span>\n` +
      `bc = <span class="kw">BarcodePaoWasm</span>::<span class="fn">${cn}</span>.new\n` +
      `bc.set_output_format(<span class="str">"${fmt}"</span>)\n` +
      drawCall;
  }
}

function generate() {
  const info = typeMap[document.getElementById('bc-type').value];
  if (!info) return;
  const fd = new FormData();
  fd.append('type', info.id);
  fd.append('code', document.getElementById('bc-code').value);
  fd.append('width', document.getElementById('bc-width').value);
  fd.append('height', document.getElementById('bc-height').value);
  fd.append('engine', currentEngine);
  fd.append('show_text', document.getElementById('chk-show-text').checked ? '1' : '0');
  fd.append('even_spacing', document.getElementById('chk-even-spacing').checked ? '1' : '0');

  // Type-specific params
  if (info.id === 'QR') {
    fd.append('qr_error_level', document.getElementById('qr-ecc').value);
    fd.append('qr_version', document.getElementById('qr-version').value);
  }
  if (info.id === 'PDF417') {
    fd.append('pdf417_error_level', document.getElementById('pdf417-el').value);
    fd.append('pdf417_cols', document.getElementById('pdf417-cols').value);
  }
  if (info.id === 'GS1DataBar14') {
    fd.append('databar14_type', document.getElementById('databar14-type').value);
  }
  if (info.id === 'GS1DataBarExpanded') {
    fd.append('databar_expanded_type', document.getElementById('databar-exp-type').value);
    fd.append('databar_expanded_cols', document.getElementById('databar-exp-cols').value);
  }

  const endpoint = currentMode === 'svg' ? '/draw-svg' : '/draw-base64';
  const preview = document.getElementById('preview-body');
  preview.innerHTML = '<div class="empty-preview">Generating...</div>';

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

populateTypes();
onTypeChange();
</script>
</body>
</html>
