import { world, EntityQueryOptions, MinecraftDimensionTypes, BlockLocation, Location, Vector, MolangVariableMap, MinecraftEffectTypes, MinecraftBlockTypes } from 'mojang-minecraft'
import { Timeout } from './fluffylib.js'

// classes

class ParchmentEffects {
    static explode = '§6Explode'
    static damage = '§4+8 Damage'
    static freeze = '§bFreeze'
    static regeneration = '§dRegeneration'
    static fire = '§cFire'
    static lightning = '§3Lightning'
    static wind = '§fWind'
    static strength = '§cStrength'
    static haste = '§eHaste'
    static slowness = '§7Slowness'
}

class ItemData {
    constructor(amount = 1, data = 0) {
        this.amount = amount
        this.data = data
    }
}

class Parchment {
    constructor(items, effect, canStack = false) {
        this.items = items
        this.effect = effect
        this.canStack = canStack
    }
}

class SpellForm {
    constructor(baseLevel, baseCooldown, callback) {
        this.baseLevel = baseLevel
        this.baseCooldown = baseCooldown
        this.callback = callback
    }
}

// constants

const overworld = world.getDimension(MinecraftDimensionTypes.overworld)

const flowers = [
    'minecraft:crimson_roots',
    'minecraft:crimson_fungus',
    'minecraft:weeping_vines',
    'minecraft:twisting_vines',
    'minecraft:warped_fungus',
    'minecraft:warped_roots',
    'minecraft:nether_sprouts'
]

const itemQuery = new EntityQueryOptions()
itemQuery.type = 'minecraft:item'

const aoeCircleQuery = new EntityQueryOptions()
aoeCircleQuery.type = 'magicalspells:aoe_circle'

const parchments = {
    'magicalspells:parchment_explode': new Parchment(
        {
            'minecraft:tnt': new ItemData(8),
            'minecraft:magma_cream': new ItemData(10),
            'minecraft:fire_charge': new ItemData(4),
            'minecraft:blaze_rod': new ItemData(8)
        },
        ParchmentEffects.explode
    ),
    'magicalspells:parchment_damage': new Parchment(
        {
            'minecraft:redstone': new ItemData(32),
            'minecraft:fermented_spider_eye': new ItemData(16),
            'minecraft:diamond_sword': new ItemData()
        },
        ParchmentEffects.damage,
        true
    ),
    'magicalspells:parchment_freeze': new Parchment(
        {
            'minecraft:water_bucket': new ItemData(),
            'minecraft:snowball': new ItemData(16)
        },
        ParchmentEffects.freeze
    ),
    'magicalspells:parchment_regeneration': new Parchment(
        {
            'minecraft:redstone': new ItemData(32),
            'minecraft:ghast_tear': new ItemData(8),
            'minecraft:glistering_melon_slice': new ItemData(16)
        },
        ParchmentEffects.regeneration
    ),
    'magicalspells:parchment_fire': new Parchment(
        {
            'minecraft:fire_charge': new ItemData(4),
            'minecraft:blaze_powder': new ItemData(4)
        },
        ParchmentEffects.fire
    ),
    'magicalspells:parchment_lightning': new Parchment(
        {
            'minecraft:fire_charge': new ItemData(4),
            'minecraft:lightning_rod': new ItemData(6),
            'minecraft:blaze_rod': new ItemData(4),
            'minecraft:glowstone_dust': new ItemData(16)
        },
        ParchmentEffects.lightning
    ),
    'magicalspells:parchment_wind': new Parchment(
        {
            'minecraft:phantom_membrane': new ItemData(16),
            'minecraft:feather': new ItemData(16)
        },
        ParchmentEffects.wind
    ),
    'magicalspells:parchment_strength': new Parchment(
        {
            'minecraft:blaze_powder': new ItemData(16),
            'minecraft:blaze_rod': new ItemData(8),
            'minecraft:redstone': new ItemData(32)
        },
        ParchmentEffects.strength
    ),
    'magicalspells:parchment_haste': new Parchment(
        {
            'minecraft:blaze_powder': new ItemData(16),
            'minecraft:sugar': new ItemData(16),
            'minecraft:glowstone_dust': new ItemData(16)
        },
        ParchmentEffects.haste
    ),
    'magicalspells:parchment_slowness': new Parchment(
        {
            'minecraft:fermented_spider_eye': new ItemData(16),
            'minecraft:sugar': new ItemData(8)
        },
        ParchmentEffects.slowness
    )
}

