// engine/analytics-detector.ts — Product analytics detection facade (SPEC-032)
// Detects analytics providers, naming conventions, and feature classification.
// Event generation, taxonomy, and property validation are in analytics-detector/events.ts.
import { createRequire } from 'node:module';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
// Load config using require for JSON files (ESM NodeNext workaround)
const require = createRequire(import.meta.url);
const providersConfig = require('../config/analytics-providers.json');
// Re-export event generation and taxonomy helpers from sub-module
export { generateEvents, generateFunnelEvents, validateEventProperties, readTaxonomy, writeTaxonomy, bumpTaxonomyVersion, } from './analytics-detector/events.js';
// ---------------------------------------------------------------------------
// PII detection
// ---------------------------------------------------------------------------
const PII_FIELD_NAMES = new Set([
    'email',
    'mail',
    'phone',
    'telefono',
    'nombre',
    'name',
    'lastname',
    'apellido',
    'firstname',
    'fullname',
    'address',
    'direccion',
    'ip',
    'ip_address',
    'user_ip',
]);
/**
 * Returns true if any property of an event includes or derives from PII.
 */
export function hasPIIProperties(properties) {
    return properties.some((p) => p.pii === true || PII_FIELD_NAMES.has(p.name.toLowerCase()));
}
// ---------------------------------------------------------------------------
// Naming convention detection
// ---------------------------------------------------------------------------
/**
 * Analyses existing event names and infers the naming convention used.
 */
export function detectNamingConvention(existingEvents) {
    if (existingEvents.length === 0) {
        return 'object_action';
    }
    let snakeCount = 0;
    let camelCount = 0;
    let prefixedCount = 0;
    for (const name of existingEvents) {
        if (name.includes(':')) {
            prefixedCount++;
        }
        else if (/^[a-z][a-zA-Z0-9]*$/.test(name) && /[A-Z]/.test(name)) {
            camelCount++;
        }
        else if (/^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(name)) {
            snakeCount++;
        }
    }
    const total = existingEvents.length;
    if (prefixedCount / total > 0.5) {
        return 'module_prefixed';
    }
    if (camelCount / total > 0.5) {
        return 'camelCase';
    }
    if (snakeCount / total > 0.3) {
        return 'object_action';
    }
    return 'custom';
}
// ---------------------------------------------------------------------------
// Vocabulary-based feature classification
// ---------------------------------------------------------------------------
/**
 * Returns true if the feature involves direct user interaction.
 */
export function isUserInteractionFeature(specTitle, specDescription, criteria) {
    const text = `${specTitle} ${specDescription} ${criteria}`.toLowerCase();
    return providersConfig.userInteractionVocabulary.some((word) => text.includes(word));
}
/**
 * Returns true if the feature creates a user funnel.
 */
export function isFunnelFeature(specTitle, specDescription, criteria) {
    const text = `${specTitle} ${specDescription} ${criteria}`.toLowerCase();
    return providersConfig.funnelVocabulary.some((word) => text.includes(word));
}
/**
 * Returns true if the feature requires an A/B experiment.
 */
export function isABTestFeature(specTitle, specDescription, criteria) {
    const text = `${specTitle} ${specDescription} ${criteria}`.toLowerCase();
    return providersConfig.abTestVocabulary.some((word) => text.includes(word));
}
// ---------------------------------------------------------------------------
// Provider detection
// ---------------------------------------------------------------------------
/**
 * Detects if a given package name matches a provider's detection patterns.
 */
function matchesProvider(pkg, provider) {
    const lower = pkg.toLowerCase();
    return (provider.sdkPackages.some((sdkPkg) => lower === sdkPkg.toLowerCase()) ||
        provider.detectionPatterns.some((pat) => lower.includes(pat.toLowerCase())));
}
/**
 * Detects the integration method from package names.
 */
function detectIntegrationMethod(provider, deps) {
    for (const dep of deps) {
        if (provider.sdkPackages.some((pkg) => dep.toLowerCase() === pkg.toLowerCase())) {
            return 'sdk';
        }
    }
    const methods = provider.integrationMethods;
    // Config always guarantees non-empty integrationMethods array, so methods[0] is never undefined
    /* v8 ignore start */
    return methods[0] ?? 'sdk';
    /* v8 ignore stop */
}
/**
 * Reads package.json dependencies (prod + dev) from a project path.
 */
async function readPackageDeps(projectPath) {
    try {
        const content = await readFile(join(projectPath, 'package.json'), 'utf-8');
        const pkg = JSON.parse(content);
        const deps = Object.keys(pkg.dependencies ?? {});
        const devDeps = Object.keys(pkg.devDependencies ?? {});
        return [...deps, ...devDeps];
    }
    catch {
        return [];
    }
}
/**
 * Searches source files for tracking function call signatures.
 */
async function findCustomEventCalls(projectPath) {
    const TRACK_PATTERNS = [
        'track(',
        'logEvent(',
        'capture(',
        'analytics.track(',
        'mixpanel.track(',
        'amplitude.track(',
        'posthog.capture(',
    ];
    const candidates = ['src', 'app', 'lib', 'pages', 'components'];
    for (const dir of candidates) {
        try {
            const { glob } = await import('glob');
            const files = await glob('**/*.{ts,tsx,js,jsx}', {
                cwd: join(projectPath, dir),
                nodir: true,
                maxDepth: 4,
                ignore: ['node_modules/**', 'dist/**', 'build/**'],
            });
            for (const file of files) {
                const content = await readFile(join(projectPath, dir, file), 'utf-8');
                if (TRACK_PATTERNS.some((pat) => content.includes(pat))) {
                    return true;
                }
            }
        }
        catch {
            // Directory doesn't exist or glob failed — skip
        }
    }
    return false;
}
/**
 * Checks if a dashboard config file exists in the project.
 */
async function hasDashboardConfig(projectPath) {
    const DASHBOARD_FILES = [
        'analytics.config.js',
        'analytics.config.ts',
        'analytics.config.json',
        '.analytics.json',
        'tracking-plan.json',
    ];
    for (const file of DASHBOARD_FILES) {
        try {
            await readFile(join(projectPath, file), 'utf-8');
            return true;
        }
        catch {
            // File not found
        }
    }
    return false;
}
/**
 * Detects the A/B testing provider from dependencies.
 */
function detectABTestingProvider(deps) {
    for (const abProvider of providersConfig.abTestingProviders) {
        for (const dep of deps) {
            if (abProvider.detectionPatterns.some((pat) => dep.toLowerCase().includes(pat.toLowerCase()))) {
                return abProvider.id;
            }
        }
    }
    return 'none';
}
/**
 * Detects the analytics stack in a project.
 * Analyses package.json and codebase to return AnalyticsStack.
 */
export async function detectStack(projectPath) {
    const deps = await readPackageDeps(projectPath);
    const hasCustomEvents = await findCustomEventCalls(projectPath);
    const hasDashboards = await hasDashboardConfig(projectPath);
    const abTestingProvider = detectABTestingProvider(deps);
    for (const provider of providersConfig.providers) {
        for (const dep of deps) {
            if (matchesProvider(dep, provider)) {
                const integrationMethod = detectIntegrationMethod(provider, deps);
                return {
                    provider: provider.id,
                    integrationMethod,
                    hasCustomEvents,
                    hasDashboards,
                    abTestingProvider,
                    namingConvention: 'object_action',
                };
            }
        }
    }
    return {
        provider: 'unknown',
        integrationMethod: 'sdk',
        hasCustomEvents,
        hasDashboards,
        abTestingProvider,
        namingConvention: 'object_action',
    };
}
//# sourceMappingURL=analytics-detector.js.map