// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.entity;

import java.util.Map;
import java.util.Collection;
import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import java.util.logging.Level;
import com.hypixel.hytale.protocol.InteractionCooldown;
import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Operation;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import javax.annotation.Nullable;
import com.hypixel.hytale.protocol.InteractionState;
import com.hypixel.hytale.protocol.InteractionSyncData;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction;
import java.util.List;
import it.unimi.dsi.fastutil.longs.Long2LongMap;
import javax.annotation.Nonnull;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import com.hypixel.hytale.protocol.ForkedChainId;
import com.hypixel.hytale.protocol.InteractionChainData;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.logger.HytaleLogger;

public class InteractionChain implements ChainSyncStorage
{
    private static final HytaleLogger LOGGER;
    private static final long NULL_FORK_ID;
    private final InteractionType type;
    private InteractionType baseType;
    private final InteractionChainData chainData;
    private int chainId;
    private final ForkedChainId forkedChainId;
    private final ForkedChainId baseForkedChainId;
    private boolean predicted;
    private final InteractionContext context;
    @Nonnull
    private final Long2ObjectMap<InteractionChain> forkedChains;
    @Nonnull
    private final Long2ObjectMap<TempChain> tempForkedChainData;
    @Nonnull
    private final Long2LongMap forkedChainsMap;
    @Nonnull
    private final List<InteractionChain> newForks;
    @Nonnull
    private final RootInteraction initialRootInteraction;
    private RootInteraction rootInteraction;
    private int operationCounter;
    @Nonnull
    private final List<CallState> callStack;
    private int simulatedCallStack;
    private final boolean requiresClient;
    private int simulatedOperationCounter;
    private RootInteraction simulatedRootInteraction;
    private int operationIndex;
    private int operationIndexOffset;
    private int clientOperationIndex;
    @Nonnull
    private final List<InteractionEntry> interactions;
    @Nonnull
    private final List<InteractionSyncData> tempSyncData;
    private int tempSyncDataOffset;
    private long timestamp;
    private long waitingForServerFinished;
    private long waitingForClientFinished;
    private InteractionState clientState;
    private InteractionState serverState;
    private InteractionState finalState;
    @Nullable
    private Runnable onCompletion;
    private boolean sentInitial;
    private boolean desynced;
    private float timeShift;
    private boolean firstRun;
    private boolean isFirstRun;
    private boolean completed;
    private boolean preTicked;
    boolean skipChainOnClick;
    
    public InteractionChain(final InteractionType type, final InteractionContext context, final InteractionChainData chainData, @Nonnull final RootInteraction rootInteraction, @Nullable final Runnable onCompletion, final boolean requiresClient) {
        this(null, null, type, context, chainData, rootInteraction, onCompletion, requiresClient);
    }
    
    public InteractionChain(final ForkedChainId forkedChainId, final ForkedChainId baseForkedChainId, final InteractionType type, final InteractionContext context, final InteractionChainData chainData, @Nonnull final RootInteraction rootInteraction, @Nullable final Runnable onCompletion, final boolean requiresClient) {
        this.forkedChains = new Long2ObjectOpenHashMap<InteractionChain>();
        this.tempForkedChainData = new Long2ObjectOpenHashMap<TempChain>();
        this.forkedChainsMap = new Long2LongOpenHashMap();
        this.newForks = new ObjectArrayList<InteractionChain>();
        this.operationCounter = 0;
        this.callStack = new ObjectArrayList<CallState>();
        this.simulatedCallStack = 0;
        this.simulatedOperationCounter = 0;
        this.operationIndex = 0;
        this.operationIndexOffset = 0;
        this.clientOperationIndex = 0;
        this.interactions = new ObjectArrayList<InteractionEntry>();
        this.tempSyncData = new ObjectArrayList<InteractionSyncData>();
        this.tempSyncDataOffset = 0;
        this.timestamp = System.nanoTime();
        this.clientState = InteractionState.NotFinished;
        this.serverState = InteractionState.NotFinished;
        this.finalState = InteractionState.Finished;
        this.firstRun = true;
        this.isFirstRun = true;
        this.completed = false;
        this.baseType = type;
        this.type = type;
        this.context = context;
        this.chainData = chainData;
        this.forkedChainId = forkedChainId;
        this.baseForkedChainId = baseForkedChainId;
        this.onCompletion = onCompletion;
        this.simulatedRootInteraction = rootInteraction;
        this.rootInteraction = rootInteraction;
        this.initialRootInteraction = rootInteraction;
        this.requiresClient = (requiresClient || rootInteraction.needsRemoteSync());
        this.forkedChainsMap.defaultReturnValue(InteractionChain.NULL_FORK_ID);
    }
    
