/* Essentials YOOtheme Pro 2.2.16 build 0813.1608; ZOOlanders https://www.zoolanders.com; Copyright (C) Joolanders, SL; http://www.gnu.org/licenses/gpl.html GNU/GPL */

import Vue from 'vue';
import api from '@yootheme/api';
import store from '@yootheme/store';
import essentials from '../api';
import { set } from '../util';
import { includes, isEmpty, isPlainObject, isString } from 'uikit-util';
import map from 'lodash-es/map';

export default {
    closestKey: '~closest',

    globalQueryKey(id) {
        return `~global:${id}`;
    },

    getDynamicOptions({ node, field }) {
        return Vue.events.trigger('essentialsDynamicOptions', { node, field }) || [];
    },

    get globalSources() {
        return (
            api.builder.helpers.Schema.rootQueryFields?.filter(
                (field) => !field?.metadata?.group
            ) || []
        );
    },

    findQueryField(query) {
        const { Schema } = api.builder.helpers;

        if (query?.field?.name) {
            return Schema.findField(`${query?.name}.${query?.field?.name}`);
        }

        return Schema.findField(query?.name ?? '');
    },

    getPageQueries() {
        const { Schema } = api.builder.helpers;
        const { usePreviewStore } = store || {};

        // ytp 4.2+ and below compatibility
        const view = usePreviewStore ? usePreviewStore().data.view : essentials.Builder?.view;

        return (
            Schema.rootQueryFields?.filter(
                (field) =>
                    field?.metadata?.group === Vue.i18n.t('Page') &&
                    includes(field?.metadata?.view ?? [], view)
            ) || []
        );
    },

    getProp(node, name) {
        const prop = node.source?.props?.[name] || node.source_extended?.props?.[name];

        // support for multiple sources
        // if (!prop) {
        //     const keys = Object.keys(node.source_extended?.props);
        //     const props = keys.filter((key) => key.startsWith(`${name}#`));

        //     if (props.length) {
        //         return props;
        //     }
        // }

        return prop;
    },

    removeProp(node, name) {
        // source
        if (node?.source?.props?.[name]) {
            Vue.delete(node.source.props, name);
        }
        if (node.source?.props && isEmpty(node.source.props)) {
            Vue.delete(node.source, 'props');
        }
        if (node.source && isEmpty(node.source)) {
            Vue.delete(node, 'source');
        }

        // source extended
        if (node?.source_extended?.props?.[name]) {
            Vue.delete(node.source_extended.props, name);
        }
        if (node.source_extended?.props && isEmpty(node.source_extended.props)) {
            Vue.delete(node.source_extended, 'props');
        }
        if (node.source_extended && isEmpty(node.source_extended)) {
            Vue.delete(node, 'source_extended');
        }
    },

    getQueryArgumentProp(node, name) {
        return node.source?.query?.arguments_extended?.[name];
    },

    setQueryArgumentProp(node, propName, prop) {
        const defaults = this.getPropDefaults(node, prop);

        this.removeQueryArgumentProp(node, propName);

        set(node, `source.query.arguments_extended.${propName}`, { ...defaults, ...prop });
    },

    removeQueryArgumentProp(node, name) {
        if (node?.source?.query?.arguments_extended?.[name]) {
            Vue.delete(node.source.query.arguments_extended, name);

            if (isEmpty(node.source.query.arguments_extended)) {
                Vue.delete(node.source.query, 'arguments_extended');
            }
        }
    },

    getSubQueryArgumentProp(node, name) {
        return node.source?.query?.field?.arguments_extended?.[name];
    },

    setSubQueryArgumentProp(node, propName, prop) {
        const defaults = this.getPropDefaults(node, prop);

        this.removeSubQueryArgumentProp(node, propName);

        set(node, `source.query.field.arguments_extended.${propName}`, { ...defaults, ...prop });
    },

    removeSubQueryArgumentProp(node, name) {
        if (node?.source?.query?.field?.arguments_extended?.[name]) {
            Vue.delete(node.source.query.field.arguments_extended, name);

            if (isEmpty(node.source.query.field.arguments_extended)) {
                Vue.delete(node.source.query.field, 'arguments_extended');
            }
        }
    },

    path(node, prop) {
        let nodes = essentials.Builder ? builderPath.call(essentials.Builder, node) : [];

        // add the prop query to the nodes
        if (prop?.query) {
            nodes = [{ type: '#prop', props: {}, source: { query: prop.query } }, ...nodes];
        }

        return nodes;
    },

    getClosestInheritableNode(node) {
        const ancestors = essentials.Builder?.path(this.isAdjacentOf(node) || node) || [];

        for (const ancestor of ancestors) {
            if (ancestor !== node && !this.isAdjacentTo(ancestor) && ancestor?.source?.query) {
                return ancestor;
            }
        }
    },

    isAdjacent(node) {
        return Boolean(this.isAdjacentOf(node));
    },

    isAdjacentTo(node) {
        return Boolean(essentials.Builder?.adjacentOf === node);
    },

    isAdjacentOf(node) {
        return node && !essentials.Builder?.exists(node) && essentials.Builder?.adjacentOf;
    },

    resolveAdjacentNode(node) {
        if (this.isAdjacent(node)) {
            return essentials.Builder?.adjacentOf;
        }

        return node;
    },

    getQueryPath(node) {
        const { Source } = api.builder.helpers;
        return [Source.getQuery(node), Source.getField(node)].filter(Boolean).join('.');
    },

    setQuery(node, name) {
        Vue.delete(node?.source || {}, 'query');
        Vue.delete(node?.source_extended || {}, 'query');

        if (name) {
            set(node, 'source.query.name', name);
        }

        if (isEmpty(node.source)) {
            Vue.delete(node, 'source');
        }

        if (isEmpty(node.source_extended)) {
            Vue.delete(node, 'source_extended');
        }
    },

    setExtendedQuery(node, query) {
        set(node, 'source_extended.query', query);

        Vue.delete(node?.source || {}, 'query');

        if (isEmpty(node.source)) {
            Vue.delete(node, 'source');
        }
    },

    setExtendedProp(node, propName, prop) {
        const defaults = this.getPropDefaults(node, prop);

        if (node.props) {
            Vue.delete(node.props, propName);
        }

        this.removeProp(node, propName);

        set(node, `source_extended.props.${propName}`, { ...defaults, ...prop });
    },

    getPropDefaults(node, prop) {
        const { Source } = api.builder.helpers;

        const nodes = this.path(node, prop);
        const propField = this.getPropField(nodes, prop);
        const sourceField = Source.getSourceField(nodes);
        const isMulti = sourceField?.type?.kind === 'LIST' || propField?.type === 'LIST';
        const fieldsets = propField?.fieldsets || [];

        return fieldsets
            .filter(({ multi }) => !multi || (multi && isMulti))
            .reduce((c, f) => ({ ...c, ...getFieldDefaults(f.fields) }), {});
    },

    getPropField(nodes, prop) {
        const { Source } = api.builder.helpers;

        const field = Source.getSourceField(nodes);
        const fields = this.getFieldsList(field);
        const propField = fields.find((field) => field.value === prop?.name);

        if (!propField) {
            return null;
        }

        return this.resolvePropFieldsets(propField);
    },

    getFieldsList(field) {
        const { Schema } = api.builder.helpers;

        // include both scalar and list fields
        const scalarFields = Schema.getFields(field);
        const listFields = Schema.getFields(field, 'LIST').map((field) => ({
            ...field,
            type: 'LIST', // a hint for the dynamic workflow
        }));

        return [...scalarFields, ...listFields];
    },

    resolvePropFieldsets(propField) {
        return {
            ...propField,
            fieldsets: [
                {
                    label: 'Arguments',
                    fields: resolveArgumentsFields(propField),
                },
                {
                    label: 'Filters',
                    fields: resolveFiltersFields(propField),
                },
                {
                    label: 'Implode',
                    multi: true,
                    fields: resolveImplodeFields(),
                },
            ],
        };
    },
};

function resolveArgumentsFields(field) {
    return prepareFields(field.arguments, 'arguments');
}

function resolveFiltersFields(field, defaultFilters = ['before', 'after', 'search', 'replace']) {
    const baseFilters = api.builder.sources.filters;

    return [...defaultFilters, ...prepareFields(field.filters, 'filters')]
        .map((name) => (isString(name) ? { name: `filters.${name}`, ...baseFilters[name] } : name))
        .filter((t) => t);
}

function resolveImplodeFields() {
    return prepareFields(api.builder.sources.implode, 'implode').filter(Boolean);
}

export function prepareFields(fields, prefix) {
    return isPlainObject(fields)
        ? map(fields, (field, name) => ({ name: `${prefix}.${name}`, ...field }))
        : fields || [];
}

function getFieldDefaults(fields) {
    const defaults = {};

    for (const field of fields) {
        if ('default' in field) {
            set(defaults, field.name, field.default);
        }
    }

    return defaults;
}

function builderPath(node) {
    const path = [];
    do {
        path.push(node);
    } while ((node = this.parent(node)));
    return path;
}
