<script setup lang="ts">
import "./player.css";
import Loading from "./Loading.vue";
import {
  FileMetas,
  PlayerEvents,
  Position,
  Thumb,
  Pagination,
  EngineComponent,
  RectangleSize,
} from "./model.ts";
import Emittery from "emittery";
import {ref, shallowRef, watch, reactive, CSSProperties, onMounted, onBeforeUnmount, computed, nextTick} from "vue";
import PptAnimationEngine from "./PPTAnimationEngine.vue";
import ImageHorizontalEngine from "./ImageHorizontalEngine.vue";
import DocumentEngine from "./DocumentEngine.vue";
import {fetchFileMetas, toBoolean} from "./utils.ts";
import DocumentControls from "./DocumentControls.vue";
import PptControls from "./PptControls.vue";

const props = withDefaults(defineProps<{
    /**
     * 文件播放地址 or 文件元信息
     */
    file: string | FileMetas | undefined;

    /**
     * 播放的初始页
     */
    defaultPage?: number;

    /**
     * 播放的初始页内第 N 个动画
     * 
     * (只针对含动画的 PPT 有效)
     */
     defaultStep?: number;

    /**
     * 播放的初始缩放比例
     */
    defaultZoom?: number;

    /**
     * 是否垂直播放
     *
     * (只针对 PPT 有效)
     */
    vertical?: boolean | "true" | "false";

    /**
     * 暗黑风格
     */
    dark?: boolean | "true" | "false";

    /**
     * 主题色
     * 
     * 用于自定义播放器的主题颜色
     */
    themeColor?: string;

    /**
     * 是否无头模式
     */
    headless?: boolean;

    /**
     * 引擎基础 URL
     *
     * 用于按需载入引擎的脚本、WASM、Iframe等。
     */
    engineBaseUrl?: string;

    /**
     * 是否开发模式
     *
     * 开发模式下，引擎地址引用的是 localhost 地址，engineBaseUrl 配置不生效
     */
    dev?: boolean;

    /**
     * 客户端类型
     *
     * 用于区分不同设备类型，以便调整播放器行为
     */
    clientType?: "pc" | "ipad" | "mobile";
}>(), {
    defaultPage:1,
    defaultStep:1,
    defaultZoom:1,
    vertical: false,
    headless: false,
    dark: false,
    dev: false,
    clientType: "pc",
    themeColor: '#00D98D'
});

const dark = computed<boolean>(() => toBoolean(props.dark));
const vertical = computed<boolean>(() => toBoolean(props.vertical));

// 内部动画状态
const animation = ref<boolean>(false);

const themeColorStyle = computed<CSSProperties>(() => {
    if (props.themeColor) {
        return {
            '--cf-player-theme-color': props.themeColor
        };
    }
    return {};
});

// 引擎类型判断
const isDocumentEngine = computed<boolean>(() => {
    return !!(metas.value && metas.value.type === 'document');
});

const isPptAnimationEngine = computed<boolean>(() => {
    return !!(metas.value && metas.value.type === 'ppt' && metas.value.animation && animation.value);
});

const isImageHorizontalEngine = computed<boolean>(() => {
    return !!(metas.value && metas.value.type === 'ppt' && !animation.value && !vertical.value);
});

const emit = defineEmits<{
    paginationChange: [pagination: Pagination];
    zoomChange: [zoom: number];
    fullscreen: [];
}>();

const playerEl = ref<HTMLElement | null>(null);
const engineEl = ref<EngineComponent | null>(null);
const viewportStyle = reactive<CSSProperties>({});
const playerSize = reactive<RectangleSize>({
    width: 0,
    height: 0,
});
const viewportSize = reactive<RectangleSize>({
    width: 0,
    height: 0,
});

const metas = shallowRef<FileMetas|null>(null);

// 控制组件的状态
const currentPage = ref<number>(props.defaultPage);
const currentZoom = ref<number>(props.defaultZoom);

const emitter = new Emittery<PlayerEvents>();
emitter.on("paginationChange", (pagination: Pagination) => {
    emit("paginationChange", pagination);
});

const loading = ref(false);

onMounted(() => {
    console.log("[cf-player] mounted, version: 0.5.0");
});

watch([() => props.file], async () => {
    if (!props.file) {
        return ;
    }
    if (typeof props.file === "string") {
      const fileMetas = await fetchFileMetas(props.file);
      if (!fileMetas) {
        return ;
      }
      metas.value = fileMetas;
    } else {
        metas.value = props.file;
    }
    if (!metas.value) {
        return ;
    }
    // 根据文件元信息初始化动画状态
    animation.value = !!(metas.value.type === 'ppt' && metas.value.animation);
}, {immediate: true});

// 监听动画状态变化，重新加载
watch(animation, async () => {
    if (!props.file || !metas.value) {
        return;
    }
    console.log('[cf-player] animation mode changed, reloading...');
    // 触发重新加载：先清空再重新设置
    const currentMetas = metas.value;
    loading.value = true;
    metas.value = null;
    await nextTick();
    metas.value = currentMetas;
    loading.value = false;
});

const prev = (): void => {
    engineEl.value?.prev();
}

const next = (): void => {
    engineEl.value?.next();
}

const goto = (pos: Position): void => {
    engineEl.value?.goto(pos);
}

const zoomIn = (): void => {
  engineEl.value?.zoomIn();
}

const zoomOut = (): void => {
  engineEl.value?.zoomOut();
}
const zoomTo = (zoom: number): void => {
  engineEl.value?.zoomTo(zoom);
}

