/* 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 api from '@yootheme/api';
import hooks from '@yootheme/hooks';
import essentials from 'essentials';
import {
    DynamicField,
    DynamicQueryArgumentField,
    DynamicSubQueryArgumentField,
} from '@essentials/core/source';
import { flattenChildren } from '@essentials/core/util';
import { hasClass } from 'uikit-util';

hooks.before('app.init', ({ extend }) => {
    extend({
        events: {
            prepareFields: {
                handler({ origin: { $parent, builder, node, $el } }, fields) {
                    if (!builder && !node && !$parent?.node) {
                        return;
                    }

                    for (const field of fields) {
                        if (!field.source) {
                            continue;
                        }

                        field.buttons = field.buttons || [];

                        // filter out other dynamic buttons
                        field.buttons = field.buttons.filter((btn) => btn.action !== 'pickSource');
                        field.buttons = field.buttons.filter(
                            (btn) => btn.action !== 'essentialsPickSource' // avoid duplicates
                        );

                        // query prop
                        if (node) {
                            const prop = essentials.SourceExtended.getProp(node, field.name);

                            field.buttons = field.buttons.concat([
                                {
                                    label: this.$t('Dynamic'),
                                    action: 'essentialsPickSource',
                                    show: () => !prop?.composed,
                                },
                            ]);

                            if (prop) {
                                field.component = DynamicField;
                            }

                            continue;
                        }

                        // query argument prop
                        if (!node) {
                            // workaround to detect if rendered fields is a multiple item field,
                            // which will help determine where the dynamic argument value should
                            // be stored, query.arguments_extended or query.field.arguments_extended
                            const sidebarFields = $parent.$children.filter((c) =>
                                hasClass(c.$el, 'yo-sidebar-fields')
                            );
                            const sidebarIndex = sidebarFields.findIndex((c) => c.$el === $el);
                            const isMultipleItemField = sidebarIndex === 1;

                            field.buttons = field.buttons.concat([
                                {
                                    label: this.$t('Dynamic'),
                                    action: 'essentialsPickArgumentSource',
                                    show: () => !prop?.composed,
                                    isMultipleItemField,
                                },
                            ]);

                            const prop = isMultipleItemField
                                ? essentials.SourceExtended.getSubQueryArgumentProp(
                                      $parent?.node,
                                      field.name
                                  )
                                : essentials.SourceExtended.getQueryArgumentProp(
                                      $parent?.node,
                                      field.name
                                  );

                            if (prop) {
                                field.component = isMultipleItemField
                                    ? DynamicSubQueryArgumentField
                                    : DynamicQueryArgumentField;
                            }
                        }
                    }
                },
                priority: -5, // execute latest
            },

            evaluateExpression(e, expression, values) {
                const { builder, node, $parent } = e.origin;

                if (builder && node) {
                    // node props
                    e.params[1] = {
                        ...values,
                        ...(node?.source_extended?.props || {}),
                    };
                } else if ($parent?.builder && $parent?.node) {
                    // query argument props
                    e.params[1] = {
                        ...values,
                        ...($parent.node?.source?.query?.arguments_extended || {}),
                        ...($parent.node?.source_extended?.query?.arguments_extended || {}),
                    };
                }
            },
        },
    });
});

// override Source functions
hooks.after('app.init', () => {
    const { Source } = api.builder.helpers;
    const { SourceExtended, SourceDynamic } = essentials;

    // override getQuery
    const getQueryFn = Source.getQuery.bind(Source);

    Source.getQuery = function (node) {
        const query = getQueryFn(node);

        // if global query
        if (query?.startsWith(SourceExtended.globalQueryKey(''))) {
            const globalQueryId = query.replace(SourceExtended.globalQueryKey(''), '');
            return SourceDynamic.getGlobalQuery(globalQueryId)?.source?.query?.name || query;
        }
        return query;
    };

    // override hasSource
    const hasSourceFn = Source.hasSource.bind(Source);

    Source.hasSource = function (node, ...args) {
        const result = hasSourceFn(node, ...args);

        return result || hasSourcedFields(node);
    };

    // override hasInvalidSource
    const hasInvalidSourceFn = Source.hasInvalidSource.bind(Source);

    Source.hasInvalidSource = function (node, ...args) {
        let result = hasInvalidSourceFn(node, ...args);

        // node has source but is the props doing the mapping
        if (result === 'empty-props' && hasInheritingProps(node)) {
            return false;
        }

        // props are inheriting, checking if inheritable node exists
        if (result && hasInheritingProps(node)) {
            return !SourceExtended.getClosestInheritableNode(node);
        }

        if (hasSourcedFields(node)) {
            const props = node?.source_extended?.props || {};

            result = Object.keys(props).some((key) => {
                const prop = props[key];

                if (prop?.composed?.sources) {
                    const sources = prop.composed.sources;
                    return Object.keys(sources).some((key) =>
                        validateSource(key, sources[key], ...args)
                    );
                }

                return validateSource(key, prop, ...args);
            });
        }

        return result;
    };

    function validateSource(key, source, ...args) {
        if (!source?.query?.name) {
            return false;
        }

        if (source.query.name === SourceExtended.closestKey) {
            return false;
        }

        // emulate node to validate query
        const n = {
            type: 'mocked',
            source: {
                props: {
                    [key]: source,
                },
                query: source.query,
            },
        };

        return hasInvalidSourceFn(n, ...args);
    }

    function hasSourcedFields(node) {
        const props = node?.source_extended?.props || {};

        return Object.keys(props).some((key) => {
            const prop = props[key];
            return prop?.query?.name || prop?.composed;
        });
    }

    function hasInheritingProps(node) {
        const nodes = [node, ...flattenChildren(node)];

        return nodes.some((child) => {
            const props = child?.source_extended?.props || {};

            return Object.keys(props).some((key) => {
                const prop = props[key];

                if (prop?.composed?.sources) {
                    const sources = prop.composed.sources;
                    return Object.keys(sources).some(
                        (key) => sources[key]?.query?.name === SourceExtended.closestKey
                    );
                }

                return prop?.query?.name === SourceExtended.closestKey;
            });
        });
    }
});
