////////////////////////////////////////////////////////////////////////////
//
// Copyright 2022 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

import { RelaxedSpec } from "./relaxed-model";
export type { MixedInfo } from "./relaxed-model";

export type ValueType = string;

type ReplaceFields<Base, Replacements> = Omit<Base, keyof Replacements> & Replacements;

export type Spec = ReplaceFields<
  Required<RelaxedSpec>,
  {
    typeAliases: { [name: string]: TypeSpec };
    enums: { [name: string]: EnumSpec };
    records: { [name: string]: RecordSpec };
    classes: { [name: string]: ClassSpec };
    keyTypes: { [name: string]: TypeSpec };
  }
>;

/**
 * @example
 * // Spec/yaml file example:
 * records:
 *   Property:
 *     fields:
 *       - name
 *       - public_name
 *       - type
 *   ObjectSchema:
 *     fields:
 *       - name
 *       - persisted_properties
 *       - computed_properties
 *
 * classes:
 *   Results:
 *     methods:
 *       - from_table
 *       - is_valid
 *       - sort_by_names
 *   Realm:
 *     methods:
 *       - get_shared_realm
 *       - config
 *       - begin_transaction
 */
export type OptInSpec = {
  classes?: {
    [name: string]: { methods: string[] };
  };
  records?: {
    [name: string]: { fields: string[] };
  };
};

type AdditionalSpec = ReplaceFields<
  Spec,
  {
    mixedInfo?: undefined;
  }
>;

export type AnySpec = Spec | AdditionalSpec;

export type EnumSpec = {
  cppName?: string;
  values: { [key: string]: number };
};

export type RecordSpec = {
  cppName?: string;
  fields: { [name: string]: FieldSpec };
};

export type FieldSpec = {
  type: TypeSpec;
  default?: ValueType;
  cppName?: string;
};

export type MethodSpec = {
  sig: FunctionTypeSpec;
  suffix?: string;
  cppName?: string;
};

export type ClassSpec = {
  cppName?: string;
  iterable?: TypeSpec;
  abstract: boolean;
  base?: string;
  needsDeref: boolean;
  sharedPtrWrapped?: string;
  constructors: { [name: string]: FunctionTypeSpec };
  staticMethods: { [name: string]: MethodSpec[] };
  properties: { [name: string]: TypeSpec };
  methods: { [name: string]: MethodSpec[] };
};

export type TypeSpec = TypeNameSpec | TemplateInstanceSpec | FunctionTypeSpec | TypeModifierSpec;

export type TypeModifierSpec = {
  kind: "const" | "ref" | "rref" | "pointer";
  type: TypeSpec;
};

export type TypeNameSpec = {
  kind: "type-name";
  name: string;
};

export type TemplateInstanceSpec = {
  kind: "template-instance";
  name: string;
  templateArguments: TypeSpec[];
};

export type FunctionTypeSpec = {
  kind: "function";
  args: ArgumentSpec[];
  ret: TypeSpec;
  isConst: boolean;
  isNoExcept: boolean;
  isOffThread: boolean;
};

export type ArgumentSpec = {
  name: string;
  type: TypeSpec;
};
