// cspell:ignore githubusercontent streetsidesoftware forgejo
import { readFile, stat } from 'node:fs/promises';
import path from 'node:path';
import { walk } from './lib/walk.mjs';

const root = process.cwd();

// GitHub is no longer linked from anywhere on the site: project source links,
// the social links, and the repo metadata all point at the self-hosted git
// host instead. Matching the host substrings (rather than the bare word
// "github") keeps the Shiki `github-light`/`github-dark` themes and the
// Forgejo Actions `github.*` context variables from tripping the check.
const bannedHosts = [/github\.com/i, /github\.io/i, /githubusercontent\.com/i];

// Upstream tooling URLs that genuinely live on GitHub and have no self-hosted
// equivalent (e.g. the cspell config JSON Schema). A flagged URL is permitted
// only when it exactly equals one of these.
const permitted = new Set([
  'https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json',
]);

const textExtensions = new Set([
  '.astro',
  '.css',
  '.html',
  '.js',
  '.json',
  '.md',
  '.mjs',
  '.ts',
  '.txt',
  '.webmanifest',
  '.xml',
]);

const roots = [
  'src',
  'public',
  'scripts',
  'README.md',
  'package.json',
  'astro.config.mjs',
  'cspell.json',
].map((entry) => path.resolve(root, entry));

async function exists(filePath) {
  try {
    await stat(filePath);
    return true;
  } catch {
    return false;
  }
}

// Grows a match outward to the whole whitespace/quote-delimited URL token so it
// can be compared against the permitted set.
function urlToken(line, index, length) {
  const boundary = /[\s"'`<>()[\]{}]/;
  let start = index;
  while (start > 0 && !boundary.test(line[start - 1])) start -= 1;
  let end = index + length;
  while (end < line.length && !boundary.test(line[end])) end += 1;
  return line.slice(start, end);
}

const files = [];

for (const entry of roots) {
  if (!(await exists(entry))) continue;
  files.push(...(await walk(entry)));
}

const textFiles = files.filter((file) => textExtensions.has(path.extname(file)));

const failures = [];

for (const file of textFiles) {
  const lines = (await readFile(file, 'utf8')).split('\n');

  lines.forEach((line, lineIndex) => {
    for (const pattern of bannedHosts) {
      for (const match of line.matchAll(new RegExp(pattern, 'gi'))) {
        const token = urlToken(line, match.index, match[0].length);
        if (permitted.has(token)) continue;
        const location = `${path.relative(root, file)}:${lineIndex + 1}:${match.index + 1}`;
        failures.push(location);
      }
    }
  });
}

if (failures.length > 0) {
  console.error(
    `GitHub links are not allowed; link the self-hosted git host instead:\n${failures.join('\n')}`
  );
  process.exit(1);
}

console.log('No GitHub links found.');
