<?php
 
 
$feedSources = Database::fetchAll('SELECT * FROM feed_sources ORDER BY id DESC');
$feedPending = [];
foreach (Database::fetchAll("SELECT source_id, COUNT(*) c FROM feed_items WHERE status='pending' GROUP BY source_id") as $r) {
    $feedPending[(int) $r['source_id']] = (int) $r['c'];
}
$feedVtypes = Video::VALID_TYPES;
$feedCanonical = FeedParser::CANONICAL_KEYS;
$feedCsrf = Router::csrfToken();

if (!function_exists('feed_rel')) {
    function feed_rel(?string $dt): string {
        if (!$dt) return 'never'; $t = strtotime($dt); if (!$t) return 'never';
        $d = max(0, time() - $t);
        if ($d < 60) return 'just now'; if ($d < 3600) return floor($d/60).'m ago';
        if ($d < 86400) return floor($d/3600).'h ago'; return floor($d/86400).'d ago';
    }
    function feed_sched(int $s): string {
        if ($s <= 0) return 'Manual'; if ($s % 86400 === 0) return 'Every '.($s/86400).'d';
        if ($s % 3600 === 0) return 'Every '.($s/3600).'h'; if ($s % 60 === 0) return 'Every '.($s/60).'m';
        return 'Every '.$s.'s';
    }
}
$feedTypeBadge = ['mrss'=>'bg-violet-50 text-violet-700','rss'=>'bg-violet-50 text-violet-700','csv'=>'bg-amber-50 text-amber-700','json'=>'bg-sky-50 text-sky-700'];
?>
<div id="feedApp">
  
  <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-5">
    <p class="text-sm text-gray-500 max-w-2xl">Subscribe to a remote <span class="font-medium text-gray-700">feed URL</span> (MRSS / RSS, or a CSV/JSON feed served at a URL) and auto-import on a schedule, with automatic de-duplication. Unlike the <span class="font-medium text-gray-700">Upload File</span> tab — a one-time file you upload — a feed keeps pulling new <strong>videos &amp; galleries (Albums)</strong> as the source updates. Choose <span class="font-medium text-gray-700">Embed</span> (store the player link) or <span class="font-medium text-gray-700">Download</span> (fetch the file to your CDN &amp; transcode).</p>
    <button type="button" onclick="FA.openWizard(0)" class="shrink-0 inline-flex items-center gap-2 px-4 py-2 bg-primary-600 text-white text-sm font-medium rounded-lg hover:bg-primary-700 transition-colors">
      <svg viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 5v14M5 12h14"/></svg> Add Source
    </button>
  </div>

  
  <details class="mb-5 bg-white border border-gray-200 rounded-lg group">
    <summary class="flex items-center gap-2 px-4 py-3 cursor-pointer select-none text-sm font-medium text-gray-700 hover:bg-gray-50 rounded-lg">
      <svg viewBox="0 0 24 24" class="w-4 h-4 text-primary-600 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/></svg>
      See a correct feed example <span class="text-gray-400 font-normal">— videos &amp; galleries</span>
    </summary>
    <div class="px-4 pb-4 pt-1 border-t border-gray-100 space-y-4">
      <p class="text-xs text-gray-500 leading-relaxed">A single feed can mix <strong>videos</strong> and <strong>galleries</strong>. An item becomes an <span class="font-semibold text-fuchsia-700">Album</span> when it carries image media and no video &mdash; in MRSS that&rsquo;s one or more <code class="px-1 bg-gray-100 rounded text-gray-700">&lt;media:content medium="image"&gt;</code>; in CSV/JSON an <code class="px-1 bg-gray-100 rounded text-gray-700">images</code> list (or <code class="px-1 bg-gray-100 rounded text-gray-700">media_type=album</code>). Everything else imports as a video.</p>

      <div>
        <div class="flex items-center justify-between mb-1">
          <span class="text-[11px] font-semibold text-gray-500 uppercase tracking-wide">MRSS / RSS &middot; recommended</span>
          <button type="button" onclick="var c=this.closest('div').nextElementSibling.querySelector('code');if(navigator.clipboard)navigator.clipboard.writeText(c.innerText);var t=this;t.textContent='Copied!';setTimeout(function(){t.textContent='Copy';},1200);" class="text-[11px] text-primary-600 hover:text-primary-700 font-medium">Copy</button>
        </div>
        <pre class="text-[11px] leading-relaxed bg-gray-900 text-gray-100 rounded-lg p-3 overflow-x-auto"><code>&lt;rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/"&gt;
  &lt;channel&gt;
    &lt;title&gt;My Network &mdash; Newest&lt;/title&gt;

    &lt;!-- A VIDEO: has a video media:content --&gt;
    &lt;item&gt;
      &lt;title&gt;Sunset Drive&lt;/title&gt;
      &lt;guid&gt;vid-1001&lt;/guid&gt;
      &lt;link&gt;https://example.com/v/1001&lt;/link&gt;
      &lt;description&gt;A short clip.&lt;/description&gt;
      &lt;pubDate&gt;Tue, 30 Jun 2026 10:00:00 +0000&lt;/pubDate&gt;
      &lt;media:content url="https://example.com/embed/1001" medium="video" duration="612"/&gt;
      &lt;media:thumbnail url="https://example.com/thumbs/1001.jpg"/&gt;
      &lt;media:category&gt;Amateur&lt;/media:category&gt;
      &lt;media:keywords&gt;outdoor, hd&lt;/media:keywords&gt;
    &lt;/item&gt;

    &lt;!-- A GALLERY (Album): only image media:content, no video --&gt;
    &lt;item&gt;
      &lt;title&gt;Golden Hour Set&lt;/title&gt;
      &lt;guid&gt;alb-2002&lt;/guid&gt;
      &lt;link&gt;https://example.com/g/2002&lt;/link&gt;
      &lt;description&gt;A 3-photo set.&lt;/description&gt;
      &lt;pubDate&gt;Tue, 30 Jun 2026 10:05:00 +0000&lt;/pubDate&gt;
      &lt;media:content url="https://example.com/pics/2002/01.jpg" medium="image"/&gt;
      &lt;media:content url="https://example.com/pics/2002/02.jpg" medium="image"/&gt;
      &lt;media:content url="https://example.com/pics/2002/03.jpg" medium="image"/&gt;
      &lt;media:thumbnail url="https://example.com/pics/2002/cover.jpg"/&gt;
      &lt;media:category&gt;Glamour&lt;/media:category&gt;
      &lt;media:keywords&gt;solo, studio&lt;/media:keywords&gt;
    &lt;/item&gt;
  &lt;/channel&gt;
