#!/usr/bin/env -S node -r esbin
import fs from "node:fs"
import { resolve } from "node:path"
import { load } from "js-yaml"

process.chdir(resolve(__dirname, ".."))

const candidates = [
  "master/LICENSE",
  "master/LICENCE",
  "master/LICENSE.md",
  "master/license.md",
  "main/LICENSE.txt",
]

async function getLicense(repo: string) {
  for (const candidate of candidates) {
    const path = `https://raw.githubusercontent.com/${repo}/${candidate}`
    const res = await fetch(path)
    if (!res.ok) continue
    const text = await res.text()
    return text
  }
  throw new Error(`Could not find license for ${repo}`)
}

async function getSDPXLicense(license: string) {
  const path = `https://raw.githubusercontent.com/spdx/license-list-data/main/text/${license}.txt`
  const res = await fetch(path)
  if (!res.ok) throw new Error(`Could not find license ${license}`)
  const text = await res.text()
  return text
}

function getVendorLicenses() {
  return fs
    .readdirSync("src/vendor")
    .map(name => ({ name, path: `src/vendor/${name}/LICENSE` }))
    .filter(({ path }) => fs.existsSync(path))
    .map(({ name, path }) => [name, fs.readFileSync(path, "utf8")])
}

async function getPackageLicense(pkg: string) {
  const files = ["LICENSE", "LICENCE", "license.md"]
  for (const file of files) {
    const path = `node_modules/${pkg}/${file}`
    if (fs.existsSync(path)) {
      return fs.readFileSync(path, "utf8")
    }
  }

  const pkgJson = JSON.parse(fs.readFileSync(`node_modules/${pkg}/package.json`, "utf8"))
  const licenseName = pkgJson.license as string
  if (!licenseName) throw new Error(`Could not find license for ${pkg}`)

  return getSDPXLicense(licenseName)
}

function getPackageLicenses() {
  const {
    dependencies = {},
    devDependencies = {},
    peerDependencies = {},
  } = JSON.parse(fs.readFileSync("package.json", "utf8"))
  const packages = Object.keys({
    ...dependencies,
    ...devDependencies,
    ...peerDependencies,
  })
  return Promise.all(packages.map(async pkg => [pkg, await getPackageLicense(pkg)]))
}

class Licenses {
  libs = new Set<string>()

  constructor(readonly json: Record<string, string>) {}

  set(name: string, license: string) {
    this.libs.add(name)
    this.json[name] = license
  }

  async setIfAbsent(name: string, license: () => Promise<string>) {
    if (!this.json[name]) {
      const text = await license()
      this.set(name, text)
    }
  }

  assign(others: Record<string, string>) {
    for (const [name, license] of Object.entries(others)) {
      this.set(name, license)
    }
  }

  purge() {
    for (const name of Object.keys(this.json)) {
      if (!this.libs.has(name)) {
        delete this.json[name]
      }
    }
  }

  toJSON() {
    return this.json
  }
}

async function main() {
  const outPath = "src/generated/licenses.json"
  if (!fs.existsSync(outPath)) {
    fs.writeFileSync(outPath, "{}")
  }
  const res = new Licenses(
    JSON.parse(fs.readFileSync(outPath, "utf8")) as Record<string, string>,
  )
  const config = load(fs.readFileSync("src/licenses.yml", "utf8")) as {
    github: string[]
    licenses: { [name: string]: string }
  }

  // res.set("archive", await getSPLicense("AGPL-3.0-or-later"));
  for (const repo of config.github) {
    await res.setIfAbsent(repo, () => getLicense(repo))
  }
  for (const [name, license] of Object.entries(config.licenses)) {
    await res.setIfAbsent(name, () => getSDPXLicense(license))
  }
  res.assign(Object.fromEntries(getVendorLicenses()))
  res.assign(Object.fromEntries(await getPackageLicenses()))
  res.purge()

  fs.writeFileSync(
    outPath,
    JSON.stringify(
      Object.fromEntries(
        Object.entries(res.toJSON()).sort(([a], [b]) => a.localeCompare(b)),
      ),
      null,
      2,
    ) + "\n",
  )
}

void main()
