// Signal Provider Registry v3.0.0
// Manages named signal providers that extract typed features from targets.
// Providers are registered by namespace (e.g. "url.*", "dom.*", "clipboard.*")
// and called on-demand by the enhanced rule engine or directly by detectors.
//
// A provider is a pure function: (target, featureStore, args) => { value, metadata? }
// Providers do NOT make decisions — they return typed data.

const PROVIDER_TIMEOUT_MS = 50;

export class SignalProviderRegistry {
  constructor() {
    this.providers = new Map(); // "namespace.name" -> { fn, config }
    this.stats = { registered: 0, evaluated: 0, errors: 0, timeouts: 0 };
  }

  /**
   * Register a signal provider.
   * @param {string} namespace - Provider namespace (e.g. "url", "dom", "clipboard")
   * @param {string} name - Provider name within namespace (e.g. "is_punycode")
   * @param {Function} fn - Provider function: (target, featureStore, args) => { value, metadata? }
   * @param {object} [config] - Optional config { description, returnType, scope, cacheable }
   */
  register(namespace, name, fn, config = {}) {
    if (typeof fn !== 'function') {
      throw new Error(`Provider ${namespace}.${name}: fn must be a function`);
    }

    const path = `${namespace}.${name}`;
    this.providers.set(path, {
      fn,
      namespace,
      name,
      path,
      description: config.description || '',
      returnType: config.returnType || 'any',
      scope: config.scope || 'frame', // "frame" or "tab"
      cacheable: config.cacheable !== false,
    });

    this.stats.registered++;
  }

  /**
   * Register multiple providers under a namespace at once.
   * @param {string} namespace - Provider namespace
   * @param {object} providers - { name: fn } or { name: { fn, config } }
   */
  registerAll(namespace, providers) {
    for (const [name, entry] of Object.entries(providers)) {
      if (typeof entry === 'function') {
        this.register(namespace, name, entry);
      } else if (entry && typeof entry.fn === 'function') {
        this.register(namespace, name, entry.fn, entry);
      } else {
        console.warn(`[SignalProviderRegistry] Skipping invalid provider: ${namespace}.${name}`);
      }
    }
  }

  /**
   * Evaluate a signal provider by its full path.
   * @param {string} signalPath - Full provider path (e.g. "url.is_punycode")
   * @param {object} target - Analysis target
   * @param {object} [featureStore] - FeatureStore instance (optional)
   * @param {object} [args] - Additional arguments for the provider
   * @returns {{ value: any, metadata?: object }} Provider result
   */
  evaluate(signalPath, target, featureStore = null, args = {}) {
    const provider = this.providers.get(signalPath);
    if (!provider) {
      console.warn(`[SignalProviderRegistry] Unknown provider: ${signalPath}`);
      return { value: undefined, metadata: { error: 'unknown_provider' } };
    }

    this.stats.evaluated++;

    try {
      const startTime = performance.now();
      const result = provider.fn(target, featureStore, args);
      const elapsed = performance.now() - startTime;

      if (elapsed > PROVIDER_TIMEOUT_MS) {
        console.warn(`[SignalProviderRegistry] Provider ${signalPath} slow: ${elapsed.toFixed(1)}ms`);
        this.stats.timeouts++;
      }

      // Normalize result
      if (result === null || result === undefined) {
        return { value: undefined };
      }
      if (typeof result !== 'object' || !('value' in result)) {
        return { value: result };
      }
      return result;
    } catch (error) {
      console.error(`[SignalProviderRegistry] Provider ${signalPath} error:`, error);
      this.stats.errors++;
      return { value: undefined, metadata: { error: error.message } };
    }
  }

  /**
   * Check if a provider is registered.
   * @param {string} signalPath - Full provider path
   * @returns {boolean}
   */
  has(signalPath) {
    return this.providers.has(signalPath);
  }

  /**
   * Get provider config/metadata.
   * @param {string} signalPath - Full provider path
   * @returns {object|null}
   */
  getProviderInfo(signalPath) {
    const provider = this.providers.get(signalPath);
    if (!provider) return null;
    return {
      path: provider.path,
      namespace: provider.namespace,
      name: provider.name,
      description: provider.description,
      returnType: provider.returnType,
      scope: provider.scope,
    };
  }

  /**
   * Get all registered provider paths.
   * @returns {string[]}
   */
  getAllPaths() {
    return Array.from(this.providers.keys());
  }

  /**
   * Get all providers in a namespace.
   * @param {string} namespace
   * @returns {string[]} Provider paths
   */
  getByNamespace(namespace) {
    return this.getAllPaths().filter(p => p.startsWith(namespace + '.'));
  }

  /**
   * Get registry statistics.
   * @returns {object}
   */
  getStats() {
    return { ...this.stats };
  }

  /**
   * Get a human-readable inventory of all providers.
   * @returns {object[]}
   */
  getInventory() {
    const inventory = [];
    for (const [path, provider] of this.providers) {
      inventory.push({
        path,
        namespace: provider.namespace,
        name: provider.name,
        description: provider.description,
        returnType: provider.returnType,
        scope: provider.scope,
      });
    }
    return inventory;
  }
}
