/* eslint-disable no-use-before-define */
/* eslint-disable no-param-reassign */
/* eslint-disable jsdoc/require-param-type */
/* eslint-disable jsdoc/require-param-description */
/* eslint-disable jsdoc/require-returns */
import dynamicStrings from '@/constants/dynamic-strings';
import { useMainStore } from '@/store';

/**
 * @function getStringWithCustomValues
 * @summary Returns str with all custom values replaced in accordance with the provided customValueMap
 * @param {string} str
 * @param {Dictionary} customValueMap
 * @returns {string}
 */
export function getStringWithCustomValues(str, customValueMap) {
    let newString = str ?? '';
    if (customValueMap != null) {
        Object.keys(customValueMap).forEach((key) => {
            newString = newString.replaceAll(`{custom:${key}}`, customValueMap[key]);
        });
    }
    return newString;
}

// This function will process the widget item and replace any global state variables with their values.
// This is a recursive function, it will call itself until it runs out of items to iterate on given the object.
/**
 * @function processWidgetItemForReplacement
 * @param widgetModel
 * @param key
 */
function processWidgetItemForReplacement(widgetModel, key) {
    // If we have a string, and it needs to be replaced.
    if (typeof widgetModel[key] === 'string') {
        if (widgetModel[key].includes('{if:')) {
            widgetModel[key] = processIfStatements(
                widgetModel[key],
                dynamicStrings.GLOBAL_STATE,
                getStoreValueFromString
            );
        }

        if (widgetModel[key].includes(dynamicStrings.MODAL_LINK)) {
            widgetModel[key] = mapStringToModal(widgetModel[key]);
        }
        if (widgetModel[key].includes(dynamicStrings.EXTERNAL_LINK)) {
            widgetModel[key] = mapStringToLink(widgetModel[key]);
        }
        if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) {
            widgetModel[key] = mapStringToState(widgetModel[key]);
        }
        return widgetModel[key];
    }

    // If we have an object. array, etc
    if (typeof widgetModel[key] === 'object' && Object.keys(widgetModel[key]).length) {
        Object.keys(widgetModel[key]).forEach((item) => {
            processWidgetItemForReplacement(widgetModel[key], item);
        });

        return widgetModel[key];
    }

    // If we have something else like a number, boolean, etc. just return it
    return widgetModel[key];
}

// Parent function for processWidgetItemForReplacement. This will loop through the parent
// object and pass any objects that need additional processing to the processWidgetItemForReplacement function.
/**
 * @function processWidgetItemForfindAndReplaceGlobalStateValuesReplacement
 * @param widgetModel
 * @param widgetName
 */
function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
    const objWithReplacements = {
        Name: widgetName,
        Model: {}
    };

    Object.keys(widgetModel).forEach((key) => {
        const modelWithReplacements = processWidgetItemForReplacement(widgetModel, key);

        objWithReplacements.Model[key] = modelWithReplacements;
    });

    return objWithReplacements;
}

// Support method for processing the page data from the CMS call.
// widgets = current widget collection used by page.
// baseResponse = contains the widgets from the base page.
// clientResponse = contains the widgets from the client override page. (null if none)
/**
 * @function processPageData
 * @param baseResponse
 * @param clientResponse
 */
function processPageData(baseResponse, clientResponse) {
    const pageDataFromCms = {};
    let widgets = [];

    if (!baseResponse?.data?.Result) {
        console.error('No result data found'); // Something has gone terribly wrong.
        return {};
    }
    if (!clientResponse?.data?.Result) {
        widgets = baseResponse.data.Result;
    } else {
        // Override any base widgets with the client override widgets if found.
        baseResponse.data.Result.forEach((baseWidget) => {
            let found = false;
            clientResponse.data.Result.forEach((clientWidget) => {
                if (clientWidget.Name === baseWidget.Name) {
                    widgets.push(clientWidget);
                    found = true;
                }
            });

            if (!found) {
                widgets.push(baseWidget);
            }
        });

        // Add any unique client widgets that does not exist in the current main widgets collection.
        clientResponse.data.Result.forEach((clientWidget) => {
            let found = false;
            widgets.forEach((currentWidget) => {
                if (clientWidget.Name === currentWidget.Name) {
                    found = true;
                }
            });

            if (!found) {
                widgets.push(clientWidget);
            }
        });
    }

    widgets.forEach((widget) => {
        // Global state value replacement.
        const widgetWithReplacements = findAndReplaceGlobalStateValues(widget.Model, widget.Name);

        // If we already have this widget, push it on the collection
        if (widgetWithReplacements.Name in pageDataFromCms) {
            pageDataFromCms[widgetWithReplacements.Name].push(widgetWithReplacements.Model);
            return;
        }

        pageDataFromCms[widgetWithReplacements.Name] = [widgetWithReplacements.Model];
    });

    Object.keys(pageDataFromCms).forEach((key) => {
        if (pageDataFromCms[key].length === 1) {
            pageDataFromCms[key] = pageDataFromCms[key][0];
        }
    });

    return pageDataFromCms;
}

