<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GAZE</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=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet">
</head>
<body>
    <nav class="navbar navbar-dark mb-4">
        <div class="container">
            <a class="navbar-brand d-flex align-items-center gap-2" href="/"><img src="{{ url_for('static', filename='icons/gaze-logo.png') }}" class="navbar-logo">GAZE</a>
            <div class="d-flex align-items-center gap-1 flex-wrap">
                <a href="/" class="btn btn-sm btn-outline-light">Characters</a>
                <a href="/outfits" class="btn btn-sm btn-outline-light">Outfits</a>
                <a href="/looks" class="btn btn-sm btn-outline-light">Looks</a>
                <a href="/actions" class="btn btn-sm btn-outline-light">Actions</a>
                <a href="/styles" class="btn btn-sm btn-outline-light">Styles</a>
                <a href="/scenes" class="btn btn-sm btn-outline-light">Scenes</a>
                <a href="/detailers" class="btn btn-sm btn-outline-light">Detailers</a>
                <a href="/checkpoints" class="btn btn-sm btn-outline-light">Checkpoints</a>
                <div class="vr mx-1 d-none d-lg-block"></div>
                <a href="/create" class="btn btn-sm btn-outline-success">+ Character</a>
                <a href="/generator" class="btn btn-sm btn-outline-light">Generator</a>
                <a href="/gallery" class="btn btn-sm btn-outline-light">Gallery</a>
                <a href="/settings" class="btn btn-sm btn-outline-light">Settings</a>
                <div class="vr mx-1 d-none d-lg-block"></div>
                <!-- Queue indicator -->
                <button id="queue-btn" class="btn btn-sm queue-btn" data-bs-toggle="modal" data-bs-target="#queueModal" title="Generation Queue">
                    <span class="queue-icon">⏳</span>
                    <span id="queue-count-badge" class="queue-badge d-none">0</span>
                </button>
                <div class="vr mx-1 d-none d-lg-block"></div>
                <!-- Service status indicators -->
                <span id="status-comfyui" class="service-status" title="ComfyUI" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="ComfyUI: checking…">
                    <span class="status-dot status-checking"></span>
                    <span class="status-label d-none d-xl-inline">ComfyUI</span>
                </span>
                <span id="status-mcp" class="service-status" title="MCP" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Danbooru MCP: checking…">
                    <span class="status-dot status-checking"></span>
                    <span class="status-label d-none d-xl-inline">MCP</span>
                </span>
            </div>
        </div>
    </nav>

    <div class="default-checkpoint-bar border-bottom mb-4">
        <div class="container d-flex align-items-center gap-2 py-2">
            <small class="text-muted text-nowrap">Default checkpoint:</small>
            <select id="defaultCheckpointSelect" class="form-select form-select-sm" style="max-width: 320px;">
                <option value="">— workflow default —</option>
                {% for ckpt in all_checkpoints %}
                <option value="{{ ckpt.checkpoint_path }}"{% if ckpt.checkpoint_path == default_checkpoint_path %} selected{% endif %}>{{ ckpt.name }}</option>
                {% endfor %}
            </select>
            <small id="checkpointSaveStatus" class="text-muted" style="opacity:0;transition:opacity 0.5s">Saved</small>
        </div>
    </div>

    <div class="container">
        {% with messages = get_flashed_messages() %}
          {% if messages %}
            {% for message in messages %}
              <div class="alert alert-info">{{ message }}</div>
            {% endfor %}
          {% endif %}
        {% endwith %}

        {% block content %}{% endblock %}
    </div>

<!-- Resource delete modal (shared across category gallery pages) -->
<div class="modal fade" id="resourceDeleteModal" tabindex="-1">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Delete <span id="rdm-name"></span></h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <div class="d-grid gap-2">
          <button class="btn btn-outline-secondary text-start" onclick="confirmResourceDelete('soft')">
            <strong>Soft delete</strong> — remove entry from this gallery<br>
            <small class="text-muted">JSON data file deleted; LoRA/checkpoint file kept on disk</small>
          </button>
          <button class="btn btn-outline-danger text-start" onclick="confirmResourceDelete('hard')">
            <strong>Hard delete</strong> — remove entry and all associated files<br>
            <small class="text-muted">Deletes JSON data file and LoRA/checkpoint safetensors from disk. Irreversible.</small>
          </button>
        </div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
      </div>
    </div>
  </div>
</div>

