import { MolangVariableMap, Player, system, world } from "@minecraft/server";
import { sendDebugMessage, vectorAbove } from "./JMmcUtils";
import { ActionFormData } from "@minecraft/server-ui";

const highestDamage = (player) => {
    const property = "lb_ga:player_damage_high_score"
    if (!player.getDynamicPropertyIds().includes(property)) set(0)

    function get() {
        return JSON.parse(player.getDynamicProperty(property))
    }

    function set(score) {
        player.setDynamicProperty(property,JSON.stringify(Math.round(score * 100)/100))
    }

    return {
        get: function() {return get()},
        set: function(score) {return set(score)},
        evaluate: function(value) {
            if (value > get()) {
                set(value)
                return true
            }
            return false
        }
    }
}

const damageTaken = (entity) => {
    const property = "lb_ga:entity_total_damage_taken"
    if (!entity.getDynamicPropertyIds().includes(property)) set(0)

    function get() {
        return JSON.parse(entity.getDynamicProperty(property))
    }

    function set(value) {
        entity.setDynamicProperty(property,JSON.stringify(Math.round(value * 100)/100))
    }
    
    return {
        get: function() {return get()},
        add: function(score) {
            return set(get() + score)
        }
    }
}

const playerDingSetting = (player) => {
    const property = "lb_ga:player_ding_setting"
    if (!player.getDynamicPropertyIds().includes(property)) set(false)

    function get() {
        return JSON.parse(player.getDynamicProperty(property))
    }

    function set(value) {
        player.setDynamicProperty(property,JSON.stringify(value))
    }

    return {
        isEnabled: function() {
            return get()
        },
        enable: function() {
            return set(true)
        },
        disable: function() {
            return set(false)
        }
    }
}

const globalDamageParticles = (player) => {
    const property = "lb_ga:global_damage_particles"
    if (!player.getDynamicPropertyIds().includes(property)) set(false)

    function get() {
        return JSON.parse(player.getDynamicProperty(property))
    }

    function set(value) {
        player.setDynamicProperty(property,JSON.stringify(value))
    }

    return {
        isEnabled: function() {
            return get()
        },
        enable: function() {
            return set(true)
        },
        disable: function() {
            return set(false)
        }
    }
}

function getSpawnLine(playerLocation, entityLocation) {
    const dx = entityLocation.x - playerLocation.x
    const dz = entityLocation.z - playerLocation.z

    let perpX = -dz
    let perpZ = dx

    const length = Math.hypot(perpX, perpZ)
    if (length === 0) {
        perpX = 1
        perpZ = 1
    } else {
        perpX /= length
        perpZ /= length
    }

    return {
        x:perpX,
        z:perpZ
    }
}

function getSpawnPoints(line, center, chars) {
    const points = []
    const narrowChars = ['.', ',']
    let currentPos = -(Math.floor(chars.length/2)/4)
  
    for (let i = 0; i < chars.length + 1; i++) {
        // standard spacing for all chars
        currentPos += 0.25
        // if narrow, this character will have its spacing adjusted
        if (chars[i] && narrowChars.includes(chars[i])) currentPos -= 0.1

        const newPoint = {
            x: center.x + currentPos * line.x,
            y: center.y,
            z: center.z + currentPos * line.z
        }

        points.push(newPoint);
        // if narrow, manipulate spacing so the next character is spaced properly
        if (chars[i] && narrowChars.includes(chars[i])) currentPos -= 0.1
    }
    return points
}