const spellForms = {
    'magicalspells:spell_projectile': new SpellForm(0, 0, (player, lore) => {
        const viewVector = Vector.multiply(player.viewVector, 2.5)
        const projectile = player.dimension.spawnEntity('magicalspells:projectile', new Location(player.location.x + viewVector.x, player.location.y + 1.5 + viewVector.y, player.location.z + viewVector.z))
        projectile.setVelocity(viewVector)
        projectile.nameTag = JSON.stringify(lore)
    }),
    'magicalspells:spell_multishot_projectile': new SpellForm(3, 20, (player, lore) => {
        const viewVector = Vector.multiply(player.viewVector, 2.5)
        for (let i = -1; i < 2; i++) {
            const projectile = player.dimension.spawnEntity('magicalspells:projectile', new Location(player.location.x + viewVector.x + i, player.location.y + 1.5 + viewVector.y + i, player.location.z + viewVector.z + i))
            projectile.setVelocity(viewVector)
            projectile.nameTag = JSON.stringify(lore)
        }
    }),
    'magicalspells:spell_aoe': new SpellForm(5, 40, (player, lore) => {
        for (const effect of lore) {
            switch (effect) {
                case ParchmentEffects.explode:
                    try {
                        player.runCommand('execute @e[r=20,c=20,type=!player,type=!item,family=!magicalspells] ~~~ summon magicalspells:explosion')
                    } catch { }
                    break
                case ParchmentEffects.damage:
                    try {
                        player.runCommand('damage @e[r=20,type=!player,type=!item,family=!magicalspells] 8')
                    } catch { }
                    break
                case ParchmentEffects.freeze:
                    try {
                        player.runCommand('execute @e[r=20,type=!player,type=!item,family=!magicalspells] ~~~ fill ~1~1~1~-1~-1~-1 packed_ice 0 keep')
                    } catch { }
                    break
                case ParchmentEffects.regeneration:
                    player.dimension.spawnEntity('magicalspells:aoe_circle', player.location).nameTag = '{"id":"regeneration","amplifier":2}'
                    player.dimension.spawnParticle('magicalspells:regeneration_circle', player.location, new MolangVariableMap())
                    break
                case ParchmentEffects.fire:
                    {
                        const query = new EntityQueryOptions()
                        query.excludeFamilies = ['magicalspells']
                        query.excludeTypes = ['player', 'item']
                        query.maxDistance = 20
                        query.location = player.location

                        for (const entity of player.dimension.getEntities(query)) {
                            const block = entity.dimension.getBlock(new BlockLocation(Math.floor(entity.location.x), Math.floor(entity.location.y), Math.floor(entity.location.z)))
                            const perm = block.permutation.clone()
                            const type = block.type

                            block.setType(MinecraftBlockTypes.fire)
                            new Timeout(2, (perm, type, block) => {
                                block.setType(type)
                                block.setPermutation(perm)
                            }, perm, type, block)
                        }
                        break
                    }
                case ParchmentEffects.lightning:
                    try {
                        player.runCommand('execute @e[r=20,c=20,type=!player,type=!item,family=!magicalspells] ~~~ summon lightning_bolt')
                    } catch { }
                    break
                case ParchmentEffects.wind:
                    {
                        const query = new EntityQueryOptions()
                        query.excludeFamilies = ['magicalspells']
                        query.excludeTypes = ['player', 'item']
                        query.maxDistance = 20
                        query.location = player.location

                        for (const entity of player.dimension.getEntities(query)) {
                            entity.teleportFacing(entity.location, entity.dimension, player.location)
                            entity.setVelocity(new Vector(entity.viewVector.x * -6, 2.5, entity.viewVector.z * -6))
                        }
                        break
                    }
                case ParchmentEffects.strength:
                    player.dimension.spawnEntity('magicalspells:aoe_circle', player.location).nameTag = '{"id":"strength","amplifier":2}'
                    player.dimension.spawnParticle('magicalspells:strength_circle', player.location, new MolangVariableMap())
                    break
                case ParchmentEffects.haste:
                    player.dimension.spawnEntity('magicalspells:aoe_circle', player.location).nameTag = '{"id":"haste","amplifier":2}'
                    player.dimension.spawnParticle('magicalspells:haste_circle', player.location, new MolangVariableMap())
                    break
                case ParchmentEffects.slowness:
                    player.dimension.spawnEntity('magicalspells:aoe_circle', player.location).nameTag = '{"id":"slowness","amplifier":2}'
                    player.dimension.spawnParticle('magicalspells:slowness_circle', player.location, new MolangVariableMap())
                    break
            }
        }
    }),
    'magicalspells:spell_self': new SpellForm(0, 0, (player, lore) => {
        for (const effect of lore) {
            switch (effect) {
                case ParchmentEffects.explode:
                    player.addEffect(MinecraftEffectTypes.resistance, 20, 255, false)
                    player.dimension.spawnEntity('magicalspells:explosion', player.location)
                    break
                case ParchmentEffects.damage:
                    player.runCommand('damage @s 8')
                    break
                case ParchmentEffects.freeze:
                    try {
                        player.runCommand('fill ~1~1~1~-1~-1~-1 packed_ice 0 keep')
                    } catch { }
                    break
                case ParchmentEffects.regeneration:
                    player.addEffect(MinecraftEffectTypes.regeneration, 200, 2)
                    break
                case ParchmentEffects.fire:
                    {
                        const block = player.dimension.getBlock(new BlockLocation(Math.floor(player.location.x), Math.floor(player.location.y), Math.floor(player.location.z)))
                        const perm = block.permutation.clone()
                        const type = block.type

                        block.setType(MinecraftBlockTypes.fire)
                        new Timeout(2, (perm, type, block) => {
                            block.setType(type)
                            block.setPermutation(perm)
                        }, perm, type, block)
                        break
                    }
                case ParchmentEffects.lightning:
                    player.dimension.spawnEntity('lightning_bolt', player.location)
                    break
                case ParchmentEffects.wind:
                    player.addEffect(MinecraftEffectTypes.slowFalling, 60)
                    try {
                        player.runCommand(`tp @s ^^^${Math.random() * 35 + 15}`)
                    } catch { }
                    break
                case ParchmentEffects.strength:
                    player.addEffect(MinecraftEffectTypes.strength, 200, 2)
                    break
                case ParchmentEffects.haste:
                    player.addEffect(MinecraftEffectTypes.haste, 200, 2)
                    break
                case ParchmentEffects.slowness:
                    player.addEffect(MinecraftEffectTypes.slowness, 200, 2)
                    break
            }
        }
    })
}

