#!/usr/bin/env node
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with this
// work for additional information regarding copyright ownership. The ASF
// licenses this file to you under the Apache License, Version 2.0; you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at http://www.apache.org/licenses/LICENSE-2.0
//
// Resolve the apache/magpie committer/PMC roster from GitHub and download each
// member's avatar locally, then regenerate src/data/community.json. The landing
// page renders those avatars from /community-avatars/ — no GitHub widgets, no
// hotlinking.
//
// Source of truth: the `apache/<team>` GitHub team membership, which mirrors the
// PMC/committers. Run on demand via the `sync-community` prek hook:
//   prek run sync-community --hook-stage manual --all-files
// or directly:
//   node scripts/sync-community.mjs
//
// Needs a GitHub token with read:org — taken from GH_TOKEN, GITHUB_TOKEN, or
// `gh auth token`. On ANY failure it leaves the committed community.json and
// avatars untouched and exits 0, so it never blocks a commit or a build.

import { writeFile, mkdir, readdir, unlink } from "node:fs/promises";
import { execSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";

const ORG = process.env.MAGPIE_GH_ORG || "apache";
// The committer team mirrors the PMC/committer roster (apache/magpie-committers).
const TEAM = process.env.MAGPIE_GH_TEAM || "magpie-committers";
const AVATAR_SIZE = 160;

const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
const DATA = join(ROOT, "src", "data", "community.json");
const AVATAR_DIR = join(ROOT, "public", "community-avatars");

function resolveToken() {
  if (process.env.GH_TOKEN) return process.env.GH_TOKEN;
  if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
  try {
    return execSync("gh auth token", { stdio: ["ignore", "pipe", "ignore"] })
      .toString()
      .trim();
  } catch {
    return null;
  }
}

// Route through an HTTP(S) proxy when one is configured (sandboxed / CI envs).
async function installProxy() {
  const url = process.env.HTTPS_PROXY || process.env.https_proxy;
  if (!url) return;
  const { ProxyAgent, setGlobalDispatcher } = await import("undici");
  setGlobalDispatcher(new ProxyAgent(url));
}

async function gh(path, token) {
  const res = await fetch(`https://api.github.com${path}`, {
    headers: {
      Accept: "application/vnd.github+json",
      "User-Agent": "magpie-site-sync-community",
      "X-GitHub-Api-Version": "2022-11-28",
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
    },
  });
  if (!res.ok) {
    throw new Error(`GitHub ${path} -> ${res.status} ${res.statusText}`);
  }
  return res.json();
}

async function main() {
  await installProxy();
  const token = resolveToken();
  if (!token) {
    throw new Error(
      "no GitHub token (set GH_TOKEN / GITHUB_TOKEN or run `gh auth login`)",
    );
  }

  console.log(`-> Listing ${ORG}/${TEAM} team members`);
  const team = await gh(`/orgs/${ORG}/teams/${TEAM}/members?per_page=100`, token);
  if (!Array.isArray(team) || team.length === 0) {
    throw new Error(`${ORG}/${TEAM} team membership is empty`);
  }

  await mkdir(AVATAR_DIR, { recursive: true });
  const members = [];
  const keep = new Set();
  for (const m of team) {
    const user = await gh(`/users/${m.login}`, token);
    const name = user.name || user.login;
    const file = `${m.login}.png`;
    const sep = user.avatar_url.includes("?") ? "&" : "?";
    const img = await fetch(`${user.avatar_url}${sep}s=${AVATAR_SIZE}`);
    let avatar = null;
    if (img.ok) {
      await writeFile(join(AVATAR_DIR, file), Buffer.from(await img.arrayBuffer()));
      avatar = `/community-avatars/${file}`;
      keep.add(file);
    }
    members.push({ name, github: m.login, url: user.html_url, avatar });
    console.log(`   - ${name} (@${m.login})`);
  }
  members.sort((a, b) => a.name.localeCompare(b.name));

  // Drop avatars for people no longer on the team.
  for (const f of await readdir(AVATAR_DIR)) {
    if (f.endsWith(".png") && !keep.has(f)) await unlink(join(AVATAR_DIR, f));
  }

  const out = {
    _comment:
      "GENERATED by scripts/sync-community.mjs (prek hook 'sync-community'). Do not edit by hand.",
    source: `github:${ORG}/${TEAM} team`,
    count: members.length,
    members,
  };
  await writeFile(DATA, `${JSON.stringify(out, null, 2)}\n`);
  console.log(`OK wrote ${members.length} members -> ${DATA}`);
}

main().catch((err) => {
  console.warn(`WARN sync-community skipped: ${err.message}`);
  console.warn("     keeping the committed src/data/community.json and avatars.");
  process.exit(0);
});
