package com.tiedup.remake.util.teleport;

import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;

/**
 * Represents a teleport position with coordinates, rotation, and dimension.
 *
 * <p>Used by collar teleport commands and warp points in the original mod.</p>
 *
 * <h2>Use Cases</h2>
 * <ul>
 *   <li>Collar warp points (home, prison, custom locations)</li>
 *   <li>EntityKidnapper prison teleportation</li>
 *   <li>Admin teleport commands</li>
 * </ul>
 *
 * <h2>NBT Structure</h2>
 * <pre>
 * position (CompoundTag)
 *   ├── x (double)
 *   ├── y (double)
 *   ├── z (double)
 *   ├── yaw (float)
 *   ├── pitch (float)
 *   └── dimension (string) - e.g., "minecraft:overworld"
 * </pre>
 *
 * <h2>Original Reference</h2>
 * Based on Original/com/yuti/kidnapmod/util/teleport/Position.java
 */
public class Position {

    private final double x;
    private final double y;
    private final double z;
    private final float yaw;
    private final float pitch;
    private final ResourceKey<Level> dimension;

    /**
     * Create a new position with full rotation.
     *
     * @param x X coordinate
     * @param y Y coordinate
     * @param z Z coordinate
     * @param yaw Horizontal rotation (0-360)
     * @param pitch Vertical rotation (-90 to 90)
     * @param dimension Dimension key (e.g., Level.OVERWORLD, Level.NETHER)
     */
    public Position(
        double x,
        double y,
        double z,
        float yaw,
        float pitch,
        ResourceKey<Level> dimension
    ) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.yaw = yaw;
        this.pitch = pitch;
        this.dimension = dimension;
    }

    /**
     * Create a new position without rotation (defaults to 0).
     *
     * @param x X coordinate
     * @param y Y coordinate
     * @param z Z coordinate
     * @param dimension Dimension key
     */
    public Position(
        double x,
        double y,
        double z,
        ResourceKey<Level> dimension
    ) {
        this(x, y, z, 0.0f, 0.0f, dimension);
    }

    /**
     * Create a position from BlockPos and dimension.
     *
     * @param pos Block position
     * @param dimension Dimension key
     */
    public Position(BlockPos pos, ResourceKey<Level> dimension) {
        this(
            pos.getX() + 0.5,
            pos.getY(),
            pos.getZ() + 0.5,
            0.0f,
            0.0f,
            dimension
        );
    }

    /**
     * Create a position from Vec3 and dimension.
     *
     * @param vec Vector position
     * @param dimension Dimension key
     */
    public Position(Vec3 vec, ResourceKey<Level> dimension) {
        this(vec.x, vec.y, vec.z, 0.0f, 0.0f, dimension);
    }

    /**
     * Create a position from an entity's current location.
     *
     * @param entity The entity
     * @return Position at entity's location with rotation
     */
    public static Position fromEntity(Entity entity) {
        return new Position(
            entity.getX(),
            entity.getY(),
            entity.getZ(),
            entity.getYRot(),
            entity.getXRot(),
            entity.level().dimension()
        );
    }

    // NBT SERIALIZATION

    /**
     * Save this position to NBT.
     *
     * @return CompoundTag containing position data
     */
    public CompoundTag save() {
        CompoundTag tag = new CompoundTag();
        tag.putDouble("x", this.x);
        tag.putDouble("y", this.y);
        tag.putDouble("z", this.z);
        tag.putFloat("yaw", this.yaw);
        tag.putFloat("pitch", this.pitch);
        tag.putString("dimension", this.dimension.location().toString());
        return tag;
    }

    /**
     * Save this position to an existing CompoundTag.
     *
     * @param tag The tag to save to
     * @return The modified tag
     */
    public CompoundTag save(CompoundTag tag) {
        tag.putDouble("x", this.x);
        tag.putDouble("y", this.y);
        tag.putDouble("z", this.z);
        tag.putFloat("yaw", this.yaw);
        tag.putFloat("pitch", this.pitch);
        tag.putString("dimension", this.dimension.location().toString());
        return tag;
    }

    /**
     * Load a position from NBT.
     *
     * @param tag The CompoundTag to load from
     * @return Position or null if invalid
     */
    @Nullable
    public static Position load(CompoundTag tag) {
        if (
            tag == null ||
            !tag.contains("x") ||
            !tag.contains("y") ||
            !tag.contains("z")
        ) {
            return null;
        }

        double x = tag.getDouble("x");
        double y = tag.getDouble("y");
        double z = tag.getDouble("z");
        float yaw = tag.contains("yaw") ? tag.getFloat("yaw") : 0.0f;
        float pitch = tag.contains("pitch") ? tag.getFloat("pitch") : 0.0f;

        // Parse dimension
        ResourceKey<Level> dimension = Level.OVERWORLD; // Default
        if (tag.contains("dimension")) {
            String dimStr = tag.getString("dimension");
            ResourceLocation dimLoc = ResourceLocation.tryParse(dimStr);
            if (dimLoc != null) {
                dimension = ResourceKey.create(Registries.DIMENSION, dimLoc);
            }
        }

        return new Position(x, y, z, yaw, pitch, dimension);
    }

    // GETTERS

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public double getZ() {
        return z;
    }

    public float getYaw() {
        return yaw;
    }

    public float getPitch() {
        return pitch;
    }

    public ResourceKey<Level> getDimension() {
        return dimension;
    }

    /**
     * Convert to Vec3.
     * @return Vector representation
     */
    public Vec3 toVec3() {
        return new Vec3(x, y, z);
    }

    /**
     * Convert to BlockPos.
     * @return Block position (floored)
     */
    public BlockPos toBlockPos() {
        return new BlockPos(
            (int) Math.floor(x),
            (int) Math.floor(y),
            (int) Math.floor(z)
        );
    }

    /**
     * Get dimension name for display.
     * @return Dimension name (e.g., "overworld", "the_nether")
     */
    public String getDimensionName() {
        return dimension.location().getPath();
    }

    /**
     * Check if this position is in the same dimension as another.
     * @param other The other position
     * @return true if same dimension
     */
    public boolean isSameDimension(Position other) {
        return other != null && this.dimension.equals(other.dimension);
    }

    /**
     * Check if this position is in the given dimension.
     * @param dim The dimension to check
     * @return true if in that dimension
     */
    public boolean isInDimension(ResourceKey<Level> dim) {
        return this.dimension.equals(dim);
    }

    /**
     * Calculate distance to another position (ignores dimension).
     * @param other The other position
     * @return Distance in blocks
     */
    public double distanceTo(Position other) {
        double dx = this.x - other.x;
        double dy = this.y - other.y;
        double dz = this.z - other.z;
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    }

    /**
     * Calculate horizontal distance to another position (ignores Y and dimension).
     * @param other The other position
     * @return Horizontal distance in blocks
     */
    public double horizontalDistanceTo(Position other) {
        double dx = this.x - other.x;
        double dz = this.z - other.z;
        return Math.sqrt(dx * dx + dz * dz);
    }

    // OBJECT OVERRIDES

    @Override
    public String toString() {
        return String.format(
            "Position{x=%.2f, y=%.2f, z=%.2f, yaw=%.1f, pitch=%.1f, dim=%s}",
            x,
            y,
            z,
            yaw,
            pitch,
            dimension.location()
        );
    }

    /**
     * Get a short string for display (e.g., in tooltips).
     * @return Short position string like "X: 100, Y: 64, Z: -200"
     */
    public String toShortString() {
        return String.format("X: %.0f, Y: %.0f, Z: %.0f", x, y, z);
    }

    /**
     * Get a display string with dimension for tooltips.
     * @return Display string like "overworld - X: 100, Y: 64, Z: -200"
     */
    public String toDisplayString() {
        return String.format(
            "%s - X: %.0f, Y: %.0f, Z: %.0f",
            getDimensionName(),
            x,
            y,
            z
        );
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Position other)) return false;
        return (
            Double.compare(other.x, x) == 0 &&
            Double.compare(other.y, y) == 0 &&
            Double.compare(other.z, z) == 0 &&
            Float.compare(other.yaw, yaw) == 0 &&
            Float.compare(other.pitch, pitch) == 0 &&
            dimension.equals(other.dimension)
        );
    }

    @Override
    public int hashCode() {
        int result = Double.hashCode(x);
        result = 31 * result + Double.hashCode(y);
        result = 31 * result + Double.hashCode(z);
        result = 31 * result + Float.hashCode(yaw);
        result = 31 * result + Float.hashCode(pitch);
        result = 31 * result + dimension.hashCode();
        return result;
    }
}
