// engine/analytics-detector/events.ts — Event generation, property validation, taxonomy (SPEC-032)
import { readFile } from 'node:fs/promises';
import { writeJsonSafe } from '../safety/atomic-writer.js';
// ---------------------------------------------------------------------------
// Event generation helpers
// ---------------------------------------------------------------------------
/**
 * Converts a title to snake_case for use as an event segment.
 */
function toSnakeSegment(text) {
    return text
        .toLowerCase()
        .normalize('NFD')
        .replace(/[\u0300-\u036f]/g, '')
        .replace(/[^a-z0-9]+/g, '_')
        .replace(/^_+|_+$/g, '')
        .slice(0, 30);
}
/**
 * Infers an object_action event name from a feature description.
 */
function inferEventName(description, convention) {
    const lower = description.toLowerCase();
    const actionMap = [
        ['sign up', 'user_signed_up'],
        ['signup', 'user_signed_up'],
        ['register', 'user_registered'],
        ['login', 'user_logged_in'],
        ['logout', 'user_logged_out'],
        ['purchase', 'purchase_completed'],
        ['checkout', 'checkout_started'],
        ['payment', 'payment_completed'],
        ['click', 'button_clicked'],
        ['view', 'page_viewed'],
        ['search', 'search_performed'],
        ['upload', 'file_uploaded'],
        ['download', 'file_downloaded'],
        ['submit', 'form_submitted'],
        ['delete', 'item_deleted'],
        ['create', 'item_created'],
        ['update', 'item_updated'],
        ['share', 'content_shared'],
        ['invite', 'user_invited'],
        ['subscribe', 'user_subscribed'],
    ];
    for (const [keyword, eventName] of actionMap) {
        if (lower.includes(keyword)) {
            if (convention === 'camelCase') {
                return eventName.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
            }
            return eventName;
        }
    }
    // Fallback: derive from description
    const segment = toSnakeSegment(description.split(' ').slice(0, 3).join(' '));
    const name = `feature_${segment}`;
    if (convention === 'camelCase') {
        return name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
    }
    return name;
}
// ---------------------------------------------------------------------------
// Public event generators
// ---------------------------------------------------------------------------
/**
 * Generates a minimal set of analytics events for a feature.
 */
export function generateEvents(featureDescription, stack) {
    const eventName = inferEventName(featureDescription, stack.namingConvention);
    const baseEvent = {
        name: eventName,
        trigger: `Fired when the user completes: ${featureDescription}`,
        properties: [
            {
                name: 'user_id',
                type: 'string',
                description: 'Hashed user identifier — never raw email or PII',
                required: true,
            },
            {
                name: 'session_id',
                type: 'string',
                description: 'Current session identifier for funnel analysis',
                required: true,
            },
            {
                name: 'timestamp_ms',
                type: 'number',
                description: 'Unix timestamp in milliseconds when the event was fired',
                required: true,
            },
        ],
        businessGoal: `Measure usage and conversion of: ${featureDescription}`,
        includesPII: false,
        consentRequired: false,
    };
    return [baseEvent];
}
/**
 * Generates funnel step events with standard conversion properties.
 */
export function generateFunnelEvents(funnelDescription, stack) {
    const segment = toSnakeSegment(funnelDescription);
    const steps = [];
    const stepNames = ['started', 'step_completed', 'completed'];
    for (let i = 0; i < stepNames.length; i++) {
        /* v8 ignore next 1 */
        const stepSuffix = stepNames[i] ?? `step_${i + 1}`;
        const conv = stack.namingConvention;
        let name = `${segment}_${stepSuffix}`;
        if (conv === 'camelCase') {
            name = name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
        }
        steps.push({
            name,
            trigger: `Fired when the user reaches step ${i + 1} of ${funnelDescription}`,
            properties: [
                {
                    name: 'step_number',
                    type: 'number',
                    description: 'Ordinal position of this step in the funnel (1-based)',
                    required: true,
                },
                {
                    name: 'step_name',
                    type: 'string',
                    description: 'Descriptive name of the current funnel step',
                    required: true,
                },
                {
                    name: 'completed',
                    type: 'boolean',
                    description: 'Whether the user successfully completed this step',
                    required: true,
                },
                {
                    name: 'time_spent_ms',
                    type: 'number',
                    description: 'Milliseconds spent on this step before proceeding',
                    required: false,
                },
            ],
            businessGoal: `Track completion rate of step ${i + 1} in ${funnelDescription}`,
            includesPII: false,
            consentRequired: false,
        });
    }
    return {
        name: funnelDescription,
        conversionGoal: `User completes all steps of: ${funnelDescription}`,
        steps,
    };
}
// ---------------------------------------------------------------------------
// Property gap validation
// ---------------------------------------------------------------------------
// Keywords that imply certain properties must be present in related events
const GOAL_PROPERTY_MAP = [
    ['checkout', ['cart_value', 'item_count', 'payment_method']],
    ['conversion', ['conversion_value', 'funnel_step']],
    ['revenue', ['revenue', 'currency', 'amount']],
    ['search', ['query', 'results_count']],
    ['error', ['error_code', 'error_message']],
    ['registration', ['registration_method', 'referral_source']],
    ['payment', ['payment_method', 'amount', 'currency']],
    ['onboarding', ['step_number', 'step_name', 'completed']],
];
/**
 * Identifies event properties required to answer each business goal
 * that are currently missing from the event definitions.
 */
export function validateEventProperties(events, businessGoals) {
    const gaps = [];
    for (const goal of businessGoals) {
        const goalLower = goal.toLowerCase();
        for (const [keyword, requiredProps] of GOAL_PROPERTY_MAP) {
            if (!goalLower.includes(keyword)) {
                continue;
            }
            // Find events related to this goal
            const relatedEvents = events.filter((e) => e.businessGoal.toLowerCase().includes(keyword) || e.name.toLowerCase().includes(keyword));
            for (const event of relatedEvents) {
                const existingPropNames = new Set(event.properties.map((p) => p.name.toLowerCase()));
                const missing = requiredProps.filter((p) => !existingPropNames.has(p));
                if (missing.length > 0) {
                    gaps.push({
                        eventName: event.name,
                        businessQuestion: goal,
                        missingProperties: missing,
                    });
                }
            }
        }
    }
    return gaps;
}
// ---------------------------------------------------------------------------
// Taxonomy persistence
// ---------------------------------------------------------------------------
const EMPTY_TAXONOMY = {
    version: '0.0.0',
    updatedAt: '',
    events: {},
    namingConvention: 'object_action',
};
/**
 * Reads the event taxonomy from the given path.
 * Returns an empty taxonomy if the file does not exist.
 */
export async function readTaxonomy(taxonomyPath) {
    try {
        const content = await readFile(taxonomyPath, 'utf-8');
        return JSON.parse(content);
    }
    catch {
        return { ...EMPTY_TAXONOMY, updatedAt: new Date().toISOString() };
    }
}
/**
 * Writes the updated taxonomy to the given path, creating parent directories
 * if they do not exist.
 */
export async function writeTaxonomy(taxonomyPath, taxonomy) {
    await writeJsonSafe(taxonomyPath, taxonomy);
}
/**
 * Bumps the patch version of a semver string.
 */
export function bumpTaxonomyVersion(current) {
    const parts = current.split('.').map(Number);
    const patch = (parts[2] ?? 0) + 1;
    /* v8 ignore next 1 */
    const version = `${parts[0] ?? 0}.${parts[1] ?? 0}.${patch}`;
    return version;
}
//# sourceMappingURL=events.js.map