<template>
  <div class="graphics">
    <div class="input-search">
      <div class="btn">
        <t-icon name="search" />
      </div>
      <t-input placeholder="搜索" />
    </div>
    <div class="groups-panel">
      <div class="groups">
        <div
          v-for="group in groups"
          :class="group.name === activedGroup ? 'active' : ''"
          @click="groupChange(group.name)"
        >
          <t-icon :name="group.icon" />
          {{ group.name }}
        </div>
      </div>
      <div class="list" :class="groupType ? 'two-columns' : ''">
        <div v-if="activedGroup === '我的'" class="px-16 mt-12 mb-8 ml-4">
          <a @click="onCreateFolder">+ 新建文件夹</a>
        </div>
        <t-collapse
          v-if="groupType < 2"
          v-model:value="activedPanel"
          @change="onChangeGroupPanel"
        >
          <t-collapse-panel
            :value="item.name"
            v-for="item in subGroups"
            :key="item.name"
          >
            <template #header>
              <div class="flex middle mr-8">
                <div v-if="item.edited" @click.stop>
                  <t-input
                    v-model="item.label"
                    style="width: 140px"
                    @blur="createFoder"
                    @enter="createFoder"
                    @keyup="onKeyHeader"
                  />
                </div>
                <div v-else class="ellipsis" style="width: 140px">
                  {{ item.name }}
                </div>
              </div>
            </template>
            <template #headerRightContent v-if="item.canEdited">
              <t-space size="small" @click.stop>
                <t-icon
                  name="edit"
                  class="hover mr-4"
                  @click="onEditHeader(item)"
                />

                <t-popconfirm
                  content="确认删除该文件夹吗"
                  placement="left"
                  @confirm=""
                >
                  <t-icon name="delete" class="hover" />
                </t-popconfirm>
              </t-space>
            </template>
            <div v-if="item.loading">
              <t-loading
                text="加载中..."
                size="small"
                style="margin-left: 32px; margin-bottom: 4px"
              />
            </div>
            <template v-else>
              <div
                class="graphic"
                v-for="elem in item.list"
                :draggable="elem.draggable !== false"
                @dragstart="dragStart($event, elem)"
                @drag="drag($event, elem)"
                @dragend="dragEnd()"
                @click.stop="dragStart($event, elem)"
                @dblclick.stop="open(elem)"
                @contextmenu="onContextMenu($event, item, elem)"
                :title="elem.draggable === false ? '双击打开' : '拖拽到画布'"
              >
                <t-image
                  v-if="elem.image"
                  :src="elem.image"
                  :lazy="true"
                  fit="contain"
                />
                <div class="svg-box" v-else-if="elem.svg" v-html="elem.svg" />
                <svg v-else class="l-icon" aria-hidden="true">
                  <use :xlink:href="'#' + elem.icon"></use>
                </svg>
                <p :title="elem.name">{{ elem.name }}</p>
                <div class="price" v-if="elem.price > 0">
                  ￥{{ elem.price }}
                </div>
              </div>
              <div
                v-if="!item.list || !item.list.length"
                class="gray center"
                style="white-space: nowrap; margin-left: 32px"
              >
                暂无数据，待更新
              </div>
            </template>
          </t-collapse-panel>
        </t-collapse>
        <div v-else class="t-collapse-panel__content" style="padding: 8px">
          <div
            class="graphic"
            v-for="elem in subGroups"
            :draggable="elem.draggable !== false"
            @dragstart="dragStart($event, elem)"
            @drag="drag($event, elem)"
            @dragend="dragEnd()"
            @click.stop="dragStart($event, elem)"
            @dblclick.stop="open(elem)"
            :title="elem.draggable === false ? '双击打开' : '拖拽到画布'"
          >
            <t-image
              v-if="elem.image"
              :src="elem.image"
              :lazy="true"
              fit="contain"
            />
            <div class="svg-box" v-else-if="elem.svg" v-html="elem.svg" />
            <svg v-else class="l-icon" aria-hidden="true">
              <use :xlink:href="'#' + elem.icon"></use>
            </svg>
            <p :title="elem.name">{{ elem.name }}</p>
            <div class="price" v-if="elem.price > 0">￥{{ elem.price }}</div>
          </div>
          <div
            v-if="!subGroups.length"
            class="gray center"
            style="white-space: nowrap; margin-left: 32px"
          >
            暂无数据，待更新
          </div>
        </div>
      </div>
    </div>

    <div
      class="context-menu-box"
      ref="contextmenuDom"
      v-if="contextmenu.visible"
      tabindex="0"
      :style="contextmenu.style"
      @blu1r="contextmenu.visible = false"
    >
      <t-menu class="context-menu" @change="onMenu" expandType="popup">
        <t-submenu
          value="move"
          title="移动到"
          v-if="contextmenu.subMenus.length"
        >
          <t-menu-item
            v-for="subMenu in contextmenu.subMenus"
            :value="'move:' + subMenu.name"
          >
            {{ subMenu.name }}
          </t-menu-item>
        </t-submenu>
        <t-menu-item value="edit"> 编辑 </t-menu-item>
        <t-menu-item value="del"> 删除 </t-menu-item>
      </t-menu>
    </div>

    <t-dialog
      v-if="delDialog.show"
      theme="danger"
      header="删除"
      :visible="true"
      @close="delDialog.show = false"
      @confirm="delComponet"
    >
      确定删除该数据吗？删除后不可恢复！
    </t-dialog>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, onUnmounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import axios from 'axios';

