///|
/// Compile the files the person edits. Only the browser's source-only validation policy is overlaid.
/// Projects execute independently so one failure cannot discard another project's results.
fn compile_workspace(engine : @engine.Engine, request : Json) -> Json raise {
  let files = @workspace_files.Files::from_json(
    @wire.field(request, "workspace"),
  )
  let tree = files.tree()
  let workspace = files.discover()
  let selector = optional_text(request, "projectPath").unwrap_or("")
  if optional_text(request, "operation") == Some("run-project") &&
    selector == "" {
    raise @config.Invalid("Compile & run requires a current project path")
  }
  let projects = workspace.select(selector~)
  let host = @engine.Host::memory(tree.files)
  let overlay = @config.parse(
    "[pipeline]\nvalidation = 'source-only'\n",
    origin="Browser host",
  )
  let results : Array[Json] = []
  let artifacts : Array[Json] = []
  let mut successful = true
  for project in projects {
    let path = if project.root == workspace.root {
      "."
    } else if workspace.root == "" {
      project.root
    } else {
      project.root[workspace.root.length() + 1:].to_owned()
    }
    let language = project.document.text("frontend.language", "scheme")
    let target = project.document.text("pipeline.backend", "ir-json")
    let reply = try {
      if project.error is Some(message) {
        raise @config.Invalid(message)
      }
      let plan = engine.plan(host, workspace, {
        ..@engine.Request::new(),
        selector: path,
        overlay,
      })
      let units : Array[@data.IRUnit] = []
      let sources : Array[String] = []
      let report = engine.execute_content(
        plan,
        fn(source, _) raise {
          files.content(source).unwrap_or(@data.Text((host.read)(source)))
        },
        on_unit=compiled => {
          units.push(compiled.unit)
          sources.push(compiled.source)
        },
      )
      let selected_unit = files
        .opened()
        .bind(opened => sources.search_by(source => source == opened))
        .unwrap_or(0)
      let run : EngineRun = {
        unit: units.get(selected_unit),
        report,
        selection: plan.source_frontends().get(selected_unit),
      }
      let fields = compile_reply(
        run,
        @ids.LanguageId::parse(language),
        if target == "checkpoint" || target == "ir-json" {
          ""
        } else {
          target
        },
      )
      if sources.get(selected_unit) is Some(source) {
        fields.push(("source", @wire.text(source)))
      }
      if optional_text(request, "operation") == Some("run-project") {
        if language != "scheme" {
          raise @config.Invalid(
            "Compile, then evaluate this project's functions in Explorer",
          )
        }
        let index = files
          .opened()
          .bind(opened => sources.search_by(source => source == opened))
          .unwrap_or(0)
        if units.get(index) is Some(unit) {
          let main = @ids.SourcePackageName::parse(project.name)
            .to_path()
            .to_canonical()
          let source_root = @workspace.join(
            project.root,
            project.document.text("project.source_directory", "src"),
          )
          let relative = sources[index]
            .strip_prefix(source_root + "/")
            .unwrap_or(sources[index][:])
            .to_owned()
          let stem = relative
            .rev_find(".")
            .map(dot => relative[:dot].to_owned())
            .unwrap_or(relative)
          let prefix = project.document.text("project.module_prefix", "")
          let module_name = if prefix == "" {
            stem
          } else {
            prefix + "/" + stem
          }
          fields.push(
            (
              "value",
              @wire.text(
                @scheme.value_to_string(
                  @backend.run_file(
                    unit.distribution,
                    @ir.FQName::parse(
                      main + ":" + @ids.canonical_path(module_name) + "#main",
                    ),
                    [],
                  ),
                ),
              ),
            ),
          )
        }
      }
      fields.push(("success", Json::boolean(report.successful())))
      fields.push(("artifacts", artifacts_json(report)))
      fields.push(
        (
          "units",
          Json::array(
            units.mapi((index, unit) => {
              let fields = compile_reply(
                {
                  unit: Some(unit),
                  report,
                  selection: plan.source_frontends().get(index),
                },
                @ids.LanguageId::parse(language),
                "",
              )
              fields.push(("source", @wire.text(sources[index])))
              @wire.obj(fields)
            }),
          ),
        ),
      )
      @wire.obj(fields)
    } catch {
      CompileFailed(reply) => reply
      @config.Invalid(message) =>
        failure(message, "workbench.project_configuration", "compile")
      error =>
        failure(
          @scheme.error_message(error),
          "workbench.project_compile",
          "compile",
        )
    }
    if @wire.optional(reply, "success") != Some(Json::boolean(true)) {
      successful = false
    }
    let generated = match @wire.optional(reply, "artifacts") {
      Some(Array(items)) => items
      _ => []
    }
    for artifact in generated {
      guard artifact is Object(fields) else { continue }
      let fields = fields.copy()
      let file = @wire.string(@wire.field(artifact, "path"))
      fields["path"] = @wire.text(
        (if path == "." { "root" } else { "projects/" + path }) + "/" + file,
      )
      fields["projectPath"] = @wire.text(path)
      artifacts.push(Json::object(fields))
    }
    results.push(
      @wire.obj([
        ("path", @wire.text(path)),
        ("name", @wire.text(project.name)),
        ("languageId", @wire.text(language)),
        ("target", @wire.text(target)),
        ("result", reply),
      ]),
    )
  }
  @wire.obj([
    ("success", Json::boolean(successful)),
    ("projects", Json::array(results)),
    ("artifacts", Json::array(artifacts)),
    ("browserBuildPolicy", @wire.text("source-only")),
    ("discovery", workspace.to_json()),
  ])
}