function damageParticles(dimension, playerLocation, entityLocation, damage) {
    if (damage > 2000000) damage = 2000000
    if (entityLocation.y < dimension.heightRange.min || entityLocation.y > dimension.heightRange.max) {
        sendDebugMessage(`§ccannot spawn damage particles outside world boundaries!  ${JSON.stringify(entityLocation)}`)
        return
    }
    function particle(index,location) {
        let id = chars[index]
        if (id === ".") id = 10
        if (index < points.length - 1) {
            const moVar = new MolangVariableMap()
            moVar.setFloat("number", parseFloat(id))
            dimension.spawnParticle(`lb_ta:number`,location, moVar)
        } else {
            dimension.spawnParticle("lb_ta:heart",location)
        }
    }
    const chars = (damage/2).toString().split("")
    const line = getSpawnLine(playerLocation,entityLocation)
    const points = getSpawnPoints(line,entityLocation,chars)
    for (let i = 0; i < points.length; i++) {
        const point = points[i];
        particle(i,point)
    }
}

world.afterEvents.entityHurt.subscribe(event => {
    const hurtEntity = event.hurtEntity
    const entity = event.damageSource.damagingEntity
    const isPlayer = entity && entity instanceof Player
    if (isPlayer) {
        highestDamage(entity).evaluate(event.damage)
        if (event.damageSource.cause === "projectile" && (hurtEntity.typeId === "lb_ga:target_golem" || playerDingSetting(entity).isEnabled())) entity.playSound("lb_ga:ding")
    }
    if (hurtEntity.typeId === "lb_ga:target_golem" || (isPlayer && globalDamageParticles(entity)?.isEnabled())) {
        if (hurtEntity.typeId === "lb_ga:target_golem" && isPlayer) {
            const healthComponent = hurtEntity.getComponent("health")
            if (healthComponent.currentValue > 0) {
                healthComponent.resetToMaxValue()
            }
            damageTaken(event.hurtEntity).add(event.damage)
        }
        if (entity && entity instanceof Player) {
            damageParticles(entity.dimension, entity.location, vectorAbove(hurtEntity.location,2), Math.round(event.damage * 100)/100)
        } else {
            const closestEntity = hurtEntity.dimension.getEntities({type:"minecraft:player",closest:1,location:hurtEntity.location})[0]
            damageParticles(hurtEntity.dimension,closestEntity.location,vectorAbove(hurtEntity.location,2),Math.round(event.damage * 100)/100)
            return
        }
    }
})

function damageReportForm(entity, player) {
    function damageToHearts(damage) {
        return (Math.round((damage / 2) * 100)/100)
    }
    const form = new ActionFormData()
    form.title({translate:"form.damageReport.title"})
    const entityDamage = damageTaken(entity).get()
    const highScore = highestDamage(player).get()
    const message = {
        rawtext: [
            {text:"\n"},
            {
                translate:"form.damageReport.highScore",
                with: [`§l§a${highScore}§r`, JSON.stringify(damageToHearts(highScore))]
            },
            {text:"\n\n"},
            {
                translate:"form.damageReport.entityDamage",
                with: [`§l§9${entityDamage}§r`, JSON.stringify(damageToHearts(entityDamage))]
            },
            {text:"\n\n"}
        ]
    }
    form.body(message)
    form.button({translate:"text.button.ok"})
    form.button({translate:`form.damageReport.ding.${playerDingSetting(player).isEnabled() ? "disable" : "enable"}`})
    form.button({translate:`form.damageReport.particles.${globalDamageParticles(player).isEnabled() ? "disable" : "enable"}`})
    return form
}

world.beforeEvents.playerInteractWithEntity.subscribe(event => {
    if (event.target.typeId !== "lb_ga:target_golem") return
    if (event.player.getComponent("equippable")?.getEquipment("Mainhand")?.typeId === "minecraft:lead") return
    system.run(() => {
        damageReportForm(event.target, event.player).show(event.player).then((response) => {
            if (response.canceled) return
            if (response.selection === 1) {
                const ding = playerDingSetting(event.player)
                if (ding.isEnabled()) {
                    ding.disable()
                } else ding.enable()
            }
            if (response.selection === 2) {
                const particleSettings = globalDamageParticles(event.player)
                if (particleSettings.isEnabled()) {
                    particleSettings.disable()
                } else particleSettings.enable()
            }
        })
    })
})