// Desktop and web target resolution shared by `moui build`, `moui run`,
// and `moui package`. Entrypoint naming follows the repository convention:
// `_skia`, `_wgpu`, `_sun`, `web_wasm`.
// The positional `` argument is the project directory that holds the
// entrypoint packages (or the entrypoint package directory itself).

///|
priv struct DesktopTarget {
  platform : String
  package_path : String
  target : String
}

///|
fn desktop_platform_supported(platform : String) -> Bool {
  platform == "macos" ||
  platform == "windows" ||
  platform == "linux" ||
  platform == "web"
}

///|
/// Entrypoint package candidates for a desktop/web platform, ordered by
/// preference. `renderer` selects a specific backend ("skia" / "wgpu" /
/// "sun"); "auto" prefers the Skia mainline and falls back to the
/// diagnostic routes when the Skia entrypoint does not exist.
fn desktop_entrypoint_candidates(
  platform : String,
  renderer? : String = "auto",
) -> Result[Array[String], String] {
  if platform == "web" {
    return Ok(["web_wasm"])
  }
  let suffix : String = match renderer {
    "skia" | "auto" => "_skia"
    "wgpu" => "_wgpu"
    "sun" => "_sun"
    other =>
      return Err(
        "unknown renderer for desktop targets: " +
        other +
        " (expected auto / skia / wgpu / sun)",
      )
  }
  let primary = platform + suffix
  let candidates = [primary]
  if renderer == "auto" {
    for fallback in ["_wgpu", "_sun", ""] {
      let name = platform + fallback
      if name != primary {
        candidates.push(name)
      }
    }
  }
  Ok(candidates)
}

///|
fn desktop_entrypoint_exists(root : String, candidate : String) -> Bool {
  let path = join_path(root, candidate)
  @fs.is_dir(path) catch {
    _ => false
  }
}

///|
/// Resolve the entrypoint package for a desktop/web target under the
/// project root that holds the entrypoint packages.
fn resolve_desktop_target(
  app_root : String,
  platform : String,
  renderer? : String = "auto",
) -> Result[DesktopTarget, String] {
  let candidates = match desktop_entrypoint_candidates(platform, renderer~) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  for candidate in candidates {
    if desktop_entrypoint_exists(app_root, candidate) {
      let target = if platform == "web" { "wasm-gc" } else { "native" }
      return Ok({ platform, package_path: candidate, target, })
    }
  }
  let expected = candidates.join(", ")
  Err(
    "no " +
    platform +
    " entrypoint package found under " +
    app_root +
    " (expected one of: " +
    expected +
    ")",
  )
}

///|
/// Resolve the project root that holds the entrypoint packages. Accepts
/// either the project directory or an entrypoint package directory such as
/// `windows_skia` / `web_wasm`.
fn resolve_desktop_root(
  base : String,
  app : String,
  platform : String,
  renderer? : String = "auto",
) -> Result[String, String] {
  let root = resolve_path(base, app)
  let candidates = match desktop_entrypoint_candidates(platform, renderer~) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  for candidate in candidates {
    if desktop_entrypoint_exists(root, candidate) {
      return Ok(root)
    }
  }
  // The argument may point at the entrypoint package itself.
  let leaf = base_name(root)
  let is_entrypoint_package = leaf == "web_wasm" ||
    leaf.has_suffix("_skia") ||
    leaf.has_suffix("_wgpu") ||
    leaf.has_suffix("_sun")
  if is_entrypoint_package {
    let has_manifest = @fs.is_file(join_path(root, "moon.pkg")) catch {
      _ => false
    }
    if has_manifest {
      return Ok(parent_dir(root))
    }
  }
  Ok(root)
}

///|
/// Relative path of `target` below `root`, or `target` unchanged when it is
/// not below `root`. Both inputs are normalized before comparison.
fn relative_path_under(root : String, target : String) -> String {
  let root = normalized_path(root)
  let target = normalized_path(target)
  if root == "" || root == "." {
    return target
  }
  let prefix = root + "/"
  if target == root {
    return "."
  }
  if target.has_prefix(prefix) {
    target[prefix.length():].to_owned()
  } else {
    target
  }
}

///|
/// Predicted `moon build` output path for a desktop/web target. `moon` names
/// executables `.exe` on every host platform.
fn desktop_artifact_path(
  build_base : String,
  package_path : String,
  target : String,
  release? : Bool = false,
) -> String {
  let stage = if release { "release" } else { "debug" }
  let extension = if target == "wasm-gc" { ".wasm" } else { ".exe" }
  join_path(
    build_base,
    "_build/" +
    target +
    "/" +
    stage +
    "/build/" +
    normalized_path(package_path) +
    "/" +
    base_name(package_path) +
    extension,
  )
}

///|
/// The directory `moon build` writes `_build` under: the enclosing workspace
/// root when one exists, otherwise the app root (standalone projects).
fn desktop_build_base(app_root : String) -> String {
  moon_workspace_root(app_root).unwrap_or(app_root)
}

///|
/// Spawn-cwd and package-path pair for `moon build`: moon is invoked from
/// the build base (workspace root) with the package path relative to it.
fn desktop_moon_invocation_for_root(
  app_root : String,
  target : DesktopTarget,
) -> (String, String) {
  let build_base = desktop_build_base(app_root)
  let package_path = relative_path_under(
    build_base,
    join_path(app_root, target.package_path),
  )
  (build_base, package_path)
}

///|
priv struct DesktopFlow {
  app_root : String
  build_base : String
  package_path : String
  target : DesktopTarget
}

///|
/// One-shot resolution for command flows that start from a base directory
/// and a positional `` argument.
fn resolve_desktop_flow(
  base : String,
  app : String,
  platform : String,
  renderer? : String = "auto",
) -> Result[DesktopFlow, String] {
  let app_root = match resolve_desktop_root(base, app, platform, renderer~) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let target = match resolve_desktop_target(app_root, platform, renderer~) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let (build_base, package_path) = desktop_moon_invocation_for_root(
    app_root, target,
  )
  Ok({ app_root, build_base, package_path, target, })
}