import { MODULE_ID } from "../main.js";
import Sortable from "../sortable.js";
import { BlockConfig } from "./BlockConfig.js";
import { ConditionEvaluator } from "../ConditionEvaluator.js";

export const conditionEvaluator = new ConditionEvaluator();

Hooks.once("ready", () => conditionEvaluator.setHooks());

export class Soundscapes extends Application {
    constructor() {
        super();
        ui.dynamicSoundscapes?.close();
        ui.dynamicSoundscapes = this;
        this._selectedPlaylist = game.settings.get(MODULE_ID, "selectedPlaylist");
        this._playingPlaylist = game.settings.get(MODULE_ID, "playingPlaylist");
        if (!this._selectedPlaylist) {
            const playlists = game.playlists.folders.getName("Soundscapes").contents;
            if (playlists.length > 0) {
                this._selectedPlaylist = playlists[0].id;
                game.settings.set(MODULE_ID, "selectedPlaylist", this._selectedPlaylist);
            }
        }
        this._hookId = Hooks.on("updatePlaylistSound", () => {
            this.debouncedRefresh();
        });
        this._playlistUpdateHookId = Hooks.on("updatePlaylist", (a, b, c) => {
            this.debouncedRefresh();
        });
        this._soundCreatedHookId = Hooks.on("createPlaylistSound", () => {
            this.debouncedRefresh();
        });
        this._soundDeletedHookId = Hooks.on("deletePlaylistSound", () => {
            this.debouncedRefresh();
        });
        const playlists = this.playlists;
        const updates = [];
        for (const playlist of playlists) {
            if (playlist.mode !== CONST.PLAYLIST_MODES.DISABLED) {
                updates.push({
                    _id: playlist.id,
                    mode: CONST.PLAYLIST_MODES.DISABLED,
                });
            }
        }
        if (updates.length) Playlist.updateDocuments(updates);
        this.updateSounds();
        this.savePositionAndSize = foundry.utils.debounce(this.savePositionAndSize, 1000);
        this.debouncedRefresh = foundry.utils.debounce(this.refresh, 200);
        this._positionLoaded = false;
    }

    refresh() {
        if (this.rendered) this.render(true, { focus: false });
        else this.updateState();
    }

    setPosition(...args) {
        const res = super.setPosition(...args);
        this.savePositionAndSize();
        return res;
    }

    get selectedPlaylist() {
        return game.playlists.get(this._selectedPlaylist);
    }

    get playingPlaylist() {
        return game.playlists.get(this._playingPlaylist);
    }

    set playingPlaylist(value) {
        this.setPlaylist(value);
    }

    async setPlaylist(playlist) {
        let playlistID = playlist?.id || playlist || "";
        playlist = game.playlists.get(playlistID) ?? game.playlists.getName(playlistID);
        playlistID = playlist?.id || "";
        await game.settings.set(MODULE_ID, "playingPlaylist", playlistID);
        this._playingPlaylist = playlistID;
        this.updateState();
        if (this.rendered) this.render(true, { focus: false });
    }

    get folderName() {
        return this.constructor.folderName;
    }

    static get folderName() {
        return "Soundscapes"; //game.settings.get(MODULE_ID, "folderName");
    }

    get playlists() {
        return game.playlists.folders.getName(this.folderName).contents;
    }

    get manualSort() {
        return game.playlists.folders.getName(this.folderName).sorting == "m";
    }

    set mood(value) {
        value = value.replace("mood", "");
        value = value.toUpperCase();
        this.playingPlaylist?.setFlag(MODULE_ID, "mood", `mood${value}`);
    }

    setMood(mood) {
        this.mood = mood;
    }

    get mood() {
        return this.playingPlaylist?.getFlag(MODULE_ID, "mood")?.replace("mood", "");
    }

    static async checkCreateFolder() {
        const folder = game.playlists.folders.getName(this.folderName);
        if (!folder) {
            await Folder.create({ name: this.folderName, type: "Playlist" });
            ui.notifications.info(game.i18n.localize(`${MODULE_ID}.folderCreated`), { permanent: true });
            const demoPlaylist = await foundry.utils.fetchJsonWithTimeout("modules/dynamic-soundscapes/assets/forest_ambient/forest-ambience.json");
            const playlist = await Playlist.create({ ...demoPlaylist, folder: game.playlists.folders.getName(this.folderName).id });
        }
    }

