/* ===================== AUTOGENERATED SOURCE IMPORT ===================== */

import Template from "/emcJS/util/html/template/HTMLTemplate.js";

const TPL = new Template(`
    <emc-panel-tabpanel id="ap-tabs">
        <div category="ap-log">
            <div class="container">
                <div class="filter-bar">
                    <emc-input-search id="ap-log-search" class="search-field" placeholder="Search" nohover></emc-input-search>
                    <emc-select-token id="ap-log-msgtype" class="filter-choice" placeholder="Message Type" nohover chooseonly multiple value="[&quot;ServerChat&quot;,&quot;Chat&quot;,&quot;ItemSend&quot;]">
                        <option value="ServerChat" label="Server Messages"></option>
                        <option value="Chat" label="Chat Messages"></option>
                        <option value="ItemSend" label="Item Transfers"></option>
                    </emc-select-token>
                    <emc-select-token id="ap-log-itemclass" class="filter-choice" placeholder="Item Class" nohover chooseonly multiple value="[&quot;progressive&quot;,&quot;useful&quot;,&quot;normal&quot;,&quot;trap&quot;]">
                        <option value="progressive" label="Progressive"></option>
                        <option value="useful" label="Useful"></option>
                        <option value="normal" label="Normal"></option>
                        <option value="trap" label="Trap"></option>
                    </emc-select-token>
                    <emc-select-token id="ap-log-direction" class="filter-choice" placeholder="Direction" nohover chooseonly multiple value="[&quot;sender&quot;,&quot;reciever&quot;]">
                        <option value="sender" label="Sender"></option>
                        <option value="reciever" label="Reciever"></option>
                    </emc-select-token>
                </div>
                <ootrt-ap-chatlist id="ap-log" autoscroll></ootrt-ap-chatlist>
            </div>
        </div>
        <div category="hints">
            <div class="container">
                <div class="filter-bar">
                    <emc-input-search id="ap-hints-search" class="search-field" placeholder="Search..." nohover></emc-input-search>
                    <emc-select-token id="ap-hints-itemclass" class="filter-choice" placeholder="Item Class" nohover chooseonly multiple value="[&quot;progressive&quot;,&quot;useful&quot;,&quot;normal&quot;,&quot;trap&quot;]">
                        <option value="progressive" label="Progressive"></option>
                        <option value="useful" label="Useful"></option>
                        <option value="normal" label="Normal"></option>
                        <option value="trap" label="Trap"></option>
                    </emc-select-token>
                    <emc-select-token id="ap-hints-direction" class="filter-choice" placeholder="Direction" nohover chooseonly multiple value="[&quot;finder&quot;,&quot;reciever&quot;]">
                        <option value="finder" label="Finder"></option>
                        <option value="reciever" label="Reciever"></option>
                    </emc-select-token>
                    <emc-select-token id="ap-hints-status" class="filter-choice" placeholder="Status" nohover chooseonly multiple value="[&quot;found&quot;,&quot;missing&quot;]">
                        <option value="found" label="Found"></option>
                        <option value="missing" label="Not found"></option>
                    </emc-select-token>
                </div>
                <emc-datagrid id="ap-hints" stretched="location">
                    <emc-datagrid-column name="finder" label="Finder" type="apuser" sortable width="300"></emc-datagrid-column>
                    <emc-datagrid-column name="item" label="Item" type="apitems" sortable width="400"></emc-datagrid-column>
                    <emc-datagrid-column name="reciever" label="Reciever" type="apuser" sortable width="300"></emc-datagrid-column>
                    <emc-datagrid-column name="location" label="Location" type="text" sortable width="400" textcolor="var(--ap-color-location, #00ff7f)"></emc-datagrid-column>
                    <emc-datagrid-column name="entrance" label="Entrance" type="text" sortable width="300" textcolor="var(--ap-color-entrance, #6495ed)"></emc-datagrid-column>
                    <emc-datagrid-column name="found" label="Found" type="boolean" sortable width="120"></emc-datagrid-column>
                </emc-datagrid>
            </div>
        </div>
        <div category="hint_builder">
            <div class="container">
                <ootrt-ap-item-hint-builder id="ap-item-hint-builder"></ootrt-ap-item-hint-builder>
            </div>
        </div>
    </emc-panel-tabpanel>
    <div class="hint-stats">
        Hints available: <span id="ap-hints-available">0</span> | Next hint: <span id="ap-hint-points-next">0</span> / <span id="ap-hint-points-needed">0</span> <progress id="ap-hint-point-progress" value="0" max="0"></progress>
    </div>
    <emc-input-string id="chat-input" placeholder="Message / Command" description="!help for a list of commands - arrow up / arrow down for command history" nohover autocomplete="off" disabled></emc-input-string>
    <!--
        [Hint Location category]
        - search field
        - datalist with 10 entries max to "force" search
        - on location click fill chat with "hint_location" message and wait for user to send
        -> locations in locationlist/-map should also get a "AP hint location" entry for contextmenu
    -->
    
`);