/**
 *
 * @param issPage
 */
export function fetchCmsContentForPage(issPage) {
    const store = useMainStore();
    const { clientName } = store.issConfig;
    const { parentAccountNumber } = store.issConfig;
    const clientOverride = clientName.length > 0 && parentAccountNumber > 0;

    return (
        store
            .getPageData(issPage)
            // Get the base/default page first.
            .then((baseResponse) => {
                if (!clientOverride) {
                    // Return the base page if there are no client override.
                    return processPageData(baseResponse, null);
                }
                // Else get the client override page.
                const pageName = `${issPage}_${clientName.toLowerCase().replace(/ /g, '')}`;

                return store.getPageData(pageName).then(
                    (clientResponse) =>
                    // Process the client override if it exists.
                        processPageData(baseResponse, clientResponse),
                    (error) => {
                        console.error(error);
                        // Process the just the base if no client override exists.
                        return processPageData(baseResponse, null);
                    }
                );
            })
    );
}

export function fetchGlobalCmsContent(issComponent) {
    const store = useMainStore();

    return (
        store.getPageData(issComponent)
            .then((baseResponse) => processPageData(baseResponse, null)));
}

export function updateCmsSiteHeader(cmsResponse) {
    const clientCmsSiteHeader = getCmsSiteHeader(cmsResponse);
    useMainStore().issConfig.clientHeader = clientCmsSiteHeader;
}

function getCmsSiteHeader(cmsResponse) {
    if (cmsResponse?.SiteHeaderWidget) {
        const siteHeader = cmsResponse?.SiteHeaderWidget;
        if (siteHeader.Answers) {
            const { parentAccountNumber } = useMainStore().issConfig;
            let headerAnswersToUse = siteHeader.Answers
                .find((answer) => answer.AccountNumber === parentAccountNumber.toString());
            if (headerAnswersToUse == null) {
                headerAnswersToUse = siteHeader.Answers
                    .find((answer) => answer.AccountNumber === '0');
            }

            if (headerAnswersToUse) {
                return {
                    altText: headerAnswersToUse.AltText,
                    clientName: headerAnswersToUse.ClientName,
                    hexCode: headerAnswersToUse.HexCode,
                    imageId: headerAnswersToUse.ImageId,
                    imageUrl: headerAnswersToUse.AnswerImageUrl
                };
            }
        }
    }
    return {};
}

/**
 * @function mapStringToModal
 * @param str
 */
function mapStringToModal(str) {
    const startIndex = str.indexOf(`{${dynamicStrings.MODAL_LINK}`);
    let linkToReplace = str.substring(startIndex, str.length);
    linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);

    const params = linkToReplace.substring(
        dynamicStrings.MODAL_LINK.length + 2,
        linkToReplace.length - 1
    );
    const splitParams = params.split(',');

    const bodyText =
        `<a href="#" modalTarget="${splitParams[0]}" class="modal-text"><span class="sr-only">Link:</span>${splitParams[1]}</a>`;

    let returnVal = str.replace(linkToReplace, bodyText);

    if (returnVal.includes(dynamicStrings.MODAL_LINK)) {
        returnVal = mapStringToModal(returnVal);
    }
    return returnVal;
}

/**
 * @function mapStringToLink
 * @param str
 */
