function normalizePath(path) {
    let p = path.startsWith('./') ? path.slice(2) : path;
    p = p.replace(/\.ejs$/, '').replace(/_dot_/g, '.');
    const match = p.match(/^(.+\/)?__([^_]+)__(.+)$/);
    return match ? (match[1] || '') + match[3] : p;
}
async function getFileProvenance(filePath, framework, addOns, starter) {
    const target = filePath.startsWith('./') ? filePath.slice(2) : filePath;
    if (starter) {
        const files = await starter.getFiles();
        if (files.some((f) => normalizePath(f) === target)) {
            return {
                source: 'starter',
                sourceId: starter.id,
                sourceName: starter.name,
            };
        }
    }
    // Order add-ons by type then phase (matches writeFiles order), check in reverse
    const typeOrder = ['add-on', 'example', 'toolchain', 'deployment'];
    const phaseOrder = ['setup', 'add-on', 'example'];
    const ordered = typeOrder.flatMap((type) => phaseOrder.flatMap((phase) => addOns.filter((a) => a.phase === phase && a.type === type)));
    for (let i = ordered.length - 1; i >= 0; i--) {
        const files = await ordered[i].getFiles();
        if (files.some((f) => normalizePath(f) === target)) {
            return {
                source: 'add-on',
                sourceId: ordered[i].id,
                sourceName: ordered[i].name,
            };
        }
    }
    const frameworkFiles = await framework.getFiles();
    if (frameworkFiles.some((f) => normalizePath(f) === target)) {
        return {
            source: 'framework',
            sourceId: framework.id,
            sourceName: framework.name,
        };
    }
    return null;
}
// Build injection patterns from integrations (for source files)
function integrationInjections(int) {
    const source = { sourceId: int._sourceId, sourceName: int._sourceName };
    const injections = [];
    const appliesTo = (path) => {
        if (int.type === 'vite-plugin')
            return path.includes('vite.config');
        if (int.type === 'provider' ||
            int.type === 'root-provider' ||
            int.type === 'devtools') {
            return path.includes('__root') || path.includes('root.tsx');
        }
        return false;
    };
    if (int.import) {
        const prefix = int.import.split(' from ')[0];
        injections.push({
            matches: (line) => line.includes(prefix),
            appliesTo,
            source,
        });
    }
    const code = int.code || int.jsName;
    if (code) {
        injections.push({
            matches: (line) => line.includes(code),
            appliesTo,
            source,
        });
    }
    return injections;
}
// Build injection pattern from a dependency (for package.json)
function dependencyInjection(dep) {
    return {
        matches: (line) => line.includes(`"${dep.name}"`),
        appliesTo: (path) => path.endsWith('package.json'),
        source: { sourceId: dep.sourceId, sourceName: dep.sourceName },
    };
}
export async function computeAttribution(input) {
    const { framework, chosenAddOns, starter, files } = input;
    // Collect integrations tagged with source
    const integrations = chosenAddOns.flatMap((addOn) => (addOn.integrations || []).map((int) => ({
        ...int,
        _sourceId: addOn.id,
        _sourceName: addOn.name,
    })));
    // Collect dependencies from add-ons (from packageAdditions or packageTemplate)
    const dependencies = chosenAddOns.flatMap((addOn) => {
        const result = [];
        const source = { sourceId: addOn.id, sourceName: addOn.name };
        const addDeps = (deps, type) => {
            if (!deps)
                return;
            for (const [name, version] of Object.entries(deps)) {
                if (typeof version === 'string') {
                    result.push({ name, version, type, ...source });
                }
            }
        };
        // From static package.json
        addDeps(addOn.packageAdditions?.dependencies, 'dependency');
        addDeps(addOn.packageAdditions?.devDependencies, 'devDependency');
        // From package.json.ejs template (strip EJS tags and parse)
        if (addOn.packageTemplate) {
            try {
                const tmpl = JSON.parse(addOn.packageTemplate.replace(/"[^"]*<%[^%]*%>[^"]*"/g, '""'));
                addDeps(tmpl.dependencies, 'dependency');
                addDeps(tmpl.devDependencies, 'devDependency');
            }
            catch { }
        }
        return result;
    });
    // Build unified injection patterns from both integrations and dependencies
    const injections = [
        ...integrations.flatMap(integrationInjections),
        ...dependencies.map(dependencyInjection),
    ];
    const attributedFiles = {};
    for (const [filePath, content] of Object.entries(files)) {
        const provenance = await getFileProvenance(filePath, framework, chosenAddOns, starter);
        if (!provenance)
            continue;
        const lines = content.split('\n');
        const relevant = injections.filter((inj) => inj.appliesTo(filePath));
        // Find injected lines
        const injectedLines = new Map();
        for (const inj of relevant) {
            lines.forEach((line, i) => {
                if (inj.matches(line) && !injectedLines.has(i + 1)) {
                    injectedLines.set(i + 1, inj.source);
                }
            });
        }
        attributedFiles[filePath] = {
            content,
            provenance,
            lineAttributions: lines.map((_, i) => {
                const lineNum = i + 1;
                const inj = injectedLines.get(lineNum);
                return inj
                    ? {
                        line: lineNum,
                        sourceId: inj.sourceId,
                        sourceName: inj.sourceName,
                        type: 'injected',
                    }
                    : {
                        line: lineNum,
                        sourceId: provenance.sourceId,
                        sourceName: provenance.sourceName,
                        type: 'original',
                    };
            }),
        };
    }
    return { attributedFiles, dependencies };
}