    public InteractionType getType() {
        return this.type;
    }
    
    public int getChainId() {
        return this.chainId;
    }
    
    public ForkedChainId getForkedChainId() {
        return this.forkedChainId;
    }
    
    public ForkedChainId getBaseForkedChainId() {
        return this.baseForkedChainId;
    }
    
    @Nonnull
    public RootInteraction getInitialRootInteraction() {
        return this.initialRootInteraction;
    }
    
    public boolean isPredicted() {
        return this.predicted;
    }
    
    public InteractionContext getContext() {
        return this.context;
    }
    
    public InteractionChainData getChainData() {
        return this.chainData;
    }
    
    public InteractionState getServerState() {
        return this.serverState;
    }
    
    public boolean requiresClient() {
        return this.requiresClient;
    }
    
    public RootInteraction getRootInteraction() {
        return this.rootInteraction;
    }
    
    public RootInteraction getSimulatedRootInteraction() {
        return this.simulatedRootInteraction;
    }
    
    public int getOperationCounter() {
        return this.operationCounter;
    }
    
    public void setOperationCounter(final int operationCounter) {
        this.operationCounter = operationCounter;
    }
    
    public int getSimulatedOperationCounter() {
        return this.simulatedOperationCounter;
    }
    
    public void setSimulatedOperationCounter(final int simulatedOperationCounter) {
        this.simulatedOperationCounter = simulatedOperationCounter;
    }
    
    public boolean wasPreTicked() {
        return this.preTicked;
    }
    
    public void setPreTicked(final boolean preTicked) {
        this.preTicked = preTicked;
    }
    
    public int getOperationIndex() {
        return this.operationIndex;
    }
    
    public void nextOperationIndex() {
        ++this.operationIndex;
        ++this.clientOperationIndex;
    }
    
    public int getClientOperationIndex() {
        return this.clientOperationIndex;
    }
    
    @Nullable
    public InteractionChain findForkedChain(@Nonnull final ForkedChainId chainId, @Nullable final InteractionChainData data) {
        long id = forkedIdToIndex(chainId);
        final long altId = this.forkedChainsMap.get(id);
        if (altId != InteractionChain.NULL_FORK_ID) {
            id = altId;
        }
        InteractionChain chain = this.forkedChains.get(id);
        if (chain != null || chainId.subIndex >= 0 || data == null) {
            return chain;
        }
        final InteractionEntry entry = this.getInteraction(chainId.entryIndex);
        if (entry == null) {
            return null;
        }
        final int rootId = entry.getServerState().rootInteraction;
        final int opCounter = entry.getServerState().operationCounter;
        final RootInteraction root = RootInteraction.getAssetMap().getAsset(rootId);
        final Operation op = root.getOperation(opCounter).getInnerOperation();
        if (op instanceof final Interaction interaction) {
            this.context.initEntry(this, entry, null);
            chain = interaction.mapForkChain(this.context, data);
            this.context.deinitEntry(this, entry, null);
            if (chain != null) {
                this.forkedChainsMap.put(id, forkedIdToIndex(chain.getBaseForkedChainId()));
            }
            return chain;
        }
        return null;
    }
    
    public InteractionChain getForkedChain(@Nonnull final ForkedChainId chainId) {
        long id = forkedIdToIndex(chainId);
        if (chainId.subIndex < 0) {
            final long altId = this.forkedChainsMap.get(id);
            if (altId != InteractionChain.NULL_FORK_ID) {
                id = altId;
            }
        }
        return this.forkedChains.get(id);
    }
    
    public void putForkedChain(@Nonnull final ForkedChainId chainId, @Nonnull final InteractionChain chain) {
        this.newForks.add(chain);
        this.forkedChains.put(forkedIdToIndex(chainId), chain);
    }
    
    @Nullable
    public TempChain getTempForkedChain(@Nonnull final ForkedChainId chainId) {
        final InteractionEntry entry = this.getInteraction(chainId.entryIndex);
        if (entry != null) {
            if (chainId.subIndex < entry.getNextForkId()) {
                return null;
            }
        }
        else if (chainId.entryIndex < this.operationIndexOffset) {
            return null;
        }
        return this.tempForkedChainData.computeIfAbsent(forkedIdToIndex(chainId), i -> new TempChain());
    }
    