    static get defaultOptions() {
        return {
            ...super.defaultOptions,
            id: "soundscapes",
            classes: ["soundscapes"],
            template: `modules/dynamic-soundscapes/templates/soundscapes.hbs`,
            title: game.i18n.localize(`${MODULE_ID}.app.title`),
            resizable: true,
            popOut: true,
            width: window.innerWidth * 0.4,
            height: window.innerHeight * 0.8,
            scrollY: ["#soundscapes-sidebar", "#soundscapes-list", "#soundscape-interface"],
        };
    }

    savePositionAndSize() {
        const position = this.position;
        game.settings.set(MODULE_ID, "position", { ...position });
    }

    loadPositionAndSize() {
        this._positionLoaded = true;
        const position = game.settings.get(MODULE_ID, "position");
        if (position.width && position.height) {
            this.setPosition(position);
        }
    }

    _getHeaderButtons() {
        const buttons = super._getHeaderButtons();
        buttons.unshift({
            class: "soundscapes-toggle-compact",
            icon: "fas fa-expand",
            onclick: () => {
                this.element[0].classList.toggle("compact");
                game.settings.set(MODULE_ID, "compact", this.element[0].classList.contains("compact"));
            },
        });
        return buttons;
    }

    async getData() {
        const playlists = this.playlists;
        playlists.forEach((playlist) => {
            playlist.soundscapeSelected = playlist.id === this._selectedPlaylist;
            playlist.soundscapePlaying = playlist.id === this._playingPlaylist;
            const mood = playlist.getFlag(MODULE_ID, "mood");
            switch (mood) {
                case "moodA":
                    playlist.moodIcon = `fa-square-a`;
                    break;
                case "moodB":
                    playlist.moodIcon = `fa-square-b`;
                    break;
                case "moodC":
                    playlist.moodIcon = `fa-square-c`;
                    break;
                default:
                    playlist.moodIcon = `fa-square-a`;
                    break;
            }
        });
        const blocks = JSON.parse(JSON.stringify(this.selectedPlaylist?.getFlag(MODULE_ID, "blocks") || []));
        const layout = this.selectedPlaylist?.getFlag(MODULE_ID, "soundscapeLayout") || "flex";
        for (const block of blocks) {
            block.playlistSounds = block.sounds.map((soundId) => {
                return this.selectedPlaylist.sounds.get(soundId);
            });
            block.conditionMet = conditionEvaluator.evaluateCondition(block);
            block.isSoundboard = block.mode === "soundboard";
            block.isAmbient = block.mode === "ambient";
            block.isRandom = block.mode === "random";
            if (block.isOrphaned) {
                block.hidden = block.playlistSounds.length === 0;
            }
        }
        this._blocks = blocks;
        return {
            playlists: this.manualSort ? playlists.sort((a, b) => a.sort - b.sort) : playlists,
            blocks: blocks,
            hideAdd: blocks.length > 1,
            useRows: layout === "rows",
        };
    }

    async addBlock(isOrphaned = false, index = null) {
        const playlist = this.selectedPlaylist;
        if (!playlist) return;
        const blocks = playlist.getFlag(MODULE_ID, "blocks") || [];
        let orphanedSounds = [];

        if (isOrphaned) {
            const sounds = Array.from(playlist.sounds);
            orphanedSounds = sounds.filter((sound) => {
                return !blocks.some((block) => block.sounds.includes(sound.id));
            });
            const orphanedBlock = blocks.find((block) => block.isOrphaned);
            if (orphanedBlock) {
                if (orphanedSounds.length) {
                    orphanedBlock.sounds.push(...orphanedSounds.map((sound) => sound.id));
                    await playlist.setFlag(MODULE_ID, "blocks", blocks);
                    if (this.rendered) this.render(true, { focus: false });
                    else this.updateState();
                }
                return;
            }
        }
        const newBlockData = {
            title: isOrphaned ? "Orphaned" : `Block ${blocks.length + 1}`,
            id: foundry.utils.randomID(20),
            sounds: isOrphaned ? orphanedSounds.map((sound) => sound.id) : [],
            isOrphaned,
            mode: "soundboard",
            time: 60,
            variance: 0.5,
            size: "70px",
            color: "#d6d6d6",
            conditions: [],
            conditionMode: "all",
        };
        if (index !== null) blocks.splice(index, 0, newBlockData);
        else blocks.push(newBlockData);
        await playlist.setFlag(MODULE_ID, "blocks", blocks);
        this.render(true, { focus: false });
    }