import { cases, shapes, charts, formComponents } from '@/services/defaults';
import { getFolders, getFiles, getIcons } from '@/services/png';
import {
  getComponents,
  getComponentsList,
  getLe5leV,
  updateCollection,
} from '@/services/api';
import { convertPen } from '@/services/upgrade';
import { deepClone } from '@meta2d/core';
import { isGif } from '@/services/utils';
import { autoSave, delAttrs } from '@/services/common';
import { MessagePlugin } from 'tdesign-vue-next';

const router = useRouter();

const activedGroup = ref('');

const groups = reactive([
  {
    icon: 'desktop',
    name: '场景',
    key: '',
    class: 'tow',
  },
  {
    icon: 'root-list',
    name: '模板',
    key: '',
  },
  {
    icon: 'chart',
    name: '图表',
    key: 'chart',
  },
  {
    icon: 'image',
    name: '素材',
    key: '',
  },
  {
    icon: 'control-platform',
    name: '图元',
    key: '',
  },
  {
    icon: 'relativity',
    name: '控件',
    key: '',
  },
  {
    icon: 'chart-bubble',
    name: '图形',
    key: 'shape',
  },
  {
    icon: 'app',
    name: '我的',
    key: '',
  },
]);
const subGroups = ref<any[]>([]);
const groupType = ref(0);
const activedPanel = ref([]);

const caseCaches = reactive<any>({});
const templates = ref([]);
const materials = ref([]);
const pngs = ref([]);
const icons = ref([]);

const groupChange = async (name: string) => {
  activedPanel.value = [];
  activedGroup.value = name;
  groupType.value = 0;
  switch (name) {
    case '场景':
      groupType.value = 1;
      subGroups.value = cases;
      subGroups.value[0].loading = true;
      if (!caseCaches[name + cases[0].name]) {
        caseCaches[name + cases[0].name] = await getCaseProjects(
          name,
          cases[0].name
        );
      }
      subGroups.value[0].list = caseCaches[name + cases[0].name];
      subGroups.value[0].loading = false;
      break;
    case '模板':
      groupType.value = 2;
      if (!templates.value.length) {
        templates.value = await getCaseProjects(name);
      }
      subGroups.value = templates.value;
      break;
    case '图表':
      subGroups.value = charts;
      break;
    case '控件':
      subGroups.value = formComponents;
      break;
    case '素材':
      groupType.value = 2;
      if (!materials.value.length) {
        materials.value = await getFiles('material/');
      }
      subGroups.value = materials.value;
      break;
    case '图元':
      subGroups.value = [];
      if (!pngs.value.length) {
        pngs.value = await getFolders('png');
      }
      subGroups.value.push(...pngs.value);
      if (!icons.value.length) {
        icons.value = await getFolders('svg');
      }
      subGroups.value.push(...icons.value);
      onChangeGroupPanel([subGroups.value[0].name]);
      break;
    case '图形':
      subGroups.value = shapes;
      break;
    case '我的':
      subGroups.value = await getPrivateGroups();
      groupType.value = 1;
      onChangeGroupPanel([subGroups.value[0].name]);
      break;
  }
  activedPanel.value = [subGroups.value[0].name];
};

