{% extends "base.html.j2" %}
<!-- The Law of Fives is never wrong. -->
{% block title %}{{ t.crawl }} - neopig{% endblock %}

{% block extra_css %}
/* Page layout */
h1 { color: #ff6b6b; margin-bottom: 5px; }
.subtitle { color: #666; margin-bottom: 20px; }
.row { display: flex; gap: 15px; }
.row > div { flex: 1; }
input[type="text"], input[type="number"], select, textarea { width: 100%; }
button { margin-top: 10px; }
button.secondary { background: #333; }
button.secondary:hover { background: #444; }
.jobs-section { margin-top: 30px; padding-top: 20px; border-top: 1px solid #333; }
h2 { color: #ff6b6b; font-size: 18px; margin-bottom: 15px; }
.job { background: #1a1a1a; border-radius: 8px; padding: 15px; margin-bottom: 10px; }
.job-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.job-id { font-family: monospace; color: #666; }
.job-status { padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: bold; }
.job-status.running { background: #2d5a27; color: #7bed72; }
.job-status.completed { background: #1a3a4a; color: #6bc5e8; }
.job-status.failed { background: #5a2727; color: #ed7272; }
.job-target { font-size: 14px; word-break: break-all; margin-bottom: 5px; }
.job-meta { font-size: 12px; color: #666; }
.job-stats { display: flex; gap: 15px; margin-top: 10px; font-size: 13px; flex-wrap: wrap; }
.job-stats span { background: #252525; padding: 4px 10px; border-radius: 4px; }
.progress-bar { height: 4px; background: #333; border-radius: 2px; margin-top: 10px; overflow: hidden; }
.progress-bar-fill { height: 100%; background: #ff6b6b; transition: width 0.3s; }
.progress-bar-fill.running {
    width: 100%;
    background: linear-gradient(90deg, #ff6b6b 0%, #4ecdc4 50%, #ff6b6b 100%);
    background-size: 200% 100%;
    animation: progress-wave 1.5s ease-in-out infinite;
}
.job-actions { margin-top: 8px; display: flex; gap: 8px; flex-wrap: wrap; }
.job-actions button { padding: 4px 12px; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; margin-top: 0; }
.pause-btn { background: #e67e22; }
.pause-btn:hover { background: #f39c12; }
.start-btn { background: #27ae60; }
.start-btn:hover { background: #2ecc71; }
.recrawl-btn { background: #3498db; }
.recrawl-btn:hover { background: #2980b9; }
.delete-btn { background: #7f8c8d; }
.delete-btn:hover { background: #c0392b; }
.replay-btn { background: #9b59b6; }
.replay-btn:hover { background: #8e44ad; }
.job-links { margin-left: 10px; font-size: 12px; }
.job-links a { color: #4ecdc4; margin-right: 8px; }
.job-links a:hover { color: #7bed72; }
@keyframes progress-wave {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
.job.running { border-color: #4ecdc4; }
.job-stats.live { color: #4ecdc4; }
.no-jobs { color: #666; text-align: center; padding: 30px; }
.console-btn { background: #2c3e50; }
.console-btn:hover { background: #34495e; }
.console-modal {
    display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0;
    background: rgba(0,0,0,0.8); z-index: 1000; padding: 20px;
}
.console-modal.active { display: flex; flex-direction: column; }
.console-header {
    display: flex; justify-content: space-between; align-items: center;
    padding: 10px 15px; background: #1a1a1a; border-radius: 8px 8px 0 0;
}
.console-header h3 { margin: 0; color: #ff6b6b; }
.console-close { background: #c0392b; border: none; color: white; padding: 5px 15px; border-radius: 4px; cursor: pointer; }
.console-body {
    flex: 1; background: #0d0d0d; padding: 15px; overflow: auto;
    font-family: monospace; font-size: 12px; white-space: pre-wrap;
    word-break: break-all; color: #4ade80; border-radius: 0 0 8px 8px;
}
.console-body .log-line { margin: 0; line-height: 1.4; }
.console-body .log-INFO { color: #4ade80; }
.console-body .log-WARNING { color: #f39c12; }
.console-body .log-ERROR { color: #e74c3c; }
{% endblock %}

{% block content %}
<div class="container">
    <h1>{{ t.crawl }}</h1>
    <p class="subtitle">{{ t.hydrate_subtitle }}</p>

    <form id="crawl-form" onsubmit="startCrawl(event)">
        <div class="form-group">
            <label>{{ t.url }}</label>
            <textarea id="target" placeholder="https://example.com https://another.com" rows="2" required></textarea>
        </div>

        <div class="form-group">
            <label>{{ t.keywords }}</label>
            <input type="text" id="keywords" placeholder="e.g. rick and morty, adult swim">
        </div>

        <div class="row">
            <div class="form-group">
                <label>{{ t.mode }}</label>
                <select id="mode">
                    <option value="all">{{ t.everything }}</option>
                    <option value="media" selected>{{ t.all_media }}</option>
                    <option value="images">{{ t.images_only }}</option>
                    <option value="videos">{{ t.videos_only }}</option>
                    <option value="text">{{ t.text_only }}</option>
                </select>
            </div>
            <div class="form-group">
                <label>{{ t.depth }}</label>
                <input type="number" id="depth" value="9" min="-1" max="15">
            </div>
            <div class="form-group">
                <label>{{ t.max_pages }}</label>
                <input type="number" id="max_pages" value="-1" min="-1">
            </div>
        </div>

        <div class="form-group checkbox-group" style="display:flex; flex-wrap:wrap; gap:20px;">
            <div>
                <input type="checkbox" id="fresh" checked>
                <label for="fresh" style="display:inline; margin:0;">{{ t.fresh_start }}</label>
            </div>
            <div>
                <input type="checkbox" id="fast">
                <label for="fast" style="display:inline; margin:0;">{{ t.fast_mode }}</label>
            </div>
            <div>
                <input type="checkbox" id="screenshots" checked>
                <label for="screenshots" style="display:inline; margin:0;">{{ t.screenshots }}</label>
            </div>
            <div>
                <input type="checkbox" id="hydra">
                <label for="hydra" style="display:inline; margin:0;" title="Parse RSS/Atom/Sitemap for fast discovery">{{ t.hydra }}</label>
            </div>
        </div>

        <button type="submit" id="start-btn">{{ t.start_crawl }}</button>
    </form>

    <div class="jobs-section">
        <h2>{{ t.recent_jobs }}</h2>
        <div id="jobs">{{ t.loading }}</div>
    </div>

    <div id="console-modal" class="console-modal" onclick="if(event.target===this)closeConsole()">
        <div class="console-header">
            <h3 id="console-title">{{ t.console }}</h3>
            <button class="console-close" onclick="closeConsole()">{{ t.close }}</button>
        </div>
        <div id="console-logs" class="console-body"></div>
    </div>
</div>
{% endblock %}

{% block scripts %}
<script>
// Translation object for JavaScript
const T = {{ t_json|safe }};
const CRAWL_DISABLED = {{ 'true' if crawl_disabled else 'false' }};

// Disable form when crawling is disabled
if (CRAWL_DISABLED) {
    document.addEventListener('DOMContentLoaded', () => {
        const form = document.getElementById('crawl-form');
        const inputs = form.querySelectorAll('input, select, textarea, button');
        inputs.forEach(el => {
            el.disabled = true;
            el.style.opacity = '0.5';
            el.style.cursor = 'not-allowed';
        });
        const banner = document.createElement('div');
        banner.style.cssText = 'background:#442;border:1px solid #664;padding:12px 20px;border-radius:8px;margin-bottom:20px;color:#ffa;';
        banner.innerHTML = T.crawl_disabled;
        form.parentNode.insertBefore(banner, form);
    });
}

async function startCrawl(e) {
    e.preventDefault();
    if (CRAWL_DISABLED) return;

    const btn = document.getElementById('start-btn');
    btn.disabled = true;
    btn.textContent = T.starting;

    const keywordsRaw = document.getElementById('keywords').value;
    const keywords = keywordsRaw.split(/[,\s]+/).map(k => k.trim()).filter(k => k.length > 0);

    const targetsRaw = document.getElementById('target').value;
    const targets = targetsRaw.split(/[,\s\n]+/).map(t => t.trim()).filter(t => t.length > 0 && t.startsWith('http'));

    if (targets.length === 0) {
        alert(T.valid_uri);
        btn.disabled = false;
        btn.textContent = T.start_crawl;
        return;
    }

    const payload = {
        targets: targets,
        keywords: keywords,
        mode: document.getElementById('mode').value,
        depth: parseInt(document.getElementById('depth').value),
        max_pages: parseInt(document.getElementById('max_pages').value),
        fresh: document.getElementById('fresh').checked,
        fast: document.getElementById('fast').checked,
        screenshots: document.getElementById('screenshots').checked,
        hydra: document.getElementById('hydra').checked
    };

    try {
        const res = await fetch('/api/crawl', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });

        if (!res.ok) {
            const err = await res.json();
            btn.textContent = T.error + ': ' + (err.detail || T.load_error);
            btn.style.background = '#c0392b';
            setTimeout(() => { btn.textContent = T.start_crawl; btn.style.background = ''; }, 3000);
        } else {
            document.getElementById('target').value = '';
            document.getElementById('keywords').value = '';
            btn.textContent = T.started_ok;
            btn.style.background = '#27ae60';
            loadJobs();
            setTimeout(() => { btn.textContent = T.start_crawl; btn.style.background = ''; }, 2000);
        }
    } catch (err) {
        btn.textContent = T.error;
        btn.style.background = '#c0392b';
        setTimeout(() => { btn.textContent = T.start_crawl; btn.style.background = ''; }, 3000);
    }
    btn.disabled = false;
}

async function pauseJob(jobId) {
    try {
        const res = await fetch('/api/crawl/jobs/' + jobId + '/pause', { method: 'POST' });
        if (res.ok) loadJobs();
        else { const err = await res.json(); alert(T.error + ': ' + (err.detail || T.load_error)); }
    } catch (err) { alert(T.error + ': ' + err.message); }
}

async function resumeJob(jobId) {
    try {
        const res = await fetch('/api/crawl/jobs/' + jobId + '/resume', { method: 'POST' });
        if (res.ok) loadJobs();
        else { const err = await res.json(); alert(T.error + ': ' + (err.detail || T.load_error)); }
    } catch (err) { alert(T.error + ': ' + err.message); }
}

function loadJobSettings(job) {
    document.getElementById('target').value = job.target_uri || '';
    document.getElementById('keywords').value = (JSON.parse(job.keywords || '[]')).join(', ');
    document.getElementById('mode').value = job.mode || 'all';
    document.getElementById('depth').value = job.depth || 15;
    document.getElementById('max_pages').value = job.max_pages || -1;
    document.getElementById('fast').checked = !!job.fast;
    document.getElementById('screenshots').checked = job.screenshots !== 0;
    document.getElementById('fresh').checked = false;
    document.getElementById('target').scrollIntoView({ behavior: 'smooth' });
    document.getElementById('target').focus();
}

async function deleteJob(jobId) {
    if (!confirm(T.delete + '?')) return;
    try {
        const res = await fetch('/api/crawl/jobs/' + jobId, { method: 'DELETE' });
        if (res.ok) loadJobs();
        else { const err = await res.json(); alert(T.error + ': ' + (err.detail || T.load_error)); }
    } catch (err) { alert(T.error + ': ' + err.message); }
}

// Track active replays to prevent double-clicking
const activeReplays = new Set();

function replayJob(jobId) {
    // Prevent duplicate replays of same job
    if (activeReplays.has(jobId)) {
        alert(T.replay_already_running + ' #' + jobId);
        return;
    }

    activeReplays.add(jobId);

    // Update button to show active state
    const btn = document.querySelector(`button[onclick="replayJob(${jobId})"]`);
    if (btn) {
        btn.textContent = T.replaying;
        btn.disabled = true;
    }

    // Open live feed page with replay parameter
    const replayWindow = window.open('/live?replay=' + jobId, '_blank');

    // Listen for window close to reset state (works for same-origin)
    const checkClosed = setInterval(() => {
        if (replayWindow && replayWindow.closed) {
            clearInterval(checkClosed);
            activeReplays.delete(jobId);
            if (btn) {
                btn.textContent = T.replay;
                btn.disabled = false;
            }
        }
    }, 1000);

    // Auto-reset after 5 minutes as fallback
    setTimeout(() => {
        clearInterval(checkClosed);
        activeReplays.delete(jobId);
        if (btn) {
            btn.textContent = T.replay;
            btn.disabled = false;
        }
    }, 300000);
}

// Recrawl all sources from an import job (async background crawls)
async function recrawlSources(jobId, sourceCount) {
    if (!confirm(T.confirm_recrawl + '\n\n' + T.recrawl_async_note)) {
        return;
    }

    const btn = document.querySelector(`button[onclick="recrawlSources(${jobId}, ${sourceCount})"]`);
    if (btn) {
        btn.textContent = T.starting;
        btn.disabled = true;
    }

    try {
        const res = await fetch('/api/crawl/jobs/' + jobId + '/recrawl-sources', { method: 'POST' });
        const data = await res.json();

        if (res.ok) {
            alert(T.started_jobs + '\n\nJob IDs: ' + data.job_ids.join(', '));
            loadJobs(); // Refresh to show new jobs
        } else {
            alert(T.error + ': ' + (data.detail || T.load_error));
        }
    } catch (err) {
        alert(T.error + ': ' + err.message);
    } finally {
        if (btn) {
            btn.textContent = T.recrawl_sources + ' (' + sourceCount + ')';
            btn.disabled = false;
        }
    }
}

function formatBytes(bytes) {
    if (!bytes) return '';
    if (bytes < 1024) return bytes + ' B';
    if (bytes < 1024*1024) return (bytes/1024).toFixed(1) + ' KB';
    if (bytes < 1024*1024*1024) return (bytes/1024/1024).toFixed(1) + ' MB';
    return (bytes/1024/1024/1024).toFixed(1) + ' GB';
}

function formatDuration(startedAt, completedAt) {
    if (!startedAt || !completedAt) return '';
    const sec = Math.round((new Date(completedAt) - new Date(startedAt)) / 1000);
    if (sec < 60) return sec + 's';
    if (sec < 3600) return Math.floor(sec / 60) + 'm ' + (sec % 60) + 's';
    return Math.floor(sec / 3600) + 'h ' + Math.floor((sec % 3600) / 60) + 'm';
}

async function loadJobs() {
    try {
        const res = await fetch('/api/crawl/jobs');
        const jobs = await res.json();
        const container = document.getElementById('jobs');

        if (jobs.length === 0) {
            container.innerHTML = '<div class="no-jobs">' + T.no_jobs + '</div>';
            return;
        }

        const hasRunning = jobs.some(j => j.status === 'running');
        container.innerHTML = jobs.map(job => renderJob(job)).join('');
        setTimeout(loadJobs, hasRunning ? 3000 : 10000);
    } catch (err) {
        console.error('loadJobs error:', err);
        document.getElementById('jobs').innerHTML = '<div class="no-jobs">' + T.load_jobs_error + ': ' + err.message + '</div>';
        setTimeout(loadJobs, 10000);
    }
}

function renderJob(job) {
    let stats = {}, keywords = [];
    try { stats = job.stats ? JSON.parse(job.stats) : {}; } catch(e) {}
    try { keywords = job.keywords ? JSON.parse(job.keywords) : []; } catch(e) {}

    const isBackfill = job.job_kind === 'backfill';
    const kindLabel = isBackfill ? '🔄 ' + job.job_type : '🕷️ ' + (job.mode || 'all');

    if (isBackfill) {
        const progress = job.total_records > 0 ? Math.round((job.processed_records / job.total_records) * 100) : 0;
        return `<div class="job ${job.status}">
            <div class="job-header"><span class="job-id">${kindLabel} #${job.id}</span><span class="job-status ${job.status}">${T[job.status] || job.status}</span></div>
            <div class="job-target">${job.domain_filter || T.all_domains}</div>
            <div class="job-meta">${job.processed_records}/${job.total_records} | ${job.error_count} ${T.error} | ${T.started}: ${new Date(job.started_at).toLocaleString()}</div>
            ${job.status === 'running' ? '<div class="progress-bar"><div class="progress-bar-fill" style="width:' + progress + '%"></div></div>' : ''}
        </div>`;
    }

    const crawled = stats.pages_crawled || 0, pending = stats.pages_pending || 0;
    const crawlProgress = (crawled + pending) > 0 ? Math.round((crawled / (crawled + pending)) * 100) : 0;

    const calcEta = () => {
        const started = stats.crawl_started || 0;
        if (crawled === 0 || started === 0 || pending === 0) return '';
        const elapsed = Date.now() / 1000 - started;
        if (elapsed < 5) return '';
        const etaSec = pending / (crawled / elapsed);
        if (etaSec < 60) return '~' + Math.round(etaSec) + 's';
        if (etaSec < 3600) return '~' + Math.round(etaSec / 60) + 'm';
        return '~' + (etaSec / 3600).toFixed(1) + 'h';
    };

    const liveStats = job.status === 'running' ? `<div class="job-stats live">
        <span>📄 ${crawled}${pending ? '/' + (crawled + pending) : ''} pages${calcEta() ? ' ⏱️' + calcEta() : ''}</span>
        <span>🖼️ ${stats.media_found || 0} found</span><span>💾 ${stats.media_downloaded || 0} saved</span>
        <span>📸 ${stats.screenshots_taken || 0}</span><span>♻️ ${(stats.duplicates_skipped || 0) + (stats.content_exists || 0)} dupes</span>
        ${stats.bytes_stored ? '<span>📦 ' + formatBytes(stats.bytes_stored) + '</span>' : ''}
    </div>` : '';

    let jobLinks = '';
    try { const h = new URL(job.target_uri).hostname; jobLinks = `<span class="job-links"><a href="/?q=${encodeURIComponent(h)}">${T.search}</a> <a href="/live?domain=${encodeURIComponent(h)}">${T.live}</a></span>`; } catch(e) {}

    return `<div class="job ${job.status}">
        <div class="job-header"><span class="job-id">${kindLabel} #${job.id}</span><span class="job-status ${job.status}">${T[job.status] || job.status}</span></div>
        <div class="job-target"><a href="${job.target_uri}" target="_blank">${job.target_uri}</a>${jobLinks}</div>
        <div class="job-meta">${keywords.length ? T.filter + ': ' + keywords.join(', ') + ' | ' : ''}${T.started}: ${new Date(job.started_at).toLocaleString()}</div>
        ${job.status === 'completed' ? `<div class="job-stats">
            <span>⏱️ ${formatDuration(job.started_at, job.completed_at)}</span><span>📄 ${crawled} pages</span>
            <span>🖼️ ${stats.media_found || 0}</span><span>💾 ${stats.media_downloaded || 0}</span>
            <span>📸 ${stats.screenshots_taken || 0}</span><span>♻️ ${(stats.duplicates_skipped || 0) + (stats.content_exists || 0)}</span>
            ${stats.bytes_stored ? '<span>📦 ' + formatBytes(stats.bytes_stored) + '</span>' : ''}
        </div>` : ''}
        ${liveStats}
        ${job.status === 'running' && job.job_kind === 'crawl' ? '<div class="progress-bar"><div class="progress-bar-fill" style="width:' + crawlProgress + '%"></div></div>' : ''}
        <div class="job-actions">
            ${job.status === 'running' && job.job_kind === 'crawl' ? '<button class="pause-btn" onclick="pauseJob(' + job.id + ')">' + T.pause + '</button>' : ''}
            ${(job.status === 'paused' || job.status === 'cancelled') && job.job_kind === 'crawl' ? '<button class="start-btn" onclick="resumeJob(' + job.id + ')">' + T.resume + '</button>' : ''}
            ${(job.status === 'completed' || job.status === 'failed' || job.status === 'cancelled') && job.job_kind === 'crawl' && job.mode !== 'import' ? "<button class=\"recrawl-btn\" onclick='loadJobSettings(" + JSON.stringify(job) + ")'>↻</button>" : ''}
            ${job.status === 'completed' && job.job_kind === 'crawl' ? '<button class="replay-btn" onclick="replayJob(' + job.id + ')">' + T.replay + '</button>' : ''}
            ${job.status === 'completed' && job.mode === 'import' && stats.imported_sources?.length ? '<button class="recrawl-btn" onclick="recrawlSources(' + job.id + ', ' + stats.imported_sources.length + ')">' + T.recrawl_sources + ' (' + stats.imported_sources.length + ')</button>' : ''}
            <button class="console-btn" onclick="openConsole(${job.id})">${T.console}</button>
            ${job.status !== 'running' ? '<button class="delete-btn" onclick="deleteJob(' + job.id + ')">' + T.delete + '</button>' : ''}
        </div>
    </div>`;
}

loadJobs();

let consoleJobId = null, consoleInterval = null;

function openConsole(jobId) {
    consoleJobId = jobId;
    document.getElementById('console-modal').classList.add('active');
    document.getElementById('console-title').textContent = T.console + ' #' + jobId;
    document.getElementById('console-logs').textContent = T.loading;
    fetchLogs();
    consoleInterval = setInterval(fetchLogs, 2000);
}

function closeConsole() {
    document.getElementById('console-modal').classList.remove('active');
    if (consoleInterval) { clearInterval(consoleInterval); consoleInterval = null; }
    consoleJobId = null;
}

async function fetchLogs() {
    if (!consoleJobId) return;
    try {
        const res = await fetch('/api/crawl/jobs/' + consoleJobId + '/logs');
        const logs = await res.text();
        const el = document.getElementById('console-logs');
        const wasAtBottom = el.scrollHeight - el.clientHeight <= el.scrollTop + 50;
        el.innerHTML = logs ? logs.split('\n').map(line => {
            let cls = 'log-line';
            if (line.includes(' INFO:')) cls += ' log-INFO';
            else if (line.includes(' WARNING:')) cls += ' log-WARNING';
            else if (line.includes(' ERROR:')) cls += ' log-ERROR';
            return '<div class="' + cls + '">' + line.replace(/</g, '&lt;') + '</div>';
        }).join('') : T.no_logs;
        if (wasAtBottom) el.scrollTop = el.scrollHeight;
    } catch (err) {
        document.getElementById('console-logs').textContent = T.error + ': ' + err.message;
    }
}

document.addEventListener('keydown', e => { if (e.key === 'Escape') closeConsole(); });
</script>
{% endblock %}
