import type { Component, Ref } from 'vue';
import { defineComponent, h, inject, mergeProps, provide, ref, shallowRef } from 'vue';
import __FloatPanel_vue from './FloatPanel.vue';
import type { FloatPanelApi, FloatPanelContentBindings, FloatPanelInstance } from './types';
import { floatPanelContextKey } from './types';

/**
 * 在 `useFloatPanel` 提供的内容子树内调用，获取指向 `FloatPanelInstance` 的 ref。
 * @throws 若在 provide 范围外调用
 */
export function injectFloatPanelContext(): Ref<FloatPanelInstance | undefined> {
  const injected = inject(floatPanelContextKey, undefined);
  if (injected === undefined) {
    throw new Error('injectFloatPanelContext() 须在 useFloatPanel 的 Wrapper 内容树内使用');
  }
  return injected;
}

/**
 * 声明式挂载浮动面板：返回 `[Wrapper, api]`。
 *
 * - **Wrapper**：放入模板根或任意位置；`open()` 后才会渲染内部 `FloatPanel`。
 * - **api**：`open` / `close` 及与 `FloatPanelInstance` 一致的面板操作。
 *
 * 关闭卸载由 `FloatPanel` 在动画结束后 **`emit('closed')`** 触发，不在此重复定时逻辑。
 *
 * @param Content - 可选内容组件；不传则用 Wrapper 的默认插槽（作用域参数含 `onComplete` / `onCancel`）
 */
export function useFloatPanel<P extends Record<string, any> = Record<string, unknown>, R = P>(
  Content?: Component,
) {
  let pending: PromiseWithResolvers<R | void> | undefined;
  /** `closeAnimated` 结束时交给 `onClosed` 再 `settle` */
  let dismissResult: R | void | undefined;

  const show = ref(false);
  /** 为 true 表示正在等待 FloatPanel `closed`，用于忽略重开后的陈旧 `closed` */
  const pendingCloseUnmount = ref(false);
  const panelOpenKey = ref(0);
  const innerProps = shallowRef<P>();
  const floatPanelRef = ref<FloatPanelInstance>();

  function settle(result?: R | void) {
    pending?.resolve(result);
    pending = undefined;
  }

  function closePanel(result?: R) {
    const inst = floatPanelRef.value;
    if (inst?.closeAnimated) {
      pendingCloseUnmount.value = true;
      dismissResult = result;
      inst.closeAnimated();
    } else {
      pendingCloseUnmount.value = false;
      dismissResult = undefined;
      settle(result);
      show.value = false;
    }
  }

  const api: FloatPanelApi<P, R> = {
    open(props: P): Promise<R | void> {
      pending?.resolve(void 0);
      pending = Promise.withResolvers<R | void>();
      dismissResult = undefined;
      pendingCloseUnmount.value = false;
      innerProps.value = props;
      panelOpenKey.value += 1;
      show.value = true;
      return pending.promise;
    },
    close(result?: R) {
      closePanel(result);
    },
    getAnchors() {
      return floatPanelRef.value?.getAnchors() ?? [];
    },
    setAnchors(value, reset) {
      floatPanelRef.value?.setAnchors(value, reset);
    },
    setHeight(value, updateAnchor) {
      floatPanelRef.value?.setHeight(value, updateAnchor);
    },
    closeAnimated() {
      floatPanelRef.value?.closeAnimated();
    },
    snapMin() {
      return floatPanelRef.value?.snapMin() ?? false;
    },
    snapMax() {
      return floatPanelRef.value?.snapMax() ?? false;
    },
    snapFull() {
      return floatPanelRef.value?.snapFull() ?? false;
    },
    snapToFirstAnchor() {
      return floatPanelRef.value?.snapToFirstAnchor() ?? false;
    },
    snapToLastAnchor() {
      return floatPanelRef.value?.snapToLastAnchor() ?? false;
    },
    snapToMaxContainerHeight() {
      return floatPanelRef.value?.snapToMaxContainerHeight() ?? false;
    },
  };

  const Wrapper = defineComponent({
    name: 'FloatPanelWrapper',
    inheritAttrs: false,
    setup(_, { attrs, slots }) {
      provide(floatPanelContextKey, floatPanelRef);

      return () => {
        if (!show.value) return null;

        const attrObj = attrs as Record<string, unknown>;
        const panelBindings = mergeProps(attrObj, {
          'onUpdate:panelHeight': (value: number) => {
            if (value !== 0) {
              show.value = true;
              return;
            }
            if (!pendingCloseUnmount.value) {
              pendingCloseUnmount.value = true;
            }
          },
          onClosed: () => {
            if (!pendingCloseUnmount.value) return;
            pendingCloseUnmount.value = false;
            const r = dismissResult;
            dismissResult = undefined;
            settle(r);
            show.value = false;
          },
        });

        return h(
          __FloatPanel_vue,
          { ref: floatPanelRef, key: panelOpenKey.value, closable: true, ...panelBindings },
          {
            header: slots.header,
            content: () => {
              const slotProps = innerProps.value ?? ({} as P);
              const contentBindings = mergeProps(slotProps as Record<string, unknown>, {
                onComplete: (result?: R) => closePanel(result),
                onCancel: () => closePanel(),
              });
              if (Content) return h(Content, contentBindings);
              return slots.default?.(contentBindings as FloatPanelContentBindings<P, R>) ?? null;
            },
          },
        );
      };
    },
  });

  return [Wrapper, api] as const;
}
