import { ApplicationRef } from '@angular/core';
import { aggregateComponentCountsByDomain, detectSuspectedComponentLeaks } from './component-tree.rules';

const DEFAULT_DOM_SCAN_BUDGET = 400;

interface NgDebugApi {
  getComponent?: (element: Element) => { constructor: { name: string } } | null;
}

export interface ComponentTreeScanResult {
  components: Record<string, number>;
  domains: Record<string, number>;
  suspectedLeaks: ReturnType<typeof detectSuspectedComponentLeaks>;
  scannedNodes: number;
  scanMode: 'application-ref' | 'ng-global';
}

export function scanComponentTree(
  appRef: ApplicationRef,
  expectedCounts: Record<string, number>,
  options: { domScanBudget?: number } = {}
): ComponentTreeScanResult {
  const domScanBudget = options.domScanBudget ?? DEFAULT_DOM_SCAN_BUDGET;
  const ngApi = (globalThis as { ng?: NgDebugApi }).ng;
  const components: Record<string, number> = {};

  let scannedNodes = 0;
  let scanMode: ComponentTreeScanResult['scanMode'] = 'application-ref';

  if (ngApi?.getComponent && typeof document !== 'undefined') {
    scanMode = 'ng-global';
    const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);

    while (walker.nextNode() && scannedNodes < domScanBudget) {
      scannedNodes += 1;

      try {
        const component = ngApi.getComponent?.(walker.currentNode as Element) as {
          constructor: { name: string };
        } | null;

        if (!component) {
          continue;
        }

        const name = component.constructor.name;

        components[name] = (components[name] ?? 0) + 1;
      } catch {
        // Ignore elements that are not component hosts.
      }
    }
  } else {
    for (const componentRef of appRef.components) {
      const name = componentRef.componentType.name;

      components[name] = (components[name] ?? 0) + 1;
      scannedNodes += 1;
    }
  }

  return {
    components,
    domains: aggregateComponentCountsByDomain(components),
    suspectedLeaks: detectSuspectedComponentLeaks(components, expectedCounts),
    scannedNodes,
    scanMode
  };
}