// events

world.events.dataDrivenEntityTriggerEvent.subscribe(e => {
    if (e.entity.id == 'magicalspells:projectile') { // projectile effects that dont need to hit an entity
        for (const effect of JSON.parse(e.entity.nameTag)) {
            switch (effect) {
                case ParchmentEffects.explode:
                    e.entity.dimension.spawnEntity('magicalspells:explosion', e.entity.location)
                    break
                case ParchmentEffects.lightning:
                    e.entity.dimension.spawnEntity('lightning_bolt', e.entity.location)
                    break
            }
        }
    }
})

world.events.entityHurt.subscribe(e => {
    if (e.projectile && e.projectile.id == 'magicalspells:projectile') { // projectile effects that do need to hit an entity
        for (const effect of JSON.parse(e.projectile.nameTag)) {
            switch (effect) {
                case ParchmentEffects.damage:
                    new Timeout(1, (entity) => {
                        const health = entity.getComponent('health')
                        if (health) {
                            health.setCurrent(health.current - 8)
                        }
                    }, e.hurtEntity)
                    break
                case ParchmentEffects.freeze:
                    try {
                        e.hurtEntity.runCommand('fill ~1~1~1~-1~-1~-1 packed_ice 0 keep')
                    } catch { }
                    break
                case ParchmentEffects.regeneration:
                    e.hurtEntity.addEffect(MinecraftEffectTypes.regeneration, 200, 2)
                    break
                case ParchmentEffects.fire:
                    {
                        const block = e.hurtEntity.dimension.getBlock(new BlockLocation(Math.floor(e.hurtEntity.location.x), Math.floor(e.hurtEntity.location.y), Math.floor(e.hurtEntity.location.z)))
                        const perm = block.permutation.clone()
                        const type = block.type

                        block.setType(MinecraftBlockTypes.fire)
                        new Timeout(2, (perm, type, block) => {
                            block.setType(type)
                            block.setPermutation(perm)
                        }, perm, type, block)
                        break
                    }
                case ParchmentEffects.wind:
                    e.hurtEntity.setVelocity(new Vector(e.projectile.velocity.x * 2, 2.5, e.projectile.velocity.z * 2))
                    break
                case ParchmentEffects.regeneration:
                    e.hurtEntity.addEffect(MinecraftEffectTypes.strength, 200, 2)
                    break
                case ParchmentEffects.haste:
                    e.hurtEntity.addEffect(MinecraftEffectTypes.haste, 200, 2)
                    break
                case ParchmentEffects.slowness:
                    e.hurtEntity.addEffect(MinecraftEffectTypes.slowness, 200, 2)
                    break
            }
        }
    }
})