&lt;/rss&gt;</code></pre>
      </div>

      <div>
        <div class="flex items-center justify-between mb-1">
          <span class="text-[11px] font-semibold text-gray-500 uppercase tracking-wide">JSON</span>
          <button type="button" onclick="var c=this.closest('div').nextElementSibling.querySelector('code');if(navigator.clipboard)navigator.clipboard.writeText(c.innerText);var t=this;t.textContent='Copied!';setTimeout(function(){t.textContent='Copy';},1200);" class="text-[11px] text-primary-600 hover:text-primary-700 font-medium">Copy</button>
        </div>
        <pre class="text-[11px] leading-relaxed bg-gray-900 text-gray-100 rounded-lg p-3 overflow-x-auto"><code>[
  {
    "title": "Sunset Drive",
    "guid": "vid-1001",
    "embed_url": "https://example.com/embed/1001",
    "thumbnail": "https://example.com/thumbs/1001.jpg",
    "duration": 612,
    "categories": "Amateur",
    "tags": "outdoor, hd",
    "performers": "Alex Ray"
  },
  {
    "title": "Golden Hour Set",
    "guid": "alb-2002",
    "media_type": "album",
    "images": [
      "https://example.com/pics/2002/01.jpg",
      "https://example.com/pics/2002/02.jpg",
      "https://example.com/pics/2002/03.jpg"
    ],
    "thumbnail": "https://example.com/pics/2002/cover.jpg",
    "categories": "Glamour",
    "performers": "Jane Doe"
  }
]</code></pre>
      </div>

      <p class="text-[11px] text-gray-400 leading-relaxed">For galleries the first image is the cover at import; once the <strong>CTR</strong> plugin is active the best-performing image becomes the cover automatically. Give each item a stable <code class="px-0.5 bg-gray-100 rounded text-gray-600">guid</code> so re-imports de-duplicate instead of creating copies.</p>
    </div>
  </details>

  <?php if (empty($feedSources)): ?>
  <div class="bg-white border border-dashed border-gray-300 rounded-lg p-10 text-center">
    <div class="w-14 h-14 mx-auto rounded-lg bg-primary-50 flex items-center justify-center mb-3">
      <svg viewBox="0 0 24 24" class="w-7 h-7 text-primary-600" fill="none" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" d="M4 11a9 9 0 0 1 9 9M4 4a16 16 0 0 1 16 16"/><circle cx="5" cy="19" r="1.6" fill="currentColor" stroke="none"/></svg>
    </div>
    <h3 class="text-base font-bold text-gray-900">Add your first feed source</h3>
    <p class="text-sm text-gray-500 mt-1 max-w-md mx-auto">Paste a feed URL and we'll preview the videos before importing. Most tube networks provide an <span class="font-medium">MRSS</span> feed; spreadsheets work as <span class="font-medium">CSV</span>.</p>
    <button type="button" onclick="FA.openWizard(0)" class="mt-4 inline-flex items-center gap-2 px-4 py-2 bg-primary-600 text-white text-sm font-medium rounded-lg hover:bg-primary-700 transition-colors">
      <svg viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 5v14M5 12h14"/></svg> Add Source
    </button>
  </div>
  <?php else: ?>
  <div class="space-y-3">
    <?php foreach ($feedSources as $s): $id=(int)$s['id']; $pend=$feedPending[$id] ?? 0; ?>
    <div class="bg-white border border-gray-200 rounded-lg" data-src="<?= $id ?>">
      <div class="p-4">
        <div class="flex items-start justify-between gap-3">
          <div class="min-w-0">
            <div class="flex items-center gap-2 flex-wrap">
              <h3 class="font-semibold text-gray-900 truncate"><?= htmlspecialchars($s['name']) ?></h3>
              <span class="text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded <?= $feedTypeBadge[$s['type']] ?? 'bg-gray-100 text-gray-600' ?>"><?= htmlspecialchars($s['type']) ?></span>
              <span class="text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded <?= $s['mode']==='download' ? 'bg-emerald-50 text-emerald-700' : 'bg-blue-50 text-blue-700' ?>"><?= $s['mode']==='download' ? 'Download' : 'Embed' ?></span>
            </div>
            <a href="<?= htmlspecialchars($s['url']) ?>" target="_blank" rel="noopener" class="block text-xs text-gray-400 font-mono truncate mt-1 hover:text-primary-600 max-w-xl"><?= htmlspecialchars($s['url']) ?></a>
          </div>
          <button type="button" onclick="FA.toggle(<?= $id ?>, this)" class="shrink-0 relative inline-flex items-center h-5 w-9 rounded-full transition-colors <?= $s['enabled'] ? 'bg-primary-600' : 'bg-gray-300' ?>" title="<?= $s['enabled'] ? 'Enabled' : 'Disabled' ?>">
            <span class="inline-block h-4 w-4 rounded-full bg-white shadow transform transition <?= $s['enabled'] ? 'translate-x-4' : 'translate-x-0.5' ?>"></span>
          </button>
        </div>

        <div class="grid grid-cols-2 sm:grid-cols-5 gap-2 mt-3.5" data-stats="<?= $id ?>">
          <div class="bg-gray-50 border border-gray-100 rounded-lg px-3 py-2"><div class="text-[11px] text-gray-400 font-medium">Fetched</div><div class="text-base font-bold text-gray-700" data-k="fetched"><?= (int)$s['stat_fetched'] ?></div></div>
          <div class="bg-emerald-50 border border-emerald-100 rounded-lg px-3 py-2"><div class="text-[11px] text-emerald-600 font-medium">Imported</div><div class="text-base font-bold text-emerald-700" data-k="imported"><?= (int)$s['stat_imported'] ?></div></div>
          <div class="bg-gray-50 border border-gray-100 rounded-lg px-3 py-2"><div class="text-[11px] text-gray-400 font-medium">Skipped</div><div class="text-base font-bold text-gray-500" data-k="skipped"><?= (int)$s['stat_skipped'] ?></div></div>
          <div class="bg-red-50 border border-red-100 rounded-lg px-3 py-2"><div class="text-[11px] text-red-400 font-medium">Failed</div><div class="text-base font-bold text-red-600" data-k="failed"><?= (int)$s['stat_failed'] ?></div></div>
          <div class="bg-amber-50 border border-amber-100 rounded-lg px-3 py-2"><div class="text-[11px] text-amber-500 font-medium">In queue</div><div class="text-base font-bold text-amber-700" data-k="pending"><?= $pend ?></div></div>
        </div>

        <div class="mt-3 text-xs text-gray-500 flex items-center gap-2 flex-wrap" data-line="<?= $id ?>">
          <span class="inline-flex items-center gap-1"><svg viewBox="0 0 24 24" class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg><?= feed_sched((int)$s['schedule_interval']) ?></span>
          <span class="text-gray-300">·</span><span>Last run <?= feed_rel($s['last_run_at']) ?></span>
          <?php if ($s['last_message']): ?><span class="text-gray-300">·</span><span class="truncate max-w-xs <?= $s['last_status']==='error' ? 'text-red-500' : '' ?>"><?= htmlspecialchars($s['last_message']) ?></span><?php endif; ?>
        </div>

        <div class="flex items-center gap-2 mt-3.5 flex-wrap">
          <button type="button" onclick="FA.fetchNow(<?= $id ?>)" class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-primary-600 text-white text-[13px] font-medium rounded-lg hover:bg-primary-700 transition-colors">
            <svg viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg> Fetch now
          </button>
          <button type="button" onclick="FA.runQueue(<?= $id ?>)" data-runbtn="<?= $id ?>" class="inline-flex items-center gap-1.5 px-3 py-1.5 border border-gray-300 text-gray-600 text-[13px] font-medium rounded-lg hover:border-primary-400 hover:text-primary-700 transition-colors <?= $pend>0 ? '' : 'hidden' ?>">
            <svg viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 3l14 9-14 9z"/></svg> Process queue
          </button>
          <button type="button" onclick="FA.openItems(<?= $id ?>, this.dataset.name)" data-name="<?= htmlspecialchars($s['name'], ENT_QUOTES) ?>" class="inline-flex items-center gap-1.5 px-3 py-1.5 border border-gray-300 text-gray-600 text-[13px] font-medium rounded-lg hover:border-gray-400 transition-colors">
            <svg viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/></svg> Items
          </button>
          <div class="flex-1"></div>
          <button type="button" onclick="FA.openWizard(<?= $id ?>)" class="text-gray-400 hover:text-primary-600 p-1.5 rounded-lg hover:bg-gray-100 transition-colors" title="Edit"><svg viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4z"/></svg></button>
          <button type="button" onclick="FA.del(<?= $id ?>, this.dataset.name)" data-name="<?= htmlspecialchars($s['name'], ENT_QUOTES) ?>" class="text-gray-400 hover:text-red-600 p-1.5 rounded-lg hover:bg-red-50 transition-colors" title="Delete"><svg viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg></button>
        </div>
        <div class="mt-3 hidden" data-prog="<?= $id ?>"><div class="h-1.5 bg-gray-100 rounded-full overflow-hidden"><div class="h-full bg-primary-600 transition-all duration-500" style="width:0%"></div></div></div>
      </div>
    </div>
    <?php endforeach; ?>
  </div>
  <?php endif; ?>