    @Nullable
    TempChain removeTempForkedChain(@Nonnull final ForkedChainId chainId, final InteractionChain forkChain) {
        long id = forkedIdToIndex(chainId);
        final long altId = this.forkedChainsMap.get(id);
        if (altId != InteractionChain.NULL_FORK_ID) {
            id = altId;
        }
        final TempChain found = this.tempForkedChainData.remove(id);
        if (found != null) {
            return found;
        }
        final InteractionEntry iEntry = this.context.getEntry();
        final RootInteraction root = RootInteraction.getAssetMap().getAsset(iEntry.getState().rootInteraction);
        final Operation op = root.getOperation(iEntry.getState().operationCounter).getInnerOperation();
        if (op instanceof final Interaction interaction) {
            final ObjectIterator<Long2ObjectMap.Entry<TempChain>> it = Long2ObjectMaps.fastIterator(this.getTempForkedChainData());
            while (it.hasNext()) {
                final Long2ObjectMap.Entry<TempChain> entry = it.next();
                final TempChain tempChain = entry.getValue();
                if (tempChain.baseForkedChainId == null) {
                    continue;
                }
                final int entryId = tempChain.baseForkedChainId.entryIndex;
                if (entryId != iEntry.getIndex()) {
                    continue;
                }
                final InteractionChain chain = interaction.mapForkChain(this.getContext(), tempChain.chainData);
                if (chain != null) {
                    this.forkedChainsMap.put(forkedIdToIndex(tempChain.baseForkedChainId), forkedIdToIndex(chain.getBaseForkedChainId()));
                }
                if (chain == forkChain) {
                    it.remove();
                    return tempChain;
                }
            }
        }
        return null;
    }
    
    public boolean hasSentInitial() {
        return this.sentInitial;
    }
    
    public void setSentInitial(final boolean sentInitial) {
        this.sentInitial = sentInitial;
    }
    
    public float getTimeShift() {
        return this.timeShift;
    }
    
    public void setTimeShift(final float timeShift) {
        this.timeShift = timeShift;
    }
    
    public boolean consumeFirstRun() {
        this.isFirstRun = this.firstRun;
        this.firstRun = false;
        return this.isFirstRun;
    }
    
    public boolean isFirstRun() {
        return this.isFirstRun;
    }
    
    public void setFirstRun(final boolean firstRun) {
        this.isFirstRun = firstRun;
    }
    
    public int getCallDepth() {
        return this.callStack.size();
    }
    
    public int getSimulatedCallDepth() {
        return this.simulatedCallStack;
    }
    
    public void pushRoot(final RootInteraction nextInteraction, final boolean simulate) {
        if (simulate) {
            this.simulatedRootInteraction = nextInteraction;
            this.simulatedOperationCounter = 0;
            ++this.simulatedCallStack;
            return;
        }
        this.callStack.add(new CallState(this.rootInteraction, this.operationCounter));
        this.operationCounter = 0;
        this.rootInteraction = nextInteraction;
    }
    
    public void popRoot() {
        final CallState state = this.callStack.removeLast();
        this.rootInteraction = state.rootInteraction;
        this.operationCounter = state.operationCounter + 1;
        this.simulatedRootInteraction = this.rootInteraction;
        this.simulatedOperationCounter = this.operationCounter;
        --this.simulatedCallStack;
    }
    
    public float getTimeInSeconds() {
        if (this.timestamp == 0L) {
            return 0.0f;
        }
        final long diff = System.nanoTime() - this.timestamp;
        return diff / 1.0E9f;
    }
    
    public void setOnCompletion(final Runnable onCompletion) {
        this.onCompletion = onCompletion;
    }
    
    void onCompletion(final CooldownHandler cooldownHandler, final boolean isRemote) {
        if (this.completed) {
            return;
        }
        this.completed = true;
        if (this.onCompletion != null) {
            this.onCompletion.run();
            this.onCompletion = null;
        }
        if (isRemote) {
            final InteractionCooldown cooldown = this.initialRootInteraction.getCooldown();
            String cooldownId = this.initialRootInteraction.getId();
            if (cooldown != null && cooldown.cooldownId != null) {
                cooldownId = cooldown.cooldownId;
            }
            final CooldownHandler.Cooldown cooldownTracker = cooldownHandler.getCooldown(cooldownId);
            if (cooldownTracker != null) {
                cooldownTracker.tick(0.016666668f);
            }
        }
    }
    
