/*
 * This file is to maintain the tag validations
 * across the forms used in creatives, very
 * similar to the FormBuilder component.
 * The motive is to chizzling out the dependency from
 * FormBuilder component into meaningful blocks
*/

import lodashForEach from 'lodash/forEach';
import lodashCloneDeep from 'lodash/cloneDeep';
import { ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS, TAG_CONTENT_REGEX, UNSUBSCRIBE_TAG, UNSUBSCRIBE_TAG_REGEX } from '../constants/unified';

const DEFAULT = 'default';
const SUBTAGS = 'subtags';

export const hasUnsubscribeTag = (content) =>
  typeof content === 'string' && UNSUBSCRIBE_TAG_REGEX.test(content);

/**
 * Shared core for tag validation. Used by validateTags (tagValidations) and FormBuilder.validateTags.
 * @param {Object} params
 * @param {string} params.contentForBraceCheck - String to run validateIfTagClosed on.
 * @param {string} params.contentForUnsubscribeScan - String to scan for {{...}} unsubscribe variants.
 * @param {Array} [params.tags] - Tag definitions (for definition-based missing tags).
 * @param {string} [params.currentModule] - Module context (e.g. 'default', 'outbound').
 * @param {boolean} [params.isFullMode] - If true, skip unsubscribe checks.
 * @param {Array} [params.initialMissingTags=null] - If set, use instead of computing from definitions.
 * @param {function} [params.skipTagsFn=skipTags] - skipTags implementation.
 * @param {boolean} [params.includeIsContentEmpty=false] - If true, response includes isContentEmpty: false.
 * @returns {{ valid: boolean, missingTags: string[], isBraceError: boolean }}
 */
export const validateTagsCore = ({
  contentForBraceCheck,
  contentForUnsubscribeScan,
  tags,
  currentModule,
  isFullMode,
  initialMissingTags = null,
  skipTagsFn = skipTags,
  includeIsContentEmpty = false,
}) => {
  const response = {
    valid: true,
    missingTags: [],
    isBraceError: false,
    ...(includeIsContentEmpty && { isContentEmpty: false }),
  };

  if (tags?.length && !isFullMode) {
    if (initialMissingTags == null) {
      // Definition-based: same as original tagValidations (when caller does not pass initialMissingTags)
      lodashForEach(tags, ({
        definition: {
          supportedModules,
          value,
        },
      }) => {
        if (value === UNSUBSCRIBE_TAG) {
          lodashForEach(supportedModules, (module) => {
            if (module.mandatory && (currentModule === module.context)) {
              if (!hasUnsubscribeTag(contentForUnsubscribeScan)) {
                response.missingTags.push(value);
              }
            }
          });
        }
      });
    } else {
      response.missingTags = [...initialMissingTags];
    }

    TAG_CONTENT_REGEX.lastIndex = 0;
    let match = TAG_CONTENT_REGEX.exec(contentForUnsubscribeScan);
    while (match !== null) {
      const tagValue = match[1]?.trim();
      const ifSkipped = skipTagsFn(tagValue);
      if (ifSkipped && tagValue?.indexOf(UNSUBSCRIBE_TAG) !== -1) {
        const missingTagIndex = response?.missingTags?.indexOf(UNSUBSCRIBE_TAG);
        if (missingTagIndex !== -1) {
          response?.missingTags?.splice(missingTagIndex, 1);
        }
      }
      match = TAG_CONTENT_REGEX.exec(contentForUnsubscribeScan);
    }

    if (response?.missingTags?.length > 0) {
      response.valid = false;
    }
  }

  response.isBraceError = !validateIfTagClosed(contentForBraceCheck);
  if (response.isBraceError) {
    response.valid = false;
  } else if (response?.missingTags?.length > 0) {
    response.valid = false;
  }
  return response;
};

/**
 * Validates the tags based on the provided parameters.
 * @param {Object} params - The parameters for tag validation.
 * @param {string} params.content - Content to validate.
 * @param {Array} [params.tagsParam] - Tag definitions.
 * @param {Object} [params.location] - Location with query.module.
 * @param {string} [params.tagModule] - Override for current module context.
 * @param {boolean} [params.isFullMode] - If true, skip unsubscribe checks.
 * @returns {{ valid: boolean, missingTags: string[], isBraceError: boolean }}
 */
export const validateTags = ({
  content,
  tagsParam,
  location,
  tagModule,
  isFullMode,
}) => {
  const tags = tagsParam;
  let currentModule = location?.query?.module ? location?.query?.module : DEFAULT;
  if (tagModule) {
    currentModule = tagModule;
  }
  return validateTagsCore({
    contentForBraceCheck: content,
    contentForUnsubscribeScan: content,
    tags,
    currentModule,
    isFullMode,
  });
};

