package com.tiedup.remake.cells;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import org.jetbrains.annotations.Nullable;

/**
 * Cell data for Cell System V2.
 *
 * Named CellDataV2 to coexist with v1 CellData during the migration period.
 * Contains geometry (interior + walls from flood-fill), breach tracking,
 * prisoner management, and auto-detected features.
 */
public class CellDataV2 {

    private static final int MAX_PRISONERS = 4;

    // Identity
    private final UUID id;
    private CellState state;
    private final BlockPos corePos;

    // Cached from Core BE (for use when chunk is unloaded)
    @Nullable
    private BlockPos spawnPoint;

    @Nullable
    private BlockPos deliveryPoint;

    // Ownership
    @Nullable
    private UUID ownerId;

    private CellOwnerType ownerType = CellOwnerType.PLAYER;

    @Nullable
    private String name;

    // Geometry (from flood-fill)
    private final Set<BlockPos> interiorBlocks;
    private final Set<BlockPos> wallBlocks;
    private final Set<BlockPos> breachedPositions;
    private int totalWallCount;

    // Interior face direction (which face of Core points inside)
    @Nullable
    private Direction interiorFace;

    // Auto-detected features
    private final List<BlockPos> beds;
    private final List<BlockPos> petBeds;
    private final List<BlockPos> anchors;
    private final List<BlockPos> doors;
    private final List<BlockPos> linkedRedstone;

    // Prisoners
    private final List<UUID> prisonerIds = new CopyOnWriteArrayList<>();
    private final Map<UUID, Long> prisonerTimestamps =
        new ConcurrentHashMap<>();

    // Camp navigation
    private final List<BlockPos> pathWaypoints = new CopyOnWriteArrayList<>();

    // ==================== CONSTRUCTORS ====================

    /**
     * Create from a successful flood-fill result.
     */
    public CellDataV2(BlockPos corePos, FloodFillResult result) {
        this.id = UUID.randomUUID();
        this.state = CellState.INTACT;
        this.corePos = corePos.immutable();
        this.interiorFace = result.getInteriorFace();

        this.interiorBlocks = ConcurrentHashMap.newKeySet();
        this.interiorBlocks.addAll(result.getInterior());

        this.wallBlocks = ConcurrentHashMap.newKeySet();
        this.wallBlocks.addAll(result.getWalls());

        this.breachedPositions = ConcurrentHashMap.newKeySet();
        this.totalWallCount = result.getWalls().size();

        this.beds = new CopyOnWriteArrayList<>(result.getBeds());
        this.petBeds = new CopyOnWriteArrayList<>(result.getPetBeds());
        this.anchors = new CopyOnWriteArrayList<>(result.getAnchors());
        this.doors = new CopyOnWriteArrayList<>(result.getDoors());
        this.linkedRedstone = new CopyOnWriteArrayList<>(
            result.getLinkedRedstone()
        );
    }

    /**
     * Create for NBT loading (minimal constructor).
     */
    public CellDataV2(UUID id, BlockPos corePos) {
        this.id = id;
        this.state = CellState.INTACT;
        this.corePos = corePos.immutable();

        this.interiorBlocks = ConcurrentHashMap.newKeySet();
        this.wallBlocks = ConcurrentHashMap.newKeySet();
        this.breachedPositions = ConcurrentHashMap.newKeySet();
        this.totalWallCount = 0;

        this.beds = new CopyOnWriteArrayList<>();
        this.petBeds = new CopyOnWriteArrayList<>();
        this.anchors = new CopyOnWriteArrayList<>();
        this.doors = new CopyOnWriteArrayList<>();
        this.linkedRedstone = new CopyOnWriteArrayList<>();
    }

    // ==================== IDENTITY ====================

    public UUID getId() {
        return id;
    }

    public CellState getState() {
        return state;
    }

    public void setState(CellState state) {
        this.state = state;
    }

    public BlockPos getCorePos() {
        return corePos;
    }

    @Nullable
    public BlockPos getSpawnPoint() {
        return spawnPoint;
    }

    public void setSpawnPoint(@Nullable BlockPos spawnPoint) {
        this.spawnPoint = spawnPoint != null ? spawnPoint.immutable() : null;
    }

    @Nullable
    public BlockPos getDeliveryPoint() {
        return deliveryPoint;
    }

    public void setDeliveryPoint(@Nullable BlockPos deliveryPoint) {
        this.deliveryPoint =
            deliveryPoint != null ? deliveryPoint.immutable() : null;
    }