    activateListeners(html) {
        super.activateListeners(html);
        if (!this._positionLoaded) this.loadPositionAndSize();
        const compact = game.settings.get(MODULE_ID, "compact");
        if (compact) this.element[0].classList.add("compact");
        this.updateState();
        this.addBlock(true);
        html = html[0];
        html.querySelectorAll(".soundscape-block-body").forEach((el) => {
            Sortable.create(el, {
                animation: 200,
                group: "soundscapes",
                filter: "input",
                preventOnFilter: false,
                onEnd: this.updateBlocks.bind(this),
            });
        });
        Sortable.create(html.querySelector("#soundscape-interface"), {
            animation: 200,
            filter: ".soundscape-block-body",
            preventOnFilter: false,
            onEnd: this.reorderBlocks.bind(this),
        });
        html.querySelectorAll(".soundscapes-playlist").forEach((el) => {
            el.addEventListener("click", (event) => {
                this._selectedPlaylist = event.currentTarget.dataset.playlistId;
                game.settings.set(MODULE_ID, "selectedPlaylist", this._selectedPlaylist);
                this.render(true, { focus: false });
            });
        });
        html.querySelector(".add-soundscape").addEventListener("click", async (event) => {
            Playlist.createDialog({ folder: game.playlists.folders.getName(this.folderName).id });
        });
        html.querySelectorAll(".soundscapes-config-playlist").forEach((el) => {
            el.addEventListener("click", async (event) => {
                event.preventDefault();
                event.stopPropagation();
                const playlistId = event.currentTarget.closest("li").dataset.playlistId;
                const playlist = game.playlists.get(playlistId);
                playlist.sheet.render(true);
            });
        });
        html.querySelectorAll(".soundscapes-add-sound").forEach((el) => {
            el.addEventListener("click", async (event) => {
                event.preventDefault();
                event.stopPropagation();
                const playlistId = event.currentTarget.closest("li").dataset.playlistId;
                const playlist = game.playlists.get(playlistId);
                const sound = new PlaylistSound({ name: game.i18n.localize("SOUND.New") }, { parent: playlist });
                sound.sheet.render(true);
            });
        });
        html.querySelectorAll(".soundscapes-cycle-mood").forEach((el) => {
            el.addEventListener("mouseup", async (event) => {
                event.preventDefault();
                event.stopPropagation();
                const isLeftClick = event.button === 0;
                const isRightClick = event.button === 2;
                if (!isLeftClick && !isRightClick) return;
                const playlistId = event.currentTarget.closest("li").dataset.playlistId;
                const playlist = game.playlists.get(playlistId);
                const mood = playlist.getFlag(MODULE_ID, "mood") ?? "moodA";
                const moods = ["moodA", "moodB", "moodC"];
                const index = moods.indexOf(mood);
                const newIndex = isLeftClick ? (index + 1) % moods.length : (index - 1 + moods.length) % moods.length;
                await playlist.setFlag(MODULE_ID, "mood", moods[newIndex]);
                this.debouncedRefresh();
            });
        });
        const soundscapeInterface = html.querySelector("#soundscape-interface");
        soundscapeInterface.addEventListener("click", (event) => {
            if (event.target !== soundscapeInterface) return;
            const mousePosition = {
                x: event.clientX,
                y: event.clientY,
            };
            //find closest block
            const blocks = Array.from(soundscapeInterface.querySelectorAll(".soundscape-block"));
            const closestBlock = blocks.reduce((prev, curr) => {
                const currRect = curr.getBoundingClientRect();
                const currCenter = {
                    x: currRect.left + currRect.width / 2,
                    y: currRect.top + currRect.height / 2,
                };
                const prevRect = prev.getBoundingClientRect();
                const prevCenter = {
                    x: prevRect.left + prevRect.width / 2,
                    y: prevRect.top + prevRect.height / 2,
                };
                const prevDistance = Math.sqrt(Math.pow(prevCenter.x - mousePosition.x, 2) + Math.pow(prevCenter.y - mousePosition.y, 2));
                const currDistance = Math.sqrt(Math.pow(currCenter.x - mousePosition.x, 2) + Math.pow(currCenter.y - mousePosition.y, 2));
                return prevDistance < currDistance ? prev : curr;
            });
            const index = blocks.indexOf(closestBlock);
            this.addBlock(false, index);
        });
        html.querySelectorAll(".soundscape-block-header-button").forEach((el) => {
            el.addEventListener("click", async (event) => {
                const action = event.currentTarget.dataset.action;
                const blockId = event.currentTarget.closest(".soundscape-block").dataset.blockId;
                switch (action) {
                    case "config": {
                        new BlockConfig(this.selectedPlaylist, blockId).render(true);
                        break;
                    }
                    case "delete": {
                        new Dialog({
                            title: game.i18n.localize(`${MODULE_ID}.app.deleteBlock.title`),
                            content: `<p>${game.i18n.localize(`${MODULE_ID}.app.deleteBlock.content`)}</p>`,
                            buttons: {
                                yes: {
                                    icon: '<i class="fas fa-trash"></i>',
                                    label: game.i18n.localize(`${MODULE_ID}.app.deleteBlock.delete`),
                                    callback: async () => {
                                        const blocksFlag = this.selectedPlaylist.getFlag(MODULE_ID, "blocks");
                                        const blockIndex = blocksFlag.findIndex((block) => block.id === blockId);
                                        blocksFlag.splice(blockIndex, 1);
                                        await this.selectedPlaylist.setFlag(MODULE_ID, "blocks", blocksFlag);
                                        this.render(true, { focus: false });
                                    },
                                },
                                all: {
                                    icon: '<i class="fas fa-dumpster"></i>',
                                    label: game.i18n.localize(`${MODULE_ID}.app.deleteBlock.deleteAll`),
                                    callback: async () => {
                                        const blocksFlag = this.selectedPlaylist.getFlag(MODULE_ID, "blocks");
                                        const blockIndex = blocksFlag.findIndex((block) => block.id === blockId);
                                        const blockSounds = blocksFlag[blockIndex].sounds;
                                        blocksFlag.splice(blockIndex, 1);
                                        await this.selectedPlaylist.deleteEmbeddedDocuments("PlaylistSound", blockSounds);
                                        await this.selectedPlaylist.setFlag(MODULE_ID, "blocks", blocksFlag);
                                        this.render(true, { focus: false });
                                    },
                                },
                                no: {
                                    icon: '<i class="fas fa-times"></i>',
                                    label: game.i18n.localize(`${MODULE_ID}.app.deleteBlock.cancel`),
                                },
                            },
                            default: "no",
                        }).render(true);
                        break;
                    }
                    case "select-all": {
                        const block = this._blocks.find((block) => block.id === blockId);
                        const updates = block.sounds.map((sound) => {
                            return { _id: sound, flags: { [MODULE_ID]: { enabled: true } } };
                        });
                        await this.selectedPlaylist.updateEmbeddedDocuments("PlaylistSound", updates);
                        break;
                    }
                    case "deselect-all": {
                        const block = this._blocks.find((block) => block.id === blockId);
                        const updates = block.sounds.map((sound) => {
                            return { _id: sound, flags: { [MODULE_ID]: { enabled: false } } };
                        });
                        await this.selectedPlaylist.updateEmbeddedDocuments("PlaylistSound", updates);
                        break;
                    }
                }
            });
        });
        html.querySelectorAll(".soundscape-sound").forEach((el) => {
            el.addEventListener("contextmenu", (event) => {
                event.preventDefault();
                const soundId = event.currentTarget.dataset.soundId;
                const sound = this.selectedPlaylist.sounds.get(soundId);
                sound.sheet.render(true);
            });
            el.addEventListener("click", async (event) => {
                if (event.target !== event.currentTarget) return;
                const soundId = event.currentTarget.dataset.soundId;
                const sound = this.selectedPlaylist.sounds.get(soundId);
                const block = this._blocks.find((block) => block.sounds.includes(soundId));
                const isSoundboard = block.mode === "soundboard";
                if (isSoundboard) {
                    const isActivePlaylist = this.playingPlaylist === this.selectedPlaylist;
                    if (isActivePlaylist) {
                        sound._onClickDocumentLink(event);
                    } else {
                        ui.notifications.warn(game.i18n.localize(`${MODULE_ID}.app.soundboardPlaylistWarning`));
                    }
                } else {
                    await sound.setFlag(MODULE_ID, "enabled", !sound.getFlag(MODULE_ID, "enabled"));
                }
                this.updateState();
                this.render(true, { focus: false });
            });
        });
        html.querySelectorAll(".soundscape-sound-volume").forEach((el) => {
            el.addEventListener("change", async (event) => {
                const soundId = event.currentTarget.closest(".soundscape-sound").dataset.soundId;
                const sound = this.selectedPlaylist.sounds.get(soundId);
                await sound.update({ volume: event.currentTarget.value });
                this.updateState();
            });
        });
        html.querySelectorAll(".soundscapes-start-stop").forEach((el) => {
            el.addEventListener("click", async (event) => {
                const playlistId = event.currentTarget.closest("li").dataset.playlistId;
                this.setPlaylist(this._playingPlaylist === playlistId ? "" : playlistId);
            });
        });
    }

