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

package com.hypixel.hytale.protocol;

import java.util.Objects;
import java.util.Arrays;
import com.hypixel.hytale.protocol.io.ValidationResult;
import java.util.Iterator;
import java.util.HashMap;
import com.hypixel.hytale.protocol.io.PacketIO;
import com.hypixel.hytale.protocol.io.ProtocolException;
import com.hypixel.hytale.protocol.io.VarInt;
import io.netty.buffer.ByteBuf;
import javax.annotation.Nonnull;
import java.util.Map;
import javax.annotation.Nullable;

public class FormattedMessage
{
    public static final int NULLABLE_BIT_FIELD_SIZE = 1;
    public static final int FIXED_BLOCK_SIZE = 6;
    public static final int VARIABLE_FIELD_COUNT = 7;
    public static final int VARIABLE_BLOCK_START = 34;
    public static final int MAX_SIZE = 1677721600;
    @Nullable
    public String rawText;
    @Nullable
    public String messageId;
    @Nullable
    public FormattedMessage[] children;
    @Nullable
    public Map<String, ParamValue> params;
    @Nullable
    public Map<String, FormattedMessage> messageParams;
    @Nullable
    public String color;
    @Nonnull
    public MaybeBool bold;
    @Nonnull
    public MaybeBool italic;
    @Nonnull
    public MaybeBool monospace;
    @Nonnull
    public MaybeBool underlined;
    @Nullable
    public String link;
    public boolean markupEnabled;
    
    public FormattedMessage() {
        this.bold = MaybeBool.Null;
        this.italic = MaybeBool.Null;
        this.monospace = MaybeBool.Null;
        this.underlined = MaybeBool.Null;
    }
    
    public FormattedMessage(@Nullable final String rawText, @Nullable final String messageId, @Nullable final FormattedMessage[] children, @Nullable final Map<String, ParamValue> params, @Nullable final Map<String, FormattedMessage> messageParams, @Nullable final String color, @Nonnull final MaybeBool bold, @Nonnull final MaybeBool italic, @Nonnull final MaybeBool monospace, @Nonnull final MaybeBool underlined, @Nullable final String link, final boolean markupEnabled) {
        this.bold = MaybeBool.Null;
        this.italic = MaybeBool.Null;
        this.monospace = MaybeBool.Null;
        this.underlined = MaybeBool.Null;
        this.rawText = rawText;
        this.messageId = messageId;
        this.children = children;
        this.params = params;
        this.messageParams = messageParams;
        this.color = color;
        this.bold = bold;
        this.italic = italic;
        this.monospace = monospace;
        this.underlined = underlined;
        this.link = link;
        this.markupEnabled = markupEnabled;
    }
    
    public FormattedMessage(@Nonnull final FormattedMessage other) {
        this.bold = MaybeBool.Null;
        this.italic = MaybeBool.Null;
        this.monospace = MaybeBool.Null;
        this.underlined = MaybeBool.Null;
        this.rawText = other.rawText;
        this.messageId = other.messageId;
        this.children = other.children;
        this.params = other.params;
        this.messageParams = other.messageParams;
        this.color = other.color;
        this.bold = other.bold;
        this.italic = other.italic;
        this.monospace = other.monospace;
        this.underlined = other.underlined;
        this.link = other.link;
        this.markupEnabled = other.markupEnabled;
    }
    
