#!/usr/bin/env bash

import "$SHELLOPS_DIR/lib/toml/api.sh"

: <<'DOC'
Cargo workspace and manifest management.

Provides introspection (metadata, crate lookup, version reading),
version management (set/sync versions), dependency helpers, and
workspace-aware publishing with topological ordering.
DOC

# ── Workspace introspection (via cargo metadata) ─────────────────────

: <<'DOC'
Returns the full cargo metadata JSON (no deps). Accepts an explicit
manifest path, or auto-detects from $PROJ by searching at, above, and
below the project root.

  json="$(cargo_metadata)"
  json="$(cargo_metadata "./backend/Cargo.toml")"
DOC
function cargo_metadata() {
  local manifest_path="${1:-$(__cargo__find_nearest_manifest)}"
  cargo metadata --format-version 1 --no-deps --manifest-path "$manifest_path" 2>/dev/null
}

function __cargo__find_nearest_manifest() {
  if [[ -f "$PROJ/Cargo.toml" ]]; then
    echo "$PROJ/Cargo.toml"
    return
  fi

  local dir="$PROJ"
  while [[ "$dir" != "/" ]]; do
    dir="$(dirname "$dir")"
    if [[ -f "$dir/Cargo.toml" ]]; then
      echo "$dir/Cargo.toml"
      return
    fi
  done

  local found
  found="$(find "$PROJ" -maxdepth 3 -name Cargo.toml -not -path "*/target/*" -print -quit 2>/dev/null)"

  if [[ -n "$found" ]]; then
    echo "$found"
    return
  fi

  error "no Cargo.toml found at or above $PROJ"
  return 1
}

: <<'DOC'
Returns the workspace root directory.

  cargo_workspace_root
  cargo_workspace_root "./backend/Cargo.toml"
DOC
function cargo_workspace_root() {
  local manifest_path="${1:-}"
  local metadata
  metadata="$(cargo_metadata "$manifest_path")"
  jq_get "$metadata" '.workspace_root'
}

: <<'DOC'
Returns the absolute path to the workspace root Cargo.toml.

  cargo_workspace_toml
  cargo_workspace_toml "./backend/Cargo.toml"
DOC
function cargo_workspace_toml() {
  echo "$(cargo_workspace_root "$1")/Cargo.toml"
}

: <<'DOC'
Finds the absolute path to a crate's directory by package name.

  cargo_find_crate "blizz"
  cargo_find_crate "blizz" "./backend/Cargo.toml"
DOC
function cargo_find_crate() {
  local name="$1" manifest_path="${2:-}"
  local metadata packages pkg

  metadata="$(cargo_metadata "$manifest_path")"
  packages="$(jq_get "$metadata" '.packages')"
  pkg="$(jq_find __cargo__match_crate_name "$packages" "$name")"

  if ! jq_defined "$pkg"; then
    error "crate '$name' not found in workspace"
    return 1
  fi

  dirname "$(jq_get "$pkg" '.manifest_path')"
}

function __cargo__match_crate_name() {
  local element="$1" target="$2"
  [[ "$(jq_get "$element" '.name')" == "$target" ]]
}

: <<'DOC'
Gets the version of a crate by package name. If no name is given,
returns the version of the first package in the workspace.

  cargo_get_crate_version "blizz"
  cargo_get_crate_version "blizz" "./backend/Cargo.toml"
DOC
function cargo_get_crate_version() {
  local name="${1:-}" manifest_path="${2:-}"
  local metadata packages pkg

  metadata="$(cargo_metadata "$manifest_path")"
  packages="$(jq_get "$metadata" '.packages')"

  if [[ -z "$name" ]]; then
    jq_get "$metadata" '.packages[0].version'
    return
  fi

  pkg="$(jq_find __cargo__match_crate_name "$packages" "$name")"

  if ! jq_defined "$pkg"; then
    error "crate '$name' not found in workspace"
    return 1
  fi

  jq_get "$pkg" '.version'
}

# ── Version management ────────────────────────────────────────────────

: <<'DOC'
Sets the version for a specific crate and updates the lockfile. Detects
whether the crate uses workspace-inherited versioning and updates the
correct Cargo.toml accordingly.

  cargo_set_crate_version "mypackage" "1.2.3"
  cargo_set_crate_version "mypackage" "1.2.3" "./backend/Cargo.toml"
DOC
function cargo_set_crate_version() {
  local package_name="$1" version="$2" manifest_path="${3:-}"

  local crate_dir
  crate_dir="$(cargo_find_crate "$package_name" "$manifest_path")"
  local cargo_toml="${crate_dir}/Cargo.toml"

  if __cargo__uses_workspace_version "$cargo_toml"; then
    cargo_set_workspace_version "$version" "$manifest_path"
    return
  fi

  local old_version
  old_version="$(cargo_get_crate_version "$package_name" "$manifest_path")"

  if [[ "$old_version" == "$version" ]]; then
    log "already at version '$version'"
    return 0
  fi

  toml_replace "$cargo_toml" "package" "version" "$old_version" "$version"
  __cargo__update_lockfile "$cargo_toml" "$package_name" "$version"
  log "Updated ${cargo_toml} to '$version'"
}

function __cargo__update_lockfile() {
  local manifest="$1" package="$2" version="$3"
  cargo update --manifest-path "$manifest" --package "$package" --precise "$version"
}

: <<'DOC'
Sets the workspace-level version directly (in [workspace.package]).

  cargo_set_workspace_version "1.2.3"
  cargo_set_workspace_version "1.2.3" "./backend/Cargo.toml"
DOC
function cargo_set_workspace_version() {
  local version="$1" manifest_path="${2:-}"
  local workspace_toml
  workspace_toml="$(cargo_workspace_toml "$manifest_path")"

  local old_version
  old_version="$(__cargo__read_workspace_package_version "$workspace_toml")"

  if [[ "$old_version" == "$version" ]]; then
    log "workspace already at version '$version'"
    return 0
  fi

  toml_replace "$workspace_toml" "workspace.package" "version" "$old_version" "$version"
  log "Updated workspace version in ${workspace_toml} to '$version'"

  cargo_workspace_sync_versions "$version" "$manifest_path"
}

function __cargo__uses_workspace_version() {
  local cargo_toml="$1"
  grep -q 'version\.workspace\s*=\s*true' "$cargo_toml" 2>/dev/null
}

function __cargo__read_workspace_package_version() {
  local version
  version="$(toml_get "$1" "workspace.package" "version")"
  echo "${version:-0.0.0}"
}

# ── Dependency helpers ────────────────────────────────────────────────

: <<'DOC'
Returns 0 if dep_name appears as a key under any [*dependencies*] section.

  cargo_has_dep "Cargo.toml" "serde"
DOC
function cargo_has_dep() {
  local file="$1" dep_name="$2"
  local in_deps=false

  while IFS= read -r line; do
    if [[ "$line" =~ ^\[.*dependencies.*\] ]]; then
      in_deps=true
    elif [[ "$line" == "["* ]]; then
      in_deps=false
    elif $in_deps && [[ "$line" =~ ^[[:space:]]*\"?${dep_name}\"?[[:space:]]*= ]]; then
      return 0
    fi
  done < "$file"

  return 1
}

: <<'DOC'
For an inline table dep with path = ..., ensures version = "=<ver>" is
present and correct. Scopes to any [*dependencies*] section.

  cargo_set_dep_version "Cargo.toml" "blizz-derive" "2.0.1"
DOC
function cargo_set_dep_version() {
  local file="$1" dep_name="$2" version="$3"
  __cargo__set_dep_version_in_section "$file" "$dep_name" "$version" "dependencies"
}

: <<'DOC'
Same as cargo_set_dep_version but scoped to [workspace.dependencies] only.

  cargo_set_workspace_dep_version "Cargo.toml" "blizz-derive" "2.0.1"
DOC
function cargo_set_workspace_dep_version() {
  local file="$1" dep_name="$2" version="$3"
  __cargo__set_dep_version_in_section "$file" "$dep_name" "$version" "workspace.dependencies"
}

function __cargo__set_dep_version_in_section() {
  local file="$1" dep_name="$2" version="$3" section_pattern="$4"
  local temp_file="${file}.tmp"
  local in_section=false

  while IFS= read -r line; do
    if [[ "$section_pattern" == "workspace.dependencies" ]]; then
      if [[ "$line" == "[workspace.dependencies]" ]]; then
        in_section=true
      elif [[ "$line" == "["* ]]; then
        in_section=false
      fi
    else
      if [[ "$line" =~ ^\[.*dependencies.*\] ]]; then
        in_section=true
      elif [[ "$line" == "["* ]]; then
        in_section=false
      fi
    fi

    if $in_section && [[ "$line" =~ ^[[:space:]]*\"?${dep_name}\"?[[:space:]]*= ]] && [[ "$line" == *"path"* ]]; then
      line="$(toml_inline_set "$line" "version" "\"=${version}\"")"
    fi

    echo "$line"
  done < "$file" > "$temp_file"
  mv "$temp_file" "$file"
}

# ── Workspace publishing ──────────────────────────────────────────────

: <<'DOC'
Returns a JSON array of internal path dependencies in the workspace.
Each entry is: { "consumer": "<manifest_path>", "dep": "<package_name>" }

Only includes deps where both consumer and dep are workspace members.

  cargo_workspace_get_internal_deps
  cargo_workspace_get_internal_deps "./backend/Cargo.toml"
DOC
function cargo_workspace_get_internal_deps() {
  local manifest_path="${1:-}"
  local metadata

  metadata="$(cargo_metadata "$manifest_path")"
  jq_run "$SHELLOPS_DIR/lib/cargo/get_internal_deps.jq" "$metadata"
}

: <<'DOC'
Ensures every internal path dep has version = "=<version>".
Handles both [workspace.dependencies] entries and inline path deps.

  cargo_workspace_sync_versions "1.2.3"
  cargo_workspace_sync_versions "1.2.3" "./backend/Cargo.toml"
DOC
function cargo_workspace_sync_versions() {
  local version="$1" manifest_path="${2:-}"
  local internal_deps workspace_toml

  internal_deps="$(cargo_workspace_get_internal_deps "$manifest_path")"
  workspace_toml="$(cargo_workspace_toml "$manifest_path")"

  __cargo__sync_workspace_deps_section "$workspace_toml" "$version" "$manifest_path"
  jq_foreach __cargo__sync_one_dep "$internal_deps" "$version"

  log "Internal dep versions synced to '=${version}'"
}

function __cargo__sync_one_dep() {
  local element="$1" version="$2"
  local consumer dep
  consumer="$(jq_get "$element" '.consumer')"
  dep="$(jq_get "$element" '.dep')"
  __cargo__sync_dep_version_in_file "$consumer" "$dep" "$version"
}

function __cargo__sync_workspace_deps_section() {
  local cargo_toml="$1" version="$2" manifest_path="${3:-}"

  [[ ! -f "$cargo_toml" ]] && return

  local internal_deps
  internal_deps="$(cargo_workspace_get_internal_deps "$manifest_path")"
  local count
  count="$(jq_len "$internal_deps")"

  for ((i = 0; i < count; i++)); do
    local dep_name
    dep_name="$(jq_get "$internal_deps" ".[$i].dep")"
    cargo_set_workspace_dep_version "$cargo_toml" "$dep_name" "$version"
  done
}

function __cargo__sync_dep_version_in_file() {
  local cargo_toml="$1" dep_name="$2" version="$3"

  [[ ! -f "$cargo_toml" ]] && return
  cargo_has_dep "$cargo_toml" "$dep_name" || return 0
  cargo_set_dep_version "$cargo_toml" "$dep_name" "$version"
}

: <<'DOC'
Returns the number of publishable workspace members (those without
publish = false or publish = []).

  cargo_workspace_get_publishable_count
  cargo_workspace_get_publishable_count "./backend/Cargo.toml"
DOC
function cargo_workspace_get_publishable_count() {
  local manifest_path="${1:-}"
  local metadata

  metadata="$(cargo_metadata "$manifest_path")"
  jq_run "$SHELLOPS_DIR/lib/cargo/get_publishable_count.jq" "$metadata"
}

: <<'DOC'
Returns workspace member package names in topological publish order.
Leaves (no internal deps) come first. Skips members with publish = false.

  cargo_workspace_get_publish_order
  cargo_workspace_get_publish_order "./backend/Cargo.toml"
DOC
function cargo_workspace_get_publish_order() {
  local manifest_path="${1:-}"
  local metadata graph

  metadata="$(cargo_metadata "$manifest_path")"
  graph="$(jq_run "$SHELLOPS_DIR/lib/cargo/get_publish_graph.jq" "$metadata")"

  __cargo__topo_sort "$graph"
}

# Kahn's algorithm. Input: JSON array of {name, deps:[]} objects.
# Outputs package names one per line in topological order (leaves first).
function __cargo__topo_sort() {
  local graph="$1"

  # shellcheck disable=SC2034
  local -A in_degree dep_map visited
  local all_names=()

  jq_foreach __cargo__topo_load_node "$graph" in_degree dep_map all_names

  local sorted=()
  while [[ ${#sorted[@]} -lt ${#all_names[@]} ]]; do
    local emitted=false
    local name
    for name in "${all_names[@]}"; do
      [[ -n "${visited[$name]:-}" ]] && continue
      [[ "${in_degree[$name]}" -ne 0 ]] && continue

      sorted+=("$name")
      visited["$name"]=1
      emitted=true
      __cargo__topo_decrement_dependents "$name" in_degree dep_map visited all_names
    done

    if ! $emitted; then
      error "cycle detected in workspace dependencies"
      return 1
    fi
  done

  printf '%s\n' "${sorted[@]}"
}

function __cargo__topo_load_node() {
  local element="$1"
  # shellcheck disable=SC2178
  local -n __in_degree="$2"
  # shellcheck disable=SC2178
  local -n __dep_map="$3"
  # shellcheck disable=SC2178
  local -n __all_names="$4"

  local name deps_json dep_list=""
  name="$(jq_get "$element" '.name')"
  deps_json="$(jq_get "$element" '.deps')"

  __all_names+=("$name")
  __in_degree["$name"]=0

  local dep_count
  dep_count="$(jq_len "$deps_json")"
  if [[ "$dep_count" -gt 0 ]]; then
    dep_list="$(jq_join "$deps_json" $'\n')"
    __in_degree["$name"]=$dep_count
  fi
  __dep_map["$name"]="$dep_list"
}

function __cargo__topo_decrement_dependents() {
  local emitted_name="$1"
  # shellcheck disable=SC2178
  local -n __in_degree="$2"
  # shellcheck disable=SC2178
  local -n __dep_map="$3"
  # shellcheck disable=SC2178
  local -n __visited="$4"
  # shellcheck disable=SC2178
  local -n __all_names="$5"

  local other
  for other in "${__all_names[@]}"; do
    [[ -n "${__visited[$other]:-}" ]] && continue
    [[ -z "${__dep_map[$other]}" ]] && continue
    if echo "${__dep_map[$other]}" | grep -qx "$emitted_name" 2>/dev/null; then
      __in_degree["$other"]=$(( ${__in_degree[$other]} - 1 ))
    fi
  done
}

: <<'DOC'
Validates that all internal path deps have the correct version pin.
Returns 0 if valid, 1 if violations found (prints errors to stderr).

  cargo_workspace_validate_versions "1.2.3"
  cargo_workspace_validate_versions "1.2.3" "./backend/Cargo.toml"
DOC
function cargo_workspace_validate_versions() {
  local expected_version="$1" manifest_path="${2:-}"
  local internal_deps

  internal_deps="$(cargo_workspace_get_internal_deps "$manifest_path")"

  __CARGO__VALIDATE_VIOLATIONS=0
  jq_foreach __cargo__validate_one_dep "$internal_deps" "$expected_version"

  if [[ $__CARGO__VALIDATE_VIOLATIONS -gt 0 ]]; then
    hint "run \`shell/action rust sync-cargo-version --workspace ${expected_version}\`"
    unset __CARGO__VALIDATE_VIOLATIONS
    return 1
  fi
  unset __CARGO__VALIDATE_VIOLATIONS
  return 0
}

function __cargo__validate_one_dep() {
  local element="$1" expected_version="$2"
  local consumer dep
  consumer="$(jq_get "$element" '.consumer')"
  dep="$(jq_get "$element" '.dep')"

  local check_file="$consumer"
  if grep -q "${dep}\.workspace\s*=\s*true\|${dep}\s*=.*workspace\s*=\s*true" "$consumer" 2>/dev/null; then
    local ws_root
    ws_root="$(dirname "$consumer")"
    while [[ "$ws_root" != "/" ]]; do
      if grep -q '^\[workspace\]' "${ws_root}/Cargo.toml" 2>/dev/null; then
        check_file="${ws_root}/Cargo.toml"
        break
      fi
      ws_root="$(dirname "$ws_root")"
    done
  fi

  if grep -q "${dep}.*version\s*=\s*\"=${expected_version}\"" "$check_file" 2>/dev/null; then
    return 0
  fi

  local found_version="(missing)"
  local existing
  existing="$(grep -oP "${dep}.*version\s*=\s*\"\K[^\"]*" "$check_file" 2>/dev/null || true)"
  [[ -n "$existing" ]] && found_version="$existing"

  error "internal dep '$dep' in $check_file has stale version spec"
  log "  found:    version = \"${found_version}\""
  log "  expected: version = \"=${expected_version}\""
  __CARGO__VALIDATE_VIOLATIONS=$(( __CARGO__VALIDATE_VIOLATIONS + 1 ))
}

: <<'DOC'
Publishes all workspace members in dependency order. Skips members
already published at the target version. Validates internal dep versions
before starting.

  cargo_workspace_publish "1.2.3" [--registry <name>] [--dry-run]
DOC
function cargo_workspace_publish() {
  local version="$1"; shift
  __CARGO_PUBLISH_REGISTRY=""
  __CARGO_PUBLISH_DRY_RUN=""
  __CARGO_PUBLISH_MANIFEST=""
  __CARGO_PUBLISH_COUNT=0

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --registry) __CARGO_PUBLISH_REGISTRY="$2"; shift ;;
      --dry-run) __CARGO_PUBLISH_DRY_RUN="--dry-run" ;;
      --cargo-path) __CARGO_PUBLISH_MANIFEST="$2"; shift ;;
    esac
    shift
  done

  if ! cargo_workspace_validate_versions "$version" "$__CARGO_PUBLISH_MANIFEST"; then
    error "Internal dep version validation failed. Aborting publish."
    return 1
  fi

  local publish_order
  publish_order="$(cargo_workspace_get_publish_order "$__CARGO_PUBLISH_MANIFEST")"

  local as_json
  as_json="$(jq_split "$publish_order")"
  jq_foreach __cargo__publish_one_crate "$as_json" "$version"

  log "Published $__CARGO_PUBLISH_COUNT crate(s)"
  unset __CARGO_PUBLISH_REGISTRY __CARGO_PUBLISH_DRY_RUN __CARGO_PUBLISH_MANIFEST __CARGO_PUBLISH_COUNT
}

function __cargo__publish_one_crate() {
  local element="$1" version="$2"
  local pkg_name
  pkg_name="$(jq_unwrap "$element")"

  if __cargo__is_already_published "$pkg_name" "$version" "$__CARGO_PUBLISH_REGISTRY"; then
    log "  $pkg_name@$version already published, skipping"
    return 0
  fi

  log "Publishing $pkg_name@$version..."
  local registry_flag=""
  [[ -n "$__CARGO_PUBLISH_REGISTRY" ]] && registry_flag="--registry $__CARGO_PUBLISH_REGISTRY"

  # shellcheck disable=SC2086
  cargo publish -p "$pkg_name" --allow-dirty $registry_flag $__CARGO_PUBLISH_DRY_RUN \
    ${__CARGO_PUBLISH_MANIFEST:+--manifest-path "$__CARGO_PUBLISH_MANIFEST"}

  __CARGO_PUBLISH_COUNT=$(( __CARGO_PUBLISH_COUNT + 1 ))

  if [[ -z "$__CARGO_PUBLISH_DRY_RUN" ]]; then
    __cargo__await_registry "$pkg_name" "$version" "$__CARGO_PUBLISH_REGISTRY"
  fi
}

function __cargo__is_already_published() {
  local name="$1" version="$2" registry="${3:-}"

  local search_output
  if [[ -n "$registry" ]]; then
    search_output="$(cargo search "$name" --registry "$registry" 2>/dev/null || true)"
  else
    search_output="$(cargo search "$name" 2>/dev/null || true)"
  fi

  echo "$search_output" | grep -q "^${name}\s*=\s*\"${version}\""
}

function __cargo__await_registry() {
  local name="$1" version="$2" registry="${3:-}"
  local max_attempts=12 attempt=0

  log "  Waiting for $name@$version to appear in registry index..."
  while [[ $attempt -lt $max_attempts ]]; do
    sleep 5
    if __cargo__is_already_published "$name" "$version" "$registry"; then
      log "  $name@$version available"
      return 0
    fi
    attempt=$((attempt + 1))
  done

  error "Timed out waiting for $name@$version in registry (${max_attempts}x5s)"
  return 1
}
