#!/usr/bin/env bash
set -euo pipefail

SCRIPT_NAME="$(basename "$0")"
DEFAULT_DAYS=90
OUT_DIR="${OUT_DIR:-./tigergate-inventory-output}"
VERSION=1.0.0

print_banner_plain() {
  cat <<'BANNER'
  _______ _                 _____       _
 |__   __(_)               / ____|     | |
    | |   _  __ _  ___ _ _| |  __  __ _| |_ ___
    | |  | |/ _` |/ _ \ '__| | |_ |/ _` | __/ _ \
    | |  | | (_| |  __/ |  | |__| | (_| | ||  __/
    |_|  |_|\__, |\___|_|   \_____|\__,_|\__\___|
             __/ |
            |___/
  TigerGate
  Secure code, cloud, containers, and runtime from one platform - version $VERSION.
BANNER
}

print_banner_color() {
  local orange=$'\033[38;5;208m'
  local black=$'\033[30m'
  local white=$'\033[97m'
  local red=$'\033[31m'
  local bold=$'\033[1m'
  local reset=$'\033[0m'
  cat <<BANNER
${bold}${red}  _______ _                 _____       _        ${reset}
${bold}${red} |__   __(_)               / ____|     | |       ${reset}
${bold}${red}    | |   _  __ _  ___ _ _| |  __  __ _| |_ ___ ${reset}
${bold}${red}    | |  | |/ _\` |/ _ \\ '__| | |_ |/ _\` | __/ _ \\${reset}
${bold}${red}    | |  | | (_| |  __/ |  | |__| | (_| | ||  __/${reset}
${bold}${red}    |_|  |_|\\__, |\\___|_|   \\_____|\\__,_|\\__\\___|${reset}
${bold}${red}             __/ |                                ${reset}
${bold}${red}            |___/                                 ${reset}
${bold}  TigerGate${reset}
  Secure code, cloud, containers, and runtime from one platform - version $VERSION.
BANNER
}

print_banner() {
  if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
    print_banner_color
  else
    print_banner_plain
  fi
}

usage() {
  cat <<'USAGE'
Customer inventory counter for TigerGate sizing.

Usage:
  scripts/customer-inventory-counts.sh
  scripts/customer-inventory-counts.sh --product code --provider github
  scripts/customer-inventory-counts.sh --product container --provider ecr
  scripts/customer-inventory-counts.sh --product cloud --provider aws
  scripts/customer-inventory-counts.sh --product kspm
  scripts/customer-inventory-counts.sh --product runtime

Products:
  code        Count active repositories updated in the last N days.
  container   Count container registries/repositories/images/tags.
  cloud       Count cloud resources by provider/type.
  kspm        Count Kubernetes clusters and workloads.
  runtime     Count Kubernetes runtime workload coverage targets.
  all         Run every configured product.

Sizing definitions:
  code        Repositories with push/update activity inside the selected day window.
  container   Registry repositories/packages plus image tags/versions/artifacts.
  cloud       Cloud inventory assets by provider resource type. AWS prefers AWS Config;
              Azure uses ARM resources; GCP uses Cloud Asset Inventory; OCI uses search.
  kspm        Kubernetes clusters plus controller workloads and pods/nodes.
  runtime     Kubernetes runtime coverage targets: nodes, pods, and controller workloads.

Supported providers:
  code:       github, gitlab, azuredevops, bitbucket
  container: dockerhub, ghcr, ecr, acr, gcr, gar, gitlab, harbor
  cloud:      aws, azure, gcp, oci

Common options:
  --product <name>       Product to count.
  --provider <name>      Provider to count. Repeat by comma for some products.
  --days <number>        Activity window for code repositories. Default: 90.
  --out-dir <path>       Output directory. Default: ./tigergate-inventory-output.
  --details              Print itemized tables to stdout.
  --no-details           Only print summaries to stdout.
  --help                 Show this help.

Credentials are read from environment variables or prompted when missing. The script never prints tokens.
At a prompt, press Enter without a value to skip that provider.

Code:
  GitHub:
    GITHUB_TOKEN, GITHUB_ORG recommended for org-level counts, GITHUB_API_URL optional
  GitLab:
    GITLAB_TOKEN, GITLAB_GROUP optional, GITLAB_API_URL optional
  Azure DevOps:
    AZURE_DEVOPS_PAT, AZURE_DEVOPS_ORG, AZURE_DEVOPS_PROJECT optional
  Bitbucket:
    BITBUCKET_USERNAME, BITBUCKET_APP_PASSWORD, BITBUCKET_WORKSPACE

Container:
  Docker Hub:
    DOCKERHUB_USERNAME, DOCKERHUB_TOKEN, DOCKERHUB_NAMESPACE
  GHCR:
    GITHUB_TOKEN, GHCR_OWNER
  AWS ECR:
    AWS_PROFILE optional, AWS_REGION or AWS_REGIONS optional
  Azure ACR:
    ACR_NAME or ACR_NAMES
  GCR:
    GCP_PROJECT or GCP_PROJECTS
  Google Artifact Registry:
    GCP_PROJECT or GCP_PROJECTS, GAR_LOCATION or GAR_LOCATIONS
  GitLab Registry:
    GITLAB_TOKEN, GITLAB_GROUP optional, GITLAB_API_URL optional
  Harbor:
    HARBOR_URL, HARBOR_USERNAME, HARBOR_PASSWORD, HARBOR_PROJECT optional

Cloud:
  AWS:
    AWS_PROFILE optional, AWS_REGION or AWS_REGIONS optional
  Azure:
    Azure CLI login, AZURE_SUBSCRIPTION_ID optional
  GCP:
    gcloud auth, GCP_PROJECT or GCP_PROJECTS optional
  OCI:
    OCI_PROFILE optional, OCI_COMPARTMENT_ID

KSPM / Runtime:
  kubectl configured contexts, KUBE_CONTEXTS optional comma-separated list
USAGE
}

log() {
  printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}

die() {
  printf 'ERROR: %s\n' "$*" >&2
  exit 1
}

have() {
  command -v "$1" >/dev/null 2>&1
}

need() {
  if ! have "$1"; then
    log "Missing required command: $1; skipping this provider"
    return 1
  fi
}

require_env() {
  local name="$1"
  [ -n "${!name:-}" ] && return 0

  if [ ! -t 0 ]; then
    log "Missing $name and stdin is not interactive; skipping this provider"
    return 1
  fi

  local value prompt secret=0
  prompt="Enter $name"
  case "$name" in
    *TOKEN*|*PASSWORD*|*PAT*|*SECRET*|*PRIVATE_KEY*) secret=1 ;;
  esac

  if [ "$secret" = "1" ]; then
    printf '%s: ' "$prompt" >&2
    read -r -s value
    printf '\n' >&2
  else
    printf '%s: ' "$prompt" >&2
    read -r value
  fi

  if [ -z "$value" ]; then
    log "No value entered for $name; skipping this provider"
    return 1
  fi

  export "$name=$value"
}

prompt_env_if_missing() {
  local name="$1"
  local message="${2:-Enter $name}"
  [ -n "${!name:-}" ] && return 0

  if [ ! -t 0 ]; then
    return 1
  fi

  local value
  printf '%s: ' "$message" >&2
  read -r value
  [ -n "$value" ] || return 1
  export "$name=$value"
}

json_escape() {
  jq -Rn --arg v "$1" '$v'
}

init_output() {
  mkdir -p "$OUT_DIR"
  SUMMARY_FILE="$OUT_DIR/summary.tsv"
  DETAILS_FILE="$OUT_DIR/details.tsv"
  JSONL_FILE="$OUT_DIR/details.jsonl"
  REPORT_FILE="$OUT_DIR/report.txt"
  : >"$SUMMARY_FILE"
  : >"$DETAILS_FILE"
  : >"$JSONL_FILE"
  : >"$REPORT_FILE"
  printf 'product\tprovider\tmetric\tcount\n' >>"$SUMMARY_FILE"
  printf 'product\tprovider\tname\ttype\tcount\tlast_activity\turl\n' >>"$DETAILS_FILE"

  # Clean up any product-specific files from previous runs
  for p in code container cloud kspm runtime; do
    rm -f "$OUT_DIR/summary-$p.tsv" "$OUT_DIR/details-$p.tsv" "$OUT_DIR/details-$p.jsonl" "$OUT_DIR/report-$p.txt"
  done
}

add_summary() {
  local product="$1" provider="$2" metric="$3" count="${4:-0}"
  printf '%s\t%s\t%s\t%s\n' "$product" "$provider" "$metric" "$count" >>"$SUMMARY_FILE"

  # Write to product-specific summary file
  local prod_summary="$OUT_DIR/summary-$product.tsv"
  if [ ! -f "$prod_summary" ]; then
    printf 'product\tprovider\tmetric\tcount\n' >"$prod_summary"
  fi
  printf '%s\t%s\t%s\t%s\n' "$product" "$provider" "$metric" "$count" >>"$prod_summary"
}

add_detail() {
  local product="$1" provider="$2" name="$3" type="$4" count="$5" last_activity="${6:-}" url="${7:-}"
  printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$product" "$provider" "$name" "$type" "$count" "$last_activity" "$url" >>"$DETAILS_FILE"
  jq -cn \
    --arg product "$product" \
    --arg provider "$provider" \
    --arg name "$name" \
    --arg type "$type" \
    --arg count "$count" \
    --arg last_activity "$last_activity" \
    --arg url "$url" \
    '{product:$product,provider:$provider,name:$name,type:$type,count:($count|tonumber? // $count),last_activity:$last_activity,url:$url}' >>"$JSONL_FILE"

  # Write to product-specific details file
  local prod_details="$OUT_DIR/details-$product.tsv"
  if [ ! -f "$prod_details" ]; then
    printf 'product\tprovider\tname\ttype\tcount\tlast_activity\turl\n' >"$prod_details"
  fi
  printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$product" "$provider" "$name" "$type" "$count" "$last_activity" "$url" >>"$prod_details"

  # Write to product-specific JSONL file
  local prod_jsonl="$OUT_DIR/details-$product.jsonl"
  jq -cn \
    --arg product "$product" \
    --arg provider "$provider" \
    --arg name "$name" \
    --arg type "$type" \
    --arg count "$count" \
    --arg last_activity "$last_activity" \
    --arg url "$url" \
    '{product:$product,provider:$provider,name:$name,type:$type,count:($count|tonumber? // $count),last_activity:$last_activity,url:$url}' >>"$prod_jsonl"
}

report_box() {
  local title="$1"
  local line="  $title  "
  local width=${#line}
  local border
  border="$(printf '%.0s-' $(seq 1 "$width"))"
  printf '+%s+\n' "$border"
  printf '|%s|\n' "$line"
  printf '+%s+\n' "$border"
}

report_kspm_workloads() {
  printf 'cluster\tworkload_total\n'
  awk -F'\t' 'NR > 1 && $1 == "kspm" && $4 == "workload_total" { split($3, a, "/workload_total"); print a[1] "\t" $5 }' "$DETAILS_FILE" |
    sort
  printf 'TOTAL\t%s\n' "$(awk -F'\t' 'NR > 1 && $1 == "kspm" && $4 == "workload_total" { sum += $5 } END { print sum + 0 }' "$DETAILS_FILE")"
}

report_runtime_observations() {
  printf 'metric\tcount\n'
  awk -F'\t' '
    NR > 1 && $1 == "runtime" && $4 ~ /^(processes_observed|unique_executables|network_connections|dns_queries|unique_images)$/ {
      print $4 "\t" $5
    }
  ' "$DETAILS_FILE" |
    awk '
      BEGIN {
        order["processes_observed"] = 1
        order["unique_executables"] = 2
        order["network_connections"] = 3
        order["dns_queries"] = 4
        order["unique_images"] = 5
      }
      {
        print order[$1] "\t" $0
      }
    ' |
    sort -n |
    cut -f2-
}

render_table_file() {
  local file="$1"
  column -t -s $'\t' "$file" 2>/dev/null || cat "$file"
}

print_outputs() {
  {
    print_banner_plain
    printf '\nGenerated: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
    printf 'Output directory: %s\n' "$OUT_DIR"
    printf '\n'
    report_box "Summary"
    render_table_file "$SUMMARY_FILE"
    printf '\n'
    if awk -F'\t' '$1 == "kspm" && $4 == "workload_total" { found = 1 } END { exit(found ? 0 : 1) }' "$DETAILS_FILE" 2>/dev/null; then
      report_box "KSPM Workload Details"
      report_kspm_workloads | column -t -s $'\t' 2>/dev/null || report_kspm_workloads
      printf '\n'
    fi
    if awk -F'\t' '$1 == "runtime" && $4 ~ /^(processes_observed|unique_executables|network_connections|dns_queries|unique_images)$/ { found = 1 } END { exit(found ? 0 : 1) }' "$DETAILS_FILE" 2>/dev/null; then
      report_box "Runtime Observations"
      report_runtime_observations | column -t -s $'\t' 2>/dev/null || report_runtime_observations
      printf '\n'
    fi
    report_box "Details"
    render_table_file "$DETAILS_FILE"
  } >"$REPORT_FILE"

  printf '\nSummary written to: %s\n' "$SUMMARY_FILE"
  printf 'Details written to: %s\n' "$DETAILS_FILE"
  printf 'JSONL details written to: %s\n' "$JSONL_FILE"
  printf 'Customer report written to: %s\n' "$REPORT_FILE"

  # Generate product-specific reports
  for p in "${unique_products[@]}"; do
    local prod_report="$OUT_DIR/report-$p.txt"
    local prod_summary="$OUT_DIR/summary-$p.tsv"
    local prod_details="$OUT_DIR/details-$p.tsv"

    if [ -f "$prod_summary" ] && [ "$(wc -l < "$prod_summary")" -gt 1 ]; then
      {
        print_banner_plain
        printf '\nGenerated: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
        printf 'Product: %s\n' "$p"
        printf 'Output directory: %s\n' "$OUT_DIR"
        printf '\n'
        report_box "Summary"
        render_table_file "$prod_summary"
        printf '\n'
        if [ "$p" = "kspm" ] && [ -f "$prod_details" ] && awk -F'\t' '$4 == "workload_total" { found = 1 } END { exit(found ? 0 : 1) }' "$prod_details" 2>/dev/null; then
          report_box "KSPM Workload Details"
          report_kspm_workloads | column -t -s $'\t' 2>/dev/null || report_kspm_workloads
          printf '\n'
        fi
        if [ "$p" = "runtime" ] && [ -f "$prod_details" ] && awk -F'\t' '$4 ~ /^(processes_observed|unique_executables|network_connections|dns_queries|unique_images)$/ { found = 1 } END { exit(found ? 0 : 1) }' "$prod_details" 2>/dev/null; then
          report_box "Runtime Observations"
          report_runtime_observations | column -t -s $'\t' 2>/dev/null || report_runtime_observations
          printf '\n'
        fi
        report_box "Details"
        render_table_file "$prod_details"
      } >"$prod_report"
      printf 'Product %s report written to: %s\n' "$p" "$prod_report"
      printf 'Product %s summary written to: %s\n' "$p" "$prod_summary"
      printf 'Product %s details written to: %s\n' "$p" "$prod_details"
    fi
  done

  printf '\nSummary:\n'
  column -t -s $'\t' "$SUMMARY_FILE" 2>/dev/null || cat "$SUMMARY_FILE"
  if [ "$SHOW_DETAILS" = "1" ]; then
    printf '\nDetails:\n'
    column -t -s $'\t' "$DETAILS_FILE" 2>/dev/null || cat "$DETAILS_FILE"
  fi
}

curl_json() {
  local url="$1"
  shift
  curl -fsSL "$@" "$url"
}

github_api() {
  local url="$1"
  curl_json "$url" \
    -H "Authorization: Bearer $GITHUB_TOKEN" \
    -H "Accept: application/vnd.github+json" \
    -H "X-GitHub-Api-Version: 2022-11-28"
}

github_paginate() {
  local url="$1"
  while [ -n "$url" ]; do
    local headers body next
    headers="$(mktemp)"
    body="$(mktemp)"
    curl -fsSL -D "$headers" \
      -H "Authorization: Bearer $GITHUB_TOKEN" \
      -H "Accept: application/vnd.github+json" \
      -H "X-GitHub-Api-Version: 2022-11-28" \
      "$url" -o "$body"
    cat "$body"
    next="$(awk -F'[<>]' '/rel="next"/ {print $2}' "$headers" | tail -n 1)"
    rm -f "$headers" "$body"
    url="$next"
  done
}

count_code_github() {
  need curl || return 1
  need jq || return 1
  require_env GITHUB_TOKEN || return 1
  local api="${GITHUB_API_URL:-https://api.github.com}"
  local cutoff="$1"
  local endpoint
  if [ -z "${GITHUB_ORG:-}" ]; then
    prompt_env_if_missing GITHUB_ORG "Enter GitHub organization name"
  fi
  if [ -n "${GITHUB_ORG:-}" ]; then
    endpoint="$api/orgs/$GITHUB_ORG/repos?per_page=100&type=all"
  else
    log "Missing GitHub organization; skipping this provider"
    return 1
  fi

  log "Counting GitHub organization $GITHUB_ORG repositories active since $cutoff"
  local tmp
  tmp="$(mktemp)"
  github_paginate "$endpoint" | jq -c '.[]' >"$tmp"
  local total active
  total="$(wc -l <"$tmp" | tr -d ' ')"
  active="$(jq --arg cutoff "$cutoff" 'select((.pushed_at // .updated_at // "") >= $cutoff)' "$tmp" | jq -s 'length')"
  local developers_tmp active_devs_tmp loc_tmp
  developers_tmp="$(mktemp)"
  active_devs_tmp="$(mktemp)"
  loc_tmp="$(mktemp)"
  printf '%s\n' "$GITHUB_ORG" >"$developers_tmp"
  add_summary code github repositories_total "$total"
  add_summary code github repositories_active_last_${DAYS}_days "$active"
  add_summary code github organizations 1
  local committers_tmp
  committers_tmp="$(mktemp)"
  jq -r --arg cutoff "$cutoff" '[.full_name,"repository",1,((.pushed_at // .updated_at // "")),.html_url] | @tsv' "$tmp" |
    while IFS=$'\t' read -r name type count last url; do
      add_detail code github "$name" "$type" "$count" "$last" "$url"
      local repo_full_name repo_cutoff cutoff_url commits_url
      repo_full_name="${name}"
      cutoff_url="$(printf '%s' "$cutoff" | jq -sRr @uri)"
      commits_url="$api/repos/$repo_full_name/commits?since=$cutoff_url&per_page=100"
      github_paginate "$commits_url" |
        jq -r '.[] | (.author.login // .commit.author.email // .commit.author.name // empty)' >>"$committers_tmp"
      github_paginate "$api/repos/$repo_full_name/contributors?anon=1&per_page=100" |
        jq -r '.[] | (.login // .email // .name // empty)' >>"$developers_tmp"
      github_api "$api/repos/$repo_full_name/languages" |
        jq -r 'to_entries | map(.value) | add // 0' >>"$loc_tmp"
    done
  local committer_count
  committer_count="$(sort -u "$committers_tmp" | sed '/^$/d' | wc -l | tr -d ' ')"
  add_summary code github active_committers_last_${DAYS}_days "$committer_count"
  add_summary code github active_developers_last_${DAYS}_days "$committer_count"
  add_summary code github developers "$(sort -u "$developers_tmp" | sed '/^$/d' | wc -l | tr -d ' ')"
  add_summary code github total_loc_estimate_bytes "$(awk '{s+=$1} END{print s+0}' "$loc_tmp")"
  add_summary code total repositories_total "$total"
  sort -u "$committers_tmp" | sed '/^$/d' |
    while IFS= read -r committer; do
      [ -n "$committer" ] || continue
      add_detail code github "$GITHUB_ORG/$committer" committer 1 "" "https://github.com/$GITHUB_ORG"
    done
  rm -f "$tmp"
  rm -f "$committers_tmp"
  rm -f "$developers_tmp" "$active_devs_tmp" "$loc_tmp"
}

gitlab_api() {
  local url="$1"
  curl_json "$url" -H "PRIVATE-TOKEN: $GITLAB_TOKEN"
}

gitlab_paginate() {
  local url="$1"
  while [ -n "$url" ]; do
    local headers body next
    headers="$(mktemp)"
    body="$(mktemp)"
    curl -fsSL -D "$headers" -H "PRIVATE-TOKEN: $GITLAB_TOKEN" "$url" -o "$body"
    cat "$body"
    next="$(awk -F': ' 'tolower($1)=="x-next-page" {gsub(/\r/,"",$2); print $2}' "$headers" | tail -n 1)"
    rm -f "$headers" "$body"
    if [ -n "$next" ]; then
      url="${url%%page=*}page=$next"
    else
      url=""
    fi
  done
}

count_code_gitlab() {
  need curl || return 1
  need jq || return 1
  require_env GITLAB_TOKEN || return 1
  local cutoff="$1"
  local api="${GITLAB_API_URL:-https://gitlab.com/api/v4}"
  local endpoint
  if [ -n "${GITLAB_GROUP:-}" ]; then
    local group
    group="$(printf '%s' "$GITLAB_GROUP" | jq -sRr @uri)"
    endpoint="$api/groups/$group/projects?include_subgroups=true&per_page=100&page=1"
  else
    endpoint="$api/projects?membership=true&simple=true&per_page=100&page=1"
  fi

  log "Counting GitLab repositories active since $cutoff"
  local tmp
  tmp="$(mktemp)"
  gitlab_paginate "$endpoint" | jq -c '.[]' >"$tmp"
  local total active
  total="$(wc -l <"$tmp" | tr -d ' ')"
  active="$(jq --arg cutoff "$cutoff" 'select((.last_activity_at // "") >= $cutoff)' "$tmp" | jq -s 'length')"
  add_summary code gitlab repositories_total "$total"
  add_summary code gitlab repositories_active_last_${DAYS}_days "$active"
  add_summary code total repositories_total "$total"
  jq -r --arg cutoff "$cutoff" 'select((.last_activity_at // "") >= $cutoff) | [.path_with_namespace,"repository",1,.last_activity_at,.web_url] | @tsv' "$tmp" |
    while IFS=$'\t' read -r name type count last url; do
      add_detail code gitlab "$name" "$type" "$count" "$last" "$url"
    done
  rm -f "$tmp"
}

azure_devops_api() {
  local url="$1"
  curl_json "$url" -u ":$AZURE_DEVOPS_PAT"
}

aws_region_seed() {
  local seed="${AWS_REGION:-${AWS_DEFAULT_REGION:-}}"
  if [ -z "$seed" ]; then
    seed="$(aws configure get region 2>/dev/null || true)"
  fi
  [ -n "$seed" ] || seed="us-east-1"
  printf '%s\n' "$seed"
}

resolve_aws_regions() {
  if [ -n "${AWS_REGIONS:-}" ]; then
    printf '%s\n' "$AWS_REGIONS"
    return 0
  fi

  local seed regions
  seed="$(aws_region_seed)"
  regions="$(aws ec2 describe-regions --region "$seed" --query 'Regions[].RegionName' --output text 2>/dev/null | tr '\t' ',')"
  [ -n "$regions" ] && printf '%s\n' "$regions"
}

count_code_azuredevops() {
  need curl || return 1
  need jq || return 1
  require_env AZURE_DEVOPS_PAT || return 1
  require_env AZURE_DEVOPS_ORG || return 1
  local cutoff="$1"
  local base="https://dev.azure.com/$AZURE_DEVOPS_ORG"
  local projects_tmp repos_tmp
  projects_tmp="$(mktemp)"
  repos_tmp="$(mktemp)"

  log "Counting Azure DevOps repositories active since $cutoff"
  if [ -n "${AZURE_DEVOPS_PROJECT:-}" ]; then
    printf '%s\n' "$AZURE_DEVOPS_PROJECT" >"$projects_tmp"
  else
    azure_devops_api "$base/_apis/projects?api-version=7.1&\$top=1000" |
      jq -r '.value[].name' >"$projects_tmp"
  fi

  while IFS= read -r project; do
    [ -n "$project" ] || continue
    local encoded_project
    encoded_project="$(printf '%s' "$project" | jq -sRr @uri)"
    azure_devops_api "$base/$encoded_project/_apis/git/repositories?api-version=7.1" |
      jq -c --arg project "$project" '.value[] | . + {project:$project}' >>"$repos_tmp"
  done <"$projects_tmp"

  local total=0 active=0
  total="$(wc -l <"$repos_tmp" | tr -d ' ')"
  while IFS= read -r repo; do
    [ -n "$repo" ] || continue
    local project repo_id name url last count
    project="$(jq -r '.project' <<<"$repo")"
    repo_id="$(jq -r '.id' <<<"$repo")"
    name="$(jq -r '.name' <<<"$repo")"
    url="$(jq -r '.webUrl // .remoteUrl // ""' <<<"$repo")"
    local encoded_project encoded_repo_id
    encoded_project="$(printf '%s' "$project" | jq -sRr @uri)"
    encoded_repo_id="$(printf '%s' "$repo_id" | jq -sRr @uri)"
    last="$(azure_devops_api "$base/$encoded_project/_apis/git/repositories/$encoded_repo_id/commits?searchCriteria.\$top=1&api-version=7.1" |
      jq -r '.value[0].committer.date // .value[0].author.date // ""')"
    if [ -n "$last" ] && [ "$last" \> "$cutoff" ]; then
      active=$((active + 1))
      add_detail code azuredevops "$project/$name" repository 1 "$last" "$url"
    fi
  done <"$repos_tmp"

  add_summary code azuredevops repositories_total "$total"
  add_summary code azuredevops repositories_active_last_${DAYS}_days "$active"
  add_summary code total repositories_total "$total"
  rm -f "$projects_tmp" "$repos_tmp"
}

count_code_bitbucket() {
  need curl || return 1
  need jq || return 1
  require_env BITBUCKET_USERNAME || return 1
  require_env BITBUCKET_APP_PASSWORD || return 1
  require_env BITBUCKET_WORKSPACE || return 1
  local cutoff="$1"
  local url="https://api.bitbucket.org/2.0/repositories/$BITBUCKET_WORKSPACE?pagelen=100"
  local tmp
  tmp="$(mktemp)"

  log "Counting Bitbucket repositories active since $cutoff"
  while [ -n "$url" ]; do
    local body
    body="$(curl -fsSL -u "$BITBUCKET_USERNAME:$BITBUCKET_APP_PASSWORD" "$url")"
    jq -c '.values[]' <<<"$body" >>"$tmp"
    url="$(jq -r '.next // ""' <<<"$body")"
  done

  local total active
  total="$(wc -l <"$tmp" | tr -d ' ')"
  active="$(jq --arg cutoff "$cutoff" 'select((.updated_on // "") >= $cutoff)' "$tmp" | jq -s 'length')"
  add_summary code bitbucket repositories_total "$total"
  add_summary code bitbucket repositories_active_last_${DAYS}_days "$active"
  add_summary code total repositories_total "$total"
  jq -r --arg cutoff "$cutoff" 'select((.updated_on // "") >= $cutoff) | [.full_name,"repository",1,.updated_on,(.links.html.href // "")] | @tsv' "$tmp" |
    while IFS=$'\t' read -r name type count last url; do
      add_detail code bitbucket "$name" "$type" "$count" "$last" "$url"
    done
  rm -f "$tmp"
}

count_container_dockerhub() {
  need curl || return 1
  need jq || return 1
  require_env DOCKERHUB_NAMESPACE || return 1
  local auth=()
  if [ -n "${DOCKERHUB_USERNAME:-}" ] && [ -n "${DOCKERHUB_TOKEN:-}" ]; then
    local token
    token="$(curl -fsSL -H 'Content-Type: application/json' -X POST \
      -d "$(jq -cn --arg username "$DOCKERHUB_USERNAME" --arg password "$DOCKERHUB_TOKEN" '{username:$username,password:$password}')" \
      https://hub.docker.com/v2/users/login/ | jq -r '.token')"
    auth=(-H "Authorization: JWT $token")
  fi
  local url="https://hub.docker.com/v2/repositories/$DOCKERHUB_NAMESPACE/?page_size=100"
  local repos=0 tags=0
  log "Counting Docker Hub images for namespace $DOCKERHUB_NAMESPACE"
  while [ -n "$url" ]; do
    local body
    body="$(curl -fsSL "${auth[@]}" "$url")"
    while IFS= read -r repo; do
      [ -n "$repo" ] || continue
      repos=$((repos + 1))
      local name tag_count
      name="$(jq -r '.name' <<<"$repo")"
      tag_count="$(curl -fsSL "${auth[@]}" "https://hub.docker.com/v2/repositories/$DOCKERHUB_NAMESPACE/$name/tags?page_size=1" | jq -r '.count // 0')"
      tags=$((tags + tag_count))
      add_detail container dockerhub "$DOCKERHUB_NAMESPACE/$name" repository "$tag_count" "" "https://hub.docker.com/r/$DOCKERHUB_NAMESPACE/$name"
    done < <(jq -c '.results[]' <<<"$body")
    url="$(jq -r '.next // ""' <<<"$body")"
  done
  add_summary container dockerhub repositories "$repos"
  add_summary container dockerhub image_tags "$tags"
  add_summary container total image_tags "$tags"
}

count_container_ghcr() {
  need curl || return 1
  need jq || return 1
  require_env GITHUB_TOKEN || return 1
  require_env GHCR_OWNER || return 1
  local api="${GITHUB_API_URL:-https://api.github.com}"
  local owner_type="${GHCR_OWNER_TYPE:-orgs}"
  local url="$api/$owner_type/$GHCR_OWNER/packages?package_type=container&per_page=100"
  local packages=0 versions=0
  log "Counting GHCR container packages for $GHCR_OWNER"
  while [ -n "$url" ]; do
    local headers body next
    headers="$(mktemp)"
    body="$(mktemp)"
    curl -fsSL -D "$headers" \
      -H "Authorization: Bearer $GITHUB_TOKEN" \
      -H "Accept: application/vnd.github+json" \
      "$url" -o "$body"
    while IFS= read -r package; do
      [ -n "$package" ] || continue
      packages=$((packages + 1))
      local name html_url versions_url count
      name="$(jq -r '.name' <<<"$package")"
      html_url="$(jq -r '.html_url // ""' <<<"$package")"
      versions_url="$api/$owner_type/$GHCR_OWNER/packages/container/$(printf '%s' "$name" | jq -sRr @uri)/versions?per_page=1"
      count="$(curl -fsSL -I \
        -H "Authorization: Bearer $GITHUB_TOKEN" \
        -H "Accept: application/vnd.github+json" \
        "$versions_url" | awk -F'[&=<>]' '/rel="last"/ {for (i=1;i<=NF;i++) if ($i=="page") print $(i+1)}' | tail -n 1)"
      if [ -z "$count" ]; then
        count="$(curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" "$versions_url" | jq 'length')"
      fi
      versions=$((versions + count))
      add_detail container ghcr "$GHCR_OWNER/$name" package "$count" "" "$html_url"
    done < <(jq -c '.[]' "$body")
    next="$(awk -F'[<>]' '/rel="next"/ {print $2}' "$headers" | tail -n 1)"
    rm -f "$headers" "$body"
    url="$next"
  done
  add_summary container ghcr packages "$packages"
  add_summary container ghcr image_versions "$versions"
  add_summary container total image_versions "$versions"
}

count_container_ecr() {
  need aws || return 1
  need jq || return 1
  local regions
  regions="$(resolve_aws_regions)"
  if [ -z "$regions" ]; then
    log "Missing AWS regions and EC2 DescribeRegions auto-discovery failed; skipping this provider"
    return 1
  fi
  local repos=0 images=0
  log "Counting AWS ECR repositories and images"
  IFS=',' read -ra region_list <<<"$regions"
  for region in "${region_list[@]}"; do
    [ -n "$region" ] || continue
    while IFS= read -r repo; do
      [ -n "$repo" ] || continue
      repos=$((repos + 1))
      local count
      count="$(aws ecr describe-images --region "$region" --repository-name "$repo" --query 'length(imageDetails[])' --output text 2>/dev/null || printf '0')"
      images=$((images + count))
      add_detail container ecr "$region/$repo" repository "$count" "" ""
    done < <(aws ecr describe-repositories --region "$region" --query 'repositories[].repositoryName' --output text 2>/dev/null | tr '\t' '\n')
  done
  add_summary container ecr repositories "$repos"
  add_summary container ecr images "$images"
  add_summary container total images "$images"
}

count_container_acr() {
  need az || return 1
  local names="${ACR_NAMES:-${ACR_NAME:-}}"
  [ -n "$names" ] || names="$(az acr list --query '[].name' -o tsv 2>/dev/null || true)"
  if [ -z "$names" ]; then
    log "Missing ACR_NAME/ACR_NAMES and Azure registry auto-discovery failed; skipping this provider"
    return 1
  fi
  local repos=0 tags=0
  log "Counting Azure Container Registry repositories and tags"
  IFS=',' read -ra registries <<<"$names"
  for registry in "${registries[@]}"; do
    [ -n "$registry" ] || continue
    while IFS= read -r repo; do
      [ -n "$repo" ] || continue
      repos=$((repos + 1))
      local count
      count="$(az acr repository show-tags --name "$registry" --repository "$repo" -o tsv 2>/dev/null | wc -l | tr -d ' ')"
      tags=$((tags + count))
      add_detail container acr "$registry/$repo" repository "$count" "" ""
    done < <(az acr repository list --name "$registry" -o tsv)
  done
  add_summary container acr repositories "$repos"
  add_summary container acr image_tags "$tags"
  add_summary container total image_tags "$tags"
}

count_container_gcr() {
  need gcloud || return 1
  local projects="${GCP_PROJECTS:-${GCP_PROJECT:-$(gcloud config get-value project 2>/dev/null || true)}}"
  if [ -z "$projects" ]; then
    prompt_env_if_missing GCP_PROJECTS "Enter GCP project IDs for GCR, comma-separated"
    projects="${GCP_PROJECTS:-${GCP_PROJECT:-}}"
  fi
  if [ -z "$projects" ]; then
    log "Missing GCP_PROJECT/GCP_PROJECTS and gcloud default project; skipping this provider"
    return 1
  fi
  local images=0 tags=0
  log "Counting GCR images and tags"
  IFS=',' read -ra project_list <<<"$projects"
  for project in "${project_list[@]}"; do
    [ -n "$project" ] || continue
    for host in gcr.io us.gcr.io eu.gcr.io asia.gcr.io; do
      while IFS= read -r image; do
        [ -n "$image" ] || continue
        images=$((images + 1))
        local count
        count="$(gcloud container images list-tags "$image" --format='value(tags)' 2>/dev/null | awk -F',' '{for(i=1;i<=NF;i++) if($i!="") c++} END{print c+0}')"
        tags=$((tags + count))
        add_detail container gcr "$image" image "$count" "" ""
      done < <(gcloud container images list --repository="$host/$project" --format='value(name)' 2>/dev/null || true)
    done
  done
  add_summary container gcr images "$images"
  add_summary container gcr image_tags "$tags"
  add_summary container total image_tags "$tags"
}

count_container_gar() {
  need gcloud || return 1
  local projects="${GCP_PROJECTS:-${GCP_PROJECT:-$(gcloud config get-value project 2>/dev/null || true)}}"
  local locations="${GAR_LOCATIONS:-${GAR_LOCATION:-}}"
  if [ -z "$projects" ]; then
    prompt_env_if_missing GCP_PROJECTS "Enter GCP project IDs for Artifact Registry, comma-separated"
    projects="${GCP_PROJECTS:-${GCP_PROJECT:-}}"
  fi
  if [ -z "$locations" ]; then
    prompt_env_if_missing GAR_LOCATIONS "Enter Artifact Registry locations, comma-separated, or leave blank to auto-discover"
    locations="${GAR_LOCATIONS:-${GAR_LOCATION:-}}"
  fi
  if [ -z "$projects" ]; then
    log "Missing GCP_PROJECT/GCP_PROJECTS and gcloud default project; skipping this provider"
    return 1
  fi
  local repos=0 packages=0 versions=0
  log "Counting Google Artifact Registry Docker packages and versions"
  IFS=',' read -ra project_list <<<"$projects"
  for project in "${project_list[@]}"; do
    [ -n "$project" ] || continue
    local loc_args=()
    if [ -n "$locations" ]; then
      IFS=',' read -ra locs <<<"$locations"
    else
      mapfile -t locs < <(gcloud artifacts locations list --project "$project" --format='value(locationId)' 2>/dev/null || true)
    fi
    for loc in "${locs[@]}"; do
      [ -n "$loc" ] || continue
      while IFS= read -r repo; do
        [ -n "$repo" ] || continue
        repos=$((repos + 1))
        while IFS= read -r package; do
          [ -n "$package" ] || continue
          packages=$((packages + 1))
          local count
          count="$(gcloud artifacts docker images list "$loc-docker.pkg.dev/$project/$repo" --include-tags --format='value(package)' 2>/dev/null | grep -F "$package" | wc -l | tr -d ' ')"
          versions=$((versions + count))
          add_detail container gar "$loc/$repo/$package" package "$count" "" ""
        done < <(gcloud artifacts packages list --project "$project" --location "$loc" --repository "$repo" --format='value(name.basename())' 2>/dev/null || true)
      done < <(gcloud artifacts repositories list --project "$project" --location "$loc" --filter='format:DOCKER' --format='value(name.basename())' 2>/dev/null || true)
    done
  done
  add_summary container gar repositories "$repos"
  add_summary container gar packages "$packages"
  add_summary container gar image_versions "$versions"
  add_summary container total image_versions "$versions"
}

count_container_gitlab() {
  need curl || return 1
  need jq || return 1
  require_env GITLAB_TOKEN || return 1
  local api="${GITLAB_API_URL:-https://gitlab.com/api/v4}"
  local endpoint
  if [ -n "${GITLAB_GROUP:-}" ]; then
    local group
    group="$(printf '%s' "$GITLAB_GROUP" | jq -sRr @uri)"
    endpoint="$api/groups/$group/projects?include_subgroups=true&per_page=100&page=1"
  else
    endpoint="$api/projects?membership=true&simple=true&per_page=100&page=1"
  fi
  local repositories=0 tags=0
  log "Counting GitLab Container Registry repositories and tags"
  while IFS= read -r project; do
    [ -n "$project" ] || continue
    local project_id project_name
    project_id="$(jq -r '.id' <<<"$project")"
    project_name="$(jq -r '.path_with_namespace' <<<"$project")"
    local registry_url="$api/projects/$project_id/registry/repositories?per_page=100&page=1"
    while IFS= read -r registry; do
      [ -n "$registry" ] || continue
      repositories=$((repositories + 1))
      local registry_id path count
      registry_id="$(jq -r '.id' <<<"$registry")"
      path="$(jq -r '.path' <<<"$registry")"
      count="$(gitlab_paginate "$api/projects/$project_id/registry/repositories/$registry_id/tags?per_page=100&page=1" | jq -c '.[]' | wc -l | tr -d ' ')"
      tags=$((tags + count))
      add_detail container gitlab "$project_name/$path" repository "$count" "" ""
    done < <(gitlab_paginate "$registry_url" | jq -c '.[]')
  done < <(gitlab_paginate "$endpoint" | jq -c '.[]')
  add_summary container gitlab repositories "$repositories"
  add_summary container gitlab image_tags "$tags"
  add_summary container total image_tags "$tags"
}

count_container_harbor() {
  need curl || return 1
  need jq || return 1
  require_env HARBOR_URL || return 1
  require_env HARBOR_USERNAME || return 1
  require_env HARBOR_PASSWORD || return 1
  local base="${HARBOR_URL%/}/api/v2.0"
  local projects_url="$base/projects?page_size=100"
  [ -n "${HARBOR_PROJECT:-}" ] && projects_url="$base/projects?name=$HARBOR_PROJECT&page_size=100"
  local repos=0 artifacts=0
  log "Counting Harbor repositories and artifacts"
  while IFS= read -r project; do
    [ -n "$project" ] || continue
    local project_name
    project_name="$(jq -r '.name' <<<"$project")"
    while IFS= read -r repo; do
      [ -n "$repo" ] || continue
      repos=$((repos + 1))
      local repo_name encoded count
      repo_name="$(jq -r '.name' <<<"$repo")"
      encoded="$(printf '%s' "$repo_name" | jq -sRr @uri)"
      count="$(curl -fsSL -I -u "$HARBOR_USERNAME:$HARBOR_PASSWORD" "$base/projects/$project_name/repositories/$encoded/artifacts?page_size=1" |
        awk -F': ' 'tolower($1)=="x-total-count" {gsub(/\r/,"",$2); print $2}' | tail -n 1)"
      if [ -z "$count" ]; then
        count="$(curl -fsSL -u "$HARBOR_USERNAME:$HARBOR_PASSWORD" "$base/projects/$project_name/repositories/$encoded/artifacts?page_size=100" | jq 'length')"
      fi
      artifacts=$((artifacts + count))
      add_detail container harbor "$repo_name" repository "$count" "" ""
    done < <(curl -fsSL -u "$HARBOR_USERNAME:$HARBOR_PASSWORD" "$base/projects/$project_name/repositories?page_size=100" | jq -c '.[]')
  done < <(curl -fsSL -u "$HARBOR_USERNAME:$HARBOR_PASSWORD" "$projects_url" | jq -c '.[]')
  add_summary container harbor repositories "$repos"
  add_summary container harbor artifacts "$artifacts"
  add_summary container total artifacts "$artifacts"
}

count_cloud_aws() {
  need aws || return 1
  need jq || return 1
  local regions
  regions="$(resolve_aws_regions)"
  if [ -z "$regions" ]; then
    log "Missing AWS regions and EC2 DescribeRegions auto-discovery failed; skipping this provider"
    return 1
  fi
  local total=0
  local config_regions=0 fallback_regions=0
  log "Counting AWS resources"
  IFS=',' read -ra region_list <<<"$regions"
  for region in "${region_list[@]}"; do
    [ -n "$region" ] || continue

    local config_tmp
    config_tmp="$(mktemp)"
    if aws configservice select-resource-config \
      --region "$region" \
      --expression 'SELECT resourceType, COUNT(*) GROUP BY resourceType' \
      --output json >"$config_tmp" 2>/dev/null; then
      local region_total
      region_total="$(jq -r '[.Results[] | fromjson | .["COUNT(*)"]] | add // 0' "$config_tmp")"
      total=$((total + region_total))
      config_regions=$((config_regions + 1))
      jq -r --arg region "$region" '.Results[] | fromjson | [$region + "/" + .resourceType, .resourceType, .["COUNT(*)"]] | @tsv' "$config_tmp" |
        while IFS=$'\t' read -r name type count; do
          add_detail cloud aws "$name" "$type" "$count" "" ""
        done
      rm -f "$config_tmp"
      continue
    fi
    rm -f "$config_tmp"

    fallback_regions=$((fallback_regions + 1))
    log "AWS Config inventory unavailable in $region; using direct API fallback for common billable resource families"
    local ec2 volumes snapshots eips eni alb nlb clb target_groups asg rds clusters lambdas eks ecr sg vpc subnets natgw ecs_clusters ecs_services ecs_tasks secrets kms log_groups apis
    ec2="$(aws ec2 describe-instances --region "$region" --query 'length(Reservations[].Instances[])' --output text 2>/dev/null || printf '0')"
    volumes="$(aws ec2 describe-volumes --region "$region" --query 'length(Volumes[])' --output text 2>/dev/null || printf '0')"
    snapshots="$(aws ec2 describe-snapshots --owner-ids self --region "$region" --query 'length(Snapshots[])' --output text 2>/dev/null || printf '0')"
    eips="$(aws ec2 describe-addresses --region "$region" --query 'length(Addresses[])' --output text 2>/dev/null || printf '0')"
    eni="$(aws ec2 describe-network-interfaces --region "$region" --query 'length(NetworkInterfaces[])' --output text 2>/dev/null || printf '0')"
    alb="$(aws elbv2 describe-load-balancers --region "$region" --query 'length(LoadBalancers[?Type==`application`])' --output text 2>/dev/null || printf '0')"
    nlb="$(aws elbv2 describe-load-balancers --region "$region" --query 'length(LoadBalancers[?Type==`network`])' --output text 2>/dev/null || printf '0')"
    target_groups="$(aws elbv2 describe-target-groups --region "$region" --query 'length(TargetGroups[])' --output text 2>/dev/null || printf '0')"
    clb="$(aws elb describe-load-balancers --region "$region" --query 'length(LoadBalancerDescriptions[])' --output text 2>/dev/null || printf '0')"
    asg="$(aws autoscaling describe-auto-scaling-groups --region "$region" --query 'length(AutoScalingGroups[])' --output text 2>/dev/null || printf '0')"
    rds="$(aws rds describe-db-instances --region "$region" --query 'length(DBInstances[])' --output text 2>/dev/null || printf '0')"
    clusters="$(aws rds describe-db-clusters --region "$region" --query 'length(DBClusters[])' --output text 2>/dev/null || printf '0')"
    lambdas="$(aws lambda list-functions --region "$region" --query 'length(Functions[])' --output text 2>/dev/null || printf '0')"
    eks="$(aws eks list-clusters --region "$region" --query 'length(clusters[])' --output text 2>/dev/null || printf '0')"
    ecr="$(aws ecr describe-repositories --region "$region" --query 'length(repositories[])' --output text 2>/dev/null || printf '0')"
    sg="$(aws ec2 describe-security-groups --region "$region" --query 'length(SecurityGroups[])' --output text 2>/dev/null || printf '0')"
    vpc="$(aws ec2 describe-vpcs --region "$region" --query 'length(Vpcs[])' --output text 2>/dev/null || printf '0')"
    subnets="$(aws ec2 describe-subnets --region "$region" --query 'length(Subnets[])' --output text 2>/dev/null || printf '0')"
    natgw="$(aws ec2 describe-nat-gateways --region "$region" --query 'length(NatGateways[])' --output text 2>/dev/null || printf '0')"
    ecs_clusters="$(aws ecs list-clusters --region "$region" --query 'length(clusterArns[])' --output text 2>/dev/null || printf '0')"
    ecs_services="$({ aws ecs list-clusters --region "$region" --query 'clusterArns[]' --output text 2>/dev/null | tr '\t' '\n' | while read -r c; do [ -n "$c" ] && aws ecs list-services --region "$region" --cluster "$c" --query 'length(serviceArns[])' --output text 2>/dev/null; done | awk '{s+=$1} END{print s+0}'; } 2>/dev/null || printf '0')"
    ecs_tasks="$({ aws ecs list-clusters --region "$region" --query 'clusterArns[]' --output text 2>/dev/null | tr '\t' '\n' | while read -r c; do [ -n "$c" ] && aws ecs list-tasks --region "$region" --cluster "$c" --query 'length(taskArns[])' --output text 2>/dev/null; done | awk '{s+=$1} END{print s+0}'; } 2>/dev/null || printf '0')"
    secrets="$(aws secretsmanager list-secrets --region "$region" --query 'length(SecretList[])' --output text 2>/dev/null || printf '0')"
    kms="$(aws kms list-keys --region "$region" --query 'length(Keys[])' --output text 2>/dev/null || printf '0')"
    log_groups="$(aws logs describe-log-groups --region "$region" --query 'length(logGroups[])' --output text 2>/dev/null || printf '0')"
    apis="$(aws apigateway get-rest-apis --region "$region" --query 'length(items[])' --output text 2>/dev/null || printf '0')"
    for pair in \
      "AWS::EC2::Instance:$ec2" "AWS::EC2::Volume:$volumes" "AWS::EC2::Snapshot:$snapshots" \
      "AWS::EC2::EIP:$eips" "AWS::EC2::NetworkInterface:$eni" "AWS::ElasticLoadBalancingV2::LoadBalancer/Application:$alb" \
      "AWS::ElasticLoadBalancingV2::LoadBalancer/Network:$nlb" "AWS::ElasticLoadBalancingV2::TargetGroup:$target_groups" \
      "AWS::ElasticLoadBalancing::LoadBalancer:$clb" "AWS::AutoScaling::AutoScalingGroup:$asg" \
      "AWS::RDS::DBInstance:$rds" "AWS::RDS::DBCluster:$clusters" "AWS::Lambda::Function:$lambdas" \
      "AWS::EKS::Cluster:$eks" "AWS::ECR::Repository:$ecr" "AWS::EC2::SecurityGroup:$sg" \
      "AWS::EC2::VPC:$vpc" "AWS::EC2::Subnet:$subnets" "AWS::EC2::NatGateway:$natgw" \
      "AWS::ECS::Cluster:$ecs_clusters" "AWS::ECS::Service:$ecs_services" "AWS::ECS::Task:$ecs_tasks" \
      "AWS::SecretsManager::Secret:$secrets" "AWS::KMS::Key:$kms" "AWS::Logs::LogGroup:$log_groups" \
      "AWS::ApiGateway::RestApi:$apis"; do
      local type="${pair%:*}" count="${pair##*:}"
      count="$(printf '%s\n' "$count" | awk 'NR==1 { print $1 + 0; exit } END { if (NR == 0) print 0 }')"
      total=$((total + count))
      add_detail cloud aws "$region/$type" "$type" "$count" "" ""
    done
  done
  local s3 iam_users iam_roles cloudfront
  s3="$(aws s3api list-buckets --query 'length(Buckets[])' --output text 2>/dev/null || printf '0')"
  iam_users="$(aws iam list-users --query 'length(Users[])' --output text 2>/dev/null || printf '0')"
  iam_roles="$(aws iam list-roles --query 'length(Roles[])' --output text 2>/dev/null || printf '0')"
  cloudfront="$(aws cloudfront list-distributions --query 'length(DistributionList.Items[])' --output text 2>/dev/null || printf '0')"
  add_detail cloud aws global/s3_bucket s3_bucket "$s3" "" ""
  add_detail cloud aws global/iam_user iam_user "$iam_users" "" ""
  add_detail cloud aws global/iam_role iam_role "$iam_roles" "" ""
  add_detail cloud aws global/cloudfront_distribution cloudfront_distribution "$cloudfront" "" ""
  total=$((total + s3 + iam_users + iam_roles + cloudfront))
  add_summary cloud aws resources_total "$total"
  add_summary cloud aws inventory_regions_aws_config "$config_regions"
  add_summary cloud aws inventory_regions_direct_api_fallback "$fallback_regions"
}

count_cloud_azure() {
  need az || return 1
  need jq || return 1
  if [ -n "${AZURE_SUBSCRIPTION_ID:-}" ]; then
    if ! az account set --subscription "$AZURE_SUBSCRIPTION_ID" >/dev/null 2>&1; then
      log "Azure subscription selection failed for AZURE_SUBSCRIPTION_ID=$AZURE_SUBSCRIPTION_ID; skipping this provider"
      return 1
    fi
  fi
  log "Counting Azure resources"
  local total=0
  local q out count label
  for q in \
    "Microsoft.Compute/disks|Compute Disks|Microsoft.Compute/disks" \
    "Microsoft.Compute/virtualMachines|Virtual Machines|Microsoft.Compute/virtualMachines" \
    "Microsoft.ContainerService/managedClusters|Managed Clusters|Microsoft.ContainerService/managedClusters" \
    "Microsoft.Network/loadBalancers|Load Balancers|Microsoft.Network/loadBalancers" \
    "Microsoft.Network/networkInterfaces|Network Interfaces|Microsoft.Network/networkInterfaces" \
    "Microsoft.Network/publicIPAddresses|Public IP Addresses|Microsoft.Network/publicIPAddresses" \
    "Microsoft.Network/routeTables|Route Tables|Microsoft.Network/routeTables" \
    "Microsoft.Network/virtualNetworks|Virtual Networks|Microsoft.Network/virtualNetworks" \
    "Microsoft.Network/virtualNetworks subnets|Subnets|Microsoft.Network/virtualNetworks/subnets"; do
    label="${q#*|}"
    label="${label%%|*}"
    local expr="${q%%|*}"
    local resource_type="${q##*|}"
    if [ "$resource_type" = "Microsoft.Network/virtualNetworks/subnets" ]; then
      out="$(az graph query -q "resources | where type =~ 'Microsoft.Network/virtualNetworks' | mv-expand subnets = properties.subnets | summarize Count = count()" -o json 2>/dev/null || true)"
    else
      out="$(az graph query -q "resources | where type =~ '$expr' | summarize Count = count()" -o json 2>/dev/null || true)"
    fi
    count="$(jq -r '.data[0].Count // 0' <<<"$out" 2>/dev/null || printf '0')"
    total=$((total + count))
    add_detail cloud azure "$label" "$resource_type" "$count" "" ""
  done
  add_summary cloud azure resources_total "$total"
  add_summary cloud total resources_total "$total"
}

count_cloud_gcp() {
  need gcloud || return 1
  need jq || return 1
  local projects="${GCP_PROJECTS:-${GCP_PROJECT:-$(gcloud config get-value project 2>/dev/null || true)}}"
  if [ -z "$projects" ]; then
    prompt_env_if_missing GCP_PROJECTS "Enter GCP project IDs to inventory, comma-separated"
    projects="${GCP_PROJECTS:-${GCP_PROJECT:-}}"
  fi
  if [ -z "$projects" ]; then
    log "Missing GCP_PROJECT/GCP_PROJECTS and gcloud default project; skipping this provider"
    return 1
  fi
  local total=0
  log "Counting GCP resources with Cloud Asset Inventory"
  IFS=',' read -ra project_list <<<"$projects"
  for project in "${project_list[@]}"; do
    [ -n "$project" ] || continue
    local project_total=0
    local asset_types=(
      "compute.googleapis.com/Subnetwork:Subnetwork"
      "compute.googleapis.com/Route:Route"
      "compute.googleapis.com/Firewall:Firewall"
      "logging.googleapis.com/LogBucket:LogBucket"
      "serviceusage.googleapis.com/Service:Service"
    )
    local asset spec label count
    for spec in "${asset_types[@]}"; do
      asset="${spec%%:*}"
      label="${spec##*:}"
      count="$(gcloud asset search-all-resources --project="$project" --asset-types="$asset" --format='value(name)' 2>/dev/null | sort -u | wc -l | tr -d ' ')"
      project_total=$((project_total + count))
      add_detail cloud gcp "$project/$label" "$asset" "$count" "" ""
    done
    total=$((total + project_total))
    add_summary cloud gcp "$project" total "$project_total"
  done
  add_summary cloud gcp resources_total "$total"
  add_summary cloud total resources_total "$total"
}

count_cloud_oci() {
  need oci || return 1
  need jq || return 1
  require_env OCI_COMPARTMENT_ID || return 1
  local profile_args=()
  [ -n "${OCI_PROFILE:-}" ] && profile_args=(--profile "$OCI_PROFILE")
  log "Counting OCI resources in compartment $OCI_COMPARTMENT_ID"
  local tmp total
  tmp="$(mktemp)"
  oci search resource structured-search "${profile_args[@]}" \
    --query-text "query all resources where compartmentId = '$OCI_COMPARTMENT_ID'" \
    --all >"$tmp"
  total="$(jq '.data.items | length' "$tmp")"
  add_summary cloud oci resources_total "$total"
  add_summary cloud total resources_total "$total"
  jq -r '.data.items | group_by(."resource-type")[] | [.[0]."resource-type", length] | @tsv' "$tmp" |
    while IFS=$'\t' read -r type count; do
      add_detail cloud oci "$type" "$type" "$count" "" ""
    done
  rm -f "$tmp"
}

selected_kube_contexts() {
  if [ -n "${KUBE_CONTEXTS:-}" ]; then
    tr ',' '\n' <<<"$KUBE_CONTEXTS"
  else
    local contexts
    contexts="$(kubectl config get-contexts -o name 2>/dev/null || true)"
    if [ -z "$contexts" ]; then
      prompt_env_if_missing KUBE_CONTEXTS "Enter Kubernetes contexts to inventory, comma-separated"
      tr ',' '\n' <<<"${KUBE_CONTEXTS:-}"
    else
      printf '%s\n' "$contexts"
    fi
  fi
}

kubectl_count_kind() {
  local ctx="$1" kind="$2" namespaced="${3:-1}"
  local args=(--context "$ctx" get "$kind")
  [ "$namespaced" = "1" ] && args+=(-A)
  kubectl "${args[@]}" --no-headers 2>/dev/null | wc -l | tr -d ' '
}

count_k8s_inventory() {
  local product="$1"
  need kubectl || return 1
  local clusters=0 namespaces=0 nodes=0 pods=0 containers=0 running_containers=0 images=0 deployments=0 statefulsets=0 daemonsets=0 jobs=0 cronjobs=0 replicasets=0 services=0 ingresses=0 endpointslices=0 configmaps=0 secrets=0 serviceaccounts=0 roles=0 rolebindings=0 clusterroles=0 clusterrolebindings=0 networkpolicies=0 pvs=0 pvcs=0 storageclasses=0 resourcequotas=0 limitranges=0 hpas=0 pdbs=0 runtimeclasses=0 priorityclasses=0 crds=0 mutatingwebhooks=0 validatingwebhooks=0 workload_total=0 kubesystem_pods=0
  log "Counting Kubernetes inventory for $product"
  while IFS= read -r ctx; do
    [ -n "$ctx" ] || continue
    if ! kubectl --context "$ctx" get ns --request-timeout=10s >/dev/null 2>&1; then
      log "Skipping Kubernetes context $ctx because it is not reachable or not authorized"
      continue
    fi
    clusters=$((clusters + 1))
    local node
    node="$(kubectl_count_kind "$ctx" nodes 0)"
    if [ "$product" = "runtime" ]; then
      nodes=$((nodes + node))
      workload_total=$((workload_total + node))
      add_detail "$product" kubernetes "$ctx/nodes" node "$node" "" ""
      continue
    fi
    local ns pod deploy sts ds job cron rs svc ing es cm sec sa role rb cr crb np pv pvc sc rq lim hpa pdb rc pc crd mw vc running_ctr ctr img proc exe net dns ks_pod
    ns="$(kubectl_count_kind "$ctx" namespaces 1)"
    pod="$(kubectl_count_kind "$ctx" pods 1)"
    deploy="$(kubectl_count_kind "$ctx" deployments 1)"
    sts="$(kubectl_count_kind "$ctx" statefulsets 1)"
    ds="$(kubectl_count_kind "$ctx" daemonsets 1)"
    job="$(kubectl_count_kind "$ctx" jobs 1)"
    cron="$(kubectl_count_kind "$ctx" cronjobs 1)"
    rs="$(kubectl_count_kind "$ctx" replicasets 1)"
    svc="$(kubectl_count_kind "$ctx" services 1)"
    ing="$(kubectl_count_kind "$ctx" ingresses 1)"
    es="$(kubectl_count_kind "$ctx" endpointslices 1)"
    cm="$(kubectl_count_kind "$ctx" configmaps 1)"
    sec="$(kubectl_count_kind "$ctx" secrets 1)"
    sa="$(kubectl_count_kind "$ctx" serviceaccounts 1)"
    role="$(kubectl_count_kind "$ctx" roles 1)"
    rb="$(kubectl_count_kind "$ctx" rolebindings 1)"
    cr="$(kubectl_count_kind "$ctx" clusterroles 0)"
    crb="$(kubectl_count_kind "$ctx" clusterrolebindings 0)"
    np="$(kubectl_count_kind "$ctx" networkpolicies 1)"
    pv="$(kubectl_count_kind "$ctx" persistentvolumes 0)"
    pvc="$(kubectl_count_kind "$ctx" persistentvolumeclaims 1)"
    sc="$(kubectl_count_kind "$ctx" storageclasses 0)"
    rq="$(kubectl_count_kind "$ctx" resourcequotas 1)"
    lim="$(kubectl_count_kind "$ctx" limitranges 1)"
    hpa="$(kubectl_count_kind "$ctx" horizontalpodautoscalers 1)"
    pdb="$(kubectl_count_kind "$ctx" poddisruptionbudgets 1)"
    rc="$(kubectl_count_kind "$ctx" runtimeclasses 0)"
    pc="$(kubectl_count_kind "$ctx" priorityclasses 0)"
    crd="$(kubectl_count_kind "$ctx" customresourcedefinitions 0)"
    mw="$(kubectl_count_kind "$ctx" mutatingwebhookconfigurations 0)"
    vc="$(kubectl_count_kind "$ctx" validatingwebhookconfigurations 0)"
    workload_count=$((deploy + sts + ds + job + cron + rs + svc + ing + es + cm + sec + sa + role + rb + cr + crb + np + pv + pvc + sc + rq + lim + hpa + pdb + rc + pc + crd + mw + vc))
    containers_ctx="$(kubectl --context "$ctx" get pods -A -o json 2>/dev/null | jq '[.items[].spec.containers[]?] | length')"
    running_ctx="$(kubectl --context "$ctx" get pods -A -o json 2>/dev/null | jq '[.items[] | select(.status.phase=="Running") | .spec.containers[]?] | length')"
    images_ctx="$(kubectl --context "$ctx" get pods -A -o json 2>/dev/null | jq -r '[.items[].spec.containers[]?.image] | unique | length')"
    ks_pod="$(kubectl --context "$ctx" -n kube-system get pods --no-headers 2>/dev/null | wc -l | tr -d ' ')"
    kubesystem_pods=$((kubesystem_pods + ks_pod))
    namespaces=$((namespaces + ns))
    nodes=$((nodes + node))
    pods=$((pods + pod))
    containers=$((containers + containers_ctx))
    running_containers=$((running_containers + running_ctx))
    images=$((images + images_ctx))
    deployments=$((deployments + deploy))
    statefulsets=$((statefulsets + sts))
    daemonsets=$((daemonsets + ds))
    jobs=$((jobs + job))
    cronjobs=$((cronjobs + cron))
    replicasets=$((replicasets + rs))
    services=$((services + svc))
    ingresses=$((ingresses + ing))
    endpointslices=$((endpointslices + es))
    configmaps=$((configmaps + cm))
    secrets=$((secrets + sec))
    serviceaccounts=$((serviceaccounts + sa))
    roles=$((roles + role))
    rolebindings=$((rolebindings + rb))
    clusterroles=$((clusterroles + cr))
    clusterrolebindings=$((clusterrolebindings + crb))
    networkpolicies=$((networkpolicies + np))
    pvs=$((pvs + pv))
    pvcs=$((pvcs + pvc))
    storageclasses=$((storageclasses + sc))
    resourcequotas=$((resourcequotas + rq))
    limitranges=$((limitranges + lim))
    hpas=$((hpas + hpa))
    pdbs=$((pdbs + pdb))
    runtimeclasses=$((runtimeclasses + rc))
    priorityclasses=$((priorityclasses + pc))
    crds=$((crds + crd))
    mutatingwebhooks=$((mutatingwebhooks + mw))
    validatingwebhooks=$((validatingwebhooks + vc))
    workload_total=$((workload_total + workload_count))
    add_detail "$product" kubernetes "$ctx/namespaces" namespace "$ns" "" ""
    add_detail "$product" kubernetes "$ctx/nodes" node "$node" "" ""
    add_detail "$product" kubernetes "$ctx/pods" pod "$pod" "" ""
    add_detail "$product" kubernetes "$ctx/kube-system/pods" pod "$ks_pod" "" ""
    add_detail "$product" kubernetes "$ctx/containers" container "$containers_ctx" "" ""
    add_detail "$product" kubernetes "$ctx/running-containers" running_container "$running_ctx" "" ""
    add_detail "$product" kubernetes "$ctx/images" image "$images_ctx" "" ""
    add_detail "$product" kubernetes "$ctx/deployments" deployment "$deploy" "" ""
    add_detail "$product" kubernetes "$ctx/statefulsets" statefulset "$sts" "" ""
    add_detail "$product" kubernetes "$ctx/daemonsets" daemonset "$ds" "" ""
    add_detail "$product" kubernetes "$ctx/jobs" job "$job" "" ""
    add_detail "$product" kubernetes "$ctx/cronjobs" cronjob "$cron" "" ""
    add_detail "$product" kubernetes "$ctx/replicasets" replicaset "$rs" "" ""
    add_detail "$product" kubernetes "$ctx/services" service "$svc" "" ""
    add_detail "$product" kubernetes "$ctx/ingresses" ingress "$ing" "" ""
    add_detail "$product" kubernetes "$ctx/endpointslices" endpointslice "$es" "" ""
    add_detail "$product" kubernetes "$ctx/configmaps" configmap "$cm" "" ""
    add_detail "$product" kubernetes "$ctx/secrets" secret "$sec" "" ""
    add_detail "$product" kubernetes "$ctx/serviceaccounts" serviceaccount "$sa" "" ""
    add_detail "$product" kubernetes "$ctx/roles" role "$role" "" ""
    add_detail "$product" kubernetes "$ctx/rolebindings" rolebinding "$rb" "" ""
    add_detail "$product" kubernetes "$ctx/clusterroles" clusterrole "$cr" "" ""
    add_detail "$product" kubernetes "$ctx/clusterrolebindings" clusterrolebinding "$crb" "" ""
    add_detail "$product" kubernetes "$ctx/networkpolicies" networkpolicy "$np" "" ""
    add_detail "$product" kubernetes "$ctx/persistentvolumes" persistentvolume "$pv" "" ""
    add_detail "$product" kubernetes "$ctx/persistentvolumeclaims" persistentvolumeclaim "$pvc" "" ""
    add_detail "$product" kubernetes "$ctx/storageclasses" storageclass "$sc" "" ""
    add_detail "$product" kubernetes "$ctx/resourcequotas" resourcequota "$rq" "" ""
    add_detail "$product" kubernetes "$ctx/limitranges" limitrange "$lim" "" ""
    add_detail "$product" kubernetes "$ctx/horizontalpodautoscalers" horizontalpodautoscaler "$hpa" "" ""
    add_detail "$product" kubernetes "$ctx/poddisruptionbudgets" poddisruptionbudget "$pdb" "" ""
    add_detail "$product" kubernetes "$ctx/runtimeclasses" runtimeclass "$rc" "" ""
    add_detail "$product" kubernetes "$ctx/priorityclasses" priorityclass "$pc" "" ""
    add_detail "$product" kubernetes "$ctx/customresourcedefinitions" customresourcedefinition "$crd" "" ""
    add_detail "$product" kubernetes "$ctx/mutatingwebhookconfigurations" mutatingwebhookconfiguration "$mw" "" ""
    add_detail "$product" kubernetes "$ctx/validatingwebhookconfigurations" validatingwebhookconfiguration "$vc" "" ""
    add_detail "$product" kubernetes "$ctx/workload_total" workload_total "$workload_count" "" ""
  done < <(selected_kube_contexts)
  if [ "$product" = "runtime" ]; then
      # Runtime licensing is based on protected compute.
      add_summary "$product" kubernetes clusters "$clusters"
      add_summary "$product" kubernetes nodes "$nodes"
  
      add_summary "$product" runtime protected_kubernetes_nodes "$nodes"
  
      add_detail "$product" runtime protected_kubernetes_nodes node "$nodes" "" ""
  
      add_summary "$product" total protected_compute "$nodes"
      return 0
  fi
  add_summary "$product" kubernetes clusters "$clusters"
  add_summary "$product" kubernetes namespaces "$namespaces"
  add_summary "$product" kubernetes nodes "$nodes"
  add_summary "$product" kubernetes pods "$pods"
  add_summary "$product" kubernetes kube_system_pods "$kubesystem_pods"
  add_summary "$product" kubernetes containers "$containers"
  add_summary "$product" kubernetes running_containers "$running_containers"
  add_summary "$product" kubernetes images "$images"
  add_summary "$product" kubernetes deployments "$deployments"
  add_summary "$product" kubernetes statefulsets "$statefulsets"
  add_summary "$product" kubernetes daemonsets "$daemonsets"
  add_summary "$product" kubernetes jobs "$jobs"
  add_summary "$product" kubernetes cronjobs "$cronjobs"
  add_summary "$product" kubernetes replicasets "$replicasets"
  add_summary "$product" kubernetes services "$services"
  add_summary "$product" kubernetes ingresses "$ingresses"
  add_summary "$product" kubernetes endpointslices "$endpointslices"
  add_summary "$product" kubernetes configmaps "$configmaps"
  add_summary "$product" kubernetes secrets "$secrets"
  add_summary "$product" kubernetes serviceaccounts "$serviceaccounts"
  add_summary "$product" kubernetes roles "$roles"
  add_summary "$product" kubernetes rolebindings "$rolebindings"
  add_summary "$product" kubernetes clusterroles "$clusterroles"
  add_summary "$product" kubernetes clusterrolebindings "$clusterrolebindings"
  add_summary "$product" kubernetes networkpolicies "$networkpolicies"
  add_summary "$product" kubernetes persistentvolumes "$pvs"
  add_summary "$product" kubernetes persistentvolumeclaims "$pvcs"
  add_summary "$product" kubernetes storageclasses "$storageclasses"
  add_summary "$product" kubernetes resourcequotas "$resourcequotas"
  add_summary "$product" kubernetes limitranges "$limitranges"
  add_summary "$product" kubernetes horizontalpodautoscalers "$hpas"
  add_summary "$product" kubernetes poddisruptionbudgets "$pdbs"
  add_summary "$product" kubernetes runtimeclasses "$runtimeclasses"
  add_summary "$product" kubernetes priorityclasses "$priorityclasses"
  add_summary "$product" kubernetes customresourcedefinitions "$crds"
  add_summary "$product" kubernetes mutatingwebhookconfigurations "$mutatingwebhooks"
  add_summary "$product" kubernetes validatingwebhookconfigurations "$validatingwebhooks"
  add_summary "$product" kubernetes workload_total "$workload_total"
  if [ "$product" = "runtime" ]; then
    :
  fi
  add_summary "$product" total workload_total "$workload_total"
}

run_code_provider() {
  local provider="$1" cutoff="$2"
  case "$provider" in
    github) count_code_github "$cutoff" ;;
    gitlab) count_code_gitlab "$cutoff" ;;
    azuredevops|azure-devops|ado) count_code_azuredevops "$cutoff" ;;
    bitbucket) count_code_bitbucket "$cutoff" ;;
    *) die "Unsupported code provider: $provider" ;;
  esac
}

run_container_provider() {
  case "$1" in
    dockerhub|docker-hub) count_container_dockerhub ;;
    ghcr) count_container_ghcr ;;
    ecr|aws-ecr) count_container_ecr ;;
    acr|azure-acr) count_container_acr ;;
    gcr) count_container_gcr ;;
    gar|artifact-registry|google-artifact-registry) count_container_gar ;;
    gitlab|gitlab-registry) count_container_gitlab ;;
    harbor) count_container_harbor ;;
    *) die "Unsupported container provider: $1" ;;
  esac
}

run_cloud_provider() {
  case "$1" in
    aws) count_cloud_aws ;;
    azure) count_cloud_azure ;;
    gcp) count_cloud_gcp ;;
    oci|oracle) count_cloud_oci ;;
    *) die "Unsupported cloud provider: $1" ;;
  esac
}

prompt_choice() {
  local prompt="$1"
  shift
  printf '\n%s\n' "$prompt" >&2
  local i=1
  for item in "$@"; do
    printf '  %s) %s\n' "$i" "$item" >&2
    i=$((i + 1))
  done
  printf 'Choose: ' >&2
  local choice
  read -r choice

  local IFS=','
  local parts
  read -ra parts <<< "$choice"
  local resolved=()
  local arr=("$@")
  for part in "${parts[@]}"; do
    local trimmed="$part"
    trimmed="${trimmed#${trimmed%%[![:space:]]*}}"
    trimmed="${trimmed%${trimmed##*[![:space:]]}}"
    if [[ "$trimmed" =~ ^[0-9]+$ ]] && [ "$trimmed" -ge 1 ] && [ "$trimmed" -le "$#" ]; then
      resolved+=("${arr[$((trimmed - 1))]}")
    elif [ -n "$trimmed" ]; then
      resolved+=("$trimmed")
    fi
  done

  if [ ${#resolved[@]} -gt 0 ]; then
    (
      local IFS=','
      printf '%s\n' "${resolved[*]}"
    )
  else
    printf '%s\n' "$choice"
  fi
}

PRODUCT=""
PROVIDER=""
DAYS="$DEFAULT_DAYS"
SHOW_DETAILS=1

while [ "$#" -gt 0 ]; do
  case "$1" in
    --product) PRODUCT="${2:-}"; shift 2 ;;
    --provider) PROVIDER="${2:-}"; shift 2 ;;
    --days) DAYS="${2:-}"; shift 2 ;;
    --out-dir) OUT_DIR="${2:-}"; shift 2 ;;
    --details) SHOW_DETAILS=1; shift ;;
    --no-details) SHOW_DETAILS=0; shift ;;
    --help|-h) usage; exit 0 ;;
    *) die "Unknown option: $1" ;;
  esac
done

[[ "$DAYS" =~ ^[0-9]+$ ]] || die "--days must be a number"

print_banner

if [ -z "$PRODUCT" ]; then
  PRODUCT="$(prompt_choice "Select product to count:" code container cloud kspm runtime all)"
fi

CODE_PROVIDER=""
CONTAINER_PROVIDER=""
CLOUD_PROVIDER=""

if [ -n "$PROVIDER" ]; then
  IFS=',' read -ra prov_list <<< "$PROVIDER"
  for prov in "${prov_list[@]}"; do
    prov="${prov#${prov%%[![:space:]]*}}"
    prov="${prov%${prov##*[![:space:]]}}"
    [ -n "$prov" ] || continue

    case "$prov" in
      github|azuredevops|azure-devops|ado|bitbucket)
        CODE_PROVIDER="${CODE_PROVIDER:+$CODE_PROVIDER,}$prov"
        ;;
      dockerhub|docker-hub|ghcr|ecr|aws-ecr|acr|azure-acr|gcr|gar|artifact-registry|google-artifact-registry|harbor|gitlab-registry)
        CONTAINER_PROVIDER="${CONTAINER_PROVIDER:+$CONTAINER_PROVIDER,}$prov"
        ;;
      gitlab)
        CODE_PROVIDER="${CODE_PROVIDER:+$CODE_PROVIDER,}$prov"
        CONTAINER_PROVIDER="${CONTAINER_PROVIDER:+$CONTAINER_PROVIDER,}$prov"
        ;;
      aws|azure|gcp|oci|oracle)
        CLOUD_PROVIDER="${CLOUD_PROVIDER:+$CLOUD_PROVIDER,}$prov"
        ;;
      all)
        CODE_PROVIDER="all"
        CONTAINER_PROVIDER="all"
        CLOUD_PROVIDER="all"
        ;;
      none)
        CODE_PROVIDER="none"
        CONTAINER_PROVIDER="none"
        CLOUD_PROVIDER="none"
        ;;
    esac
  done
fi

# Expand "all" in PRODUCT
ALL_PRODUCTS=("code" "container" "cloud" "kspm" "runtime")
IFS=',' read -ra prod_list <<< "$PRODUCT"
expanded_products=()
for p in "${prod_list[@]}"; do
  p="${p#${p%%[![:space:]]*}}"
  p="${p%${p##*[![:space:]]}}"
  if [ "$p" = "all" ]; then
    expanded_products+=("${ALL_PRODUCTS[@]}")
  elif [ -n "$p" ]; then
    expanded_products+=("$p")
  fi
done

# Remove duplicate products to be clean
unique_products=()
for p in "${expanded_products[@]}"; do
  dup=0
  for u in "${unique_products[@]}"; do
    if [ "$u" = "$p" ]; then
      dup=1
      break
    fi
  done
  if [ "$dup" -eq 0 ]; then
    unique_products+=("$p")
  fi
done

# Prompt for provider if missing for the active products
for prod in "${unique_products[@]}"; do
  case "$prod" in
    code)
      if [ -z "$CODE_PROVIDER" ]; then
        CODE_PROVIDER="$(prompt_choice "Select code provider:" github gitlab azuredevops bitbucket all none)"
      fi
      ;;
    container)
      if [ -z "$CONTAINER_PROVIDER" ]; then
        CONTAINER_PROVIDER="$(prompt_choice "Select container registry:" dockerhub ghcr ecr acr gcr gar gitlab harbor all none)"
      fi
      ;;
    cloud)
      if [ -z "$CLOUD_PROVIDER" ]; then
        CLOUD_PROVIDER="$(prompt_choice "Select cloud provider:" aws azure gcp oci all none)"
      fi
      ;;
  esac
done

init_output

cutoff="$(date -u -d "$DAYS days ago" '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u -v-"$DAYS"d '+%Y-%m-%dT%H:%M:%SZ')"

for prod in "${unique_products[@]}"; do
  (
    case "$prod" in
      code)
        if [ -z "$CODE_PROVIDER" ] || [ "$CODE_PROVIDER" = "none" ]; then
          log "Skipping code counting"
          exit 0
        fi
        if [ "$CODE_PROVIDER" = "all" ]; then
          for p in github gitlab azuredevops bitbucket; do
            ( run_code_provider "$p" "$cutoff" ) || log "Skipping code/$p after error"
          done
        else
          IFS=',' read -ra providers <<<"$CODE_PROVIDER"
          for p in "${providers[@]}"; do
            p="${p#${p%%[![:space:]]*}}"
            p="${p%${p##*[![:space:]]}}"
            [ -n "$p" ] && [ "$p" != "none" ] || continue
            ( run_code_provider "$p" "$cutoff" ) || log "Skipping code/$p after error"
          done
        fi
        ;;
      container)
        if [ -z "$CONTAINER_PROVIDER" ] || [ "$CONTAINER_PROVIDER" = "none" ]; then
          log "Skipping container counting"
          exit 0
        fi
        if [ "$CONTAINER_PROVIDER" = "all" ]; then
          for p in dockerhub ghcr ecr acr gcr gar gitlab harbor; do
            ( run_container_provider "$p" ) || log "Skipping container/$p after error"
          done
        else
          IFS=',' read -ra providers <<<"$CONTAINER_PROVIDER"
          for p in "${providers[@]}"; do
            p="${p#${p%%[![:space:]]*}}"
            p="${p%${p##*[![:space:]]}}"
            [ -n "$p" ] && [ "$p" != "none" ] || continue
            ( run_container_provider "$p" ) || log "Skipping container/$p after error"
          done
        fi
        ;;
      cloud)
        if [ -z "$CLOUD_PROVIDER" ] || [ "$CLOUD_PROVIDER" = "none" ]; then
          log "Skipping cloud counting"
          exit 0
        fi
        if [ "$CLOUD_PROVIDER" = "all" ]; then
          for p in aws azure gcp oci; do
            ( run_cloud_provider "$p" ) || log "Skipping cloud/$p after error"
          done
        else
          IFS=',' read -ra providers <<<"$CLOUD_PROVIDER"
          for p in "${providers[@]}"; do
            p="${p#${p%%[![:space:]]*}}"
            p="${p%${p##*[![:space:]]}}"
            [ -n "$p" ] && [ "$p" != "none" ] || continue
            ( run_cloud_provider "$p" ) || log "Skipping cloud/$p after error"
          done
        fi
        ;;
      kspm)
        ( count_k8s_inventory kspm ) || log "Skipping kspm after error"
        ;;
      runtime)
        ( count_k8s_inventory runtime ) || log "Skipping runtime after error"
        ;;
      *)
        die "Unsupported product: $prod"
        ;;
    esac
  ) || log "Skipping product $prod after error"
done

print_outputs
