// vn_grid.js -- Visual Novel module
// This module is activated by setting the configuration information with setConfig or setConfigModule
// The module expects three data structures in the config argument:
//
//  setConfig{ {
//     item_placement_config, // Parameters to define the coordinate system to place objects visually
//     options,               // Global properties that don't change "per screen"
//     screens                // Sequence of text and images to display when user clicks "next" button.
//     });
//
// These parameters can be defined in their own module, which is the conventional way to define them. In this
// case, the module can be imported with a dynamic import call and passed to this module:
//
//  import * as vn from "./vn_old.js";
//  let vnConfigModule = import("./vnconfig.js");
//  vn.setConfigModule(vnConfigModule);
//
//  ...or, the vnconfig module can be imported statically and the module namespace can be passed via setconfig:
//
//  import * as vn from "./vn_grid.js";
//  import * as vnconfig from "./vnconfig.js";
//  vn.setConfig(vnconfig);
//
import $ from "./jquery.module.js";
import {ItemPlacement} from "./item_placement_class.js"
import * as audio from "./audio.js";
import * as language from "./language.js";
import SetConfig from "./set_config_class.js";

let next = 0;
let itemDisplay;
let audioForCurrentScreen = null;
let bkgOnloadSet = false;
// let setConfigResolve = null;
let setConfigHandler = new SetConfig();

// The following parameters are passed in from the setConfig function
let item_placement_config;
let config_options;
let config_ui = null;
let screens;
let decisionFunctions = {}; // Used in visual_novel_plus
let decisionButtonIDs = []; // Used in visual_novel_plus
let nextButtonHandler = goToNextScreen; // Default behavior for next button, can be overridden
let endScreenHandler = null;
let screenHandlerParameters = {currentScreen: null};
let processedDecisionItems = false; // Make sure decision items are only processed once
let currentScreenImage = null;
let currentLanguage = null;

let showingTitle = false;
let screenHistory = [];
let visitedPages = new Set();
let $backButton = null;

//If there is a title in the config file, a title page will be displayed, showing the title and background image supplied in the config file ;
function titleSetup() {
    if ("title" in config_options || "titleImage" in config_options) {
        showingTitle = true;
        let $mainDisplay = $("#v-main");
        if ("titleImage" in config_options) {
            const alt = config_options?.titleImageAltText ?? "title background";
            let $titleImage = $(`<img class="img-fluid theme-border" id="title-image" src="${config_options.titleImage}" alt="${alt}">`);
            $mainDisplay.html($titleImage) ;
        }
        if ("title" in config_options) {
            $mainDisplay.append(`<div id="title"><span class='lang1'>${config_options.title}</span> <span class='d-none lang2'>${config_options.title2}</span></div>`);
        }
        return true;
    }
    else {
        return false;
    }
}

// SHOWS NAME OF LANGUAGE TO SWITCH TO IN THE LANGUAGE BUTTON ;
function langSetup() {
    if ("language1" in config_options) {
        $("#lang1button").html(config_options.language2);
        $("#lang2button").html(config_options.language1);
        currentLanguage = "lang1";
    }
    else {
        $(".lang").addClass('d-none') ;
    }
    $(window).on("lang1Event7Gen", (evt)=> {
        currentLanguage = "lang2";
        language.setLang(currentLanguage);
        playAudioForScreen();
    } );
    $(window).on("lang2Event7Gen", (evt)=> {
        currentLanguage = "lang1";
        language.setLang(currentLanguage);
        playAudioForScreen();
    } );
}

async function getNewImage({src, id, classes, alt}) {
    // Creates new image with src and id, and returns promise which is resolved when image
    // is decoded (i.e. ready to be displayed). This does not actually insert the image into
    // the DOM.
    const img = new Image();
    if (id) {
        img.id = id;
    }
    if (src) {
        img.src = src;

    }
    if (alt) {
        img.alt = alt;
    }
    if (classes) {
        classes.split(" ").forEach((tok)=>{img.classList.add(tok)});
    }
    await img.decode();
    return $(img);
}