    async updateBlocks() {
        const blocksEls = this.element[0].querySelectorAll(".soundscape-block");
        const blockMap = {};
        const computedSounds = [];
        blocksEls.forEach((el) => {
            const sounds = [];
            el.querySelectorAll(".soundscape-sound").forEach((soundEl) => {
                const sound = this.selectedPlaylist.sounds.get(soundEl.dataset.soundId);
                if (sound) sounds.push(soundEl.dataset.soundId);
            });
            blockMap[el.dataset.blockId] = sounds.filter((sound) => !computedSounds.includes(sound));
            computedSounds.push(...sounds);
        });
        const blocksFlag = this.selectedPlaylist.getFlag(MODULE_ID, "blocks");
        blocksFlag.forEach((block) => {
            block.sounds = blockMap[block.id];
        });
        await this.selectedPlaylist.setFlag(MODULE_ID, "blocks", blocksFlag);
        await this.updateSounds();
        this.render(true, { focus: false });
    }

    async reorderBlocks() {
        const blocksEls = this.element[0].querySelectorAll(".soundscape-block");
        const blocksFlag = this.selectedPlaylist.getFlag(MODULE_ID, "blocks");
        const newBlocksFlag = [];
        blocksEls.forEach((el) => {
            const block = blocksFlag.find((block) => block.id === el.dataset.blockId);
            newBlocksFlag.push(block);
        });
        await this.selectedPlaylist.setFlag(MODULE_ID, "blocks", newBlocksFlag);
        this.render(true, { focus: false });
    }