import GlobalStyle from "/emcJS/util/html/template/CSSTemplate.js";

const STYLE = new GlobalStyle(`
    :host {
        display: flex;
        flex-direction: column;
        flex: 1;
        width: 100%;
        height: 100%;
        font-size: 1rem;
    }
    
    #ap-tabs {
        flex: 1;
    }
    
    .panel[category="hint_builder"] {
        border-bottom-style: solid;
        border-bottom-width: var(--tabpanel-categories-border-width, 2px);
        border-bottom-color: var(--tabpanel-categories-border-color, #222222);
    }
    
    .container {
        display: flex;
        flex-direction: column;
        width: 100%;
        height: 100%;
    }
    
    #ap-log,
    #ap-hints {
        flex: 1;
        margin-top: 0px;
        margin-bottom: 0px;
        font-size: 1.3rem;
    }
    
    .filter-bar {
        display: flex;
        padding: 3px;
    }
    
    .search-field,
    .filter-choice {
        padding: 3px;
    }
    
    .search-field {
        width: 300px;
    }
    
    .filter-choice {
        width: 240px;
        flex-grow: 0;
    }
    
    #chat-input {
        flex-grow: 0;
    }
    
    .hint-stats {
        padding: 10px 10px 0px;
    }
    
    #ap-hint-point-progress {
        margin-left: 5px;
        -webkit-appearance: none;
    }
    
    #ap-hint-point-progress::-webkit-progress-inner-element {
        border-style: solid;
        border-width: var(--progress-border-width, 2px);
        border-color: var(--progress-border-color, #222222);
        border-radius: var(--progress-border-radius, 6px);
        overflow: hidden;
    }
    
    #ap-hint-point-progress::-webkit-progress-bar {
        background-color: var(--progress-back-color, #ffffff);
    }
    
    #ap-hint-point-progress::-webkit-progress-value {
        background-color: var(--progress-fill-color, #eaeaea);
        border-radius: var(--progress-border-radius, 6px);
    }
    
`);

/* ======================================================================= */

import SimpleDataProvider from "/emcJS/util/dataprovider/SimpleDataProvider.js";
import ArraySet from "/emcJS/data/collection/ArraySet.js";
import {
    recordsToDict, dictToRecords
} from "/emcJS/util/helper/storage/Records.js";
import {
    isStringNotEmpty
} from "/emcJS/util/helper/CheckType.js";
import Panel from "/emcJS/ui/layout/Panel.js";
import Savestate from "/GameTrackerJS/savestate/Savestate.js";
import {
    VALID_JSON_MESSAGE_TYPE
} from "/ArchipelagoJS/types/JSONMessagePart.js";
import {
    ITEM_FLAGS
} from "/ArchipelagoJS/consts/ItemFlags.js";
import {
    PRINT_JSON_TYPE
} from "/ArchipelagoJS/consts/PrintJSONType.js";
import ArchipelagoController from "../../../util/archipelago/ArchipelagoController.js";
import "/emcJS/ui/dataview/datagrid/DataGrid.js";
import "/emcJS/ui/form/element/input/string/StringInput.js";
import "/emcJS/ui/form/element/select/token/TokenSelect.js";
import "./chat/ChatList.js";
import "./hintbuilder/ItemHintBuilder.js";
import "./cell/itemcell/DataGridCellAPItem.js";
import "./cell/itemcell/DataGridCellAPUser.js";
import {
    AP_STORAGES
} from "../../../util/archipelago/storage/RegisterAPStorage.js";

const ITEM_CLASSES = {
    [ITEM_FLAGS.PROGRESSION]: "progressive",
    [ITEM_FLAGS.NEVER_EXCLUDE]: "useful",
    [ITEM_FLAGS.FILLER]: "normal",
    [ITEM_FLAGS.TRAP]: "trap"
};

// TODO
// add hint location function through clicking the desired locations (generate text user needs to send)

class APTextClient extends Panel {

    #apTabsEl;

    #chatInputEl;

    #commandHistory = new ArraySet();

    #commandHistoryNeedle = -1;

    #apHintsAvailable;

    #apHintPointsNext;

    #apHintPointsNeeded;

