#!/usr/bin/env bash

: <<'DOC'
Returns the normalized OS name for the current platform.

  xplat_os
  # => linux | darwin
DOC
function xplat_os() {
  case "$(uname -s)" in
    Linux)  echo "linux" ;;
    Darwin) echo "darwin" ;;
    *)
      error "Unsupported OS: $(uname -s)"
      return 1
      ;;
  esac
}

: <<'DOC'
Returns the normalized CPU architecture.

Uses dpkg on Debian-based systems for accuracy (reflects multiarch config),
falls back to uname -m mapping otherwise.

  xplat_arch
  # => amd64 | arm64
DOC
function xplat_arch() {
  if command -v dpkg >/dev/null 2>&1; then
    dpkg --print-architecture
    return
  fi

  local machine
  machine="$(uname -m)"
  case "$machine" in
    x86_64)  echo "amd64" ;;
    aarch64) echo "arm64" ;;
    *)       echo "$machine" ;;
  esac
}

: <<'DOC'
Returns the CPU architecture as reported by uname -m.
This is the canonical form used by LLVM/GNU target triples.

  xplat_arch_uname
  # => x86_64 | aarch64
DOC
function xplat_arch_uname() {
  uname -m
}

: <<'DOC'
Composes a target triple from its components: arch-vendor-os or arch-vendor-os-abi.

  xplat_target_triple "x86_64" "unknown" "linux" "gnu"
  # => x86_64-unknown-linux-gnu

  xplat_target_triple "aarch64" "apple" "darwin"
  # => aarch64-apple-darwin
DOC
function xplat_target_triple() {
  local arch="$1" vendor="$2" os="$3" abi="${4:-}"

  if [[ -n "$abi" ]]; then
    echo "${arch}-${vendor}-${os}-${abi}"
  else
    echo "${arch}-${vendor}-${os}"
  fi
}

: <<'DOC'
Returns the target triple for the current host.

  xplat_host_triple
  # => x86_64-unknown-linux-gnu | aarch64-apple-darwin
DOC
function xplat_host_triple() {
  local arch os

  arch="$(xplat_arch_uname)"
  os="$(xplat_os)"

  case "$os" in
    darwin) xplat_target_triple "$arch" "apple" "darwin" ;;
    linux)  xplat_target_triple "$arch" "unknown" "linux" "gnu" ;;
    *)
      error "No target triple mapping for OS: $os"
      return 1
      ;;
  esac
}

: <<'DOC'
Returns a JSON array of target triples from the build targets config.
Checks for a "build-targets" key in $PROJ/manifest.json first,
then falls back to lib/xplat/build_targets.default.json.

  xplat_build_triples
  # => ["x86_64-apple-darwin","aarch64-apple-darwin","x86_64-unknown-linux-gnu","aarch64-unknown-linux-gnu"]
DOC
function xplat_build_triples() {
  local targets
  targets="$(__xplat__resolve_build_target_config)"

  jq_reduce __xplat__concat_target_triples "$targets" "[]"
}

function __xplat__concat_target_triples() {
  local acc="$1" target="$2"
  local triples
  triples="$(__xplat__expand_target "$target")"
  jq_concat "$acc" "$triples"
}

function __xplat__expand_target() {
  local target="$1"
  local vendor os abi archs

  vendor="$(jq_get "$target" '.vendor')"
  os="$(jq_get "$target" '.os')"
  abi="$(jq_get "$target" '.abi' '')"
  archs="$(jq_get "$target" '.archs')"

  jq_map __xplat__arch_to_triple "$archs" "$vendor" "$os" "$abi"
}

function __xplat__arch_to_triple() {
  local arch vendor="$2" os="$3" abi="$4"
  local triple

  arch="$(jq_get "$1" '.')"

  if jq_defined "$abi"; then
    triple="$(xplat_target_triple "$arch" "$vendor" "$os" "$abi")"
  else
    triple="$(xplat_target_triple "$arch" "$vendor" "$os")"
  fi

  jq_wrap "$triple"
}

function __xplat__resolve_build_target_config() {
  local manifest="${PROJ}/manifest.json"
  local default_config="${SHELLOPS_DIR}/lib/xplat/build_targets.default.json"

  if [[ -f "$manifest" ]]; then
    local manifest_json override
    manifest_json="$(<"$manifest")"
    override="$(jq_get "$manifest_json" '."build-targets"' 'null')"
    if jq_defined "$override"; then
      echo "$override"
      return
    fi
  fi

  if [[ ! -f "$default_config" ]]; then
    error "Build targets config not found: $default_config"
    return 1
  fi

  cat "$default_config"
}