/* BEGIN SCREEN SETUP */

//sets up the screen
async function screenSetup() {
    if (showingTitle) {
        // Then exit now and show first text screen after the next button click
        showingTitle = false;
        $("#title").remove();
        $("#title-image").remove();
        $("#next-col").addClass('next-col');
        $backButton?.removeClass("no-show");
    }

    $("#gn-caption-container").addClass("gn-caption-container-style") ;
    $("#gn-caption").removeClass("clear").addClass("gn-caption-style");

    // IF no more screens, go to next HTML page
    if (next >= screens.length) {
        if (endScreenHandler) {
            endScreenHandler();
        } else if (config_ui && config_ui?.reloadWindowAtEnd) {
            window.location.reload();
            return;
        } else {
            if (config_options?.sendCompletionToParent) {
                parent.postMessage({
                    action: "goto",
                    destination: config_options.completionURL
                });
            } else {
                window.location.href = config_options.completionURL;
            }
        }
    }

    let $mainDisplay = $(`#v-main`);

    /* DO ALL OF THIS IF THERE ARE STILL SCREENS TO DISPLAY IN THE CONFIG FILE */
    if (next < Object.keys(screens).length) {
        let currentScreen = screens[next];
        screenHandlerParameters.currentScreen = currentScreen;
        processedDecisionItems = false; // Used in visual novel plus ;
        //  Below is used in visual_novel_bilingual and visual_novel_plus
        if (currentScreen.hasOwnProperty("txt2")) {
            $("#lang").show();
        }

        setCaptionText(currentScreen.txt1, "lang1");
        setCaptionText(currentScreen.txt2, "lang2");

        //Check if there's a background image for this screen and add it
        if ("bg_img" in currentScreen) {
            currentScreenImage = currentScreen.bg_img;
            let $bkgImage = $("#bkg-img");
            let newBkgImage = ($bkgImage.length === 0);
            if (!newBkgImage) {
                let bkgImage = $bkgImage.attr("src");
                newBkgImage = !bkgImage || (bkgImage !== currentScreenImage)
            }
            if (newBkgImage) {
                let $newImg = await getNewImage({
                    id: "bkg-img",
                    src: currentScreenImage,
                    alt: currentScreen?.bg_img_alt ??  "background image",
                    classes: "img-fluid bkg-img"
                });
                $bkgImage.remove();
                $mainDisplay.append($newImg);
            }
        }

        // Set up audio for screen
        setAudioForCurrentScreen(next);

        processScreenItems();

        /* PROCESS DISPLAY ITEMS FUNCTION WILL ADD, MOVE AND REMOVE
           OTHER IMAGES OVERLAID ON THE BACKGROUND IMAGE. ONLY AVAILABLE IN VISUAL NOVEL PLUS BLOCK ;
         */
        function processDisplayItems() {
            let currentScreen = screenHandlerParameters.currentScreen;
            itemDisplay.setCurrentImageSize();


            if (currentScreen.hasOwnProperty("images")) {
                for (let image of currentScreen.images) {
                    let theCSSProperties = {position: "absolute"};
                    if ("layer" in image) {
                        theCSSProperties.zIndex = image.layer;
                    }
                    itemDisplay.addDisplayItem({
                        displayItemID: image.id,
                        image: image.path,
                        location: {
                            left: image.location.left,
                            top: image.location.top,
                        },
                        scaling: image.scaling,
                        cssProperties: theCSSProperties
                    });
                }
            }

            // If autoplay is set, play now
            if (config_options.autoPlayAudioOnNewScreen) {
                playAudioForScreen();
                $("#play-audio").addClass('d-none') ;
            }

            if (Object.hasOwn(currentScreen, "remove_img_id")) {
                let removeImageID = currentScreen.remove_img_id;
                if (Array.isArray(removeImageID)) {
                    for (const imageID of removeImageID) {
                        itemDisplay.removeDisplayItem(imageID);
                    }
                }
                else {
                    itemDisplay.removeDisplayItem(removeImageID);
                }
            }

            if (Object.hasOwn(currentScreen, "move_img")) {
                let movePosition = {
                    left: currentScreen.move_img.left,
                    top: currentScreen.move_img.top
                }
                itemDisplay.moveDisplayItemTo(currentScreen.move_img.id, movePosition);
            }

        }
        /*  END OF PROCESS DISPLAY ITEMS FUNCTION */

        function processDecisionItems() {
            let currentScreen = screenHandlerParameters.currentScreen;
            let decision = currentScreen?.decision;
            if (!decision) {
                return;
            }
            if (processedDecisionItems) {
                return;
            }
            let $decisionContainer = $(`<div id="choice-button-container"></div>`);
            $mainDisplay.append($decisionContainer);
            for (const [index, choice] of decision.entries()) {
                let ID = `choice-btn-${index}`;
                decisionButtonIDs[index] = ID;
                let buttonElements = "";
                if ("buttonImage" in choice) {
                    buttonElements += `<div class="buttonImage"><img src="${choice.buttonImage}"></div>`;
                }
                if ("buttonTitle" in choice) {
                    buttonElements += `<div class="buttonTitle"><span class="lang1">${choice.buttonTitle}</span> 
                                       <span class="lang2 d-none">${choice.buttonTitle2}</span></div>`;

                }
                let buttonClass = "choice-button btn-outline-primary ";
                buttonClass += choice?.buttonClass ?? "";
                let $button = $(`<button id="${ID}" class="${buttonClass}">${buttonElements}</button>`);
                $decisionContainer.append($button);
                $button.on("click", {idx: index, choiceID: choice.choiceID}, decisionButtonHandler);
                // If decisionURL is defined, make default button handler go to the URL
                let decisionURL = choice?.decisionURL ?? null;
                if (decisionURL) {
                    decisionFunctions[choice.choiceID] = () => {window.location.href = decisionURL;}
                }
            }
            // Hide next button to force player to pick one of the choice buttons
            $("#next").hide();
            processedDecisionItems = true;
        }

        function decisionButtonHandler(event) {
            let idx = Number(event.data.idx);
            let choice = event.data.choiceID;

            // Button handler can be stored by the key choiceID and/or by the array index
            // We favor the choiceID if that is what was used.
            let decisionHandler = decisionFunctions?.[choice] ?? decisionFunctions?.[idx];

            $("#choice-button-container").remove();
            $("#next").show();
            decisionHandler?.(event);
        }

        function processScreenItems() {
            console.log("Called processScreenItems");
            processDisplayItems();
            processDecisionItems();
        }

    }
    /* END FUNCTIONS TO DO FOR EACH SCREEN */
}
/* END SCREEN SETUP */

