'use strict';

const { updateElementReadonly, updateElementInvalid, setPropValue, getPropValue } = require('../../utils/assets');


exports.template = /* html */`
<div class="container">
    <div class="animator-config">
        <ui-prop class="animator-config-import-all-animator">
            <ui-label slot="label" value="i18n:ENGINE.assets.fbx.GlTFUserData.mountAllAnimationsOnPrefab.name"></ui-label>
            <ui-checkbox slot="content"></ui-checkbox>
        </ui-prop>
    </div>
    <div class="show-type-wrap">
        <ui-tab class="show-type" value="0">
            <ui-button value="time">Time</ui-button>
            <ui-button value="frame">Frame</ui-button>
        </ui-tab>
    </div>
    <div class="clips"></div>
    <div class="editor">
        <div class="anim-name">
            <ui-icon value="video"></ui-icon>
            <ui-input class="clip-name"></ui-input>
        </div>
        <div class="clip-info">
            <div class="left">Time: <span class="clip-duration"></span> (s)</div>
            <div class="right">
                <ui-num-input class="clip-fps" min="1" max="120" step="1"></ui-num-input>
                FPS
            </div>
        </div>
        <div class="edit-ruler">
            <div class="grid ruler-making"></div>
            <div class="grid ruler-gear"></div>
            <div class="control-wrap">
                <div class="control-duration"></div>
                <div class="control control-left">
                    <div class="box"></div>
                    <div class="direction"></div>
                </div>
                <div class="control control-right">
                    <div class="box"></div>
                    <div class="direction"></div>
                </div>
                <div class="control control-virtual">
                    <div class="box"></div>
                    <div class="direction"></div>
                    <div class="number control-virtual-number"></div>
                </div>
            </div>
        </div>
        <div class="cut-info">
            <div class="left">
                <span>Start: </span>
                <ui-num-input path="from" step="1" min="0" class="clip-from"></ui-num-input>
            </div>
            <div class="frames-info">
                <div class="left">Frames: <span class="clip-frames"></span></div>
            </div>
            <div class="right">
                <span>End: </span>
                <ui-num-input path="to" step="1" min="0" class="clip-to"></ui-num-input>
            </div>
        </div>
        <ui-prop>
            <span slot="label">WrapMode</span>
            <ui-select slot="content" class="wrap-mode">
                <option value="0">Default</option>
                <option value="1">Normal</option>
                <option value="2">Loop</option>
                <option value="22">PingPong</option>
                <option value="36">Reverse</option>
                <option value="38">LoopReverse</option>
            </ui-select>
        </ui-prop>
        <ui-prop>
            <span slot="label">Speed</span>
            <ui-num-input slot="content" class="speed"></ui-num-input>
        </ui-prop>
        <ui-section expand cache-expand="inspector-asset-fbx-animation-additive">
            <ui-label slot="header" value="i18n:ENGINE.assets.fbx.animationSetting.additive.header"></ui-label>
            <ui-prop>
                <ui-label slot="label" value="i18n:ENGINE.assets.fbx.animationSetting.additive.enabled.label"></ui-label>
                <ui-checkbox slot="content" class="additive-enabled"
                    tooltip="i18n:ENGINE.assets.fbx.animationSetting.additive.enabled.tooltip"></ui-checkbox>
            </ui-prop>
            <ui-prop ui="asset">
                <ui-label slot="label" value="i18n:ENGINE.assets.fbx.animationSetting.additive.refClip.label"></ui-label>
                <ui-asset slot="content" droppable="cc.AnimationClip" class="ref-clip"
                    tooltip="i18n:ENGINE.assets.fbx.animationSetting.additive.refClip.tooltip"></ui-asset>
            </ui-prop>
        </ui-section>
    </div>
    <ui-label class="multiple-warn-tip" value="i18n:ENGINE.assets.multipleWarning"></ui-label>
</div>
`;

