/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.*/

class channelSettingsPage{
    constructor(){
        //Get channel name off of the URL
        this.channel = window.location.pathname.slice(3).replace('/settings','');
        //Instantiate UX handling objects, making sure to pass the channel name.
        this.chanInfo = new chanInfo(this.channel);
        this.rankList = new rankList(this.channel);
        this.banList = new banList(this.channel);
        this.permList = new permList(this.channel);
        this.prefrenceList = new prefrenceList(this.channel);
        this.tokeCommandList = new tokeCommandList(this.channel);
        this.emoteList = new emoteList(this.channel);
        this.deleteBtn = new deleteBtn(this.channel);
    }
}

class chanInfo{
    constructor(channel){
        //Define channel
        this.channel = channel;
        //Define UX elements
        this.thumbnail = document.querySelector("#channel-info-thumbnail");
        this.thumbnailInput = document.querySelector("#channel-info-thumbnail-prompt");
        this.description = document.querySelector("#channel-info-description");
        //Create description prompt
        this.descriptionInput = document.createElement("textarea");
        this.descriptionInput.id = "channel-info-description-prompt";
        this.descriptionInput.value = this.description.textContent;

        //Setup Input Event Handlers
        this.setupInput();
    }

    setupInput(){
        this.thumbnail.addEventListener('click', ()=>{this.toggleThumbnailPrompt(true)});
        this.thumbnailInput.addEventListener('keydown', this.submitThumbnail.bind(this));

        this.description.addEventListener('click', ()=>{this.toggleDescriptionPrompt(true)})
        this.descriptionInput.addEventListener('keydown', this.submitDescription.bind(this));
    }

    toggleThumbnailPrompt(enabled){
        this.thumbnailInput.style.display = enabled ? "" : "none";

        if(enabled){
            //focus thumbnail input
            this.thumbnailInput.focus();

            //Remove interactive class from thumby
            this.thumbnail.classList.remove('interactive');
        }else{
            //add interactive class to thumby
            this.thumbnail.classList.add('interactive');
        }
    }

    async submitThumbnail(event){
        //If we hit didnt hit enter or escape
        if(!(event.key == "Enter" || event.key == "Escape") && event.key != null){
            //bail!
            return;
        //Only returns w/ content after this point
        }else if(event.key == "Escape" && event.key != null){
            //Toggle prompt
            this.toggleThumbnailPrompt(false);
            return;
        }

        //Send update off to server and wait for response
        const data = await utils.ajax.setChannelThumbnail(this.channel, this.thumbnailInput.value);

        //Set new image from updated thumby 
        this.thumbnail.src = data.thumbnail;

        //Toggle prompt
        this.toggleThumbnailPrompt(false);
    }

    toggleDescriptionPrompt(enabled){
        if(enabled){
            this.description.replaceWith(this.descriptionInput);
            this.descriptionInput.focus()
        }else{
            this.descriptionInput.replaceWith(this.description);
        }
    }

    async submitDescription(event){
        //If we hit didnt hit enter (without shift) or escape
        if(!((event.key == "Enter" && !event.shiftKey) || event.key == "Escape") && event.key != null){
            //bail!
            return;
        }else if(event.key == "Escape" && event.key != null){
            //Toggle prompt
            this.toggleDescriptionPrompt(false);
            return;
        }

        //Stop newline from being processed
        event.preventDefault();

        //Set Description
        const data = await utils.ajax.setChannelDescription(this.channel, this.descriptionInput.value);

        //Unescape entities from server-side sanatization and safely put the newly made un-safe text inside of the element via .textContent.
        //Ensuring sanatized content displays proper, and that any unsanatized content that some how made it through is still safe.
        this.description.textContent = utils.unescapeEntities(data.description);

        //Toggle prompt
        this.toggleDescriptionPrompt(false);
    }
}

class rankList{
    constructor(channel){
        this.channel = channel
        this.table = document.querySelector(".admin-list-table");
        this.userPrompt = document.querySelector("#new-rank-input");
        this.rankSelect = document.querySelector("#new-rank-select");
    
        //Load the userlist and setup input
        this.loadList();
        this.setupInput();
    }

    setupInput(){
        this.userPrompt.addEventListener("keydown", this.submitNewRank.bind(this));
    }

    async loadList(){
        const list = await utils.ajax.getChannelRank(this.channel);

        this.updateList(list);
    }

