#!/usr/bin/env bash
# Validation, audit, quality, automation, config, dedupe, and UI-analysis modules.

security() {
  local root project dir file
  for root in "${NODE_ROOTS[@]}"; do run 'pnpm audit' "$root" pnpm audit --audit-level moderate || true; done
  if has dotnet; then
    for project in "${DOTNET_PROJECTS[@]}"; do
      dir="$(dirname "$project")"
      if dotnet package list --help >/dev/null 2>&1; then
        run 'NuGet vulnerable packages' "$dir" dotnet package list --project "$project" --vulnerable --include-transitive --format json || true
      else
        run 'NuGet vulnerable packages' "$dir" dotnet list "$project" package --vulnerable --include-transitive --format json || true
      fi
    done
    run 'NuGet sources' "$ROOT" dotnet nuget list source || true
  fi
  while IFS= read -r file; do
    grep -En 'value="http://|Version="(\*|[^"]*-)' "$file" >>"$LOG_DIR/security-static.txt" 2>/dev/null || true
  done < <(find "$ROOT" -type f \( -iname NuGet.Config -o -name '*.csproj' -o -name Directory.Packages.props \) -not -path '*/bin/*' -not -path '*/obj/*')
}

node_script() {
  local manifest="$1" script="$2" dir
  dir="$(dirname "$manifest")"
  node -e 'const p=require(process.argv[1]);process.exit(p.scripts&&p.scripts[process.argv[2]]?0:1)' "$manifest" "$script" 2>/dev/null || return 0
  run "$script: ${manifest#$ROOT/}" "$dir" pnpm run "$script" || true
}

validate() {
  local manifest project dir action index project_type
  if [[ "$ECOSYSTEMS" != dotnet ]]; then
    for manifest in "${NODE_PROJECTS[@]}"; do
      case "$VALIDATE_PROFILE" in
        quick) for action in lint typecheck; do node_script "$manifest" "$action"; done ;;
        build|publish) node_script "$manifest" build ;;
        format) node_script "$manifest" format:check; node_script "$manifest" format ;;
        test) node_script "$manifest" test ;;
        run)
          if node -e 'const p=require(process.argv[1]);process.exit(p.scripts&&p.scripts.dev?0:1)' "$manifest" 2>/dev/null; then node_script "$manifest" dev
          else node_script "$manifest" start
          fi
          ;;
        *) for action in lint format:check typecheck test build; do node_script "$manifest" "$action"; done ;;
      esac
    done
  fi
  if [[ "$ECOSYSTEMS" != node ]] && has dotnet; then
    for index in "${!DOTNET_PROJECTS[@]}"; do
      project="${DOTNET_PROJECTS[$index]}"; project_type="${DOTNET_TYPES[$index]}"; dir="$(dirname "$project")"
      run 'dotnet restore' "$dir" dotnet restore "$project" || true
      case "$VALIDATE_PROFILE" in quick|full|build) run 'dotnet build' "$dir" dotnet build "$project" --no-restore || true ;; esac
      case "$VALIDATE_PROFILE" in
        format|full) if dotnet format --help >/dev/null 2>&1; then run 'dotnet format' "$dir" dotnet format "$project" --verify-no-changes || true; fi ;;
        run)
          if [[ "$project_type" == dotnet-app || "$project_type" == avalonia-app ]]; then run 'dotnet run' "$dir" dotnet run --project "$project" --no-restore || true; fi
          ;;
        publish)
          if [[ "$project_type" == dotnet-app || "$project_type" == avalonia-app ]]; then
            local publish_args=(publish "$project" -c Release --no-restore)
            if [[ -n "$RID" ]]; then publish_args+=(-r "$RID" --self-contained true); fi
            run 'dotnet publish' "$dir" dotnet "${publish_args[@]}" || true
          fi
          ;;
      esac
      if grep -Eqi 'Microsoft.NET.Test.Sdk|<IsTestProject>true' "$project"; then
        case "$VALIDATE_PROFILE" in test|full) run 'dotnet test' "$dir" dotnet test "$project" --no-restore || true ;; esac
      fi
    done
  fi
}

