#!/usr/bin/env bash

set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PROVIDERS_FILE="${ROOT_DIR}/Sources/CodexBarCore/Providers/Providers.swift"
DESCRIPTOR_DIR="${ROOT_DIR}/Sources/CodexBarCore/Providers"
IMPLEMENTATION_DIR="${ROOT_DIR}/Sources/CodexBar/Providers"
DESCRIPTOR_OUTPUT="${DESCRIPTOR_DIR}/ProviderManifest.swift"
IMPLEMENTATION_OUTPUT="${IMPLEMENTATION_DIR}/Shared/ProviderImplementationManifest.swift"
INSTANCE_ID_OUTPUT="${DESCRIPTOR_DIR}/ProviderInstanceIDAliases.generated.swift"
PROVIDER_IDS_OUTPUT="${ROOT_DIR}/docs/provider-ids.md"
MODE="${1:-write}"

case "$MODE" in
  write | --write)
    CHECK_ONLY=0
    ;;
  check | --check)
    CHECK_ONLY=1
    ;;
  *)
    echo "Usage: $0 [write|--write|check|--check]" >&2
    exit 2
    ;;
esac

# UsageProvider is the canonical first-party bootstrap order. Provider types are discovered from
# their provider-owned declarations; this parsed order generates both manifests and the aliases.
PROVIDERS=()
while IFS= read -r provider; do
  PROVIDERS+=("$provider")
done < <(sed -nE '/public enum UsageProvider:/,/^}/s/^[[:space:]]*case ([a-z0-9]+)$/\1/p' "$PROVIDERS_FILE")

if [[ "${#PROVIDERS[@]}" -eq 0 ]]; then
  echo "error: no UsageProvider cases found in ${PROVIDERS_FILE#${ROOT_DIR}/}" >&2
  exit 1
fi

DESCRIPTOR_TYPES=()
IMPLEMENTATION_TYPES=()
for provider in "${PROVIDERS[@]}"; do
  descriptor_matches=()
  while IFS= read -r match; do
    [[ -n "$match" ]] && descriptor_matches+=("$match")
  done < <(grep -rlE "id: \\.${provider}," --include='*ProviderDescriptor.swift' "$DESCRIPTOR_DIR" || true)
  if [[ "${#descriptor_matches[@]}" -ne 1 ]]; then
    echo "error: provider '${provider}' must have exactly one descriptor file declaring 'id: .${provider},'; found ${#descriptor_matches[@]}" >&2
    exit 1
  fi
  descriptor_type="$(sed -nE 's/^public enum ([A-Za-z0-9]+ProviderDescriptor).*/\1/p' "${descriptor_matches[0]}" | head -n 1)"
  if [[ -z "$descriptor_type" ]]; then
    echo "error: could not find the public ProviderDescriptor type for provider '${provider}' in ${descriptor_matches[0]#${ROOT_DIR}/}" >&2
    exit 1
  fi
  DESCRIPTOR_TYPES+=("$descriptor_type")

  implementation_matches=()
  while IFS= read -r match; do
    [[ -n "$match" ]] && implementation_matches+=("$match")
  done < <(grep -rlE "let id: UsageProvider = \\.${provider}\$" --include='*ProviderImplementation.swift' "$IMPLEMENTATION_DIR" || true)
  if [[ "${#implementation_matches[@]}" -ne 1 ]]; then
    echo "error: provider '${provider}' must have exactly one implementation file declaring 'let id: UsageProvider = .${provider}'; found ${#implementation_matches[@]}" >&2
    exit 1
  fi
  implementation_type="$(sed -nE 's/^(struct|final class) ([A-Za-z0-9]+ProviderImplementation).*/\2/p' "${implementation_matches[0]}" | head -n 1)"
  if [[ -z "$implementation_type" ]]; then
    echo "error: could not find the ProviderImplementation type for provider '${provider}' in ${implementation_matches[0]#${ROOT_DIR}/}" >&2
    exit 1
  fi
  IMPLEMENTATION_TYPES+=("$implementation_type")
done

render_descriptors() {
  cat <<'SWIFT'
// Generated by Scripts/regenerate-provider-manifests.sh. Do not edit by hand.
import Foundation

/// Closed first-party bootstrap list. Runtime/plugin registrations remain owned by
/// `ProviderDescriptorRegistry.register(_:)`.
public enum ProviderManifest {
    public static let allDescriptors: [ProviderDescriptor] = [
SWIFT
  for descriptor_type in "${DESCRIPTOR_TYPES[@]}"; do
    printf '        %s.descriptor,\n' "$descriptor_type"
  done
  cat <<'SWIFT'
    ]
}
SWIFT
}

render_implementations() {
  cat <<'SWIFT'
// Generated by Scripts/regenerate-provider-manifests.sh. Do not edit by hand.
import CodexBarCore
import Foundation

/// Closed first-party bootstrap list. Runtime/plugin registrations remain owned by
/// `ProviderImplementationRegistry.register(_:)`.
enum ProviderImplementationManifest {
    static let makeImplementations: [@Sendable () -> any ProviderImplementation] = [
SWIFT
  for implementation_type in "${IMPLEMENTATION_TYPES[@]}"; do
    printf '        { %s() },\n' "$implementation_type"
  done
  cat <<'SWIFT'
    ]
}
SWIFT
}

render_instance_ids() {
  cat <<'SWIFT'
// Generated by Scripts/regenerate-provider-manifests.sh. Do not edit by hand.

// First-party spellings keep call sites concise while their values remain validated instance IDs.
// swiftformat:disable sortDeclarations
extension ProviderInstanceID {
SWIFT
  for provider in "${PROVIDERS[@]}"; do
    printf '    public static let %s = UsageProvider.%s.instanceID\n' "$provider" "$provider"
  done
  cat <<'SWIFT'
}

// swiftformat:enable sortDeclarations
SWIFT
}

render_provider_ids() {
  cat <<'MARKDOWN'
<!-- Generated by Scripts/regenerate-provider-manifests.sh. Do not edit by hand. -->

# Provider IDs

MARKDOWN
  local separator=""
  local provider
  for provider in "${PROVIDERS[@]}"; do
    printf '%s`%s`' "$separator" "$provider"
    separator=", "
  done
  printf '.\n'
}

check_or_write() {
  local output="$1"
  local renderer="$2"
  local expected
  expected="$(mktemp)"
  "$renderer" >"$expected"
  if [[ "$CHECK_ONLY" -eq 1 ]]; then
    if ! cmp -s "$expected" "$output"; then
      echo "error: ${output#${ROOT_DIR}/} is stale. Run Scripts/regenerate-provider-manifests.sh and commit the result." >&2
      if [[ -f "$output" ]]; then
        diff -u "$output" "$expected" >&2 || true
      else
        diff -u /dev/null "$expected" >&2 || true
      fi
      rm -f "$expected"
      return 1
    fi
    rm -f "$expected"
    return 0
  fi
  mv "$expected" "$output"
  echo "Updated ${output#${ROOT_DIR}/}"
}

check_or_write "$DESCRIPTOR_OUTPUT" render_descriptors
check_or_write "$IMPLEMENTATION_OUTPUT" render_implementations
check_or_write "$INSTANCE_ID_OUTPUT" render_instance_ids
check_or_write "$PROVIDER_IDS_OUTPUT" render_provider_ids
if [[ "$CHECK_ONLY" -eq 1 ]]; then
  echo "Provider manifests are current (${#PROVIDERS[@]} providers)"
fi
