import set from 'lodash/fp/set.js';
import get from 'lodash/get.js';
import intersection from 'lodash/intersection.js';
import isEmpty from 'lodash/isEmpty.js';
import isEqual from 'lodash/isEqual.js';
import omit from 'lodash/omit.js';
import omitBy from 'lodash/omitBy.js';
import unset from 'lodash/unset.js';
import { AGENTS_UPDATE_KEY, GUARDS_UPDATE_KEY, TOOLS_UPDATE_KEY } from "../migration/types.js";
/**
 * Array of lodash.get paths to keys that are merged when applying a schema update.
 */
const MERGED_KEYS = [
    'queries',
    'mutations',
    'shapes',
    'workflows',
    'forms',
    'services',
    AGENTS_UPDATE_KEY,
    GUARDS_UPDATE_KEY,
    TOOLS_UPDATE_KEY
];
function shallowMerge(original, update) {
    const result = { ...original };
    for (const [key, obj] of Object.entries(update)) {
        // Null is a sentinel value to indicate delete
        if (obj === null) {
            unset(result, key);
        }
        else {
            result[key] = obj;
        }
    }
    return result;
}
/**
 * Apply a schema update to a schema. This can operate on a schema with frozen properties,
 * such as one produced by immer. Resulting schema is not assumed to be valid and should
 * be validated if appropriate.
 */
export function patchSchema(projectSchema, schemaUpdate) {
    // Patch old schema with update
    let updatedSchema = {
        ...projectSchema,
        ...omit(schemaUpdate, MERGED_KEYS)
    };
    for (const key of MERGED_KEYS) {
        const update = get(schemaUpdate, key);
        if (update) {
            updatedSchema = set(key, shallowMerge(get(projectSchema, key) ?? {}, update), updatedSchema);
        }
    }
    return updatedSchema;
}
const DEFAULT_OPTIONS = {
    mode: 'preserve',
    servicesMode: 'preserve'
};
function setSectionUpdate(update, patternKey, currentValues, patternValues, options) {
    const { mode, servicesMode } = { ...DEFAULT_OPTIONS, ...options };
    const keyMode = patternKey === 'services' ? servicesMode : mode;
    // Check for conflict in "fail" mode
    if (keyMode === 'fail' && currentValues && patternValues) {
        const wouldBeOverwritten = intersection(Object.keys(currentValues), Object.keys(patternValues));
        if (wouldBeOverwritten.length > 0) {
            throw new Error(`The following ${patternKey} would be overwritten: ${wouldBeOverwritten.join(', ')}`);
        }
    }
    // Add values to update
    if (patternValues) {
        const updateValues = keyMode === 'preserve' && currentValues
            ? omit(patternValues, Object.keys(currentValues))
            : omitBy(patternValues, (value, key) => isEqual(currentValues?.[key], value));
        if (!isEmpty(updateValues)) {
            update[patternKey] = updateValues;
        }
    }
}
function isEmptyUpdate(update) {
    return MERGED_KEYS.every((key) => isEmpty(update[key]));
}
export function getSchemaUpdateFromPattern(currentSchema, patternSchema, options) {
    const update = {};
    for (const path of MERGED_KEYS) {
        setSectionUpdate(update, path, get(currentSchema, path), get(patternSchema, path), options);
    }
    return isEmptyUpdate(update) ? undefined : update;
}