    @Nullable
    public Direction getInteriorFace() {
        return interiorFace;
    }

    // ==================== OWNERSHIP ====================

    @Nullable
    public UUID getOwnerId() {
        return ownerId;
    }

    public void setOwnerId(@Nullable UUID ownerId) {
        this.ownerId = ownerId;
    }

    public CellOwnerType getOwnerType() {
        return ownerType;
    }

    public void setOwnerType(CellOwnerType ownerType) {
        this.ownerType = ownerType;
    }

    @Nullable
    public String getName() {
        return name;
    }

    public void setName(@Nullable String name) {
        this.name = name;
    }

    public boolean isOwnedBy(UUID playerId) {
        return playerId != null && playerId.equals(ownerId);
    }

    /**
     * Check if a player can manage this cell (open menu, rename, modify settings).
     * - OPs (level 2+) can always manage any cell (including camp-owned).
     * - Player-owned cells: only the owning player.
     * - Camp-owned cells: OPs only.
     *
     * @param playerId UUID of the player
     * @param hasOpPerms true if the player has OP level 2+
     * @return true if the player is allowed to manage this cell
     */
    public boolean canPlayerManage(UUID playerId, boolean hasOpPerms) {
        if (hasOpPerms) return true;
        if (isCampOwned()) return false;
        return isOwnedBy(playerId);
    }

    public boolean hasOwner() {
        return ownerId != null;
    }

    public boolean isCampOwned() {
        return ownerType == CellOwnerType.CAMP;
    }

    public boolean isPlayerOwned() {
        return ownerType == CellOwnerType.PLAYER;
    }

    @Nullable
    public UUID getCampId() {
        return isCampOwned() ? ownerId : null;
    }

    // ==================== GEOMETRY ====================

    public Set<BlockPos> getInteriorBlocks() {
        return Collections.unmodifiableSet(interiorBlocks);
    }

    public Set<BlockPos> getWallBlocks() {
        return Collections.unmodifiableSet(wallBlocks);
    }

    public Set<BlockPos> getBreachedPositions() {
        return Collections.unmodifiableSet(breachedPositions);
    }

    public int getTotalWallCount() {
        return totalWallCount;
    }

    public boolean isContainedInCell(BlockPos pos) {
        return interiorBlocks.contains(pos);
    }

    public boolean isWallBlock(BlockPos pos) {
        return wallBlocks.contains(pos);
    }

    // ==================== BREACH MANAGEMENT ====================

    public void addBreach(BlockPos wallPos) {
        if (wallBlocks.remove(wallPos)) {
            breachedPositions.add(wallPos.immutable());
        }
    }

    public void repairBreach(BlockPos wallPos) {
        if (breachedPositions.remove(wallPos)) {
            wallBlocks.add(wallPos.immutable());
        }
    }

    public float getBreachPercentage() {
        if (totalWallCount == 0) return 0.0f;
        return (float) breachedPositions.size() / totalWallCount;
    }

    // ==================== FEATURES ====================

    public List<BlockPos> getBeds() {
        return Collections.unmodifiableList(beds);
    }

    public List<BlockPos> getPetBeds() {
        return Collections.unmodifiableList(petBeds);
    }

    public List<BlockPos> getAnchors() {
        return Collections.unmodifiableList(anchors);
    }

    public List<BlockPos> getDoors() {
        return Collections.unmodifiableList(doors);
    }

    public List<BlockPos> getLinkedRedstone() {
        return Collections.unmodifiableList(linkedRedstone);
    }

    // ==================== PRISONER MANAGEMENT ====================

    public List<UUID> getPrisonerIds() {
        return Collections.unmodifiableList(prisonerIds);
    }

    public boolean hasPrisoner(UUID prisonerId) {
        return prisonerIds.contains(prisonerId);
    }

    public boolean isFull() {
        return prisonerIds.size() >= MAX_PRISONERS;
    }

    public boolean isOccupied() {
        return !prisonerIds.isEmpty();
    }

    public int getPrisonerCount() {
        return prisonerIds.size();
    }

    public boolean addPrisoner(UUID prisonerId) {
        if (isFull() || prisonerIds.contains(prisonerId)) {
            return false;
        }
        prisonerIds.add(prisonerId);
        prisonerTimestamps.put(prisonerId, System.currentTimeMillis());
        return true;
    }

