/*
 * Tencent is pleased to support the open source community by making TMagicEditor available.
 *
 * Copyright (C) 2023 THL A29 Limited, a Tencent company.  All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import serialize from 'serialize-javascript';

import type { Id, MApp, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema';
import { NodeType } from '@tmagic/schema';
import type StageCore from '@tmagic/stage';
import { calcValueByFontsize, getElById, getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils';

import { Layout } from '@editor/type';

export const COPY_STORAGE_KEY = '$MagicEditorCopyData';
export const COPY_CODE_STORAGE_KEY = '$MagicEditorCopyCode';
export const COPY_DS_STORAGE_KEY = '$MagicEditorCopyDataSource';

/**
 * 获取所有页面配置
 * @param root DSL跟节点
 * @returns 所有页面配置
 */
export const getPageList = (root?: MApp | null): MPage[] => {
  if (!root) return [];
  if (!Array.isArray(root.items)) return [];
  return root.items.filter((item) => isPage(item)) as MPage[];
};

export const getPageFragmentList = (root?: MApp | null): MPageFragment[] => {
  if (!root) return [];
  if (!Array.isArray(root.items)) return [];
  return root.items.filter((item) => isPageFragment(item)) as MPageFragment[];
};

/**
 * 获取所有页面名称
 * @param pages 所有页面配置
 * @returns 所有页面名称
 */
export const getPageNameList = (pages: (MPage | MPageFragment)[]): string[] =>
  pages.map((page) => page.name || 'index');

/**
 * 新增页面时，生成页面名称
 * @param {Object} pageNameList 所有页面名称
 * @returns {string}
 */
export const generatePageName = (pageNameList: string[], type: NodeType.PAGE | NodeType.PAGE_FRAGMENT): string => {
  let pageLength = pageNameList.length;

  if (!pageLength) return `${type}_index`;

  let pageName = `${type}_${pageLength}`;
  while (pageNameList.includes(pageName)) {
    pageLength += 1;
    pageName = `${type}_${pageLength}`;
  }

  return pageName;
};

/**
 * 新增页面时，生成页面名称
 * @param {Object} app 所有页面配置
 * @returns {string}
 */
export const generatePageNameByApp = (app: MApp, type: NodeType.PAGE | NodeType.PAGE_FRAGMENT): string =>
  generatePageName(getPageNameList(type === 'page' ? getPageList(app) : getPageFragmentList(app)), type);

/**
 * @param {Object} node
 * @returns {boolean}
 */
export const isFixed = (node: MNode): boolean => node.style?.position === 'fixed';

export const getNodeIndex = (id: Id, parent: MContainer | MApp): number => {
  const items = parent?.items || [];
  return items.findIndex((item: MNode) => `${item.id}` === `${id}`);
};

export const getRelativeStyle = (style: Record<string, any> = {}): Record<string, any> => ({
  ...style,
  position: 'relative',
  top: 0,
  left: 0,
});

const getMiddleTop = (node: MNode, parentNode: MNode, stage: StageCore | null) => {
  let height = node.style?.height || 0;

  if (!stage || typeof node.style?.top !== 'undefined' || !parentNode.style) return node.style?.top;

  if (!isNumber(height)) {
    height = 0;
  }

  const { height: parentHeight } = parentNode.style;
  // wrapperHeight 是未 calcValue的高度, 所以要将其calcValueByFontsize一下, 否则在pad or pc端计算的结果有误
  const { scrollTop = 0, wrapperHeight } = stage.mask;
  const wrapperHeightDeal = calcValueByFontsize(stage.renderer.getDocument()!, wrapperHeight);
  const scrollTopDeal = calcValueByFontsize(stage.renderer.getDocument()!, scrollTop);
  if (isPage(parentNode)) {
    return (wrapperHeightDeal - height) / 2 + scrollTopDeal;
  }

  // 如果容器的元素高度大于当前视口高度的2倍, 添加的元素居中位置也会看不见, 所以要取最小值计算
  return (Math.min(parentHeight, wrapperHeightDeal) - height) / 2;
};

export const getInitPositionStyle = (style: Record<string, any> = {}, layout: Layout) => {
  if (layout === Layout.ABSOLUTE) {
    const newStyle: Record<string, any> = {
      ...style,
      position: 'absolute',
    };

    if (typeof newStyle.left === 'undefined' && typeof newStyle.right === 'undefined') {
      newStyle.left = 0;
    }

    return newStyle;
  }

  if (layout === Layout.RELATIVE) {
    return getRelativeStyle(style);
  }

  return style;
};

export const setChildrenLayout = (node: MContainer, layout: Layout) => {
  node.items?.forEach((child: MNode) => {
    setLayout(child, layout);
  });
  return node;
};