    async submitNewRank(event){
        if((event.key != "Enter" ) && event.key != null){
            //Bail out if we didn't hit enter
            return;
        }

        //Send new rank
        this.submitUserRank(this.userPrompt.value, this.rankSelect.value);

        //Clear out prompt
        this.userPrompt.value = "";
    }

    async submitUpdate(event){
        const user = event.target.dataset.user;
        const rank = event.target.value;

        await this.submitUserRank(user, rank);
    }

    async submitUserRank(user, rank){
        await this.updateList(await utils.ajax.setChannelRank(this.channel, user, rank));
    }

    async updateList(data){
        //If no data
        if(!data){
            //Do not pass go, do not collect $200
            return;
        }
        //Get name/rank of logged in user
        const curName = document.querySelector("#username").textContent
        const curUser = data[curName];
        const rankEnum = await utils.ajax.getRankEnum();

        //clear the table
        this.clearTable();

        //For each user in the list
        Object.entries(data).forEach((userAr) => {
            //pull user object from entry array
            const user = userAr[1];
            
            //Create IMG node inside of IMG cell
            const imgNode = document.createElement('img');
            imgNode.classList.add("admin-list-entry","admin-list-entry-item");
            imgNode.src = user.img;

            //If the listed user rank is equal or higher than the signed-in user
            if(curUser != null && rankEnum.indexOf(user.rank) >= rankEnum.indexOf(curUser.rank)){
                var rankContent = user.rank;
            }else{
                //Create rank select
                var rankContent = document.createElement('select');
                rankContent.dataset.user = user.user;
                rankContent.classList.add("channel-rank-select")
                rankContent.addEventListener("change", this.submitUpdate.bind(this));

                //for each rank in the enum
                rankEnum.slice().reverse().forEach((rank) => {
                    //Create an option for the given rank
                    const rankOption = document.createElement('option');
                    rankOption.value = rank;
                    rankOption.textContent = utils.unescapeEntities(rank);
                    rankOption.selected = user.rank == rank;
                    rankContent.appendChild(rankOption);
                });
            }

            //Generate row and append to table
            this.table.appendChild(utils.ux.newTableRow([
                imgNode,
                user.id,
                user.user,
                rankContent
            ]));
        });
    }

    clearTable(){
        //get all non-title table rows
        const rows = this.table.querySelectorAll("tr.gen-row");

        //for each row
        rows.forEach((row) => {
            //The Lord Yeeteth, and The Lord Yoinketh away...
            row.remove();
        });
    }
}

class banList{
    constructor(channel){
        this.channel = channel;
        this.table = document.querySelector("#admin-ban-list-table");
        this.banPrompt = document.querySelector("#new-ban-input");
        this.banButton = document.querySelector("#new-ban-button");

        this.loadList();
        this.setupInput();
    }

    setupInput(){
        this.banButton.addEventListener('click', this.ban.bind(this));
    }

    async loadList(){
        const data = await utils.ajax.getChanBans(this.channel);
        this.updateList(data);
    }

    clearList(){
        const oldRows = this.table.querySelectorAll('tr.gen-row');
        oldRows.forEach((row) => {
            row.remove();
        });
    }

    async ban(event){
        new chanBanUserPopup(this.channel, this.banPrompt.value, this.updateList.bind(this));
    }

    async unban(event){
        //Rip user outta the target id
        const user = event.target.dataset.name;
        //Tell the server to unban them and get the list returned
        const list = await utils.ajax.chanUnban(this.channel, user);

        //Use the list to update the UI
        this.updateList(list);
    }

    updateList(bans){
        //for each ban listed
        this.clearList();
        bans.forEach((ban)=>{
            //Create IMG node
            const imgNode = document.createElement('img');
            imgNode.classList.add("admin-list-entry","admin-list-entry-item");
            imgNode.src = ban.user.img;

            //Create banAlts check
            const banAlts = document.createElement('i');
            banAlts.classList.add("admin-user-list-ban-alts",ban.banAlts ? "bi-check" : "bi-x");

            //Create unban icon node
            const unbanIcon = document.createElement('i');
            unbanIcon.classList.add("bi-emoji-smile-fill","admin-user-list-icon","admin-user-list-unban-icon");
            unbanIcon.dataset.name = ban.user.user;
            unbanIcon.title = `Unban ${ban.user.user}`;
            unbanIcon.addEventListener("click", this.unban.bind(this));

            //THIS IS NOT YET PROPERLY IMPLEMENTED AS getDaysUntilExpiration has not been made for chan bans
            const expirationString = ban.expirationDays < 0 ? "Never" :  `${new Date(ban.expirationDate).toDateString()}<br>(${ban.daysUntilExpiration} day(s) left)`;

            //Generate row and append to table
            this.table.appendChild(utils.ux.newTableRow([
                imgNode,
                ban.user.id,
                ban.user.user,
                banAlts,
                new Date(ban.banDate).toDateString(),
                expirationString,
                unbanIcon
            ]));
        });
    }
}