</div>


<div id="feedModal" class="fixed inset-0 z-[60] hidden" aria-modal="true" role="dialog">
  <div class="absolute inset-0 bg-black/40"></div>
  <div class="absolute inset-0 overflow-y-auto p-4 flex items-start justify-center">
    <div class="relative bg-white rounded-lg shadow-2xl w-full max-w-2xl my-8">
      <div class="flex items-center justify-between px-5 py-4 border-b border-gray-200 sticky top-0 bg-white rounded-t-lg z-10">
        <h2 class="text-base font-bold text-gray-900" id="fmTitle">Add Feed Source</h2>
        <button type="button" onclick="FA.closeWizard()" class="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"><svg viewBox="0 0 24 24" class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
      </div>
      <div class="px-5 py-5 space-y-5">
        <input type="hidden" id="fm_id" value="0"><input type="hidden" id="fm_type" value="mrss">
        <div>
          <label class="block text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2">1 · Source</label>
          <input id="fm_name" type="text" placeholder="Source name (e.g. Network XYZ — Newest)" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none mb-2">
          <div class="flex gap-2">
            <input id="fm_url" type="url" placeholder="https://… feed URL (MRSS / RSS / CSV / JSON)" class="flex-1 px-3 py-2 text-sm font-mono border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none">
            <button type="button" onclick="FA.detect()" id="fm_detect" class="shrink-0 inline-flex items-center gap-1.5 px-4 bg-gray-800 hover:bg-gray-900 text-white text-sm font-medium rounded-lg transition-colors"><svg viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg> Detect &amp; Preview</button>
          </div>
          <p class="text-xs text-gray-400 mt-1.5">Tip: use <code class="bg-gray-100 px-1 rounded">{page}</code> in the URL or set a page parameter below for multi-page feeds.</p>
        </div>
        <div id="fm_preview" class="hidden">
          <div class="flex items-center justify-between mb-2"><label class="block text-xs font-semibold text-gray-500 uppercase tracking-wide">Preview</label><span id="fm_preview_meta" class="text-xs font-medium text-emerald-600"></span></div>
          <div id="fm_preview_body" class="border border-gray-200 rounded-lg p-3 bg-gray-50 max-h-64 overflow-y-auto"></div>
        </div>
        <div>
          <label class="block text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2">2 · Import settings</label>
          <div class="grid grid-cols-2 gap-3">
            <label class="col-span-2 sm:col-span-1 border border-gray-200 rounded-lg p-3 cursor-pointer hover:border-primary-300 has-[:checked]:border-primary-500 has-[:checked]:bg-primary-50 transition-colors">
              <div class="flex items-center gap-2"><input type="radio" name="fm_mode" value="embed" class="text-primary-600 focus:ring-primary-500" checked><span class="font-medium text-sm text-gray-800">Embed</span></div>
              <p class="text-xs text-gray-500 mt-1">Store the player link. Instant, no storage/bandwidth. Plays from the source.</p>
            </label>
            <label class="col-span-2 sm:col-span-1 border border-gray-200 rounded-lg p-3 cursor-pointer hover:border-primary-300 has-[:checked]:border-primary-500 has-[:checked]:bg-primary-50 transition-colors">
              <div class="flex items-center gap-2"><input type="radio" name="fm_mode" value="download" class="text-primary-600 focus:ring-primary-500"><span class="font-medium text-sm text-gray-800">Download</span></div>
              <p class="text-xs text-gray-500 mt-1">Fetch the file to your CDN &amp; transcode to multiple qualities. Full control.</p>
            </label>
          </div>
          <div class="grid grid-cols-2 gap-3 mt-3">
            <div><label class="block text-xs font-medium text-gray-700 mb-1">Video type</label><select id="fm_vtype" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none"><?php foreach ($feedVtypes as $vt): ?><option value="<?= htmlspecialchars($vt) ?>"><?= ucfirst(htmlspecialchars($vt)) ?></option><?php endforeach; ?></select></div>
            <div><label class="block text-xs font-medium text-gray-700 mb-1">On duplicate</label><select id="fm_dedup" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none"><option value="skip">Skip (recommended)</option><option value="update">Update existing</option></select></div>
            <div><label class="block text-xs font-medium text-gray-700 mb-1">Schedule</label><select id="fm_sched" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none"><option value="0">Manual only</option><option value="900">Every 15 min</option><option value="3600">Every hour</option><option value="21600">Every 6 hours</option><option value="86400">Every day</option></select></div>
            <div><label class="block text-xs font-medium text-gray-700 mb-1">Max pages</label><input id="fm_pages" type="number" min="1" max="50" value="1" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none"></div>
            <div><label class="block text-xs font-medium text-gray-700 mb-1">Page parameter</label><input id="fm_pageparam" type="text" value="page" class="w-full px-3 py-2 text-sm font-mono border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none"></div>
            <label class="flex items-center gap-2 mt-6 cursor-pointer select-none"><input id="fm_autopub" type="checkbox" checked class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"><span class="text-sm text-gray-700">Publish immediately</span></label>
          </div>
        </div>
        <div id="fm_mapwrap" class="hidden">
          <label class="block text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2">3 · Field mapping <span class="text-gray-400 normal-case font-normal">— match your columns to video fields</span></label>
          <div id="fm_mapgrid" class="grid grid-cols-2 gap-2"></div>
        </div>
      </div>
      <div class="flex items-center justify-end gap-2 px-5 py-4 border-t border-gray-200 sticky bottom-0 bg-white rounded-b-lg">
        <span id="fm_err" class="text-sm text-red-600 mr-auto"></span>
        <button type="button" onclick="FA.closeWizard()" class="px-4 py-2 text-sm font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors">Cancel</button>
        <button type="button" onclick="FA.save()" id="fm_save" class="px-4 py-2 bg-primary-600 text-white text-sm font-medium rounded-lg hover:bg-primary-700 transition-colors">Save source</button>
      </div>
    </div>
  </div>