quality() {
  local root package project report node_tools=1
  if ((${#NODE_ROOTS[@]})) && ! ensure_pnpm; then node_tools=0; fi
  if ((node_tools)); then
    for root in "${NODE_ROOTS[@]}"; do
      run 'Syncpack' "$root" pnpm dlx syncpack@latest "$([[ "$FIX" == 1 ]] && printf fix || printf lint)" || true
      run 'Knip' "$root" pnpm dlx knip@latest --reporter json || true
      if [[ "$QUALITY_PROFILE" != quick ]]; then
        run 'Dependency Cruiser' "$root" pnpm dlx dependency-cruiser@latest --no-config --output-type json --exclude '(^|/)node_modules(/|$)' . || true
      fi
    done
    for package in "${SHARED_PACKAGES[@]}"; do
      run 'Publint' "$package" pnpm dlx publint@latest "$package" || true
      [[ "$QUALITY_PROFILE" == quick ]] || run 'Are The Types Wrong' "$package" pnpm dlx @arethetypeswrong/cli@latest --pack "$package" || true
    done
    [[ "$QUALITY_PROFILE" == quick ]] || run 'JSCPD' "$ROOT" pnpm dlx jscpd@latest --min-lines 10 --min-tokens 70 --reporters console,json --output "$LOG_DIR/jscpd" "$ROOT" || true
  fi
  if has osv-scanner; then
    report="$LOG_DIR/osv-scanner.json"; run 'OSV-Scanner' "$ROOT" osv-scanner scan source --recursive --format=json --output-file "$report" "$ROOT" || true
  else
    warn 'OSV-Scanner is unavailable; scan skipped.'
  fi
  if [[ "$QUALITY_PROFILE" == deep ]]; then
    if has trivy; then run 'Trivy deep scan' "$ROOT" trivy fs --scanners vuln,secret,misconfig,license --format json --output "$LOG_DIR/trivy.json" "$ROOT" || true; else warn 'Trivy is unavailable; deep scan skipped.'; fi
    if has dotnet && ((${#DOTNET_PROJECTS[@]})); then
      local stryker="$ROOT/.maintenance/tools/stryker/dotnet-stryker"
      if [[ ! -x "$stryker" && "$INSTALL_TOOLS" == 1 ]]; then run 'Install Stryker.NET locally' "$ROOT" dotnet tool install dotnet-stryker --tool-path "$(dirname "$stryker")" || true; fi
      if [[ -x "$stryker" ]]; then
        for project in "${DOTNET_PROJECTS[@]}"; do grep -Eqi 'Microsoft.NET.Test.Sdk|<IsTestProject>true' "$project" && run 'Stryker.NET' "$(dirname "$project")" "$stryker" || true; done
      else
        warn 'Stryker.NET is unavailable; use --install-tools to install it locally.'
      fi
    fi
  fi
}

automation() {
  local file="$ROOT/renovate.json"
  if ((GENERATE_RENOVATE)); then
    if [[ -e "$file" ]]; then warn 'renovate.json already exists and was not overwritten.'
    elif ((DRY_RUN)); then info "[Dry run] Create $file"
    else cat >"$file" <<'JSON'
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"],
  "minimumReleaseAge": "7 days",
  "dependencyDashboard": true,
  "rangeStrategy": "replace",
  "packageRules": [
    {"matchManagers": ["npm"], "groupName": "Node dependencies", "minimumReleaseAge": "7 days"},
    {"matchManagers": ["nuget"], "groupName": "NuGet dependencies", "minimumReleaseAge": "7 days"},
    {"matchDepTypes": ["devDependencies"], "groupName": "Development dependencies"}
  ]
}
JSON
      ok "Created: $file"; CHANGED=1
    fi
  fi
  if ((GENERATE_CHANGESETS)) && ((${#SHARED_PACKAGES[@]})); then
    file="$ROOT/.changeset/config.json"
    if [[ -e "$file" ]]; then warn '.changeset/config.json already exists and was not overwritten.'
    elif ((DRY_RUN)); then info "[Dry run] Create $file"
    else
      mkdir -p "$(dirname "$file")"
      cat >"$file" <<'JSON'
{
  "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "restricted",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}
JSON
      printf '# Changesets\n\nRun `pnpm changeset` for user-facing package changes.\n' >"$ROOT/.changeset/README.md"
      ok 'Changesets configured.'; CHANGED=1
    fi
  fi
}

config_analysis() {
  local env_report="$LOG_DIR/env-keys.txt" dotnet_report="$LOG_DIR/dotnet-config-keys.txt"
  { grep -RhoE 'process\.env\.[A-Z0-9_]+' "$ROOT" --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=bin --exclude-dir=obj --exclude-dir=.maintenance 2>/dev/null || true; } \
    | sed 's/^process\.env\.//' | sort -u >"$env_report"
  { grep -RhoE 'Environment\.GetEnvironmentVariable\("[^"]+"\)|Configuration\["[^"]+"\]' "$ROOT" --exclude-dir=.git --exclude-dir=bin --exclude-dir=obj --exclude-dir=.maintenance 2>/dev/null || true; } \
    | sed -E 's/.*\("([^"]+)"\).*/\1/;s/.*\["([^"]+)"\].*/\1/' | sort -u >"$dotnet_report"
  cat "$env_report" "$dotnet_report" | sort -u >"$LOG_DIR/config-keys.txt"
  ok "Configuration report: $LOG_DIR/config-keys.txt"

  ((ASSUME_YES || FIX)) || return 0
  if [[ -s "$env_report" && ! -e "$ROOT/.env.example" ]]; then
    if ((DRY_RUN)); then info "[Dry run] Create $ROOT/.env.example"
    else while IFS= read -r key; do [[ -n "$key" ]] && printf '%s=\n' "$key"; done <"$env_report" >"$ROOT/.env.example"; ok 'Created: .env.example'; CHANGED=1
    fi
  fi
  if [[ -s "$dotnet_report" && ! -e "$ROOT/appsettings.Example.json" ]]; then
    if ((DRY_RUN)); then info "[Dry run] Create $ROOT/appsettings.Example.json"
    else node - "$dotnet_report" "$ROOT/appsettings.Example.json" <<'NODE'
const fs=require('fs');const [input,output]=process.argv.slice(2);const result={};
for(const key of fs.readFileSync(input,'utf8').split(/\r?\n/).filter(Boolean)) result[key]='';
fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n');
NODE
      ok 'Created: appsettings.Example.json'; CHANGED=1
    fi
  fi
}

dedupe() {
  local root
  for root in "${NODE_ROOTS[@]}"; do run 'pnpm dedupe' "$root" pnpm dedupe || true; run 'Syncpack lint' "$root" pnpm dlx syncpack@latest lint || true; done
  grep -RhoE '<PackageReference[^>]+Include="[^"]+"[^>]+Version="[^"]+"' "$ROOT" --include='*.csproj' --exclude-dir=bin --exclude-dir=obj 2>/dev/null | sort >"$LOG_DIR/nuget-version-consolidation.txt" || true
  ok "NuGet consolidation report: $LOG_DIR/nuget-version-consolidation.txt"
}

ui_analysis() {
  find "$ROOT" -type d \( -name .git -o -name node_modules -o -name .next -o -name bin -o -name obj -o -name .maintenance \) -prune -o -type f \( \
    -path '*/components/*' -o -path '*/Styles/*' -o -path '*/Themes/*' -o -path '*/Controls/*' -o -path '*/Views/*' -o -path '*/ViewModels/*' \
    -o -name '*.axaml' -o -name '*.css' -o -name '*.scss' \) -print | sed "s,^$ROOT/,," | sort >"$LOG_DIR/ui-candidates.txt"
  ok "UI candidates: $LOG_DIR/ui-candidates.txt"
  warn 'Analysis only: no files were moved and no imports were rewritten.'
}

interactive_menu() {
  print_inventory
  printf '\n%sChoose a module%s\n' "$C_BOLD" "$C_RESET"
  PS3='Selection: '
  select choice in 'Complete pipeline' 'Dependencies' 'Validation' 'Security' 'Quality' 'Automation' 'Configuration analysis' 'Dedupe / consolidation' 'UI analysis' 'Exit'; do
    case "$REPLY" in 1) MODULE=all;; 2) MODULE=deps;; 3) MODULE=validate;; 4) MODULE=security;; 5) MODULE=quality;; 6) MODULE=automation;; 7) MODULE=config;; 8) MODULE=dedupe;; 9) MODULE=ui-analysis;; 10) exit 0;; *) continue;; esac
    break
  done
  if [[ "$MODULE" == deps || "$MODULE" == all ]]; then
    printf 'Update mode [latest/minor/patch/semver-safe/interactive] (%s): ' "$MODE"; read -r input; [[ -n "$input" ]] && MODE="$input"
    warn 'Node projects: .next and node_modules will be deleted, and npm will be migrated to pnpm.'
    confirm 'Continue?' || exit 0
  fi
}