class prefrenceList{
    constructor(channel){
        this.channel = channel;
        this.inputs = document.querySelectorAll(".channel-preference-list-item");

        this.setupInput();
    }

    setupInput(){
        this.inputs.forEach((input) => {
            input.addEventListener("change", this.submitUpdate.bind(this));
        });
    }

    async submitUpdate(event){
        //Get key from event target
        const key = event.target.dataset.key;

        //Pull value from event target
        let value = event.target.value;

        //If this is a checkmark
        if(event.target.type == "checkbox"){
            //Use the .checked property instead of .value
            value = event.target.checked;
        }

        //Create settings map
        const settingsMap = new Map([
            [key, value]
        ]);

        //Send update and collect results
        const update = await utils.ajax.setChannelSetting(this.channel, settingsMap);

        //Handle update from server
        this.handleUpdate(update, event.target, key);
    }

    handleUpdate(data, target, key){
        if(data){
            target = data[key];
        }
    }
}

class permList{
    constructor(channel){
        this.channel = channel
        this.inputs = document.querySelectorAll(".channel-perm-select");
        this.setupInput();
    }

    setupInput(){
        this.inputs.forEach((input) => {
            input.addEventListener("change", this.submitUpdate.bind(this));
        });
    }

    async submitUpdate(event){
        const key = event.target.dataset.key;
        const value = event.target.value;
        const permMap = new Map([
            [key, value]
        ]);

        this.handleUpdate(await utils.ajax.setChannelPermissions(this.channel, permMap), event.target, key);
    }

    handleUpdate(data, target, key){
        target.value = data[key];
    }
}

class tokeCommandList{
    constructor(channel){
        this.channel = channel;
        this.tokeCommandList = document.querySelector('div.toke-command-list');
        this.newTokeCommandPrompt = document.querySelector('#new-toke-command-input');
        this.newTokeCommandButton = document.querySelector('#new-toke-command-button');

        //Setup input
        this.setupInput();

        //Pull the toke list on load
        this.getTokeList();
    }

    setupInput(){
        this.newTokeCommandButton.addEventListener('click', this.addToke.bind(this));
    }

    async addToke(event){
        //Send out the new toke command and get the new list
        const tokeList = await utils.ajax.addChanToke(this.channel, this.newTokeCommandPrompt.value);
        //clear the prompt
        this.newTokeCommandPrompt.value = "";
        //render the returned list
        this.renderTokeList(tokeList);
    }

    async getTokeList(){
        const tokeList = await utils.ajax.getChanTokes(this.channel);
        this.renderTokeList(tokeList);
    }

    clearTokeList(){
        this.tokeCommandList.innerHTML = "";
    }

    async deleteToke(event){
        const name = event.target.dataset.name;

        const tokeList = await utils.ajax.deleteChanToke(this.channel, name);

        this.renderTokeList(tokeList);
    }

    renderTokeList(tokeList){
        if(tokeList != null){
            //Clear our the toke list
            this.clearTokeList();

            //For each toke in the received list
            tokeList.forEach((toke)=>{
                //generate a toke command span, and append it to the toke list div
                this.tokeCommandList.appendChild(this.generateTokeSpan(toke));
            });
        }
    }

    generateTokeSpan(toke){
        //Create toke command span
        const tokeSpan = document.createElement('span');
        tokeSpan.classList.add('toke-command-list');

        //Create toke command label
        const tokeLabel = document.createElement('p');
        tokeLabel.textContent = utils.unescapeEntities(`!${toke}`);
        tokeLabel.classList.add('toke-command-list');

        //Create toke command delete icon
        const tokeDelete = document.createElement('i');
        tokeDelete.classList.add('toke-command-list', 'bi-trash-fill', 'toke-command-delete');
        tokeDelete.dataset.name = toke;
        tokeDelete.addEventListener('click', this.deleteToke.bind(this));

        //append span contents to tokeSpan
        tokeSpan.appendChild(tokeLabel);
        tokeSpan.appendChild(tokeDelete);

        //return the toke span
        return tokeSpan
    }
}