    #apHintPointProgress;

    constructor() {
        super();
        this.shadowRoot.append(TPL.generate());
        STYLE.apply(this.shadowRoot);
        /* --- */
        this.#apTabsEl = this.shadowRoot.getElementById("ap-tabs");
        this.#chatInputEl = this.shadowRoot.getElementById("chat-input");
        /* --- */
        const apLogListEl = this.shadowRoot.getElementById("ap-log");
        const apLogSearchEl = this.shadowRoot.getElementById("ap-log-search");
        const apLogMsgTypeEl = this.shadowRoot.getElementById("ap-log-msgtype");
        const apLogItemClassEl = this.shadowRoot.getElementById("ap-log-itemclass");
        const apLogDirectionEl = this.shadowRoot.getElementById("ap-log-direction");
        const apLogListDataProvider = new SimpleDataProvider(apLogListEl, [], {
            config: {
                filterIgnoreNullValues: true
            }
        });
        const apHintGridEl = this.shadowRoot.getElementById("ap-hints");
        const apHintsSearchEl = this.shadowRoot.getElementById("ap-hints-search");
        const apHintsItemClassEl = this.shadowRoot.getElementById("ap-hints-itemclass");
        const apHintsDirectionEl = this.shadowRoot.getElementById("ap-hints-direction");
        const apHintsStatusEl = this.shadowRoot.getElementById("ap-hints-status");
        const apHintGridDataProvider = new SimpleDataProvider(apHintGridEl, dictToRecords(AP_STORAGES.hints.getAll()), {
            config: {
                searchFields: ["finder", "item.item", "reciever", "location", "entrance"],
                filterIgnoreNullValues: true,
                sort: ["found"]
            }
        });
        const apItemHintBuilderEl = this.shadowRoot.getElementById("ap-item-hint-builder");
        this.#apHintsAvailable = this.shadowRoot.getElementById("ap-hints-available");
        this.#apHintPointsNext = this.shadowRoot.getElementById("ap-hint-points-next");
        this.#apHintPointsNeeded = this.shadowRoot.getElementById("ap-hint-points-needed");
        this.#apHintPointProgress = this.shadowRoot.getElementById("ap-hint-point-progress");
        /* --- */
        ArchipelagoController.addEventListener("connected", () => {
            this.#calculateHintPoints();
            this.#chatInputEl.disabled = false;
        });
        ArchipelagoController.addEventListener("disconnected", () => {
            this.#chatInputEl.disabled = true;
        });
        /* --- */
        ArchipelagoController.addEventListener("message", (event) => {
            const {data} = event;
            const resolvedMessage = this.#resolveMessagegParts(data);
            apLogListDataProvider.addEntry(resolvedMessage);
        });
        apLogSearchEl.addEventListener("change", () => {
            apLogListDataProvider.updateConfig({search: apLogSearchEl.value});
        });
        apLogMsgTypeEl.addEventListener("change", () => {
            const {filter} = apLogListDataProvider.getOptions();
            filter.type = apLogMsgTypeEl.value;
            apLogListDataProvider.updateConfig({filter});
        });
        apLogItemClassEl.addEventListener("change", () => {
            const {filter} = apLogListDataProvider.getOptions();
            filter.itemClass = apLogItemClassEl.value;
            apLogListDataProvider.updateConfig({filter});
        });
        apLogDirectionEl.addEventListener("change", () => {
            const {filter} = apLogListDataProvider.getOptions();
            const value = apLogDirectionEl.value;
            if (value.includes("sender")) {
                if (value.includes("reciever")) {
                    filter.isSender = null;
                    filter.isReciever = null;
                } else {
                    filter.isSender = true;
                    filter.isReciever = null;
                }
            } else if (value.includes("reciever")) {
                filter.isSender = null;
                filter.isReciever = true;
            } else {
                filter.isSender = false;
                filter.isReciever = false;
            }
            apLogListDataProvider.updateConfig({filter});
        });
        apLogListEl.addEventListener("scrollend", (event) => {
            event.stopPropagation();
            apLogListEl.autoscroll = apLogListEl.getVerticalScrollFactor() === 1;
        }, true);
        /* --- */
        ArchipelagoController.addEventListener("hintupdate", (event) => {
            for (const hint of event.data) {
                const resolvedHint = this.#resolveHint(hint);
                apHintGridDataProvider.addEntry(resolvedHint);
            }
            AP_STORAGES.hints.setAll(recordsToDict(apHintGridDataProvider.getSource()));
        });
        apHintsSearchEl.addEventListener("change", () => {
            apHintGridDataProvider.updateConfig({search: apHintsSearchEl.value});
        });
        apHintsItemClassEl.addEventListener("change", () => {
            const {filter} = apHintGridDataProvider.getOptions();
            filter["item.itemClass"] = apHintsItemClassEl.value;
            apHintGridDataProvider.updateConfig({filter});
        });
        apHintsDirectionEl.addEventListener("change", () => {
            const {filter} = apHintGridDataProvider.getOptions();
            const value = apHintsDirectionEl.value;
            if (value.includes("finder")) {
                if (value.includes("reciever")) {
                    filter["finder.current"] = null;
                    filter["reciever.current"] = null;
                } else {
                    filter["finder.current"] = true;
                    filter["reciever.current"] = null;
                }
            } else if (value.includes("reciever")) {
                filter["finder.current"] = null;
                filter["reciever.current"] = true;
            } else {
                filter["finder.current"] = false;
                filter["reciever.current"] = false;
            }
            apHintGridDataProvider.updateConfig({filter});
        });
        apHintsStatusEl.addEventListener("change", () => {
            const {filter} = apHintGridDataProvider.getOptions();
            const value = apHintsStatusEl.value;
            if (value.includes("found")) {
                if (value.includes("missing")) {
                    filter.found = null;
                } else {
                    filter.found = true;
                }
            } else if (value.includes("missing")) {
                filter.found = false;
            } else {
                filter.found = [];
            }
            apHintGridDataProvider.updateConfig({filter});
        });
        /* --- */
        apItemHintBuilderEl.addEventListener("hint", (event) => {
            const {value} = event;
            this.setItemHintMessage(value);
        });
        /* --- */
        Savestate.addEventListener("load", () => {
            apLogListDataProvider.setSource([]);
            apHintGridDataProvider.setSource(dictToRecords(AP_STORAGES.hints.getAll()));
        });
        /* --- */
        this.#chatInputEl.addEventListener("keydown", (event) => {
            const {key} = event;
            if (key === "Enter") {
                const text = this.#chatInputEl.value;
                ArchipelagoController.sendChatMessage(text);
                this.#commandHistoryNeedle = -1;
                if (text.startsWith("!")) {
                    this.#commandHistory.insertAt(0, text);
                }
                this.#chatInputEl.value = "";
                event.stopPropagation();
                return false;
            } else if (key === "ArrowUp") {
                if (this.#commandHistoryNeedle < this.#commandHistory.size - 1) {
                    const index = ++this.#commandHistoryNeedle;
                    const command = this.#commandHistory.at(index);
                    this.#chatInputEl.value = command;
                }
            } else if (key === "ArrowDown") {
                if (this.#commandHistoryNeedle >= 0) {
                    const index = --this.#commandHistoryNeedle;
                    if (index >= 0) {
                        const command = this.#commandHistory.at(index);
                        this.#chatInputEl.value = command;
                    } else {
                        this.#chatInputEl.value = "";
                    }
                }
            }
        });
        /* --- */
        ArchipelagoController.addEventListener("hintpoints", () => {
            this.#calculateHintPoints();
        });
        ArchipelagoController.addEventListener("hintcost", () => {
            this.#calculateHintPoints();
        });
        this.#calculateHintPoints();
    }

    switchToApLog() {
        this.#apTabsEl.active = "ap-log";
    }

    switchToHints() {
        this.#apTabsEl.active = "hints";
    }

    setItemHintMessage(item) {
        if (/* ArchipelagoController.isConnected() &&  */isStringNotEmpty(item)) {
            this.#chatInputEl.value = `!hint ${item}`;
            this.#chatInputEl.focus();
        }
    }

    setLocationHintMessage(location) {
        if (/* ArchipelagoController.isConnected() &&  */isStringNotEmpty(location)) {
            this.#chatInputEl.value = `!hint_location ${location}`;
            this.#chatInputEl.focus();
        }
    }

    #resolveMessagegParts(packet) {
        if (packet.type === PRINT_JSON_TYPE.SERVER_CHAT) {
            return {
                type: packet.type,
                message: packet.message
            };
        }

        if (packet.type === PRINT_JSON_TYPE.CHAT) {
            const playerAlias = ArchipelagoController.getPlayerAlias(packet.slot);
            return {
                type: packet.type,
                message: packet.message,
                playerAlias
            };
        }

        const apPlayerId = ArchipelagoController.playerId;
        /**
         * TODO
         * add dictionary to combine message resolving and rendering into one entry.
         * the idea is to make it easily extendible.
         */
        const resolvedMessageData = packet.data.map((piece) => {
            switch (piece.type) {
                case VALID_JSON_MESSAGE_TYPE.PLAYER_ID: {
                    const playerId = parseInt(piece.text);
                    const playerAlias = ArchipelagoController.getPlayerAlias(playerId);
                    if (playerId === apPlayerId) {
                        return {
                            type: piece.type,
                            current: true,
                            playerAlias
                        };
                    } else {
                        const playerGame = ArchipelagoController.getPlayerGame(playerId);
                        return {
                            type: piece.type,
                            current: false,
                            playerAlias,
                            playerGame
                        };
                    }
                }

                case VALID_JSON_MESSAGE_TYPE.LOCATION_ID: {
                    const locationId = parseInt(piece.text);
                    const locationName = ArchipelagoController.getLocationName(piece.player, locationId);
                    return {
                        type: piece.type,
                        locationName
                    };
                }

                case VALID_JSON_MESSAGE_TYPE.ITEM_ID: {
                    const itemClass = ITEM_CLASSES[piece.flags];
                    const itemId = parseInt(piece.text);
                    const itemName = ArchipelagoController.getItemName(piece.player, itemId);
                    return {
                        type: piece.type,
                        itemName,
                        itemClass
                    };
                }

                case VALID_JSON_MESSAGE_TYPE.ENTRANCE_NAME: {
                    return {
                        type: piece.type,
                        entranceName: piece.text
                    };
                }

                case VALID_JSON_MESSAGE_TYPE.HINT_STATUS: {
                    return {
                        type: piece.type,
                        hintStatus: piece.hint_status,
                        text: piece.text
                    };
                }

                case VALID_JSON_MESSAGE_TYPE.COLOR: {
                    return {
                        type: piece.type,
                        color: piece.color,
                        text: piece.text
                    };
                }

                default: {
                    return {
                        type: piece.type,
                        text: piece.text
                    };
                }
            }
        });

        if (packet.type === PRINT_JSON_TYPE.ITEM_SEND) {
            const senderAlias = ArchipelagoController.getPlayerAlias(packet.item.player);
            const recieverAlias = ArchipelagoController.getPlayerAlias(packet.receiving);
            const itemClass = ITEM_CLASSES[packet.item.flags];
            const itemName = ArchipelagoController.getItemName(packet.receiving, packet.item.item);
            const locationName = ArchipelagoController.getLocationName(packet.item.player, packet.item.location);

            return {
                type: packet.type,
                isSender: packet.item.player === apPlayerId,
                isReciever: packet.receiving === apPlayerId,
                sender: senderAlias,
                reciever: recieverAlias,
                itemClass,
                itemName,
                locationName,
                message: resolvedMessageData
            };
        }

        return {
            type: packet.type,
            message: resolvedMessageData
        };
    }

    #resolveHint(data) {
        const {
            receiving_player,
            finding_player,
            location,
            item,
            found,
            entrance,
            item_flags
        } = data;

        const apPlayerId = ArchipelagoController.playerId;

        const finderAlias = ArchipelagoController.getPlayerAlias(finding_player);
        const recieverAlias = ArchipelagoController.getPlayerAlias(receiving_player);
        const locationName = ArchipelagoController.getLocationName(finding_player, location);
        const itemName = ArchipelagoController.getItemName(receiving_player, item);
        const itemClass = ITEM_CLASSES[item_flags];

        return {
            key: `${finding_player}_${location}`,
            isSender: finding_player === apPlayerId,
            isReciever: receiving_player === apPlayerId,
            finder: {
                alias: finderAlias,
                current: finding_player === apPlayerId
            },
            reciever: {
                alias: recieverAlias,
                current: receiving_player === apPlayerId
            },
            location: locationName,
            item: {
                item: itemName,
                itemClass: itemClass
            },
            entrance,
            found
        };
    }

    #calculateHintPoints() {
        const currentHintPoints = ArchipelagoController.currentHintPoints;
        const neededHintPoints = ArchipelagoController.neededHintPoints;

        const points = Math.floor(currentHintPoints / neededHintPoints);
        const nextHint = currentHintPoints % neededHintPoints;

        this.#apHintsAvailable.innerText = points || 0;
        this.#apHintPointsNext.innerText = nextHint || 0;
        this.#apHintPointsNeeded.innerText = neededHintPoints || 0;

        this.#apHintPointProgress.max = neededHintPoints || 0;
        this.#apHintPointProgress.value = nextHint || 0;
    }

}

Panel.registerReference("ap-text-client", APTextClient);
customElements.define("ootrt-aptextclient", APTextClient);
