///|
pub struct BuildStage {
  id : String
  description : String
  dependencies : Array[String]
  abi : String?
  outputs : Array[String]
}

///|
fn stage(
  id : String,
  description : String,
  dependencies : Array[String],
  outputs : Array[String],
  abi? : String? = None,
) -> BuildStage {
  { id, description, dependencies, outputs, abi, }
}

///|
pub fn BuildStage::to_json(self : BuildStage) -> Json {
  {
    "id": self.id,
    "description": self.description,
    "dependencies": self.dependencies.to_json(),
    "abi": self.abi.to_json(),
    "outputs": self.outputs.to_json(),
  }
}

///|
pub fn build_stages(
  project : Project,
  abis : Array[String],
) -> Array[BuildStage] raise ConfigError {
  project.validate()
  parse_abis(abis.join(",")) |> ignore
  let stages = [
    stage("environment", "Verify pinned compiler, runtime and SDK", [], []),
    stage(
      "stage",
      "Copy source into an isolated build directory",
      ["environment"],
      ["module/"],
    ),
    stage(
      "interface",
      "Parse declarations and verify manifest signatures",
      ["stage"],
      ["interface.json"],
    ),
    stage(
      "generate",
      "Generate opaque adapters and wrapper package",
      ["interface"],
      ["native/", "module/moonohos_bridge/"],
    ),
    stage(
      "compile",
      "Execute necessary moonc operations as argument arrays",
      ["generate"],
      ["native/core.c"],
    ),
  ]
  let inspections : Array[String] = []
  for abi in abis {
    let configure = "configure/" + abi
    let link = "link/" + abi
    let inspect = "elf/" + abi
    let library = "libs/" + abi + "/lib" + project.native_module + ".so"
    stages.push(
      stage(
        configure,
        "Configure HarmonyOS CMake",
        ["compile"],
        ["cmake-" + abi + "/"],
        abi=Some(abi),
      ),
    )
    stages.push(
      stage(
        link,
        "Link MoonBit runtime and Node-API",
        [configure],
        [library],
        abi=Some(abi),
      ),
    )
    stages.push(
      stage(
        inspect,
        "Verify target architecture, symbols and dependencies",
        [link],
        [],
        abi=Some(abi),
      ),
    )
    inspections.push(inspect)
  }
  stages.push(
    stage("package", "Write types, notices and content manifest", inspections, [
      "manifest.json", "types/", "cmake/",
    ]),
  )
  stages.push(
    stage(
      "verify",
      "Validate complete distribution before publishing",
      ["package"],
      [],
    ),
  )
  stages.push(
    stage(
      "publish",
      "Move complete package into the new output directory",
      ["verify"],
      ["dist/"],
    ),
  )
  validate_stages(stages)
  stages
}

///|
pub fn validate_stages(stages : Array[BuildStage]) -> Unit raise ConfigError {
  let completed : Map[String, Bool] = Map([])
  for stage in stages {
    if stage.id == "" || completed.contains(stage.id) {
      raise Invalid("plan: duplicate or empty stage " + stage.id)
    }
    let seen : Map[String, Bool] = Map([])
    for dependency in stage.dependencies {
      if !completed.contains(dependency) {
        raise Invalid(
          "plan: dependency must precede " + stage.id + ": " + dependency,
        )
      }
      if seen.contains(dependency) {
        raise Invalid("plan: duplicate dependency " + dependency)
      }
      seen[dependency] = true
    }
    completed[stage.id] = true
  }
}

///|
pub fn Project::build_plan(
  self : Project,
  abis : Array[String],
) -> Json raise ConfigError {
  {
    "schema": "moonohos-plan-v1",
    "interface": self.summary(),
    "generation": self.generation_plan(),
    "stages": build_stages(self, abis).map(s => s.to_json()).to_json(),
    "commandTimeoutSeconds": 300,
    "runtime": "not_run",
  }
}