<script setup lang="ts">
import type { Option } from '@/request/model';
import { tryOnBeforeMount, tryOnUnmounted } from '@vueuse/core';
import type { DialogProps } from 'vant';

export interface Props {
  options: Option[];
  title?: string;
  multiple?: boolean;
  /* 单选点击选项直接确定 */
  immediate?: boolean;
  selected?: (string | EditableOption)[];
}

type EditableOption = Option & { inputValue?: string; showLabel?: string };

let cache: WeakMap<Option[], EditableOption[]>;
const onCleanup = () => {
  cache = new WeakMap();
};

const props = defineProps<Props>();
const emits = defineEmits<{ closed: []; selected: [{ label: string; value: string }[]] }>();
const show = defineModel('show', { default: false });

const options = ref<EditableOption[]>([]);
const dialog = reactive<Partial<DialogProps>>({
  showCancelButton: true,
});

watchEffect(() => {
  dialog.title = props.title ?? void 0;
  dialog.showConfirmButton = props.multiple || !props.immediate;
});
const selectedInput = useTemplateRef<{ focus(): void; $el: HTMLInputElement; }>('input');
const selectedOptions = ref<EditableOption>();
const dialogConfirmHandle = (emit?: boolean) => {
  if (selectedOptions.value && !emit) {
    const option = selectedOptions.value;
    if (!option.inputValue) option.inputValue = option.showLabel;
    if (!option.inputValue || option.inputValue === option.label) return dialogCancelHandle();

    selectedOptions.value.showLabel = option.inputValue;
    selectedOptions.value.checked = true;
    handle(selectedOptions.value, true);
  } else {
    emits('selected', options.value.filter((option) => option.checked).map((option) => ({
      label: option.showLabel ?? option.label,
      value: option.editable ? `${option.value}:${option.showLabel ?? option.label}` : option.value,
    })));
    show.value = false;
  }
};
const dialogCancelHandle = () => {
  if (selectedOptions.value) {
    dialog.showConfirmButton = props.multiple || !props.immediate;
    delete dialog.confirmButtonText;
    delete dialog.cancelButtonText;
    const option = selectedOptions.value;
    selectedOptions.value.inputValue = option.showLabel !== option.label ? option.showLabel : ``;
    selectedOptions.value.checked = false;
    selectedOptions.value = void 0;
  } else {
    show.value = false;
  }
};
const dialogOpenHandle = (select?: EditableOption[]) => {
  select ??= (props.selected?.map((option) => {
    if (typeof option === 'string') {
      const [value, inputValue] = option.split(':');
      if (!value) return false;
      option = { value, inputValue } as EditableOption;
    }
    return option;
  })?.filter(Boolean) as EditableOption[]) ?? [];

  const value = props.options;
  if (!cache.has(value)) cache.set(value, value.map((item) => Object.assign({ inputValue: '', showLabel: item.label }, item)));
  const values = cache.get(value)!;

  if (select.length) for (const option of values) option.checked = false;
  for (const item of select) {
    if (!item) continue;
    const option = values.find((v) => v.value === item.value);
    if (!option) continue;
    if (option.editable) {
      option.inputValue = item.inputValue;
      option.showLabel = item.inputValue ?? option.label;
    }
    option.checked = true;
  }

  options.value = values;
  show.value = true;
};
const dialogClosedHandle = () => {
  emits('closed');
  selectedOptions.value = void 0;
};
const handle = (option: EditableOption, checked?: boolean) => {
  if (option.disabled) return;

  if (option.editable && checked == null) {
    selectedOptions.value = option;
    dialog.showConfirmButton = true;
    dialog.confirmButtonText = '确认';
    dialog.cancelButtonText = '返回';
    return nextTick(() => {
      /*try { selectedInput.value?.$el?.querySelector('input')?.select(); } catch (e) {}*/
      try { selectedInput.value?.focus?.(); } catch (e) {}
    })
  }

  checked ??= !option.checked;
  if (checked) {
    if (!props.multiple || option.single) for (const option of options.value) option.checked = false;
    else {
      const single = options.value.find((option) => option.single);
      if (single) single.checked = false;
    }
  }

  option.checked = checked;
  if (props.immediate) setTimeout(() => dialogConfirmHandle(true), 100);
  else selectedOptions.value = void 0;
};

tryOnBeforeMount(() => onCleanup());
tryOnUnmounted(() => onCleanup());
defineExpose({ onCleanup });
</script>

<template>
  <div>
    <slot :handle="dialogOpenHandle"></slot>
    <van-dialog
      class="sub-dialog scrollable"
      :show="show"
      v-bind="dialog"
      @confirm="dialogConfirmHandle"
      @cancel="dialogCancelHandle"
      @open="dialogOpenHandle"
      @closed="dialogClosedHandle"
    >
      <template v-if="selectedOptions">
        <van-cell-group inset class="form-wrapper">
          <van-field ref="input" v-model="selectedOptions.inputValue" :placeholder="selectedOptions.showLabel" maxlength="10" />
        </van-cell-group>
      </template>
      <template v-else>
        <slot name="options" :options="options" :handle="handle">
          <div class="grid grid-rows-1 grid-cols-2 gap-4 py-4 px-12">
            <div
              v-for="(option, index) in options"
              :key="option.value"
              class="sub-option flex justify-center items-center min-h-16 text-lg text-primary hover:text-white rounded-xl border border-primary hover:border-primary-400"
              :class="{ checked: option.checked, disabled: option.disabled }"
              @click="handle(option)"
            >
              <div class="p-2 text-center">{{ option.showLabel }}</div>
            </div>
          </div>
        </slot>
      </template>
    </van-dialog>
  </div>
</template>

<style scoped lang="scss">
.form-wrapper {
  .van-field {
    margin: 0;
    padding: 0;
  }

  :deep(.van-field__control) {
    margin-bottom: 24px;
    padding: 8px;
    border: 1px solid #38ff6e;
    border-radius: 8px;
    text-align: center;
  }
}

.sub-option.checked {
  color: #fff;
  background-color: var(--primary-color);
}
</style>
<style lang="scss">
.sub-dialog {
  --van-dialog-width: 60vw;
  --van-dialog-font-size: 24px;
  --van-dialog-button-height: 60px;
  --van-button-default-font-size: 24px;

  &.scrollable {
    max-height: 80vh;
    display: flex;
    flex-direction: column;

    .van-dialog__header {
      padding-bottom: var(--van-dialog-header-padding-top);
    }

    .van-dialog__content {
      flex: auto;
      overflow-y: auto;
    }
  }
}
</style>
