import { Event } from "@clarity-types/data";
import { AnimationOperation, AnimationState } from "@clarity-types/layout";
import { time } from "@src/core/time";
import { shortid } from "@src/data/metadata";
import encode from "@src/layout/encode";
import { getId } from "@src/layout/dom";
import * as core from "@src/core";

export let state: AnimationState[] = [];
let elementAnimate: (keyframes: Keyframe[] | PropertyIndexedKeyframes, options?: number | KeyframeAnimationOptions) => Animation = null;
let overridden = false;
const animationId = '__clrAId';
const operationCount = '__clrOCnt';
const maxOperations = 20;

export function start(): void {
    if (
        window["Animation"] && 
        window["Animation"].prototype && 
        window["KeyframeEffect"] && 
        window["KeyframeEffect"].prototype && 
        window["KeyframeEffect"].prototype.getKeyframes &&
        window["KeyframeEffect"].prototype.getTiming
    ) {
        reset();
        if (!overridden) {
            overridden = true;
            ["play", "pause", "commitStyles", "cancel", "finish"].forEach(overrideAnimationHelper);
        }
        if (elementAnimate === null) {
            elementAnimate = Element.prototype.animate;
            Element.prototype.animate = function(): Animation {
                var createdAnimation = elementAnimate.apply(this, arguments);
                trackAnimationOperation(createdAnimation, "play");
                return createdAnimation;
            }
        }
        if (document.getAnimations) {
            for (var animation of document.getAnimations()) {
                if (animation.playState === "finished") {
                    trackAnimationOperation(animation, "finish");
                }
                else if (animation.playState === "paused" || animation.playState === "idle") {
                    trackAnimationOperation(animation, "pause");
                }
                else if (animation.playState === "running") {
                    trackAnimationOperation(animation, "play");
                }
            }
        }
    }    
}

export function reset(): void {
    state = [];
}

function track(time: number, id: string, operation: AnimationOperation, keyFrames?: string, timing?: string, targetId?: number, timeline?: string): void {
    state.push({
        time,
        event: Event.Animation,
        data: {
            id,
            operation,
            keyFrames,
            timing,
            targetId,
            timeline
        }
    });

    encode(Event.Animation);
}

export function stop(): void {
    reset();
}

function overrideAnimationHelper(name: string) {
    let original = Animation.prototype[name];
    if (typeof original !== "function") { return; }
    Animation.prototype[name] = function(): void {
        trackAnimationOperation(this, name);
        return original.apply(this, arguments);
    }
}

function trackAnimationOperation(animation: Animation, name: string) {
    if (core.active()) {
        let effect = <KeyframeEffect>animation.effect;
        let target = effect?.target ? getId(effect.target) : null;
        if (target !== null && effect.getKeyframes && effect.getTiming) {
            if (!animation[animationId]) {
                animation[animationId] = shortid();
                animation[operationCount] = 0;
                
                let keyframes = effect.getKeyframes();
                let timing = effect.getTiming();
                track(time(), animation[animationId], AnimationOperation.Create, JSON.stringify(keyframes), JSON.stringify(timing), target);
            }

            if (animation[operationCount]++ < maxOperations)  {
                let operation: AnimationOperation = null;
                switch (name) {
                    case "play":
                        operation = AnimationOperation.Play;
                        break;
                    case "pause":
                        operation = AnimationOperation.Pause;
                        break;
                    case "cancel":
                        operation = AnimationOperation.Cancel;
                        break;
                    case "finish":
                        operation = AnimationOperation.Finish;
                        break;
                    case "commitStyles":
                        operation = AnimationOperation.CommitStyles;
                        break;
                }
                if (operation) {
                    track(time(), animation[animationId], operation);
                }
            }
        }            
    }
}