world.events.itemUse.subscribe(e => {
    if (e.item.id == 'magicalspells:magic_book') {
        e.source.runCommand(`tellraw @s {"rawtext": [{"text": "§l[Magical Spells 2]\n§r§aXP: ${world.scoreboard.getObjective('msxp').getScore(e.source.scoreboard)}/200\n§2Level: ${world.scoreboard.getObjective('mslevel').getScore(e.source.scoreboard)}"}]}`)
    }
    else {
        const spellForm = spellForms[e.item.id]

        if (!spellForm) {
            return
        }

        const lore = e.item.getLore()

        if (lore.length == 0) {
            return
        }
        else if (e.source.getItemCooldown(JSON.stringify(lore)) > 0) {
            e.source.runCommand(`tellraw @s {"rawtext": [{"text": "§cThis spell is on cooldown. ${(e.source.getItemCooldown(JSON.stringify(lore)) / 20).toFixed(2)}s remaining."}]}`)
            return
        }

        const level = world.scoreboard.getObjective('mslevel').getScore(e.source.scoreboard)
        const levelRequired = Math.floor((lore.length - 1) * 1.5) + spellForm.baseLevel

        if (level < levelRequired) {
            e.source.runCommand(`tellraw @s {"rawtext": [{"text": "§cThis spell requires level ${levelRequired} to use. You are currently level ${level}."}]}`)
            return
        }

        const cooldown = lore.length * 12 + spellForm.baseCooldown

        e.source.startItemCooldown(JSON.stringify(lore), cooldown)
        e.source.runCommand('playsound mob.evocation_illager.cast_spell @s')
        e.source.runCommand('scoreboard players add @s msxp 1')
        if (world.scoreboard.getObjective('msxp').getScore(e.source.scoreboard) >= 200) {
            e.source.runCommand('scoreboard players set @s msxp 0')
            e.source.runCommand('scoreboard players add @s mslevel 1')
            e.source.playSound('random.levelup')
            e.source.runCommand(`tellraw @s {"rawtext": [{"text": "§aYou leveled up to level ${level + 1}!"}]}`)
        }

        spellForm.callback(e.source, lore)
    }
})

world.events.tick.subscribe(e => {
    // aoe effects
    for (const entity of overworld.getEntities(aoeCircleQuery)) {
        if (e.currentTick % 20 == 0) {
            const effect = JSON.parse(entity.nameTag)
            try {
                entity.runCommand(`effect @e[r=7] ${effect.id} 2 ${effect.amplifier}`)
            } catch { }
        }
    }

    // test for the parchment ritual
    itemLoop: for (const entity of overworld.getEntities(itemQuery)) {
        const parchment = parchments[entity.getComponent('item').itemStack.id]
        if (!parchment) {
            continue
        }

        const x = Math.floor(entity.location.x)
        const y = Math.floor(entity.location.y)
        const z = Math.floor(entity.location.z)

        if (
            overworld.getBlock(new BlockLocation(x, y, z)).id != 'minecraft:water' ||
            !flowers.includes(overworld.getBlock(new BlockLocation(x + 1, y + 1, z)).id) ||
            !flowers.includes(overworld.getBlock(new BlockLocation(x - 1, y + 1, z)).id) ||
            !flowers.includes(overworld.getBlock(new BlockLocation(x, y + 1, z + 1)).id) ||
            !flowers.includes(overworld.getBlock(new BlockLocation(x, y + 1, z - 1)).id)
        ) {
            continue
        }

        const query = new EntityQueryOptions()
        query.type = 'minecraft:item'
        query.maxDistance = 1
        query.location = entity.location

        const processed = []
        let spell

        for (const itemEntity of overworld.getEntities(query)) {
            const item = itemEntity.getComponent('item').itemStack
            const requestedItemData = parchment.items[item.id]

            if (item.id.startsWith('magicalspells:spell')) {
                if (spell) {
                    continue itemLoop
                }

                spell = [itemEntity, item]
            }
            else if (requestedItemData && item.amount == requestedItemData.amount && item.data == requestedItemData.data) {
                processed.push(itemEntity)
            }
        }

        if (!spell || processed.length != Object.keys(parchment.items).length || (!parchment.canStack && spell[1].getLore().includes(parchment.effect))) {
            continue
        }

        for (const itemEntity of processed) {
            itemEntity.kill()
        }

        // spawn new item

        spell[1].setLore([...spell[1].getLore(), parchment.effect])

        overworld.spawnParticle('magicalspells:create_spell', new Location(x + 0.5, y + 1, z + 0.5), new MolangVariableMap())

        try {
            overworld.runCommand(`playsound respawn_anchor.set_spawn @a ${spell[0].location.x} ${spell[0].location.y} ${spell[0].location.z}`)
        } catch { }
        try {
            overworld.runCommand(`playsound respawn_anchor.ambient @a ${spell[0].location.x} ${spell[0].location.y} ${spell[0].location.z}`)
        } catch { }

        new Timeout(120, (item, location) => {
            overworld.spawnItem(item, location)
            try {
                overworld.runCommand(`playsound item.trident.return @a ${location.x} ${location.y} ${location.z}`)
            } catch { }
        }, spell[1], spell[0].location)

        spell[0].kill()
        return
    }
})