    async updateState() {
        //stop all other sounds
        const playlists = this.playlists;
        for (const playlist of playlists) {
            if (playlist.id === this._playingPlaylist) continue;
            const hasPlayingSounds = playlist.sounds.some((sound) => sound.playing);
            hasPlayingSounds && playlist.stopAll();
        }
        //handle current playlist
        const playlist = this.playingPlaylist;
        if (!playlist) return;
        const blocks = playlist.getFlag(MODULE_ID, "blocks") || [];
        for (const sound of playlist.sounds) {
            const enabled = sound.getFlag(MODULE_ID, "enabled");
            const block = blocks.find((block) => block.sounds.includes(sound.id));
            const isConditionMet = conditionEvaluator.evaluateCondition(block);
            const isAmbient = block.mode === "ambient";
            if (!isAmbient && isConditionMet) continue;
            if (enabled && isConditionMet && !sound.playing) {
                playlist.playSound(sound);
            } else if ((!enabled || !isConditionMet) && sound.playing) {
                playlist.stopSound(sound);
            }
        }
        game.playlists.soundscapesRandomSoundController.reCheckRandomBlocks();
    }

    async updateSounds() {
        for (const playlist of this.playlists) {
            const updates = [];
            const blocks = playlist.getFlag(MODULE_ID, "blocks") || [];
            for (const block of blocks) {
                const mode = block.mode;
                for (const soundId of block.sounds) {
                    const sound = playlist.sounds.get(soundId);
                    if (!sound) continue;
                    const repeat = mode == "ambient";
                    const soundboard = mode == "soundboard";
                    const updateData = {};
                    if (sound.repeat !== repeat) {
                        updateData._id = sound._id;
                        updateData.repeat = repeat;
                    }
                    if (sound.flags[MODULE_ID]?.enabled && soundboard) {
                        updateData._id = sound._id;
                        updateData.flags = {
                            [MODULE_ID]: {
                                enabled: false,
                            },
                        };
                    }
                    if (updateData._id) updates.push(updateData);
                }
            }
            if (updates.length) {
                await playlist.updateEmbeddedDocuments("PlaylistSound", updates);
            }
        }
    }

    async close(...args) {
        Hooks.off("updatePlaylistSound", this._hookId);
        Hooks.off("updatePlaylist", this._playlistUpdateHookId);
        Hooks.off("createPlaylistSound", this._soundCreatedHookId);
        Hooks.off("deletePlaylistSound", this._soundDeletedHookId);
        return await super.close(...args);
    }
}