const thumbs = (): Thumb[] => {
    if (!metas.value || !metas.value.page || !metas.value.thumb) {
        return [];
    }
    const thumbs: Thumb[] = [];
    for (let i = 1; i <= metas.value.page; i++) {
        thumbs.push({page: i, url: metas.value.thumb + i});
    }
    return thumbs;
}

// ------------------------------------------------------------------------------------------------ 引擎事件监听

const onEngineLoading = (loadingVal: boolean) => {
    loading.value = loadingVal;
}

const onEnginePaginationChange = (pagination: Pagination) => {
    currentPage.value = pagination.current;
    emit("paginationChange", pagination);
}

const onEngineZoomChange = (zoom: number) => {
    currentZoom.value = zoom;
    emit("zoomChange", zoom);
}

// ------------------------------------------------------------------------------------------------ 控制组件事件处理

const handlePageChange = (page: number) => {
    currentPage.value = page;
    goto({ page });
}

const handleZoomChange = (zoom: number) => {
    currentZoom.value = zoom;
    zoomTo(zoom);
}

const handleModeChange = (mode: boolean) => {
    animation.value = mode;
}

const handlePrevPage = () => {
    prev();
}

const handleNextPage = () => {
    next();
}

const handleFullscreen = () => {
    emit('fullscreen');
}

// ------------------------------------------------------------------------------------------------ 重置视窗窗口大小
const resizeViewport = () => {
    const aspectRatio = 16/9;

    playerSize.width = (playerEl.value as HTMLElement).clientWidth;
    playerSize.height = (playerEl.value as HTMLElement).clientHeight;

    console.log(`[cf-player] resize viewport, width ${playerSize.width}, height ${playerSize.height}`);

    if (playerSize.width / playerSize.height > aspectRatio) {
        viewportSize.width = Math.round(playerSize.height * aspectRatio);
        viewportSize.height = playerSize.height;

        viewportStyle.width = `${viewportSize.width}px`;
        viewportStyle.height = `${playerSize.height}px`;
        viewportStyle.top = "0";
        viewportStyle.left = `${Math.round((playerSize.width - viewportSize.width) / 2)}px`;
    } else {
        viewportSize.width = playerSize.width;
        viewportSize.height = Math.round(playerSize.width * (1/aspectRatio));

        viewportStyle.width = `${playerSize.width}px`;
        viewportStyle.height = `${viewportSize.height}px`;
        viewportStyle.top = `${Math.round(playerSize.height - viewportSize.height) / 2}px`;
        viewportStyle.left = "0";
    }
}

const viewportObserver = new ResizeObserver(() => {
    console.log(`[cf-player] resize viewport by observer.`);
    resizeViewport();
});

onMounted(() => {
    viewportObserver.observe(playerEl.value as HTMLElement);
    console.log(`[cf-player] resize viewport, when on mounted.`);
    resizeViewport();
});

onBeforeUnmount(() => {
    viewportObserver.unobserve(playerEl.value as HTMLElement);
});

defineExpose({
    prev,
    next,
    goto,
    zoomIn,
    zoomOut,
    zoomTo,
    thumbs,
});
</script>

<template>
    <div ref="playerEl" class="cf-player" :class="{'cf-player--dark': dark, 'cf-player--pc': clientType === 'pc'}" :style="themeColorStyle">
        <div class="cf-player__viewport" :style="viewportStyle">
            <DocumentEngine
                v-if="isDocumentEngine && metas"
                ref="engineEl"
                :metas="metas"
                :viewport-size="viewportSize"
                :default-page="props.defaultPage"
                :default-zoom="props.defaultZoom"
                :engine-base-url="engineBaseUrl"
                :dev="props.dev"
                @loading="onEngineLoading"
                @paginationChange="onEnginePaginationChange"
                @zoomChange="onEngineZoomChange"
            />
            <PptAnimationEngine
                v-if="isPptAnimationEngine && metas"
                ref="engineEl"
                :metas="metas"
                :default-page="props.defaultPage"
                :default-step="props.defaultStep"
                :engine-base-url="engineBaseUrl"
                :dev="props.dev"
                @loading="onEngineLoading"
                @paginationChange="onEnginePaginationChange"
                class="cf-player__engine cf-player__engine--animation"
            />
            <ImageHorizontalEngine
                v-else-if="isImageHorizontalEngine && metas"
                ref="engineEl"
                :metas="metas"
                :viewport-size="viewportSize"
                :default-page="props.defaultPage"
                :engine-base-url="engineBaseUrl"
                :dev="props.dev"
                @loading="onEngineLoading"
                @paginationChange="onEnginePaginationChange"
            />
            <div class="cf-player__mask" v-if="loading" >
                <Loading /> <span class="cf-player__mask__text">文件加载中...</span>
            </div>
        </div>
        <div class="cf-player__controls" :class="`cf-player__controls--${props.clientType}`">
            <DocumentControls
                v-if="isDocumentEngine"
                :current-page="currentPage"
                :max-page="metas?.page || 1"
                :current-zoom="currentZoom"
                :client-type="clientType"
                :theme-color="themeColor"
                @page-change="handlePageChange"
                @zoom-change="handleZoomChange"
                @fullscreen="handleFullscreen"
            />
            
            <PptControls
                v-else-if="isPptAnimationEngine || isImageHorizontalEngine"
                :animation="animation"
                :current-page="currentPage"
                :max-page="metas?.page || 1"
                :client-type="clientType"
                @mode-change="handleModeChange"
                @page-change="handlePageChange"
                @prev-page="handlePrevPage"
                @next-page="handleNextPage"
                @fullscreen="handleFullscreen"
            />
        </div>
    </div>
</template>
