import { world, system, ItemStack } from "@minecraft/server";
import { ActionFormData, ModalFormData, MessageFormData } from "@minecraft/server-ui";
import { DataComponent } from "conduit/data_components";
import { Vec3 } from "conduit/vec3";
import animals_data from "everbloom_au/data";

const bookItemId = "everbloom_au:book";
const hologramEntityIdPrefix = "everbloom_au:hologram.";
const bookHologramEntityId = "everbloom_au:hologram.book_base";
const nearbySearchRange = 10;
const maxRecentEntries = 5;


world.afterEvents.itemUse.subscribe((e) => {
    if(e.itemStack.typeId == bookItemId) {
        let player = e.source;
        removePlayerHologram(player);
        showAnimalSelectionDialog(player);
    }
});
world.afterEvents.entityLoad.subscribe((e) => {
    let entity = e.entity;
    if(entity.typeId.startsWith(hologramEntityIdPrefix)) {
        validateHologramEntity(entity);
    }
});
world.afterEvents.playerSpawn.subscribe((e) => {
    if(e.initialSpawn) {
        let player = e.player;
        if(!player.isValid()) return;
        let dataComponent = getDataComponent(player);
        if(!(dataComponent.given_book ?? false)) {
            if(giveBook(player)) {
                dataComponent.given_book = true;
                dataComponent.save();
            }
        }
    }
});

function mainTick() {
    for(let player of world.getAllPlayers()) {
        tickPlayer(player);
    }
}

system.runInterval(mainTick);

function giveBook(player) {
    let inventory = player.getComponent("minecraft:inventory");
    if(inventory == null) return false;
    let container = inventory.container;
    if(container == null) return false;
    let itemStack = new ItemStack(bookItemId);
    let itemsFailedToGive = container.addItem(itemStack);
    if(itemsFailedToGive == undefined) {
        return true;
    }
    try {
        player.dimension.spawnItem(itemStack, player.location);
        return true;
    } catch(err) {
        return false;
    }
}

function isHoldingBook(player) {
    let slotIndex = player.selectedSlotIndex;
    let inventory = player.getComponent("minecraft:inventory");
    if(inventory == null) return false;
    let container = inventory.container;
    if(container == null) return false;
    let selectedItem = container.getItem(slotIndex);
    return selectedItem?.typeId == bookItemId;
}

function calculateHologramLocation(player) {
    let dimension = player.dimension;
    let rayOrigin = player.getHeadLocation();
    rayOrigin.y -= 0.25;
    let rayDirection = player.getViewDirection();
    rayDirection.y = 0;
    rayDirection = Vec3.normalize(rayDirection);
    let maxDistance = 0.75;
    let farthestLocation = Vec3.add(rayOrigin, Vec3.scale(rayDirection, maxDistance));
    let ray = dimension.getBlockFromRay(rayOrigin, rayDirection, {maxDistance: maxDistance + 3});
    if(ray == null) return farthestLocation;

    let hitLocation = Vec3.add(ray.block.location, ray.faceLocation);
    if(Vec3.len(Vec3.sub(hitLocation, rayOrigin)) >= maxDistance) return farthestLocation;

    return hitLocation;
}

function tickPlayer(player) {
    if(player.scriptData != null && (player.scriptData["everbloom_au:holograms"]?.length ?? 0) > 0) {
        let holdingBook = isHoldingBook(player);
        if(!holdingBook) {
            removePlayerHologram(player);
        } else {
            for(let hologramEntity of player.scriptData["everbloom_au:holograms"]) {
                if(hologramEntity.isValid()) {
                    // tp
                    let newLocation = calculateHologramLocation(player);
                    let tpOptions = null;
                    if(hologramEntity.typeId == bookHologramEntityId) {
                        tpOptions = {
                            "rotation": player.getRotation()
                        };
                    }
                    if(newLocation != null) {
                        hologramEntity.teleport(newLocation, tpOptions);
                    }
                }
            }
        }
    }

    if(isHoldingBook(player)) {
        // player.sendMessage("holding book");
    }
}