    void updateServerState() {
        if (this.serverState != InteractionState.NotFinished) {
            return;
        }
        if (this.operationCounter >= this.rootInteraction.getOperationMax()) {
            this.serverState = this.finalState;
            return;
        }
        final InteractionEntry entry = this.getOrCreateInteractionEntry(this.operationIndex);
        this.serverState = switch (entry.getServerState().state) {
            case NotFinished,  Finished -> InteractionState.NotFinished;
            default -> InteractionState.Failed;
        };
    }
    
    void updateSimulatedState() {
        if (this.clientState != InteractionState.NotFinished) {
            return;
        }
        if (this.simulatedOperationCounter >= this.rootInteraction.getOperationMax()) {
            this.clientState = this.finalState;
            return;
        }
        final InteractionEntry entry = this.getOrCreateInteractionEntry(this.clientOperationIndex);
        this.clientState = switch (entry.getSimulationState().state) {
            case NotFinished,  Finished -> InteractionState.NotFinished;
            default -> InteractionState.Failed;
        };
    }
    
    @Override
    public InteractionState getClientState() {
        return this.clientState;
    }
    
    @Override
    public void setClientState(final InteractionState state) {
        this.clientState = state;
    }
    
    @Nonnull
    public InteractionEntry getOrCreateInteractionEntry(final int index) {
        final int oIndex = index - this.operationIndexOffset;
        if (oIndex < 0) {
            throw new IllegalArgumentException("Trying to access removed interaction entry");
        }
        InteractionEntry entry = (oIndex < this.interactions.size()) ? this.interactions.get(oIndex) : null;
        if (entry == null) {
            if (oIndex != this.interactions.size()) {
                throw new IllegalArgumentException("Trying to add interaction entry at a weird location: " + oIndex + " " + this.interactions.size());
            }
            entry = new InteractionEntry(index, this.operationCounter, RootInteraction.getRootInteractionIdOrUnknown(this.rootInteraction.getId()));
            this.interactions.add(entry);
        }
        return entry;
    }
    
    @Nullable
    @Override
    public InteractionEntry getInteraction(int index) {
        index -= this.operationIndexOffset;
        if (index < 0 || index >= this.interactions.size()) {
            return null;
        }
        return this.interactions.get(index);
    }
    
    public void removeInteractionEntry(@Nonnull final InteractionManager interactionManager, final int index) {
        final int oIndex = index - this.operationIndexOffset;
        if (oIndex != 0) {
            this.flagDesync();
            return;
        }
        final InteractionEntry entry = this.interactions.remove(oIndex);
        ++this.operationIndexOffset;
        this.tempForkedChainData.values().removeIf(fork -> {
            if (fork.baseForkedChainId.entryIndex != entry.getIndex()) {
                return false;
            }
            else {
                interactionManager.sendCancelPacket(this.getChainId(), fork.forkedChainId);
                return true;
            }
        });
    }
    
    @Override
    public void putInteractionSyncData(int index, final InteractionSyncData data) {
        index -= this.tempSyncDataOffset;
        if (index >= 0) {
            if (index < this.tempSyncData.size()) {
                this.tempSyncData.set(index, data);
            }
            else if (index == this.tempSyncData.size()) {
                this.tempSyncData.add(data);
            }
            else {
                InteractionChain.LOGGER.at(Level.WARNING).log("Temp sync data sent out of order: " + index + " " + this.tempSyncData.size());
            }
        }
    }
    
    @Override
    public void clearInteractionSyncData(final int operationIndex) {
        final int tempIdx = operationIndex - this.tempSyncDataOffset;
        if (!this.tempSyncData.isEmpty()) {
            for (int end = this.tempSyncData.size() - 1; end >= tempIdx && end >= 0; --end) {
                this.tempSyncData.remove(end);
            }
        }
        final int idx = operationIndex - this.operationIndexOffset;
        for (int i = Math.max(idx, 0); i < this.interactions.size(); ++i) {
            this.interactions.get(i).setClientState(null);
        }
    }
    
    @Nullable
    public InteractionSyncData removeInteractionSyncData(int index) {
        index -= this.tempSyncDataOffset;
        if (index != 0) {
            return null;
        }
        if (this.tempSyncData.isEmpty()) {
            return null;
        }
        if (this.tempSyncData.get(index) == null) {
            return null;
        }
        ++this.tempSyncDataOffset;
        return this.tempSyncData.remove(index);
    }
    
