<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Search } from '@element-plus/icons-vue';
import { OBJECT_CONTEXT_QUERY_KEY } from '@/constants/object-context';
import { fetchGetProductPage, fetchGetProjectPage } from '@/service/api';
import { useObjectContextStore } from '@/store/modules/object-context';

defineOptions({ name: 'ObjectContextSwitcher' });

interface Props {
  domainConfig: App.ObjectContext.DomainConfig;
}

type ObjectOption = {
  id: string;
  name: string;
  code?: string | null;
  createTime?: string | null;
};

const props = defineProps<Props>();

const route = useRoute();
const router = useRouter();
const objectContextStore = useObjectContextStore();

const visible = ref(false);
const keyword = ref('');
const expanded = ref(false);
const loading = ref(false);
const switchingId = ref('');
const options = ref<ObjectOption[]>([]);
let searchTimer: ReturnType<typeof setTimeout> | null = null;

const OBJECT_SWITCHER_PAGE_SIZE = 100;

const isProductDomain = computed(() => props.domainConfig.domainKey === 'product');
const domainLabel = computed(() => (isProductDomain.value ? '产品' : '项目'));
const allLabel = computed(() => `全部${domainLabel.value}`);
const placeholder = computed(() => `搜索${domainLabel.value}`);
const previewOptions = computed(() => options.value.slice(0, 3));
const displayOptions = computed(() => {
  if (keyword.value.trim() || expanded.value) {
    return options.value;
  }

  return previewOptions.value;
});
const hiddenCount = computed(() => Math.max(options.value.length - previewOptions.value.length, 0));
const showAllEntry = computed(() => !keyword.value.trim() && !expanded.value && hiddenCount.value > 0);

function sortByCreateTimeDesc(list: ObjectOption[]) {
  return list.slice().sort((left, right) => {
    const leftTime = left.createTime ? new Date(left.createTime).getTime() : 0;
    const rightTime = right.createTime ? new Date(right.createTime).getTime() : 0;

    return rightTime - leftTime;
  });
}

async function fetchObjectOptionsPage(pageNo: number, keywordValue?: string) {
  const result =
    props.domainConfig.domainKey === 'product'
      ? await fetchGetProductPage({ pageNo, pageSize: OBJECT_SWITCHER_PAGE_SIZE, keyword: keywordValue })
      : await fetchGetProjectPage({ pageNo, pageSize: OBJECT_SWITCHER_PAGE_SIZE, keyword: keywordValue });

  if (result.error || !result.data) {
    return {
      total: 0,
      list: []
    };
  }

  const list = result.data.list.map(item => {
    if (props.domainConfig.domainKey === 'product') {
      const product = item as Api.Product.Product;

      return {
        id: product.id,
        name: product.name,
        code: product.code,
        createTime: product.createTime
      };
    }

    const project = item as Api.Project.Project;

    return {
      id: project.id,
      name: project.projectName,
      code: project.projectCode,
      createTime: project.createTime
    };
  });

  return {
    total: result.data.total,
    list
  };
}

async function loadOptions() {
  loading.value = true;

  const keywordValue = keyword.value.trim() || undefined;
  const firstPage = await fetchObjectOptionsPage(1, keywordValue);
  const pageCount = Math.ceil(firstPage.total / OBJECT_SWITCHER_PAGE_SIZE);
  const restPages =
    pageCount > 1
      ? await Promise.all(
          Array.from({ length: pageCount - 1 }, (_, index) => fetchObjectOptionsPage(index + 2, keywordValue))
        )
      : [];
  const list = [firstPage, ...restPages].flatMap(page => page.list);

  loading.value = false;
  options.value = sortByCreateTimeDesc(list);
}

function handleVisibleChange(value: boolean) {
  visible.value = value;

  if (value) {
    expanded.value = false;
    loadOptions();
  }
}

async function handleSelect(option: ObjectOption) {
  if (option.id === objectContextStore.objectId) {
    visible.value = false;
    return;
  }

  switchingId.value = option.id;
  const result = await objectContextStore.switchContext(props.domainConfig, option.id);
  switchingId.value = '';

  if (result.error) {
    return;
  }

  visible.value = false;
  const query = {
    ...route.query,
    [OBJECT_CONTEXT_QUERY_KEY]: option.id
  };
  const targetLocation = route.name ? { name: route.name, query } : { path: route.path, query };

  await router.push(targetLocation);
}

watch(
  () => keyword.value,
  () => {
    if (!visible.value) {
      return;
    }

    expanded.value = Boolean(keyword.value.trim());

    if (searchTimer) {
      clearTimeout(searchTimer);
    }

    searchTimer = setTimeout(() => {
      loadOptions();
    }, 250);
  }
);
</script>