function showAnimalSelectionDialog(player) {
    let form = new ActionFormData()
        .title("everbloom_au.book.ui.select_animal")
        .body("everbloom_au.book.ui.suggestions")
    ;

    let returnHereFunction = () => showAnimalSelectionDialog(player);

    var buttonCallbacks = [];

    let maxSuggestionsToShow = 4;
    let maxNearbyToShow = 3;
    let suggestedEntries = [];

    let nearbyEntries = getNearbyEntries(player);
    for(let i = 0; i < nearbyEntries.length && suggestedEntries.length < maxNearbyToShow; i++) {
        let key = nearbyEntries[i];
        let entry = animals_data.book_entries[key];
        if(entry == null) continue;
        suggestedEntries.push(key);
    }
    
    let recentEntries = getRecentEntries(player);
    for(let i = 0; i < recentEntries.length && suggestedEntries.length < maxSuggestionsToShow; i++) {
        let key = recentEntries[i];
        if(suggestedEntries.includes(key)) continue;
        let entry = animals_data.book_entries[key];
        if(entry == null) continue;
        suggestedEntries.push(key);
    }

    if(suggestedEntries.length == 0) return showLongAnimalSelectionDialog(player);
    for(let suggestionKey of suggestedEntries) {
        let entry = animals_data.book_entries[suggestionKey];
        if(entry == null) continue;
        addEntryButton(entry, suggestionKey, form, buttonCallbacks, player, returnHereFunction);
    }

    form.button("everbloom_au.book.ui.button.show_all");
    buttonCallbacks.push(() => {
        showLongAnimalSelectionDialog(player);
    });
    form.button("everbloom_au.book.ui.button.credits");
    buttonCallbacks.push(() => {
        showCreditsDialog(player, returnHereFunction);
    });
    form.show(player).then((response) => {
        if(response.selection != null) {
            buttonCallbacks[response.selection]();
        }
    });
}

function addEntryButton(entry, key, form, buttonCallbacks, player, backFunction) {
    form.button({"rawtext":[{"translate":entry.name_key}]}, entry.icon ?? "textures/misc/missing_texture");
    buttonCallbacks.push(() => {
        viewEntry(player, entry, key, backFunction);
    });
}

function showLongAnimalSelectionDialog(player, query) {
    let form = new ActionFormData()
        .title("everbloom_au.book.ui.select_animal")
    ;
    let results = animals_data.book_entries;
    if(query != null) {
        results = {};
        for(let key in animals_data.book_entries ?? "") {
            let entry = animals_data.book_entries[key];
            if(matchesQuery(query, key, entry)) {
                results[key] = entry;
            }
        }

        let length = Object.keys(results).length;
        if(length == 0) {
            form.body({"rawtext":[{"translate":"everbloom_au.book.ui.search.no_results","with":[query.text]}]});
        } else {
            form.body({"rawtext":[{"translate":"everbloom_au.book.ui.search.results","with":[query.text,""+length]}]});
        }
    }

    var buttonCallbacks = [];
    var returnHereFunction = () => showLongAnimalSelectionDialog(player, query);

    form.button("everbloom_au.book.ui.button.search");
    buttonCallbacks.push(() => {
        showSearchDialog(player);
    });
    for(let key in results ?? "") {
        let entry = results[key];
        addEntryButton(entry, key, form, buttonCallbacks, player, returnHereFunction);
    }
    form.button("everbloom_au.book.ui.button.credits");
    buttonCallbacks.push(() => {
        showCreditsDialog(player, returnHereFunction);
    });
    form.show(player).then((response) => {
        if(response.selection != null) {
            buttonCallbacks[response.selection]();
        }
    });
}


function showSearchDialog(player, callback) {
    const form = new ModalFormData()
        .title("everbloom_au.book.ui.search")
        .textField("everbloom_au.book.ui.search.text_field", "", "")
        .toggle("everbloom_au.book.ui.search.search_descriptions", getDataComponent(player).search_in_descriptions ?? false) //TODO make this persistent
        ;

    form.show(player).then((response) => {
        if(!response.canceled) {
            
            let query = {};
            query.text = response.formValues[0].toLowerCase();
            query.search_in_descriptions = response.formValues[1];
            showLongAnimalSelectionDialog(player, query);

            let dataComponent = getDataComponent(player);
            dataComponent.search_in_descriptions = query.search_in_descriptions;
            dataComponent.save();
        } else if(response.cancelationReason == "UserClosed" && callback != null) {
            callback();
        }
    });
}

function matchesQuery(query, key, entry) {
    // query.text must be lowercase already

    if(entry.searchable_name.includes(query.text)) {
        return true;
    }
    if(query.search_in_descriptions && entry.searchable_description.includes(query.text)) {
        return true;
    }

    return false;
}

function showCreditsDialog(player, backFunction) {
    let form = new MessageFormData()
        .title("everbloom_au.book.ui.credits")
        .body({"rawtext":[{"translate":"everbloom_au.book.credits","with":["\n"]}]})
        .button2("gui.back")
        .button1("gui.close")
    ;
    form.show(player).then((response) => {
        if(response.selection == 1) {
            backFunction();
        }
    });
}