const getCaseProjects = async (name: string, group?: string) => {
  const query: any = { tags: name };
  if (group) {
    query.case = group;
  }
  const ret: any = await axios.post(
    '/api/data/le5leV/list?current=1&pageSize=100',
    {
      query,
      shared: 'true',
      projection: { _id: 1, name: 1, image: 1, price: 1 },
    }
  );

  if (!ret) {
    return [];
  }
  for (const item of ret.list) {
    item.draggable = false;
  }
  return ret.list;
};

const getPrivateGroups = async () => {
  const list = [
    {
      name: '我的组件',
      list: [],
    },
  ];
  const config = {
    params: {
      current: 1,
      pageSize: 1000,
    },
  };
  let ret: any = await axios.post(
    '/api/data/folders/list',
    {
      projection: {
        image: 1,
        _id: 1,
        name: 1,
        list: 1,
      },
      query: {
        type: `le5leV-components`,
      },
      sort: { createdAt: 1 },
    },
    config
  );
  if (!ret) {
    ret = { list: [] };
  }

  for (const item of ret.list) {
    item.canEdited = true;
  }

  list.push(...ret.list);
  list.push({
    name: '3D',
    list: [],
  });

  return list;
};

const dragStart = async (event: DragEvent | MouseEvent, item: any) => {
  if (
    !item ||
    item.draggable === false ||
    (event instanceof DragEvent && !event.dataTransfer)
  ) {
    return;
  }
  let data = null;
  if (item['3d']) {
    data = {
      name: 'iframe',
      width: 400,
      height: 300,
      externElement: true,
      iframe: 'https://view3d.le5le.com/?id=' + (item._id || item.id),
    };
  } else {
    if (item._id && !item.componentDatas) {
      let res: any = await getComponents(item._id);
      item.component = true;
      item.componentDatas = res.componentDatas;
      item.componentData = res.componentData;
    }
    if (!item.data && !item.component && item.image) {
      let target: any = event.target;
      target.children[0] && (target = target.children[0].children[0]);
      // firefox naturalWidth svg 图片 可能是 0
      const normalWidth = target.naturalWidth || target.width;
      const normalHeight = target.naturalHeight || target.height;
      const [name, lockedOnCombine] = isGif(item.image)
        ? ['gif', 0]
        : ['image', undefined];

      data = {
        name,
        width: 100,
        height: 100 * (normalHeight / normalWidth),
        image: item.image,
        imageRatio: true,
        lockedOnCombine,
      };
    } else if (item.component) {
      if (item.componentData) {
        const pens = convertPen([item.componentData]);
        data = deepClone(pens);
      } else if (item.componentDatas) {
        data = deepClone(item.componentDatas);
      }
    } else {
      data = item.componentDatas || item.data;
    }
  }

  if (event instanceof DragEvent) {
    meta2d.canvas.addCaches = [];
    event.dataTransfer?.setData('Meta2d', JSON.stringify(data));
  } else {
    if (!Array.isArray(data)) {
      data = deepClone([data]);
    }
    meta2d.canvas.addCaches = data;
  }
  event.stopPropagation();
};

const drag = (event: DragEvent, item: any) => {};

const dragEnd = () => {};

const dragstart = (event: any) => {
  event.target.style.opacity = 0.5;
};

const dragend = (event: any) => {
  event.target.style.opacity = 1;
};