<template>
  <ElPopover
    :visible="visible"
    trigger="click"
    placement="bottom-start"
    :width="300"
    popper-class="object-context-switcher__popper"
    @update:visible="handleVisibleChange"
  >
    <template #reference>
      <button type="button" class="object-context-switcher__trigger" :class="{ 'is-open': visible }">
        <span class="object-context-switcher__trigger-label">{{ objectContextStore.objectName }}</span>
        <icon-ep:sort class="object-context-switcher__trigger-icon" />
      </button>
    </template>

    <div class="object-context-switcher__panel">
      <ElInput v-model="keyword" clearable :placeholder="placeholder" class="object-context-switcher__search">
        <template #suffix>
          <ElIcon>
            <Search />
          </ElIcon>
        </template>
      </ElInput>

      <div v-loading="loading" class="object-context-switcher__list">
        <button
          v-for="item in displayOptions"
          :key="item.id"
          type="button"
          class="object-context-switcher__item"
          :class="{ 'is-active': item.id === objectContextStore.objectId }"
          :disabled="switchingId === item.id"
          @click="handleSelect(item)"
        >
          <span class="object-context-switcher__item-icon">
            <icon-ep:box v-if="isProductDomain" />
            <icon-ep:folder v-else />
          </span>
          <span class="object-context-switcher__item-main">
            <span class="object-context-switcher__item-name">{{ item.name }}</span>
            <span v-if="item.code" class="object-context-switcher__item-code">{{ item.code }}</span>
          </span>
          <icon-ep:check v-if="item.id === objectContextStore.objectId" class="object-context-switcher__check" />
        </button>

        <ElEmpty v-if="!loading && !displayOptions.length" :description="`暂无可选${domainLabel}`" :image-size="54" />
      </div>

      <button v-if="showAllEntry" type="button" class="object-context-switcher__all" @click="expanded = true">
        <span>{{ allLabel }}</span>
        <span class="object-context-switcher__all-meta">{{ hiddenCount }} 个更多</span>
        <icon-ep:arrow-right class="object-context-switcher__all-arrow" />
      </button>
    </div>
  </ElPopover>
</template>

<style scoped>
.object-context-switcher__trigger {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  max-width: 16rem;
  height: 32px;
  gap: 6px;
  padding: 0 10px 0 12px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--el-color-primary);
  cursor: pointer;
  font: inherit;
  line-height: 1;
}

.object-context-switcher__trigger:hover,
.object-context-switcher__trigger.is-open {
  background: var(--el-color-primary-light-9);
}

.object-context-switcher__trigger-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.object-context-switcher__trigger-icon {
  flex-shrink: 0;
  color: var(--el-color-primary);
  font-size: 13px;
}

.object-context-switcher__panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.object-context-switcher__search {
  padding: 4px 4px 0;
}

.object-context-switcher__list {
  min-height: 84px;
  max-height: 300px;
  overflow-y: auto;
}

.object-context-switcher__item {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 42px;
  gap: 10px;
  padding: 7px 10px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--el-text-color-primary);
  cursor: pointer;
  font: inherit;
  text-align: left;
}

.object-context-switcher__item:hover,
.object-context-switcher__item.is-active {
  background: rgb(59 130 246 / 10%);
}

.object-context-switcher__item:disabled {
  cursor: wait;
  opacity: 0.75;
}

.object-context-switcher__item-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  border-radius: 5px;
  background: var(--el-color-primary-light-8);
  color: var(--el-color-primary);
}

.object-context-switcher__item-main {
  display: flex;
  min-width: 0;
  flex: 1;
  flex-direction: column;
  gap: 2px;
}

.object-context-switcher__item-name,
.object-context-switcher__item-code {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.object-context-switcher__item-name {
  font-size: 13px;
}

.object-context-switcher__item-code {
  color: var(--el-text-color-placeholder);
  font-size: 11px;
}

.object-context-switcher__check {
  flex-shrink: 0;
  color: var(--el-color-primary);
  font-size: 14px;
}

.object-context-switcher__all {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  align-items: center;
  width: calc(100% + 24px);
  height: 38px;
  gap: 8px;
  margin: 0 -12px -12px;
  padding: 0 14px;
  border: none;
  border-top: 1px solid var(--el-border-color-lighter);
  background: transparent;
  color: var(--el-text-color-primary);
  cursor: pointer;
  font: inherit;
  text-align: left;
}

.object-context-switcher__all:hover {
  background: var(--el-fill-color-light);
  color: var(--el-color-primary);
}

.object-context-switcher__all-meta {
  flex: 1;
  color: var(--el-text-color-placeholder);
  font-size: 12px;
  text-align: right;
}

.object-context-switcher__all-arrow {
  color: var(--el-text-color-placeholder);
  font-size: 13px;
}

:global(.object-context-switcher__popper.el-popover) {
  padding: 12px;
  border: 1px solid rgb(226 232 240 / 90%);
  border-radius: 10px;
  box-shadow:
    0 12px 28px rgb(15 23 42 / 10%),
    0 2px 8px rgb(15 23 42 / 6%);
}
</style>
