{% if sg_has_lora %}
{# -----------------------------------------------------------------------
   Strengths Gallery partial
   Required context variables (set via {% set %} before {% include %}):
     sg_entity    — the entity model object
     sg_category  — URL category string, e.g. 'outfits', 'characters'
     sg_has_lora  — boolean: entity has a non-empty lora_name
   ----------------------------------------------------------------------- #}
{% set sg_lora = sg_entity.data.lora if sg_entity.data.lora else {} %}
{% set sg_weight_min = sg_lora.get('lora_weight_min', sg_lora.get('lora_weight', 0.0)) %}
{% set sg_weight_max = sg_lora.get('lora_weight_max', sg_lora.get('lora_weight', 1.0)) %}
<div class="card mt-4" id="sg-card">
  <div class="card-header d-flex justify-content-between align-items-center"
       style="background: linear-gradient(135deg,#1a1a3e,#2a1a4e); cursor:pointer;"
       onclick="document.getElementById('sg-body').classList.toggle('d-none')">
    <span>&#9889; Strengths Gallery
      <small class="text-muted ms-2" style="font-size:.8em;">
        — sweep this LoRA weight with a fixed seed
      </small>
    </span>
    <span id="sg-badge" class="badge bg-secondary">0 images</span>
  </div>

  <div class="card-body" id="sg-body">

    {# Saved range indicator #}
    <div id="sg-saved-range" class="alert alert-secondary py-1 px-2 mb-2 d-flex align-items-center gap-2" style="font-size:.85em;">
      <span>&#127919; Active range:
        <strong id="sg-saved-min">{{ sg_weight_min }}</strong>
        &ndash;
        <strong id="sg-saved-max">{{ sg_weight_max }}</strong>
      </span>
      <span class="text-muted ms-auto fst-italic" id="sg-save-status"></span>
    </div>

    {# Config row #}
    <div class="row g-2 align-items-end mb-3">
      <div class="col-sm-2">
        <label class="form-label small mb-1">Min Weight</label>
        <input type="number" id="sg-min" class="form-control form-control-sm"
               value="{{ sg_weight_min }}" step="0.05" min="-5" max="5">
      </div>
      <div class="col-sm-2">
        <label class="form-label small mb-1">Max Weight</label>
        <input type="number" id="sg-max" class="form-control form-control-sm"
               value="{{ sg_weight_max }}" step="0.05" min="-5" max="5">
      </div>
      <div class="col-sm-2">
        <label class="form-label small mb-1">Interval</label>
        <input type="number" id="sg-interval" class="form-control form-control-sm"
               value="0.05" step="0.01" min="0.01" max="1.0">
      </div>
      <div class="col-sm-3">
        <label class="form-label small mb-1">Seed</label>
        <div class="input-group input-group-sm">
          <input type="number" id="sg-seed" class="form-control" placeholder="auto">
          <button class="btn btn-outline-secondary" type="button"
                  onclick="sgRollSeed()" title="Random seed">&#127922;</button>
        </div>
      </div>
      <div class="col-sm-1">
        <label class="form-label small mb-1">Count</label>
        <span id="sg-step-count" class="form-control form-control-sm bg-transparent border-0 text-muted ps-0">11</span>
      </div>
      <div class="col-sm-2 d-flex flex-column gap-1">
        <div class="d-flex gap-1">
          <button id="sg-btn-run" class="btn btn-sm btn-primary flex-grow-1"
                  onclick="sgStart()">Generate</button>
          <button id="sg-btn-stop" class="btn btn-sm btn-warning d-none"
                  onclick="sgStop()">Stop</button>
          <button id="sg-btn-clear" class="btn btn-sm btn-outline-danger"
                  onclick="sgClear()" title="Clear results">&#128465;</button>
        </div>
        <button id="sg-btn-save-range" class="btn btn-sm btn-outline-success w-100"
                onclick="sgSaveRange()" title="Save current Min/Max as the randomisation range for this LoRA">
          &#128190; Save Range
        </button>
      </div>
    </div>

    {# Progress #}
    <div id="sg-progress" class="d-none mb-3">
      <div class="progress" style="height:6px;">
        <div id="sg-progress-bar" class="progress-bar progress-bar-striped progress-bar-animated"
             style="width:0%"></div>
      </div>
      <small class="text-muted" id="sg-progress-label">0 / 0 &mdash; weight: —</small>
    </div>

    {# Results grid #}
    <div id="sg-grid" class="row g-2">
      {# Populated by JS on load and after each generation step #}
    </div>

  </div>{# /card-body #}
</div>{# /card #}

<script>
(function () {
  'use strict';

  const SG_CAT  = {{ sg_category | tojson }};
  const SG_SLUG = {{ sg_entity.slug | tojson }};

  let sgRunning = false;
  let sgShouldStop = false;
  let sgQueuedJobs = []; // track all queued job IDs so stop can cancel them

  // ---- helpers ----

  function sgRollSeed() {
    document.getElementById('sg-seed').value = Math.floor(Math.random() * 1e15);
  }

  function sgGetInterval() {
    const v = parseFloat(document.getElementById('sg-interval').value);
    if (isNaN(v) || v < 0.01) return 0.01;
    if (v > 1.0) return 1.0;
    return v;
  }

  function sgBuildSteps(min, max, interval) {
    if (min > max) return [];
    interval = interval || 0.1;
    const scale = Math.round(1 / interval);
    const steps = [];
    let v = min;
    while (v <= max + interval * 0.5) {
      steps.push(Math.round(v * scale) / scale);
      v = Math.round((v + interval) * scale) / scale;
    }
    return steps;
  }

  // ---- validation & visual feedback ----

  function sgValidateBounds() {
    const minEl = document.getElementById('sg-min');
    const maxEl = document.getElementById('sg-max');
    const min = parseFloat(minEl.value);
    const max = parseFloat(maxEl.value);
    const invalid = !isNaN(min) && !isNaN(max) && min > max;
    minEl.classList.toggle('is-invalid', invalid);
    maxEl.classList.toggle('is-invalid', invalid);
    return !invalid;
  }

  function sgUpdateCount() {
    const valid = sgValidateBounds();
    const min = parseFloat(document.getElementById('sg-min').value) || 0;
    const max = parseFloat(document.getElementById('sg-max').value) || 1;
    const count = valid ? sgBuildSteps(min, max, sgGetInterval()).length : 0;
    document.getElementById('sg-step-count').textContent = count;
  }

  // Clamp on blur: push the other bound to match rather than block the user mid-type
  document.getElementById('sg-min').addEventListener('change', () => {
    const minEl = document.getElementById('sg-min');
    const maxEl = document.getElementById('sg-max');
    const min = parseFloat(minEl.value), max = parseFloat(maxEl.value);
    if (!isNaN(min) && !isNaN(max) && min > max) maxEl.value = min;
    sgUpdateCount();
    sgHighlightBounds();
  });
  document.getElementById('sg-max').addEventListener('change', () => {
    const minEl = document.getElementById('sg-min');
    const maxEl = document.getElementById('sg-max');
    const min = parseFloat(minEl.value), max = parseFloat(maxEl.value);
    if (!isNaN(min) && !isNaN(max) && max < min) minEl.value = max;
    sgUpdateCount();
    sgHighlightBounds();
  });
  document.getElementById('sg-min').addEventListener('input', () => { sgUpdateCount(); sgHighlightBounds(); });
  document.getElementById('sg-max').addEventListener('input', () => { sgUpdateCount(); sgHighlightBounds(); });
  document.getElementById('sg-interval').addEventListener('input', sgUpdateCount);

  // ---- highlight matching min/max buttons ----

  function sgHighlightBounds() {
    const currentMin = parseFloat(document.getElementById('sg-min').value);
    const currentMax = parseFloat(document.getElementById('sg-max').value);
    document.querySelectorAll('#sg-grid .sg-thumb').forEach(thumb => {
      const sv = parseFloat(thumb.dataset.sgStrength);
      const minBtn = thumb.querySelector('[data-sg-role="min-btn"]');
      const maxBtn = thumb.querySelector('[data-sg-role="max-btn"]');
      if (minBtn) {
        const active = sv === currentMin;
        minBtn.classList.toggle('btn-primary',         active);
        minBtn.classList.toggle('btn-outline-primary', !active);
      }
      if (maxBtn) {
        const active = sv === currentMax;
        maxBtn.classList.toggle('btn-warning',         active);
        maxBtn.classList.toggle('btn-outline-warning', !active);
      }
    });
  }

  function sgUpdateBadge() {
    const count = document.querySelectorAll('#sg-grid .sg-thumb').length;
    document.getElementById('sg-badge').textContent = count + ' image' + (count !== 1 ? 's' : '');
  }

  function sgSetMin(v) {
    const maxEl = document.getElementById('sg-max');
    document.getElementById('sg-min').value = v;
    if (v > parseFloat(maxEl.value)) maxEl.value = v;
    sgUpdateCount();
    sgHighlightBounds();
  }

  function sgSetMax(v) {
    const minEl = document.getElementById('sg-min');
    document.getElementById('sg-max').value = v;
    if (v < parseFloat(minEl.value)) minEl.value = v;
    sgUpdateCount();
    sgHighlightBounds();
  }

  function sgAddImage(imageUrl, strengthValue) {
    const grid = document.getElementById('sg-grid');
    const col = document.createElement('div');
    col.className = 'col-6 col-sm-4 col-md-3 col-lg-2';
    col.innerHTML = `
      <div class="card h-100 sg-thumb" data-sg-strength="${strengthValue}">
        <img src="${imageUrl}" class="card-img-top" style="object-fit:cover;height:160px;cursor:zoom-in;"
             loading="lazy" onclick="window.open(this.src,'_blank')">
        <div class="card-footer py-1 px-1">
          <div class="text-center mb-1"><span class="badge bg-secondary">${strengthValue}</span></div>
          <div class="d-flex gap-1">
            <button class="btn btn-outline-primary btn-sm flex-grow-1 py-0" data-sg-role="min-btn"
                    style="font-size:.7em;" onclick="sgSetMin(${strengthValue})" title="Set as Min weight">&#8595; Min</button>
            <button class="btn btn-outline-warning btn-sm flex-grow-1 py-0" data-sg-role="max-btn"
                    style="font-size:.7em;" onclick="sgSetMax(${strengthValue})" title="Set as Max weight">Max &#8593;</button>
          </div>
        </div>
      </div>`;
    grid.appendChild(col);
    sgUpdateBadge();
    sgHighlightBounds();
  }

  // ---- Job queue wait ----

  function sgWaitForJob(jobId) {
    return new Promise((resolve, reject) => {
      const poll = setInterval(async () => {
        try {
          const resp = await fetch(`/api/queue/${jobId}/status`);
          const data = await resp.json();
          if (data.status === 'done') { clearInterval(poll); resolve(data); }
          else if (data.status === 'failed' || data.status === 'removed') {
            clearInterval(poll); reject(new Error(data.error || 'Job failed'));
          }
        } catch (err) { console.error('[Strengths] poll error:', err); }
      }, 1500);
    });
  }

  // ---- main flow ----

  async function sgClearImages() {
    await fetch(`/strengths/${SG_CAT}/${SG_SLUG}/clear`, { method: 'POST' });
    document.getElementById('sg-grid').innerHTML = '';
    sgUpdateBadge();
  }

  async function sgStart() {
    if (sgRunning) return;

    const min  = parseFloat(document.getElementById('sg-min').value);
    const max  = parseFloat(document.getElementById('sg-max').value);
    let seed   = parseInt(document.getElementById('sg-seed').value);
    if (isNaN(seed)) {
      seed = Math.floor(Math.random() * 1e15);
      document.getElementById('sg-seed').value = seed;
    }

    if (!sgValidateBounds()) return;
    const steps = sgBuildSteps(min, max, sgGetInterval());
    if (!steps.length) return;

    await sgClearImages();

    sgRunning = true;
    sgShouldStop = false;
    sgQueuedJobs = [];
    document.getElementById('sg-btn-run').classList.add('d-none');
    document.getElementById('sg-btn-stop').classList.remove('d-none');
    document.getElementById('sg-progress').classList.remove('d-none');

    const charSelect = document.getElementById('character_select');
    const charSlug = charSelect ? charSelect.value : '';

    // Phase 1: Queue all steps upfront so generation continues even if the page is navigated away
    document.getElementById('sg-progress-bar').style.width = '100%';
    document.getElementById('sg-progress-bar').classList.add('progress-bar-striped', 'progress-bar-animated');
    for (let i = 0; i < steps.length; i++) {
      if (sgShouldStop) break;
      const sv = steps[i];
      document.getElementById('sg-progress-label').textContent =
        `Queuing ${i + 1} / ${steps.length} \u2014 weight: ${sv}`;
      try {
        const formData = new URLSearchParams({ strength_value: sv, seed, character_slug: charSlug });
        const queueResp = await fetch(`/strengths/${SG_CAT}/${SG_SLUG}/generate`, {
          method: 'POST',
          headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded' },
          body: formData,
        });
        const queueData = await queueResp.json();
        if (!queueData.job_id) throw new Error('No job_id returned');
        sgQueuedJobs.push({ jobId: queueData.job_id, sv });
      } catch (err) {
        console.error('[Strengths] queue error:', sv, err);
      }
    }

    // Phase 2: Poll all jobs concurrently; show results as each finishes
    document.getElementById('sg-progress-bar').classList.remove('progress-bar-striped', 'progress-bar-animated');
    document.getElementById('sg-progress-bar').style.width = '0%';

    let completed = 0;
    await Promise.all(sgQueuedJobs.map(async ({ jobId, sv }) => {
      try {
        const jobResult = await sgWaitForJob(jobId);
        if (jobResult.result && jobResult.result.image_url) {
          sgAddImage(jobResult.result.image_url, sv);
        }
      } catch (err) {
        console.error('[Strengths] job error:', sv, err);
      }
      completed++;
      const pct = Math.round((completed / sgQueuedJobs.length) * 100);
      document.getElementById('sg-progress-bar').style.width = pct + '%';
      document.getElementById('sg-progress-label').textContent =
        `${completed} / ${sgQueuedJobs.length} done`;
    }));

    document.getElementById('sg-progress-bar').style.width = '100%';
    document.getElementById('sg-progress-label').textContent =
      `Done \u2014 ${sgQueuedJobs.length} images generated`;
    setTimeout(() => document.getElementById('sg-progress').classList.add('d-none'), 3000);

    document.getElementById('sg-btn-stop').classList.add('d-none');
    document.getElementById('sg-btn-run').classList.remove('d-none');
    sgRunning = false;
  }

  function sgStop() {
    sgShouldStop = true;
    // Cancel any pending (not yet processing) queued jobs
    sgQueuedJobs.forEach(({ jobId }) => {
      fetch(`/api/queue/${jobId}/remove`, { method: 'POST' }).catch(() => {});
    });
    document.getElementById('sg-btn-stop').classList.add('d-none');
    document.getElementById('sg-btn-run').classList.remove('d-none');
  }

  async function sgClear() {
    if (!confirm('Clear all Strengths Gallery images for this item?')) return;
    await sgClearImages();
  }

  async function sgSaveRange() {
    const min = parseFloat(document.getElementById('sg-min').value);
    const max = parseFloat(document.getElementById('sg-max').value);
    if (isNaN(min) || isNaN(max)) {
      alert('Set valid Min and Max values first.');
      return;
    }
    const statusEl = document.getElementById('sg-save-status');
    statusEl.textContent = 'Saving…';
    try {
      const body = new URLSearchParams({ min_weight: Math.min(min, max), max_weight: Math.max(min, max) });
      const resp = await fetch(`/strengths/${SG_CAT}/${SG_SLUG}/save_range`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body,
      });
      const data = await resp.json();
      if (data.success) {
        document.getElementById('sg-saved-min').textContent = data.lora_weight_min;
        document.getElementById('sg-saved-max').textContent = data.lora_weight_max;
        statusEl.textContent = '✓ Saved';
        setTimeout(() => { statusEl.textContent = ''; }, 3000);
      } else {
        statusEl.textContent = '✗ ' + (data.error || 'Error');
      }
    } catch (e) {
      statusEl.textContent = '✗ Network error';
    }
  }

  // Expose functions to inline onclick handlers
  window.sgStart      = sgStart;
  window.sgStop       = sgStop;
  window.sgClear      = sgClear;
  window.sgSaveRange  = sgSaveRange;
  window.sgRollSeed   = sgRollSeed;
  window.sgSetMin     = sgSetMin;
  window.sgSetMax     = sgSetMax;

  // ---- Load existing images on page load ----

  fetch(`/strengths/${SG_CAT}/${SG_SLUG}/list`)
    .then(r => r.json())
    .then(data => {
      (data.images || []).forEach(img => sgAddImage(img.url, img.strength));
      sgUpdateCount();
      sgHighlightBounds();
    })
    .catch(() => sgUpdateCount());

})();
</script>
{% endif %}
