/* ================================================================
   URL Shortener — app.js
   Single-page frontend: shorten URLs, view metadata, admin panel.
   ================================================================ */

/* --- SVG Icons (Lucide-style) --- */
const IC = {
  sun:  `<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>`,
  moon: `<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>`,
  copy: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`,
  trash: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`,
  chevL: `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>`,
  chevR: `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>`,
  inbox: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg>`,
};

/* ================================================================
   STATE
   ================================================================ */
const API_BASE = window.location.pathname.replace(/\/static(\/.*)?$/, '').replace(/\/+$/, '') || '';
let state = {
  apiKey:     localStorage.getItem('urlshort-api-key') || '',
  isAdmin:    false,
  allUrls:    [],
  currentMeta: null,
  sortCol:    'created_at',
  sortDir:    'desc',
  page:       1,
  perPage:    20,
};

/* ================================================================
   THEME
   ================================================================ */
function getEffectiveTheme() {
  const m = document.documentElement.dataset.theme;
  if (m === 'dark' || m === 'light') return m;
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
function applyTheme(t) {
  document.documentElement.dataset.theme = t;
  document.querySelector('meta[name="color-scheme"]').content = t;
  updateThemeToggle();
}
function toggleTheme() {
  const next = getEffectiveTheme() === 'dark' ? 'light' : 'dark';
  localStorage.setItem('urlshort-theme', next);
  applyTheme(next);
}
function updateThemeToggle() {
  const btn = document.getElementById('theme-toggle');
  if (!btn) return;
  const dark = getEffectiveTheme() === 'dark';
  btn.innerHTML = dark ? IC.sun : IC.moon;
  btn.title = dark ? 'Switch to light mode' : 'Switch to dark mode';
}
function initTheme() {
  const saved = localStorage.getItem('urlshort-theme');
  if (saved === 'dark' || saved === 'light') applyTheme(saved);
  else updateThemeToggle();
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
    if (!localStorage.getItem('urlshort-theme')) updateThemeToggle();
  });
}

/* ================================================================
   BACKGROUND (dot-grid, same as navpage)
   ================================================================ */
function mulberry32(seed) {
  return () => {
    seed = (seed + 0x6D2B79F5) | 0;
    let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
function generateBackground() {
  const bg = document.getElementById('bg');
  if (!bg) return;
  const W = window.innerWidth, H = window.innerHeight, STEP = 15;
  const rand = mulberry32(0xC0FFEE42);
  const cols = Math.ceil(W / STEP) + 2, rows = Math.ceil(H / STEP) + 4;
  let dots = '';
  for (let r = -1; r <= rows; r++) {
    for (let c = 0; c <= cols; c++) {
      const x  = (c * STEP + (rand() - 0.5) * 3).toFixed(1);
      const y  = (r * STEP + (rand() - 0.5) * 3).toFixed(1);
      const op = (0.12 + rand() * 0.20).toFixed(3);
      const rr = (0.5  + rand() * 0.25).toFixed(2);
      dots += `<circle cx="${x}" cy="${y}" r="${rr}" opacity="${op}"/>`;
    }
  }
  bg.innerHTML = `
    <svg xmlns="http://www.w3.org/2000/svg"
         style="position:absolute;inset:0;width:100%;height:100%;pointer-events:none"
         aria-hidden="true">
      <defs>
        <radialGradient id="top-glow" cx="${(W/2).toFixed(0)}" cy="0"
          r="${(Math.max(W,H)*0.65).toFixed(0)}" gradientUnits="userSpaceOnUse">
          <stop offset="0%"   style="stop-color:var(--accent);stop-opacity:0.10"/>
          <stop offset="100%" style="stop-color:var(--accent);stop-opacity:0"/>
        </radialGradient>
        <style>
          @keyframes dot-drift { from{transform:translateY(0)} to{transform:translateY(${STEP}px)} }
          #bg-dots { animation: dot-drift ${STEP*1.6}s linear infinite; }
        </style>
      </defs>
      <g id="bg-dots" style="fill:currentColor">${dots}</g>
      <rect width="100%" height="100%" fill="url(#top-glow)"/>
    </svg>`;
}
let _resizeTimer;
window.addEventListener('resize', () => { clearTimeout(_resizeTimer); _resizeTimer = setTimeout(generateBackground, 250); });

/* ================================================================
   HELPERS
   ================================================================ */
function esc(s) {
  return String(s).replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}

function formatDate(ts) {
  const d = new Date(ts * 1000);
  const pad = n => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}

function formatRetention(days) {
  if (!days || days === 0) return '∞';
  if (days === 1) return '1 day';
  return `${days} days`;
}

function isValidUrl(s) {
  try {
    const u = new URL(s);
    return u.protocol === 'http:' || u.protocol === 'https:';
  } catch { return false; }
}

let _toastTimer;
function showToast(msg) {
  const el = document.getElementById('toast');
  el.textContent = msg;
  el.classList.add('show');
  clearTimeout(_toastTimer);
  _toastTimer = setTimeout(() => el.classList.remove('show'), 2000);
}

/* ================================================================
   API
   ================================================================ */
async function apiGet(path) {
  const res = await fetch(API_BASE + path);
  return res;
}
async function apiPost(path, body) {
  const res = await fetch(API_BASE + path, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  return res;
}
async function apiDelete(path) {
  const res = await fetch(API_BASE + path, { method: 'DELETE' });
  return res;
}

/* ================================================================
   URL INPUT
   ================================================================ */
let _lookupTimer;

function setupUrlInput() {
  const input = document.getElementById('url-input');
  input.addEventListener('input', () => {
    clearTimeout(_lookupTimer);
    const val = input.value.trim();
    if (!val) {
      clearResult();
      if (state.isAdmin) renderAdminPanel();
      return;
    }
    // Hide admin panel while typing
    document.getElementById('admin-area').innerHTML = '';
    // Debounced lookup
    _lookupTimer = setTimeout(() => lookupUrl(val), 350);
  });
  input.addEventListener('keydown', (e) => {
    if (e.key === 'Enter') {
      e.preventDefault();
      handleEnter();
    }
  });
}

async function lookupUrl(url) {
  if (!isValidUrl(url)) {
    clearResult();
    return;
  }
  try {
    const res = await apiGet('/api/lookup?url=' + encodeURIComponent(url));
    if (res.ok) {
      const data = await res.json();
      state.currentMeta = data;
      renderMeta(data);
      setStatus('exists', 'ok');
    } else {
      state.currentMeta = null;
      clearResult();
      setStatus('', '');
    }
  } catch {
    clearResult();
  }
}

async function handleEnter() {
  clearTimeout(_lookupTimer);          // cancel any pending debounced lookup
  const input = document.getElementById('url-input');
  const url = input.value.trim();

  if (!url || !isValidUrl(url)) {
    clearResult();
    setStatus('', '');
    return;
  }

  // If the debounced lookup already found this URL exists, just show it
  if (state.currentMeta && state.currentMeta.original_url === url) {
    setStatus('exists', 'ok');
    return;
  }

  // Create a new short URL
  setStatus('creating…', '');
  try {
    const res = await apiPost('/api/shorten', { url: url });
    if (res.status === 201) {
      const shortUrl = await res.text();
      const code = shortUrl.split('/').pop();
      const metaRes = await apiGet('/api/urls/' + encodeURIComponent(code));
      if (metaRes.ok) {
        const data = await metaRes.json();
        state.currentMeta = data;
        renderMeta(data);
        setStatus('created ✓', 'ok');
        if (state.isAdmin) fetchAdminUrls();
      }
    } else {
      const err = await res.json().catch(() => null);
      setStatus(err?.error || 'error', 'err');
    }
  } catch {
    setStatus('network error', 'err');
  }
}

function setStatus(text, cls) {
  const el = document.getElementById('url-status');
  el.textContent = text;
  el.className = 'url-status' + (cls ? ' ' + cls : '');
}

function clearResult() {
  state.currentMeta = null;
  document.getElementById('result-area').innerHTML = '';
}

/* ================================================================
   METADATA CARD
   ================================================================ */
function renderMeta(data) {
  const area = document.getElementById('result-area');
  area.innerHTML = `
    <div class="meta-card">
      <div class="meta-header">
        <span class="meta-title">URL Metadata</span>
        <button class="meta-copy-btn" onclick="copyToClipboard('${esc(data.short_url)}')">
          ${IC.copy} Copy short URL
        </button>
      </div>
      <table class="meta-table">
        <tr><th>Short URL</th><td><a href="${esc(data.short_url)}" target="_blank" rel="noopener">${esc(data.short_url)}</a></td></tr>
        <tr><th>Original URL</th><td><a href="${esc(data.original_url)}" target="_blank" rel="noopener">${esc(data.original_url)}</a></td></tr>
        <tr><th>Short Code</th><td>${esc(data.short_code)}</td></tr>
        <tr><th>Visit Count</th><td>${data.visit_count}</td></tr>
        <tr><th>Created</th><td>${formatDate(data.created_at)}</td></tr>
        <tr><th>Retention</th><td>${formatRetention(data.retention_days)}</td></tr>
      </table>
    </div>`;
}

/* ================================================================
   ADMIN PANEL
   ================================================================ */
async function validateApiKey() {
  if (!state.apiKey) {
    state.isAdmin = false;
    document.getElementById('admin-area').innerHTML = '';
    return;
  }
  try {
    const res = await apiGet('/api/urls?api_key=' + encodeURIComponent(state.apiKey));
    if (res.ok) {
      const data = await res.json();
      state.isAdmin = true;
      state.allUrls = data.urls || [];
      const input = document.getElementById('url-input');
      if (!input.value.trim()) renderAdminPanel();
    } else {
      state.isAdmin = false;
      state.allUrls = [];
      document.getElementById('admin-area').innerHTML = '';
    }
  } catch {
    state.isAdmin = false;
    document.getElementById('admin-area').innerHTML = '';
  }
}

async function fetchAdminUrls() {
  if (!state.isAdmin || !state.apiKey) return;
  try {
    const res = await apiGet('/api/urls?api_key=' + encodeURIComponent(state.apiKey));
    if (res.ok) {
      const data = await res.json();
      state.allUrls = data.urls || [];
      const input = document.getElementById('url-input');
      if (!input.value.trim()) renderAdminPanel();
    }
  } catch { /* silent */ }
}

function renderAdminPanel() {
  const area = document.getElementById('admin-area');
  if (!state.isAdmin || state.allUrls.length === 0) {
    if (state.isAdmin) {
      area.innerHTML = `<div class="empty-state">${IC.inbox}<p>No shortened URLs yet.</p></div>`;
    } else {
      area.innerHTML = '';
    }
    updateFooter();
    return;
  }

  const sorted = sortUrls(state.allUrls);
  const { rows, totalPages, start, end } = paginate(sorted);

  area.innerHTML = `
    <div class="admin-panel">
      <div class="admin-panel-header">
        <span class="admin-panel-title">All Shortened URLs</span>
        <span class="admin-panel-count">${state.allUrls.length} total</span>
      </div>
      <div class="admin-table-wrap">
        <table class="admin-table">
          <thead>
            <tr>
              ${thCol('short_url',       'Short URL',    'col-short')}
              ${thCol('original_url',    'Original URL', 'col-original')}
              ${thCol('visit_count',     'Visits',       'col-visits')}
              ${thCol('created_at',      'Created',      'col-created')}
              ${thCol('retention_days',  'Retention',    'col-retention')}
              <th class="col-actions no-sort"></th>
            </tr>
          </thead>
          <tbody>
            ${rows.map(renderAdminRow).join('')}
          </tbody>
        </table>
        ${renderPagination(sorted.length, totalPages, start, end)}
      </div>
    </div>`;
  updateFooter();
}

function thCol(col, label, cls) {
  const active = state.sortCol === col;
  const arrow = active ? (state.sortDir === 'asc' ? '▲' : '▼') : '▲';
  return `<th class="${cls} ${active ? 'sorted' : ''}"
              onclick="onSort('${col}')">
            ${label}<span class="sort-arrow">${arrow}</span>
          </th>`;
}

function renderAdminRow(u) {
  return `
    <tr>
      <td><a href="${esc(u.short_url)}" target="_blank" rel="noopener">${esc(u.short_url.replace(/^https?:\/\/[^/]+/, ''))}</a></td>
      <td><span class="original-url" title="${esc(u.original_url)}">${esc(u.original_url)}</span></td>
      <td class="r">${u.visit_count}</td>
      <td>${formatDate(u.created_at)}</td>
      <td class="r">${formatRetention(u.retention_days)}</td>
      <td class="r">
        <div class="row-actions">
          <button class="btn-icon" title="Copy short URL"
                  onclick="event.stopPropagation(); copyToClipboard('${esc(u.short_url)}')">${IC.copy}</button>
          <button class="btn-icon btn-delete" title="Delete"
                  onclick="event.stopPropagation(); deleteUrl('${esc(u.short_code)}')">${IC.trash}</button>
        </div>
      </td>
    </tr>`;
}

/* ================================================================
   SORTING
   ================================================================ */
function sortUrls(urls) {
  const col = state.sortCol;
  const dir = state.sortDir === 'asc' ? 1 : -1;
  return [...urls].sort((a, b) => {
    let va = a[col], vb = b[col];
    if (col === 'short_url') {
      va = a.short_url || ''; vb = b.short_url || '';
    }
    if (typeof va === 'string') return va.localeCompare(vb) * dir;
    return ((va || 0) - (vb || 0)) * dir;
  });
}

function onSort(col) {
  if (state.sortCol === col) {
    state.sortDir = state.sortDir === 'asc' ? 'desc' : 'asc';
  } else {
    state.sortCol = col;
    state.sortDir = col === 'created_at' ? 'desc' : 'asc';
  }
  state.page = 1;
  renderAdminPanel();
}

/* ================================================================
   PAGINATION
   ================================================================ */
function paginate(sorted) {
  const total = sorted.length;
  if (state.perPage === 'all' || state.perPage >= total) {
    return { rows: sorted, totalPages: 1, start: 1, end: total };
  }
  const pp = parseInt(state.perPage, 10);
  const totalPages = Math.ceil(total / pp);
  if (state.page > totalPages) state.page = totalPages;
  if (state.page < 1) state.page = 1;
  const start = (state.page - 1) * pp;
  const end = Math.min(start + pp, total);
  return { rows: sorted.slice(start, end), totalPages, start: start + 1, end };
}

function renderPagination(total, totalPages, start, end) {
  const sizes = [20, 50, 100, 200, 500, 'all'];
  const sizeOptions = sizes.map(s =>
    `<option value="${s}" ${String(state.perPage) === String(s) ? 'selected' : ''}>${s === 'all' ? 'All' : s}</option>`
  ).join('');

  // Page buttons
  let pageButtons = '';
  if (totalPages > 1) {
    pageButtons += `<button class="pagination-btn" ${state.page <= 1 ? 'disabled' : ''} onclick="goPage(${state.page - 1})">${IC.chevL}</button>`;
    // Show limited page numbers
    const maxShow = 5;
    let pStart = Math.max(1, state.page - Math.floor(maxShow / 2));
    let pEnd = Math.min(totalPages, pStart + maxShow - 1);
    if (pEnd - pStart < maxShow - 1) pStart = Math.max(1, pEnd - maxShow + 1);

    for (let p = pStart; p <= pEnd; p++) {
      pageButtons += `<button class="pagination-btn ${p === state.page ? 'active' : ''}" onclick="goPage(${p})">${p}</button>`;
    }
    pageButtons += `<button class="pagination-btn" ${state.page >= totalPages ? 'disabled' : ''} onclick="goPage(${state.page + 1})">${IC.chevR}</button>`;
  }

  return `
    <div class="pagination">
      <span class="pagination-info">Showing ${start}–${end} of ${total}</span>
      <div class="pagination-controls">
        ${pageButtons}
        <select class="page-size-select" onchange="changePageSize(this.value)">
          ${sizeOptions}
        </select>
      </div>
    </div>`;
}

function goPage(p) {
  state.page = p;
  renderAdminPanel();
  // Scroll to top of admin area
  document.getElementById('admin-area').scrollIntoView({ behavior: 'smooth', block: 'start' });
}

function changePageSize(val) {
  state.perPage = val === 'all' ? 'all' : parseInt(val, 10);
  state.page = 1;
  renderAdminPanel();
}

/* ================================================================
   ACTIONS
   ================================================================ */
function copyToClipboard(text) {
  navigator.clipboard.writeText(text).then(() => {
    showToast('Copied to clipboard');
  }).catch(() => {
    // Fallback
    const ta = document.createElement('textarea');
    ta.value = text;
    ta.style.position = 'fixed';
    ta.style.opacity = '0';
    document.body.appendChild(ta);
    ta.select();
    document.execCommand('copy');
    document.body.removeChild(ta);
    showToast('Copied to clipboard');
  });
}

async function deleteUrl(code) {
  if (!state.apiKey) return;
  try {
    const res = await apiDelete('/api/urls/' + encodeURIComponent(code) + '?api_key=' + encodeURIComponent(state.apiKey));
    if (res.status === 204) {
      state.allUrls = state.allUrls.filter(u => u.short_code !== code);
      showToast('Deleted');
      renderAdminPanel();
    } else if (res.status === 404) {
      showToast('Not found');
    } else if (res.status === 403) {
      showToast('Not authorized');
    }
  } catch {
    showToast('Network error');
  }
}

/* ================================================================
   API KEY INPUT
   ================================================================ */
let _apiKeyTimer;

function setupApiKeyInput() {
  const input = document.getElementById('api-key-input');
  // Restore saved key
  if (state.apiKey) {
    input.value = state.apiKey;
    validateApiKey();
  }
  input.addEventListener('input', () => {
    clearTimeout(_apiKeyTimer);
    state.apiKey = input.value.trim();
    localStorage.setItem('urlshort-api-key', state.apiKey);
    _apiKeyTimer = setTimeout(() => validateApiKey(), 500);
  });
}

/* ================================================================
   FOOTER
   ================================================================ */
function updateFooter() {
  const el = document.getElementById('footer-info');
  if (!el) return;
  if (state.isAdmin && state.allUrls.length > 0) {
    el.textContent = `${state.allUrls.length} shortened URL${state.allUrls.length !== 1 ? 's' : ''}`;
  } else {
    el.textContent = 'URL Shortener';
  }
}

/* ================================================================
   BOOT
   ================================================================ */
document.addEventListener('DOMContentLoaded', () => {
  initTheme();
  generateBackground();
  setupUrlInput();
  setupApiKeyInput();
  updateFooter();
});


