(function friends() {
  "use strict";

  var FRIENDS = [
    {
      title: "Fiancée",
      members: [
        { name: "Aria", img: "/assets/friends/ari.png", tier: "wife", discordId: "1305215902685597797", link: null }
      ]
    },
    {
      title: "Close Friends",
      members: [
        { name: "Ria", img: "/assets/friends/ria.png", tier: "close", discordId: "1513506390088618145", link: null },
        { name: "Camilla", img: "/assets/friends/camilla.png", tier: "close", discordId: "1110542429838397471", link: "https://cammy-the-cat.com" },
        { name: "Saphie", img: "/assets/friends/saphie.png", tier: "close", discordId: "527709099186716673", link: null },
        { name: "Ari", img: "/assets/friends/meowhem.png", tier: "close", discordId: "1474568910736199825", link: "https://a.stupid.cat" }
      ]
    },
    {
      title: "Friends",
      members: [
        { name: "Fin", img: "/assets/friends/fin.png", tier: "friend", discordId: "867818211574808607", link: null },
        { name: "Meme", img: "/assets/friends/meme.png", tier: "friend", discordId: "812998699667161098", link: null },
        { name: "N", img: "/assets/friends/n.png", tier: "friend", discordId: "639399972407869450", link: null },
        { name: "Lylla", img: "/assets/friends/lylla.png", tier: "friend", discordId: "1009889543878611016", link: null },
        { name: "Simon", img: "/assets/friends/simon.png", tier: "friend", discordId: "758466783354814514", link: null }
      ]
    },
    {
      title: "Other Peeps",
      subtitle: "You can request to be added here!",
      members: [
        { name: "Aureal", img: "/assets/known-people/aureal.gif", tier: "known", discordId: "1498977251134279900", link: "https://aureal.dev/" },
        { name: "Sy", img: "/assets/known-people/sy.png", tier: "known", discordId: "1354212422839308469", link: "https://synapsexen.xyz/"}
      ]
    },
    {
      title: "Alts",
      subtitle: "My other accounts, dead or alive",
      members: [
        { name: "J", img: "/assets/alts/j.png", tier: "active-alt", discordId: "1500197577336033301", link: null},
        { name: "Uzi", img: "/assets/alts/uzi.png", tier: "active-alt", discordId: "526626867973849123", link: null },
        { name: "clovetwilight3", img: "/assets/alts/clovetwilight3.png", tier: "dead-alt", discordId: null, link: null },
        { name: "estrogenhrt", img: "/assets/alts/estrogenhrt.png", tier: "dead-alt", discordId: null, link: null },
        { name: "Clove <3", img: "/assets/alts/clove.png", tier: "dead-alt", discordId: "1125844710511104030", link: null},
        { name: "Clove ⛤", img: "/assets/alts/butterfly.png", tier: "dead-alt", discordId: "514994021970739201", link: null },
        { name: "Mrow", img: "/assets/alts/mrow.png", tier: "dead-alt", discordId: "219480349053288450", link: null }
      ]
    }
  ];

  var REFRESH_MS = 60000; // re-poll live friends once a minute

  var root = document.getElementById("friends-root");
  if (!root) return;

  // title → URL-safe anchor id, e.g. "Close Friends" -> "close-friends"
  function slugify(str) {
    return String(str == null ? "" : str)
      .toLowerCase()
      .trim()
      .replace(/[^a-z0-9]+/g, "-")
      .replace(/^-+|-+$/g, "");
  }

  // ---- helpers (mirrors now-playing.js) -------------------------------
  function esc(str) {
    return String(str == null ? "" : str)
      .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
  }
  function intToHex(n) {
    return "#" + (Number(n) >>> 0).toString(16).padStart(6, "0").slice(-6);
  }
  // Re-serve Discord CDN images cookieless via wsrv.nl (same trick as the
  // presence card) so no third-party cookies are set.
  function proxyImg(url, opts) {
    if (!url) return url;
    if (!/^https:\/\/(cdn|media)\.discordapp\.(com|net)\//.test(url)) return url;
    var src = url.replace(/^https:\/\//, "");
    var q = "https://wsrv.nl/?url=" + encodeURIComponent(src) + "&output=webp";
    if (opts && opts.w) q += "&w=" + opts.w + "&dpr=2&fit=cover";
    return q;
  }
  function avatarUrl(u) {
    if (!u || !u.avatar) {
      var def = (Number(u && u.discriminator) || 0) % 5;
      return proxyImg("https://cdn.discordapp.com/embed/avatars/" + def + ".png");
    }
    var ext = String(u.avatar).startsWith("a_") ? "gif" : "png";
    return proxyImg("https://cdn.discordapp.com/avatars/" + u.id + "/" + u.avatar + "." + ext + "?size=128", { w: 96 });
  }
  function bannerUrl(id, hash) {
    if (!id || !hash) return null;
    // Animated banners (a_) must be requested WITHOUT the .gif extension:
    // Discord's CDN throws HTTP 415 for some a_*.gif banners, but the
    // extension-less URL works (wsrv then re-serves it as cookieless webp).
    var animated = String(hash).startsWith("a_");
    var url = "https://cdn.discordapp.com/banners/" + id + "/" + hash + (animated ? "" : ".png") + "?size=480";
    return proxyImg(url, { w: 480 });
  }

  // custom-status emoji → CDN url (custom emoji) or null (unicode emoji)
  function customEmojiUrl(e) {
    if (!e || !e.id) return null;
    return proxyImg("https://cdn.discordapp.com/emojis/" + e.id + (e.animated ? ".gif" : ".png") + "?size=32");
  }
  // Render the Discord custom status (activity type 4): emoji + text.
  function renderCustomStatus(refs, activities) {
    if (!refs.custom) return;
    var c = (activities || []).find(function (a) { return a && a.type === 4; });
    var text = c && c.state ? String(c.state) : "";
    var emoji = c && c.emoji;
    if (!text && !(emoji && (emoji.id || emoji.name))) {
      refs.custom.hidden = true;
      refs.custom.innerHTML = "";
      return;
    }
    var eu = emoji ? customEmojiUrl(emoji) : null;
    var emojiHtml = eu
      ? '<img class="fc-custom-emoji" src="' + eu + '" alt="" onerror="this.remove()">'
      : (emoji && emoji.name ? '<span class="fc-custom-emoji-uni">' + esc(emoji.name) + "</span>" : "");
    refs.custom.innerHTML = emojiHtml + (text ? '<span class="fc-custom-text">' + esc(text) + "</span>" : "");
    refs.custom.hidden = false;
  }

  // dstn.to badge list → small icon row (same source the presence card uses)
  function renderDstnBadges(badges) {
    if (!Array.isArray(badges)) return "";
    return badges.map(function (b) {
      var img = '<img class="fc-badge" src="' +
        proxyImg("https://cdn.discordapp.com/badge-icons/" + esc(b.icon) + ".png") +
        '" alt="' + esc(b.description || b.id) + '" title="' + esc(b.description || b.id) +
        '" onerror="this.remove()">';
      return b.link
        ? '<a class="fc-badge-link" href="' + esc(b.link) + '" target="_blank" rel="noopener">' + img + "</a>"
        : img;
    }).join("");
  }

  // Discord server (clan) tag — the little guild badge + tag next to a name
  function guildTagBadgeUrl(pg) {
    if (!pg || !pg.badge || !pg.identity_guild_id) return null;
    return proxyImg("https://cdn.discordapp.com/guild-tag-badges/" + pg.identity_guild_id + "/" + pg.badge + ".png?size=24");
  }
  function renderClanTag(refs, pg) {
    if (!refs.tag) return;
    if (pg && pg.tag && pg.identity_enabled) {
      var b = guildTagBadgeUrl(pg);
      refs.tag.innerHTML = (b ? '<img class="fc-tag-badge" src="' + b + '" alt="" onerror="this.remove()">' : "") +
        '<span class="fc-tag-text">' + esc(pg.tag) + "</span>";
      refs.tag.hidden = false;
    } else {
      refs.tag.hidden = true;
    }
  }

  // ---- build one card -------------------------------------------------
  function buildCard(m) {
    var card = document.createElement("article");
    card.className = "friend-card" + (m.tier ? " tier-" + m.tier : "");
    card.dataset.status = "unconnected"; // until proven otherwise

    var nameTag = m.link ? "a" : "span";
    var nameAttrs = m.link ? ' href="' + esc(m.link) + '" target="_blank" rel="noopener"' : "";

    card.innerHTML =
      '<div class="fc-banner"></div>' +
      '<div class="fc-main">' +
        '<span class="fc-avatar">' +
          '<img class="fc-pfp" src="' + esc(m.img) + '" alt="' + esc(m.name) + '" loading="eager">' +
          '<span class="fc-status" title="not connected to Lanyard"></span>' +
        '</span>' +
        '<span class="fc-id">' +
          '<span class="fc-name-row">' +
            '<' + nameTag + ' class="fc-name' + (m.tier ? " " + m.tier : "") + '"' + nameAttrs + '>' + esc(m.name) + '</' + nameTag + '>' +
            '<span class="fc-tag" hidden></span>' +
          '</span>' +
          '<span class="fc-user"></span>' +
          '<span class="fc-custom" hidden></span>' +
          '<span class="fc-badges"></span>' +
        '</span>' +
      '</div>';

    return {
      el: card,
      pfp: card.querySelector(".fc-pfp"),
      statusDot: card.querySelector(".fc-status"),
      user: card.querySelector(".fc-user"),
      custom: card.querySelector(".fc-custom"),
      tag: card.querySelector(".fc-tag"),
      badges: card.querySelector(".fc-badges"),
      banner: card.querySelector(".fc-banner")
    };
  }

  var STATUS_TITLE = {
    online: "Online",
    idle: "Idle",
    dnd: "Do Not Disturb",
    offline: "Offline"
  };

  // ---- live data: Lanyard (status + avatar) ---------------------------
  function loadLanyard(m, refs) {
    return fetch("https://api.lanyard.rest/v1/users/" + m.discordId)
      .then(function (r) { return r.json().catch(function () { return null; }); })
      .then(function (j) {
        if (!j || !j.success || !j.data) {
          // in config but not in the Lanyard server → stays "unconnected"
          return false;
        }
        var d = j.data;
        var u = d.discord_user || {};
        var status = d.discord_status || "offline";
        refs.el.dataset.status = status;
        refs.statusDot.title = STATUS_TITLE[status] || "Offline";
        if (u.avatar) refs.pfp.src = avatarUrl(u);
        if (u.username) refs.user.textContent = "@" + u.username;
        renderCustomStatus(refs, d.activities);
        renderClanTag(refs, u.primary_guild);
        return true;
      })
      .catch(function () {
        // API down / network error → static fallback, blue dot
        return false;
      });
  }

  // ---- live data: dstn.to (badges + Nitro banner) ---------------------
  function loadDstn(m, refs) {
    return fetch("https://dcdn.dstn.to/profile/" + m.discordId)
      .then(function (r) { return r.ok ? r.json() : null; })
      .then(function (j) {
        if (!j) return;
        var u = j.user || {};
        var prof = j.user_profile || {};
        if (Array.isArray(j.badges) && j.badges.length) {
          refs.badges.innerHTML = renderDstnBadges(j.badges);
        }
        var bUrl = bannerUrl(u.id || m.discordId, prof.banner || u.banner);
        if (bUrl) {
          refs.banner.style.backgroundImage = "url('" + bUrl + "')";
          refs.el.classList.add("has-banner");
        } else if (u.banner_color) {
          refs.banner.style.background = u.banner_color;
          refs.el.classList.add("has-banner");
        } else if (typeof u.accent_color === "number") {
          refs.banner.style.background = intToHex(u.accent_color);
          refs.el.classList.add("has-banner");
        }
      })
      .catch(function () {});
  }

  function refreshMember(m, refs) {
    loadLanyard(m, refs).then(function (live) {
      if (live) loadDstn(m, refs);
    });
  }

  // ---- render ---------------------------------------------------------
  var liveMembers = []; // {m, refs} for those with an id, for polling

  FRIENDS.forEach(function (group) {
    var section = document.createElement("section");
    section.className = "section";
    section.id = slugify(group.title); // anchor target, e.g. #alts

    var h2 = document.createElement("h2");
    h2.className = "section-title";
    h2.textContent = group.title;
    section.appendChild(h2);

    if (group.subtitle) {
      var sub = document.createElement("p");
      sub.className = "section-subtitle";
      sub.textContent = group.subtitle;
      section.appendChild(sub);
    }

    var grid = document.createElement("div");
    grid.className = "friend-grid";

    group.members.forEach(function (m) {
      var refs = buildCard(m);
      grid.appendChild(refs.el);
      // dead alts are banned/retired — never pull live data for them
      if (m.discordId && m.tier !== "dead-alt") {
        liveMembers.push({ m: m, refs: refs });
        refreshMember(m, refs);
      }
    });

    section.appendChild(grid);
    root.appendChild(section);
  });

  // ---- jump to anchor (sections are built after page load) ------------
  function scrollToHash() {
    var id = (location.hash || "").slice(1);
    if (!id) return;
    var target = document.getElementById(id);
    if (target) target.scrollIntoView();
  }
  scrollToHash();
  window.addEventListener("hashchange", scrollToHash);

  // ---- poll live members ----------------------------------------------
  if (liveMembers.length) {
    setInterval(function () {
      if (document.hidden) return;
      liveMembers.forEach(function (x) { refreshMember(x.m, x.refs); });
    }, REFRESH_MS);
  }
})();
