<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue';

import { useVModel } from '@vueuse/core';
import { Input, Popover } from 'ant-design-vue';

import type {
  AddressListRow,
  AddressNode,
  AddressSearchHit,
  AddressValue,
  BreadcrumbItem,
  RegionLevel,
  SelectionContext,
} from '#/util/address-index';
import {
  applySearchHit,
  buildAddressOutput,
  buildBreadcrumbFromContext,
  canCompleteAfterSelect,
  formatAddressDisplay,
  getInitialPickerLevel,
  getLevelSearchPlaceholder,
  getNextLevel,
  isMunicipalityProvince,
  levelOptionsToRows,
  resolveAddressValue,
  rowHasNextLevel,
  searchAddressHits,
  searchHitsToRows,
  truncateContextToBreadcrumb,
} from '#/util/address-index';

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

const props = defineProps<{
  value?: AddressValue | null;
  disabled?: boolean;
}>();

const emits = defineEmits<{
  'update:value': [value: AddressValue | undefined];
}>();

const mValue = useVModel(props, 'value', emits, {
  passive: true,
});

const open = ref(false);
const searchKeyword = ref('');
const currentLevel = ref<RegionLevel>('province');
const activeIndex = ref(0);
const listRef = ref<HTMLElement | null>(null);
const searchInputRef = ref<InstanceType<typeof Input> | null>(null);
const triggerWrapRef = ref<HTMLElement | null>(null);

const ctx = ref<SelectionContext>({
  provinceId: undefined,
  cityId: undefined,
  districtId: undefined,
});

const displayText = computed(() => formatAddressDisplay(mValue.value));

const breadcrumbItems = computed(() => buildBreadcrumbFromContext(ctx.value));

const isSearching = computed(() => !!searchKeyword.value.trim());

const listRows = computed((): AddressListRow[] => {
  if (isSearching.value) {
    return searchHitsToRows(
      searchAddressHits(searchKeyword.value, ctx.value),
    );
  }
  return levelOptionsToRows(currentLevel.value, ctx.value);
});

const searchPlaceholder = computed(() =>
  getLevelSearchPlaceholder(currentLevel.value),
);

function syncContextFromValue(val?: AddressValue | null) {
  const resolved = resolveAddressValue(val ?? undefined);
  ctx.value = {
    provinceId: resolved.provinceId,
    cityId: resolved.cityId,
    districtId: resolved.districtId,
  };
}

function resetPickerToValue(val?: AddressValue | null) {
  syncContextFromValue(val);
  const resolved = resolveAddressValue(val ?? undefined);
  currentLevel.value = getInitialPickerLevel(resolved);
  searchKeyword.value = '';
  activeIndex.value = 0;
}

function resetActiveIndex() {
  activeIndex.value = listRows.value.length > 0 ? 0 : -1;
}

function closePanel(output?: AddressValue) {
  if (output !== undefined) {
    mValue.value = output;
  }
  open.value = false;
  searchKeyword.value = '';
}

function completeSelection() {
  closePanel(
    buildAddressOutput(
      ctx.value.provinceId,
      ctx.value.cityId,
      ctx.value.districtId,
    ),
  );
}

function applySelectionAtLevel(level: RegionLevel, item: AddressNode) {
  if (level === 'province') {
    ctx.value = {
      provinceId: item.value,
      cityId: undefined,
      districtId: undefined,
    };
  } else if (level === 'city') {
    ctx.value = {
      ...ctx.value,
      cityId: item.value,
      districtId: undefined,
    };
  } else {
    ctx.value = {
      ...ctx.value,
      districtId: item.value,
    };
  }
}

function searchHitFromRow(row: AddressListRow): AddressSearchHit {
  return {
    level: row.level,
    node: row.node,
    pathLabel: row.label,
    complete: row.complete,
  };
}

function advanceAfterRowSelect(row: AddressListRow) {
  searchKeyword.value = '';

  if (row.complete) {
    completeSelection();
    return;
  }

  const next = getNextLevel(row.level, ctx.value);
  if (next) {
    currentLevel.value = next;
    return;
  }

  completeSelection();
}