    public boolean removePrisoner(UUID prisonerId) {
        boolean removed = prisonerIds.remove(prisonerId);
        if (removed) {
            prisonerTimestamps.remove(prisonerId);
        }
        return removed;
    }

    @Nullable
    public Long getPrisonerTimestamp(UUID prisonerId) {
        return prisonerTimestamps.get(prisonerId);
    }

    // ==================== CAMP NAVIGATION ====================

    public List<BlockPos> getPathWaypoints() {
        return Collections.unmodifiableList(pathWaypoints);
    }

    public void setPathWaypoints(List<BlockPos> waypoints) {
        pathWaypoints.clear();
        pathWaypoints.addAll(waypoints);
    }

    // ==================== WIRE RECONSTRUCTION (client-side) ====================

    /** Add a wall block position (used for client-side packet reconstruction). */
    public void addWallBlock(BlockPos pos) {
        wallBlocks.add(pos.immutable());
    }

    /** Add a bed position (used for client-side packet reconstruction). */
    public void addBed(BlockPos pos) {
        beds.add(pos.immutable());
    }

    /** Add an anchor position (used for client-side packet reconstruction). */
    public void addAnchor(BlockPos pos) {
        anchors.add(pos.immutable());
    }

    /** Add a door position (used for client-side packet reconstruction). */
    public void addDoor(BlockPos pos) {
        doors.add(pos.immutable());
    }

    // ==================== GEOMETRY UPDATE (rescan) ====================

    /**
     * Replace geometry with a new flood-fill result (used during rescan).
     */
    public void updateGeometry(FloodFillResult result) {
        interiorBlocks.clear();
        interiorBlocks.addAll(result.getInterior());

        wallBlocks.clear();
        wallBlocks.addAll(result.getWalls());

        breachedPositions.clear();
        totalWallCount = result.getWalls().size();

        if (result.getInteriorFace() != null) {
            this.interiorFace = result.getInteriorFace();
        }

        beds.clear();
        beds.addAll(result.getBeds());
        petBeds.clear();
        petBeds.addAll(result.getPetBeds());
        anchors.clear();
        anchors.addAll(result.getAnchors());
        doors.clear();
        doors.addAll(result.getDoors());
        linkedRedstone.clear();
        linkedRedstone.addAll(result.getLinkedRedstone());

        state = CellState.INTACT;
    }

    // ==================== NBT PERSISTENCE ====================

    public CompoundTag save() {
        CompoundTag tag = new CompoundTag();

        tag.putUUID("id", id);
        tag.putString("state", state.getSerializedName());
        tag.put("corePos", NbtUtils.writeBlockPos(corePos));

        if (spawnPoint != null) {
            tag.put("spawnPoint", NbtUtils.writeBlockPos(spawnPoint));
        }
        if (deliveryPoint != null) {
            tag.put("deliveryPoint", NbtUtils.writeBlockPos(deliveryPoint));
        }
        if (interiorFace != null) {
            tag.putString("interiorFace", interiorFace.getSerializedName());
        }

        // Ownership
        if (ownerId != null) {
            tag.putUUID("ownerId", ownerId);
        }
        tag.putString("ownerType", ownerType.getSerializedName());
        if (name != null) {
            tag.putString("name", name);
        }

        // Geometry
        tag.put("interior", saveBlockPosSet(interiorBlocks));
        tag.put("walls", saveBlockPosSet(wallBlocks));
        tag.put("breached", saveBlockPosSet(breachedPositions));
        tag.putInt("totalWallCount", totalWallCount);

        // Features
        tag.put("beds", saveBlockPosList(beds));
        tag.put("petBeds", saveBlockPosList(petBeds));
        tag.put("anchors", saveBlockPosList(anchors));
        tag.put("doors", saveBlockPosList(doors));
        tag.put("linkedRedstone", saveBlockPosList(linkedRedstone));

        // Prisoners
        if (!prisonerIds.isEmpty()) {
            ListTag prisonerList = new ListTag();
            for (UUID uuid : prisonerIds) {
                CompoundTag prisonerTag = new CompoundTag();
                prisonerTag.putUUID("id", uuid);
                prisonerTag.putLong(
                    "timestamp",
                    prisonerTimestamps.getOrDefault(
                        uuid,
                        System.currentTimeMillis()
                    )
                );
                prisonerList.add(prisonerTag);
            }
            tag.put("prisoners", prisonerList);
        }

        // Path waypoints
        if (!pathWaypoints.isEmpty()) {
            tag.put("pathWaypoints", saveBlockPosList(pathWaypoints));
        }

        return tag;
    }

