package org.allaymc.api.entity.component;

import org.allaymc.api.entity.Entity;
import org.allaymc.api.entity.damage.DamageContainer;
import org.allaymc.api.entity.effect.EffectInstance;
import org.allaymc.api.entity.effect.EffectType;
import org.allaymc.api.item.ItemStack;
import org.jetbrains.annotations.UnmodifiableView;

import java.util.List;
import java.util.Map;

/**
 * Component for living entities with health, damage, effects, air supply, fire, freeze, and drop behavior.
 *
 * @author daoge_cmd
 */
public interface EntityLivingComponent extends EntityComponent {

    int DEFAULT_MAX_AIR_SUPPLY = 300;
    int DEFAULT_MAX_HEALTH = 20;
    int MAX_FREEZE_TICKS = 140;

    /**
     * Attack this entity with the given damage container.
     *
     * @param damage         the damage container
     * @param ignoreCoolDown {@code true} to ignore the attack cool down, {@code false} otherwise
     * @return {@code true} if the entity was damaged, {@code false} otherwise.
     */
    boolean attack(DamageContainer damage, boolean ignoreCoolDown);

    /**
     * @see #attack(DamageContainer, boolean)
     */
    default boolean attack(DamageContainer damage) {
        return attack(damage, false);
    }

    /**
     * Attack this entity with the given damage.
     *
     * @param damage the damage
     * @return {@code true} if the entity was damaged, {@code false} otherwise.
     */
    default boolean attack(float damage) {
        return attack(DamageContainer.simpleAttack(damage));
    }

    /**
     * Attack this entity with the given attacker and damage.
     *
     * @param attacker the attacker
     * @param damage   the damage
     * @return {@code true} if the entity was damaged, {@code false} otherwise.
     */
    default boolean attack(Entity attacker, float damage) {
        return attack(DamageContainer.entityAttack(attacker, damage));
    }

    /**
     * Check if this entity can be attacked with the given damage container. Notes that this method
     * will not check if the entity is alive or if is in damage cool down.
     *
     * @param damage the damage container
     * @return {@code true} if the entity can be attacked, {@code false} otherwise.
     */
    boolean canBeAttacked(DamageContainer damage);

    /**
     * Get the last damage that was dealt to this entity.
     *
     * @return the last damage container, or {@code null} if there is no last damage
     */
    DamageContainer getLastDamage();

    /**
     * Get the time when the last damage was dealt to this entity.
     *
     * @return the time in entity ticks, or {@code -1} if no damage has been received
     */
    long getLastDamageTime();

    /**
     * Check if the entity has fall damage.
     *
     * @return {@code true} if the entity has fall damage, {@code false} otherwise.
     */
    boolean hasFallDamage();

    /**
     * Check if the entity has fire damage.
     *
     * @return {@code true} if the entity has fire damage, {@code false} otherwise.
     */
    boolean hasFireDamage();

    /**
     * Check if the entity has drowning damage.
     *
     * @return {@code true} if the entity has drowning damage, {@code false} otherwise.
     */
    boolean hasDrowningDamage();

    /**
     * Check if the entity has void damage. Void damage will be applied to the entity every second
     * when its y coordinate is less than {@code minHeight - 18}.
     *
     * @return {@code true} if the entity has void damage, {@code false} otherwise.
     */
    boolean hasVoidDamage();

    /**
     * Check if this entity can against fire damage even if it does not have a fire resistance effect.
     * For example, netherite items. When return {@code true}, the entity will not be able to be set on
     * fire and method {@link #setOnFireTicks(int)} will always return {@code false}.
     *
     * @return {@code true} if the entity is fireproof, {@code false} otherwise.
     */
    boolean isFireproof();

    /**
     * Get the on fire ticks of the entity.
     *
     * @return the on fire ticks, or zero if the entity is not on fire or is fireproof.
     * @see #isFireproof()
     */
    int getOnFireTicks();

    /**
     * Set the on fire ticks of the entity. This method will return {@code false} if the entity is fireproof.
     *
     * @param onFireTicks the on fire ticks
     * @return {@code true} if the on fire ticks were set, {@code false} if the entity is fireproof.
     * @see #isFireproof()
     */
    boolean setOnFireTicks(int onFireTicks);

    /**
     * Extinguishes the entity by resetting its "on fire" ticks to {@code 0}.
     */
    default void extinguish() {
        setOnFireTicks(0);
    }

    /**
     * Check if the entity is on fire.
     *
     * @return {@code true} if the entity is on fire, {@code false} otherwise.
     */
    default boolean isOnFire() {
        return getOnFireTicks() > 0;
    }

    /**
     * Get the current freeze ticks of the entity.
     * Freeze ticks increase while the entity is inside powder snow, up to {@link #MAX_FREEZE_TICKS}.
     *
     * @return the freeze ticks, {@code 0} if not freezing.
     */
    int getFreezeTicks();

    /**
     * Set the freeze ticks of the entity.
     *
     * @param freezeTicks the freeze ticks to set, clamped to [0, {@link #MAX_FREEZE_TICKS}]
     */
    void setFreezeTicks(int freezeTicks);