function onConfirmRow(row: AddressListRow) {
  if (isSearching.value) {
    const hit = searchHitFromRow(row);
    if (
      hit.level === 'province' &&
      (ctx.value.cityId || ctx.value.districtId)
    ) {
      ctx.value = applySearchHit(hit, ctx.value);
      currentLevel.value = isMunicipalityProvince(hit.node.value)
        ? 'district'
        : 'city';
      searchKeyword.value = '';
      resetActiveIndex();
      return;
    }
    ctx.value = applySearchHit(hit, ctx.value);
    advanceAfterRowSelect(row);
    return;
  }

  onSelectItem(row.node, row.level);
}

function onSelectItem(item: AddressNode, level = currentLevel.value) {
  if (level === 'province' && (ctx.value.cityId || ctx.value.districtId)) {
    ctx.value = {
      provinceId: item.value,
      cityId: undefined,
      districtId: undefined,
    };
    searchKeyword.value = '';
    currentLevel.value = isMunicipalityProvince(item.value)
      ? 'district'
      : 'city';
    resetActiveIndex();
    return;
  }

  applySelectionAtLevel(level, item);
  const row: AddressListRow = {
    level,
    node: item,
    label: item.label,
    complete: canCompleteAfterSelect(level, ctx.value, item),
  };
  advanceAfterRowSelect(row);
}

function confirmActiveItem() {
  const rows = listRows.value;
  if (activeIndex.value < 0 || activeIndex.value >= rows.length) {
    return;
  }
  onConfirmRow(rows[activeIndex.value]!);
}

function drillActiveRow() {
  const rows = listRows.value;
  const row = rows[activeIndex.value];
  if (!row || !rowHasNextLevel(row, ctx.value)) {
    return;
  }

  if (isSearching.value) {
    const hit = searchHitFromRow(row);
    if (
      hit.level === 'province' &&
      (ctx.value.cityId || ctx.value.districtId)
    ) {
      ctx.value = applySearchHit(hit, ctx.value);
    } else {
      ctx.value = applySearchHit(hit, ctx.value);
    }
    searchKeyword.value = '';
  } else {
    applySelectionAtLevel(row.level, row.node);
  }

  const next = getNextLevel(row.level, ctx.value);
  if (next) {
    currentLevel.value = next;
    resetActiveIndex();
  }
}

function goBackOneLevel() {
  if (currentLevel.value === 'province') {
    return;
  }

  if (currentLevel.value === 'district') {
    if (isMunicipalityProvince(ctx.value.provinceId)) {
      ctx.value = { provinceId: ctx.value.provinceId };
      currentLevel.value = 'province';
    } else {
      ctx.value = {
        provinceId: ctx.value.provinceId,
        cityId: ctx.value.cityId,
      };
      currentLevel.value = 'city';
    }
  } else if (currentLevel.value === 'city') {
    ctx.value = {};
    currentLevel.value = 'province';
  }

  searchKeyword.value = '';
  resetActiveIndex();
}

function scrollActiveIntoView() {
  nextTick(() => {
    const el = listRef.value?.querySelector(
      `[data-index="${activeIndex.value}"]`,
    );
    el?.scrollIntoView({ block: 'nearest' });
  });
}

function getPopupContainer(triggerNode: HTMLElement): HTMLElement {
  return (
    (triggerNode.closest('[role="dialog"]') as HTMLElement | null) ??
    document.body
  );
}

function resolveSearchInputEl(): HTMLInputElement | null {
  const inst = searchInputRef.value as
    | (InstanceType<typeof Input> & { input?: HTMLInputElement })
    | null;
  if (inst?.input instanceof HTMLInputElement) {
    return inst.input;
  }
  const scope =
    (triggerWrapRef.value?.closest('[role="dialog"]') as HTMLElement | null) ??
    document.body;
  return scope.querySelector(
    '.region-address-picker-popover .region-address-search input',
  );
}