    @Nullable
    public static CellDataV2 load(CompoundTag tag) {
        if (!tag.contains("id") || !tag.contains("corePos")) {
            return null;
        }

        UUID id = tag.getUUID("id");
        BlockPos corePos = NbtUtils.readBlockPos(tag.getCompound("corePos"));
        CellDataV2 cell = new CellDataV2(id, corePos);

        cell.state = CellState.fromString(tag.getString("state"));

        if (tag.contains("spawnPoint")) {
            cell.spawnPoint = NbtUtils.readBlockPos(
                tag.getCompound("spawnPoint")
            );
        }
        if (tag.contains("deliveryPoint")) {
            cell.deliveryPoint = NbtUtils.readBlockPos(
                tag.getCompound("deliveryPoint")
            );
        }
        if (tag.contains("interiorFace")) {
            cell.interiorFace = Direction.byName(tag.getString("interiorFace"));
        }

        // Ownership
        if (tag.contains("ownerId")) {
            cell.ownerId = tag.getUUID("ownerId");
        }
        if (tag.contains("ownerType")) {
            cell.ownerType = CellOwnerType.fromString(
                tag.getString("ownerType")
            );
        }
        if (tag.contains("name")) {
            cell.name = tag.getString("name");
        }

        // Geometry
        loadBlockPosSet(tag, "interior", cell.interiorBlocks);
        loadBlockPosSet(tag, "walls", cell.wallBlocks);
        loadBlockPosSet(tag, "breached", cell.breachedPositions);
        cell.totalWallCount = tag.getInt("totalWallCount");

        // Features
        loadBlockPosList(tag, "beds", cell.beds);
        loadBlockPosList(tag, "petBeds", cell.petBeds);
        loadBlockPosList(tag, "anchors", cell.anchors);
        loadBlockPosList(tag, "doors", cell.doors);
        loadBlockPosList(tag, "linkedRedstone", cell.linkedRedstone);

        // Prisoners
        if (tag.contains("prisoners")) {
            ListTag prisonerList = tag.getList("prisoners", Tag.TAG_COMPOUND);
            for (int i = 0; i < prisonerList.size(); i++) {
                CompoundTag prisonerTag = prisonerList.getCompound(i);
                UUID prisonerId = prisonerTag.getUUID("id");
                cell.prisonerIds.add(prisonerId);
                long timestamp = prisonerTag.contains("timestamp")
                    ? prisonerTag.getLong("timestamp")
                    : System.currentTimeMillis();
                cell.prisonerTimestamps.put(prisonerId, timestamp);
            }
        }

        // Path waypoints
        loadBlockPosList(tag, "pathWaypoints", cell.pathWaypoints);

        return cell;
    }

    // ==================== NBT HELPERS ====================

    private static ListTag saveBlockPosSet(Set<BlockPos> positions) {
        ListTag list = new ListTag();
        for (BlockPos pos : positions) {
            list.add(NbtUtils.writeBlockPos(pos));
        }
        return list;
    }

    private static ListTag saveBlockPosList(List<BlockPos> positions) {
        ListTag list = new ListTag();
        for (BlockPos pos : positions) {
            list.add(NbtUtils.writeBlockPos(pos));
        }
        return list;
    }

    private static void loadBlockPosSet(
        CompoundTag parent,
        String key,
        Set<BlockPos> target
    ) {
        if (parent.contains(key)) {
            ListTag list = parent.getList(key, Tag.TAG_COMPOUND);
            for (int i = 0; i < list.size(); i++) {
                target.add(NbtUtils.readBlockPos(list.getCompound(i)));
            }
        }
    }

    private static void loadBlockPosList(
        CompoundTag parent,
        String key,
        List<BlockPos> target
    ) {
        if (parent.contains(key)) {
            ListTag list = parent.getList(key, Tag.TAG_COMPOUND);
            for (int i = 0; i < list.size(); i++) {
                target.add(NbtUtils.readBlockPos(list.getCompound(i)));
            }
        }
    }

    // ==================== DEBUG ====================

    @Override
    public String toString() {
        return (
            "CellDataV2{id=" +
            id.toString().substring(0, 8) +
            "..., state=" +
            state +
            ", core=" +
            corePos.toShortString() +
            ", interior=" +
            interiorBlocks.size() +
            ", walls=" +
            wallBlocks.size() +
            ", prisoners=" +
            prisonerIds.size() +
            "}"
        );
    }
}
