package emu.grasscutter.server.http.documentation;

import static emu.grasscutter.config.Configuration.*;

import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.data.excels.SceneData;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Language;
import emu.grasscutter.utils.Utils;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

final class HandbookRequestHandler implements DocumentationHandler {
    private List<String> handbookHtmls;
    private final String template;

    public HandbookRequestHandler() {
        final File templateFile = new File(Utils.toFilePath(DATA("documentation/handbook.html")));
        if (templateFile.exists()) {
            this.template = new String(FileUtils.read(templateFile), StandardCharsets.UTF_8);
            this.handbookHtmls = generateHandbookHtmls();
        } else {
            Grasscutter.getLogger().warn("File does not exist: " + templateFile);
            this.template = null;
        }
    }

    @Override
    public void handle(Context ctx) {
        final int langIdx = Language.TextStrings.MAP_LANGUAGES.getOrDefault(DOCUMENT_LANGUAGE, 0);  // TODO: This should really be based off the client language somehow
        if (template == null) {
            ctx.status(500);
        } else {
            ctx.contentType(ContentType.TEXT_HTML);
            ctx.result(handbookHtmls.get(langIdx));
        }
    }

    private List<String> generateHandbookHtmls() {
        final int NUM_LANGUAGES = Language.TextStrings.NUM_LANGUAGES;
        final List<String> output = new ArrayList<>(NUM_LANGUAGES);
        final List<Language> languages = Language.TextStrings.getLanguages();
        final List<StringBuilder> sbs = new ArrayList<>(NUM_LANGUAGES);
        for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
            sbs.add(new StringBuilder(""));

        // Commands table
        CommandMap.getInstance().getHandlersAsList().forEach(cmd -> {
            String label = cmd.getLabel();
            String descKey = cmd.getDescriptionKey();
            for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
                sbs.get(langIdx).append("<tr><td><code>" + label + "</code></td><td>" + languages.get(langIdx).get(descKey) + "</td></tr>\n");
        });
        sbs.forEach(sb -> sb.setLength(sb.length()-1));  // Remove trailing \n
        final List<String> cmdsTable = sbs.stream().map(StringBuilder::toString).toList();

        // Avatars table
        final Int2ObjectMap<AvatarData> avatarMap = GameData.getAvatarDataMap();
        sbs.forEach(sb -> sb.setLength(0));
        avatarMap.keySet().intStream().sorted().mapToObj(avatarMap::get).forEach(data -> {
            int id = data.getId();
            Language.TextStrings name = Language.getTextMapKey(data.getNameTextMapHash());
            for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
                sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + name.get(langIdx) + "</td></tr>\n");
        });
        sbs.forEach(sb -> sb.setLength(sb.length()-1));  // Remove trailing \n
        final List<String> avatarsTable = sbs.stream().map(StringBuilder::toString).toList();

        // Items table
        final Int2ObjectMap<ItemData> itemMap = GameData.getItemDataMap();
        sbs.forEach(sb -> sb.setLength(0));
        itemMap.keySet().intStream().sorted().mapToObj(itemMap::get).forEach(data -> {
            int id = data.getId();
            Language.TextStrings name = Language.getTextMapKey(data.getNameTextMapHash());
            for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
                sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + name.get(langIdx) + "</td></tr>\n");
        });
        sbs.forEach(sb -> sb.setLength(sb.length()-1));  // Remove trailing \n
        final List<String> itemsTable = sbs.stream().map(StringBuilder::toString).toList();

        // Scenes table
        final Int2ObjectMap<SceneData> sceneMap = GameData.getSceneDataMap();
        sceneMap.keySet().intStream().sorted().mapToObj(sceneMap::get).forEach(data -> {
            int id = data.getId();
            for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
                sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + data.getScriptData() + "</td></tr>\n");
        });
        sbs.forEach(sb -> sb.setLength(sb.length()-1));  // Remove trailing \n
        final List<String> scenesTable = sbs.stream().map(StringBuilder::toString).toList();

        // Monsters table
        final Int2ObjectMap<MonsterData> monsterMap = GameData.getMonsterDataMap();
        monsterMap.keySet().intStream().sorted().mapToObj(monsterMap::get).forEach(data -> {
            int id = data.getId();
            Language.TextStrings name = Language.getTextMapKey(data.getNameTextMapHash());
            for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
                sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + name.get(langIdx) + "</td></tr>\n");
        });
        sbs.forEach(sb -> sb.setLength(sb.length()-1));  // Remove trailing \n
        final List<String> monstersTable = sbs.stream().map(StringBuilder::toString).toList();

        // Add translated title etc. to the page.
        for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++) {
            Language lang = languages.get(langIdx);
            output.add(template
                .replace("{{TITLE}}", lang.get("documentation.handbook.title"))
                .replace("{{TITLE_COMMANDS}}", lang.get("documentation.handbook.title_commands"))
                .replace("{{TITLE_AVATARS}}", lang.get("documentation.handbook.title_avatars"))
                .replace("{{TITLE_ITEMS}}", lang.get("documentation.handbook.title_items"))
                .replace("{{TITLE_SCENES}}", lang.get("documentation.handbook.title_scenes"))
                .replace("{{TITLE_MONSTERS}}", lang.get("documentation.handbook.title_monsters"))
                .replace("{{HEADER_ID}}", lang.get("documentation.handbook.header_id"))
                .replace("{{HEADER_COMMAND}}", lang.get("documentation.handbook.header_command"))
                .replace("{{HEADER_DESCRIPTION}}", lang.get("documentation.handbook.header_description"))
                .replace("{{HEADER_AVATAR}}", lang.get("documentation.handbook.header_avatar"))
                .replace("{{HEADER_ITEM}}", lang.get("documentation.handbook.header_item"))
                .replace("{{HEADER_SCENE}}", lang.get("documentation.handbook.header_scene"))
                .replace("{{HEADER_MONSTER}}", lang.get("documentation.handbook.header_monster"))
                // Commands table
                .replace("{{COMMANDS_TABLE}}", cmdsTable.get(langIdx))
                .replace("{{AVATARS_TABLE}}", avatarsTable.get(langIdx))
                .replace("{{ITEMS_TABLE}}", itemsTable.get(langIdx))
                .replace("{{SCENES_TABLE}}", scenesTable.get(langIdx))
                .replace("{{MONSTERS_TABLE}}", monstersTable.get(langIdx))
            );
        }
        return output;
    }
}
