/*
 *
 * TagList
 *
 */

import PropTypes from 'prop-types';

import React from 'react';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { Tree } from 'antd';
import _ from 'lodash';
import { createStructuredSelector } from 'reselect';
import { bindActionCreators } from 'redux';
import makeSelectTagList from './selectors';
import { UserIsAuthenticated } from '../../utils/authWrapper';
import * as actions from './actions';
import * as globalActions from '../Cap/actions';
import messages, { scope } from './messages';

// import styled from styled-components;
import CapTagList from '../../v2Components/CapTagList';
import './_tagList.scss';
import { selectCurrentOrgDetails, makeSelectFetchingSchemaError, makeSelectFetchingSchema } from '../Cap/selectors';
import {
  handleInjectedData, hasGiftVoucherFeature, hasPromoFeature, hasBadgesFeature, transformBadgeTags,
} from '../../utils/common';
import {
  GIFT_VOUCHER_RELATED_TAGS, PROMO_ENGINE_RELATED_TAGS, BADGES_RELATED_TAGS, BADGES_ENROLL, BADGES_ISSUE,
} from '../../constants/unified';

const {TreeNode} = Tree;

export class TagList extends React.Component { // eslint-disable-line react/prefer-stateless-function
  constructor(props) {
    super(props);
    const { tags, injectedTags } = props;
    const hasInitialData = (tags && tags.length > 0) || !_.isEmpty(injectedTags);
    this.state = {
      loading: false,
      tags: [],
      tagsError: false,
      currentContext: null, // Track current context to detect changes
      hasTriggeredInitialApiCall: hasInitialData, // Seed from initial props to avoid duplicate fetch on popover open
    };
    this.renderTags = this.renderTags.bind(this);
    this.populateTags = this.populateTags.bind(this);
    this.populateTagForChildren = this.populateTagForChildren.bind(this);
    this.transformInjectedTags = this.transformInjectedTags.bind(this);
    this.transformCouponTags = this.transformCouponTags.bind(this);
    this.loadingTimeout = null; // Add timeout reference
  }

  componentDidMount() {
    this.generateTags(this.props);
    // Initial schema fetch is the parent's responsibility (useTagManagement hook handles it)
    // This avoids duplicate requests when both parent and child try to fetch on mount
  }

  componentWillReceiveProps(nextProps) {
    // Set loading to true in these scenarios:
    // 1. Both injectedTags and tags are empty (initial load)
    // 2. Context change is happening (detected by different tag arrays)
    const { injectedTags: currentInjectedTags, tags: currentTags } = this.props;
    const { injectedTags: nextInjectedTags, tags: nextTags, fetchingSchemaError } = nextProps;

    const isInitialLoad = _.isEmpty(currentInjectedTags) && _.isEmpty(currentTags);
    const isContextChange = !_.isEqual(nextTags, currentTags) && !_.isEmpty(currentTags);

    if (isInitialLoad || isContextChange) {
      this.setState({loading: true});
    }

    // Only reset loading for injectedTags changes if we're not currently loading due to context change
    if (!_.isEqual(nextInjectedTags, currentInjectedTags) && !this.state.loading) {
      this.setState({loading: false});
      this.clearLoadingTimeout();
    }

    if (!_.isEqual(nextTags, currentTags)) {
      this.setState({loading: false});
      this.clearLoadingTimeout();
      // Tags received (from prefetch or button-click API call) — mark as triggered
      // so handlePopoverVisibilityChange won't fire a duplicate call on popover open
      if (nextTags && nextTags.length > 0) {
        this.setState({ hasTriggeredInitialApiCall: true });
      }
    }
    if (fetchingSchemaError) {
      this.setState({tagsError: fetchingSchemaError, loading: false});
      this.clearLoadingTimeout();
    }
  }

  componentDidUpdate(prevProps) {
    const {
      tags,
      injectedTags,
      selectedOfferDetails,
      eventContextTags,
      waitEventContextTags,
    } = this.props;
    const {
      tags: prevTags,
      injectedTags: prevInjectedTags,
      selectedOfferDetails: prevSelectedOfferDetails,
      eventContextTags: prevEventContextTags,
      waitEventContextTags: prevWaitEventContextTags,
    } = prevProps;

    if (
      tags !== prevTags
      || injectedTags !== prevInjectedTags
      || selectedOfferDetails !== prevSelectedOfferDetails
      || !_.isEqual(eventContextTags, prevEventContextTags)
      || !_.isEqual(waitEventContextTags, prevWaitEventContextTags)
    ) {
      this.generateTags(this.props);
    }
  }

  componentWillUnmount() {
    this.clearLoadingTimeout();
  }

