<script setup>
import {
  ref,
  reactive,
  computed,
  watch,
  h,
  onMounted,
  onUnmounted,
  markRaw,
  nextTick,
  shallowReactive
} from "vue";
import { useNamespace, useScrollLock, useLocale } from "@yh-ui/hooks";
import { useComponentTheme } from "@yh-ui/theme";
import { YhButton } from "../../button";
import { YhInput } from "../../input";
import { YhIcon, IconClose, IconSuccess, IconWarning, IconError, IconInfo } from "../../icon";
defineOptions({
  name: "YhMessageBox",
  inheritAttrs: false
});
const ns = useNamespace("message-box");
const { t } = useLocale();
const DEFAULT_OPTIONS = {
  confirmButtonLoading: false,
  cancelButtonLoading: false,
  confirmButtonType: "primary",
  cancelButtonType: "default",
  showClose: true,
  lockScroll: true,
  draggableBoundary: true,
  autofocus: true,
  closeOnClickModal: true,
  closeOnPressEscape: true,
  width: 420
};
const props = defineProps({
  title: { type: String, required: false },
  message: { type: [String, Object, Function], required: false },
  type: { type: String, required: false },
  dangerouslyUseHTMLString: { type: Boolean, required: false },
  iconType: { type: String, required: false },
  icon: { type: null, required: false },
  confirmButtonText: { type: String, required: false },
  confirmButtonType: { type: null, required: false },
  cancelButtonText: { type: String, required: false },
  cancelButtonType: { type: null, required: false },
  showCancelButton: { type: Boolean, required: false },
  showConfirmButton: { type: Boolean, required: false },
  showClose: { type: Boolean, required: false },
  closeOnClickModal: { type: Boolean, required: false },
  closeOnPressEscape: { type: Boolean, required: false },
  lockScroll: { type: Boolean, required: false },
  glass: { type: Boolean, required: false },
  center: { type: Boolean, required: false },
  roundButton: { type: Boolean, required: false },
  draggable: { type: Boolean, required: false },
  draggableBoundary: { type: Boolean, required: false },
  width: { type: [String, Number], required: false },
  customClass: { type: String, required: false },
  inputPlaceholder: { type: String, required: false },
  inputValue: { type: String, required: false },
  inputPattern: { type: null, required: false },
  inputValidator: { type: Function, required: false },
  inputErrorMessage: { type: String, required: false },
  beforeClose: { type: Function, required: false },
  callback: { type: Function, required: false },
  appContext: { type: [Object, null], required: false },
  autofocus: { type: Boolean, required: false },
  appendTo: { type: null, required: false },
  confirmButtonLoading: { type: Boolean, required: false },
  cancelButtonLoading: { type: Boolean, required: false },
  loadingIcon: { type: null, required: false },
  themeOverrides: { type: null, required: false }
});
const { themeStyle } = useComponentTheme(
  "message-box",
  computed(() => state.options.themeOverrides)
);
const visible = ref(
  typeof window === "undefined" || !!props.title || !!props.message || !!props.type || !!props.glass
);
const boxRef = ref(null);
const inputRef = ref(null);
const state = shallowReactive({
  options: Object.assign({}, DEFAULT_OPTIONS, props),
  confirmLoading: false,
  cancelLoading: false,
  inputValue: props.inputValue || "",
  validateError: ""
});
watch(
  props,
  (val) => {
    state.options = { ...state.options, ...val };
  },
  { deep: true }
);
watch(
  () => visible.value,
  async (val) => {
    if (val && state.options.autofocus !== false) {
      await nextTick();
      if (state.options.type === "prompt") {
        inputRef.value?.focus?.();
      } else {
        boxRef.value?.focus?.({ preventScroll: true });
      }
    }
  }
);
const shouldLockScroll = computed(() => !!(visible.value && state.options.lockScroll));
useScrollLock(shouldLockScroll);
const isDragging = ref(false);
const transform = reactive({ x: 0, y: 0 });
const onMousedown = (e) => {
  if (!state.options.draggable || !boxRef.value) return;
  if (e.target.closest(`.${ns.e("close")}`)) return;
  isDragging.value = true;
  e.preventDefault();
  const startX = e.clientX;
  const startY = e.clientY;
  const { x: initialX, y: initialY } = transform;
  const onMousemove = (moveE) => {
    if (!isDragging.value) return;
    const deltaX = moveE.clientX - startX;
    const deltaY = moveE.clientY - startY;
    let nextX = initialX + deltaX;
    let nextY = initialY + deltaY;
    if (state.options.draggableBoundary && boxRef.value) {
      const rect = boxRef.value.getBoundingClientRect();
      const viewportW = window.innerWidth;
      const viewportH = window.innerHeight;
      const maxX = Math.max(0, (viewportW - rect.width) / 2);
      const maxY = Math.max(0, (viewportH - rect.height) / 2);
      nextX = Math.max(-maxX, Math.min(maxX, nextX));
      nextY = Math.max(-maxY, Math.min(maxY, nextY));
    }
    transform.x = nextX;
    transform.y = nextY;
  };
  const onMouseup = () => {
    isDragging.value = false;
    document.removeEventListener("mousemove", onMousemove);
    document.removeEventListener("mouseup", onMouseup);
  };
  document.addEventListener("mousemove", onMousemove);
  document.addEventListener("mouseup", onMouseup);
};
let isMousedownOnWrapper = false;
const handleWrapperMousedown = (e) => {
  isMousedownOnWrapper = e.target === e.currentTarget;
};
const handleWrapperClick = (e) => {
  if (isMousedownOnWrapper && e.target === e.currentTarget) {
    if (state.options.closeOnClickModal) {
      handleAction("close");
    }
  }
  isMousedownOnWrapper = false;
};
const validate = () => {
  if (state.options.type !== "prompt") return true;
  const { inputPattern, inputValidator, inputErrorMessage } = state.options;
  const value = state.inputValue;
  if (inputPattern && !inputPattern.test(value)) {
    state.validateError = inputErrorMessage || t("messagebox.error");
    return false;
  }
  if (inputValidator) {
    const result = inputValidator(value);
    if (typeof result === "string") {
      state.validateError = result;
      return false;
    } else if (result === false) {
      state.validateError = inputErrorMessage || t("messagebox.error");
      return false;
    }
  }
  state.validateError = "";
  return true;
};
watch(
  () => state.inputValue,
  () => {
    if (state.options.type === "prompt" && state.validateError) {
      validate();
    }
  }
);
const open = (options) => {
  const { appContext, ...rest } = options;
  state.options = {
    ...DEFAULT_OPTIONS,
    ...rest,
    appContext: appContext ? markRaw(appContext) : null
  };
  state.inputValue = options.inputValue || "";
  state.validateError = "";
  state.confirmLoading = false;
  state.cancelLoading = false;
  transform.x = 0;
  transform.y = 0;
  visible.value = true;
};
const close = () => {
  visible.value = false;
};
const handleAction = (action) => {
  if (action === "confirm" && !validate()) {
    return;
  }
  if (state.options.beforeClose) {
    state.options.beforeClose(action, exposed, () => {
      state.confirmLoading = false;
      state.cancelLoading = false;
      doClose(action);
    });
  } else {
    doClose(action);
  }
};
let _callback;
const setCallback = (cb) => {
  _callback = cb;
};
const doClose = (action) => {
  close();
  if (_callback) {
    _callback({
      action,
      value: state.inputValue
    });
  }
};
const onKeydown = (e) => {
  if (visible.value && state.options.closeOnPressEscape && e.code === "Escape") {
    handleAction("close");
  }
};
const handleBoxKeydown = (e) => {
  if (e.code === "Enter" && visible.value && state.options.type !== "prompt" && state.options.showConfirmButton !== false && document.activeElement === boxRef.value) {
    e.preventDefault();
    handleAction("confirm");
  }
};
onMounted(() => document.addEventListener("keydown", onKeydown));
onUnmounted(() => {
  document.removeEventListener("keydown", onKeydown);
});
const MessageContent = () => {
  const { message, dangerouslyUseHTMLString } = state.options;
  if (!message) return null;
  if (typeof message === "function") return message();
  if (typeof message === "object") return message;
  if (dangerouslyUseHTMLString) {
    return h("div", { class: ns.e("html"), innerHTML: message });
  }
  return h("span", message);
};
const boxStyle = computed(() => ({
  width: typeof state.options.width === "number" ? `${state.options.width}px` : state.options.width,
  transform: `translate(${transform.x}px, ${transform.y}px)`
}));
const exposed = reactive({
  open,
  close,
  setCallback,
  get confirmLoading() {
    return state.confirmLoading;
  },
  set confirmLoading(val) {
    state.confirmLoading = val;
  },
  get cancelLoading() {
    return state.cancelLoading;
  },
  set cancelLoading(val) {
    state.cancelLoading = val;
  }
});
defineExpose(exposed);
const getStatusIcon = (type) => {
  switch (type) {
    case "success":
      return IconSuccess;
    case "warning":
      return IconWarning;
    case "error":
      return IconError;
    default:
      return IconInfo;
  }
};
</script>

