const ROOT_PROPS = [
  'options',
  'parsedOptions',
  'name',
  'parent',
  'resolved',
  'comment',
  'filename',
  'nested',
  '_nestedArray',
];

/**
 * Create a version of `root` with non-nested namespaces to match the generated types.
 * For more information, see:
 * https://github.com/temporalio/sdk-typescript/blob/main/docs/protobuf-libraries.md#current-solution
 * @param root Generated by `pbjs -t json-module -w commonjs -o json-module.js *.proto`
 * @returns A new patched `root`
 */
export function patchProtobufRoot<T extends Record<string, unknown>>(root: T): T {
  return _patchProtobufRoot(root);
}

function _patchProtobufRoot<T extends Record<string, unknown>>(root: T, name?: string): T {
  const newRoot = new (root.constructor as any)(isNamespace(root) ? name : {});
  for (const key in root) {
    newRoot[key] = root[key];
  }

  if (isRecord(root.nested)) {
    for (const typeOrNamespace in root.nested) {
      const value = root.nested[typeOrNamespace];
      if (ROOT_PROPS.includes(typeOrNamespace)) {
        console.log(
          `patchProtobufRoot warning: overriding property '${typeOrNamespace}' that is used by protobufjs with the '${typeOrNamespace}' protobuf ${
            isNamespace(value) ? 'namespace' : 'type'
          }. This may result in protobufjs not working property.`
        );
      }

      if (isNamespace(value)) {
        newRoot[typeOrNamespace] = _patchProtobufRoot(value, typeOrNamespace);
      } else if (isType(value)) {
        newRoot[typeOrNamespace] = value;
      }
    }
  }

  return newRoot;
}

type Type = Record<string, unknown>;
type Namespace = { nested: Record<string, unknown> };

function isType(value: unknown): value is Type {
  // constructor.name may get mangled by minifiers; thanksfuly protobufjs also sets a constructor.className property
  return isRecord(value) && (value.constructor as any).className === 'Type';
}

function isNamespace(value: unknown): value is Namespace {
  // constructor.name may get mangled by minifiers; thanksfuly protobufjs also sets a constructor.className property
  return isRecord(value) && (value.constructor as any).className === 'Namespace';
}

// Duplicate from type-helpers instead of importing in order to avoid circular dependency
function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null;
}
