<script lang="ts" setup>
import type { FormSelectorEmits, FormSelectorProps } from './types';

import { computed, ref, watch } from 'vue';

import { Table2 } from '@vben/icons';
import { $t } from '@vben/locales';

import {
  ElButton,
  ElCheckbox,
  ElEmpty,
  ElInput,
  ElOption,
  ElPagination,
  ElRadio,
  ElSelect,
  ElTable,
  ElTableColumn,
} from 'element-plus';

import { requestClient } from '#/api/request';
import { ZqDialog } from '#/components/zq-dialog';

defineOptions({
  name: 'FormSelector',
  inheritAttrs: false,
});

const selectorText = {
  clickToSelect: '点击选择',
  nodeLabel: '名称',
  nodeValue: '值',
  search: '搜索',
  selectData: '选择数据',
  selectFormFirst: '请先选择表单',
  selectedCount: (count: number) => `已选择 ${count} 项`,
};

const props = withDefaults(defineProps<FormSelectorProps>(), {
  multiple: false,
  collapseTags: false,
  maxCollapseTags: 1,
  placeholder: selectorText.clickToSelect,
  disabled: false,
  clearable: true,
  dialogTitle: selectorText.selectData,
  dialogWidth: '1200px',
  valueField: 'id',
  labelField: 'name',
  expandMultipleToRows: false,
  externalSelectedValues: () => [],
  initialFilters: undefined,
});

const emit = defineEmits<FormSelectorEmits>();

const dialogVisible = ref(false);
const loading = ref(false);
const labelsLoading = ref(false);
const tableData = ref<Array<Record<string, any>>>([]);
const total = ref(0);
const currentPage = ref(1);
const pageSize = ref(10);
const searchKeyword = ref('');
const selectedValues = ref<Set<string>>(new Set());
const selectedItemsMap = ref<Map<string, Record<string, any>>>(new Map());

function normalizeValues(value: null | string | string[] | undefined) {
  if (!value) return [];
  return Array.isArray(value) ? value.map(String) : [String(value)];
}

function toStringArray(value: unknown) {
  if (!value) return [];
  return Array.isArray(value) ? value.map(String) : [String(value)];
}

function getItemLabel(item: undefined | Record<string, any>, value: string) {
  if (!item) return value;
  return (
    item[props.labelField] ??
    item.name ??
    item.title ??
    item.label ??
    item.text ??
    value
  );
}

function pickList(response: any) {
  return Array.isArray(response)
    ? response
    : response?.items || response?.list || response?.data || [];
}

function normalizeFilters() {
  const params: Record<string, any> = {};
  for (const [field, filter] of Object.entries(props.initialFilters || {})) {
    if (filter?.value === undefined || filter.value === null || filter.value === '') {
      continue;
    }
    const op = filter.type || 'eq';
    params[op === 'eq' ? field : `${field}__${op}`] = filter.value;
  }
  return params;
}

async function loadInitialLabels(values: string[]) {
  if (!props.formCode || values.length === 0) return;

  try {
    const response = await requestClient.get(
      `/api/online_dev/form-data/${props.formCode}/list`,
      {
        params: {
          ...normalizeFilters(),
          [`${props.valueField}__in`]: values.join(','),
          pageSize: values.length,
        },
      },
    );
    for (const item of pickList(response)) {
      selectedItemsMap.value.set(String(item[props.valueField]), item);
    }
  } catch (error) {
    console.error('load form selector labels failed:', error);
  }
}

function emitSelectItem() {
  const items = [...selectedValues.value]
    .map((value) => selectedItemsMap.value.get(value))
    .filter(Boolean);
  emit('select-item', props.multiple ? items : items[0]);
}

watch(
  () => props.modelValue,
  async (value) => {
    const values = normalizeValues(value);
    selectedValues.value = new Set(values);
    const missingValues = values.filter((v) => !selectedItemsMap.value.has(v));
    if (missingValues.length > 0) {
      labelsLoading.value = true;
      await loadInitialLabels(missingValues);
      labelsLoading.value = false;
      if (!props.expandMultipleToRows) emitSelectItem();
    }
  },
  { immediate: true },
);

watch(
  () => props.formCode,
  async () => {
    const values = normalizeValues(props.modelValue);
    const missingValues = values.filter((v) => !selectedItemsMap.value.has(v));
    if (missingValues.length > 0) {
      labelsLoading.value = true;
      await loadInitialLabels(missingValues);
      labelsLoading.value = false;
      if (!props.expandMultipleToRows) emitSelectItem();
    }
  },
);

