import { createHash } from 'node:crypto';
export function hashProperty(property) {
    const normalized = normalizeProperty(property);
    const json = JSON.stringify(normalized, Object.keys(normalized).sort());
    return createHash('md5').update(json).digest('hex');
}
function normalizeProperty(property) {
    const normalized = {};
    for (const [key, value] of Object.entries(property)) {
        if (value === undefined || value === null || value === '')
            continue;
        if (typeof value === 'object' && !Array.isArray(value)) {
            const nestedNormalized = {};
            for (const [k, v] of Object.entries(value)) {
                if (v !== undefined && v !== null && v !== '') {
                    nestedNormalized[k] = v;
                }
            }
            if (Object.keys(nestedNormalized).length > 0) {
                normalized[key] = nestedNormalized;
            }
        }
        else if (Array.isArray(value)) {
            if (value.length > 0) {
                normalized[key] = value;
            }
        }
        else {
            normalized[key] = value;
        }
    }
    return normalized;
}
export function diffSchemas(yamlSchema, sheetSchema) {
    const diff = {
        hasChanges: false,
        metadataChanges: {},
        moduleName: yamlSchema.moduleName,
        pivotsChanged: [],
        propertiesAdded: [],
        propertiesModified: [],
        propertiesRemoved: [],
    };
    const metadataKeys = [
        'version',
        'moduleName',
        'moduleNames',
        'aggregateName',
        'boundedContextName',
        'hasOAuth',
        'hasTenant',
        'hasAuditing',
        'description',
    ];
    for (const key of metadataKeys) {
        const yamlVal = yamlSchema[key];
        const sheetVal = sheetSchema[key];
        if (normalizeValue(yamlVal) !== normalizeValue(sheetVal)) {
            diff.metadataChanges[key] = { sheet: sheetVal, yaml: yamlVal };
            diff.hasChanges = true;
        }
    }
    const yamlProps = new Map();
    const sheetProps = new Map();
    for (const prop of yamlSchema.aggregateProperties || []) {
        yamlProps.set(prop.name, prop);
    }
    for (const prop of sheetSchema.aggregateProperties || []) {
        sheetProps.set(prop.name, prop);
    }
    // Added: in sheet, not in YAML
    for (const [name, sheetProp] of sheetProps) {
        if (!yamlProps.has(name)) {
            diff.propertiesAdded.push({
                name,
                sheetValue: sheetProp,
                type: 'added',
            });
            diff.hasChanges = true;
        }
    }
    // Removed: in YAML, not in sheet
    for (const [name, yamlProp] of yamlProps) {
        if (!sheetProps.has(name)) {
            diff.propertiesRemoved.push({
                name,
                type: 'removed',
                yamlValue: yamlProp,
            });
            diff.hasChanges = true;
        }
    }
    for (const [name, yamlProp] of yamlProps) {
        const sheetProp = sheetProps.get(name);
        if (!sheetProp)
            continue;
        const yamlHash = hashProperty(yamlProp);
        const sheetHash = hashProperty(sheetProp);
        if (yamlHash !== sheetHash) {
            const changes = findPropertyChanges(yamlProp, sheetProp);
            diff.propertiesModified.push({
                changes,
                name,
                sheetValue: sheetProp,
                type: 'modified',
                yamlValue: yamlProp,
            });
            diff.hasChanges = true;
        }
    }
    return diff;
}
function findPropertyChanges(yamlProp, sheetProp) {
    const changes = {};
    const allKeys = new Set([
        ...Object.keys(sheetProp),
        ...Object.keys(yamlProp),
    ]);
    for (const key of allKeys) {
        const yamlVal = yamlProp[key];
        const sheetVal = sheetProp[key];
        if (normalizeValue(yamlVal) !== normalizeValue(sheetVal)) {
            changes[key] = { sheet: sheetVal, yaml: yamlVal };
        }
    }
    return changes;
}
function normalizeValue(value) {
    if (value === undefined || value === null || value === '')
        return '';
    if (typeof value === 'object')
        return JSON.stringify(value);
    return String(value);
}
export function formatDiff(diff) {
    const lines = [];
    if (!diff.hasChanges) {
        lines.push(`${diff.moduleName}: No changes detected`);
        return lines.join('\n');
    }
    lines.push(`\n--- ${diff.moduleName} ---`);
    if (Object.keys(diff.metadataChanges).length > 0) {
        lines.push('\nMetadata Changes:');
        for (const [key, { sheet, yaml }] of Object.entries(diff.metadataChanges)) {
            lines.push(`  ${key}:`, `    YAML:  ${formatValue(yaml)}`, `    Sheet: ${formatValue(sheet)}`);
        }
    }
    if (diff.propertiesAdded.length > 0) {
        lines.push('\n+ Added Properties (in Sheet, not in YAML):');
        for (const prop of diff.propertiesAdded) {
            lines.push(`  + ${prop.name} (${prop.sheetValue?.type})`);
        }
    }
    if (diff.propertiesRemoved.length > 0) {
        lines.push('\n- Removed Properties (in YAML, not in Sheet):');
        for (const prop of diff.propertiesRemoved) {
            lines.push(`  - ${prop.name} (${prop.yamlValue?.type})`);
        }
    }
    if (diff.propertiesModified.length > 0) {
        lines.push('\n~ Modified Properties:');
        for (const prop of diff.propertiesModified) {
            lines.push(`  ~ ${prop.name}:`);
            if (prop.changes) {
                for (const [key, { sheet, yaml }] of Object.entries(prop.changes)) {
                    lines.push(`      ${key}: "${formatValue(yaml)}" -> "${formatValue(sheet)}"`);
                }
            }
        }
    }
    return lines.join('\n');
}
function formatValue(value) {
    if (value === undefined || value === null)
        return '(empty)';
    if (typeof value === 'object')
        return JSON.stringify(value);
    return String(value);
}
export function formatDiffSummary(diffs) {
    const lines = [];
    const changed = diffs.filter((d) => d.hasChanges);
    const unchanged = diffs.filter((d) => !d.hasChanges);
    lines.push('\n========================================', '           DIFF SUMMARY', '========================================\n', `Total modules: ${diffs.length}`, `Changed: ${changed.length}`, `Unchanged: ${unchanged.length}`);
    if (changed.length > 0) {
        lines.push('\nModules with changes:');
        for (const diff of changed) {
            const adds = diff.propertiesAdded.length;
            const removes = diff.propertiesRemoved.length;
            const mods = diff.propertiesModified.length;
            const meta = Object.keys(diff.metadataChanges).length;
            const parts = [];
            if (adds)
                parts.push(`+${adds}`);
            if (removes)
                parts.push(`-${removes}`);
            if (mods)
                parts.push(`~${mods}`);
            if (meta)
                parts.push(`meta:${meta}`);
            lines.push(`  ${diff.moduleName}: ${parts.join(', ')}`);
        }
    }
    return lines.join('\n');
}