const open = async (item: any) => {
  if (item.draggable !== false) {
    return;
  }
  autoSave();
  router.push({
    path: '/',
    query: {
      r: Date.now() + '',
    },
  });

  const ret: any = await getLe5leV(item._id || item.id);
  for (const k of delAttrs) {
    delete (ret as any)[k];
  }
  meta2d.open(ret);
};

const onChangeGroupPanel = async (val: string[]) => {
  if (val?.length) {
    for (const name of val) {
      switch (activedGroup.value) {
        case '场景':
          if (
            !caseCaches[activedGroup.value + name] ||
            !caseCaches[activedGroup.value + name].length
          ) {
            for (const item of subGroups.value) {
              if (item.name === name) {
                item.loading = true;
              }
            }
            caseCaches[activedGroup.value + name] = await getCaseProjects(
              activedGroup.value,
              name
            );
            for (const item of subGroups.value) {
              if (item.name === name) {
                item.list = caseCaches[activedGroup.value + name];
                item.loading = false;
              }
            }
          }
          break;
        case '图元':
          for (const item of subGroups.value) {
            if (item.name === name && !item.list.length) {
              item.loading = true;
              if (item.svg) {
                item.list = await getIcons(item.folder);
              } else {
                item.list = await getFiles(item.folder + item.name);
              }
              item.loading = false;
            }
          }
          break;

        case '我的':
          for (const item of subGroups.value) {
            if (!item.list.length) {
              item.loading = true;
              if (item.name === '我的组件') {
                const data = {
                  query: { folder: '' },
                  projection: {
                    image: 1,
                    _id: 1,
                    name: 1,
                    component: 1,
                  },
                };
                const config = {
                  params: {
                    current: 1,
                    pageSize: 1000,
                  },
                };
                const res: any = await getComponentsList(data, config);
                if (res?.list) {
                  item.list = res.list;
                }
              } else if (item.name === '3D') {
                const data = {
                  projection: {
                    image: 1,
                    _id: 1,
                    name: 1,
                  },
                };
                const config = {
                  params: {
                    current: 1,
                    pageSize: 1000,
                  },
                };
                const res: any = await axios.post(
                  '/api/data/le5le3d/list',
                  data,
                  config
                );
                if (res?.list) {
                  for (const item of res.list) {
                    item['3d'] = true;
                  }
                  item.list = res.list;
                }
              }
              item.loading = false;
            }
          }
          break;
      }
    }
  }
};

const editedFolder = ref<any>(undefined);
const onCreateFolder = () => {
  editedFolder.value = {
    _id: '',
    name: '',
    label: '新建文件夹',
    list: [],
    edited: true,
    canEdited: true,
  };
  subGroups.value.splice(subGroups.value.length - 1, 0, editedFolder.value);
};

const createFoder = async () => {
  if (!editedFolder.value.label) {
    return;
  }
  if (editedFolder.value.label === editedFolder.value.name) {
    editedFolder.value.edited = false;
    return;
  }
  const found = subGroups.value.findIndex(
    (group: any) => group.name === editedFolder.value.label
  );
  if (found >= 0) {
    MessagePlugin.error('已经存在相同名称文件夹');
    return;
  }

  if (editedFolder.value._id) {
    const ret: any = await axios.post('/api/data/folders/update', {
      _id: editedFolder.value._id,
      name: editedFolder.value.label,
    });
    if (ret) {
      editedFolder.value.name = editedFolder.value.label;
      editedFolder.value.edited = false;
    }
  } else {
    const ret: any = await axios.post('/api/data/folders/add', {
      name: editedFolder.value.label,
      type: 'le5leV-components',
      list: [],
    });
    if (ret) {
      editedFolder.value.name = editedFolder.value.label;
      editedFolder.value._id = ret._id;
      editedFolder.value.edited = false;
    }
  }
};

const onEditHeader = (item: any) => {
  item.label = item.name;
  item.edited = true;
  editedFolder.value = item;
};