export const setLayout = (node: MNode, layout: Layout) => {
  if (isPop(node)) return;

  const style = node.style || {};

  // 是 fixed 不做处理
  if (style.position === 'fixed') return;

  if (layout !== Layout.RELATIVE) {
    style.position = 'absolute';
  } else {
    node.style = getRelativeStyle(style);
    node.style.right = 'auto';
    node.style.bottom = 'auto';
  }
  return node;
};

export const change2Fixed = (node: MNode, root: MApp) => {
  const path = getNodePath(node.id, root.items);
  const offset = {
    left: 0,
    top: 0,
  };

  path.forEach((value) => {
    offset.left = offset.left + globalThis.parseFloat(value.style?.left || 0);
    offset.top = offset.top + globalThis.parseFloat(value.style?.top || 0);
  });

  return {
    ...(node.style || {}),
    ...offset,
  };
};

export const Fixed2Other = async (
  node: MNode,
  root: MApp,
  getLayout: (parent: MNode, node?: MNode) => Promise<Layout>,
) => {
  const path = getNodePath(node.id, root.items);
  const cur = path.pop();
  const offset = {
    left: cur?.style?.left || 0,
    top: cur?.style?.top || 0,
    right: '',
    bottom: '',
  };

  path.forEach((value) => {
    offset.left = offset.left - globalThis.parseFloat(value.style?.left || 0);
    offset.top = offset.top - globalThis.parseFloat(value.style?.top || 0);
  });
  const style = node.style || {};

  const parent = path.pop();
  if (!parent) {
    return getRelativeStyle(style);
  }

  const layout = await getLayout(parent);
  if (layout !== Layout.RELATIVE) {
    return {
      ...style,
      ...offset,
      position: 'absolute',
    };
  }

  return getRelativeStyle(style);
};

export const getGuideLineFromCache = (key: string): number[] => {
  if (!key) return [];

  const guideLineCacheData = globalThis.localStorage.getItem(key);
  if (guideLineCacheData) {
    try {
      return JSON.parse(guideLineCacheData) || [];
    } catch (e) {
      console.error(e);
    }
  }

  return [];
};

export const fixNodeLeft = (config: MNode, parent: MContainer, doc?: Document) => {
  if (!doc || !config.style || !isNumber(config.style.left)) return config.style?.left;

  const el = getElById()(doc, `${config.id}`);
  const parentEl = getElById()(doc, `${parent.id}`);

  const left = Number(config.style?.left) || 0;
  if (el && parentEl) {
    const calcParentOffsetWidth = calcValueByFontsize(doc, parentEl.offsetWidth);
    const calcElOffsetWidth = calcValueByFontsize(doc, el.offsetWidth);
    if (calcElOffsetWidth + left > calcParentOffsetWidth) {
      return calcParentOffsetWidth - calcElOffsetWidth;
    }
  }

  return config.style.left;
};

export const fixNodePosition = (config: MNode, parent: MContainer, stage: StageCore | null) => {
  if (config.style?.position !== 'absolute') {
    return config.style;
  }

  return {
    ...(config.style || {}),
    top: getMiddleTop(config, parent, stage),
    left: fixNodeLeft(config, parent, stage?.renderer.contentWindow?.document),
  };
};

// 序列化配置
export const serializeConfig = (config: any) =>
  serialize(config, {
    space: 2,
    unsafe: true,
  }).replace(/"(\w+)":\s/g, '$1: ');

export interface NodeItem {
  items?: NodeItem[];
  [key: string]: any;
}

export const traverseNode = <T extends NodeItem = NodeItem>(
  node: T,
  cb: (node: T, parents: T[]) => void,
  parents: T[] = [],
) => {
  cb(node, parents);

  if (Array.isArray(node.items) && node.items.length) {
    parents.push(node);
    node.items.forEach((item) => {
      traverseNode(item as T, cb, [...parents]);
    });
  }
};

export const moveItemsInContainer = (sourceIndices: number[], parent: MContainer, targetIndex: number) => {
  sourceIndices.sort((a, b) => a - b);
  for (let i = sourceIndices.length - 1; i >= 0; i--) {
    const sourceIndex = sourceIndices[i];
    if (sourceIndex === targetIndex) {
      continue;
    }
    const [item] = parent.items.splice(sourceIndex, 1);
    parent.items.splice(sourceIndex < targetIndex ? targetIndex - 1 : targetIndex, 0, item);

    // 更新后续源索引（因为数组已经改变）
    for (let j = i - 1; j >= 0; j--) {
      if (sourceIndices[j] >= targetIndex) {
        sourceIndices[j] += 1;
      }
    }
  }
};