function mapStringToLink(str) {
    const startIndex = str.indexOf(`{${dynamicStrings.EXTERNAL_LINK}`);
    let linkToReplace = str.substring(startIndex, str.length);
    linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);

    const params = linkToReplace.substring(
        dynamicStrings.EXTERNAL_LINK.length + 2,
        linkToReplace.length - 1
    );
    const splitParams = params.split(',');

    const bodyText = `<a href="${splitParams[0]}" class="external-text" target="_blank">${splitParams[1]}</a>`;

    let returnVal = str.replace(linkToReplace, bodyText);

    if (returnVal.includes(dynamicStrings.EXTERNAL_LINK)) {
        returnVal = mapStringToLink(returnVal);
    }
    return returnVal;
}

// Function to convert a string, into a matching global state item.
/**
 * @function mapStringToState
 * @param str
 */
function mapStringToState(str) {
    // Pull all matches out of the string.
    const regexExp = /{([^{}]*?):([^{}]*?)}/g;
    const regexMatches = [...str.matchAll(regexExp)];
    const globalStateMatches = regexMatches.filter((match) => match[1] === dynamicStrings.GLOBAL_STATE);

    // Our final string value that will be built from the matches.
    const stringBuilder = '';

    // eslint-disable-next-line no-restricted-syntax
    for (const match of globalStateMatches) {
        // Reset store state for each match.
        const valueFromStore = getStoreValueFromString(match[2]);
        if (!valueFromStore) {
            console.warn('Unable to resolve global state data.');
            console.warn(`Tried to resolve: ${match[2]}`);
            return ''; // if we can't map our string to state data, return an empty string.
        }
        const stringWithReplacement = str.replace(match[0], valueFromStore);

        // If we still have values we need to substitute, call this function again.
        if (stringWithReplacement.includes(dynamicStrings.GLOBAL_STATE)) {
            return mapStringToState(stringWithReplacement);
        }

        // Concatenate the string.
        str = `${stringBuilder} ${stringWithReplacement}`;
    }

    return str.trimStart();
}

/**
 * @function getStoreValueFromString
 * @param str
 */
function getStoreValueFromString(str) {
    if (!str) return '';

    let storeOrStateObject = useMainStore();
    // eslint-disable-next-line no-restricted-syntax
    for (const s of str.split('.')) {
        if (s === 'getters') continue; // For backward compatibility
        // TODO: I don't think this next line is doing what they think it's doing.
        // eslint-disable-next-line eqeqeq, valid-typeof
        if (typeof storeOrStateObject[s] != undefined) {
            storeOrStateObject = storeOrStateObject[s];
        } else {
            break;
        }
    }
    return storeOrStateObject ?? '';
}

/// ////////////////////////////////
// If Statement Processing Logic //
/// ////////////////////////////////

/**
 * Recursive function - replaces all instances of if statements from the CMS that utilize the specified ifConditionKeyword
 * @param {*} str string - Input string to be processed
 * @param {*} ifConditionKeyword string - Defines which if statements to process ex: 'globalState'
 * @param {*} replacePlaceholderCallback function - Callback to replace CMS placeholder values
 * @returns The processed string
 */
export function processIfStatements(str, ifConditionKeyword, replacePlaceholderCallback) {
    const containsRelevantIfStatement = new RegExp(`{if:${ifConditionKeyword}:.+?}`, 'g').test(str);

    if (!containsRelevantIfStatement) {
        return str;
    }
    const ifStatementRegexExpression = getIfStatementRegexExpression();
    const ifStatementRegexMatches = [...str.matchAll(ifStatementRegexExpression)];
    const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword(
        ifStatementRegexMatches,
        ifConditionKeyword
    );
    executeIfStatementAndSetProcessedStrings(completeIfStatementArray, replacePlaceholderCallback);
    const reconstructedPostProcessedString = joinProcessedRegexArray(ifStatementRegexMatches);
    return processIfStatements(
        reconstructedPostProcessedString,
        ifConditionKeyword,
        replacePlaceholderCallback
    );
}

/**
 *
 * @param matches
 * @param ifConditionKeyword
 */