  clearLoadingTimeout = () => {
    if (this.loadingTimeout) {
      clearTimeout(this.loadingTimeout);
      this.loadingTimeout = null;
    }
  };

  onSelect = (selectedKeys) => {
    const { onTagSelect } = this.props;
    onTagSelect(selectedKeys[0]);
  };

  getTagsforContext = (data) => {
    // Set loading state when context change is requested
    this.setState({loading: true, currentContext: data});

    // Set a timeout to prevent infinite loading (fallback safety)
    this.clearLoadingTimeout();
    this.loadingTimeout = setTimeout(() => {
      this.setState({loading: false});
    }, 5000); // Reduced timeout to 5 seconds for better UX

    const { onContextChange } = this.props;
    // Always call onContextChange if available - this triggers the API call
    // The API call will fetch tags from /meta/TAG endpoint
    if (onContextChange) {
      onContextChange(data);
    } else {
      console.warn('TagList: onContextChange is not available. API call will not be triggered.');
    }
  }

  handlePopoverVisibilityChange = (visible) => {
    // When popover opens, trigger API call if tags are empty or if we haven't triggered it yet
    // This ensures API call happens when user clicks "Add Label" button
    if (visible && this.props.onContextChange) {
      const { tags, injectedTags } = this.props;
      // Check if tags array is empty or if state tags are empty
      const hasNoTags = (!tags || tags.length === 0) && _.isEmpty(injectedTags);
      const hasNoStateTags = _.isEmpty(this.state.tags);
      const hasNotTriggeredApiCall = !this.state.hasTriggeredInitialApiCall;

      // Trigger API call if tags are not loaded yet OR if we haven't triggered it yet
      if ((hasNoTags || hasNoStateTags || hasNotTriggeredApiCall)) {
        // Mark that we've triggered the API call
        this.setState({ hasTriggeredInitialApiCall: true });
        this.getTagsforContext('Outbound'); // Default to Outbound context
      }
    }
  };

  generateTags = (props) => {
    let tags = {};
    let injectedTags = {};
    const eventContextTagsObj = {};

    const {selectedOfferDetails, eventContextTags, waitEventContextTags } = props;
    if (props.injectedTags && !_.isEmpty(props.injectedTags)) {
      const formattedInjectedTags = handleInjectedData(
        props.injectedTags,
        scope
      );
      injectedTags = this.transformInjectedTags(formattedInjectedTags);
    }
    if (props.tags && props.tags.length > 0) {
      tags = this.populateTags(props.tags);
    }
    if (selectedOfferDetails && !_.isEmpty(selectedOfferDetails) ) {
      if (([BADGES_ENROLL, BADGES_ISSUE].includes(selectedOfferDetails[0]?.type)) && (tags && tags?.Badge) ) {
        transformBadgeTags(selectedOfferDetails, tags);
      } else if (tags && tags?.coupon) {
        this.transformCouponTags(selectedOfferDetails, tags);
      }
    }
    // Journey event context tags also should be displayed in the Add Labels.
    if (eventContextTags?.length) {
      const TAG_HEADER_MSG_LABEL = this.props.intl.formatMessage(messages.entryEvent);
      eventContextTagsObj.eventContextTags = {
        "name": TAG_HEADER_MSG_LABEL,
        "desc": TAG_HEADER_MSG_LABEL,
        "resolved": true,
        'tag-header': true,
        "subtags": {},
      };

      eventContextTags.forEach((tag) => {
        const {
          tagName, label, profileId, profileName,
        } = tag || {};
        if (!profileId || !tagName || !label || !profileName) return;
        // Initializing the tags profile if it doesn't exist
        if (!eventContextTagsObj?.eventContextTags?.subtags?.[profileId]) {
          eventContextTagsObj.eventContextTags.subtags[profileId] = {
            "name": profileName,
            "desc": profileName,
            "resolved": true,
            'tag-header': true,
            "subtags": {},
          };
        }
        // Adding the current tag to the profile group
        eventContextTagsObj.eventContextTags.subtags[profileId].subtags[tagName] = {
          name: label,
          desc: label,
          resolved: true,
        };
      });
    }
    // Wait event context tags should be displayed in the Add Labels when node is next to Event based wait node.
    if (waitEventContextTags && Object.keys(waitEventContextTags)?.length) {

      Object.keys(waitEventContextTags).forEach((blockId) => {
      const WAIT_EVENT_HEADER_MSG_LABEL = `${waitEventContextTags[blockId].eventName} (${waitEventContextTags[blockId].blockName})`;
        eventContextTagsObj[blockId] = {
          "name": WAIT_EVENT_HEADER_MSG_LABEL,
          "desc": WAIT_EVENT_HEADER_MSG_LABEL,
          "resolved": true,
          'tag-header': true,
          "subtags": {},
        };

      waitEventContextTags?.[blockId]?.tags?.forEach((tag) => {
        const {
          tagName, label, profileId, profileName, blockName, eventName
        } = tag || {};
        if (!profileId || !tagName || !label || !profileName) return;
        // Initializing the tags profile if it doesn't exist
        if (!eventContextTagsObj?.[blockId]?.subtags?.[profileId]) {
          eventContextTagsObj[blockId].subtags[profileId] = {
            "name": profileName,
            "desc": profileName,
            "resolved": true,
            'tag-header': true,
            "subtags": {},
          };
        }
        // Adding the current tag to the profile group
        eventContextTagsObj[blockId].subtags[profileId].subtags[tagName] = {
          name: label,
          desc: label,
          resolved: true,
        };
      });
    });
    }
    this.setState({tags: _.merge( {}, tags, injectedTags, eventContextTagsObj )});
  }

