(() => {
  'use strict';

  // ── Config Loader ──
  async function loadConfig() {
    const res = await fetch('/services.json');
    if (!res.ok) throw new Error(`Failed to load services.json: ${res.status}`);
    return res.json();
  }

  // ── Nav Renderer ──
  function renderNav(config) {
    const nav = document.getElementById('nav');
    config.categories.filter(c => c.showInNav).forEach(cat => {
      const a = document.createElement('a');
      a.href = `#${cat.id}`;
      a.textContent = cat.navLabel;
      nav.appendChild(a);
    });

    // Search button in nav
    const btn = document.createElement('button');
    btn.className = 'nav-search-btn';
    btn.setAttribute('aria-label', 'Search services');
    btn.innerHTML = '<i class="ri-search-line"></i>';
    btn.addEventListener('click', () => SearchOverlay.open());
    nav.appendChild(btn);
  }

  // ── Services Renderer ──
  function renderServices(config) {
    const container = document.getElementById('services-container');
    const grouped = {};
    config.categories.forEach(cat => { grouped[cat.id] = []; });
    config.services.forEach(svc => {
      if (grouped[svc.category]) grouped[svc.category].push(svc);
    });

    config.categories.forEach(cat => {
      const services = grouped[cat.id];
      if (!services || services.length === 0) return;

      const section = document.createElement('section');
      section.id = cat.id;
      section.className = 'category';

      const inner = document.createElement('div');
      inner.className = 'container';

      const title = document.createElement('h2');
      title.className = 'section-title';
      title.innerHTML = `<i class="${cat.icon}"></i> ${cat.title}`;

      const grid = document.createElement('div');
      grid.className = 'card-grid';

      services.forEach((svc, i) => {
        const card = document.createElement('a');
        card.href = svc.url;
        card.className = 'service-card';
        card.dataset.name = svc.name.toLowerCase();
        card.dataset.category = cat.id;
        card.style.animationDelay = `${(i + 1) * 0.02}s`;

        // Status dot
        const dot = document.createElement('span');
        dot.className = 'card-status-dot unknown';
        card.appendChild(dot);

        const icon = document.createElement('i');
        icon.className = svc.icon;
        card.appendChild(icon);

        const name = document.createElement('span');
        name.className = 'card-name';
        name.textContent = svc.name;
        card.appendChild(name);

        if (svc.sub) {
          const sub = document.createElement('span');
          sub.className = 'card-sub';
          sub.textContent = svc.sub;
          card.appendChild(sub);
        }

        grid.appendChild(card);
      });

      inner.appendChild(title);
      inner.appendChild(grid);
      section.appendChild(inner);
      container.appendChild(section);
    });
  }

  // ── Status Bar Renderer ──
  function renderStatusBar(config) {
    const grid = document.getElementById('status-grid');
    config.statusMonitors.forEach(mon => {
      const card = document.createElement('a');
      card.href = mon.url;
      card.className = 'status-card';
      card.id = mon.id;

      card.innerHTML = `
        <div class="status-dot loading"></div>
        <div class="status-info">
          <span class="status-label">${mon.label}</span>
          <span class="status-value">Checking...</span>
        </div>`;

      grid.appendChild(card);
    });
  }

  // ── Status Monitor ──
  class StatusMonitor {
    constructor(config) {
      this.config = config;

      // Rich monitor parsers
      this.parsers = {
        glances: (data) => {
          // Glances v4 uses 'cpu'/'mem', v3 uses 'cpu_percent'/'mem_percent'
          const cpu = data.cpu ?? data.cpu_percent;
          const mem = data.mem ?? data.mem_percent;
          if (cpu == null || mem == null) return 'No data';
          return `CPU ${Math.round(cpu)}% | RAM ${Math.round(mem)}%`;
        },
        uptime: (data) => {
          const groups = data.heartbeatList || {};
          const monitors = Object.values(groups);
          const total = monitors.length;
          if (total === 0) return 'No monitors';
          const up = monitors.filter(beats => Array.isArray(beats) && beats.length > 0 && beats[beats.length - 1].status === 1).length;
          return `${up}/${total} services up`;
        },
        cadvisor: (data) => {
          const cores = data.num_cores || '?';
          const ramGB = data.memory_capacity ? (data.memory_capacity / 1073741824).toFixed(0) : '?';
          return `${cores} cores | ${ramGB} GB RAM`;
        }
      };

      this.pollRichStatus();
      this.pollHealthChecks();
      setInterval(() => this.pollRichStatus(), 60000);
      setInterval(() => this.pollHealthChecks(), 120000);
    }

    // ── Rich status monitors (3 detailed cards) ──
    async pollRichStatus() {
      this.config.statusMonitors.forEach(mon => this.checkRich(mon));
    }

    async checkRich(mon) {
      const card = document.getElementById(mon.id);
      if (!card) return;

      const dot = card.querySelector('.status-dot');
      const value = card.querySelector('.status-value');
      dot.className = 'status-dot loading';

      try {
        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), 5000);
        const res = await fetch(mon.apiUrl, { signal: controller.signal });
        clearTimeout(timeout);

        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const data = await res.json();
        const result = this.parsers[mon.parseType](data);
        dot.className = 'status-dot ok';
        value.textContent = result;
      } catch (err) {
        console.warn(`[StatusMonitor] ${mon.label} failed:`, err);
        dot.className = 'status-dot error';
        value.textContent = 'Unreachable';
      }
    }

    // ── Per-card health checks (simple up/down) ──
    async pollHealthChecks() {
      const services = this.config.services.filter(s => s.healthCheckUrl);
      const batchSize = 6;

      for (let i = 0; i < services.length; i += batchSize) {
        const batch = services.slice(i, i + batchSize);
        await Promise.allSettled(batch.map(svc => this.checkHealth(svc)));
        if (i + batchSize < services.length) {
          await new Promise(r => setTimeout(r, 200));
        }
      }
    }

    async checkHealth(svc) {
      const card = document.querySelector(`.service-card[data-name="${svc.name.toLowerCase()}"]`);
      if (!card) return;

      const dot = card.querySelector('.card-status-dot');
      if (!dot) return;

      dot.className = 'card-status-dot loading';

      try {
        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), 8000);

        const url = svc.healthCheckUrl;
        const isRelative = url.startsWith('/');
        const isHttps = url.startsWith('https://');

        // Relative URLs and HTTPS: normal fetch. HTTP cross-origin: no-cors.
        const fetchOpts = { signal: controller.signal };
        if (!isRelative && !isHttps) {
          fetchOpts.mode = 'no-cors';
        }

        await fetch(url, fetchOpts);
        clearTimeout(timeout);
        dot.className = 'card-status-dot ok';
      } catch {
        dot.className = 'card-status-dot error';
      }
    }
  }

  // ── Search Overlay ──
  const SearchOverlay = {
    overlay: null,
    input: null,
    results: null,
    services: [],
    selectedIndex: -1,
    isOpen: false,

    init(config) {
      this.overlay = document.getElementById('search-overlay');
      this.input = document.getElementById('search-input');
      this.results = document.getElementById('search-results');
      this.services = config.services;

      // Close on backdrop click
      this.overlay.addEventListener('click', (e) => {
        if (e.target === this.overlay) this.close();
      });

      // Input handler
      this.input.addEventListener('input', () => {
        this.selectedIndex = -1;
        this.renderResults(this.input.value.trim());
        this.filterCards(this.input.value.trim());
      });

      // Keyboard navigation
      this.input.addEventListener('keydown', (e) => {
        const items = this.results.querySelectorAll('.search-result-item');
        if (e.key === 'ArrowDown') {
          e.preventDefault();
          this.selectedIndex = Math.min(this.selectedIndex + 1, items.length - 1);
          this.highlightItem(items);
        } else if (e.key === 'ArrowUp') {
          e.preventDefault();
          this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
          this.highlightItem(items);
        } else if (e.key === 'Enter') {
          e.preventDefault();
          if (this.selectedIndex >= 0 && items[this.selectedIndex]) {
            window.location.href = items[this.selectedIndex].dataset.url;
          } else if (items.length === 1) {
            window.location.href = items[0].dataset.url;
          }
        } else if (e.key === 'Escape') {
          this.close();
        }
      });

      // Global '/' shortcut
      document.addEventListener('keydown', (e) => {
        if (e.key === '/' && !this.isOpen && !isInputFocused()) {
          e.preventDefault();
          this.open();
        } else if (e.key === 'Escape' && this.isOpen) {
          this.close();
        }
      });
    },

    open() {
      this.isOpen = true;
      this.overlay.classList.remove('hidden');
      this.input.value = '';
      this.results.innerHTML = '';
      this.selectedIndex = -1;
      this.clearCardFilters();
      setTimeout(() => this.input.focus(), 50);
    },

    close() {
      this.isOpen = false;
      this.overlay.classList.add('hidden');
      this.input.value = '';
      this.results.innerHTML = '';
      this.selectedIndex = -1;
      this.clearCardFilters();
    },

    fuzzyMatch(query, text) {
      query = query.toLowerCase();
      text = text.toLowerCase();
      let qi = 0;
      for (let ti = 0; ti < text.length && qi < query.length; ti++) {
        if (text[ti] === query[qi]) qi++;
      }
      return qi === query.length;
    },

    renderResults(query) {
      this.results.innerHTML = '';
      if (!query) return;

      const matches = this.services.filter(svc => this.fuzzyMatch(query, svc.name));

      matches.forEach((svc, i) => {
        const item = document.createElement('div');
        item.className = 'search-result-item' + (i === this.selectedIndex ? ' active' : '');
        item.dataset.url = svc.url;

        item.innerHTML = `<i class="${svc.icon}"></i><span>${svc.name}</span>`;

        item.addEventListener('click', () => {
          window.location.href = svc.url;
        });

        item.addEventListener('mouseenter', () => {
          this.selectedIndex = i;
          this.highlightItem(this.results.querySelectorAll('.search-result-item'));
        });

        this.results.appendChild(item);
      });

      if (matches.length === 0) {
        const empty = document.createElement('div');
        empty.className = 'search-result-empty';
        empty.textContent = 'No services found';
        this.results.appendChild(empty);
      }
    },

    highlightItem(items) {
      items.forEach((item, i) => {
        item.classList.toggle('active', i === this.selectedIndex);
      });
      if (items[this.selectedIndex]) {
        items[this.selectedIndex].scrollIntoView({ block: 'nearest' });
      }
    },

    filterCards(query) {
      const allCards = document.querySelectorAll('.service-card');
      const allSections = document.querySelectorAll('.category');

      if (!query) {
        this.clearCardFilters();
        return;
      }

      allCards.forEach(card => {
        const name = card.dataset.name || '';
        const match = this.fuzzyMatch(query, name);
        card.classList.toggle('search-hidden', !match);
        card.classList.toggle('search-highlight', match);
      });

      // Hide sections with no visible cards
      allSections.forEach(section => {
        const visibleCards = section.querySelectorAll('.service-card:not(.search-hidden)');
        section.classList.toggle('search-hidden', visibleCards.length === 0);
      });
    },

    clearCardFilters() {
      document.querySelectorAll('.search-hidden').forEach(el => el.classList.remove('search-hidden'));
      document.querySelectorAll('.search-highlight').forEach(el => el.classList.remove('search-highlight'));
    }
  };

  function isInputFocused() {
    const el = document.activeElement;
    return el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable);
  }

  // ── Hero Widget (Clock / Weather / Greeting) ──
  class HeroWidget {
    constructor(config) {
      this.el = document.getElementById('hero-widget');
      this.city = config.weather?.city || 'Copenhagen';
      this.weatherData = null;

      this.render();
      this.updateTime();
      this.fetchWeather();

      setInterval(() => this.updateTime(), 1000);
      setInterval(() => this.fetchWeather(), 900000); // 15 min
    }

    render() {
      this.el.innerHTML = `
        <p class="hero-greeting" id="hero-greeting"></p>
        <p class="hero-clock" id="hero-clock"></p>
        <p class="hero-date" id="hero-date"></p>
        <p class="hero-weather" id="hero-weather"><i class="ri-loader-4-line ri-spin"></i> Loading weather...</p>`;
    }

    updateTime() {
      const now = new Date();
      const hour = now.getHours();

      let greeting;
      if (hour < 5) greeting = 'Good Night';
      else if (hour < 12) greeting = 'Good Morning';
      else if (hour < 17) greeting = 'Good Afternoon';
      else if (hour < 21) greeting = 'Good Evening';
      else greeting = 'Good Night';

      const greetEl = document.getElementById('hero-greeting');
      const clockEl = document.getElementById('hero-clock');
      const dateEl = document.getElementById('hero-date');

      if (greetEl) greetEl.textContent = greeting;
      if (clockEl) clockEl.textContent = now.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
      if (dateEl) dateEl.textContent = now.toLocaleDateString('en-GB', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
    }

    async fetchWeather() {
      const weatherEl = document.getElementById('hero-weather');
      if (!weatherEl) return;

      try {
        const res = await fetch(`https://wttr.in/${encodeURIComponent(this.city)}?format=j1`);
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const data = await res.json();

        const current = data.current_condition?.[0];
        if (!current) throw new Error('No data');

        const temp = current.temp_C;
        const desc = current.weatherDesc?.[0]?.value || '';
        const code = parseInt(current.weatherCode, 10);
        const iconClass = this.weatherIcon(code);

        weatherEl.innerHTML = `<i class="${iconClass}"></i> ${temp}°C — ${desc}`;
        this.weatherData = current;
      } catch {
        weatherEl.innerHTML = `<i class="ri-cloud-off-line"></i> Weather unavailable`;
      }
    }

    weatherIcon(code) {
      // wttr.in weather codes to Remix Icon
      if (code === 113) return 'ri-sun-line';               // Clear/Sunny
      if (code === 116) return 'ri-sun-cloudy-line';         // Partly cloudy
      if (code === 119 || code === 122) return 'ri-cloudy-line'; // Cloudy / Overcast
      if (code === 143 || code === 248 || code === 260) return 'ri-mist-line'; // Fog/Mist
      if ([176, 263, 266, 293, 296, 299, 302, 305, 308, 353, 356, 359].includes(code)) return 'ri-rainy-line'; // Rain
      if ([179, 182, 185, 227, 230, 323, 326, 329, 332, 335, 338, 368, 371, 374, 377].includes(code)) return 'ri-snowy-line'; // Snow
      if ([200, 386, 389, 392, 395].includes(code)) return 'ri-thunderstorms-line'; // Thunder
      if ([281, 284, 311, 314, 317, 320, 350, 362, 365].includes(code)) return 'ri-drizzle-line'; // Sleet/Freezing
      return 'ri-cloudy-line';
    }
  }

  // ── Mobile Nav Toggle (event delegation for dynamic links) ──
  function initNavToggle() {
    const navToggle = document.getElementById('nav-toggle');
    const nav = document.getElementById('nav');

    navToggle.addEventListener('click', () => {
      const open = nav.classList.toggle('open');
      navToggle.setAttribute('aria-expanded', open);
      navToggle.querySelector('i').className = open ? 'ri-close-line' : 'ri-menu-line';
    });

    // Event delegation for dynamically added nav links
    nav.addEventListener('click', (e) => {
      if (e.target.closest('a')) {
        nav.classList.remove('open');
        navToggle.setAttribute('aria-expanded', 'false');
        navToggle.querySelector('i').className = 'ri-menu-line';
      }
    });
  }

  // ── Header Scroll Effect ──
  function initScrollEffect() {
    const header = document.getElementById('header');
    let ticking = false;

    window.addEventListener('scroll', () => {
      if (!ticking) {
        requestAnimationFrame(() => {
          header.classList.toggle('scrolled', window.scrollY > 50);
          ticking = false;
        });
        ticking = true;
      }
    });
  }

  // ── Init ──
  async function init() {
    // These work independently of config
    initNavToggle();
    initScrollEffect();

    try {
      const config = await loadConfig();

      renderNav(config);
      renderStatusBar(config);
      renderServices(config);

      new StatusMonitor(config);
      SearchOverlay.init(config);
      new HeroWidget(config);
    } catch (err) {
      console.error('Failed to initialize dashboard:', err);
      const container = document.getElementById('services-container');
      if (container) {
        container.innerHTML = `
          <div class="container" style="padding: 60px 24px; text-align: center;">
            <i class="ri-error-warning-line" style="font-size: 48px; color: var(--red); margin-bottom: 16px;"></i>
            <h2 style="color: var(--text-heading); margin-bottom: 8px;">Failed to load services</h2>
            <p style="color: var(--text-muted);">Could not load services.json. Check the console for details.</p>
          </div>`;
      }
    }
  }

  init();
})();