const selectValue = computed(() => {
  if (labelsLoading.value) return props.multiple ? [] : '';

  if (props.expandMultipleToRows) {
    const values = normalizeValues(props.modelValue);
    return props.multiple ? values : values[0] || '';
  }

  const values = [...selectedValues.value];
  return props.multiple ? values : values[0] || '';
});

const selectOptions = computed(() => {
  const values = props.expandMultipleToRows
    ? normalizeValues(props.modelValue)
    : [...selectedValues.value];
  return values.map((value) => ({
    value,
    label: getItemLabel(selectedItemsMap.value.get(value), value),
  }));
});

async function loadData() {
  if (!props.formCode) {
    tableData.value = [];
    total.value = 0;
    return;
  }

  loading.value = true;
  try {
    const params: Record<string, any> = {
      ...normalizeFilters(),
      page: currentPage.value,
      pageSize: pageSize.value,
    };
    if (searchKeyword.value) {
      params[`${props.labelField}__like`] = searchKeyword.value;
    }
    const response = await requestClient.get(
      `/api/online_dev/form-data/${props.formCode}/list`,
      { params },
    );
    const rows = pickList(response);
    tableData.value = rows;
    total.value = Number(response?.total ?? rows.length);

    for (const row of rows) {
      const value = String(row[props.valueField]);
      if (selectedValues.value.has(value)) selectedItemsMap.value.set(value, row);
    }
  } catch (error) {
    console.error('load form selector data failed:', error);
    tableData.value = [];
    total.value = 0;
  } finally {
    loading.value = false;
  }
}

function openDialog() {
  if (props.disabled) return;

  if (props.expandMultipleToRows) {
    for (const value of toStringArray(props.externalSelectedValues)) {
      selectedValues.value.add(value);
    }
    selectedValues.value = new Set(selectedValues.value);
  }

  dialogVisible.value = true;
  loadData();
}

function handleSearch() {
  currentPage.value = 1;
  loadData();
}

function handlePageChange(page: number) {
  currentPage.value = page;
  loadData();
}

function handleSizeChange(size: number) {
  pageSize.value = size;
  currentPage.value = 1;
  loadData();
}

function getRowValue(row: Record<string, any>) {
  return String(row[props.valueField]);
}

function isRowSelected(row: Record<string, any>) {
  return selectedValues.value.has(getRowValue(row));
}

function toggleRow(row: Record<string, any>, checked?: boolean | number | string) {
  const value = getRowValue(row);
  if (props.multiple) {
    const nextChecked = checked ?? !selectedValues.value.has(value);
    if (nextChecked) {
      selectedValues.value.add(value);
      selectedItemsMap.value.set(value, row);
    } else {
      selectedValues.value.delete(value);
      selectedItemsMap.value.delete(value);
    }
    selectedValues.value = new Set(selectedValues.value);
    return;
  }

  selectedValues.value = new Set([value]);
  selectedItemsMap.value.clear();
  selectedItemsMap.value.set(value, row);
}

const isAllSelected = computed(
  () =>
    tableData.value.length > 0 &&
    tableData.value.every((row) => isRowSelected(row)),
);

const isIndeterminate = computed(() => {
  const selectedCount = tableData.value.filter((row) => isRowSelected(row)).length;
  return selectedCount > 0 && selectedCount < tableData.value.length;
});

function handleSelectAll(checked: boolean | number | string) {
  for (const row of tableData.value) toggleRow(row, checked);
}

function handleConfirm() {
  const values = [...selectedValues.value];
  const result = props.multiple ? values : values[0] || null;

  emit('update:modelValue', result);
  emit('change', result);
  emitSelectItem();
  dialogVisible.value = false;
}

function handleClear() {
  selectedValues.value = new Set();
  selectedItemsMap.value.clear();
  emit('update:modelValue', props.multiple ? [] : null);
  emit('change', props.multiple ? [] : null);
  emit('select-item', undefined);
}

function handleRemoveTag(value: string) {
  selectedValues.value.delete(value);
  selectedItemsMap.value.delete(value);
  selectedValues.value = new Set(selectedValues.value);

  const values = [...selectedValues.value];
  const result = props.multiple ? values : values[0] || null;
  emit('update:modelValue', result);
  emit('change', result);
  emitSelectItem();
}
</script>