class emoteList{
    constructor(channel){
        this.channel = channel
        this.linkPrompt = document.querySelector('#new-emote-link-input');
        this.namePrompt = document.querySelector('#new-emote-name-input');
        this.addButton = document.querySelector('#new-emote-button');
        this.emoteList = document.querySelector('#emote-list');

        //Setup input
        this.setupInput();

        //Pull and render emote list
        this.updateList();
    }

    setupInput(){
       this.addButton.addEventListener('click', this.addEmote.bind(this));
    }

    async deleteEmote(event){
        //Strip name from element id
        const name = event.target.dataset.name;

        //Delete emote and pull list
        const list = await utils.ajax.deleteChanEmote(this.channel, name);

        //If we received a list
        if(list != null){
            //Pass updated liste to renderEmoteList function instead of pulling it twice
            this.renderEmoteList(list);
        }
    }

    async addEmote(event){
        //Add emote to list and ingest returned updates list
        const list = await utils.ajax.addChanEmote(this.channel, this.namePrompt.value, this.linkPrompt.value);

        //If we received a list
        if(list != null){
            //Pass updated liste to renderEmoteList function instead of pulling it twice
            this.renderEmoteList(list);

            //Clear out the prompts
            this.namePrompt.value = '';
            this.linkPrompt.value = '';
        }
    }

    async updateList(){
        const list = await utils.ajax.getChanEmotes(this.channel);
        this.renderEmoteList(list);
    }

    renderEmoteList(list){
        //Clear the current list
        this.emoteList.innerHTML = "";

        //For each emote in the list
        list.forEach((emote) => {
            //Create span to hold emote
            const emoteDiv = document.createElement('div');
            emoteDiv.classList.add('emote-list-emote');

            //If the emote is an image
            if(emote.type == 'image'){
                //Create image node
                var emoteMedia = document.createElement('img');
            //if emote is a video
            }else if(emote.type == 'video'){
                //create video node
                var emoteMedia = document.createElement('video');
                //Set video properties
                emoteMedia.autoplay = true;
                emoteMedia.muted = true;
                emoteMedia.controls = false;
                emoteMedia.loop = true;
            }

            //set media link as source
            emoteMedia.src = emote.link;
            //Set media class
            emoteMedia.classList.add('emote-list-media');

            //Create title span
            const titleSpan = document.createElement('span');
            titleSpan.classList.add('emote-list-title');

            //Create paragraph tag
            const emoteTitle = document.createElement('p');
            //Set title class
            emoteTitle.classList.add('emote-list-title');
            //Set emote title
            emoteTitle.textContent = utils.unescapeEntities(`[${emote.name}]`);
            
            //Create delete icon
            const deleteIcon = document.createElement('i');
            //Set delete icon id and class
            deleteIcon.classList.add('bi-trash-fill', 'emote-list-delete');
            deleteIcon.dataset.name = emote.name;
            //Add delete icon event listener
            deleteIcon.addEventListener('click',this.deleteEmote.bind(this));


            //Add the emote media to the emote span
            emoteDiv.appendChild(emoteMedia);
            //Add title paragraph node
            titleSpan.appendChild(emoteTitle);
            //Add trash icon node
            titleSpan.appendChild(deleteIcon);
            //Add title span
            emoteDiv.appendChild(titleSpan);

            //Append the mote span to the emote list
            this.emoteList.appendChild(emoteDiv);
        });
    }
}

class deleteBtn{
    constructor(channel){
        this.channel = channel;
        this.delete = document.querySelector("#chan-delete");
    
        this.setupInput();
    }

    setupInput(){
        this.delete.addEventListener('click', () => {new deleteAccountPopup(this.channel)});
    }

}

class deleteAccountPopup{
    constructor(channel){
        this.channel = channel;
        this.popup = new canopyUXUtils.popup("nukeChannel", true, this.asyncConstructor.bind(this));
    }

    asyncConstructor(){
        this.prompt = document.querySelector("#delete-channel-popup-prompt");
        this.label = document.querySelector("#delete-channel-popup-content");

        //Fill channel label
        this.label.textContent = this.label.textContent.replaceAll("[CHANNEL]", utils.unescapeEntities(this.channel));

        this.setupInput();
    }

    setupInput(){
        this.prompt.addEventListener("keydown", this.nukeChannel.bind(this));
    }

    async nukeChannel(event){
        if(event.key == "Enter"){
            if(this.channel === event.target.value){
                await utils.ajax.deleteChannel(this.channel, event.target.value);
            }
        }
    }
}

const channelSettings = new channelSettingsPage();