const onKeyHeader = (text: string, event: any) => {
  if (event.e.key === 'Escape') {
    editedFolder.value.edited = false;
  }
};

// 我的组件右键菜单
const contextmenu = reactive<any>({
  visible: false,
  style: {},
  // 子分类
  group: undefined,
  // 组件图纸
  component: undefined,
  // 右键二级子菜单
  subMenus: [],
});
const contextmenuDom = ref<any>(null);
const onContextMenu = async (e: MouseEvent, group: string, item: any) => {
  e.preventDefault();
  e.stopPropagation();

  if (activedGroup.value !== '我的' || !item.component) {
    return;
  }

  contextmenu.group = group;
  contextmenu.component = item;

  if (document.body.clientHeight - e.clientY < 128) {
    contextmenu.style = {
      left: e.clientX + 'px',
      bottom: '4px',
    };
  } else {
    contextmenu.style = {
      left: e.clientX + 'px',
      top: e.clientY + 'px',
    };
  }

  contextmenu.subMenus = [];
  for (const elem of subGroups.value) {
    if (elem === group || elem.name === '3D') {
      continue;
    }
    contextmenu.subMenus.push(elem);
  }
  contextmenu.visible = true;
  setTimeout(() => {
    if (contextmenuDom.value) {
      contextmenuDom.value.focus();
    }
  }, 500);
};

const delDialog = reactive<any>({});

const onMenu = async (val: string) => {
  const id = contextmenu.component._id || contextmenu.component.id;

  switch (val) {
    case 'edit':
      autoSave();
      router.push({
        path: '/',
        query: {
          id,
          c: 1,
          r: Date.now() + '',
        },
      });

      break;
    case 'del':
      delDialog.show = true;
      break;
    default:
      if (val.indexOf('move:')) {
        return;
      }

      val = val.replace('move:', '');
      const group = contextmenu.subMenus.find(
        (element: any) => element.name === val
      );
      if (!group) {
        return;
      }
      // 前端: 添加组件到目标文件夹
      group.list.push(contextmenu.component);
      // 前端：从源文件夹移出组件
      contextmenu.group.list.forEach((item: any, index: number, arr: any[]) => {
        if (id === item._id || id === item.id) {
          arr.splice(index, 1);
        }
      });

      // 更新后端组件信息
      let ret = await updateCollection('le5leV-components', {
        _id: id,
        folder: val === '我的组件' ? '' : val,
      });
      if (!ret) {
        return;
      }

      // 更新后端源文件夹列表
      if (contextmenu.group.name !== '我的组件') {
        await axios.post('/api/data/folders/update', {
          _id: contextmenu.group._id || contextmenu.group.id,
          list: contextmenu.group.list,
        });
      }

      // 更新后端目标文件夹列表
      if (group.name !== '我的组件') {
        await axios.post('/api/data/folders/update', {
          _id: group._id || group.id,
          list: group.list,
        });
      }
      break;
  }

  contextmenu.visible = false;
};

const delComponet = async () => {
  const id = contextmenu.component._id || contextmenu.component.id;
  await axios.post(`/api/data/le5leV-components/delete`, {
    id,
  });

  // 前端：从源文件夹移出组件
  contextmenu.group.list.forEach((item: any, index: number, arr: any[]) => {
    if (id === item._id || id === item.id) {
      arr.splice(index, 1);
    }
  });

  // 更新后端源文件夹列表
  if (contextmenu.group.name !== '我的组件') {
    await axios.post('/api/data/folders/update', {
      _id: contextmenu.group._id || contextmenu.group.id,
      list: contextmenu.group.list,
    });
  }

  delDialog.show = false;
};

onMounted(() => {
  groupChange('场景');
  document.addEventListener('dragstart', dragstart, false);
  document.addEventListener('dragend', dragend, false);
});