    /**
     * Check whether the entity was inside powder snow during the last block-collision pass.
     * This flag is set by the powder-snow block component and cleared at the start of each tick.
     *
     * @return {@code true} if the entity was in powder snow last tick
     */
    boolean isInPowderSnow();

    /**
     * Mark that the entity is (or is not) currently inside powder snow.
     * Called by the powder-snow block component during block-collision handling.
     *
     * @param inPowderSnow {@code true} if the entity is inside powder snow
     */
    void setInPowderSnow(boolean inPowderSnow);

    /**
     * Check if the entity is fully frozen (freeze ticks have reached {@link #MAX_FREEZE_TICKS}).
     *
     * @return {@code true} if the entity is fully frozen
     */
    default boolean isFrozen() {
        return getFreezeTicks() >= MAX_FREEZE_TICKS;
    }

    /**
     * Gets the current number of air supply ticks remaining for this entity.
     * This value determines how long the entity can stay underwater before taking drowning damage.
     *
     * @return The current air supply ticks
     */
    int getAirSupplyTicks();

    /**
     * Sets the current number of air supply ticks for this entity.
     * Setting this to 0 will cause the entity to start taking drowning damage if underwater.
     *
     * @param ticks The number of air supply ticks to set
     */
    void setAirSupplyTicks(int ticks);

    /**
     * Gets the maximum number of air supply ticks this entity can have.
     * This represents the total time the entity can stay underwater before running out of air.
     *
     * @return The maximum air supply ticks
     */
    int getAirSupplyMaxTicks();

    /**
     * Sets the maximum number of air supply ticks this entity can have.
     * This affects how long the entity can stay underwater before needing to surface for air.
     *
     * @param ticks The maximum number of air supply ticks to set
     */
    void setAirSupplyMaxTicks(int ticks);

    /**
     * Checks if the entity can breathe.
     *
     * @return {@code true} if the entity can breathe, {@code false} otherwise.
     */
    boolean canBreathe();

    /**
     * Gets an unmodifiable view of the current effects applied to the entity.
     * <p>
     * Each entry in the returned map represents an active effect and its corresponding instance.
     *
     * @return an unmodifiable map containing the effects applied to the entity, where the keys are the effect types
     * and the values are the corresponding effect instances
     */
    @UnmodifiableView
    Map<EffectType, EffectInstance> getEffects();

    /**
     * Check if the entity has the specified effect.
     *
     * @param effectType the effect type to check
     * @return {@code true} if the entity has the specified effect, otherwise {@code false}.
     */
    boolean hasEffect(EffectType effectType);

    /**
     * Get the effect level of the specified effect.
     *
     * @param effectType the effect type to get
     * @return the effect level of the specified effect
     */
    int getEffectLevel(EffectType effectType);

    /**
     * Add the specified effect to the entity.
     *
     * @param effectInstance the effect instance to add
     * @return {@code true} if the effect is added successfully, otherwise {@code false}.
     */
    boolean addEffect(EffectInstance effectInstance);

    /**
     * Remove the specified effect from the entity.
     *
     * @param effectType the effect type to remove
     */
    void removeEffect(EffectType effectType);

    /**
     * Remove all effects from the entity.
     */
    void removeAllEffects();

    /**
     * Check if the specific effect can apply on the entity.
     *
     * @param effectType the specific effect
     * @return {@code true} if the specific effect can apply on the entity, otherwise {@code false}.
     */
    default boolean canApplyEffect(EffectType effectType) {
        return true;
    }

    /**
     * Get the items this entity drops on death.
     *
     * @param lootingLevel the looting enchantment level of the killer's weapon
     * @return the list of item stacks to drop
     */
    List<ItemStack> getDrops(int lootingLevel);

    /**
     * Get the amount of XP this entity drops on death.
     *
     * @return the amount of XP to drop
     */
    int getDropXpAmount();

    /**
     * Gets the damage absorption value of this entity. This value represents how much
     * damage the entity can absorb before taking actual damage.
     *
     * @return the damage absorption value as a floating-point number
     */
    float getAbsorption();

    /**
     * Sets the damage absorption value for this entity.
     *
     * @param absorption the damage absorption value to set
     */
    void setAbsorption(float absorption);

    /**
     * Retrieves the current health of the entity.
     *
     * @return the current health of the entity as a floating-point number
     */
    float getHealth();

    /**
     * Sets the health of the entity.
     *
     * @param health the health value to set for the entity
     */
    void setHealth(float health);

    /**
     * Resets the health of the entity to its maximum value.
     * This method sets the current health of the entity to the value returned by {@link #getMaxHealth()}.
     */
    default void resetHealth() {
        setHealth(getMaxHealth());
    }

    /**
     * Kills the entity by applying 1,000,000 points of damage.
     */
    default void kill() {
        attack(DamageContainer.api(1_000_000));
    }

    /**
     * Returns the maximum health value of the entity.
     *
     * @return the maximum health as a floating-point number
     */
    float getMaxHealth();

    /**
     * Sets the maximum health value for the entity.
     *
     * @param maxHealth the maximum health value to set, must be a positive floating-point number
     */
    void setMaxHealth(float maxHealth);
}