function focusSearchInput() {
  const tryFocus = (): boolean => {
    const inst = searchInputRef.value;
    if (inst?.focus) {
      inst.focus();
      return true;
    }
    const input = resolveSearchInputEl();
    if (input) {
      input.focus();
      return true;
    }
    return false;
  };

  const schedule = (attempt: number) => {
    if (!open.value || attempt > 8) {
      return;
    }
    if (tryFocus()) {
      return;
    }
    requestAnimationFrame(() => {
      if (tryFocus()) {
        return;
      }
      const delay = attempt < 3 ? 0 : attempt < 6 ? 50 : 100;
      setTimeout(() => schedule(attempt + 1), delay);
    });
  };

  nextTick(() => schedule(0));
}

function handleKeydown(e: KeyboardEvent) {
  if (!open.value) {
    return;
  }

  const rows = listRows.value;

  switch (e.key) {
    case 'ArrowDown': {
      if (rows.length === 0) {
        return;
      }
      e.preventDefault();
      if (activeIndex.value < 0) {
        activeIndex.value = 0;
      } else {
        activeIndex.value = (activeIndex.value + 1) % rows.length;
      }
      scrollActiveIntoView();
      break;
    }
    case 'ArrowUp': {
      if (rows.length === 0) {
        return;
      }
      e.preventDefault();
      activeIndex.value =
        activeIndex.value <= 0 ? rows.length - 1 : activeIndex.value - 1;
      scrollActiveIntoView();
      break;
    }
    case 'Enter': {
      if (rows.length === 0) {
        return;
      }
      e.preventDefault();
      confirmActiveItem();
      break;
    }
    case 'ArrowLeft': {
      e.preventDefault();
      goBackOneLevel();
      break;
    }
    case 'ArrowRight': {
      if (rows.length === 0 || activeIndex.value < 0) {
        return;
      }
      e.preventDefault();
      drillActiveRow();
      break;
    }
    default:
      break;
  }
}

function onBreadcrumbClick(item: BreadcrumbItem) {
  ctx.value = truncateContextToBreadcrumb(ctx.value, item);
  if (item.level === 'province') {
    currentLevel.value = isMunicipalityProvince(item.id) ? 'district' : 'city';
  } else if (item.level === 'city') {
    currentLevel.value = 'district';
  } else {
    currentLevel.value = 'district';
  }
  searchKeyword.value = '';
  resetActiveIndex();
}

function goProvinceLevel() {
  ctx.value = {};
  currentLevel.value = 'province';
  searchKeyword.value = '';
  resetActiveIndex();
}

function onClear() {
  ctx.value = {};
  mValue.value = undefined;
  currentLevel.value = 'province';
  searchKeyword.value = '';
  resetActiveIndex();
}

function onOpenChange(v: boolean) {
  open.value = v;
  if (v) {
    resetPickerToValue(mValue.value);
  }
}

watch(open, (v) => {
  if (v) {
    focusSearchInput();
  }
}, { flush: 'post' });

watch(listRows, () => {
  if (open.value) {
    resetActiveIndex();
  }
});

watch(
  () => props.value,
  (val) => {
    if (!open.value) {
      syncContextFromValue(val ?? undefined);
    }
  },
  { immediate: true, deep: true },
);
</script>

