/*
 * Copyright (C) 2023 Xibo Signage Ltd
 *
 * Xibo - Digital Signage - https://xibosignage.com
 *
 * This file is part of Xibo.
 *
 * Xibo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * Xibo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Xibo.  If not, see <http://www.gnu.org/licenses/>.
 */
const PlayerHelper = function() {
  // Check the query params to see if we're in editor mode
  const self = this;

  this.getPinnedSlots = function(dataSlots) {
    return Object.keys(dataSlots)
      .reduce(function(a, b) {
        const dataSlot = dataSlots[b];
        if (dataSlot.hasPinnedSlot) return [...a, dataSlot.slot];
        return a;
      }, []);
  };

  this.getPinnedItems = function(dataSlotItems) {
    if (Object.values(dataSlotItems).length === 0) {
      return dataSlotItems;
    }

    return Object.keys(dataSlotItems).reduce(function(items, itemKey) {
      const item = dataSlotItems[itemKey];

      if (item.pinSlot) {
        items[itemKey] = item;
      }

      return items;
    }, {});
  };

  /**
   * Get items by Key
   * @param {Object} items
   * @param {String} itemsKey
   * @param {Boolean} isStandalone
   *
   * @return {Array}
   */
  this.getItemsByKey = (items, itemsKey, isStandalone) => {
    if (isStandalone && items.hasOwnProperty(itemsKey) &&
        Object.keys(items[itemsKey]).length > 0
    ) {
      return Object.keys(items[itemsKey]).reduce(function(a, itemKey) {
        return [...a, items[itemsKey][itemKey]];
      }, []);
    }

    if (items.hasOwnProperty(itemsKey)) {
      return items[itemsKey];
    }

    return [];
  };

  /**
   * Gets minimum and maximum slot
   * If minSlot is zero, it means it's not a data slot
   * @param {Array} collection
   * @return {{minSlot: (number|number), maxSlot: (number|number)}}
   */
  this.getMinAndMaxSlot = function(collection) {
    const minValue = 1;
    const getSlots = (items) => items.map(function(elem) {
      return elem?.slot + 1 || 0;
    });
    const minSlot = collection === null ?
      minValue :
      Math.min(...getSlots(collection));
    const maxSlot = collection === null ?
      minValue :
      Math.max(...getSlots(collection));

    return {
      minSlot,
      maxSlot,
    };
  };

  this.getMaxMinSlot = (objectsArray, itemsKey, isStandalone) => {
    const minValue = 1;
    const groupItems = objectsArray?.length > 0 ?
      objectsArray.reduce(
        (a, b) => {
          return [...a, ...self.getItemsByKey(b, itemsKey, isStandalone)];
        }, []) : null;
    const getSlots = (items) => items.map(function(elem) {
      return elem?.slot || 0;
    });
    const minSlot = groupItems === null ?
      minValue :
      Math.min(...getSlots(groupItems)) + 1;
    const maxSlot = groupItems === null ?
      minValue :
      Math.max(...getSlots(groupItems)) + 1;

    return {
      minSlot,
      maxSlot,
    };
  };

  this.isGroup = function(element) {
    return element.hasOwnProperty('groupId');
  };

  this.isMarquee = function(effect) {
    return effect === 'marqueeLeft' ||
      effect === 'marqueeRight' ||
      effect === 'marqueeUp' ||
      effect === 'marqueeDown';
  };

  this.renderElement = function(hbs, props, isStatic) {
    const hbsTemplate = hbs(Object.assign(props, globalOptions));
    let topPos = props.top;
    let leftPos = props.left;
    const hasGroup = Boolean(props.groupId);
    const hasGroupProps = Boolean(props.groupProperties);

    // @NOTE: I think this is deprecated but needs more checking
    if (props.group) {
      if (props.group.isMarquee) {
        topPos = (props.top - props.group.top);
        leftPos = (props.left - props.group.left);
      } else {
        if (props.top >= props.group.top) {
          topPos = (props.top - props.group.top);
        }
        if (props.left >= props.group.left) {
          leftPos = (props.left - props.group.left);
        }
      }
    }

    let cssStyles = {
      height: props.height,
      width: props.width,
      position: 'absolute',
      top: topPos,
      left: leftPos,
      zIndex: props.layer,
      transform: `rotate(${props?.rotation || 0}deg)`,
    };

    if (isStatic) {
      cssStyles = {
        ...cssStyles,
        top: props.top,
        left: props.left,
        zIndex: props.layer,
      };

      if (hasGroup && hasGroupProps) {
        cssStyles.top = (props.top >= props.groupProperties.top) ?
          (props.top - props.groupProperties.top) : 0;
        cssStyles.left = (props.left >= props.groupProperties.left) ?
          (props.left - props.groupProperties.left) : 0;
        cssStyles.zIndex = 'none';
      }
    }

    if (!props.isGroup && props.dataOverride === 'text' &&
      (props.group && props.group.isMarquee) &&
      (props.effect === 'marqueeLeft' || props.effect === 'marqueeRight')
    ) {
      cssStyles = {
        ...cssStyles,
        position: 'static',
        top: 'unset',
        left: 'unset',
        width: props?.textWrap ? props.width : 'initial',
        display: 'flex',
        flexShrink: '0',
        wordWrap: 'break-word',
      };
    }

    const $renderedElem = $(hbsTemplate).first()
      .attr('id', props.elementId)
      .addClass(`${props.uniqueID}--item`)
      .css(cssStyles);

    if (!props.isGroup && props.dataOverride === 'text' &&
      (props.group && props.group.isMarquee) &&
      (props.effect === 'marqueeLeft' || props.effect === 'marqueeRight')
    ) {
      $renderedElem.get(0).style.removeProperty('white-space');
      $renderedElem.get(0).style.setProperty(
        'white-space',
        props?.textWrap ? 'unset' : 'nowrap',
        'important',
      );
    }

    return $renderedElem.prop('outerHTML');
  };

  this.renderDataItem = function(
    isGroup,
    dataItemKey,
    dataItem,
    item,
    slot,
    maxSlot,
    isPinSlot,
    pinnedSlots,
    groupId,
    $groupContent,
    groupObj,
    meta,
    $content,
  ) {
    const $groupContentItem = $(`<div class="${groupId}--item"
      data-group-key="${dataItemKey}"></div>`);
    const groupKey = '.' + groupId + '--item[data-group-key=%key%]';

    // For each data item, parse it and add it to the content;
    if (item.hasOwnProperty('hbs') &&
      typeof item.hbs === 'function' && dataItemKey !== 'empty'
    ) {
      let groupItemStyles = {
        width: groupObj.width,
        height: groupObj.height,
      };

      if (groupObj && groupObj.isMarquee) {
        groupItemStyles = {
          ...groupItemStyles,
          position: 'relative',
          display: 'flex',
          flexShrink: '0',
        };
      }

      $groupContentItem.css(groupItemStyles);

      if ($groupContent &&
        $groupContent.find(
          groupKey.replace('%key%', dataItemKey),
        ).length === 0
      ) {
        $groupContent.append($groupContentItem);
      }

      let isSingleElement = false;

      if (!isGroup && item.dataOverride === 'text' && groupObj.isMarquee) {
        if (item.effect === 'marqueeLeft' || item.effect === 'marqueeRight') {
          if ($groupContent.find(
            groupKey.replace('%key%', dataItemKey)).length === 1
          ) {
            $groupContent.find(
              groupKey.replace('%key%', dataItemKey),
            ).remove();
          }
          isSingleElement = true;
        } else if (item.effect === 'marqueeDown' ||
            item.effect === 'marqueeUp') {
          isSingleElement = false;
        }
      }

      const $itemContainer = isSingleElement ?
        $groupContent : $groupContent.find(
          groupKey.replace('%key%', dataItemKey),
        );
      const props = Object.assign(
        item.templateData,
        {isGroup},
        (String(item.dataOverride).length > 0 &&
        String(item.dataOverrideWith).length > 0) ?
          dataItem : {data: dataItem},
        {group: groupObj},
      );

      // Handle special cases where data field name for override
      // that's the same as template variable
      // E.g. When a dataset column is "text" and the element is using
      // text element, extended or not
      if (props.isExtended) {
        if (props.type === 'dataset' &&
          props.hasOwnProperty('datasetField') &&
          dataItem.hasOwnProperty(props.datasetField)
        ) {
          props[props.dataOverride] = dataItem[props.datasetField];
        } else {
          const extendWith =
            transformer.getExtendedDataKey(props.dataOverrideWith);
          if (props.dataOverride === extendWith &&
            dataItem.hasOwnProperty(extendWith)
          ) {
            props[props.dataOverride] = dataItem[extendWith];
          }
        }
      }

      const $elementContent = $(self.renderElement(
        item.hbs,
        props,
      ));

      // Add style scope to container
      const $elementContentContainer = $('<div>');
      $elementContentContainer.append($elementContent).attr(
        'data-style-scope',
        'element_' +
        props.type + '__' +
        props.id,
      );

      $itemContainer.append(
        $elementContentContainer,
      );

      const itemID = item.uniqueID || item.templateData?.uniqueID;
      // Handle the rendering of the template
      (item.onTemplateRender() !== undefined) && item.onTemplateRender()(
        item.elementId,
        $itemContainer.find(`.${itemID}--item`).parent(),
        dataItem,
        {item, ...item.templateData, data: dataItem},
        meta,
      );
    } else {
      if ($groupContent &&
        $groupContent.find(
          groupKey.replace('%key%', dataItemKey)).length === 0
      ) {
        $groupContent.append($groupContentItem);
      }

      const $itemContainer = $groupContent.find(
        groupKey.replace('%key%', dataItemKey),
      );

      $itemContainer.append('');
    }
  };

  return this;
};

module.exports = new PlayerHelper();