<template>
  <Transition :name="ns.b('fade')">
    <div
      v-if="visible"
      :class="ns.b('wrapper')"
      @mousedown="handleWrapperMousedown"
      @click="handleWrapperClick"
      role="dialog"
      aria-modal="true"
    >
      <div
        ref="boxRef"
        :class="[ns.b(), state.options.customClass, {
  [ns.m('glass')]: state.options.glass
}, {
  [ns.m('center')]: state.options.center
}, {
  'is-dragging': isDragging
}]"
        :style="[boxStyle, themeStyle]"
        tabindex="-1"
        @keydown="handleBoxKeydown"
      >
        <div v-if="state.options.glass" :class="ns.e('bg')" />

        <div
          :class="[ns.e('header'), {
  'is-draggable': state.options.draggable
}]"
          @mousedown="onMousedown"
        >
          <div :class="ns.e('title')">{{ state.options.title || t("messagebox.title") }}</div>
          <button
            v-if="state.options.showClose !== false"
            :class="ns.e('close')"
            @click="handleAction('close')"
            :aria-label="t('messagebox.close')"
          >
            <YhIcon :svg="IconClose.svg" :view-box="IconClose.viewBox" />
          </button>
        </div>

        <div :class="ns.e('content')">
          <div
            v-if="state.options.iconType || state.options.icon"
            :class="[ns.e('status'), ns.is(state.options.iconType || '')]"
          >
            <template v-if="state.options.icon">
              <component
                :is="typeof state.options.icon === 'string' ? YhIcon : state.options.icon"
                v-bind="typeof state.options.icon === 'string' ? {
  name: state.options.icon
} : {}"
              />
            </template>
            <YhIcon
              v-else-if="state.options.iconType"
              :svg="getStatusIcon(state.options.iconType).svg"
              :view-box="getStatusIcon(state.options.iconType).viewBox"
            />
          </div>
          <div :class="ns.e('message')">
            <MessageContent />
          </div>
        </div>

        <div v-if="state.options.type === 'prompt'" :class="ns.e('input')">
          <YhInput
            ref="inputRef"
            v-model="state.inputValue"
            :placeholder="state.options.inputPlaceholder"
            :class="{
  'is-error': !!state.validateError
}"
            @keyup.enter="handleAction('confirm')"
            @blur="validate"
            @input="state.validateError && validate()"
          />
          <Transition name="yh-zoom-in-top">
            <div v-show="state.validateError" :class="ns.e('err-msg')">
              {{ state.validateError }}
            </div>
          </Transition>
        </div>

        <div :class="ns.e('footer')">
          <YhButton
            v-if="state.options.showCancelButton !== false"
            :type="state.options.cancelButtonType"
            size="small"
            :round="state.options.roundButton"
            :loading="state.cancelLoading || state.options.cancelButtonLoading"
            :disabled="state.confirmLoading || state.cancelLoading"
            @click="handleAction('cancel')"
          >
            {{ state.options.cancelButtonText || t("messagebox.cancel") }}
          </YhButton>
          <YhButton
            v-if="state.options.showConfirmButton !== false"
            :type="state.options.confirmButtonType"
            size="small"
            :loading="state.confirmLoading || state.options.confirmButtonLoading"
            :disabled="state.confirmLoading || state.cancelLoading"
            :round="state.options.roundButton"
            @click="handleAction('confirm')"
          >
            {{ state.options.confirmButtonText || t("messagebox.confirm") }}
          </YhButton>
        </div>
      </div>
    </div>
  </Transition>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-message-box-wrapper {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.45);
  padding-right: var(--yh-scrollbar-width, 0px);
  overflow: hidden;
}

