import {
  FoxgloveMessageSchema,
  foxgloveEnumSchemas,
  generateProto,
} from "@foxglove/schemas/internal";
import { McapWriter } from "@mcap/core";
import protobufjs from "protobufjs";
import descriptor from "protobufjs/ext/descriptor";

export type ProtobufChannelInfo = {
  id: number;
  rootType: protobufjs.Type;
};
export async function addProtobufChannel(
  writer: McapWriter,
  topic: string,
  rootSchema: FoxgloveMessageSchema,
): Promise<ProtobufChannelInfo> {
  const schemaName = `foxglove.${rootSchema.name}`;

  const root = new protobufjs.Root();
  root.addJSON(
    protobufjs.common.get("google/protobuf/timestamp.proto")!.nested!,
  );
  root.addJSON(
    protobufjs.common.get("google/protobuf/duration.proto")!.nested!,
  );

  function addMessageSchema(msgSchema: FoxgloveMessageSchema) {
    const nestedEnums = Object.values(foxgloveEnumSchemas).filter(
      (enumSchema) => enumSchema.parentSchemaName === msgSchema.name,
    );
    const protoSrc = generateProto(msgSchema, nestedEnums);
    const parseResult = protobufjs.parse(protoSrc, { keepCase: true });
    const filename = `foxglove/${msgSchema.name}.proto`;
    parseResult.root.nestedArray[0]!.filename = filename;
    root.add(parseResult.root.nestedArray[0]!);

    for (const field of msgSchema.fields) {
      if (field.type.type === "nested") {
        addMessageSchema(field.type.schema);
      }
    }
  }
  addMessageSchema(rootSchema);

  const rootType = root.lookupType(schemaName);
  const descriptorSet = root.toDescriptor("proto3");
  for (const file of descriptorSet.file) {
    // protobufjs does not generate dependency fields, so fix them up manually
    if (file.name == undefined || file.name.length === 0) {
      throw new Error(
        `Missing filename for ${file.package ?? "(unknown package)"}`,
      );
    }
    if (file.name !== "google_protobuf.proto") {
      // default filename generated by toDescriptor
      file.dependency = ["google_protobuf.proto"];
    }
  }

  const schemaId = await writer.registerSchema({
    name: schemaName,
    encoding: "protobuf",
    data: descriptor.FileDescriptorSet.encode(descriptorSet).finish(),
  });
  const id = await writer.registerChannel({
    topic,
    messageEncoding: "protobuf",
    schemaId,
    metadata: new Map(),
  });

  return { rootType, id };
}