<!-- Generation Queue Modal -->
<div class="modal fade" id="queueModal" tabindex="-1">
  <div class="modal-dialog modal-dialog-centered modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">
          Generation Queue
          <span id="queue-modal-count" class="badge bg-secondary ms-2">0</span>
        </h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body p-0">
        <div id="queue-empty-msg" class="text-center text-muted py-4">
          <p class="mb-0">No jobs in queue.</p>
        </div>
        <ul id="queue-job-list" class="list-group list-group-flush d-none"></ul>
      </div>
      <div class="modal-footer">
        <small class="text-muted me-auto">Jobs are processed sequentially. Close this window to continue browsing.</small>
        <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => new bootstrap.Tooltip(el));

            const ckptSelect = document.getElementById('defaultCheckpointSelect');
            const saveStatus = document.getElementById('checkpointSaveStatus');
            if (ckptSelect) {
                ckptSelect.addEventListener('change', () => {
                    fetch('/set_default_checkpoint', {
                        method: 'POST',
                        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                        body: 'checkpoint_path=' + encodeURIComponent(ckptSelect.value)
                    }).then(() => {
                        saveStatus.style.opacity = '1';
                        setTimeout(() => { saveStatus.style.opacity = '0'; }, 1500);
                    });
                });
            }
        });
    </script>
    <script>
    // ---- Resource delete modal (category galleries) ----
    let _rdmCategory = '', _rdmSlug = '';
    let resourceDeleteModal;
    document.addEventListener('DOMContentLoaded', () => {
      const el = document.getElementById('resourceDeleteModal');
      if (el) resourceDeleteModal = new bootstrap.Modal(el);

      // Attach listeners to resource delete buttons via data attrs (avoids inline onclick issues)
      document.querySelectorAll('.resource-delete-btn').forEach(btn => {
        btn.addEventListener('click', function(e) {
          e.stopPropagation();
          openResourceDeleteModal(this.dataset.category, this.dataset.slug, this.dataset.name);
        });
      });
    });
    function openResourceDeleteModal(category, slug, name) {
      _rdmCategory = category;
      _rdmSlug = slug;
      document.getElementById('rdm-name').textContent = name;
      resourceDeleteModal.show();
    }
    async function confirmResourceDelete(mode) {
      resourceDeleteModal.hide();
      try {
        const res = await fetch(`/resource/${_rdmCategory}/${_rdmSlug}/delete`, {
          method: 'POST',
          headers: {'Content-Type': 'application/json'},
          body: JSON.stringify({mode}),
        });
        const data = await res.json();
        if (data.status === 'ok') {
          const card = document.getElementById(`card-${_rdmSlug}`);
          if (card) card.remove();
        } else {
          alert('Delete failed: ' + (data.error || 'unknown error'));
        }
      } catch (e) {
        alert('Delete failed: ' + e);
      }
    }

    function initJsonEditor(saveUrl) {
        const jsonModal = document.getElementById('jsonEditorModal');
        if (!jsonModal) return;
        const textarea  = document.getElementById('json-editor-textarea');
        const errBox    = document.getElementById('json-editor-error');
        const simplePanel   = document.getElementById('json-simple-panel');
        const advancedPanel = document.getElementById('json-advanced-panel');
        const simpleTab     = document.getElementById('json-simple-tab');
        const advancedTab   = document.getElementById('json-advanced-tab');
        let activeTab = 'simple';

        function buildSimpleForm(data) {
            simplePanel.innerHTML = '';
            for (const [key, value] of Object.entries(data)) {
                const row = document.createElement('div');
                row.className = 'row mb-2 align-items-start';

                const labelCol = document.createElement('div');
                labelCol.className = 'col-sm-3 col-form-label fw-semibold text-capitalize small pt-1';
                labelCol.textContent = key.replace(/_/g, ' ');

                const inputCol = document.createElement('div');
                inputCol.className = 'col-sm-9';

                let el;
                if (typeof value === 'boolean') {
                    const wrap = document.createElement('div');
                    wrap.className = 'form-check mt-2';
                    el = document.createElement('input');
                    el.type = 'checkbox';
                    el.className = 'form-check-input';
                    el.checked = value;
                    el.dataset.dtype = 'boolean';
                    wrap.appendChild(el);
                    inputCol.appendChild(wrap);
                } else if (typeof value === 'number') {
                    el = document.createElement('input');
                    el.type = 'number';
                    el.step = 'any';
                    el.className = 'form-control form-control-sm';
                    el.value = value;
                    el.dataset.dtype = 'number';
                    inputCol.appendChild(el);
                } else if (typeof value === 'string') {
                    if (value.length > 80) {
                        el = document.createElement('textarea');
                        el.className = 'form-control form-control-sm';
                        el.rows = 3;
                    } else {
                        el = document.createElement('input');
                        el.type = 'text';
                        el.className = 'form-control form-control-sm';
                    }
                    el.value = value;
                    el.dataset.dtype = 'string';
                    inputCol.appendChild(el);
                } else {
                    el = document.createElement('textarea');
                    el.className = 'form-control form-control-sm font-monospace';
                    const lines = JSON.stringify(value, null, 2).split('\n');
                    el.rows = Math.min(10, lines.length + 1);
                    el.value = JSON.stringify(value, null, 2);
                    el.dataset.dtype = 'json';
                    inputCol.appendChild(el);
                }
                el.dataset.key = key;
                row.appendChild(labelCol);
                row.appendChild(inputCol);
                simplePanel.appendChild(row);
            }
        }

        function readSimpleForm() {
            const result = {};
            simplePanel.querySelectorAll('[data-key]').forEach(el => {
                const key = el.dataset.key;
                const dtype = el.dataset.dtype;
                if (dtype === 'boolean') result[key] = el.checked;
                else if (dtype === 'number') { const n = parseFloat(el.value); result[key] = isNaN(n) ? el.value : n; }
                else if (dtype === 'json') { try { result[key] = JSON.parse(el.value); } catch { result[key] = el.value; } }
                else result[key] = el.value;
            });
            return result;
        }

        simpleTab.addEventListener('click', () => {
            errBox.classList.add('d-none');
            let data;
            try { data = JSON.parse(textarea.value); }
            catch (e) { errBox.textContent = 'Cannot switch: invalid JSON — ' + e.message; errBox.classList.remove('d-none'); return; }
            buildSimpleForm(data);
            simplePanel.classList.remove('d-none');
            advancedPanel.classList.add('d-none');
            simpleTab.classList.add('active');
            advancedTab.classList.remove('active');
            activeTab = 'simple';
        });

        advancedTab.addEventListener('click', () => {
            textarea.value = JSON.stringify(readSimpleForm(), null, 2);
            advancedPanel.classList.remove('d-none');
            simplePanel.classList.add('d-none');
            advancedTab.classList.add('active');
            simpleTab.classList.remove('active');
            activeTab = 'advanced';
        });

        jsonModal.addEventListener('show.bs.modal', () => {
            const raw = document.getElementById('json-raw-data').textContent;
            let data;
            try { data = JSON.parse(raw); } catch { data = {}; }
            buildSimpleForm(data);
            textarea.value = JSON.stringify(data, null, 2);
            simplePanel.classList.remove('d-none');
            advancedPanel.classList.add('d-none');
            simpleTab.classList.add('active');
            advancedTab.classList.remove('active');
            activeTab = 'simple';
            errBox.classList.add('d-none');
        });

        document.getElementById('json-save-btn').addEventListener('click', async () => {
            errBox.classList.add('d-none');
            let parsed;
            if (activeTab === 'simple') {
                parsed = readSimpleForm();
            } else {
                try { parsed = JSON.parse(textarea.value); }
                catch (e) { errBox.textContent = 'Invalid JSON: ' + e.message; errBox.classList.remove('d-none'); return; }
            }
            const fd = new FormData();
            fd.append('json_data', JSON.stringify(parsed));
            const resp = await fetch(saveUrl, { method: 'POST', body: fd });
            const result = await resp.json();
            if (result.success) { bootstrap.Modal.getInstance(jsonModal).hide(); location.reload(); }
            else { errBox.textContent = result.error || 'Save failed.'; errBox.classList.remove('d-none'); }
        });
    }
    </script>
    <script>
    // ---- Service status indicators ----
    (function () {
        const services = [
            { id: 'status-comfyui', url: '/api/status/comfyui', label: 'ComfyUI' },
            { id: 'status-mcp',     url: '/api/status/mcp',     label: 'Danbooru MCP' },
        ];

        function setStatus(id, label, ok) {
            const el = document.getElementById(id);
            if (!el) return;
            const dot = el.querySelector('.status-dot');
            dot.className = 'status-dot ' + (ok ? 'status-ok' : 'status-error');
            const tooltipText = label + ': ' + (ok ? 'online' : 'offline');
            el.setAttribute('data-bs-title', tooltipText);
            el.setAttribute('title', tooltipText);
            // Refresh tooltip instance if already initialised
            const tip = bootstrap.Tooltip.getInstance(el);
            if (tip) {
                tip.setContent({ '.tooltip-inner': tooltipText });
            }
        }

        async function pollService(svc) {
            try {
                const r = await fetch(svc.url, { cache: 'no-store' });
                const data = await r.json();
                setStatus(svc.id, svc.label, data.status === 'ok');
            } catch {
                setStatus(svc.id, svc.label, false);
            }
        }

        function pollAll() {
            services.forEach(pollService);
        }

        document.addEventListener('DOMContentLoaded', () => {
            pollAll();
            setInterval(pollAll, 30000); // re-check every 30 s
        });
    })();
    </script>
    <script>
    // ---- Generation Queue UI ----
    (function() {
        const badge = document.getElementById('queue-count-badge');
        const modalCount = document.getElementById('queue-modal-count');
        const jobList = document.getElementById('queue-job-list');
        const emptyMsg = document.getElementById('queue-empty-msg');

        const STATUS_LABELS = {
            pending: { text: 'Pending', cls: 'text-muted' },
            processing: { text: 'Generating…', cls: 'text-warning' },
            paused: { text: 'Paused', cls: 'text-secondary' },
            done: { text: 'Done', cls: 'text-success' },
            failed: { text: 'Failed', cls: 'text-danger' },
            removed: { text: 'Removed', cls: 'text-muted' },
        };

        function renderQueue(jobs) {
            const activeJobs = jobs.filter(j => !['done', 'failed', 'removed'].includes(j.status));
            const count = activeJobs.length;

            // Update badge
            if (count > 0) {
                badge.textContent = count;
                badge.classList.remove('d-none');
                document.getElementById('queue-btn').classList.add('queue-btn-active');
            } else {
                badge.classList.add('d-none');
                document.getElementById('queue-btn').classList.remove('queue-btn-active');
            }

            // Update modal count
            if (modalCount) modalCount.textContent = jobs.length;

            // Render job list
            if (!jobList) return;
            if (jobs.length === 0) {
                jobList.classList.add('d-none');
                if (emptyMsg) emptyMsg.classList.remove('d-none');
                return;
            }
            jobList.classList.remove('d-none');
            if (emptyMsg) emptyMsg.classList.add('d-none');

            jobList.innerHTML = '';
            jobs.forEach(job => {
                const li = document.createElement('li');
                li.className = 'list-group-item d-flex align-items-center gap-2 py-2';
                li.id = `queue-job-${job.id}`;

                const statusInfo = STATUS_LABELS[job.status] || { text: job.status, cls: 'text-muted' };

                // Status indicator
                const statusDot = document.createElement('span');
                statusDot.className = `queue-status-dot queue-status-${job.status}`;
                li.appendChild(statusDot);

                // Label
                const label = document.createElement('span');
                label.className = 'flex-grow-1 small';
                label.textContent = job.label;
                li.appendChild(label);

                // Status text
                const statusText = document.createElement('span');
                statusText.className = `small ${statusInfo.cls}`;
                statusText.textContent = statusInfo.text;
                if (job.status === 'failed' && job.error) {
                    statusText.title = job.error;
                    statusText.style.cursor = 'help';
                }
                li.appendChild(statusText);

                // Action buttons
                const btnGroup = document.createElement('div');
                btnGroup.className = 'd-flex gap-1';

                if (job.status === 'pending') {
                    const pauseBtn = document.createElement('button');
                    pauseBtn.className = 'btn btn-xs btn-outline-secondary';
                    pauseBtn.textContent = '⏸';
                    pauseBtn.title = 'Pause';
                    pauseBtn.onclick = () => queuePause(job.id);
                    btnGroup.appendChild(pauseBtn);
                }
                if (job.status === 'paused') {
                    const resumeBtn = document.createElement('button');
                    resumeBtn.className = 'btn btn-xs btn-outline-success';
                    resumeBtn.textContent = '▶';
                    resumeBtn.title = 'Resume';
                    resumeBtn.onclick = () => queuePause(job.id);
                    btnGroup.appendChild(resumeBtn);
                }
                if (['pending', 'paused', 'failed'].includes(job.status)) {
                    const removeBtn = document.createElement('button');
                    removeBtn.className = 'btn btn-xs btn-outline-danger';
                    removeBtn.textContent = '✕';
                    removeBtn.title = 'Remove';
                    removeBtn.onclick = () => queueRemove(job.id);
                    btnGroup.appendChild(removeBtn);
                }

                li.appendChild(btnGroup);
                jobList.appendChild(li);
            });
        }

        async function fetchQueue() {
            try {
                const resp = await fetch('/api/queue');
                const data = await resp.json();
                renderQueue(data.jobs || []);
            } catch (e) {}
        }

        async function queueRemove(jobId) {
            try {
                await fetch(`/api/queue/${jobId}/remove`, { method: 'POST' });
                fetchQueue();
            } catch (e) {}
        }

        async function queuePause(jobId) {
            try {
                await fetch(`/api/queue/${jobId}/pause`, { method: 'POST' });
                fetchQueue();
            } catch (e) {}
        }

        // Poll queue every 2 seconds
        document.addEventListener('DOMContentLoaded', () => {
            fetchQueue();
            setInterval(fetchQueue, 2000);

            // Refresh when modal opens
            const queueModal = document.getElementById('queueModal');
            if (queueModal) {
                queueModal.addEventListener('show.bs.modal', fetchQueue);
            }
        });
    })();
    </script>
    {% block scripts %}{% endblock %}
</body>
</html>