.yh-message-box {
  position: relative;
  background: var(--yh-bg-color-overlay, #ffffff);
  border-radius: 20px;
  border: 1px solid var(--yh-border-color-light, #ebeef5);
  box-shadow: 0 12px 32px -4px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.05);
  width: 420px;
  max-width: 90vw;
  outline: none;
  overflow: visible;
  transform-origin: center center;
  backface-visibility: hidden;
  transform: translate3d(0, 0, 0);
}
.yh-message-box.is-dragging {
  transition: none !important;
}
.yh-message-box--glass {
  background: transparent;
  box-shadow: none;
  border: none;
}
.yh-message-box--glass .yh-message-box__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.35));
  backdrop-filter: blur(25px) saturate(180%);
  -webkit-backdrop-filter: blur(25px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-shadow: inset 0 0 30px rgba(255, 255, 255, 0.2), 0 20px 60px rgba(0, 0, 0, 0.15);
  transform: translate3d(0, 0, 0);
  will-change: transform;
}
.yh-message-box--glass .yh-message-box__bg::before {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.04;
  pointer-events: none;
  border-radius: inherit;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme: dark) {
  .yh-message-box--glass .yh-message-box__bg {
    background: linear-gradient(135deg, rgba(45, 45, 45, 0.85), rgba(30, 30, 30, 0.7));
    border-color: rgba(255, 255, 255, 0.2);
  }
}
:root.dark .yh-message-box--glass .yh-message-box__bg, html.dark .yh-message-box--glass .yh-message-box__bg, body.dark .yh-message-box--glass .yh-message-box__bg {
  background: linear-gradient(135deg, rgba(45, 45, 45, 0.85), rgba(30, 30, 30, 0.7));
  border-color: rgba(255, 255, 255, 0.2);
}