<template>
  <div class="form-selector">
    <ElSelect
      :model-value="selectValue"
      :multiple="multiple"
      :placeholder="labelsLoading ? $t('common.loading') : placeholder"
      :disabled="disabled"
      :clearable="clearable"
      :loading="labelsLoading"
      :collapse-tags="collapseTags"
      :collapse-tags-tooltip="collapseTags"
      :max-collapse-tags="maxCollapseTags"
      :suffix-icon="Table2"
      class="w-full"
      popper-class="form-selector-popper-hidden"
      @click="openDialog"
      @clear="handleClear"
      @remove-tag="handleRemoveTag"
    >
      <ElOption
        v-for="item in selectOptions"
        :key="item.value"
        :label="item.label"
        :value="item.value"
      />
    </ElSelect>

    <ZqDialog
      v-model="dialogVisible"
      :title="dialogTitle"
      :width="dialogWidth"
      append-to-body
      destroy-on-close
      :show-fullscreen-button="false"
      class="form-selector-dialog h-[90%]"
    >
      <div v-if="formCode" class="form-selector-content flex h-[650px] flex-col">
        <div class="mb-4 flex shrink-0 items-center gap-2">
          <ElInput
            v-model="searchKeyword"
            :placeholder="selectorText.search"
            clearable
            class="w-64"
            @change="handleSearch"
            @keyup.enter="handleSearch"
          />
          <ElButton type="primary" @click="handleSearch">
            {{ $t('common.search') }}
          </ElButton>
        </div>

        <div class="min-h-0 flex-1">
          <ElTable
            v-loading="loading"
            :data="tableData"
            border
            stripe
            height="100%"
            :row-class-name="
              ({ row }: any) => (isRowSelected(row) ? 'selected-row' : '')
            "
            style="width: 100%"
            @row-click="toggleRow"
          >
            <ElTableColumn v-if="!multiple" width="55" align="center">
              <template #default="{ row }">
                <ElRadio
                  :model-value="isRowSelected(row) ? getRowValue(row) : ''"
                  :value="getRowValue(row)"
                  @click.stop
                  @change="toggleRow(row)"
                >
                  <span></span>
                </ElRadio>
              </template>
            </ElTableColumn>
            <ElTableColumn v-else width="55" align="center">
              <template #header>
                <ElCheckbox
                  :model-value="isAllSelected"
                  :indeterminate="isIndeterminate"
                  @change="handleSelectAll"
                />
              </template>
              <template #default="{ row }">
                <ElCheckbox
                  :model-value="isRowSelected(row)"
                  @click.stop
                  @change="toggleRow(row, $event)"
                />
              </template>
            </ElTableColumn>
            <ElTableColumn :prop="labelField" :label="selectorText.nodeLabel" />
            <ElTableColumn :prop="valueField" :label="selectorText.nodeValue" width="180" />
            <template #empty>
              <ElEmpty :description="$t('common.noData')" />
            </template>
          </ElTable>
        </div>
      </div>
      <div v-else class="no-form-code">
        {{ selectorText.selectFormFirst }}
      </div>

      <template #footer>
        <div class="flex w-full items-center">
          <div class="flex-shrink-0">
            <ElPagination
              v-if="formCode"
              v-model:current-page="currentPage"
              v-model:page-size="pageSize"
              :total="total"
              :page-sizes="[10, 20, 50, 100]"
              layout="total, sizes, prev, pager, next"
              small
              @current-change="handlePageChange"
              @size-change="handleSizeChange"
            />
          </div>
          <div class="flex flex-1 justify-center text-sm text-[var(--el-text-color-secondary)]">
            {{
              selectorText.selectedCount(selectedValues.size)
            }}
          </div>
          <div class="flex flex-shrink-0 gap-2">
            <ElButton @click="dialogVisible = false">
              {{ $t('common.cancel') }}
            </ElButton>
            <ElButton type="primary" @click="handleConfirm">
              {{ $t('common.ok') }}
            </ElButton>
          </div>
        </div>
      </template>
    </ZqDialog>
  </div>
</template>

<style scoped>
.form-selector {
  width: 100%;
}

.form-selector-content :deep(.selected-row) {
  background-color: var(--el-color-primary-light-9) !important;
}

.form-selector-content :deep(.el-table__row) {
  cursor: pointer;
}

.no-form-code {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 200px;
  color: var(--el-text-color-secondary);
}
</style>

<style>
.form-selector-popper-hidden {
  display: none !important;
}

.form-selector-dialog .el-dialog__body {
  padding: 16px;
}
</style>