/**
 * Checks if the given tag is supported based on the injected tags.
 * @param {string} checkingTag - The tag to check.
 * @param {Object} injectedTags - The injected tags.
 * @returns {boolean} - True if the tag is supported, false otherwise.
 */
export const checkIfSupportedTag = (checkingTag, injectedTags) => {
  let result = false;
  lodashForEach(injectedTags, (tag, key) => {
    if (tag.subtags && checkIfSupportedTag(checkingTag, tag.subtags)) {
      result = true;
    } else if (checkingTag === key) {
      result = true;
    }
  });

  return result;
};

const indexOfEnd = (targetString, string) => {
  const io = targetString.indexOf(string);
  return io == -1 ? -1 : io + string.length;
};

export const skipTags = (tag) => {
  // If the tag contains the word "entryTrigger.", then it's an event context tag and should not be skipped.
  if (tag?.match(ENTRY_TRIGGER_TAG_REGEX)) {
    return false;
  }
  // Use some() to check if any pattern matches (stops on first match)
  return SKIP_TAGS_REGEX_GROUPS.some((group) => {
    // Create a new RegExp for each test to avoid state issues with global flag
    const groupRegex = new RegExp(group);
    return groupRegex.test(tag);
  });
};

export const transformInjectedTags = (tags) => {
  lodashForEach(tags, (tag) => {
    const temp = tag;
    let subKey = '';
    Object.keys(tag).map( (k) => {
      if (k.indexOf(SUBTAGS) !== -1) {
        subKey = k;
      }
      return true;
    });
    if (subKey !== '') {
      temp['tag-header'] = true;
      if (subKey !== SUBTAGS) {
        temp.subtags = lodashCloneDeep(temp[subKey]);
        delete temp[subKey];
      }
      temp.subtags = transformInjectedTags(temp.subtags);
    }
  });
  return tags;
};

//checks if the opening curly brackets have corresponding closing brackets
export const validateIfTagClosed = (value) => {
  if (value.includes("{{{{") || value.includes("}}}}")) {
    return false;
  }
  const regex1 = /{{.*?}}/g;
  const regex2 = /{{/g;
  const regex3 = /}}/g;

  const l1 = value.match(regex1)?.length;
  const l2 = value.match(regex2)?.length;
  const l3 = value.match(regex3)?.length;

  return (l1 == l2 && l2 == l3 && l1 == l3);
};

//replaces encoded string with their respective characters
export const preprocessHtml = (content) => {
  const replacements = {
    "&#39;": "'",
    "&quot;": '"',
    "&amp;": "&",
    "&lt;": "<",
    "&gt;": ">",
    "\n": "", // Handling newlines by replacing them with an empty string
  };


  const styleTagRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;

  const contentWithStyleFixes = content?.replace(styleTagRegex, (match, styleContent) => {
    // Replace all occurrences of %} with % } within the style content
    const modifiedStyleContent = styleContent?.replace(/%}/g, '% }');
    // Reconstruct the <style> tag with the modified content
    return match.replace(styleContent, modifiedStyleContent);
  });

  // Step 2: Perform the standard replacements on the entire content
  return contentWithStyleFixes?.replace(/&#39;|&quot;|&amp;|&lt;|&gt;|\n/g, (match) => replacements[match]);
};

//this is used to get the subtags from custom or extended tags
export const getTagMapValue = (object = {}) => Object.values(
  object
).reduce((acc, current) => ({ ...acc, ...current?.subtags ?? {} }), {});

export const getLoyaltyTagsMapValue = (object = {}) => Object.entries(object).reduce((acc, [key, current]) => {
  if (current?.subtags && Object.keys(current.subtags).length > 0) {
    // If subtags exist → merge them
    return { ...acc, ...(current.subtags ?? {}) };
  }
  // If no subtags → keep the tag itself
  return { ...acc, [key]: current };
}, {});


/**
 * Extracts and merges all subtags and top-level keys from the provided object into a single flat map.
 *
 * @param {Object} object - The input object containing top-level keys with optional subtags.
 * @returns {Object} - A flat map containing all top-level keys and their subtags.
 */
export const getForwardedMapValues = (object = {}) => Object?.entries(object)?.reduce((acc, [key, current]) => {
  // Check if current has 'subtags' and it's an object
  if (current && current?.subtags && typeof current?.subtags === 'object') {
    // Add the top-level key with its 'name' and 'desc'
    acc[key] = {
      name: current?.name,
      desc: current?.desc,
    };

    // Merge the subtags into the accumulator
    acc = { ...acc, ...current?.subtags };
  } else if (current && typeof current === 'object') {
    // If no 'subtags', add the top-level key with its 'name' and 'desc'
    acc[key] = {
      name: current?.name,
      desc: current?.desc,
    };
  }

  // If the current entry is not an object or lacks 'name'/'desc', skip it
  return acc;
}, {});
