////
// Runs commands and such on the server.
// This is the "shell" - the "terminal" is the browser UI.

import type { CommandResult, CommandOutput } from "./shared/types"
import type { Session } from "./session"
import { commandExists, commandPath } from "./commands"
import { ALS } from "./session"

const sessions: Map<string, Session> = new Map()

export async function runCommand(sessionId: string, taskId: string, input: string, ws?: any): Promise<CommandResult> {
    const [cmd = "", ...args] = input.split(" ")

    if (!commandExists(cmd))
        return { status: "error", output: `${cmd} not found` }

    let status: "ok" | "error" = "ok"
    let output: CommandOutput = ""
    const state = getState(sessionId, taskId, ws)

    try {
        [status, output] = await ALS.run(state, async () => await exec(cmd, args))
    } catch (err) {
        status = "error"
        output = errorMessage(err)
    }

    return { status, output }
}

async function exec(cmd: string, args: string[]): Promise<["ok" | "error", CommandOutput]> {
    const module = await import(commandPath(cmd) + "?t+" + Date.now())

    if (module?.game)
        return ["ok", { game: cmd }]

    if (!module || !module.default)
        return ["error", `${cmd} has no default export`]

    return processExecOutput(await module.default(...args))
}

export function processExecOutput(output: string | any): ["ok" | "error", CommandOutput] {
    if (typeof output === "string") {
        return ["ok", output]
    } else if (typeof output === "object") {
        if (output.error) {
            return ["error", output.error]
        } else if (isJSX(output)) {
            return ["ok", { html: output.toString() }]
        } else if (output.html && isJSX(output.html)) {
            output.html = output.html.toString()
            return ["ok", output]
        } else {
            return ["ok", output]
        }
    } else if (output === undefined) {
        return ["ok", ""]
    } else {
        return ["ok", String(output)]
    }
}

function getState(sessionId: string, taskId: string, ws?: any): Session {
    let state = sessions.get(sessionId)
    if (!state) {
        state = { sessionId: sessionId, project: "" }
        sessions.set(sessionId, state)
    }
    state.taskId = taskId
    if (ws) state.ws = ws
    return state
}

function errorMessage(error: Error | any): string {
    if (!(error instanceof Error))
        return String(error)

    let msg = `${error.name}: ${error.message}`
    if (error.stack) msg += `\n${error.stack}`
    return msg
}

function isJSX(obj: any): boolean {
    return typeof obj === 'object' && 'tag' in obj && 'props' in obj && 'children' in obj
}