</div>


<div id="itemsModal" class="fixed inset-0 z-[60] hidden" aria-modal="true" role="dialog">
  <div class="absolute inset-0 bg-black/40"></div>
  <div class="absolute inset-0 overflow-y-auto p-4 flex items-start justify-center">
    <div class="relative bg-white rounded-lg shadow-2xl w-full max-w-3xl my-8">
      <div class="flex items-center justify-between px-5 py-4 border-b border-gray-200"><h2 class="text-base font-bold text-gray-900">Queue &amp; history — <span id="im_name"></span></h2><button type="button" onclick="FA.closeItems()" class="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"><svg viewBox="0 0 24 24" class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg></button></div>
      <div class="px-5 py-3 border-b border-gray-200 flex items-center gap-2 flex-wrap">
        <select id="im_filter" onchange="FA.loadItems()" class="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 outline-none"><option value="">All statuses</option><option value="pending">Pending</option><option value="imported">Imported</option><option value="skipped">Skipped (dup)</option><option value="failed">Failed</option></select>
        <button type="button" onclick="FA.reprocess()" class="px-3 py-1.5 text-sm font-medium text-primary-700 bg-primary-50 hover:bg-primary-100 rounded-lg transition-colors">Re-queue failed</button>
      </div>
      <div id="im_body" class="px-2 py-2 max-h-[60vh] overflow-y-auto"></div>
    </div>
  </div>
