/* eslint-disable */

export interface Message<TypeName extends string = string> {
  readonly $typeName: TypeName;
}

export type MessageInit<T extends Message> = Partial<Omit<T, "$typeName">>;

export interface MessageType<T extends Message = Message> {
  readonly typeName: T["$typeName"];
  create(value?: MessageInit<T>): T;
  fromBinary(data: Uint8Array): T;
  toBinary(message: T): Uint8Array;
}

export const file_message_common = {
  name: "message_common.proto",
  messages: ["PacketBase", "HeartBeat", "TestData"],
} as const;

export type PacketBase = Message<"proto_socket.PacketBase"> & {
  typeName: string;
  nonce: number;
  data: Uint8Array;
  responseNonce: number;
};

export type HeartBeat = Message<"proto_socket.HeartBeat">;

export type TestData = Message<"proto_socket.TestData"> & {
  index: number;
  message: string;
};

export const PacketBaseSchema: MessageType<PacketBase> = {
  typeName: "proto_socket.PacketBase",
  create(value = {}) {
    return {
      $typeName: "proto_socket.PacketBase",
      typeName: value.typeName ?? "",
      nonce: value.nonce ?? 0,
      data: cloneBytes(value.data),
      responseNonce: value.responseNonce ?? 0,
    };
  },
  fromBinary(data) {
    return decodePacketBase(data);
  },
  toBinary(message) {
    return encodePacketBase(message);
  },
};

export const HeartBeatSchema: MessageType<HeartBeat> = {
  typeName: "proto_socket.HeartBeat",
  create() {
    return { $typeName: "proto_socket.HeartBeat" };
  },
  fromBinary(data) {
    skipUnknownMessage(data);
    return this.create();
  },
  toBinary() {
    return new Uint8Array();
  },
};

export const TestDataSchema: MessageType<TestData> = {
  typeName: "proto_socket.TestData",
  create(value = {}) {
    return {
      $typeName: "proto_socket.TestData",
      index: value.index ?? 0,
      message: value.message ?? "",
    };
  },
  fromBinary(data) {
    return decodeTestData(data);
  },
  toBinary(message) {
    return encodeTestData(message);
  },
};

export function create<T extends Message>(schema: MessageType<T>, value?: MessageInit<T>): T {
  return schema.create(value);
}

export function fromBinary<T extends Message>(schema: MessageType<T>, data: Uint8Array): T {
  return schema.fromBinary(data);
}

export function toBinary<T extends Message>(schema: MessageType<T>, message: T): Uint8Array {
  return schema.toBinary(message);
}

function encodePacketBase(message: PacketBase): Uint8Array {
  const chunks: Uint8Array[] = [];
  if (message.typeName !== "") {
    writeString(chunks, 1, message.typeName);
  }
  if (message.nonce !== 0) {
    writeInt32(chunks, 2, message.nonce);
  }
  if (message.data.byteLength > 0) {
    writeBytes(chunks, 3, message.data);
  }
  if (message.responseNonce !== 0) {
    writeInt32(chunks, 4, message.responseNonce);
  }
  return concat(chunks);
}

function decodePacketBase(data: Uint8Array): PacketBase {
  const reader = new BinaryReader(data);
  const message = PacketBaseSchema.create();

  while (!reader.done()) {
    const tag = reader.readVarintNumber();
    const fieldNumber = tag >>> 3;
    const wireType = tag & 7;
    switch (fieldNumber) {
      case 1:
        assertWireType(wireType, 2);
        message.typeName = reader.readString();
        break;
      case 2:
        assertWireType(wireType, 0);
        message.nonce = reader.readInt32();
        break;
      case 3:
        assertWireType(wireType, 2);
        message.data = reader.readBytes();
        break;
      case 4:
        assertWireType(wireType, 0);
        message.responseNonce = reader.readInt32();
        break;
      default:
        reader.skip(wireType);
        break;
    }
  }

  return message;
}

function encodeTestData(message: TestData): Uint8Array {
  const chunks: Uint8Array[] = [];
  if (message.index !== 0) {
    writeInt32(chunks, 1, message.index);
  }
  if (message.message !== "") {
    writeString(chunks, 2, message.message);
  }
  return concat(chunks);
}

function decodeTestData(data: Uint8Array): TestData {
  const reader = new BinaryReader(data);
  const message = TestDataSchema.create();

  while (!reader.done()) {
    const tag = reader.readVarintNumber();
    const fieldNumber = tag >>> 3;
    const wireType = tag & 7;
    switch (fieldNumber) {
      case 1:
        assertWireType(wireType, 0);
        message.index = reader.readInt32();
        break;
      case 2:
        assertWireType(wireType, 2);
        message.message = reader.readString();
        break;
      default:
        reader.skip(wireType);
        break;
    }
  }

  return message;
}