.yh-message-box--glass .yh-message-box__header .yh-message-box__title {
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
  .yh-message-box--glass .yh-message-box__header .yh-message-box__title {
    color: #ffffff;
  }
}
:root.dark .yh-message-box--glass .yh-message-box__header .yh-message-box__title, html.dark .yh-message-box--glass .yh-message-box__header .yh-message-box__title, body.dark .yh-message-box--glass .yh-message-box__header .yh-message-box__title {
  color: #ffffff;
}

.yh-message-box--glass .yh-message-box__message {
  font-weight: 500;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
}
@media (prefers-color-scheme: dark) {
  .yh-message-box--glass .yh-message-box__message {
    color: #ffffff;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
  }
}
:root.dark .yh-message-box--glass .yh-message-box__message, html.dark .yh-message-box--glass .yh-message-box__message, body.dark .yh-message-box--glass .yh-message-box__message {
  color: #ffffff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}

.yh-message-box--glass .yh-message-box__footer .yh-button:not(.yh-button--primary) {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.2);
}
@media (prefers-color-scheme: dark) {
  .yh-message-box--glass .yh-message-box__footer .yh-button:not(.yh-button--primary) {
    color: #ffffff;
  }
}
:root.dark .yh-message-box--glass .yh-message-box__footer .yh-button:not(.yh-button--primary), html.dark .yh-message-box--glass .yh-message-box__footer .yh-button:not(.yh-button--primary), body.dark .yh-message-box--glass .yh-message-box__footer .yh-button:not(.yh-button--primary) {
  color: #ffffff;
}

.yh-message-box--center .yh-message-box__header {
  justify-content: center;
}
.yh-message-box--center .yh-message-box__header .yh-message-box__close {
  position: absolute;
  right: 16px;
  top: 16px;
}