</div>

<div id="fa_toast" class="fixed bottom-5 right-5 z-[70] hidden"></div>

<script>
window.FA = (function () {
  const CSRF = <?= json_encode($feedCsrf) ?>;
  const MAP_FIELDS = [
    {k:'title', label:'Title', req:true},{k:'video_url', label:'Video URL (download / direct file)'},{k:'embed_url', label:'Embed / player URL'},
    {k:'thumbnail', label:'Thumbnail URL'},{k:'description', label:'Description'},{k:'duration', label:'Duration'},{k:'guid', label:'Unique ID (for dedup)'},
    {k:'categories', label:'Categories'},{k:'tags', label:'Tags'},{k:'performers', label:'Performers'},{k:'channels', label:'Channels'},{k:'published_at', label:'Published date'},{k:'views', label:'Views'},
    {k:'images', label:'Images (Album / gallery)'},{k:'media_type', label:'Type (video / album)'},
  ];
  let previewCols = [], previewCanonical = true, polling = {}, itemsSrc = 0;
  function post(url, data){ const fd=new FormData(); fd.append('csrf_token',CSRF); for(const k in data) fd.append(k,data[k]); return fetch(url,{method:'POST',body:fd,headers:{'X-CSRF-TOKEN':CSRF}}).then(r=>r.json()); }
  function getJ(url){ return fetch(url).then(r=>r.json()); }
  function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c])); }
  function toast(msg,type){ const el=document.getElementById('fa_toast'); el.innerHTML='<div class="px-4 py-2.5 rounded-lg shadow-lg text-sm font-medium text-white '+(type==='err'?'bg-red-600':(type==='warn'?'bg-amber-600':'bg-gray-900'))+'">'+esc(msg)+'</div>'; el.classList.remove('hidden'); clearTimeout(el._t); el._t=setTimeout(()=>el.classList.add('hidden'),4000); }
  function fmtDur(s){ s=parseInt(s)||0; if(!s) return ''; const h=Math.floor(s/3600),m=Math.floor(s%3600/60),x=s%60; return (h?h+':':'')+(h?String(m).padStart(2,'0'):m)+':'+String(x).padStart(2,'0'); }

  function openWizard(id){
    document.getElementById('fm_err').textContent=''; document.getElementById('fm_preview').classList.add('hidden'); document.getElementById('fm_mapwrap').classList.add('hidden');
    document.getElementById('fm_id').value=id||0;
    if(id){ document.getElementById('fmTitle').textContent='Edit Feed Source';
      getJ('/admin/feeds/get?id='+id).then(r=>{ if(!r.ok){toast('Could not load source','err');return;} const s=r.source;
        document.getElementById('fm_name').value=s.name||''; document.getElementById('fm_url').value=s.url||''; document.getElementById('fm_type').value=s.type||'mrss';
        document.getElementById('fm_vtype').value=s.video_type||'hetero'; document.getElementById('fm_pages').value=s.max_pages||1; document.getElementById('fm_pageparam').value=s.page_param||'page';
        document.getElementById('fm_sched').value=s.schedule_interval||0; document.getElementById('fm_autopub').checked=(parseInt(s.auto_publish)===1);
        document.querySelectorAll('input[name=fm_mode]').forEach(r=>r.checked=(r.value===(s.mode||'embed')));
        const opt=s.options||{}; document.getElementById('fm_dedup').value=(opt.dedup==='update')?'update':'skip'; FA._editMapping=s.mapping||{};
        if(s.type!=='mrss'&&s.type!=='rss') previewCanonical=false;
      });
    } else { document.getElementById('fmTitle').textContent='Add Feed Source';
      ['fm_name','fm_url'].forEach(i=>document.getElementById(i).value=''); document.getElementById('fm_type').value='mrss'; document.getElementById('fm_pages').value=1;
      document.getElementById('fm_pageparam').value='page'; document.getElementById('fm_sched').value=0; document.getElementById('fm_autopub').checked=true; document.getElementById('fm_dedup').value='skip';
      document.querySelectorAll('input[name=fm_mode]').forEach(r=>r.checked=(r.value==='embed')); FA._editMapping={};
    }
    document.getElementById('feedModal').classList.remove('hidden');
  }
  function closeWizard(){ document.getElementById('feedModal').classList.add('hidden'); }
  function detect(){
    const url=document.getElementById('fm_url').value.trim(); const err=document.getElementById('fm_err'); err.textContent='';
    if(!url){ err.textContent='Enter a feed URL first.'; return; }
    const btn=document.getElementById('fm_detect'); btn.disabled=true; btn.style.opacity=.6;
    const body=document.getElementById('fm_preview_body'); body.innerHTML='<div class="text-sm text-gray-400 py-6 text-center">Fetching &amp; parsing…</div>'; document.getElementById('fm_preview').classList.remove('hidden');
    post('/admin/feeds/preview',{url:url,type:'auto',page_param:document.getElementById('fm_pageparam').value}).then(r=>{
      btn.disabled=false; btn.style.opacity=1;
      if(!r.ok){ document.getElementById('fm_preview_meta').textContent=''; body.innerHTML='<div class="text-sm text-red-600 py-6 text-center">'+esc(r.error||'Could not read feed')+'</div>'; return; }
      document.getElementById('fm_type').value=r.type; previewCols=r.columns||[]; previewCanonical=!!r.canonical;
      const n=r.count||r.items.length; document.getElementById('fm_preview_meta').textContent=n+' item'+(n===1?'':'s')+' · '+r.type.toUpperCase();
      renderPreview(r);
      if(!previewCanonical){ renderMapping(); document.getElementById('fm_mapwrap').classList.remove('hidden'); } else document.getElementById('fm_mapwrap').classList.add('hidden');
      if(!document.getElementById('fm_name').value.trim()){ try{ document.getElementById('fm_name').value=new URL(url).hostname.replace(/^www\./,''); }catch(e){} }
    }).catch(()=>{ btn.disabled=false; btn.style.opacity=1; body.innerHTML='<div class="text-sm text-red-600 py-6 text-center">Network error</div>'; });
  }
  function renderPreview(r){
    const body=document.getElementById('fm_preview_body');
    if(!r.items||!r.items.length){ body.innerHTML='<div class="text-sm text-gray-400 py-6 text-center">No items found.</div>'; return; }
    if(previewCanonical){
      body.innerHTML='<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">'+r.items.map(it=>{
        const _imgs=(it.images||'').split(/[\n,|]/).map(s=>s.trim()).filter(Boolean);
        const _isAlbum=(it.media_type==='album')||(_imgs.length>0 && !it.video_url && !it.embed_url);
        const _cover=it.thumbnail||_imgs[0]||'';
        const thumb=_cover?('<img src="'+esc(_cover)+'" class="w-full h-20 object-cover" loading="lazy" onerror="this.style.display=\'none\'">'):'<div class="w-full h-20 bg-gray-200 flex items-center justify-center text-gray-400 text-xs">no thumb</div>';
        const _badge=_isAlbum?'<span class="absolute top-1 left-1 bg-fuchsia-600 text-white text-[9px] font-bold px-1 rounded">ALBUM</span>':'';
        const dur=_isAlbum?(_imgs.length?('<span class="absolute bottom-1 right-1 bg-black/75 text-white text-[10px] px-1 rounded">'+_imgs.length+' imgs</span>'):''):(it.duration?('<span class="absolute bottom-1 right-1 bg-black/75 text-white text-[10px] px-1 rounded">'+fmtDur(it.duration)+'</span>'):'');
        return '<div class="bg-white border border-gray-200 rounded-lg overflow-hidden"><div class="relative">'+thumb+dur+_badge+'</div><div class="p-1.5 text-[11px] text-gray-700 leading-tight line-clamp-2 h-8">'+esc(it.title)+'</div></div>';
      }).join('')+'</div>';
    } else {
      const cols=r.columns||[]; const rows=r.items.slice(0,8);
      body.innerHTML='<div class="overflow-x-auto"><table class="text-[11px] w-full"><thead><tr>'+cols.map(c=>'<th class="text-left font-semibold text-gray-500 px-2 py-1 whitespace-nowrap">'+esc(c)+'</th>').join('')+'</tr></thead><tbody>'+rows.map(row=>'<tr class="border-t border-gray-100">'+cols.map(c=>'<td class="px-2 py-1 text-gray-600 max-w-[140px] truncate">'+esc(row[c]||'')+'</td>').join('')+'</tr>').join('')+'</tbody></table></div>';
    }
  }
  function renderMapping(){
    const grid=document.getElementById('fm_mapgrid'); const cols=previewCols;
    const guess=(k)=>{ const al={title:['title','name'],video_url:['video_url','url','mp4','file','video','source'],embed_url:['embed','embed_url','iframe','player'],thumbnail:['thumb','thumbnail','image','poster','screenshot'],description:['description','desc','text'],duration:['duration','length','time'],guid:['id','guid','external_id','video_id'],categories:['categories','category','cats'],tags:['tags','keywords','tag'],performers:['performers','models','actors','pornstars'],channels:['channels','channel','site','channel'],published_at:['published','date','pubdate','added'],views:['views','view_count','hits'],images:['images','gallery','galleries','photos','pics','image_urls','imageurls'],media_type:['media_type','mediatype','type','kind','item_type']}[k]||[k]; return cols.find(c=>al.some(a=>c.toLowerCase().includes(a)))||''; };
    grid.innerHTML=MAP_FIELDS.map(f=>{ const sel=(FA._editMapping&&FA._editMapping[f.k])||guess(f.k);
      const opts='<option value="">— none —</option>'+cols.map(c=>'<option value="'+esc(c)+'"'+(c===sel?' selected':'')+'>'+esc(c)+'</option>').join('');
      return '<div><label class="block text-[11px] font-medium text-gray-600 mb-0.5">'+esc(f.label)+(f.req?' <span class="text-red-500">*</span>':'')+'</label><select data-mapk="'+f.k+'" class="w-full px-2 py-1.5 text-[13px] border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 outline-none">'+opts+'</select></div>';
    }).join('');
  }
  function save(){
    const err=document.getElementById('fm_err'); err.textContent='';
    const name=document.getElementById('fm_name').value.trim(), url=document.getElementById('fm_url').value.trim();
    if(!name||!url){ err.textContent='Name and feed URL are required.'; return; }
    const mode=(document.querySelector('input[name=fm_mode]:checked')||{}).value||'embed'; const mapping={};
    document.querySelectorAll('#fm_mapgrid select[data-mapk]').forEach(s=>{ if(s.value) mapping[s.dataset.mapk]=s.value; });
    const type=document.getElementById('fm_type').value;
    if((type==='csv'||type==='json')&&!mapping.title){ err.textContent='Map at least the Title column (run Detect first).'; return; }
    const btn=document.getElementById('fm_save'); btn.disabled=true; btn.style.opacity=.6; btn.textContent='Saving…';
    post('/admin/feeds/save',{ id:document.getElementById('fm_id').value, name, url, type, mode, video_type:document.getElementById('fm_vtype').value, dedup:document.getElementById('fm_dedup').value, schedule_interval:document.getElementById('fm_sched').value, max_pages:document.getElementById('fm_pages').value, page_param:document.getElementById('fm_pageparam').value, auto_publish:document.getElementById('fm_autopub').checked?1:0, enabled:1, mapping:JSON.stringify(mapping) })
    .then(r=>{ btn.disabled=false; btn.style.opacity=1; btn.textContent='Save source'; if(!r.ok){ err.textContent=r.error||'Save failed'; return; } toast('Source saved'); setTimeout(()=>location.assign('/admin/import?tab=feeds'),400); })
    .catch(()=>{ btn.disabled=false; btn.style.opacity=1; btn.textContent='Save source'; err.textContent='Network error'; });
  }
  function fetchNow(id){ toast('Fetching feed…'); setProg(id,true); post('/admin/feeds/fetch',{id}).then(r=>{ if(!r.ok&&r.error){ toast(r.error,'err'); setProg(id,false); return; } toast((r.new||0)+' new · '+(r.dup||0)+' already seen'+(r.processed_now?(' · '+r.processed_now+' imported'):''), (r.new||r.processed_now)?'ok':'warn'); startPolling(id); }).catch(()=>{ toast('Fetch failed','err'); setProg(id,false); }); }
  function runQueue(id){ toast('Processing queue…'); setProg(id,true); post('/admin/feeds/run',{id}).then(()=>startPolling(id)).catch(()=>{ toast('Failed','err'); setProg(id,false); }); }
  function startPolling(id){ if(polling[id]) clearInterval(polling[id]); let idle=0;
    const tick=()=>getJ('/admin/feeds/stats?id='+id).then(r=>{ if(!r.ok)return; const c=r.counts; updateStats(id,c);
      const active=(c.pending>0||c.importing>0); const total=c.total||1, done=c.imported+c.skipped+c.failed; setProgVal(id,active,Math.round(done/total*100));
      if(c.pending>0) post('/admin/feeds/run',{id});
      if(!active){ idle++; if(idle>=2){ clearInterval(polling[id]); delete polling[id]; setProg(id,false); } } else idle=0;
    });
    polling[id]=setInterval(tick,2500); tick();
  }
  function updateStats(id,c){ const box=document.querySelector('[data-stats="'+id+'"]'); if(!box)return; ['imported','skipped','failed','pending'].forEach(k=>{ const el=box.querySelector('[data-k="'+k+'"]'); if(el) el.textContent=c[k]||0; }); const rb=document.querySelector('[data-runbtn="'+id+'"]'); if(rb) rb.classList.toggle('hidden',!(c.pending>0)); }
  function setProg(id,on){ const w=document.querySelector('[data-prog="'+id+'"]'); if(w){ w.classList.toggle('hidden',!on); if(!on){ const b=w.querySelector('div>div'); if(b) b.style.width='0%'; } } }
  function setProgVal(id,on,pct){ const w=document.querySelector('[data-prog="'+id+'"]'); if(w){ w.classList.toggle('hidden',!on); const b=w.querySelector('div>div'); if(b) b.style.width=Math.max(3,Math.min(100,pct))+'%'; } }
  function toggle(id,btn){ post('/admin/feeds/toggle',{id}).then(r=>{ if(!r.ok)return; const on=r.enabled===1; btn.classList.toggle('bg-primary-600',on); btn.classList.toggle('bg-gray-300',!on); const dot=btn.querySelector('span'); dot.classList.toggle('translate-x-4',on); dot.classList.toggle('translate-x-0.5',!on); }); }
  function del(id,name){ if(!confirm('Delete feed source "'+name+'"?\n\nThis removes the source and its queue history. Already-imported videos are NOT deleted.'))return; post('/admin/feeds/delete',{id}).then(r=>{ if(r.ok){ const card=document.querySelector('[data-src="'+id+'"]'); if(card) card.remove(); toast('Source deleted'); } }); }
  function openItems(id,name){ itemsSrc=id; document.getElementById('im_name').textContent=name||''; document.getElementById('im_filter').value=''; document.getElementById('itemsModal').classList.remove('hidden'); loadItems(); }
  function closeItems(){ document.getElementById('itemsModal').classList.add('hidden'); }
  function loadItems(){ const f=document.getElementById('im_filter').value; const body=document.getElementById('im_body'); body.innerHTML='<div class="text-sm text-gray-400 py-8 text-center">Loading…</div>';
    getJ('/admin/feeds/items?id='+itemsSrc+(f?'&status='+f:'')).then(r=>{ if(!r.ok||!r.items.length){ body.innerHTML='<div class="text-sm text-gray-400 py-8 text-center">No items.</div>'; return; }
      const pill=(s)=>({pending:'bg-amber-50 text-amber-700',importing:'bg-blue-50 text-blue-700',imported:'bg-emerald-50 text-emerald-700',skipped:'bg-gray-100 text-gray-500',failed:'bg-red-50 text-red-700'}[s]||'bg-gray-100');
      body.innerHTML='<div class="divide-y divide-gray-100">'+r.items.map(it=>{ const link=it.video_slug?'<a href="/video/'+esc(it.video_slug)+'" target="_blank" class="text-primary-600 hover:underline text-[13px]">view →</a>':''; const e=it.error?'<div class="text-[11px] text-red-500 mt-0.5 truncate">'+esc(it.error)+'</div>':'';
        return '<div class="flex items-center gap-3 px-3 py-2"><span class="text-[10px] font-semibold uppercase px-1.5 py-0.5 rounded '+pill(it.status)+' shrink-0 w-16 text-center">'+esc(it.status)+'</span><div class="min-w-0 flex-1"><div class="text-[13px] text-gray-800 truncate">'+esc(it.title)+'</div>'+e+'</div>'+link+'</div>';
      }).join('')+'</div>';
    });
  }
  function reprocess(){ post('/admin/feeds/reprocess',{id:itemsSrc}).then(r=>{ toast((r.requeued||0)+' item(s) re-queued'); loadItems(); startPolling(itemsSrc); }); }
  return { openWizard, closeWizard, detect, save, fetchNow, runQueue, toggle, del, openItems, closeItems, loadItems, reprocess, _editMapping:{} };
})();
</script>