exports.style = /* css */`
.container {
    padding: 4px;
}

.container[multiple-invalid] > *:not(.multiple-warn-tip) {
    display: none!important;
 }

 .container[multiple-invalid] > .multiple-warn-tip {
    display: block;
 }

.container .multiple-warn-tip {
    display: none;
    text-align: center;
    color: var(--color-focus-contrast-weakest);
    margin-top: 8px;
}
.container > .show-type-wrap {
    text-align: center;
    margin-top: 8px;
}
.container > .clips {
    padding: 4px;
    border-radius: calc(var(--size-normal-radius) * 1px);
    overflow-y: auto;
    max-height: 250px;
    background: var(--color-normal-fill-emphasis);
    margin-bottom: 8px;
}
.container > .clips > .clip {}
.container > .clips > .clip > .table {
    border-radius: 4px;
    border-bottom-right-radius: 0;
    background: var(--color-normal-fill-emphasis);
}
.container > .clips > .clip > .table > .header {
    display: flex;
    padding: 0 4px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--color-default-border);
    color: var(--color-default-fill-weakest);
}
.container > .clips > .clip > .table > .line {
    display: flex;
    padding: 0 4px;
    cursor: pointer;
}
.container > .clips > .clip > .table > .line[active] {
    background-color: var(--color-info-fill-important);
}
.container > .clips > .clip > .table > .line > .name,
.container > .clips > .clip > .table > .header > .name {
    flex: 1;
}
.container > .clips > .clip > .table > .line > .time,
.container > .clips > .clip > .table > .header > .time {
    width: 40px;
    text-align: right;
}
.container > .clips > .clip > .add-clip {
    display: flex;
    justify-content: flex-end;
}
.container > .clips > .clip > .add-clip > .button > ui-icon {
    padding: 0 4px;
    border-radius: 2px;
    line-height: 16px;
    margin-left: 10px;
    margin-right: 2px;
    cursor: pointer;
}
.container > .clips > .clip > .add-clip > .button > ui-icon:hover {
    background: var(--color-normal-fill);
}

.container > .clips > .clip > .add-clip > .button > ui-icon[disabled] {
    opacity: 0.55;
    pointer-events: none;
}

.container > .editor[disabled] {
    opacity: 0.55;
    pointer-events: none;
}

.container > .editor > .anim-name {
    display: flex;
    justify-content: space-between;
    margin-top: 10px;
    margin-bottom: 5px;
}
.container > .editor > .anim-name {
    display: flex;
    justify-content: space-between;
    margin-top: 10px;
    margin-bottom: 5px;
}
.container > .editor > .anim-name > ui-icon {
    font-size: 18px;
    margin: auto 10px;
    margin-left: 0;
}
.container > .editor > .anim-name > ui-input {
    flex: 1;
}
.container > .editor > .clip-info {
    font-size: 11px;
    color: var(--color-normal-fill-weakest);
    display: flex;
    justify-content: space-between;
    margin-bottom: 5px;
}
.container > .editor ui-num-input {
    color: var(--color-normal-contrast);
    width: 50px;
}
.container > .editor > .cut-info {
    margin: 5px 0 10px;
    line-height: 20px;
    display: flex;
    justify-content: space-between;
}
.container > .editor > .cut-info > .frames-info {
    font-size: 11px;
    color: var(--color-normal-fill-weakest);
}
.container > .editor > .edit-ruler {
    border: 1px solid var(--color-normal-contrast-important);
    border-top: transparent;
    padding: 0 20px;
    display: flex;
    flex-direction: column;
    position: relative;
    user-select: none;
}
.container > .editor > .edit-ruler > .grid {
    position: relative;
    display: flex;
    font-size: 10px;
    box-sizing: border-box;
    height: 18px;
}
.container > .editor > .edit-ruler > .grid > .label-item {
    position: absolute;
}
.container > .editor > .edit-ruler > .ruler-gear > .start {
    position: absolute;
    top: 3px;
}
.container > .editor > .edit-ruler > .ruler-gear > .grid-item {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
}
.container > .editor > .edit-ruler > .grid .sm-grid {
    display: inline-block;
    width: 20%;
    height: 4px;
    border-left: 1px solid var(--color-normal-contrast-important);
}
.container > .editor > .edit-ruler > .grid .mid-grid {
    display: inline-block;
    width: 20%;
    height: 8px;
    border-left: 1px solid var(--color-normal-contrast-important);
}
.container > .editor > .edit-ruler > .control-wrap {
    position: relative;
    width: 100%;
    left: 0;
    top: -16px;
}
.container > .editor > .edit-ruler > .control-wrap > .control-duration {
    background: var(--color-focus-fill);
    position: absolute;
    height: 16px;
    opacity: 0.2;
}
.container > .editor > .edit-ruler > .control-wrap > .control {
    user-select: none;
    width: 6px;
    position: absolute;
    cursor: pointer;
    opacity: 0.7;
}
.container > .editor > .edit-ruler > .control-wrap > .control:hover {
    opacity: 1;
}
.container > .editor > .edit-ruler > .control-wrap > .control > .box {
    background: var(--color-focus-fill);
    width: 100%;
    height: 10px;
}
.container > .editor > .edit-ruler > .control-wrap > .control > .direction {
    border: 3px solid var(--color-focus-fill);
    width: 0;
    height: 0;
    border-bottom-color: transparent;
}
.container > .editor > .edit-ruler > .control-wrap > .control-left > .direction {
    border-left-color: transparent;
}
.container > .editor > .edit-ruler > .control-wrap > .control-right > .direction {
    border-right-color: transparent;
}
.container > .editor > .edit-ruler > .control-wrap > .control-virtual {
    --color-focus-fill: var(--color-success-fill);
    display: none;
}
.container > .editor > .edit-ruler > .control-wrap > .control-virtual > .box {
    background: var(--color-focus-fill);
    width: 100%;
    height: 10px;
}
.container > .editor > .edit-ruler > .control-wrap > .control-virtual[direction="right"] > .direction {
    border-right-color: transparent;
    border-left-color: var(--color-focus-fill);
}

.container > .editor > .edit-ruler > .control-wrap > .control-virtual > .direction {
    border-left-color: transparent;
}
.container > .editor > .edit-ruler > .control-wrap > .control-virtual > .number {
    margin-top: -47px;
    font-size: 10px;
    color: var(--color-success-fill-weaker);
}
`;