onUnmounted(() => {
  document.removeEventListener('dragstart', dragstart);
  document.removeEventListener('dragend', dragend);
});
</script>
<style lang="postcss" scoped>
.graphics {
  display: flex;
  flex-direction: column;

  .input-search {
    flex-shrink: 0;
    height: 40px;
  }

  .groups-panel {
    display: grid;
    grid-template-columns: 50px 1fr;
    border-top: 1px solid var(--color-background-input);
    flex-grow: 1;
    overflow-y: auto;
    font-size: 12px;
    z-index: 100;

    .groups {
      & > div {
        display: flex;
        flex-direction: column;
        align-items: center;
        padding: 16px 4px;
        line-height: 1;
        cursor: pointer;

        .t-icon {
          font-size: 20px;
          margin-bottom: 8px;
        }

        &:hover {
          color: var(--color-primary);
        }
      }

      .active {
        background-color: var(--color-background-active);
        color: var(--color-primary);
        border-left: 2px solid var(--color-primary);
      }
    }

    .list {
      overflow-y: auto;
      max-height: calc(100vh - 100px);
      background-color: var(--color-background-active);
      padding-top: 8px;

      * {
        background-color: var(--color-background-active);
      }

      :deep(.t-collapse) {
        min-height: 100vh;
        border: none;
      }
      :deep(.t-collapse-panel__header) {
        border: none;
        font-size: 12px;
        font-weight: 400;
        padding: 8px 16px;
        &:hover {
          color: var(--color-primary);
        }
      }

      :deep(.t-collapse-panel__body) {
        border: none;
      }
      :deep(.t-collapse-panel__content) {
        background-color: var(--color-background-active);
        padding: 4px 4px 20px 4px;
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-row-gap: 12px;
      }

      :deep(.t-loading--center) {
        width: 100px;
        .t-loading__text {
          margin-left: 8px;
          height: 24px;
        }
      }

      :deep(.t-image__error) {
        .t-space-item:last-child {
          display: none;
        }
      }

      :deep(.t-image__loading) {
        .t-space-item:last-child {
          display: none;
        }
      }

      .graphic {
        position: relative;
        padding: 10px 0;
        border-radius: 2px;
        border: 1px solid transparent;

        &:hover {
          cursor: pointer;
          border-color: var(--color-primary);
        }
        p {
          margin-top: 10px;
          padding: 0 10px;
          text-align: center;
          font-size: 12px;
          height: 12px;
          line-height: 1;
          overflow: hidden;
          text-overflow: ellipsis;
          display: -webkit-box;
          -webkit-line-clamp: 1;
          word-break: break-all;
          -webkit-box-orient: vertical;
        }
        .t-image__wrapper {
          height: 32px;
          width: 32px;
          margin: auto;
          :deep(.t-image) {
            border-radius: 2px;
          }
        }

        svg {
          color: var(--color);
          height: 32px;
          width: 100%;
          margin: auto;
        }

        .svg-box {
          height: 32px;
          width: 32px;
          margin-left: calc(50% - 16px);
          margin-top: 10px;
          margin-bottom: 10px;
          &:deep(svg) {
            height: 100%;
            width: 100%;
            .cls-1 {
              stroke: var(--color) !important;
            }
          }
        }

        .price {
          position: absolute;
          top: 8px;
          right: 8px;
          display: inline-block;
          z-index: 1;
          border-radius: 2px;
          background-color: #ff400060;
          color: var(--color-bland);
          font-size: 10px;
          line-height: 1;
          padding: 3px;
        }
      }
    }

    .two-columns {
      :deep(.t-collapse-panel__content) {
        grid-template-columns: 1fr 1fr;
      }
      .graphic {
        .t-image__wrapper {
          width: 100px;
          height: 88px;
          background-color: var(--color-background);
        }
      }
    }
  }

  .context-menu-box {
    position: fixed;
    z-index: 200;
    & > div {
      width: 140px !important;
    }

    :deep(.t-menu) {
      .t-menu__item {
        &.t-is-opened {
          background-color: var(--color-background-popup-hover);
          transition: none !important;
        }
      }
      .t-fake-arrow {
        transform: rotate(-90deg) !important;
      }

      .t-fake-arrow--active {
        transform: rotate(90deg) !important;
      }
    }
  }
}
</style>