    @Override
    public void updateSyncPosition(final int index) {
        if (this.tempSyncDataOffset == index) {
            this.tempSyncDataOffset = index + 1;
        }
        else if (index > this.tempSyncDataOffset) {
            throw new IllegalArgumentException("Temp sync data sent out of order: " + index + " " + this.tempSyncData.size());
        }
    }
    
    @Override
    public boolean isSyncDataOutOfOrder(final int index) {
        return index > this.tempSyncDataOffset + this.tempSyncData.size();
    }
    
    @Override
    public void syncFork(@Nonnull final Ref<EntityStore> ref, @Nonnull final InteractionManager manager, @Nonnull final SyncInteractionChain packet) {
        ForkedChainId baseId;
        for (baseId = packet.forkedId; baseId.forkedId != null; baseId = baseId.forkedId) {}
        final InteractionChain fork = this.findForkedChain(baseId, packet.data);
        if (fork != null) {
            manager.sync(ref, fork, packet);
        }
        else {
            final TempChain temp = this.getTempForkedChain(baseId);
            if (temp == null) {
                return;
            }
            temp.setForkedChainId(packet.forkedId);
            temp.setBaseForkedChainId(baseId);
            temp.setChainData(packet.data);
            manager.sync(ref, temp, packet);
        }
    }
    
    public void copyTempFrom(@Nonnull final TempChain temp) {
        this.setClientState(temp.clientState);
        this.tempSyncData.addAll(temp.tempSyncData);
        this.getTempForkedChainData().putAll((Map<?, ?>)temp.tempForkedChainData);
    }
    
    private static long forkedIdToIndex(@Nonnull final ForkedChainId chainId) {
        return (long)chainId.entryIndex << 32 | ((long)chainId.subIndex & 0xFFFFFFFFL);
    }
    
    public void setChainId(final int chainId) {
        this.chainId = chainId;
    }
    
    public InteractionType getBaseType() {
        return this.baseType;
    }
    
    public void setBaseType(final InteractionType baseType) {
        this.baseType = baseType;
    }
    
    @Nonnull
    public Long2ObjectMap<InteractionChain> getForkedChains() {
        return this.forkedChains;
    }
    
    @Nonnull
    public Long2ObjectMap<TempChain> getTempForkedChainData() {
        return this.tempForkedChainData;
    }
    
    public long getTimestamp() {
        return this.timestamp;
    }
    
    public void setTimestamp(final long timestamp) {
        this.timestamp = timestamp;
    }
    
    public long getWaitingForServerFinished() {
        return this.waitingForServerFinished;
    }
    
    public void setWaitingForServerFinished(final long waitingForServerFinished) {
        this.waitingForServerFinished = waitingForServerFinished;
    }
    
    public long getWaitingForClientFinished() {
        return this.waitingForClientFinished;
    }
    
    public void setWaitingForClientFinished(final long waitingForClientFinished) {
        this.waitingForClientFinished = waitingForClientFinished;
    }
    
    public void setServerState(final InteractionState serverState) {
        this.serverState = serverState;
    }
    
    public InteractionState getFinalState() {
        return this.finalState;
    }
    
    public void setFinalState(final InteractionState finalState) {
        this.finalState = finalState;
    }
    
    void setPredicted(final boolean predicted) {
        this.predicted = predicted;
    }
    
    public void flagDesync() {
        this.desynced = true;
        this.forkedChains.forEach((k, c) -> c.flagDesync());
    }
    
    public boolean isDesynced() {
        return this.desynced;
    }
    