.yh-message-box--center .yh-message-box__content {
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding-top: 24px;
}

.yh-message-box--center .yh-message-box__status {
  margin-top: 0;
  margin-bottom: 12px;
  font-size: 40px;
}

.yh-message-box--center .yh-message-box__footer {
  justify-content: center;
}

.yh-message-box__header {
  padding: 24px 24px 12px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.yh-message-box__header.is-draggable {
  cursor: move;
  user-select: none;
}

.yh-message-box__title {
  font-size: 18px;
  font-weight: 600;
  color: var(--yh-text-color-primary, #1d1d1f);
  line-height: 1.4;
  letter-spacing: -0.01em;
}

.yh-message-box__close {
  background: transparent;
  border: none;
  outline: none;
  cursor: pointer;
  color: var(--yh-text-color-placeholder);
  font-size: 20px;
  transition: all 0.2s;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
}
.yh-message-box__close:hover {
  background: rgba(0, 0, 0, 0.05);
  color: var(--yh-text-color-primary);
  transform: rotate(90deg);
}

.yh-message-box__content {
  padding: 12px 24px 24px;
  display: flex;
  align-items: center;
  gap: 16px;
}

.yh-message-box__status {
  font-size: 28px;
  display: flex;
  align-items: center;
}
.yh-message-box__status.is-success {
  color: #34c759;
}
.yh-message-box__status.is-warning {
  color: #ff9f0a;
}
.yh-message-box__status.is-error {
  color: #ff3b30;
}
.yh-message-box__status.is-info {
  color: #007aff;
}

.yh-message-box__message {
  font-size: 15px;
  color: var(--yh-text-color-regular, #3a3a3c);
  line-height: 1.6;
  flex: 1;
}

.yh-message-box__input {
  padding: 0 24px 20px;
}
.yh-message-box__input .yh-input.is-error {
  --yh-input-border-color: var(--yh-color-danger);
  --yh-input-hover-border-color: var(--yh-color-danger);
  --yh-input-focus-border-color: var(--yh-color-danger);
}

.yh-message-box__err-msg {
  color: var(--yh-color-danger);
  font-size: 12px;
  margin-top: 4px;
  padding-left: 2px;
}

.yh-message-box__footer {
  padding: 12px 24px 24px;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 12px;
}
.yh-message-box__footer .yh-button {
  min-width: 80px;
  font-weight: 500;
}
.yh-message-box__footer .yh-button--small {
  padding: 8px 16px;
  height: auto;
}

.yh-message-box {
  /* 响应式适配 */
}
@media (max-width: 480px) {
  .yh-message-box {
    width: calc(100vw - 32px);
    margin: 0 16px;
    border-radius: 16px;
  }
  .yh-message-box__header {
    padding: 20px 20px 8px;
  }
  .yh-message-box__content {
    padding: 8px 20px 20px;
  }
  .yh-message-box__footer {
    padding: 8px 20px 20px;
  }
}

.yh-zoom-in-top-enter-active,
.yh-zoom-in-top-leave-active {
  transition: opacity 0.2s, transform 0.2s;
  transform-origin: top;
}

.yh-zoom-in-top-enter-from,
.yh-zoom-in-top-leave-to {
  opacity: 0;
  transform: scaleY(0);
}

html.yh-popup-parent--hidden {
  overflow: hidden !important;
}
html.yh-popup-parent--hidden body {
  overflow: hidden !important;
}

.yh-message-box-fade-enter-active {
  transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1), transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.yh-message-box-fade-enter-active .yh-message-box__bg {
  backdrop-filter: blur(10px) saturate(150%);
}
.yh-message-box-fade-leave-active {
  transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1), transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.yh-message-box-fade-enter-from, .yh-message-box-fade-leave-to {
  opacity: 0;
}
.yh-message-box-fade-enter-from .yh-message-box, .yh-message-box-fade-leave-to .yh-message-box {
  transform: scale(0.95) translate3d(0, -15px, 0);
}
</style>