  populateTags(tagsList) {
    const mainTags = {};
    const excludedTags = [];
    if (!hasPromoFeature()) {
      excludedTags.push(...PROMO_ENGINE_RELATED_TAGS);
      //filtering PROMO_ENGINE_RELATED_TAGS if org does not have PROMO_ENGINE feature enabled
    }
    if (!hasGiftVoucherFeature()) {
      //filtering GIFT_VOUCHER_RELATED_TAGS if org does not have GIFT_CARDS feature enabled
      excludedTags.push(...GIFT_VOUCHER_RELATED_TAGS);
    }
    if (!hasBadgesFeature()) {
      //filtering BADGES_RELATED_TAGS if org does not have BADGES feature enabled
      excludedTags.push(...BADGES_RELATED_TAGS);
    }
    //Form tags object with tag headers
    _.forEach(tagsList, (temp) => {
      const tag = temp.definition;
      const { intl } = this.props;
      const { locale: userLocale } = intl || {};

      // Check if the tag.value should be skipped based on feature control
      if (_.includes(excludedTags, tag.value)) {
        return; // Skip processing this tag
      }
      if (!tag['tag-header']) {
        mainTags[tag.value] = {
          name: tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
          desc: tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
        };
      } else if (tag['tag-header'] && mainTags[tag.value]) {
        mainTags[tag.value].subtags = _.concat(mainTags[tag.value].subtags, tag.subtags);
      } else if (!mainTags[tag.value]) {
        mainTags[tag.value] = {
          'tag-header': true,
          "name": tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
          "desc": tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
          "subtags": tag?.subtags,
        };
      }
    });

    const mainTagsCloned = _.cloneDeep(mainTags);
    //Insert subtags in tag headers and remove them from top level
    const result = {};
    _.forEach(mainTagsCloned, (tag, key) => {
      if (tag['tag-header']) {
        const tagWithChildren = this.populateTagForChildren(tag, mainTagsCloned, key);
        tagWithChildren.resolved = true;
        result[key] = tagWithChildren;
      } else {
        result[key] = tag;
      }
    });

    //Delete extra tag headers
    _.forEach(result, (tag) => {
      if (tag['tag-header']) {
        _.forEach(tag.subtags, (subtag, key) => {
          if (result[key]) {
            result[key].deleted = true;
          }
        });
      }
    });
    //Actually delete them
    _.forEach(result, (tag, key) => {
      if (tag.deleted) {
        delete result[key];
      }
    });
    return result;
  }

  populateTagForChildren(targetTag, tagsObject, tagValue) {
    const tag = targetTag;
    if (tag && tag['tag-header'] && !tag.resolved) {
      const tempSubTags = {};
      _.forEach(tag.subtags, (subtag) => {
        if (tagsObject[subtag]) {
          const children = this.populateTagForChildren(tagsObject[subtag], tagsObject, subtag);
          children.resolved = true;
          tempSubTags[subtag] = children;
        }
      });
      tag.subtags = tempSubTags;
      return tag;
    }
    return tagsObject[tagValue];
  }