function skipUnknownMessage(data: Uint8Array): void {
  const reader = new BinaryReader(data);
  while (!reader.done()) {
    const tag = reader.readVarintNumber();
    reader.skip(tag & 7);
  }
}

function writeInt32(chunks: Uint8Array[], fieldNumber: number, value: number): void {
  chunks.push(encodeTag(fieldNumber, 0));
  chunks.push(encodeInt32(value));
}

function writeString(chunks: Uint8Array[], fieldNumber: number, value: string): void {
  writeLengthDelimited(chunks, fieldNumber, textEncoder.encode(value));
}

function writeBytes(chunks: Uint8Array[], fieldNumber: number, value: Uint8Array): void {
  writeLengthDelimited(chunks, fieldNumber, value);
}

function writeLengthDelimited(chunks: Uint8Array[], fieldNumber: number, value: Uint8Array): void {
  chunks.push(encodeTag(fieldNumber, 2));
  chunks.push(encodeVarint(BigInt(value.byteLength)));
  chunks.push(value);
}

function encodeTag(fieldNumber: number, wireType: number): Uint8Array {
  return encodeVarint(BigInt((fieldNumber << 3) | wireType));
}

function encodeInt32(value: number): Uint8Array {
  if (!Number.isInteger(value) || value < -2147483648 || value > 2147483647) {
    throw new Error(`int32 out of range: ${value}`);
  }
  const normalized = value | 0;
  const varint = normalized < 0 ? BigInt.asUintN(64, BigInt(normalized)) : BigInt(normalized);
  return encodeVarint(varint);
}

function encodeVarint(value: bigint): Uint8Array {
  if (value < 0n) {
    throw new Error("varint cannot be negative");
  }
  const bytes: number[] = [];
  let current = value;
  while (current > 0x7fn) {
    bytes.push(Number((current & 0x7fn) | 0x80n));
    current >>= 7n;
  }
  bytes.push(Number(current));
  return new Uint8Array(bytes);
}

class BinaryReader {
  private offset = 0;

  constructor(private readonly data: Uint8Array) {}

  done(): boolean {
    return this.offset >= this.data.byteLength;
  }

  readInt32(): number {
    return Number(BigInt.asIntN(32, this.readVarintBig()));
  }

  readVarintNumber(): number {
    const value = this.readVarintBig();
    if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
      throw new Error("varint exceeds safe integer range");
    }
    return Number(value);
  }

  readString(): string {
    return textDecoder.decode(this.readBytes());
  }

  readBytes(): Uint8Array {
    const length = this.readVarintNumber();
    this.require(length);
    const bytes = this.data.subarray(this.offset, this.offset + length);
    this.offset += length;
    return new Uint8Array(bytes);
  }

  skip(wireType: number): void {
    switch (wireType) {
      case 0:
        this.readVarintBig();
        return;
      case 1:
        this.require(8);
        this.offset += 8;
        return;
      case 2: {
        const length = this.readVarintNumber();
        this.require(length);
        this.offset += length;
        return;
      }
      case 5:
        this.require(4);
        this.offset += 4;
        return;
      default:
        throw new Error(`unsupported protobuf wire type: ${wireType}`);
    }
  }

  private readVarintBig(): bigint {
    let shift = 0n;
    let result = 0n;

    for (let i = 0; i < 10; i += 1) {
      this.require(1);
      const byte = this.data[this.offset]!;
      this.offset += 1;
      result |= BigInt(byte & 0x7f) << shift;
      if ((byte & 0x80) === 0) {
        return result;
      }
      shift += 7n;
    }

    throw new Error("invalid protobuf varint");
  }

  private require(length: number): void {
    if (length < 0 || this.offset + length > this.data.byteLength) {
      throw new Error("truncated protobuf message");
    }
  }
}

function assertWireType(actual: number, expected: number): void {
  if (actual !== expected) {
    throw new Error(`unexpected protobuf wire type ${actual}, expected ${expected}`);
  }
}

function concat(chunks: Uint8Array[]): Uint8Array {
  const length = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
  const result = new Uint8Array(length);
  let offset = 0;
  for (const chunk of chunks) {
    result.set(chunk, offset);
    offset += chunk.byteLength;
  }
  return result;
}

function cloneBytes(value: Uint8Array | undefined): Uint8Array {
  return value === undefined ? new Uint8Array() : new Uint8Array(value);
}

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