function setAudioForCurrentScreen(screenIdx) {
    let screen = screens[screenIdx];
    audioForCurrentScreen = null;

    if ("audio1" in screen) {
        audioForCurrentScreen = {audio1: screen.audio1}
        if ("audio2" in screen) {
            audioForCurrentScreen.audio2 = screen.audio2;
        }
        if (!config_options.autoPlayAudioOnNewScreen) {
            $("#play-audio").removeClass('d-none') ;
        }
    }
}

function playAudioForScreen() {
    if (audioForCurrentScreen !== null) {
        switch(currentLanguage) {
            case "lang1":
                audio.playAudioFile(audioForCurrentScreen.audio1);
                break;

            case "lang2":
                audio.playAudioFile(audioForCurrentScreen.audio2);
                break;

            default: // current language will be null if not on bilingual page, so use audio1 by default
                audio.playAudioFile(audioForCurrentScreen.audio1);
                break;
        }
    }
}

function saveCurrentScreenInHistory() {
    let $mainDisplay = $("#v-main");
    screenHistory[next] = {display: $mainDisplay.clone(true), caption: $("#gn-caption-container").clone(true)};
}

function loadCurrentScreenFromHistory() {
    let $mainDisplay = $("#v-main");
    let {display, caption} =  screenHistory[next];
    $mainDisplay.replaceWith(display.clone(true));
    $("#gn-caption-container").replaceWith(caption.clone(true));
    setAudioForCurrentScreen(next);
    if (config_options.autoPlayAudioOnNewScreen) {
        playAudioForScreen();
        $("#play-audio").addClass('d-none') ;
    }
}