exports.$ = {
    container: '.container',
    importAllAnimationsCheckbox: '.animator-config-import-all-animator ui-checkbox',
    importAllAnimatorWrap: '.animator-config-import-all-animator',
    clips: '.clips',
    editor: '.editor',
    clipName: '.clip-name',
    clipDuration: '.clip-duration',
    clipFPS: '.clip-fps',
    clipFrom: '.clip-from',
    clipTo: '.clip-to',
    clipFrames: '.clip-frames',
    wrapMode: '.wrap-mode',
    speed: '.speed',
    additiveEnabled: '.additive-enabled',
    refClip: '.ref-clip',
    rulerMaking: '.ruler-making',
    rulerGear: '.ruler-gear',
    controlWrap: '.control-wrap',
    controlDuration: '.control-duration',
    controlLeft: '.control-left',
    controlRight: '.control-right',
    controlVirtual: '.control-virtual',
    controlVirtualNumber: '.control-virtual-number',
    showTypeWrap: '.show-type-wrap',
    showType: '.show-type',
};

/**
 * attribute corresponds to the edit element
 */
const Elements = {
    // infos put first
    infos: {
        ready() {
            const panel = this;

            Object.assign(panel, {
                animationInfos: null,
            });
        },
        update() {
            const panel = this;

            if (panel.meta && panel.meta.userData.animationImportSettings) {
                panel.animationInfos = panel.meta.userData.animationImportSettings;
                // Support multiple selection when the list display, limit the number of display clip name collection for renaming and new to determine whether the same name
                panel.clipNames = new Set();
                for (const animationInfo of panel.animationInfos) {
                    panel.clipNames.add(animationInfo.name);
                    for (const subAnimInfo of animationInfo.splits) {
                        panel.clipNames.add(subAnimInfo.name);
                    }
                }
            } else {
                panel.animationInfos = null;
            }
        },
    },
    showType: {
        ready() {
            const panel = this;
            panel.animationTimeShowType = panel.$.showType.value === 0 ? 'time' : 'frame';
            panel.$.showType.addEventListener('change', (event) => {
                panel.animationTimeShowType = event.target.value === 0 ? 'time' : 'frame';
                Elements.clips.update.call(panel);
            });
        },
        update() {
            const panel = this;

            if (!panel.animationInfos) {
                panel.$.showTypeWrap.style.display = 'none';
                return;
            } else {
                panel.$.showTypeWrap.style.display = 'block';
            }

            panel.animationTimeShowType = panel.$.showType.value === 0 ? 'time' : 'frame';
        },
    },
    clips: {
        ready() {
            const panel = this;
            Object.assign(panel, {
                splitClipIndex: 0,
                rawClipIndex: 0,
                currentClipInfo: null,
            });
        },
        update() {
            const panel = this;

            panel.$.clips.innerText = '';
            if (!panel.animationInfos) {
                panel.$.clips.style.display = 'none';
                return;
            } else {
                panel.$.clips.style.display = 'block';
            }

            panel.updateRawClipInfo();
            panel.updateCurrentClipInfo();

            panel.animationInfos.forEach((animInfo, rawClipIndex) => {
                const clip = document.createElement('div');
                clip.setAttribute('class', 'clip');
                panel.$.clips.appendChild(clip);

                if (!animInfo.duration) {
                    clip.setAttribute('disabled', 'true');
                }

                const table = document.createElement('div');
                table.setAttribute('class', 'table');
                clip.appendChild(table);

                const header = document.createElement('div');
                header.setAttribute('class', 'header');
                table.appendChild(header);

                const name = document.createElement('div');
                name.setAttribute('class', 'name');
                name.innerHTML = `Clips <i>( ${animInfo.name} )</i> `;
                header.appendChild(name);
                const time = document.createElement('div');
                time.setAttribute('class', 'time');
                time.innerHTML = 'Start';
                header.appendChild(time);
                const timeEnd = document.createElement('div');
                timeEnd.setAttribute('class', 'time end');
                timeEnd.innerHTML = 'End';
                header.appendChild(timeEnd);

                animInfo.splits.forEach((subAnim, splitClipIndex) => {
                    const line = document.createElement('div');
                    line.setAttribute('class', 'line');
                    if (panel.rawClipIndex === rawClipIndex && panel.splitClipIndex === splitClipIndex) {
                        line.setAttribute('active', true);
                    }
                    table.appendChild(line);
                    line.setAttribute('rawCLipIndex', rawClipIndex);
                    line.setAttribute('splitClipIndex', splitClipIndex);
                    line.addEventListener('click', () => {
                        panel.onSelect(rawClipIndex, splitClipIndex);
                    });

                    const name = document.createElement('div');
                    name.setAttribute('class', 'name');
                    name.innerHTML = subAnim.name;
                    line.appendChild(name);
                    const time = document.createElement('div');
                    time.setAttribute('class', 'time');
                    time.innerHTML = panel.animationTimeShowType === 'time' ? subAnim.from.toFixed(3) : Math.round(subAnim.from * (subAnim.fps || panel.rawClipInfo.fps));
                    line.appendChild(time);
                    const timeEnd = document.createElement('div');
                    timeEnd.setAttribute('class', 'time end');
                    timeEnd.innerHTML = panel.animationTimeShowType === 'time' ? subAnim.to.toFixed(3) : Math.round(subAnim.to * (subAnim.fps || panel.rawClipInfo.fps));
                    line.appendChild(timeEnd);
                });

                // Button area
                const addClip = document.createElement('div');
                addClip.setAttribute('class', 'add-clip');
                clip.appendChild(addClip);

                const button = document.createElement('div');
                button.setAttribute('class', 'button');
                addClip.appendChild(button);
                const addIcon = document.createElement('ui-icon');
                addIcon.setAttribute('value', 'add');
                addIcon.setAttribute('tooltip', 'Duplicate Selected');
                updateElementReadonly.call(panel, addIcon);
                button.appendChild(addIcon);
                addIcon.addEventListener('click', () => {
                    const newInfo = panel.newClipTemplate();
                    panel.clipNames.add(newInfo.name);
                    panel.animationInfos[panel.rawClipIndex].splits.push(newInfo);

                    Elements.clips.update.call(panel);
                    Elements.editor.update.call(panel);
                    panel.dispatch('change');
                    panel.dispatch('snapshot');
                });

                const miniIcon = document.createElement('ui-icon');
                miniIcon.setAttribute('value', 'mini');
                miniIcon.setAttribute('tooltip', 'Remove Selected');
                updateElementReadonly.call(panel, miniIcon);
                button.prepend(miniIcon);
                miniIcon.addEventListener('click', () => {
                    panel.updateCurrentClipInfo();

                    if (!panel.currentClipInfo) {
                        return;
                    }
                    panel.clipNames.delete(panel.currentClipInfo.name);
                    panel.animationInfos[panel.rawClipIndex].splits.splice(panel.splitClipIndex, 1);
                    const length = panel.animationInfos[panel.rawClipIndex].splits.length;
                    if (length > 0 && panel.splitClipIndex > 0 && panel.splitClipIndex >= length) {
                        panel.splitClipIndex = length - 1;
                    }
                    Elements.clips.update.call(panel);
                    Elements.editor.update.call(panel);
                    panel.dispatch('change');
                    panel.dispatch('snapshot');
                });
            });
        },
    },
    editor: {
        ready() {
            const panel = this;

            Object.assign(panel, {
                gridTableWith: panel.$.container.getBoundingClientRect().width,
                virtualControl: null,
                clipNames: [],
            });
            panel.onClipNameBind = panel.onClipName.bind(panel);
            panel.$.clipName.addEventListener('confirm', panel.onClipNameBind);

            panel.onMouseDownBindLeft = panel.onMouseDown.bind(panel, 'left');
            panel.onMouseDownBindRight = panel.onMouseDown.bind(panel, 'right');
            panel.$.controlLeft.addEventListener('mousedown', panel.onMouseDownBindLeft);
            panel.$.controlRight.addEventListener('mousedown', panel.onMouseDownBindRight);

            panel.onCutClipBind = panel.onCutClip.bind(panel);
            panel.$.clipFrom.addEventListener('confirm', panel.onCutClipBind);
            panel.$.clipTo.addEventListener('confirm', panel.onCutClipBind);

            panel.onFpsChangeBind = panel.onFpsChange.bind(panel);
            panel.$.clipFPS.addEventListener('confirm', panel.onFpsChangeBind);

            panel.onWrapModeChangeBind = panel.onWrapModeChange.bind(panel);
            panel.$.wrapMode.addEventListener('confirm', panel.onWrapModeChangeBind);

            panel.onSpeedChangeBind = panel.onSpeedChange.bind(panel);
            panel.$.speed.addEventListener('confirm', panel.onSpeedChangeBind);

            panel.onAdditiveEnabledChangedBind = panel.onAdditiveEnabledChanged.bind(panel);
            panel.$.additiveEnabled.addEventListener('confirm', panel.onAdditiveEnabledChangedBind);

            panel.onRefClipChangedBind = panel.onRefClipChanged.bind(panel);
            panel.$.refClip.addEventListener('confirm', panel.onRefClipChangedBind);

            function observer() {
                const rect = panel.$.editor.getBoundingClientRect();
                panel.gridTableWith = rect.width - 60;

                if (panel.gridTableWith < 0) {
                    panel.gridTableWith = panel.$.container.getBoundingClientRect().width;
                }

                Elements.clips.update.call(panel);
                Elements.editor.update.call(panel);
            }

            panel.resizeObserver = new window.ResizeObserver(observer);
            panel.resizeObserver.observe(panel.$.editor);
            observer();
        },
        close() {
            const panel = this;
            panel.resizeObserver.unobserve(panel.$.editor);

            panel.$.clipName.removeEventListener('confirm', panel.onClipNameBind);

            panel.$.controlLeft.removeEventListener('mousedown', panel.onMouseDownBindLeft);
            panel.$.controlRight.removeEventListener('mousedown', panel.onMouseDownBindRight);

            panel.$.clipFrom.removeEventListener('confirm', panel.onCutClipBind);
            panel.$.clipTo.removeEventListener('confirm', panel.onCutClipBind);

            panel.$.clipFPS.removeEventListener('confirm', panel.onFpsChangeBind);

            panel.$.wrapMode.removeEventListener('confirm', panel.onWrapModeChangeBind);
            panel.$.speed.removeEventListener('confirm', panel.onSpeedChangeBind);
            panel.$.additiveEnabled.removeEventListener('confirm', panel.onAdditiveEnabledChangedBind);
            panel.$.refClip.removeEventListener('confirm', panel.onRefClipChangedBind);
        },
        update() {
            const panel = this;

            panel.updateRawClipInfo();
            panel.updateCurrentClipInfo();
            panel.updateGridConfig();

            if (!panel.currentClipInfo) {
                panel.$.editor.style.display = 'none';
                return;
            } else {
                panel.$.editor.style.display = 'block';
            }

            updateElementReadonly.call(panel, panel.$.editor);

            panel.$.clipName.value = panel.currentClipInfo.name;

            // ruler making
            panel.$.rulerMaking.innerText = '';
            const maxNum = panel.gridConfig.mod + 1;
            for (let minNum = 1; minNum <= maxNum; minNum++) {
                // If the remaining cells are less than 1.5, hide the label
                if (((panel.gridConfig.width / panel.gridConfig.spacing) % 5) >= 1.5 || minNum !== maxNum) {
                    const label = document.createElement('div');
                    label.setAttribute('class', 'label-item');
                    label.style.left = `${panel.gridConfig.spacing * 5 * (minNum - 1) - 6}px`;
                    panel.$.rulerMaking.appendChild(label);
                    const span = document.createElement('span');
                    span.setAttribute('class', 'mid-label');
                    span.innerText = (panel.gridConfig.labelStep * (minNum - 1)).toFixed(3);
                    label.appendChild(span);
                }
            }
            const lastMakingLabel = document.createElement('div');
            lastMakingLabel.setAttribute('class', 'label-item');
            lastMakingLabel.style.left = `${panel.gridConfig.width}px`;
            panel.$.rulerMaking.appendChild(lastMakingLabel);
            lastMakingLabel.innerText = panel.rawClipInfo.duration.toFixed(3);

            // ruler gear
            panel.$.rulerGear.innerText = '';
            Object.assign(panel.$.rulerGear.style, {
                'margin-left': `${panel.gridConfig.spacing}px`,
            });
            const firstRulerGear = document.createElement('div');
            firstRulerGear.setAttribute('class', 'start');
            firstRulerGear.style.left = `${0 - panel.gridConfig.spacing}px`;
            firstRulerGear.innerHTML = '<span class="mid-grid"></span>';
            panel.$.rulerGear.appendChild(firstRulerGear);

            for (let minNum = 0; minNum < panel.gridConfig.mod; minNum++) {
                const item = document.createElement('div');
                item.setAttribute('class', 'grid-item');
                item.style.width = `${panel.gridConfig.spacing * 5}px`;
                const lines = Array(4).fill('<span class="sm-grid"></span>');
                lines.push('<span class="mid-grid"></span>');
                item.innerHTML = lines.join('');
                panel.$.rulerGear.appendChild(item);
            }

            const lastGearItem = document.createElement('div');
            lastGearItem.setAttribute('class', 'grid-item');
            lastGearItem.style.width = `${panel.gridConfig.rest * panel.gridConfig.spacing}px`;
            lastGearItem.innerHTML = Array(panel.gridConfig.rest).fill('<span class="sm-grid"></span>').join('');
            panel.$.rulerGear.appendChild(lastGearItem);

            // control-wrap
            Object.assign(panel.$.controlDuration.style, panel.currentClipInfo.durationStyle);
            Object.assign(panel.$.controlLeft.style, panel.currentClipInfo.ctrlStartStyle);
            Object.assign(panel.$.controlRight.style, panel.currentClipInfo.ctrlEndStyle);
        },
    },
    mountAllAnimationsOnPrefab: {
        ready() {
            const panel = this;

            panel.$.importAllAnimationsCheckbox.addEventListener('change', panel.setProp.bind(panel, 'mountAllAnimationsOnPrefab', 'boolean'));
            panel.$.importAllAnimationsCheckbox.addEventListener('confirm', () => {
                panel.dispatch('snapshot');
            });
        },
        update() {
            const panel = this;

            if (!panel.animationInfos) {
                panel.$.importAllAnimatorWrap.style.display = 'none';
                return;
            } else {
                panel.$.importAllAnimatorWrap.style.display = 'block';
            }

            panel.$.importAllAnimationsCheckbox.value = getPropValue.call(panel, panel.meta.userData.mountAllAnimationsOnPrefab, true);

            updateElementInvalid.call(panel, panel.$.importAllAnimationsCheckbox, 'mountAllAnimationsOnPrefab');
            updateElementReadonly.call(panel, panel.$.importAllAnimationsCheckbox);
        },
    },
};