  transformCouponTags(offerDetails, allTags) {
    const couponTags = {};
    const couponTagsKeys = Object.keys(_.get(allTags, 'coupon.subtags', []));
    const withCouponId = (obj, id) => {
      const tempObj = {};
      if (obj.subtags) {
        tempObj.subtags = _.mapValues(obj.subtags, (tag) => ({
          ...withCouponId(tag, id),
        }));
      }
      return {
        ...obj,
        ...tempObj,
        incentiveSeriesId: id,
      };
    };
    offerDetails.forEach((offer) => {
      const {
        id, couponName, description, couponSeriesId,
      } = offer;
      const couponId = id || couponSeriesId;
      const couponSubTags = {};
      couponTagsKeys.forEach((couponTagKey) => {
        const couponSubTagsObj = withCouponId(allTags.coupon.subtags[couponTagKey], couponId);
        couponSubTags[couponTagKey] = couponSubTagsObj;
      });
      const couponNameFinal = couponName || description;
      const key = couponNameFinal || couponId;
      couponTags[key] = {
        'tag-header': true,
        'name': couponNameFinal || couponId,
        'desc': couponNameFinal || couponId,
        'incentiveSeriesId': couponId,
        'subtags': couponSubTags,
        'resolved': true,
        'couponTags': true,
      };
    });
    // eslint-disable-next-line no-param-reassign
    allTags.coupon.subtags = couponTags;
  }

  transformInjectedTags(tags) {
    _.forEach(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 = _.cloneDeep(temp[subKey]);
          delete temp[subKey];
        }
        temp.subtags = this.transformInjectedTags(temp.subtags);
      }
    });
    return tags;
  }

  renderTags(tags) {
    const list = [];
    _.forEach(tags, (val, key) => {
      if (_.has(val, 'subtags')) {
        const temp = this.renderTags(val.subtags);
        list.push((
          <TreeNode title={val.name} key={key}>
            {temp}
          </TreeNode>
        ));
      } else {
        const tempNode = (<TreeNode title={val.name} key={key}></TreeNode>);
        list.push(tempNode);
      }
    });
    return list;
  }

  render() {
    const { restrictPersonalization, disabled, disableTooltipMsg, intl } = this.props;
    
    // Compute disabled state and tooltip message
    let isDisabled = disabled || false;
    let tooltipMsg = disableTooltipMsg;
    
    if (restrictPersonalization && !disabled) {
      isDisabled = true;
      tooltipMsg = intl.formatMessage(messages.personalizationNotSupportedAnonymous);
    }
    
    return (
      <div className={this.props.className ? this.props.className : ''}>
        <CapTagList
          loading={this.state.loading}
          tags={this.state.tags}
          onSelect={this.onSelect}
          label={this.props.label}
          visibleTaglist={this.props.visibleTaglist}
          hidePopover={this.props.hidePopover}
          onContextChange={this.getTagsforContext}
          onVisibleChange={this.handlePopoverVisibilityChange}
          moduleFilterEnabled={this.props.moduleFilterEnabled}
          modalProps={this.props.modalProps}
          currentOrgDetails={this.props.currentOrgDetails}
          channel={this.props.channel}
          disabled={isDisabled}
          // custom tooltip message to show when disabled
          disableTooltipMsg={tooltipMsg}
          fetchingSchemaError={this?.state?.tagsError}
          popoverPlacement={this.props.popoverPlacement}
        />
      </div>
    );
  }
}

TagList.defaultProps = {
  isNewVersionFlow: false,
  userLocale: 'en',
  eventContextTags: [],
  waitEventContextTags: {},
};

TagList.propTypes = {
  onTagSelect: PropTypes.func,
  tags: PropTypes.array,
  injectedTags: PropTypes.object,
  label: PropTypes.string,
  onContextChange: PropTypes.func,
  moduleFilterEnabled: PropTypes.bool,
  className: PropTypes.string,
  userLocale: PropTypes.string,
  selectedOfferDetails: PropTypes.array,
  visibleTaglist: PropTypes.bool,
  hidePopover: PropTypes.bool,
  modalProps: PropTypes.any,
  currentOrgDetails: PropTypes.object,
  channel: PropTypes.string,
  disabled: PropTypes.bool,
  fetchingSchemaError: PropTypes.bool,
  eventContextTags: PropTypes.array,
  waitEventContextTags: PropTypes.object,
  popoverPlacement: PropTypes.string,
  // message to show when Add Label button is disabled (e.g. personalization restriction)
  disableTooltipMsg: PropTypes.string,
  restrictPersonalization: PropTypes.bool,
  intl: PropTypes.shape({
    formatMessage: PropTypes.func.isRequired,
    locale: PropTypes.string,
  }).isRequired,
};

const mapStateToProps = createStructuredSelector({
  TagList: makeSelectTagList(),
  currentOrgDetails: selectCurrentOrgDetails(),
  fetchingSchemaError: makeSelectFetchingSchemaError(),
  fetchingSchema: makeSelectFetchingSchema(),
});

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(actions, dispatch),
    globalActions: bindActionCreators(globalActions, dispatch),
  };
}

export default UserIsAuthenticated(connect(mapStateToProps, mapDispatchToProps)(injectIntl(TagList)));
