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

import { Input, InputNumber } from 'ant-design-vue';

import { getDataColScreenX } from '../composables/useAutoMeasure';

import type { SpreadsheetColumnConfig } from '../types';

const BLUR_GUARD_MS = 150;

const props = defineProps<{
  visible: boolean;
  viewRow: number;
  col: number;
  colWidths: number[];
  rowHeight: number;
  scrollTop: number;
  scrollLeft: number;
  fixedCols: number[];
  fixedWidth: number;
  isDark?: boolean;
  column?: SpreadsheetColumnConfig;
  value: unknown;
  draftSeed?: string | number | null;
  selectAllOnFocus?: boolean;
}>();

const emit = defineEmits<{
  commit: [value: unknown];
  cancel: [];
}>();

const inputRef = ref<{ focus?: () => void; select?: () => void } | null>(null);
const draft = ref<string | number>('');
const ignoreBlurUntil = ref(0);
const suppressBlurCommit = ref(false);
const isCommitting = ref(false);

const style = computed(() => {
  const left = getDataColScreenX(
    props.col,
    props.colWidths,
    props.scrollLeft,
    props.fixedCols,
    props.fixedWidth,
  );
  const top = props.viewRow * props.rowHeight - props.scrollTop;
  const width = props.colWidths[props.col] ?? 100;
  return {
    left: `${left}px`,
    top: `${top}px`,
    width: `${width}px`,
    height: `${props.rowHeight}px`,
  };
});

function getNativeInput(): HTMLInputElement | null {
  const el = inputRef.value as
    | { focus?: () => void; select?: () => void; $el?: HTMLElement }
    | null;
  if (!el) return null;
  if (el.$el) {
    return el.$el.querySelector('input') as HTMLInputElement | null;
  }
  return null;
}

function focusInput(options?: { selectAll?: boolean }) {
  const el = inputRef.value as
    | { focus?: () => void; select?: () => void; $el?: HTMLElement }
    | null;
  const native = getNativeInput();

  if (native) {
    native.focus({ preventScroll: true });
    if (options?.selectAll) {
      native.select();
    } else {
      const len = String(native.value ?? '').length;
      native.setSelectionRange(len, len);
    }
    return;
  }

  if (!el) return;
  if (typeof el.focus === 'function') {
    el.focus();
    if (options?.selectAll) {
      el.select?.();
    }
  }
}

function blurInput() {
  getNativeInput()?.blur();
}

function getDraftValue(): unknown {
  return props.column?.cellType === 'number'
    ? Number(draft.value)
    : draft.value;
}

watch(
  () => props.visible,
  async (v, _old, onCleanup) => {
    let cancelled = false;
    onCleanup(() => {
      cancelled = true;
    });

    if (!v) {
      suppressBlurCommit.value = false;
      return;
    }

    ignoreBlurUntil.value = Date.now() + BLUR_GUARD_MS;

    if (props.draftSeed != null) {
      draft.value =
        props.column?.cellType === 'number'
          ? Number(props.draftSeed)
          : String(props.draftSeed);
    } else {
      draft.value =
        props.column?.cellType === 'number'
          ? Number(props.value ?? 0)
          : String(props.value ?? '');
    }

    await nextTick();
    if (cancelled || !props.visible) return;
    focusInput({ selectAll: props.selectAllOnFocus !== false });
  },
  { immediate: true },
);

function commitFromEnter() {
  if (isCommitting.value) return;
  isCommitting.value = true;
  suppressBlurCommit.value = true;
  submit();
}

function onKeydown(e: KeyboardEvent) {
  if (e.key === 'Enter') {
    e.preventDefault();
    e.stopPropagation();
    commitFromEnter();
  } else if (e.key === 'Escape') {
    e.preventDefault();
    e.stopPropagation();
    suppressBlurCommit.value = true;
    blurInput();
    emit('cancel');
  }
}

function submit() {
  emit('commit', getDraftValue());
}

function onBlur() {
  if (Date.now() < ignoreBlurUntil.value) return;
  if (suppressBlurCommit.value) return;
  submit();
}

function stopPointer(e: MouseEvent) {
  e.stopPropagation();
}

defineExpose({
  getDraftValue,
  blurInput,
});
</script>

<template>
  <div
    class="cell-editor-overlay"
    :class="{ 'is-dark': isDark }"
    :style="style"
    @mousedown.stop="stopPointer"
    @click.stop="stopPointer"
  >
    <InputNumber
      v-if="column?.cellType === 'number'"
      ref="inputRef"
      v-model:value="draft as number"
      class="cell-editor-input"
      size="small"
      :controls="false"
      @keydown="onKeydown"
      @blur="onBlur"
    />
    <Input
      v-else
      ref="inputRef"
      v-model:value="draft as string"
      class="cell-editor-input"
      size="small"
      @keydown="onKeydown"
      @blur="onBlur"
    />
  </div>
</template>

<style scoped lang="scss">
.cell-editor-overlay {
  position: absolute;
  z-index: 20;
  box-sizing: border-box;
  border: 2px solid #165dff;
  background: #fff;

  &.is-dark {
    border-color: #1668dc;
    background: #141414;
  }
}

.cell-editor-input {
  width: 100%;
  height: 100%;

  :deep(.ant-input-number),
  :deep(.ant-input) {
    width: 100%;
    height: 100%;
    border: none;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
  }
}
</style>
