import { mustBe, mustBeString, mustMatch } from "./mustbe.js";

export const MessageType = Object.freeze({
    /**
     * Very bad logic error. Player must quit game, refresh page, and log in again.
     *
     * Client-->Server
     *      or
     * Server-->Client-->Plater
     */
    CALAMITY: "CALAMITY",

    /**
     * Tell recipient that an error has occurred
     *
     * Server-->Client-->Player
     */
    ERROR: "E",

    /**
     * Message to be displayed.
     *
     * Server-->Client-->Player
     */
    TEXT: "T",

    /**
     * Player has entered data, and sends it to server.
     *
     * Player-->Client-->Server
     */
    REPLY: "R",

    /**
     * Player wants to quit.
     *
     * Player-->Client-->Server
     */
    QUIT: "QUIT",

    /**
     * Player wants help
     *
     * Player-->Client-->Server
     */
    HELP: "HELP",

    /**
     * Server tells the client to prompt the player for some data
     *
     * Server-->Client-->Player
     */
    PROMPT: "P",

    /**
     * Server tells the client to prompt the player for some data
     *
     * Server-->Client-->Player
     */
    SYSTEM: "_",

    /**
     * Debug message, to be completely ignored in production
     *
     * Client-->Server
     *      or
     * Server-->Client-->Plater
     */
    DEBUG: "dbg",

    /**
     * Player sent colon-prefixed, an out-of-order, command
     *
     * Player-->Client-->Server
     */
    COLON: ":",
});

/**
 * Represents a message sent to/from client
 *
 * @property {string?} command
 * @property {any[]} args
 */
export class WebsocketMessage {
    /** @protected @type {any[]} _arr The array that contains the message data */
    _data;

    /** @constant @readonly @type {string} _arr The array that contains the message data */
    type;

    /**
     * @param {string} msgData the raw text data in the websocket message.
     */
    constructor(msgData) {
        if (typeof msgData !== "string") {
            throw new Error(
                "Could not create client message. Attempting to parse json, but data was not even a string, it was a " +
                    typeof msgData,
            );
            return;
        }

        let data;
        try {
            data = JSON.parse(msgData);
        } catch (_) {
            throw new Error(
                `Could not create client message. Attempting to parse json, but data was invalid json: >>> ${msgData} <<<`,
            );
        }

        if (!Array.isArray(data)) {
            throw new Error(`Could not create client message. Excpected an array, but got a ${typeof this._data}`);
        }

        if (data.length < 1) {
            throw new Error(
                "Could not create client message. Excpected an array with at least 1 element, but got an empty one",
            );
        }

        this.type = mustBeString(data[0]);

        switch (this.type) {
            case MessageType.REPLY: // player ==> client ==> server
                this.text = mustBeString(data[1]);
                break;
            case MessageType.HELP: // player ==> client ==> server
                this.text = data[1] === undefined ? "" : mustBeString(data[1]).trim();
                break;
            case MessageType.COLON: // player ==> client ==> server
                this.command = mustMatch(data[1], /^[a-z0-9_]+$/);
                this.args = mustBe(data[2], "any[]");
                break;
            case MessageType.DEBUG: // server ==> client
            case MessageType.ERROR: // server ==> client ==> player
            case MessageType.QUIT: // player ==> client ==> server
            case MessageType.SYSTEM: // client <==> server
            case MessageType.PROMPT: // server ==> client ==> player
            case MessageType.TEXT: // server ==> client ==> player
                break;
            default:
                throw new Error(`Unknown message type: >>${typeof this.type}<<`);
        }
    }

    isQuit() {
        return this.type === MessageType.QUIT;
    }

    isHelp() {
        return this.type === MessageType.HELP;
    }

    isColon() {
        return this.type === MessageType.COLON;
    }

    isReply() {
        return this.type === MessageType.REPLY;
    }

    isSysMessage() {
        return this.type === MessageType.SYSTEM;
    }

    isDebug() {
        return this.type === MessageType.DEBUG;
    }
}

/**
 * Given a message type and some args, create a string that can be sent from the server to the client (or vise versa)
 *
 * @param {string} messageType
 * @param {...any} args
 */
export function formatMessage(messageType, ...args) {
    return JSON.stringify([messageType, ...args]);
}