function viewEntry(player, entry, key, backFunction) {
    if(player != null) {
        addRecentEntry(player, key);
    }
    let form = new MessageFormData()
        .title({"rawtext":[{"translate":entry.name_key}]})
        .body({"rawtext":[{"translate":entry.description_key,"with":["\n"]}]})
        .button2("everbloom_au.book.ui.button.show_preview")
        .button1("gui.back")
    ;
    form.show(player).then((response) => {
        if(response.canceled) return;
        if(response.selection == 1) {
            // show preview
            addPlayerHologram(player, bookHologramEntityId);
            addPlayerHologram(player, entry.hologram_id);
        } else {
            // back
            if(backFunction != null) {
                backFunction();
            }
        }
    });
}

function getDataComponent(player) {
    return DataComponent.for(player, "everbloom_au:book_memory");
}

function getRecentEntries(player) {
    return getDataComponent(player).recent_entries ??= [];
}

function getNearbyEntries(player) {
    let entryKeys = [];
    let nearbyEntities = player.dimension.getEntities({"location":player.location,"maxDistance": nearbySearchRange});
    for(let entity of nearbyEntities) {
        let entryKey = animals_data.entity_to_entry_key[entity.typeId];
        if(entryKey != null && !entryKeys.includes(entryKey)) {
            entryKeys.push(entryKey);
        }
    }
    return entryKeys;
}

function addRecentEntry(player, key) {
    let dataComponent = getDataComponent(player);
    dataComponent.recent_entries ??= [];
    dataComponent.recent_entries = dataComponent.recent_entries.filter(e => e != key);
    dataComponent.recent_entries.splice(0, 0, key);
    if(dataComponent.recent_entries.length > maxRecentEntries) {
        dataComponent.recent_entries.splice(maxRecentEntries, dataComponent.recent_entries.length-maxRecentEntries);
    }
    dataComponent.save();
}

function validateHologramEntity(entity) {
    let ownerPlayer = entity.scriptData != null ? entity.scriptData["everbloom_au:hologram_owner"] : null;
    if(ownerPlayer == null || !ownerPlayer.isValid()) entity.remove();
}

function addPlayerHologram(player, id) {
    let hologramEntity = player.dimension.spawnEntity(id, player.location);
    let spawnRotation = player.getRotation();
    hologramEntity.setRotation(spawnRotation);
    hologramEntity.scriptData ??= {};
    hologramEntity.scriptData["everbloom_au:hologram_owner"] = player;
    player.scriptData ??= {};
    player.scriptData["everbloom_au:holograms"] ??= [];
    player.scriptData["everbloom_au:holograms"].push(hologramEntity);
}

function removePlayerHologram(player) {
    if(player.scriptData != null) {
        for(let hologramEntity of player.scriptData["everbloom_au:holograms"] ?? "") {
            if(hologramEntity.isValid()) {
                hologramEntity.remove();
            }
        }
        player.scriptData["everbloom_au:holograms"] = [];
    }
}

system.run(()=>{
    let dimensions = [];
    for(let player of world.getAllPlayers()) {
        let dimension = player.dimension;
        if(!dimensions.includes(dimension)) {
            dimensions.push(dimension);
        }
    }
    for(let dimension of dimensions) {
        let entities = dimension.getEntities();
        for(let entity of entities) {
            if(entity.typeId.startsWith(hologramEntityIdPrefix)) {
                validateHologramEntity(entity);
            }
        }
    }
});

const WORLD_UP = {x: 0, y: 1, z : 0};
class LocalSpace {
    origin;
    forward;
    left;
    up;

    get yaw() {
        let yaw = -Math.atan2(this.forward.x, this.forward.z) / Math.PI * 180;
        if(isNaN(yaw)) yaw = 0;
        return yaw;
    }

    get pitch() {
        let pitch = -Math.atan2(this.forward.y, 1) / Math.PI * 180;
        return pitch;
    }

    constructor(origin, forward) {
        this.origin = origin;
        forward = Vec3.normalize(forward);
        this.forward = forward;

        this.left = Vec3.normalize(Vec3.cross(WORLD_UP, forward));
        if(isNaN(this.left.x)) {
            this.left = Vec3.vec(1, 0, 0);
        }
        this.up = Vec3.normalize(Vec3.cross(forward, this.left));
    }

    pos(vec) {
        return Vec3.add(
            this.origin,
            this.off(vec)
        );
    }

    off(vec) {
        return Vec3.add(
            Vec3.scale(this.forward, vec.z),
            Vec3.add(
                Vec3.scale(this.up, vec.y),
                Vec3.scale(this.left, vec.x)
            )
        );
    }

    drawGizmos() {
        Gizmos.drawLocalSpace(this, 5);
    }
}