    @Nonnull
    public static FormattedMessage deserialize(@Nonnull final ByteBuf buf, final int offset) {
        final FormattedMessage obj = new FormattedMessage();
        final byte nullBits = buf.getByte(offset);
        obj.bold = MaybeBool.fromValue(buf.getByte(offset + 1));
        obj.italic = MaybeBool.fromValue(buf.getByte(offset + 2));
        obj.monospace = MaybeBool.fromValue(buf.getByte(offset + 3));
        obj.underlined = MaybeBool.fromValue(buf.getByte(offset + 4));
        obj.markupEnabled = (buf.getByte(offset + 5) != 0);
        if ((nullBits & 0x1) != 0x0) {
            final int varPos0 = offset + 34 + buf.getIntLE(offset + 6);
            final int rawTextLen = VarInt.peek(buf, varPos0);
            if (rawTextLen < 0) {
                throw ProtocolException.negativeLength("RawText", rawTextLen);
            }
            if (rawTextLen > 4096000) {
                throw ProtocolException.stringTooLong("RawText", rawTextLen, 4096000);
            }
            obj.rawText = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8);
        }
        if ((nullBits & 0x2) != 0x0) {
            final int varPos2 = offset + 34 + buf.getIntLE(offset + 10);
            final int messageIdLen = VarInt.peek(buf, varPos2);
            if (messageIdLen < 0) {
                throw ProtocolException.negativeLength("MessageId", messageIdLen);
            }
            if (messageIdLen > 4096000) {
                throw ProtocolException.stringTooLong("MessageId", messageIdLen, 4096000);
            }
            obj.messageId = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8);
        }
        if ((nullBits & 0x4) != 0x0) {
            final int varPos3 = offset + 34 + buf.getIntLE(offset + 14);
            final int childrenCount = VarInt.peek(buf, varPos3);
            if (childrenCount < 0) {
                throw ProtocolException.negativeLength("Children", childrenCount);
            }
            if (childrenCount > 4096000) {
                throw ProtocolException.arrayTooLong("Children", childrenCount, 4096000);
            }
            final int varIntLen = VarInt.length(buf, varPos3);
            if (varPos3 + varIntLen + childrenCount * 6L > buf.readableBytes()) {
                throw ProtocolException.bufferTooSmall("Children", varPos3 + varIntLen + childrenCount * 6, buf.readableBytes());
            }
            obj.children = new FormattedMessage[childrenCount];
            int elemPos = varPos3 + varIntLen;
            for (int i = 0; i < childrenCount; ++i) {
                obj.children[i] = deserialize(buf, elemPos);
                elemPos += computeBytesConsumed(buf, elemPos);
            }
        }
        if ((nullBits & 0x8) != 0x0) {
            final int varPos4 = offset + 34 + buf.getIntLE(offset + 18);
            final int paramsCount = VarInt.peek(buf, varPos4);
            if (paramsCount < 0) {
                throw ProtocolException.negativeLength("Params", paramsCount);
            }
            if (paramsCount > 4096000) {
                throw ProtocolException.dictionaryTooLarge("Params", paramsCount, 4096000);
            }
            final int varIntLen = VarInt.length(buf, varPos4);
            obj.params = new HashMap<String, ParamValue>(paramsCount);
            int dictPos = varPos4 + varIntLen;
            for (int i = 0; i < paramsCount; ++i) {
                final int keyLen = VarInt.peek(buf, dictPos);
                if (keyLen < 0) {
                    throw ProtocolException.negativeLength("key", keyLen);
                }
                if (keyLen > 4096000) {
                    throw ProtocolException.stringTooLong("key", keyLen, 4096000);
                }
                final int keyVarLen = VarInt.length(buf, dictPos);
                final String key = PacketIO.readVarString(buf, dictPos);
                dictPos += keyVarLen + keyLen;
                final ParamValue val = ParamValue.deserialize(buf, dictPos);
                dictPos += ParamValue.computeBytesConsumed(buf, dictPos);
                if (obj.params.put(key, val) != null) {
                    throw ProtocolException.duplicateKey("params", key);
                }
            }
        }
        if ((nullBits & 0x10) != 0x0) {
            final int varPos5 = offset + 34 + buf.getIntLE(offset + 22);
            final int messageParamsCount = VarInt.peek(buf, varPos5);
            if (messageParamsCount < 0) {
                throw ProtocolException.negativeLength("MessageParams", messageParamsCount);
            }
            if (messageParamsCount > 4096000) {
                throw ProtocolException.dictionaryTooLarge("MessageParams", messageParamsCount, 4096000);
            }
            final int varIntLen = VarInt.length(buf, varPos5);
            obj.messageParams = new HashMap<String, FormattedMessage>(messageParamsCount);
            int dictPos = varPos5 + varIntLen;
            for (int i = 0; i < messageParamsCount; ++i) {
                final int keyLen = VarInt.peek(buf, dictPos);
                if (keyLen < 0) {
                    throw ProtocolException.negativeLength("key", keyLen);
                }
                if (keyLen > 4096000) {
                    throw ProtocolException.stringTooLong("key", keyLen, 4096000);
                }
                final int keyVarLen = VarInt.length(buf, dictPos);
                final String key = PacketIO.readVarString(buf, dictPos);
                dictPos += keyVarLen + keyLen;
                final FormattedMessage val2 = deserialize(buf, dictPos);
                dictPos += computeBytesConsumed(buf, dictPos);
                if (obj.messageParams.put(key, val2) != null) {
                    throw ProtocolException.duplicateKey("messageParams", key);
                }
            }
        }
        if ((nullBits & 0x20) != 0x0) {
            final int varPos6 = offset + 34 + buf.getIntLE(offset + 26);
            final int colorLen = VarInt.peek(buf, varPos6);
            if (colorLen < 0) {
                throw ProtocolException.negativeLength("Color", colorLen);
            }
            if (colorLen > 4096000) {
                throw ProtocolException.stringTooLong("Color", colorLen, 4096000);
            }
            obj.color = PacketIO.readVarString(buf, varPos6, PacketIO.UTF8);
        }
        if ((nullBits & 0x40) != 0x0) {
            final int varPos7 = offset + 34 + buf.getIntLE(offset + 30);
            final int linkLen = VarInt.peek(buf, varPos7);
            if (linkLen < 0) {
                throw ProtocolException.negativeLength("Link", linkLen);
            }
            if (linkLen > 4096000) {
                throw ProtocolException.stringTooLong("Link", linkLen, 4096000);
            }
            obj.link = PacketIO.readVarString(buf, varPos7, PacketIO.UTF8);
        }
        return obj;
    }
    
    public static int computeBytesConsumed(@Nonnull final ByteBuf buf, final int offset) {
        final byte nullBits = buf.getByte(offset);
        int maxEnd = 34;
        if ((nullBits & 0x1) != 0x0) {
            final int fieldOffset0 = buf.getIntLE(offset + 6);
            int pos0 = offset + 34 + fieldOffset0;
            final int sl = VarInt.peek(buf, pos0);
            pos0 += VarInt.length(buf, pos0) + sl;
            if (pos0 - offset > maxEnd) {
                maxEnd = pos0 - offset;
            }
        }
        if ((nullBits & 0x2) != 0x0) {
            final int fieldOffset2 = buf.getIntLE(offset + 10);
            int pos2 = offset + 34 + fieldOffset2;
            final int sl = VarInt.peek(buf, pos2);
            pos2 += VarInt.length(buf, pos2) + sl;
            if (pos2 - offset > maxEnd) {
                maxEnd = pos2 - offset;
            }
        }
        if ((nullBits & 0x4) != 0x0) {
            final int fieldOffset3 = buf.getIntLE(offset + 14);
            int pos3 = offset + 34 + fieldOffset3;
            final int arrLen = VarInt.peek(buf, pos3);
            pos3 += VarInt.length(buf, pos3);
            for (int i = 0; i < arrLen; ++i) {
                pos3 += computeBytesConsumed(buf, pos3);
            }
            if (pos3 - offset > maxEnd) {
                maxEnd = pos3 - offset;
            }
        }
        if ((nullBits & 0x8) != 0x0) {
            final int fieldOffset4 = buf.getIntLE(offset + 18);
            int pos4 = offset + 34 + fieldOffset4;
            final int dictLen = VarInt.peek(buf, pos4);
            pos4 += VarInt.length(buf, pos4);
            for (int i = 0; i < dictLen; ++i) {
                final int sl2 = VarInt.peek(buf, pos4);
                pos4 += VarInt.length(buf, pos4) + sl2;
                pos4 += ParamValue.computeBytesConsumed(buf, pos4);
            }
            if (pos4 - offset > maxEnd) {
                maxEnd = pos4 - offset;
            }
        }
        if ((nullBits & 0x10) != 0x0) {
            final int fieldOffset5 = buf.getIntLE(offset + 22);
            int pos5 = offset + 34 + fieldOffset5;
            final int dictLen = VarInt.peek(buf, pos5);
            pos5 += VarInt.length(buf, pos5);
            for (int i = 0; i < dictLen; ++i) {
                final int sl2 = VarInt.peek(buf, pos5);
                pos5 += VarInt.length(buf, pos5) + sl2;
                pos5 += computeBytesConsumed(buf, pos5);
            }
            if (pos5 - offset > maxEnd) {
                maxEnd = pos5 - offset;
            }
        }
        if ((nullBits & 0x20) != 0x0) {
            final int fieldOffset6 = buf.getIntLE(offset + 26);
            int pos6 = offset + 34 + fieldOffset6;
            final int sl = VarInt.peek(buf, pos6);
            pos6 += VarInt.length(buf, pos6) + sl;
            if (pos6 - offset > maxEnd) {
                maxEnd = pos6 - offset;
            }
        }
        if ((nullBits & 0x40) != 0x0) {
            final int fieldOffset7 = buf.getIntLE(offset + 30);
            int pos7 = offset + 34 + fieldOffset7;
            final int sl = VarInt.peek(buf, pos7);
            pos7 += VarInt.length(buf, pos7) + sl;
            if (pos7 - offset > maxEnd) {
                maxEnd = pos7 - offset;
            }
        }
        return maxEnd;
    }
    
    public void serialize(@Nonnull final ByteBuf buf) {
        final int startPos = buf.writerIndex();
        byte nullBits = 0;
        if (this.rawText != null) {
            nullBits |= 0x1;
        }
        if (this.messageId != null) {
            nullBits |= 0x2;
        }
        if (this.children != null) {
            nullBits |= 0x4;
        }
        if (this.params != null) {
            nullBits |= 0x8;
        }
        if (this.messageParams != null) {
            nullBits |= 0x10;
        }
        if (this.color != null) {
            nullBits |= 0x20;
        }
        if (this.link != null) {
            nullBits |= 0x40;
        }
        buf.writeByte(nullBits);
        buf.writeByte(this.bold.getValue());
        buf.writeByte(this.italic.getValue());
        buf.writeByte(this.monospace.getValue());
        buf.writeByte(this.underlined.getValue());
        buf.writeByte(this.markupEnabled ? 1 : 0);
        final int rawTextOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int messageIdOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int childrenOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int paramsOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int messageParamsOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int colorOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int linkOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int varBlockStart = buf.writerIndex();
        if (this.rawText != null) {
            buf.setIntLE(rawTextOffsetSlot, buf.writerIndex() - varBlockStart);
            PacketIO.writeVarString(buf, this.rawText, 4096000);
        }
        else {
            buf.setIntLE(rawTextOffsetSlot, -1);
        }
        if (this.messageId != null) {
            buf.setIntLE(messageIdOffsetSlot, buf.writerIndex() - varBlockStart);
            PacketIO.writeVarString(buf, this.messageId, 4096000);
        }
        else {
            buf.setIntLE(messageIdOffsetSlot, -1);
        }
        if (this.children != null) {
            buf.setIntLE(childrenOffsetSlot, buf.writerIndex() - varBlockStart);
            if (this.children.length > 4096000) {
                throw ProtocolException.arrayTooLong("Children", this.children.length, 4096000);
            }
            VarInt.write(buf, this.children.length);
            for (final FormattedMessage item : this.children) {
                item.serialize(buf);
            }
        }
        else {
            buf.setIntLE(childrenOffsetSlot, -1);
        }
        if (this.params != null) {
            buf.setIntLE(paramsOffsetSlot, buf.writerIndex() - varBlockStart);
            if (this.params.size() > 4096000) {
                throw ProtocolException.dictionaryTooLarge("Params", this.params.size(), 4096000);
            }
            VarInt.write(buf, this.params.size());
            for (final Map.Entry<String, ParamValue> e : this.params.entrySet()) {
                PacketIO.writeVarString(buf, e.getKey(), 4096000);
                e.getValue().serializeWithTypeId(buf);
            }
        }
        else {
            buf.setIntLE(paramsOffsetSlot, -1);
        }
        if (this.messageParams != null) {
            buf.setIntLE(messageParamsOffsetSlot, buf.writerIndex() - varBlockStart);
            if (this.messageParams.size() > 4096000) {
                throw ProtocolException.dictionaryTooLarge("MessageParams", this.messageParams.size(), 4096000);
            }
            VarInt.write(buf, this.messageParams.size());
            for (final Map.Entry<String, FormattedMessage> e2 : this.messageParams.entrySet()) {
                PacketIO.writeVarString(buf, e2.getKey(), 4096000);
                e2.getValue().serialize(buf);
            }
        }
        else {
            buf.setIntLE(messageParamsOffsetSlot, -1);
        }
        if (this.color != null) {
            buf.setIntLE(colorOffsetSlot, buf.writerIndex() - varBlockStart);
            PacketIO.writeVarString(buf, this.color, 4096000);
        }
        else {
            buf.setIntLE(colorOffsetSlot, -1);
        }
        if (this.link != null) {
            buf.setIntLE(linkOffsetSlot, buf.writerIndex() - varBlockStart);
            PacketIO.writeVarString(buf, this.link, 4096000);
        }
        else {
            buf.setIntLE(linkOffsetSlot, -1);
        }
    }
    
    public int computeSize() {
        int size = 34;
        if (this.rawText != null) {
            size += PacketIO.stringSize(this.rawText);
        }
        if (this.messageId != null) {
            size += PacketIO.stringSize(this.messageId);
        }
        if (this.children != null) {
            int childrenSize = 0;
            for (final FormattedMessage elem : this.children) {
                childrenSize += elem.computeSize();
            }
            size += VarInt.size(this.children.length) + childrenSize;
        }
        if (this.params != null) {
            int paramsSize = 0;
            for (final Map.Entry<String, ParamValue> kvp : this.params.entrySet()) {
                paramsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSizeWithTypeId();
            }
            size += VarInt.size(this.params.size()) + paramsSize;
        }
        if (this.messageParams != null) {
            int messageParamsSize = 0;
            for (final Map.Entry<String, FormattedMessage> kvp2 : this.messageParams.entrySet()) {
                messageParamsSize += PacketIO.stringSize(kvp2.getKey()) + kvp2.getValue().computeSize();
            }
            size += VarInt.size(this.messageParams.size()) + messageParamsSize;
        }
        if (this.color != null) {
            size += PacketIO.stringSize(this.color);
        }
        if (this.link != null) {
            size += PacketIO.stringSize(this.link);
        }
        return size;
    }
    
    public static ValidationResult validateStructure(@Nonnull final ByteBuf buffer, final int offset) {
        if (buffer.readableBytes() - offset < 34) {
            return ValidationResult.error("Buffer too small: expected at least 34 bytes");
        }
        final byte nullBits = buffer.getByte(offset);
        if ((nullBits & 0x1) != 0x0) {
            final int rawTextOffset = buffer.getIntLE(offset + 6);
            if (rawTextOffset < 0) {
                return ValidationResult.error("Invalid offset for RawText");
            }
            int pos = offset + 34 + rawTextOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for RawText");
            }
            final int rawTextLen = VarInt.peek(buffer, pos);
            if (rawTextLen < 0) {
                return ValidationResult.error("Invalid string length for RawText");
            }
            if (rawTextLen > 4096000) {
                return ValidationResult.error("RawText exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            pos += rawTextLen;
            if (pos > buffer.writerIndex()) {
                return ValidationResult.error("Buffer overflow reading RawText");
            }
        }
        if ((nullBits & 0x2) != 0x0) {
            final int messageIdOffset = buffer.getIntLE(offset + 10);
            if (messageIdOffset < 0) {
                return ValidationResult.error("Invalid offset for MessageId");
            }
            int pos = offset + 34 + messageIdOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for MessageId");
            }
            final int messageIdLen = VarInt.peek(buffer, pos);
            if (messageIdLen < 0) {
                return ValidationResult.error("Invalid string length for MessageId");
            }
            if (messageIdLen > 4096000) {
                return ValidationResult.error("MessageId exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            pos += messageIdLen;
            if (pos > buffer.writerIndex()) {
                return ValidationResult.error("Buffer overflow reading MessageId");
            }
        }
        if ((nullBits & 0x4) != 0x0) {
            final int childrenOffset = buffer.getIntLE(offset + 14);
            if (childrenOffset < 0) {
                return ValidationResult.error("Invalid offset for Children");
            }
            int pos = offset + 34 + childrenOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for Children");
            }
            final int childrenCount = VarInt.peek(buffer, pos);
            if (childrenCount < 0) {
                return ValidationResult.error("Invalid array count for Children");
            }
            if (childrenCount > 4096000) {
                return ValidationResult.error("Children exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            for (int i = 0; i < childrenCount; ++i) {
                final ValidationResult structResult = validateStructure(buffer, pos);
                if (!structResult.isValid()) {
                    return ValidationResult.error("Invalid FormattedMessage in Children[" + i + "]: " + structResult.error());
                }
                pos += computeBytesConsumed(buffer, pos);
            }
        }
        if ((nullBits & 0x8) != 0x0) {
            final int paramsOffset = buffer.getIntLE(offset + 18);
            if (paramsOffset < 0) {
                return ValidationResult.error("Invalid offset for Params");
            }
            int pos = offset + 34 + paramsOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for Params");
            }
            final int paramsCount = VarInt.peek(buffer, pos);
            if (paramsCount < 0) {
                return ValidationResult.error("Invalid dictionary count for Params");
            }
            if (paramsCount > 4096000) {
                return ValidationResult.error("Params exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            for (int i = 0; i < paramsCount; ++i) {
                final int keyLen = VarInt.peek(buffer, pos);
                if (keyLen < 0) {
                    return ValidationResult.error("Invalid string length for key");
                }
                if (keyLen > 4096000) {
                    return ValidationResult.error("key exceeds max length 4096000");
                }
                pos += VarInt.length(buffer, pos);
                pos += keyLen;
                if (pos > buffer.writerIndex()) {
                    return ValidationResult.error("Buffer overflow reading key");
                }
                pos += ParamValue.computeBytesConsumed(buffer, pos);
            }
        }
        if ((nullBits & 0x10) != 0x0) {
            final int messageParamsOffset = buffer.getIntLE(offset + 22);
            if (messageParamsOffset < 0) {
                return ValidationResult.error("Invalid offset for MessageParams");
            }
            int pos = offset + 34 + messageParamsOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for MessageParams");
            }
            final int messageParamsCount = VarInt.peek(buffer, pos);
            if (messageParamsCount < 0) {
                return ValidationResult.error("Invalid dictionary count for MessageParams");
            }
            if (messageParamsCount > 4096000) {
                return ValidationResult.error("MessageParams exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            for (int i = 0; i < messageParamsCount; ++i) {
                final int keyLen = VarInt.peek(buffer, pos);
                if (keyLen < 0) {
                    return ValidationResult.error("Invalid string length for key");
                }
                if (keyLen > 4096000) {
                    return ValidationResult.error("key exceeds max length 4096000");
                }
                pos += VarInt.length(buffer, pos);
                pos += keyLen;
                if (pos > buffer.writerIndex()) {
                    return ValidationResult.error("Buffer overflow reading key");
                }
                pos += computeBytesConsumed(buffer, pos);
            }
        }
        if ((nullBits & 0x20) != 0x0) {
            final int colorOffset = buffer.getIntLE(offset + 26);
            if (colorOffset < 0) {
                return ValidationResult.error("Invalid offset for Color");
            }
            int pos = offset + 34 + colorOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for Color");
            }
            final int colorLen = VarInt.peek(buffer, pos);
            if (colorLen < 0) {
                return ValidationResult.error("Invalid string length for Color");
            }
            if (colorLen > 4096000) {
                return ValidationResult.error("Color exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            pos += colorLen;
            if (pos > buffer.writerIndex()) {
                return ValidationResult.error("Buffer overflow reading Color");
            }
        }
        if ((nullBits & 0x40) != 0x0) {
            final int linkOffset = buffer.getIntLE(offset + 30);
            if (linkOffset < 0) {
                return ValidationResult.error("Invalid offset for Link");
            }
            int pos = offset + 34 + linkOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for Link");
            }
            final int linkLen = VarInt.peek(buffer, pos);
            if (linkLen < 0) {
                return ValidationResult.error("Invalid string length for Link");
            }
            if (linkLen > 4096000) {
                return ValidationResult.error("Link exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            pos += linkLen;
            if (pos > buffer.writerIndex()) {
                return ValidationResult.error("Buffer overflow reading Link");
            }
        }
        return ValidationResult.OK;
    }
    
    public FormattedMessage clone() {
        final FormattedMessage copy = new FormattedMessage();
        copy.rawText = this.rawText;
        copy.messageId = this.messageId;
        Map.Entry<String, FormattedMessage> e = null;
        copy.children = (FormattedMessage[])((this.children != null) ? ((FormattedMessage[])Arrays.stream(this.children).map(e -> e.clone()).toArray(FormattedMessage[]::new)) : null);
        copy.params = ((this.params != null) ? new HashMap<String, ParamValue>(this.params) : null);
        if (this.messageParams != null) {
            final Map<String, FormattedMessage> m = new HashMap<String, FormattedMessage>();
            final Iterator<Map.Entry<String, FormattedMessage>> iterator = this.messageParams.entrySet().iterator();
            while (iterator.hasNext()) {
                e = iterator.next();
                m.put(e.getKey(), e.getValue().clone());
            }
            copy.messageParams = m;
        }
        copy.color = this.color;
        copy.bold = this.bold;
        copy.italic = this.italic;
        copy.monospace = this.monospace;
        copy.underlined = this.underlined;
        copy.link = this.link;
        copy.markupEnabled = this.markupEnabled;
        return copy;
    }
    
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof final FormattedMessage other) {
            return Objects.equals(this.rawText, other.rawText) && Objects.equals(this.messageId, other.messageId) && Arrays.equals(this.children, other.children) && Objects.equals(this.params, other.params) && Objects.equals(this.messageParams, other.messageParams) && Objects.equals(this.color, other.color) && Objects.equals(this.bold, other.bold) && Objects.equals(this.italic, other.italic) && Objects.equals(this.monospace, other.monospace) && Objects.equals(this.underlined, other.underlined) && Objects.equals(this.link, other.link) && this.markupEnabled == other.markupEnabled;
        }
        return false;
    }
    
    @Override
    public int hashCode() {
        int result = 1;
        result = 31 * result + Objects.hashCode(this.rawText);
        result = 31 * result + Objects.hashCode(this.messageId);
        result = 31 * result + Arrays.hashCode(this.children);
        result = 31 * result + Objects.hashCode(this.params);
        result = 31 * result + Objects.hashCode(this.messageParams);
        result = 31 * result + Objects.hashCode(this.color);
        result = 31 * result + Objects.hashCode(this.bold);
        result = 31 * result + Objects.hashCode(this.italic);
        result = 31 * result + Objects.hashCode(this.monospace);
        result = 31 * result + Objects.hashCode(this.underlined);
        result = 31 * result + Objects.hashCode(this.link);
        result = 31 * result + Boolean.hashCode(this.markupEnabled);
        return result;
    }
}
