/**
 * Process a single property value and add to items array
 * @param items Array to add items to
 * @param fullPath Full property path
 * @param value Property value to process
 */
function processPropertyValue(items, fullPath, value) {
    if (value === null) {
        items.push({ path: fullPath });
        return;
    }
    // Cast to record for checking
    const valueObj = value;
    const keys = Object.keys(valueObj);
    const hasAlias = '_alias' in valueObj;
    const hasDescription = '#description' in valueObj;
    const otherKeys = keys.filter((k) => k !== '_alias' && k !== '#description');
    // If it's {} or only has metadata (_alias, #description), treat as simple property
    if (otherKeys.length === 0) {
        const aliasValue = valueObj._alias;
        const descriptionValue = valueObj['#description'];
        if (aliasValue || descriptionValue) {
            items.push({
                path: fullPath,
                alias: aliasValue,
                description: descriptionValue,
            });
        }
        else {
            items.push({ path: fullPath });
        }
        return;
    }
    // If has metadata + other properties, we need to handle both
    // Add the nested properties (metadata is preserved in conversion back)
    if (hasAlias || hasDescription) {
        // Build a PropertySelection without the metadata for recursion
        const nestedSelection = {};
        for (const key of otherKeys) {
            nestedSelection[key] = valueObj[key];
        }
        items.push(...propertySelectionToItems(nestedSelection, fullPath));
        return;
    }
    // Nested property selection without metadata
    items.push(...propertySelectionToItems(value, fullPath));
}
/**
 * Convert a nested PropertySelection to a flat list of property items
 * @param selection The nested PropertySelection object
 * @param prefix Current path prefix (used for recursion)
 * @returns Flat array of PropertySelectionItem objects
 */
export function propertySelectionToItems(selection, prefix = '') {
    if (!selection) {
        return [];
    }
    const items = [];
    for (const [key, value] of Object.entries(selection)) {
        const fullPath = prefix ? `${prefix}.${key}` : key;
        if (typeof value === 'object') {
            // NOTE: We want to send null values to the processPropertyValue
            // function but we don't need a special check for null, since
            // `typeof null === 'object'`
            processPropertyValue(items, fullPath, value);
        }
    }
    return items;
}
/**
 * Set the final property value (with or without alias/description)
 * @param current Current PropertySelection object
 * @param part Property name/key
 * @param item Property selection item with path and optional alias/description
 */
function setPropertyValue(current, part, item) {
    // Check if there's already a nested PropertySelection with properties
    const existing = current[part];
    if (existing &&
        typeof existing === 'object' &&
        !('_alias' in existing) &&
        !('#description' in existing)) {
        // There's already a nested PropertySelection object with properties
        // Don't overwrite it - preserve the nested selections
        return;
    }
    // Build metadata object, stripping empty values
    const metadata = {};
    if (item.alias && item.alias.trim() !== '') {
        metadata._alias = item.alias;
    }
    if (item.description && item.description.trim() !== '') {
        metadata['#description'] = item.description;
    }
    // If we have metadata, use it; otherwise use null
    if (Object.keys(metadata).length > 0) {
        current[part] = metadata;
    }
    else {
        current[part] = null;
    }
}
/**
 * Ensure a nested object exists and return it
 * Preserves any existing _alias and #description when converting to nested object
 * @param current Current PropertySelection object
 * @param part Property name/key
 * @returns The nested PropertySelection object
 */
function ensureNestedObject(current, part) {
    const existing = current[part];
    // If doesn't exist or is null, create empty object
    if (!existing) {
        current[part] = {};
        return current[part];
    }
    // If it's already a PropertySelection object, check for metadata
    if (typeof existing === 'object') {
        const existingObj = existing;
        // If it has metadata but no other properties, preserve the metadata
        const keys = Object.keys(existingObj);
        const hasMetadata = '_alias' in existingObj || '#description' in existingObj;
        const otherKeys = keys.filter((k) => k !== '_alias' && k !== '#description');
        if (hasMetadata && otherKeys.length === 0) {
            // Convert { _alias: "...", "#description": "..." } to { _alias: "...", "#description": "...", ...nested }
            // Keep the existing object and return it (caller will add nested props)
            return existingObj;
        }
        // Already has nested properties, return as is
        return existingObj;
    }
    // Shouldn't reach here, but fallback to creating new object
    current[part] = {};
    return current[part];
}
/**
 * Convert a flat list of property items to a nested PropertySelection structure
 * @param items Array of PropertySelectionItem objects
 * @returns Nested PropertySelection object
 */
export function itemsToPropertySelection(items) {
    const result = {};
    for (const item of items) {
        const parts = item.path.split('.');
        let current = result;
        for (let i = 0; i < parts.length; i++) {
            const part = parts[i];
            const isLast = i === parts.length - 1;
            if (isLast) {
                setPropertyValue(current, part, item);
            }
            else {
                current = ensureNestedObject(current, part);
            }
        }
    }
    return result;
}
