///|
const WPT_INTERFACES_URL = "https://wpt.live/interfaces/"

///|
const FIXTURE_DIRECTORY = "parser/fixtures/wpt"

///|
const GENERATED_TEST_FILE = "parser/wpt_generated_test.mbt"

///|
struct WptFile {
  name : String
}

///|
fn exclude_wpt_idl(name : StringView) -> Bool {
  name is "webrtc-ice.idl" || name is "mediacapture-surface-control.idl"
}

///|
async fn fetch_text(url : String) -> String {
  let (response, body) = @http.get(url)
  if response.code != 200 {
    fail("GET " + url + " returned HTTP " + response.code.to_string())
  }
  body.text()
}

///|
const IDL_LINK = re"href=\"([^\"]+[.]idl)\""

///|
async fn fetch_wpt_idl_files() -> Array[WptFile] {
  let listing = fetch_text(WPT_INTERFACES_URL)
  let files : Array[WptFile] = []
  let mut remaining : StringView = listing
  while IDL_LINK.execute(remaining) is Some(link) {
    guard link.group(1) is Some(name) else {
      remaining = link.after()
      continue
    }
    if !exclude_wpt_idl(name) {
      files.push(WptFile::{ name: name.to_owned() })
    }
    remaining = link.after()
  }
  files.sort_by((a, b) => a.name.compare(b.name))
  files
}

///|
fn generate_test_source(files : ArrayView[WptFile]) -> String {
  let mut output =
    #|/// Auto-generated by cmd/build_idl_tests. Do not edit manually.
    #|/// Each WPT IDL fixture has its own independently filterable test.
    #|
    #|///|
    #|async fn parse_wpt_idl_fixture(file : String) -> Unit {
    #|  let source = @fs.read_file("parser/fixtures/wpt/" + file).text()
    #|  let tokens = @lexer.tokenize(source)
    #|  let (_, rest) = parse(tokens.view())
    #|  if rest is [(_, text), ..] {
    #|    fail(
    #|      file + ": first unconsumed token '" + text.to_owned() + "' (" +
    #|      rest.length().to_string() + " tokens remaining)",
    #|    )
    #|  }
    #|}
    #|

  for file in files {
    output = output +
      "///|\nasync test \"WPT: " +
      file.name.escape(quote=false) +
      "\" {\n" +
      "  parse_wpt_idl_fixture(" +
      file.name.escape() +
      ")\n" +
      "}\n\n"
  }
  output
}

///|
fn generate_readme(count : Int) -> String {
  let output =
    $|# WPT Web IDL fixtures
    $|
    $|These files are copied byte-for-byte from interfaces/*.idl in the
    $|web-platform-tests/wpt repository:
    $|https://github.com/web-platform-tests/wpt
    $|
    $|- Source: https://wpt.live/interfaces/
    $|- Files: \{count}, including *.tentative.idl
    $|- Excluded: webrtc-ice.idl and mediacapture-surface-control.idl because
    $|  they contain constructors in partial interfaces, which Web IDL forbids.
    $|
    $|Run moon run cmd/build_idl_tests to replace the fixtures from WPT Live
    $|and regenerate parser/wpt_generated_test.mbt.
    $|
    $|Do not edit the IDL fixtures locally.
  output
}

///|
async fn remove_stale_idl_files() -> Unit {
  if @fs.exists(FIXTURE_DIRECTORY) {
    for file in @fs.readdir(FIXTURE_DIRECTORY) {
      if file.has_suffix(".idl") {
        @fs.remove(FIXTURE_DIRECTORY + "/" + file)
      }
    }
  } else {
    @fs.mkdir(FIXTURE_DIRECTORY, recursive=true)
  }
}

///|
async fn main {
  println("Fetching the latest WPT Web IDL corpus...")
  let files = fetch_wpt_idl_files()
  remove_stale_idl_files()
  let downloads : Array[async () -> Unit] = files.mapi((index, file) => {
    async fn() {
      let source = fetch_text(WPT_INTERFACES_URL + file.name)
      @fs.write_file(
        FIXTURE_DIRECTORY + "/" + file.name,
        source,
        create_mode=@fs.CreateMode::CreateOrTruncate,
      )
      println(
        "[" +
        (index + 1).to_string() +
        "/" +
        files.length().to_string() +
        "] " +
        file.name,
      )
    }
  })
  ignore(@async.all(downloads, max_concurrent=16))
  @fs.write_file(
    GENERATED_TEST_FILE,
    generate_test_source(files),
    create_mode=@fs.CreateMode::CreateOrTruncate,
  )
  @fs.write_file(
    FIXTURE_DIRECTORY + "/README.md",
    generate_readme(files.length()),
    create_mode=@fs.CreateMode::CreateOrTruncate,
  )
  println("Generated " + files.length().to_string() + " WPT IDL tests")
}