<template>
  <Popover
    v-model:open="open"
    :trigger="['click']"
    placement="bottomLeft"
    overlay-class-name="region-address-picker-popover"
    :get-popup-container="getPopupContainer"
    @open-change="onOpenChange"
  >
    <template #content>
      <div class="region-address-panel" @click.stop>
        <div v-if="breadcrumbItems.length" class="region-address-breadcrumb-row">
          <div class="region-address-breadcrumb">
            <template
              v-for="(item, idx) in breadcrumbItems"
              :key="`${item.level}-${item.id}`"
            >
              <span v-if="idx > 0" class="region-address-breadcrumb-sep">/</span>
              <button
                type="button"
                class="region-address-breadcrumb-item"
                @click="onBreadcrumbClick(item)"
              >
                {{ item.label }}
              </button>
            </template>
          </div>
          <button
            type="button"
            class="region-address-repick-province"
            @click="goProvinceLevel"
          >
            重选省份
          </button>
        </div>
        <Input
          ref="searchInputRef"
          v-model:value="searchKeyword"
          :placeholder="searchPlaceholder"
          allow-clear
          class="region-address-search"
          @keydown="handleKeydown"
        />
        <ul ref="listRef" class="region-address-list">
          <li
            v-for="(row, index) in listRows"
            :key="`${row.level}-${row.node.value}`"
            :class="[
              'region-address-list-item',
              { 'region-address-list-item-active': index === activeIndex },
            ]"
            :data-index="index"
            @click="onConfirmRow(row)"
            @mouseenter="activeIndex = index"
          >
            {{ row.label }}
          </li>
          <li v-if="listRows.length === 0" class="region-address-list-empty">
            暂无数据
          </li>
        </ul>
      </div>
    </template>
    <div
      ref="triggerWrapRef"
      class="region-address-trigger-wrap"
      @click="!disabled && (open = true)"
    >
      <Input
        :disabled="disabled"
        :value="displayText"
        class="region-address-trigger"
        placeholder="请选择省市区"
        readonly
      >
        <template v-if="displayText && !disabled" #suffix>
          <span
            class="region-address-clear"
            @click.stop="onClear"
          >×</span>
        </template>
      </Input>
    </div>
  </Popover>
</template>

<style scoped>
.region-address-trigger-wrap {
  width: 100%;
}

.region-address-trigger {
  width: 100%;
  cursor: pointer;
}

.region-address-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  color: rgba(0, 0, 0, 0.35);
  cursor: pointer;
  font-size: 12px;
}

.region-address-clear:hover {
  color: rgba(0, 0, 0, 0.65);
}

.region-address-panel {
  width: 320px;
}

.region-address-breadcrumb-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
}

.region-address-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  flex: 1;
  font-size: 13px;
}

.region-address-repick-province {
  flex-shrink: 0;
  padding: 0;
  border: none;
  background: none;
  color: rgba(0, 0, 0, 0.45);
  cursor: pointer;
  font-size: 12px;
  white-space: nowrap;
}

.region-address-repick-province:hover {
  color: #1677ff;
}

.region-address-breadcrumb-sep {
  color: rgba(0, 0, 0, 0.35);
  margin: 0 4px;
}

.region-address-breadcrumb-item {
  padding: 0;
  border: none;
  background: none;
  color: #1677ff;
  cursor: pointer;
  font-size: 13px;
}

.region-address-breadcrumb-item:hover {
  color: #4096ff;
}

.region-address-search {
  margin-bottom: 8px;
}

.region-address-list {
  margin: 0;
  padding: 0;
  max-height: 240px;
  overflow-y: auto;
  list-style: none;
}

.region-address-list-item {
  padding: 8px 12px;
  cursor: pointer;
  border-radius: 4px;
  transition: background 0.2s;
}

.region-address-list-item:hover {
  background: rgba(0, 0, 0, 0.04);
}

.region-address-list-item-active {
  background: #e6f4ff;
}

.region-address-list-item-active:hover {
  background: #e6f4ff;
}

.region-address-list-empty {
  padding: 16px 12px;
  color: rgba(0, 0, 0, 0.45);
  text-align: center;
}

/* 暗色模式适配（对齐 drug-search-select / menu-search-select） */
.dark {
  .region-address-clear {
    color: rgba(255, 255, 255, 0.35);
  }

  .region-address-clear:hover {
    color: rgba(255, 255, 255, 0.65);
  }

  .region-address-repick-province {
    color: rgba(255, 255, 255, 0.45);
  }

  .region-address-repick-province:hover {
    color: #60a5fa;
  }

  .region-address-breadcrumb-sep {
    color: rgba(255, 255, 255, 0.35);
  }

  .region-address-breadcrumb-item {
    color: #60a5fa;
  }

  .region-address-breadcrumb-item:hover {
    color: #93c5fd;
  }

  .region-address-list-item {
    color: #f3f4f6;
  }

  .region-address-list-item:hover {
    background: #374151;
  }

  .region-address-list-item-active,
  .region-address-list-item-active:hover {
    background: #1e3a5f;
  }

  .region-address-list-empty {
    color: #9ca3af;
  }
}
</style>