function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyword) {
    let index = 0;
    // eslint-disable-next-line no-restricted-syntax
    for (const match of matches) {
        if (match.groups.isIfStatement && match.groups.ifConditionType === ifConditionKeyword) {
            let interiorIndex = 0;
            let nestedLevel = 0;
            let elseStatementIndex = null;
            // eslint-disable-next-line no-restricted-syntax
            for (const interiorMatch of matches.slice(index + 1)) {
                if (interiorMatch.groups.isIfStatement) {
                    if (interiorMatch.groups.ifConditionType === ifConditionKeyword) {
                        break;
                    } else {
                        nestedLevel++;
                    }
                } else if (interiorMatch.groups.isElseStatement) {
                    if (!nestedLevel) {
                        elseStatementIndex = interiorIndex + 1;
                    }
                } else if (interiorMatch.groups.isEndStatement) {
                    if (nestedLevel) {
                        nestedLevel--;
                    } else {
                        const ifStatementArray = matches.slice(index, index + interiorIndex + 2);
                        flagMatchesForProcessing(ifStatementArray, elseStatementIndex);
                        return ifStatementArray;
                    }
                }
                interiorIndex++;
            }
        }
        index++;
    }
    console.error('Did not find end in conditional logic');
    return matches;
}

/**
 *
 * @param matches
 * @param elseStatementIndex
 */
function flagMatchesForProcessing(matches, elseStatementIndex) {
    matches[0].isFlaggedForProcessing = true;
    matches[matches.length - 1].isFlaggedForProcessing = true;
    if (elseStatementIndex) {
        matches[elseStatementIndex].isFlaggedForProcessing = true;
    }
}

/**
 *
 * @param regexMatches
 */
function joinProcessedRegexArray(regexMatches) {
    let processedString = '';
    regexMatches.forEach((match) => {
        const rawString = match[0];
        processedString += match.groups.processedString ?? rawString;
    });
    return processedString;
}

/**
 *
 * @param ifStatementArray
 * @param replacePlaceholderCallback
 */
function executeIfStatementAndSetProcessedStrings(ifStatementArray, replacePlaceholderCallback) {
    const ifCondition = replacePlaceholderCallback(ifStatementArray[0].groups.ifCondition);
    let isInsideDesiredBlock = ifCondition;

    ifStatementArray.forEach((entry) => {
        if (entry.groups.isElseStatement && entry.isFlaggedForProcessing) {
            isInsideDesiredBlock = !ifCondition;
        } else if (entry.groups.isEndStatement && entry.isFlaggedForProcessing) {
            isInsideDesiredBlock = true;
        }
        setProcessedStringOnEntry(entry, isInsideDesiredBlock);
    });
}

/**
 *
 * @param entry
 * @param isInsideDesiredBlock
 */
function setProcessedStringOnEntry(entry, isInsideDesiredBlock) {
    if (!isInsideDesiredBlock) {
        entry.groups.processedString = '';
    } else if (entry.groups.isIfStatement) {
        entry.groups.processedString = entry.isFlaggedForProcessing
            ? entry.groups.ifTrailingString
            : entry[0];
    } else if (entry.groups.isElseStatement) {
        entry.groups.processedString = entry.isFlaggedForProcessing
            ? entry.groups.elseTrailingString
            : entry[0];
    } else {
        entry.groups.processedString = entry.isFlaggedForProcessing
            ? entry.groups.endTrailingString
            : entry[0];
    }
}

/**
 *
 */
function getIfStatementRegexExpression() {
    // Matches but does not capture:
    // {if:...} or {else} or {end}
    const anyLogicOperatorNonCapture = '(?:{(?:end|else|if:.*?)})';
    // NOTE: ?<variableName> syntax stores the captured match like so: match.groups.variableName
    const matchStartOfString =
        '(?<processedString>^.+?)' // Match and Capture all characters (lazy), cannot be empty
        + '(?=(?:{if))'; // Looks ahead but does not capture {if
    const matchIfOperator =
        '(?<isIfStatement>{if:)' // Match & Capture {if:
        + '(?<ifConditionType>.*?):' // Match all chars up to and including next ':' - Capture all chars up to ':'
        + '(?<ifCondition>.*?)}' // Match all chars up to and including next '}' - Capture all chars up to '}'
        + '(?<ifTrailingString>.*?)' // Match and Capture all characters (lazy), can be empty
        + `(?=${anyLogicOperatorNonCapture})`; // Looks ahead but does not capture the next logic operator
    const matchElseOperator =
        '(?<isElseStatement>{else})' // Match & Capture {else}
        + '(?<elseTrailingString>.*?)' // Match & Capture all characters (lazy), can be empty
        + `(?=${anyLogicOperatorNonCapture})`; // Looks ahead but does not capture the next logic operator
    const matchEndOperator =
        '(?<isEndStatement>{end})' // Match & Capture {end}
        + '(?<endTrailingString>.*?)' // Match & Capture all characters (lazy), can be empty
        + `(?=${anyLogicOperatorNonCapture}|$)`; // Looks ahead but does not capture the next logic operator
    // Combine all matching patterns, separated by 'or' pipes
    return new RegExp(
        `${matchStartOfString}|${matchIfOperator}|${matchElseOperator}|${matchEndOperator}`,
        'g'
    );
}