async function callModelPreviewFunction(funcName, ...args) {
    return await Editor.Message.request('scene', 'call-preview-function', 'scene:model-preview', funcName, ...args);
}

exports.methods = {
    /** animation name -> uuid */
    initAnimationNameToUUIDMap() {
        if (this.meta && this.meta.subMetas) {
            const animationNameToUUIDMap = new Map();
            Object.keys(this.meta.subMetas).forEach((id) => {
                const subMeta = this.meta.subMetas[id];
                if (subMeta.importer === 'gltf-animation') {
                    const sourceName = subMeta.name;
                    const animName = sourceName.slice(0, sourceName.lastIndexOf('.'));
                    animationNameToUUIDMap.set(animName, subMeta.uuid);
                }
            });

            this.animationNameToUUIDMap = animationNameToUUIDMap;
        }
    },
    initAnimationInfos() {
        if (this.meta && this.meta.userData.animationImportSettings) {
            this.animationInfos = this.meta.userData.animationImportSettings;
            // Collect clip names for renaming and creating to determine whether the name is repeated
            this.clipNames = new Set();
            for (const animationInfo of this.animationInfos) {
                this.clipNames.add(animationInfo.name);
                for (const subAnimInfo of animationInfo.splits) {
                    this.clipNames.add(subAnimInfo.name);
                }
            }
        } else {
            this.animationInfos = null;
        }
    },

    onSelect(rawClipIndex, splitClipIndex) {
        this.rawClipIndex = rawClipIndex;
        this.splitClipIndex = splitClipIndex;
        const isElementSelect = (element) => element.getAttribute('rawClipIndex') == rawClipIndex && element.getAttribute('splitClipIndex') == splitClipIndex;
        Elements.editor.update.call(this);

        this.$.clips.querySelectorAll('.line').forEach((child) => {
            if (isElementSelect(child)) {
                child.setAttribute('active', true);
            } else {
                child.removeAttribute('active');
            }
        });
        const curClipInfo = this.getCurClipInfo();
        Editor.Message.broadcast('fbx-inspector:animation-change', curClipInfo);
    },
    getCurClipInfo() {
        if (!this.animationInfos) {
            return null;
        }
        const animInfo = this.animationInfos[this.rawClipIndex];
        const splitInfo = animInfo.splits[this.splitClipIndex];

        if (!animInfo) {
            return null;
        }
        if (!splitInfo) {
            return null;
        }
        const rawClipUUID = this.animationNameToUUIDMap.get(animInfo.name);
        const clipUUID = this.animationNameToUUIDMap.get(splitInfo.name);
        let duration = animInfo.duration;
        let fps = animInfo.fps;
        let from = 0;
        let to = duration;
        if (splitInfo) {
            from = splitInfo.from;
            to = splitInfo.to;
            duration = to - from;
            if (splitInfo.fps !== undefined) {
                fps = splitInfo.fps;
            }

            // if (this.animationNameToUUIDMap.has(splitInfo.name)) {
            //     clipUUID = this.animationNameToUUIDMap.get(splitInfo.name);
            // }
        }

        return {
            rawClipUUID,
            rawClipIndex: this.rawClipIndex,
            clipUUID,
            duration,
            fps,
            from,
            to,
            wrapMode: splitInfo.wrapMode,
            speed: splitInfo.speed || 1,
            additiveEnabled: splitInfo.additive?.enabled ?? false,
            refClip: splitInfo.additive?.refClip || '',
        };
    },
    getRightName(name) {
        if (!name) {
            return null;
        }
        const panel = this;
        do {
            const result = name.match(/(.*)_(\d{0,3})/);
            if (result) {
                name = `${result[1]}_${Number(result[2]) + 1}`;
            } else {
                name += '_1';
            }
        } while (panel.clipNames.has(name));
        return name;
    },
    newClipTemplate() {
        const panel = this;
        // Verify the name
        return {
            name: panel.getRightName(panel.rawClipInfo.name),
            from: 0,
            to: panel.rawClipInfo.duration,
            wrapMode: 2 /* Loop */,
            speed: 1,
        };
    },
    updateCurrentClipInfo() {
        const panel = this;
        if (!panel.animationInfos) {
            panel.currentClipInfo = null;
            return;
        }

        const info = panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex];
        if (!info || !panel.gridTableWith) {
            panel.currentClipInfo = null;
            return;
        }

        const duration = info.to - info.from;
        const ctrlStart = (info.from / panel.rawClipInfo.duration) * panel.gridTableWith;
        const ctrlEnd = (info.to / panel.rawClipInfo.duration) * panel.gridTableWith;
        const durationWidth = (duration / panel.rawClipInfo.duration) * panel.gridTableWith;
        const fps = info.fps !== undefined ? info.fps : panel.rawClipInfo.fps;
        const wrapMode = info.wrapMode ?? panel.rawClipInfo.wrapMode;
        const speed = info.speed ?? panel.rawClipInfo.speed;
        const additiveEnabled = (info.additive?.enabled) ?? panel.rawClipInfo.additiveEnabled;
        const refClip = (info.additive?.refClip) ?? panel.rawClipInfo.refClip;
        panel.currentClipInfo = {
            name: info.name,
            from: info.from * fps,
            to: info.to * fps,
            ctrlStart,
            ctrlEnd,
            ctrlStartStyle: {
                transform: `translateX(${ctrlStart - 6}px)`,
            },
            ctrlEndStyle: {
                transform: `translateX(${ctrlEnd}px)`,
            },
            durationStyle: {
                width: `${durationWidth}px`,
                transform: `translateX(${ctrlStart}px)`,
            },
            duration,
            fps,
            wrapMode,
            speed,
            additiveEnabled,
            refClip,
        };

        const maxFrames = (panel.rawClipInfo.duration * panel.currentClipInfo.fps).toFixed(0);
        const startFrames = panel.currentClipInfo.from.toFixed(0);
        const endFrames = panel.currentClipInfo.to.toFixed(0);

        panel.$.clipFrames.innerText = maxFrames;
        panel.$.clipFPS.value = fps;

        // TODO: hack for bug at 3d-tasks#10113. Because the new value would be limited in min and max, should firstly remove min and max.
        panel.$.clipFrom.max = null;
        panel.$.clipTo.min = null;
        panel.$.clipTo.max = null;

        panel.$.clipFrom.value = startFrames;
        panel.$.clipFrom.setAttribute('max', endFrames);

        panel.$.clipTo.value = endFrames;
        panel.$.clipTo.setAttribute('min', startFrames);
        panel.$.clipTo.setAttribute('max', maxFrames);

        panel.$.wrapMode.value = panel.currentClipInfo.wrapMode;
        panel.$.speed.value = panel.currentClipInfo.speed || 1;
        panel.$.additiveEnabled.value = panel.currentClipInfo.additiveEnabled || false;
        panel.$.refClip.value = panel.currentClipInfo.refClip || '';
    },
    updateRawClipInfo() {
        const panel = this;
        if (!panel.animationInfos) {
            panel.rawClipInfo = null;
            return;
        }

        if (!panel.animationInfos[panel.rawClipIndex]) {
            panel.rawClipInfo = null;
            return;
        }
        const { name, duration, fps } = panel.animationInfos[panel.rawClipIndex];
        panel.rawClipInfo = { name, duration, fps };

        panel.$.clipDuration.innerText = duration.toFixed(3);
    },
    updateGridConfig() {
        const panel = this;

        if (!panel.currentClipInfo) {
            return null;
        }

        let width = panel.gridTableWith;
        const info = panel.rawClipInfo;

        const { step, spacing } = panel.getStepAndSpacing(width, panel.currentClipInfo.fps * info.duration);
        width = (panel.currentClipInfo.fps * info.duration * spacing) / step;
        const mod = Math.floor(panel.gridTableWith / (spacing * 5));
        const rest = Math.floor((panel.gridTableWith % (spacing * 5)) / spacing);
        const labelStep = info.duration * ((5 * step) / (panel.currentClipInfo.fps * info.duration));

        panel.gridConfig = {
            step,
            spacing,
            mod,
            rest,
            width,
            labelStep,
        };
    },
    getStepAndSpacing(width, frames) {
        const config = {
            minSpacing: 10,
            maxSpacing: 20,
        };

        const rawMinSpacing = width / frames;
        let spacing = rawMinSpacing;
        let step = 1;
        if (rawMinSpacing < config.minSpacing) {
            // Calculates a minimum spacing value that is a multiple of maxSpacing
            step = Math.ceil(config.minSpacing / rawMinSpacing);
            spacing = rawMinSpacing * step;
        }

        return {
            step,
            spacing,
        };
    },
    onMouseDown(type) {
        const panel = this;

        const info = panel.currentClipInfo;

        if (!info) {
            return;
        }

        panel.virtualControl = { type };

        if (type === 'right') {
            panel.virtualControl.style = info.ctrlEndStyle;
            panel.virtualControl.value = info.to / panel.currentClipInfo.fps;
        } else {
            panel.virtualControl.style = info.ctrlStartStyle;
            panel.virtualControl.value = info.from / panel.currentClipInfo.fps;
        }

        panel.$.controlVirtual.setAttribute('direction', panel.virtualControl.type);
        panel.$.controlVirtual.style.display = 'block';

        panel.updateVirtualControl();

        panel.onMouseMoveBind = panel.onMouseMove.bind(panel);
        panel.onMouseUpBind = panel.onMouseUp.bind(panel);
        document.addEventListener('mousemove', panel.onMouseMoveBind);
        document.addEventListener('mouseup', panel.onMouseUpBind);
    },
    onMouseMove(event) {
        const panel = this;

        event.preventDefault();

        if (!panel.virtualControl) {
            return;
        }

        // beyond border
        const { type } = panel.virtualControl;
        let x = event.x - panel.$.rulerMaking.getBoundingClientRect().x;
        if (
            x > panel.gridConfig.width
            || x < 0
            || (type === 'left' && x > panel.currentClipInfo.ctrlEnd)
            || (type === 'right' && x < panel.currentClipInfo.ctrlStart)
        ) {
            return;
        }
        const { duration } = panel.rawClipInfo;
        const value = (x / panel.gridConfig.width) * duration;
        const currentTime = Editor.Utils.Math.clamp(value, 0, duration);
        const currentFrame = parseInt((currentTime * panel.currentClipInfo.fps).toFixed(0));
        panel.virtualControl.value = currentTime;
        if (type === 'left') {
            panel.virtualControl.startFrame = currentFrame;
            x -= 6;
        } else {
            panel.virtualControl.endFrame = currentFrame;
        }
        panel.virtualControl.style = {
            transform: `translateX(${x}px)`,
        };

        cancelAnimationFrame(panel.animationId);
        panel.animationId = requestAnimationFrame(() => {
            panel.updateVirtualControl();
        });
    },
    onMouseUp() {
        const panel = this;

        if (!panel.virtualControl) {
            return;
        }
        document.removeEventListener('mousemove', panel.onMouseMoveBind);
        document.removeEventListener('mouseup', panel.onMouseUpBind);
        const { value } = panel.virtualControl;
        let { type } = panel.virtualControl;
        type = type === 'right' ? 'to' : 'from';

        // refresh data
        const splitInfo = panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex];
        if (splitInfo[type] !== value) {
            const { duration } = panel.rawClipInfo;
            splitInfo[type] = Editor.Utils.Math.clamp(value, 0, duration);
        }

        Elements.clips.update.call(panel);

        Object.assign(panel.$.controlDuration.style, panel.currentClipInfo.durationStyle);
        Object.assign(panel.$.controlLeft.style, panel.currentClipInfo.ctrlStartStyle);
        Object.assign(panel.$.controlRight.style, panel.currentClipInfo.ctrlEndStyle);

        panel.$.controlVirtual.style.display = 'none';
        const curClipInfo = panel.getCurClipInfo();
        Editor.Message.broadcast('fbx-inspector:animation-change', curClipInfo);
        panel.dispatch('change');
        panel.dispatch('snapshot');
    },
    updateVirtualControl() {
        const panel = this;

        Object.assign(panel.$.controlVirtual.style, panel.virtualControl.style);
        panel.$.controlVirtualNumber.innerText = panel.virtualControl.value.toFixed(3);

        if (panel.virtualControl.startFrame || panel.virtualControl.endFrame) {
            if (panel.virtualControl.type === 'left') {
                panel.$.clipFrom.value = panel.virtualControl.startFrame;
            } else {
                panel.$.clipTo.value = panel.virtualControl.endFrame;
            }
        }
    },
    onClipName(event) {
        const panel = this;

        if (!panel.currentClipInfo) {
            return;
        }
        let name = event.target.value;
        if (!name) {
            return;
        }
        if (panel.clipNames.has(name)) {
            name = panel.getRightName(name);
        }
        const info = panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex];
        if (info.name === name) {
            return;
        }

        panel.clipNames.delete(info.name);
        info.name = name;
        panel.clipNames.add(name);

        panel.dispatch('change');
        panel.dispatch('snapshot');
        Elements.clips.update.call(panel);
    },
    onCutClip(event) {
        const panel = this;

        const path = event.target.getAttribute('path');
        panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex][path] = event.target.value / panel.currentClipInfo.fps;

        Elements.clips.update.call(panel);
        Elements.editor.update.call(panel);

        panel.dispatch('change');
        panel.dispatch('snapshot');
    },
    onFpsChange(event) {
        const panel = this;

        panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].fps = Number(event.target.value);

        Elements.editor.update.call(panel);
        panel.dispatch('change');
        panel.dispatch('snapshot');
    },
    onWrapModeChange(event) {
        const panel = this;

        const wrapMode = Number(event.target.value);
        panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].wrapMode = wrapMode;
        callModelPreviewFunction(
            'setClipConfig',
            {
                wrapMode,
            }
        );
        Elements.editor.update.call(panel);
        panel.dispatch('change');
        panel.dispatch('snapshot');
    },
    onSpeedChange(event) {
        const panel = this;

        const speed = Number(event.target.value);
        panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].speed = speed;
        callModelPreviewFunction(
            'setClipConfig',
            {
                speed,
            }
        );

        Elements.editor.update.call(panel);
        panel.dispatch('change');
        panel.dispatch('snapshot');
    },
    onAdditiveEnabledChanged(event) {
        const panel = this;

        const enabled = Boolean(event.target.value);
        (panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].additive ??= { enabled: false }).enabled = enabled;

        Elements.editor.update.call(panel);
        panel.dispatch('change');
        panel.dispatch('snapshot');
    },
    onRefClipChanged(event) {
        const panel = this;

        const refClipUUID = String(event.target.value);
        (panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].additive ??= { enabled: false }).refClip = refClipUUID;

        Elements.editor.update.call(panel);
        panel.dispatch('change');
        panel.dispatch('snapshot');
    },
    setProp(prop, type, event) {
        setPropValue.call(this, prop, type, event);

        this.dispatch('change');
        this.dispatch('track', { tab: 'animation', prop, value: event.target.value });
    },
};

exports.ready = function() {
    for (const prop in Elements) {
        const element = Elements[prop];
        if (element.ready) {
            element.ready.call(this);
        }
    }
};

exports.update = function(assetList, metaList) {
    this.assetList = assetList;
    this.metaList = metaList;
    this.asset = assetList[0];
    this.meta = metaList[0];

    if (assetList.length > 1) {
        this.$.container.setAttribute('multiple-invalid', '');
        return;
    } else {
        this.$.container.removeAttribute('multiple-invalid');
    }

    for (const prop in Elements) {
        const element = Elements[prop];
        if (element.update) {
            element.update.call(this);
        }
    }
    this.initAnimationNameToUUIDMap();
    this.initAnimationInfos();
    if (this.animationInfos) {
        this.onSelect(this.rawClipIndex, this.splitClipIndex);
    }
};

exports.close = function() {
    for (const prop in Elements) {
        const element = Elements[prop];
        if (element.close) {
            element.close.call(this);
        }
    }
};