function goToNextScreen() {
    if (!(next in screenHistory)) {
        saveCurrentScreenInHistory();
        $backButton?.removeClass("d-none");
        next++;
        visitedPages.add(next);
        screenSetup();
    }
    else {
        next++;
        if (next in screenHistory) {
            loadCurrentScreenFromHistory();
        } else {
            visitedPages.add(next);
            screenSetup();
        }
    }
    language.setLang(currentLanguage);
}

function goToPreviousScreen(evt) {
    if (!((next - 1) in screenHistory)) {
        return;
    }
    if (!(next in screenHistory)) {
        saveCurrentScreenInHistory();
    }
    let $mainDisplay = $("#v-main");
    next--;
    loadCurrentScreenFromHistory();
    language.setLang(currentLanguage);
    // if (!((next - 1) in screenHistory)) {
    //     $("#back").addClass("d-none");
    // }
}

function executeNextButtonHandler () {
    if (typeof nextButtonHandler === "function") {
        nextButtonHandler();
    }
    else {
        goToNextScreen();
    }
}

$("#play-audio").on("click", function () {
    playAudioForScreen();
});

function setConfig(config) {
    setConfigHandler.setConfig(config);
}

function setConfigModule(modulePromise) {
    modulePromise.then(config => setConfig(config));
}

function adjustCaptionWidth($image) {
    let $bkg_img = $image ??  $("#bkg-img");
    let width = $bkg_img.css("width");
    $("#gn-caption").css("width", width);
}

function setDecisionButtonFunctions(arrayOfFunctions) {
    decisionFunctions = arrayOfFunctions;
}

function setOneDecisionButtonFunction(btnKey, func) {
    decisionFunctions[btnKey] = func;
}

function setCaptionText(text, lang = "lang1") {
    $(`#text .${lang}`).html(text);
}

function setBackgroundImage(imagePath) {
    let $bkgImage = $("#bkg-img");
    $bkgImage.attr("src", `${imagePath}`);
}
function setNextHandler(newHandler) {
    nextButtonHandler = newHandler;
}

function setEndScreenHandler(newHandler) {
    endScreenHandler = newHandler;
}

$(window).on("resize", ()=>{
   // adjustCaptionWidth();
});

$(async () => {
    let config = await setConfigHandler.getConfigAsync();

    item_placement_config = config.item_placement_config;

    item_placement_config.backgroundDivID = "v-main";  // ID of div containing background image.
    item_placement_config.itemDivClass    = "images";  // Class assigned to each displayed item (for styling).
    item_placement_config.displayItems    = [];        // items is an array (see below).

    config_options = config.options;
    config_ui = config?.ui_params;
    screens = config.screens;

    itemDisplay = new ItemPlacement(item_placement_config);

    $("#next").on("click", executeNextButtonHandler);
    langSetup() ;

    if (config_options.autoPlayAudioOnNewScreen) {
        $("#play-audio").addClass('d-none') ;
    }

    let startFunction = () => {
        itemDisplay.onDocumentReady();
        screenSetup(); //initial screen setup
        return false;
    }
    let startButton =  titleSetup();
    if (startButton) {
        setNextHandler(() => {
            setNextHandler(null);
            startFunction();
        });

    }
    else {
        startFunction();
    }
    if (config.options.hasBackButton) {
        $backButton = $("#back");
        $backButton.on("click", goToPreviousScreen);
    }
});

export {setConfig, setConfigModule}; // One of these must be called by user
export {
    setCaptionText, setBackgroundImage,
    setDecisionButtonFunctions, setOneDecisionButtonFunction, setNextHandler, setEndScreenHandler
};