/// ///////////////////////////////////////
// End of If Statement Processing Logic //
/// ///////////////////////////////////////

/**
 *
 * @param copy
 */
export function doesCopyContainTextLink(copy) {
    return copy.includes(dynamicStrings.TEXT_LINK);
}

/**
 *
 * @param context
 */
export function setupModalLinks(context) {
    context.$nextTick(() => {
        const elements = document.getElementsByClassName('modal-text');
        // eslint-disable-next-line no-restricted-syntax
        for (const element of elements) {
            const target = element.getAttribute('modalTarget');
            if (target) {
                element.addEventListener('click', () => context.$refs[target].openModal());
            }
        }
    });
}

/**
 *
 * @param context
 * @param desiredTarget
 */
export function setupModalLink(context, desiredTarget) {
    context.$nextTick(() => {
        const element = document.querySelector(`[modalTarget=${desiredTarget}]`);
        if (element) {
            element.addEventListener('click', () => context.$refs[desiredTarget].openModal());
        }
    });
}

/**
 *
 * @param copy
 */
export function doesCopyContainRouterLink(copy) {
    return copy.includes(this.dynamicStrings.ROUTER_LINK);
}

/**
 * splits copy on { ... } such as {routerlink: ...}
 * @param copy
 * @returns array of strings
 */
export function splitCopyOnCMSPlaceHolder(copy) {
    // splits copy on { ... } such as {routerlink: ...}
    return copy.split(/{(.*?)}/g);
}

export function getExternalLink(copy) {
    const url = copy.split(':')[1].split(',')[0];
    if (url.includes('https-')) {
        const prefixAdded = url.replace('https-', 'https://');
        return prefixAdded;
    }

    return '#!';
}

/**
 * Returns string2 of input following this pattern: {string1:string2,string3}
 * @param copy
 * @returns string
 */
export function getRouterLinkRouteFromCopy(copy) {
    // sample input:      {routerLink:estimate,provide your VIN}
    // first split would return      'estimate,provide your VIN'
    // second split would return     'estimate'
    return copy.split(':')[1].split(',')[0];
}

/**
 * Returns string3 of input following this pattern: { string1: string2, string3 }
 * @param copy
 * @returns string
 */
export function getRouterLinkDisplayTextFromCopy(copy) {
    // sample input:     {routerLink:estimate,provide your VIN}
    // first split would return     'estimate,provide your VIN'
    // second split would return             'provide your VIN'
    return copy.split(':')[1].split(',')[1];
}

/**
 * Returns a router link as an 'a' tag element
 * @param copy
 * @returns string
 */
export function getRouterLinkHtmlStringFromCopy(copy) {
    return `<a href='/?issPage=${getRouterLinkRouteFromCopy(copy)}' class='router-link'>${getRouterLinkDisplayTextFromCopy(copy)}</a>`;
}

/**
 * Returns a phone link as an 'a' tag element
 * @param copy
 * @returns string
 */
export function getPhoneLinkHtmlStringFromPhoneNumber(phoneNumber) {
    return `<a href='tel:${phoneNumber}' class='phone-link'>${phoneNumber}.</a>`;
}

// Copy returned from the CMS that has newlines will return blocks wrapped in
//   <p ... >...</p>
// This function returns an array of each paragraph, works with or without html
//   attributes present
/**
 *
 * @param copy
 */
export function splitCMSCopyOnParagraphTag(copy) {
    // filter removes empty strings that are a result of string.split with regex
    return copy.split(/(?:<p(?:.*?)>)|(?:<\/p>)/g).filter((paragraph) => paragraph !== '');
}