    @Nonnull
    public List<InteractionChain> getNewForks() {
        return this.newForks;
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "InteractionChain{type=" + String.valueOf(this.type) + ", chainData=" + String.valueOf(this.chainData) + ", chainId=" + this.chainId + ", forkedChainId=" + String.valueOf(this.forkedChainId) + ", predicted=" + this.predicted + ", context=" + String.valueOf(this.context) + ", forkedChains=" + String.valueOf(this.forkedChains) + ", tempForkedChainData=" + String.valueOf(this.tempForkedChainData) + ", initialRootInteraction=" + String.valueOf(this.initialRootInteraction) + ", rootInteraction=" + String.valueOf(this.rootInteraction) + ", operationCounter=" + this.operationCounter + ", callStack=" + String.valueOf(this.callStack) + ", simulatedCallStack=" + this.simulatedCallStack + ", requiresClient=" + this.requiresClient + ", simulatedOperationCounter=" + this.simulatedOperationCounter + ", simulatedRootInteraction=" + String.valueOf(this.simulatedRootInteraction) + ", operationIndex=" + this.operationIndex + ", operationIndexOffset=" + this.operationIndexOffset + ", clientOperationIndex=" + this.clientOperationIndex + ", interactions=" + String.valueOf(this.interactions) + ", tempSyncData=" + String.valueOf(this.tempSyncData) + ", tempSyncDataOffset=" + this.tempSyncDataOffset + ", timestamp=" + this.timestamp + ", waitingForServerFinished=" + this.waitingForServerFinished + ", waitingForClientFinished=" + this.waitingForClientFinished + ", clientState=" + String.valueOf(this.clientState) + ", serverState=" + String.valueOf(this.serverState) + ", onCompletion=" + String.valueOf(this.onCompletion) + ", sentInitial=" + this.sentInitial + ", desynced=" + this.desynced + ", timeShift=" + this.timeShift + ", firstRun=" + this.firstRun + ", skipChainOnClick=" + this.skipChainOnClick;
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
        NULL_FORK_ID = forkedIdToIndex(new ForkedChainId(-1, Integer.MAX_VALUE, null));
    }
    
    static class TempChain implements ChainSyncStorage
    {
        final Long2ObjectMap<TempChain> tempForkedChainData;
        final List<InteractionSyncData> tempSyncData;
        ForkedChainId forkedChainId;
        InteractionState clientState;
        ForkedChainId baseForkedChainId;
        InteractionChainData chainData;
        
        TempChain() {
            this.tempForkedChainData = new Long2ObjectOpenHashMap<TempChain>();
            this.tempSyncData = new ObjectArrayList<InteractionSyncData>();
            this.clientState = InteractionState.NotFinished;
        }
        
        @Nonnull
        public TempChain getOrCreateTempForkedChain(@Nonnull final ForkedChainId chainId) {
            return this.tempForkedChainData.computeIfAbsent(InteractionChain.forkedIdToIndex(chainId), i -> new TempChain());
        }
        
        @Override
        public InteractionState getClientState() {
            return this.clientState;
        }
        
        @Override
        public void setClientState(final InteractionState state) {
            this.clientState = state;
        }
        
        @Nullable
        @Override
        public InteractionEntry getInteraction(final int index) {
            return null;
        }
        
        @Override
        public void putInteractionSyncData(final int index, final InteractionSyncData data) {
            if (index < this.tempSyncData.size()) {
                this.tempSyncData.set(index, data);
            }
            else {
                if (index != this.tempSyncData.size()) {
                    throw new IllegalArgumentException("Temp sync data sent out of order: " + index + " " + this.tempSyncData.size());
                }
                this.tempSyncData.add(data);
            }
        }
        
        @Override
        public void updateSyncPosition(final int index) {
        }
        
        @Override
        public boolean isSyncDataOutOfOrder(final int index) {
            return index > this.tempSyncData.size();
        }
        
        @Override
        public void syncFork(@Nonnull final Ref<EntityStore> ref, @Nonnull final InteractionManager manager, @Nonnull final SyncInteractionChain packet) {
            ForkedChainId baseId;
            for (baseId = packet.forkedId; baseId.forkedId != null; baseId = baseId.forkedId) {}
            final TempChain temp = this.getOrCreateTempForkedChain(baseId);
            temp.setForkedChainId(packet.forkedId);
            temp.setBaseForkedChainId(baseId);
            temp.setChainData(packet.data);
            manager.sync(ref, temp, packet);
        }
        
        @Override
        public void clearInteractionSyncData(final int index) {
            for (int end = this.tempSyncData.size() - 1; end >= index; --end) {
                this.tempSyncData.remove(end);
            }
        }
        
        public InteractionChainData getChainData() {
            return this.chainData;
        }
        
        public void setChainData(final InteractionChainData chainData) {
            this.chainData = chainData;
        }
        
        public ForkedChainId getBaseForkedChainId() {
            return this.baseForkedChainId;
        }
        
        public void setBaseForkedChainId(final ForkedChainId baseForkedChainId) {
            this.baseForkedChainId = baseForkedChainId;
        }
        
        public void setForkedChainId(final ForkedChainId forkedChainId) {
            this.forkedChainId = forkedChainId;
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "TempChain{tempForkedChainData=" + String.valueOf(this.tempForkedChainData) + ", tempSyncData=" + String.valueOf(this.tempSyncData) + ", clientState=" + String.valueOf(this.clientState);
        }
    }
    
    record CallState(RootInteraction rootInteraction, int operationCounter) {}
}
