import { AmfMixin, findAmfType, getArrayItems } from './AmfMixin.js'
import {
  IAmfAnyShape,
  IAmfApi,
  IAmfArrayNode,
  IAmfArrayShape,
  IAmfAsyncApi,
  IAmfCreativeWork,
  IAmfDataArrangeShape,
  IAmfDataNode,
  IAmfDocumentSourceMaps,
  IAmfDomainElement,
  IAmfEndPoint,
  IAmfExample,
  IAmfFileShape,
  IAmfLicense,
  IAmfNodeShape,
  IAmfObjectNode,
  IAmfOperation,
  IAmfOrganization,
  IAmfParameter,
  IAmfPropertyShape,
  IAmfRecursiveShape,
  IAmfScalarNode,
  IAmfScalarShape,
  IAmfSchemaShape,
  IAmfSecurityRequirement,
  IAmfServer,
  IAmfShape,
  IAmfSynthesizedField,
  IAmfTupleShape,
  IAmfUnionShape,
  IAmfWebApi,
  IAmfXMLSerializer,
  IAmfPayload,
  IAmfTag,
  IAmfCallback,
  IAmfRequest,
  IAmfResponse,
  IAmfTemplatedLink,
  IAmfIriTemplateMapping,
  IAmfParametrizedSecurityScheme,
  IAmfSecurityScheme,
  IAmfSettings,
  IAmfOAuth1Settings,
  IAmfOAuth2Settings,
  IAmfApiKeySettings,
  IAmfHttpSettings,
  IAmfOpenIdConnectSettings,
  IAmfOAuth2Flow,
  IAmfScope,
  IAmfParametrizedDeclaration,
  IAmfParametrizedTrait,
  IAmfParametrizedResourceType,
  IAmfVariableValue,
  IAmfAbstractDeclaration,
} from './definitions/Amf.js'
import {
  IApiAbstractDeclaration,
  IApiAsync,
  IApiBase,
  IApiCallback,
  IApiDocumentation,
  IApiDocumentSourceMaps,
  IApiEndPoint,
  IApiEndPointWithOperationsListItem,
  IApiIriTemplateMapping,
  IApiLicense,
  IApiOperation,
  IApiOperationListItem,
  IApiOrganization,
  IApiParameter,
  IApiParametrizedDeclaration,
  IApiParametrizedResourceType,
  IApiParametrizedSecurityScheme,
  IApiParametrizedTrait,
  IApiPayload,
  IApiRequest,
  IApiResponse,
  IApiSecurityApiKeySettings,
  IApiSecurityHttpSettings,
  IApiSecurityOAuth1Settings,
  IApiSecurityOAuth2Flow,
  IApiSecurityOAuth2Settings,
  IApiSecurityOpenIdConnectSettings,
  IApiSecurityRequirement,
  IApiSecurityScheme,
  IApiSecuritySchemeListItem,
  IApiSecurityScope,
  IApiSecuritySettings,
  IApiSecuritySettingsUnion,
  IApiServer,
  IApiSummary,
  IApiSynthesizedField,
  IApiTag,
  IApiTemplatedLink,
  IApiVariableValue,
  IApiWeb,
  IShapeProcessingOptions,
} from './definitions/Api.js'
import { IApiCustomDomainProperty } from './definitions/Base.js'
import { AmfNamespace as ns } from './definitions/Namespace.js'
import {
  IApiAnyShape,
  IApiArrayNode,
  IApiArrayShape,
  IApiDataArrangeShape,
  IApiDataExample,
  IApiDataNode,
  IApiDataNodeUnion,
  IApiFileShape,
  IApiNodeShape,
  IApiObjectNode,
  IApiPropertyShape,
  IApiRecursiveShape,
  IApiScalarNode,
  IApiScalarShape,
  IApiSchemaShape,
  IApiShape,
  IShapeUnion,
  IApiTupleShape,
  IApiUnionShape,
  IApiXmlSerializer,
} from './definitions/Shapes.js'

/**
 * A class that takes AMF's ld+json model and outputs JavaScript interface of it.
 */
export class AmfSerializer extends AmfMixin(Object) {
  /**
   * @param graph Optional AMF generated graph model.
   */
  constructor(graph?: IAmfDomainElement) {
    super()
    if (graph) {
      this.amf = graph
    }
  }

  /**
   * @param types The list of graph object types. When not defined it returns an empty array.
   * @returns The expanded types.
   */
  readTypes(types: string[], context?: Record<string, string>): string[] {
    let target = types
    if (typeof target === 'string') {
      target = [target]
    }
    if (!Array.isArray(target)) {
      return []
    }
    return target.map((type) => this._expandKey(type, context))
  }

  /**
   * @param object The API to serialize.
   * @returns API summary, without complex objects.
   */
  apiSummary(object: IAmfApi): IApiSummary {
    const context = object['@context']
    const result: IApiSummary = {
      id: object['@id'],
      types: this.readTypes(object['@type'], context),
      customDomainProperties: this.customDomainProperties(object, context),
      sourceMaps: this.sourceMap(object),
      schemes: [],
      accepts: [],
      contentType: [],
      documentations: [],
      tags: [],
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, context)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, context)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const version = this._getValue(object, ns.aml.vocabularies.core.version, context)
    if (version && typeof version === 'string') {
      result.version = version
    }
    const termsOfService = this._getValue(object, ns.aml.vocabularies.core.termsOfService, context)
    if (termsOfService && typeof termsOfService === 'string') {
      result.termsOfService = termsOfService
    }
    const accepts = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.accepts, context) as keyof IAmfApi
    ] as unknown[]
    if (Array.isArray(accepts) && accepts.length) {
      result.accepts = this._getValueArray(object, ns.aml.vocabularies.apiContract.accepts, context) as string[]
    }
    const contentType = object[this._getAmfKey(ns.aml.vocabularies.apiContract.contentType, context) as keyof IAmfApi]
    if (Array.isArray(contentType) && contentType.length) {
      result.contentType = this._getValueArray(object, ns.aml.vocabularies.apiContract.contentType, context) as string[]
    }
    const schemes = object[this._getAmfKey(ns.aml.vocabularies.apiContract.scheme, context) as keyof IAmfApi]
    if (Array.isArray(schemes) && schemes.length) {
      result.schemes = this._getValueArray(object, ns.aml.vocabularies.apiContract.scheme, context) as string[]
    }
    let provider = object[this._getAmfKey(ns.aml.vocabularies.core.provider, context) as keyof IAmfApi] as
      | IAmfOrganization[]
      | IAmfOrganization
    if (Array.isArray(provider)) {
      ;[provider] = provider
    }
    if (provider) {
      result.provider = this.provider(provider)
    }
    let license = object[this._getAmfKey(ns.aml.vocabularies.core.license, context) as keyof IAmfApi] as
      | IAmfLicense
      | IAmfLicense[]
    if (Array.isArray(license)) {
      ;[license] = license
    }
    if (license) {
      result.license = this.license(license)
    }
    const tags = object[this._getAmfKey(ns.aml.vocabularies.apiContract.tag, context) as keyof IAmfApi] as
      | IAmfTag[]
      | undefined
    if (Array.isArray(tags) && tags.length) {
      result.tags = tags.map((t) => this.tag(t))
    }
    const docs = object[this._getAmfKey(ns.aml.vocabularies.core.documentation, context) as keyof IAmfApi] as
      | IAmfCreativeWork[]
      | undefined
    if (Array.isArray(docs) && docs.length) {
      result.documentations = docs.map((d) => this.documentation(d))
    }
    return result
  }

  api(object: IAmfApi, context?: Record<string, string>): IApiBase {
    const objectContext = context || object['@context']
    const result = this.apiSummary(object) as IApiBase
    result.endPoints = []
    result.servers = []
    result.security = []

    const endPoints = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.endpoint, objectContext) as keyof IAmfApi
    ] as IAmfEndPoint[] | undefined
    if (Array.isArray(endPoints) && endPoints.length) {
      result.endPoints = endPoints.map((e) => this.endPoint(e, objectContext))
    }
    const servers = object[this._getAmfKey(ns.aml.vocabularies.apiContract.server, objectContext) as keyof IAmfApi] as
      | IAmfServer
      | IAmfServer[]
      | undefined
    if (Array.isArray(servers) && servers.length) {
      result.servers = servers.map((s) => this.server(s, objectContext))
    }
    const security = object[this._getAmfKey(ns.aml.vocabularies.security.security, objectContext) as keyof IAmfApi] as
      | IAmfSecurityRequirement[]
      | undefined
    if (Array.isArray(security) && security.length) {
      result.security = security.map((s) => this.securityRequirement(s, objectContext))
    }
    return result
  }

  webApi(object: IAmfWebApi, context?: Record<string, string>): IApiWeb {
    return this.api(object, context)
  }

  asyncApi(object: IAmfAsyncApi, context?: Record<string, string>): IApiAsync {
    return this.api(object, context)
  }

  provider(object: IAmfOrganization, context?: Record<string, string>): IApiOrganization {
    const objectContext = context || object['@context']
    const result: IApiOrganization = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const url = this._getLinkValue(object, ns.aml.vocabularies.core.url, objectContext)
    if (url && typeof url === 'string') {
      result.url = url
    }
    const email = this._getValue(object, ns.aml.vocabularies.core.email, objectContext)
    if (email && typeof email === 'string') {
      result.email = email
    }
    return result
  }

  license(object: IAmfLicense, context?: Record<string, string>): IApiLicense {
    const objectContext = context || object['@context']
    const result: IApiLicense = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const url = this._getLinkValue(object, ns.aml.vocabularies.core.url, objectContext)
    if (url && typeof url === 'string') {
      result.url = url
    }
    return result
  }

  /**
   * @param object The AMF Server to serialize.
   * @returns Serialized Server
   */
  server(object: IAmfServer, context?: Record<string, string>): IApiServer {
    const objectContext = context || object['@context']

    const url = (this._getValue(object, ns.aml.vocabularies.core.urlTemplate, objectContext) as string) || ''
    const result: IApiServer = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      url,
      variables: [],
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const variables = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.variable, objectContext) as keyof IAmfServer
    ] as IAmfParameter[]
    if (Array.isArray(variables) && variables.length) {
      result.variables = variables.map((p) => this.parameter(p, objectContext))
    }
    const protocol = this._getValue(object, ns.aml.vocabularies.apiContract.protocol, objectContext) as string
    const protocolVersion = this._getValue(
      object,
      ns.aml.vocabularies.apiContract.protocolVersion,
      objectContext
    ) as string
    if (protocol) {
      result.protocol = protocol
    }
    if (protocolVersion) {
      result.protocolVersion = protocolVersion
    }
    const security = object[
      this._getAmfKey(ns.aml.vocabularies.security.security, objectContext) as keyof IAmfServer
    ] as IAmfSecurityRequirement[]
    if (Array.isArray(security) && security.length) {
      result.security = security.map((p) => this.securityRequirement(p, objectContext))
    }
    return result
  }

  /**
   * @param object The Parameter to serialize.
   * @returns Serialized Parameter
   */
  parameter(object: IAmfParameter, context?: Record<string, string>): IApiParameter {
    const objectContext = context || object['@context']
    const result: IApiParameter = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      payloads: [],
      examples: [],
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const paramName = this._getValue(object, ns.aml.vocabularies.apiContract.paramName, objectContext)
    if (paramName && typeof paramName === 'string') {
      result.paramName = paramName
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const required = this._getValue(object, ns.aml.vocabularies.apiContract.required, objectContext)
    if (typeof required === 'boolean') {
      result.required = required
    }
    const allowEmptyValue = this._getValue(object, ns.aml.vocabularies.apiContract.allowEmptyValue, objectContext)
    if (typeof allowEmptyValue === 'boolean') {
      result.allowEmptyValue = allowEmptyValue
    }
    const deprecated = this._getValue(object, ns.aml.vocabularies.document.deprecated, objectContext)
    if (typeof deprecated === 'boolean') {
      result.deprecated = deprecated
    }
    const explode = this._getValue(object, ns.aml.vocabularies.apiContract.explode, objectContext)
    if (typeof explode === 'boolean') {
      result.explode = explode
    }
    const allowReserved = this._getValue(object, ns.aml.vocabularies.apiContract.allowReserved, objectContext)
    if (typeof allowReserved === 'boolean') {
      result.allowReserved = allowReserved
    }
    const style = this._getValue(object, ns.aml.vocabularies.apiContract.style, objectContext)
    if (style && typeof style === 'string') {
      result.style = style
    }
    const binding = this._getValue(object, ns.aml.vocabularies.apiContract.binding, objectContext)
    if (binding && typeof binding === 'string') {
      result.binding = binding
    }
    const schemas = object[
      this._getAmfKey(ns.aml.vocabularies.shapes.schema, objectContext) as keyof IAmfParameter
    ] as IAmfShape[]
    if (Array.isArray(schemas) && schemas.length) {
      const [schema] = schemas
      result.schema = this.unknownShape(
        schema,
        {
          trackedId: object['@id'],
        },
        objectContext
      )
    }
    const payloads = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.payload, objectContext) as keyof IAmfParameter
    ] as IAmfPayload[]
    if (Array.isArray(payloads) && payloads.length) {
      result.payloads = payloads.map((p) => this.payload(p, objectContext))
    }
    const examples = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.examples, objectContext) as keyof IAmfParameter
    ] as IAmfParameter[]
    if (Array.isArray(examples) && examples.length) {
      result.examples = examples.map((e) => this.example(e, objectContext))
    }
    return result
  }

  unknownShape(object: IAmfShape, options?: IShapeProcessingOptions, context?: Record<string, string>): IShapeUnion {
    const objectContext = context || object['@context']
    const types = this.readTypes(object['@type'], objectContext)

    if (types.includes(ns.aml.vocabularies.shapes.ScalarShape)) {
      return this.scalarShape(object as IAmfScalarShape, options, objectContext)
    }
    if (types.includes(ns.w3.shacl.NodeShape)) {
      return this.nodeShape(object as IAmfNodeShape, options, objectContext)
    }
    if (types.includes(ns.aml.vocabularies.shapes.UnionShape)) {
      return this.unionShape(object as IAmfUnionShape, options, objectContext)
    }
    if (types.includes(ns.aml.vocabularies.shapes.FileShape)) {
      return this.fileShape(object as IAmfFileShape, options, objectContext)
    }
    if (types.includes(ns.aml.vocabularies.shapes.SchemaShape)) {
      return this.schemaShape(object as IAmfSchemaShape, options, objectContext)
    }
    // this must be before the ArrayShape
    if (types.includes(ns.aml.vocabularies.shapes.TupleShape)) {
      return this.tupleShape(object as IAmfTupleShape, options, objectContext)
    }
    if (
      types.includes(ns.aml.vocabularies.shapes.ArrayShape) ||
      types.includes(ns.aml.vocabularies.shapes.MatrixShape)
    ) {
      return this.arrayShape(object as IAmfArrayShape, options, objectContext)
    }
    if (types.includes(ns.aml.vocabularies.shapes.RecursiveShape)) {
      return this.recursiveShape(object as IAmfRecursiveShape, objectContext)
    }
    // recursiveShape
    return this.anyShape(object as IAmfAnyShape, options, objectContext)
  }

  isLink(object: IAmfDomainElement, context?: Record<string, string>): boolean {
    const objectContext = context || object['@context']
    return !!this._getLinkValue(object, ns.aml.vocabularies.document.linkTarget, objectContext)
  }

  getLinkTarget<T extends IAmfDomainElement>(object: T, context?: Record<string, string>): T | undefined {
    const objectContext = context || object['@context']
    const id = this._getLinkValue(object, ns.aml.vocabularies.document.linkTarget, objectContext)
    if (!id) {
      return undefined
    }
    return this[findAmfType](id, objectContext) as T | undefined
  }

  shape(object: IAmfShape, context?: Record<string, string>): IApiShape {
    const objectContext = context || object['@context']
    this._resolve(object)
    let linkLabel: string | undefined
    let target = object
    if (this.isLink(target)) {
      linkLabel = this._getValue(target, ns.aml.vocabularies.document.linkLabel, objectContext) as string
      const id = this._getLinkValue(target, ns.aml.vocabularies.document.linkTarget, objectContext)
      const value = this[findAmfType](id, objectContext) as IAmfShape | undefined
      if (value) {
        target = value
      }
    }
    const result: IApiShape = {
      id: target['@id'],
      types: this.readTypes(object['@type'], objectContext),
      values: [],
      inherits: [],
      or: [],
      and: [],
      xone: [],
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }
    if (linkLabel) {
      result.linkLabel = linkLabel
    }

    const name = this._getValue(target, ns.w3.shacl.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const displayName = this._getValue(target, ns.aml.vocabularies.core.displayName, objectContext)
    if (displayName && typeof displayName === 'string') {
      result.displayName = displayName
    } else {
      const coreName = this._getValue(target, ns.aml.vocabularies.core.name, objectContext)
      if (coreName && typeof coreName === 'string') {
        result.displayName = coreName
      }
    }
    const description = this._getValue(target, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const defaultValueStr = this._getValue(target, ns.w3.shacl.defaultValueStr, objectContext)
    if (defaultValueStr && typeof defaultValueStr === 'string') {
      result.defaultValueStr = defaultValueStr
    }
    const deprecated = this._getValue(target, ns.aml.vocabularies.shapes.deprecated, objectContext)
    if (typeof deprecated === 'boolean') {
      result.deprecated = deprecated
    }
    const readOnly = this._getValue(target, ns.aml.vocabularies.shapes.readOnly, objectContext)
    if (typeof readOnly === 'boolean') {
      result.readOnly = readOnly
    }
    const writeOnly = this._getValue(target, ns.aml.vocabularies.shapes.writeOnly, objectContext)
    if (typeof writeOnly === 'boolean') {
      result.writeOnly = writeOnly
    }
    const defValue = target[this._getAmfKey(ns.w3.shacl.defaultValue, objectContext) as keyof IAmfShape]
    if (Array.isArray(defValue) && defValue.length) {
      result.defaultValue = this.unknownDataNode(defValue[0] as IAmfDataNode, objectContext)
    }
    // @TODO:
    // if (Array.isArray(inherits) && inherits.length) {
    //   result.inherits = inherits.map((item) => this.unknownShape(item));
    // }
    const orKey = this._getAmfKey(ns.w3.shacl.or, objectContext) as keyof IAmfShape
    const orGroup = target[orKey] as IAmfShape[]
    if (Array.isArray(orGroup) && orGroup.length) {
      result.or = orGroup.map((item) => this.unknownShape(item, undefined, objectContext))
    }
    const andKey = this._getAmfKey(ns.w3.shacl.and, objectContext) as keyof IAmfShape
    const andGroup = target[andKey] as IAmfShape[]
    if (Array.isArray(andGroup) && andGroup.length) {
      result.and = andGroup.map((item) => this.unknownShape(item, undefined, objectContext))
    }
    const xoneKey = this._getAmfKey(ns.w3.shacl.xone, objectContext) as keyof IAmfShape
    const xone = target[xoneKey] as IAmfShape[]
    if (Array.isArray(xone) && xone.length) {
      result.xone = xone.map((item) => this.unknownShape(item, undefined, objectContext))
    }

    const valuesList = target[this._getAmfKey(ns.w3.shacl.in, objectContext) as keyof IAmfShape]
    if (Array.isArray(valuesList) && valuesList.length) {
      const [values] = valuesList
      const prefix = ns.w3.rdfSchema.key
      const prefixCompact = this._getAmfKey(prefix, objectContext) as string
      Object.keys(values).forEach((key) => {
        if (key.startsWith(prefix) || key.startsWith(prefixCompact)) {
          let value = (values as unknown as Record<string, IAmfDataNode>)[key]
          if (Array.isArray(value)) {
            ;[value] = value
          }
          const processed = this.unknownDataNode(value, objectContext)
          if (processed) {
            result.values.push(processed)
          }
        }
      })
    }
    const notKey = this._getAmfKey(ns.w3.shacl.not, objectContext) as keyof IAmfShape
    let not = target[notKey] as IAmfShape | undefined
    if (not) {
      if (Array.isArray(not)) {
        ;[not] = not
      }
      result.not = this.unknownShape(not as IAmfShape, undefined, objectContext)
    }
    return result
  }

  anyShape(
    object: IAmfAnyShape,
    options: IShapeProcessingOptions = {},
    context?: Record<string, string>
  ): IApiAnyShape {
    const objectContext = context || object['@context']
    let target = object
    const result = this.shape(target, objectContext) as IApiAnyShape
    if (this.isLink(target)) {
      const value = this.getLinkTarget(target, objectContext) as IAmfShape | undefined
      if (value) {
        target = value
      }
    }
    result.examples = []
    const examples = target[
      this._getAmfKey(ns.aml.vocabularies.apiContract.examples, objectContext) as keyof IAmfAnyShape
    ] as IAmfExample[]

    if (Array.isArray(examples) && examples.length) {
      if (options.trackedId) {
        const filtered = this.filterTrackedExamples(examples, options.trackedId, objectContext)
        result.examples = filtered.map((item) => this.example(item, objectContext))
      } else {
        const filtered = this.filterNonTrackedExamples(examples, objectContext)
        result.examples = filtered.map((item) => this.example(item, objectContext))
      }
    }
    const docs = target[
      this._getAmfKey(ns.aml.vocabularies.core.documentation, objectContext) as keyof IAmfAnyShape
    ] as IAmfCreativeWork[] | undefined
    if (Array.isArray(docs) && docs.length) {
      const [documentation] = docs
      result.documentation = this.documentation(documentation, objectContext)
    }
    const xml = target[
      this._getAmfKey(ns.aml.vocabularies.shapes.xmlSerialization, objectContext) as keyof IAmfAnyShape
    ] as IAmfXMLSerializer[] | undefined
    if (Array.isArray(xml) && xml.length) {
      result.xmlSerialization = this.xmlSerializer(xml[0] as IAmfXMLSerializer, objectContext)
    }
    return result
  }

  /**
   * Filters examples that should be rendered for a payload identified by `trackedId`.
   *
   * This function is copied from old `api-example-generator/ExampleGenerator`.
   */
  filterTrackedExamples(examples: IAmfExample[], trackedId: string, context?: Record<string, string>): IAmfExample[] {
    const { docSourceMaps } = ns.aml.vocabularies
    const sourceKey = this._getAmfKey(docSourceMaps.sources, context) as keyof IAmfExample
    const trackedKey = this._getAmfKey(docSourceMaps.trackedElement, context) as keyof IAmfDocumentSourceMaps
    const longId = trackedId.indexOf('amf') === -1 ? `amf://id${trackedId}` : trackedId
    return examples.filter((item) => {
      let example = item
      if (Array.isArray(example)) {
        ;[example] = example
      }
      let sm = example[sourceKey] as IAmfDocumentSourceMaps[] | IAmfDocumentSourceMaps | undefined
      if (!sm) {
        return true
      }
      if (Array.isArray(sm)) {
        ;[sm] = sm
      }
      let tracked = sm[trackedKey] as IAmfSynthesizedField[] | IAmfSynthesizedField | undefined
      if (!tracked) {
        return true
      }
      if (Array.isArray(tracked)) {
        ;[tracked] = tracked
      }
      const { value } = this.synthesizedField(tracked, context)
      if (!value) {
        return true
      }
      const ids = value.split(',')
      if (ids.indexOf(longId) !== -1 || ids.indexOf(trackedId) !== -1) {
        return true
      }
      return false
    })
  }

  /**
   * Kind of the opposite of the `filterTrackedExamples`. It gathers examples that only have been
   * defined for the parent Shape (ed in the type declaration). It filters out all examples
   * defined in a payload.
   */
  filterNonTrackedExamples(examples: IAmfExample[], context?: Record<string, string>): IAmfExample[] {
    const { docSourceMaps } = ns.aml.vocabularies
    const sourceKey = this._getAmfKey(docSourceMaps.sources, context) as keyof IAmfExample
    const trackedKey = this._getAmfKey(docSourceMaps.trackedElement, context) as keyof IAmfDocumentSourceMaps
    return examples.filter((item) => {
      let example = item
      if (Array.isArray(example)) {
        ;[example] = example
      }
      let sm = example[sourceKey] as IAmfDocumentSourceMaps[] | IAmfDocumentSourceMaps | undefined
      if (!sm) {
        return true
      }
      if (Array.isArray(sm)) {
        ;[sm] = sm
      }
      let tracked = sm[trackedKey] as IAmfSynthesizedField[] | IAmfSynthesizedField | undefined
      if (!tracked) {
        return true
      }
      if (Array.isArray(tracked)) {
        ;[tracked] = tracked
      }
      const { value } = this.synthesizedField(tracked, context)
      if (!value) {
        return true
      }
      return false
    })
  }

  scalarShape(
    object: IAmfScalarShape,
    options: IShapeProcessingOptions = {},
    context?: Record<string, string>
  ): IApiScalarShape {
    const objectContext = context || object['@context']
    let target = object
    const result = this.anyShape(target, options, objectContext) as IApiScalarShape
    if (this.isLink(target)) {
      const value = this.getLinkTarget(target, objectContext) as IAmfScalarShape | undefined
      if (value) {
        target = value
      }
    }

    const pattern = this._getValue(target, ns.w3.shacl.pattern, objectContext)
    if (pattern && typeof pattern === 'string') {
      result.pattern = pattern
    }
    const dataType = this._getLinkValue(target, ns.w3.shacl.datatype, objectContext)
    if (dataType && typeof dataType === 'string') {
      result.dataType = dataType
    }
    const format = this._getValue(target, ns.aml.vocabularies.shapes.format, objectContext)
    if (format && typeof format === 'string') {
      result.format = format
    }
    const multipleOf = this._getValue(target, ns.aml.vocabularies.shapes.multipleOf, objectContext)
    if (typeof multipleOf === 'number') {
      result.multipleOf = multipleOf
    }
    const minInclusive = this._getValue(target, ns.w3.shacl.minInclusive, objectContext)
    if (typeof minInclusive === 'number') {
      result.minimum = minInclusive
      result.exclusiveMinimum = false
    }
    const maxInclusive = this._getValue(target, ns.w3.shacl.maxInclusive, objectContext)
    if (typeof maxInclusive === 'number') {
      result.maximum = maxInclusive
      result.exclusiveMaximum = false
    }
    const minLength = this._getValue(target, ns.w3.shacl.minLength, objectContext)
    if (typeof minLength === 'number') {
      result.minLength = minLength
    }
    const maxLength = this._getValue(target, ns.w3.shacl.maxLength, objectContext)
    if (typeof maxLength === 'number') {
      result.maxLength = maxLength
    }
    return result
  }

  nodeShape(
    object: IAmfNodeShape,
    options: IShapeProcessingOptions = {},
    context?: Record<string, string>
  ): IApiNodeShape {
    const objectContext = context || object['@context']
    let target = object
    const result = this.anyShape(target, options, objectContext) as IApiNodeShape
    if (this.isLink(target)) {
      const value = this.getLinkTarget(target, objectContext)
      if (value) {
        target = value
      }
    }

    const discriminator = this._getValue(target, ns.aml.vocabularies.shapes.discriminator, objectContext)
    if (discriminator && typeof discriminator === 'string') {
      result.discriminator = discriminator
    }
    const discriminatorValue = this._getValue(target, ns.aml.vocabularies.shapes.discriminatorValue, objectContext)
    if (discriminatorValue && typeof discriminatorValue === 'string') {
      result.discriminatorValue = discriminatorValue
    }
    const closed = this._getValue(target, ns.w3.shacl.closed, objectContext)
    if (typeof closed === 'boolean') {
      result.closed = closed
    }

    result.customShapeProperties = []
    result.customShapePropertyDefinitions = []
    result.dependencies = []
    // todo: not sure what the keys are.
    // if (!minProperties.isNull) {
    //   result.minProperties = minProperties.value();
    // }
    // if (!maxProperties.isNull) {
    //   result.maxProperties = maxProperties.value();
    // }
    // if (Array.isArray(customShapeProperties) && customShapeProperties.length) {
    //   result.customShapeProperties = customShapeProperties.map((item) => item.id);
    // } else {
    //   result.customShapeProperties = [];
    // }
    // if (Array.isArray(customShapePropertyDefinitions) && customShapePropertyDefinitions.length) {
    //   result.customShapePropertyDefinitions = customShapePropertyDefinitions.map((item) => item.id);
    // } else {
    //   result.customShapePropertyDefinitions = [];
    // }
    const properties = target[
      this._getAmfKey(ns.w3.shacl.property, objectContext) as keyof IAmfNodeShape
    ] as IAmfPropertyShape[]
    if (Array.isArray(properties) && properties.length) {
      result.properties = properties.map((item) => this.propertyShape(item, objectContext))
    } else {
      result.properties = []
    }
    // if (Array.isArray(dependencies) && dependencies.length) {
    //   result.dependencies = dependencies.map((item) => item.id);
    // } else {
    //   result.dependencies = [];
    // }
    return result
  }

  propertyShape(object: IAmfPropertyShape, context?: Record<string, string>): IApiPropertyShape {
    const objectContext = context || object['@context']
    let target = object
    const result = this.shape(target, objectContext) as IApiPropertyShape
    if (this.isLink(target)) {
      const value = this.getLinkTarget(target, objectContext)
      if (value) {
        target = value
      }
    }

    const path = this._getLinkValue(target, ns.w3.shacl.path, objectContext)
    if (path && typeof path === 'string') {
      result.path = path
    }
    const minCount = this._getValue(target, ns.w3.shacl.minCount, objectContext)
    if (typeof minCount === 'number') {
      result.minCount = minCount
    }
    const maxCount = this._getValue(target, ns.w3.shacl.maxCount, objectContext)
    if (typeof maxCount === 'number') {
      result.maxCount = maxCount
    }
    // if (!patternName.isNullOrEmpty) {
    //   result.patternName = patternName.value();
    // }

    const ranges = target[
      this._getAmfKey(ns.aml.vocabularies.shapes.range, objectContext) as keyof IAmfPropertyShape
    ] as IAmfShape[]
    if (Array.isArray(ranges) && ranges.length) {
      const [range] = ranges
      result.range = this.unknownShape(range, undefined, objectContext)
    }
    return result
  }

  unionShape(
    object: IAmfUnionShape,
    options: IShapeProcessingOptions = {},
    context?: Record<string, string>
  ): IApiUnionShape {
    const objectContext = context || object['@context']
    const anyOf = object[
      this._getAmfKey(ns.aml.vocabularies.shapes.anyOf, objectContext) as keyof IAmfUnionShape
    ] as IAmfShape[]
    const result = this.anyShape(object, options, objectContext) as IApiUnionShape
    if (Array.isArray(anyOf) && anyOf.length) {
      const opt = { ...options, trackedId: undefined }
      result.anyOf = anyOf.map((shape) => this.unknownShape(shape, opt, objectContext))
    } else {
      result.anyOf = []
    }
    return result
  }

  fileShape(
    object: IAmfFileShape,
    options: IShapeProcessingOptions = {},
    context?: Record<string, string>
  ): IApiFileShape {
    const objectContext = context || object['@context']
    let target = object
    const result = this.anyShape(target, options, objectContext) as IApiFileShape
    if (this.isLink(target)) {
      const value = this.getLinkTarget(target, objectContext)
      if (value) {
        target = value
      }
    }

    const pattern = this._getValue(target, ns.w3.shacl.pattern, objectContext)
    if (pattern && typeof pattern === 'string') {
      result.pattern = pattern
    }
    const fileTypes = this._getValueArray(target, ns.aml.vocabularies.shapes.fileType, objectContext) as
      | string[]
      | undefined
    if (Array.isArray(fileTypes) && fileTypes.length) {
      result.fileTypes = fileTypes
    }
    const minLength = this._getValue(target, ns.w3.shacl.minLength, objectContext)
    if (typeof minLength === 'number') {
      result.minLength = minLength
    }
    const maxLength = this._getValue(target, ns.w3.shacl.maxLength, objectContext)
    if (typeof maxLength === 'number') {
      result.maxLength = maxLength
    }
    const minInclusive = this._getValue(target, ns.w3.shacl.minInclusive, objectContext)
    if (typeof minInclusive === 'number') {
      result.minimum = minInclusive
      result.exclusiveMinimum = false
    }
    const maxInclusive = this._getValue(target, ns.w3.shacl.maxInclusive, objectContext)
    if (typeof maxInclusive === 'number') {
      result.maximum = maxInclusive
      result.exclusiveMaximum = false
    }
    const format = this._getValue(target, ns.aml.vocabularies.shapes.format, objectContext)
    if (format && typeof format === 'string') {
      result.format = format
    }
    const multipleOf = this._getValue(target, ns.aml.vocabularies.shapes.multipleOf, objectContext)
    if (typeof multipleOf === 'number') {
      result.multipleOf = multipleOf
    }
    return result
  }

  schemaShape(
    object: IAmfSchemaShape,
    options: IShapeProcessingOptions = {},
    context?: Record<string, string>
  ): IApiSchemaShape {
    const objectContext = context || object['@context']
    let target = object
    const result = this.anyShape(target, options, objectContext) as IApiSchemaShape
    if (this.isLink(target)) {
      const value = this.getLinkTarget(target, objectContext)
      if (value) {
        target = value
      }
    }

    const mediaType = this._getValue(target, ns.aml.vocabularies.core.mediaType, objectContext)
    if (mediaType && typeof mediaType === 'string') {
      result.mediaType = mediaType
    }
    const raw = this._getValue(target, ns.aml.vocabularies.document.raw, objectContext)
    if (raw && typeof raw === 'string') {
      result.raw = raw
    }
    return result
  }

  recursiveShape(object: IAmfRecursiveShape, context?: Record<string, string>): IApiRecursiveShape {
    const objectContext = context || object['@context']
    let target = object
    const result = this.shape(target, objectContext) as IApiRecursiveShape
    if (this.isLink(target)) {
      const value = this.getLinkTarget(target, objectContext)
      if (value) {
        target = value
      }
    }

    const fp = this._getLinkValue(object, ns.aml.vocabularies.shapes.fixPoint, objectContext)
    if (fp && typeof fp === 'string') {
      result.fixPoint = fp
    }
    return result
  }

  dataArrangeShape(
    object: IAmfDataArrangeShape,
    options: IShapeProcessingOptions = {},
    context?: Record<string, string>
  ): IApiDataArrangeShape {
    const objectContext = context || object['@context']
    let target = object
    const result = this.anyShape(target, options, objectContext) as IApiDataArrangeShape
    if (this.isLink(target)) {
      const value = this.getLinkTarget(target, objectContext)
      if (value) {
        target = value
      }
    }
    //
    // const { minItems, maxItems, uniqueItems } = object;
    // if (!minItems.isNull) {
    //   result.minItems = minItems.value();
    // }
    // if (!maxItems.isNull) {
    //   result.maxItems = maxItems.value();
    // }
    // if (!uniqueItems.isNull) {
    //   result.uniqueItems = uniqueItems.value();
    // }
    return result
  }

  arrayShape(
    object: IAmfArrayShape,
    options: IShapeProcessingOptions = {},
    context?: Record<string, string>
  ): IApiArrayShape {
    const objectContext = context || object['@context']
    let target = object
    const result = this.dataArrangeShape(target, options, objectContext) as IApiArrayShape
    if (this.isLink(target)) {
      const value = this.getLinkTarget(target, objectContext)
      if (value) {
        target = value
      }
    }

    const items = target[this._getAmfKey(ns.aml.vocabularies.shapes.items, objectContext) as keyof IAmfArrayShape] as
      | IAmfShape[]
      | undefined
    if (Array.isArray(items) && items.length) {
      const [item] = items
      result.items = this.unknownShape(item, undefined, objectContext)
    }
    return result
  }

  tupleShape(
    object: IAmfTupleShape,
    options?: IShapeProcessingOptions,
    context?: Record<string, string>
  ): IApiTupleShape {
    const objectContext = context || object['@context']
    let target = object
    const result = this.dataArrangeShape(target, options, objectContext) as IApiTupleShape
    if (this.isLink(target)) {
      const value = this.getLinkTarget(target, objectContext)
      if (value) {
        target = value
      }
    }
    const items = target[this._getAmfKey(ns.aml.vocabularies.shapes.items, objectContext) as keyof IAmfTupleShape] as
      | IAmfShape[]
      | undefined
    const prefix = this._getAmfKey(ns.w3.rdfSchema.key, objectContext) as string
    if (Array.isArray(items) && items.length) {
      result.items = []
      items.forEach((item) => {
        if (Array.isArray(item)) {
          ;[item] = item
        }
        Object.keys(item)
          .filter((k) => k.startsWith(prefix))
          .forEach((key) => {
            let shape = (item as unknown as Record<string, IAmfShape | IAmfShape[] | undefined>)[
              key as keyof IAmfShape
            ] as IAmfShape
            if (Array.isArray(shape)) {
              ;[shape] = shape
            }
            const value = this.unknownShape(shape, undefined, objectContext)
            result.items.push(value)
          })
      })
    } else {
      result.items = []
    }
    return result
  }

  /**
   * @param object The CreativeWork to serialize.
   * @returns Serialized CreativeWork
   */
  documentation(object: IAmfCreativeWork, context?: Record<string, string>): IApiDocumentation {
    const objectContext = context || object['@context']
    const result: IApiDocumentation = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }
    const url = this._getLinkValue(object, ns.aml.vocabularies.core.url, objectContext)
    if (url && typeof url === 'string') {
      result.url = url
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const title = this._getValue(object, ns.aml.vocabularies.core.title, objectContext)
    if (title && typeof title === 'string') {
      result.title = title
    }
    return result
  }

  /**
   * @param object The Example to serialize.
   * @returns Serialized Example
   */
  example(object: IAmfExample, context?: Record<string, string>): IApiDataExample {
    const objectContext = context || object['@context']
    this._resolve(object)
    const result: IApiDataExample = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
      strict: false,
    }

    const strict = this._getValue(object, ns.aml.vocabularies.document.strict, objectContext)
    if (typeof strict === 'boolean') {
      result.strict = strict
    }
    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const displayName = this._getValue(object, ns.aml.vocabularies.core.displayName, objectContext)
    if (displayName && typeof displayName === 'string') {
      result.displayName = displayName
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const raw = this._getValue(object, ns.aml.vocabularies.document.raw, objectContext)
    if (raw && typeof raw === 'string') {
      result.value = raw
    }
    const location = this._getValue(object, ns.aml.vocabularies.document.location, objectContext)
    if (location && typeof location === 'string') {
      result.location = location
    }
    // if (!mediaType.isNullOrEmpty) {
    //   result.mediaType = mediaType.value();
    // }
    const structuredValue = object[
      this._getAmfKey(ns.aml.vocabularies.document.structuredValue, objectContext) as keyof IAmfExample
    ] as IAmfDataNode[] | undefined
    if (Array.isArray(structuredValue) && structuredValue.length) {
      const [value] = structuredValue
      result.structuredValue = this.unknownDataNode(value, objectContext)
    }
    return result
  }

  xmlSerializer(object: IAmfXMLSerializer, context?: Record<string, string>): IApiXmlSerializer {
    const objectContext = context || object['@context']
    const result: IApiXmlSerializer = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }

    const xmlAttribute = this._getValue(object, ns.aml.vocabularies.shapes.xmlAttribute, objectContext)
    if (typeof xmlAttribute === 'boolean') {
      result.attribute = xmlAttribute
    }
    const wrapped = this._getValue(object, ns.aml.vocabularies.shapes.xmlWrapped, objectContext)
    if (typeof wrapped === 'boolean') {
      result.wrapped = wrapped
    }
    const name = this._getValue(object, ns.aml.vocabularies.shapes.xmlName, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const xmlNs = this._getValue(object, ns.aml.vocabularies.shapes.xmlNamespace, objectContext)
    if (xmlNs && typeof xmlNs === 'string') {
      result.namespace = xmlNs
    }
    const xmlPrefix = this._getValue(object, ns.aml.vocabularies.shapes.xmlPrefix, objectContext)
    if (xmlPrefix && typeof xmlPrefix === 'string') {
      result.prefix = xmlPrefix
    }
    return result
  }

  unknownDataNode(object: IAmfDataNode, context?: Record<string, string>): IApiDataNodeUnion | undefined {
    const types = this.readTypes(object['@type'], context)

    if (types.includes(ns.aml.vocabularies.data.Scalar)) {
      return this.scalarNode(object as IAmfScalarNode, context)
    }
    if (types.includes(ns.aml.vocabularies.data.Object)) {
      return this.objectNode(object as IAmfObjectNode, context)
    }
    if (types.includes(ns.aml.vocabularies.data.Array)) {
      return this.arrayNode(object as IAmfArrayNode, context)
    }
    return undefined
  }

  dataNode(object: IAmfDataNode, context?: Record<string, string>): IApiDataNode {
    const result: IApiDataNode = {
      id: object['@id'],
      types: this.readTypes(object['@type'], context),
      customDomainProperties: this.customDomainProperties(object, context),
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, context)
    if (name && typeof name === 'string') {
      result.name = name
    }
    return result
  }

  scalarNode(object: IAmfScalarNode, context?: Record<string, string>): IApiScalarNode {
    const objectContext = context || object['@context']
    const result = this.dataNode(object, context) as IApiScalarNode

    const value = this._getValue(object, ns.aml.vocabularies.data.value, objectContext)
    if (value && typeof value === 'string') {
      result.value = value
    }
    const dataType = this._getLinkValue(object, ns.w3.shacl.datatype, objectContext)
    if (dataType && typeof dataType === 'string') {
      result.dataType = dataType
    }
    return result
  }

  objectNode(object: IAmfObjectNode, context?: Record<string, string>): IApiObjectNode {
    const objectContext = context || object['@context']
    const result = this.dataNode(object, context) as IApiObjectNode
    result.properties = {}
    const prefix = ns.aml.vocabularies.data.key
    const prefixCompact = `${this._getAmfKey(prefix, objectContext)}:`
    Object.keys(object).forEach((key) => {
      if (key.startsWith(prefix) || key.startsWith(prefixCompact)) {
        let value = object[key as keyof IAmfObjectNode] as IAmfDataNode | undefined
        if (Array.isArray(value)) {
          ;[value] = value
        }
        const name = key.replace(prefix, '').replace(prefixCompact, '')
        const nodeValue = this.unknownDataNode(value as IAmfDataNode, context)
        if (nodeValue) {
          result.properties[name] = nodeValue
        }
      }
    })
    return result
  }

  arrayNode(object: IAmfArrayNode, context?: Record<string, string>): IApiArrayNode {
    const objectContext = context || object['@context']
    const result = this.dataNode(object, objectContext) as IApiArrayNode
    result.members = []
    const members = this._computePropertyArray(object, ns.w3.rdfSchema.member) as IAmfDataNode[]
    if (Array.isArray(members) && members.length) {
      members.forEach((n) => {
        const item = this.unknownDataNode(n)
        if (item) {
          result.members.push(item)
        }
      })
    }
    return result
  }

  /**
   * Adds the custom domain properties to the currently processed property, a.k.a annotations.
   * @returns  The list of custom domain properties.
   */
  customDomainProperties(object: IAmfDomainElement, context?: Record<string, string>): IApiCustomDomainProperty[] {
    const result: IApiCustomDomainProperty[] = []
    const objectContext = context || object['@context']
    const ids = this._getLinkValues(object, ns.aml.vocabularies.document.customDomainProperties, objectContext)
    if (Array.isArray(ids) && ids.length) {
      ids.forEach((id) => {
        const key = `amf://id${id}`
        let value = (object[id as keyof IAmfDomainElement] || object[key as keyof IAmfDomainElement]) as
          | IAmfDomainElement[]
          | IAmfDomainElement
          | undefined
        if (!value) {
          return
        }
        if (Array.isArray(value)) {
          ;[value] = value
        }
        const extension = this.unknownDataNode(value, objectContext)
        const name = this._getValue(value, ns.aml.vocabularies.core.extensionName, objectContext) as string | undefined
        if (!name || !extension) {
          return
        }
        const cdp: IApiCustomDomainProperty = {
          id: key,
          name,
          extension,
        }
        result.push(cdp)
      })
    }
    return result
  }

  endPoint(object: IAmfEndPoint, context?: Record<string, string>): IApiEndPoint {
    const objectContext = context || object['@context']
    const result: IApiEndPoint = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object),
      path: '',
      operations: [],
      parameters: [],
      payloads: [],
      servers: [],
      security: [],
      extends: [],
    }

    const path = this._getValue(object, ns.aml.vocabularies.apiContract.path, objectContext)
    if (path && typeof path === 'string') {
      result.path = path
    }
    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const summary = this._getValue(object, ns.aml.vocabularies.core.summary, objectContext)
    if (summary && typeof summary === 'string') {
      result.summary = summary
    }
    const operations = this[getArrayItems](object, ns.aml.vocabularies.apiContract.supportedOperation, objectContext)
    if (Array.isArray(operations) && operations.length) {
      result.operations = operations.map((i) => this.operation(i as IAmfOperation, objectContext))
    }
    const parameters = this[getArrayItems](object, ns.aml.vocabularies.apiContract.parameter, objectContext)
    if (Array.isArray(parameters) && parameters.length) {
      result.parameters = parameters.map((i) => this.parameter(i, objectContext))
    }
    const payloads = this[getArrayItems](object, ns.aml.vocabularies.apiContract.payload, objectContext)
    if (Array.isArray(payloads) && payloads.length) {
      result.payloads = payloads.map((i) => this.payload(/** @type IAmfPayload */ i as IAmfPayload, objectContext))
    }
    const servers = this[getArrayItems](object, ns.aml.vocabularies.apiContract.server, objectContext)
    if (Array.isArray(servers) && servers.length) {
      result.servers = servers.map((i) => this.server(i, objectContext))
    }
    const security = this[getArrayItems](object, ns.aml.vocabularies.security.security, objectContext)
    if (Array.isArray(security) && security.length) {
      result.security = security.map((i) => this.securityRequirement(i, objectContext))
    }
    const extensions = this[getArrayItems](object, ns.aml.vocabularies.document.extends, objectContext)
    if (Array.isArray(extensions) && extensions.length) {
      result.extends = []
      extensions.forEach((ex) => {
        let extension = ex
        if (Array.isArray(extension)) {
          ;[extension] = extension
        }
        if (this._hasType(extension, ns.aml.vocabularies.apiContract.ParametrizedResourceType, objectContext)) {
          result.extends.push(this.parametrizedResourceType(extension, objectContext))
        } else if (this._hasType(extension, ns.aml.vocabularies.apiContract.ParametrizedTrait, objectContext)) {
          result.extends.push(this.parametrizedTrait(extension, objectContext))
        }
      })
    }
    return result
  }

  operation(object: IAmfOperation, context?: Record<string, string>): IApiOperation {
    const objectContext = context || object['@context']
    const result: IApiOperation = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object),
      method: '',
      deprecated: false,
      callbacks: [],
      responses: [],
      servers: [],
      security: [],
      accepts: [],
      schemes: [],
      contentType: [],
      tags: [],
      extends: [],
    }

    const method = this._getValue(object, ns.aml.vocabularies.apiContract.method, objectContext)
    if (method && typeof method === 'string') {
      result.method = method
    }
    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const summary = this._getValue(object, ns.aml.vocabularies.apiContract.guiSummary, objectContext)
    if (summary && typeof summary === 'string') {
      result.summary = summary
    }
    const deprecated = this._getValue(object, ns.aml.vocabularies.core.deprecated, objectContext)
    if (typeof deprecated === 'boolean') {
      result.deprecated = deprecated
    }
    const operationId = this._getValue(object, ns.aml.vocabularies.apiContract.operationId, objectContext)
    if (operationId && typeof operationId === 'string') {
      result.operationId = operationId
    }
    const accepts = this._getValueArray(object, ns.aml.vocabularies.apiContract.accepts, objectContext) as string[]
    if (Array.isArray(accepts)) {
      result.accepts = accepts
    }
    const schemes = this._getValueArray(object, ns.aml.vocabularies.apiContract.scheme, objectContext) as string[]
    if (Array.isArray(schemes)) {
      result.schemes = schemes
    }
    const contentType = this._getValueArray(
      object,
      ns.aml.vocabularies.apiContract.contentType,
      objectContext
    ) as string[]
    if (Array.isArray(contentType)) {
      result.contentType = contentType
    }

    let expects = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.expects, objectContext) as keyof IAmfOperation
    ] as IAmfRequest[] | IAmfRequest | undefined
    if (expects) {
      if (Array.isArray(expects)) {
        ;[expects] = expects
      }
      result.request = this.request(expects, objectContext)
    }
    let documentation = object[
      this._getAmfKey(ns.aml.vocabularies.core.documentation, objectContext) as keyof IAmfOperation
    ] as IAmfCreativeWork[] | IAmfCreativeWork | undefined
    if (documentation) {
      if (Array.isArray(documentation)) {
        ;[documentation] = documentation
      }
      result.documentation = this.documentation(documentation, objectContext)
    }
    const responses = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.returns, objectContext) as keyof IAmfOperation
    ] as IAmfResponse[] | undefined
    if (Array.isArray(responses)) {
      result.responses = responses.map((r) => this.response(r, objectContext))
    }
    const callbacks = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.callback, objectContext) as keyof IAmfOperation
    ] as IAmfCallback[] | undefined
    if (Array.isArray(callbacks)) {
      result.callbacks = callbacks.map((c) => this.callback(c, objectContext))
    }
    const servers = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.server, objectContext) as keyof IAmfOperation
    ] as IAmfServer[] | undefined
    if (Array.isArray(servers)) {
      result.servers = servers.map((s) => this.server(s))
    }
    const security = object[
      this._getAmfKey(ns.aml.vocabularies.security.security, objectContext) as keyof IAmfOperation
    ] as IAmfSecurityRequirement[] | undefined
    if (Array.isArray(security)) {
      result.security = security.map((s) => this.securityRequirement(s, objectContext))
    }
    const tags = object[this._getAmfKey(ns.aml.vocabularies.apiContract.tag, objectContext) as keyof IAmfOperation] as
      | IAmfTag[]
      | undefined
    if (Array.isArray(tags) && tags.length) {
      result.tags = tags.map((s) => this.tag(s, objectContext))
    }
    const traits = object[
      this._getAmfKey(ns.aml.vocabularies.document.extends, objectContext) as keyof IAmfOperation
    ] as IAmfParametrizedTrait[] | undefined
    if (Array.isArray(traits) && traits.length) {
      result.extends = traits.map((t) => this.parametrizedTrait(t, objectContext))
    }
    return result
  }

  tag(object: IAmfTag, context?: Record<string, string>): IApiTag {
    const objectContext = context || object['@context']
    const result: IApiTag = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object),
      name: '',
    }
    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    return result
  }

  callback(object: IAmfCallback, context?: Record<string, string>): IApiCallback {
    const objectContext = context || object['@context']
    const result: IApiCallback = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object),
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const expression = this._getValue(object, ns.aml.vocabularies.apiContract.expression, objectContext)
    if (expression && typeof expression === 'string') {
      result.expression = expression
    }
    let endpoint = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.endpoint, objectContext) as keyof IAmfCallback
    ] as IAmfEndPoint[] | IAmfEndPoint | undefined
    if (endpoint) {
      if (Array.isArray(endpoint)) {
        ;[endpoint] = endpoint
      }
      result.endpoint = this.endPoint(endpoint, objectContext)
    }
    return result
  }

  request(object: IAmfRequest, context?: Record<string, string>): IApiRequest {
    const objectContext = context || object['@context']
    const result: IApiRequest = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object),
      required: false,
      headers: [],
      queryParameters: [],
      payloads: [],
      uriParameters: [],
      cookieParameters: [],
    }

    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const required = this._getValue(object, ns.aml.vocabularies.apiContract.required, objectContext)
    if (required && typeof required === 'boolean') {
      result.required = required
    }
    let queryString = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.queryString, objectContext) as keyof IAmfRequest
    ] as IAmfShape[] | IAmfShape | undefined
    if (queryString) {
      if (Array.isArray(queryString)) {
        ;[queryString] = queryString
      }
      result.queryString = this.unknownShape(queryString, undefined, objectContext)
    }
    const headers = this[getArrayItems](object, ns.aml.vocabularies.apiContract.header, objectContext)
    if (Array.isArray(headers) && headers.length) {
      result.headers = headers.map((p) => this.parameter(p, objectContext))
    }
    const queryParameters = this[getArrayItems](object, ns.aml.vocabularies.apiContract.parameter, objectContext)
    if (Array.isArray(queryParameters) && queryParameters.length) {
      result.queryParameters = queryParameters.map((p) => this.parameter(p, objectContext))
    }
    const uriParameters = this[getArrayItems](object, ns.aml.vocabularies.apiContract.uriParameter, objectContext)
    if (Array.isArray(uriParameters) && uriParameters.length) {
      result.uriParameters = uriParameters.map((p) => this.parameter(p, objectContext))
    }
    const cookieParameters = this[getArrayItems](object, ns.aml.vocabularies.apiContract.cookieParameter, objectContext)
    if (Array.isArray(cookieParameters) && cookieParameters.length) {
      result.cookieParameters = cookieParameters.map((p) => this.parameter(p, objectContext))
    }
    const payloads = this[getArrayItems](object, ns.aml.vocabularies.apiContract.payload, objectContext) as
      | IAmfPayload[]
      | undefined
    if (Array.isArray(payloads) && payloads.length) {
      result.payloads = payloads.map((p) => this.payload(p, objectContext))
    }
    return result
  }

  response(object: IAmfResponse, context?: Record<string, string>): IApiResponse {
    const objectContext = context || object['@context']
    const result: IApiResponse = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
      headers: [],
      payloads: [],
      examples: [],
      links: [],
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const statusCode = this._getValue(object, ns.aml.vocabularies.apiContract.statusCode, objectContext)
    if (statusCode && typeof statusCode === 'string') {
      result.statusCode = statusCode
    }
    const headers = this[getArrayItems](object, ns.aml.vocabularies.apiContract.header, objectContext)
    if (Array.isArray(headers) && headers.length) {
      result.headers = headers.map((p) => this.parameter(p, objectContext))
    }
    const payloads = this[getArrayItems](object, ns.aml.vocabularies.apiContract.payload, objectContext) as
      | IAmfPayload[]
      | undefined
    if (Array.isArray(payloads) && payloads.length) {
      result.payloads = payloads.map((p) => this.payload(p, objectContext))
    }
    const examples = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.examples, objectContext) as keyof IAmfResponse
    ] as IAmfExample[] | undefined
    if (Array.isArray(examples) && examples.length) {
      result.examples = examples.map((e) => this.example(e, objectContext))
    }
    const links = object[this._getAmfKey(ns.aml.vocabularies.apiContract.link, objectContext) as keyof IAmfResponse] as
      | IAmfTemplatedLink[]
      | undefined
    if (Array.isArray(links) && links.length) {
      result.links = links.map((p) => this.templatedLink(p, objectContext))
    }
    return result
  }

  payload(object: IAmfPayload, context?: Record<string, string>): IApiPayload {
    const objectContext = context || object['@context']
    const result: IApiPayload = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
      examples: [],
      // encoding: [],
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const mediaType = this._getValue(object, ns.aml.vocabularies.core.mediaType, objectContext)
    if (mediaType && typeof mediaType === 'string') {
      result.mediaType = mediaType
    }
    let schema = object[this._getAmfKey(ns.aml.vocabularies.shapes.schema, objectContext) as keyof IAmfPayload] as
      | IAmfShape
      | IAmfShape[]
      | undefined
    if (schema) {
      if (Array.isArray(schema)) {
        ;[schema] = schema
      }
      result.schema = this.unknownShape(
        schema,
        {
          trackedId: result.id,
        },
        objectContext
      )
    }
    const examples = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.examples, objectContext) as keyof IAmfPayload
    ] as IAmfExample[] | undefined
    if (Array.isArray(examples) && examples.length) {
      result.examples = examples.map((e) => this.example(e, objectContext))
    }
    // if (Array.isArray(encoding) && encoding.length) {
    //   result.encoding = encoding.map((p) => p.id);
    // }
    return result
  }

  templatedLink(object: IAmfTemplatedLink, context?: Record<string, string>): IApiTemplatedLink {
    const objectContext = context || object['@context']
    const result: IApiTemplatedLink = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
      mapping: [],
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const operationId = this._getValue(object, ns.aml.vocabularies.apiContract.operationId, objectContext)
    if (operationId && typeof operationId === 'string') {
      result.operationId = operationId
    }
    let server = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.server, objectContext) as keyof IAmfTemplatedLink
    ] as IAmfServer[] | IAmfServer | undefined
    if (server) {
      if (Array.isArray(server)) {
        ;[server] = server
      }
      result.server = this.server(server, objectContext)
    }
    let mapping = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.mapping, objectContext) as keyof IAmfTemplatedLink
    ] as IAmfIriTemplateMapping[] | undefined
    if (mapping) {
      if (mapping && !Array.isArray(mapping)) {
        mapping = [mapping]
      }
      if (mapping) {
        result.mapping = mapping.map((item) => this.iriTemplateMapping(item, objectContext))
      }
    }
    // if (!template.isNullOrEmpty) {
    //   result.template = template.value();
    // }
    // if (!requestBody.isNullOrEmpty) {
    //   result.requestBody = requestBody.value();
    // }
    return result
  }

  iriTemplateMapping(object: IAmfIriTemplateMapping, context?: Record<string, string>): IApiIriTemplateMapping {
    const objectContext = context || object['@context']
    const result: IApiIriTemplateMapping = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }

    const templateVariable = this._getValue(object, ns.aml.vocabularies.apiContract.templateVariable, objectContext)
    if (templateVariable && typeof templateVariable === 'string') {
      result.templateVariable = templateVariable
    }
    const linkExpression = this._getValue(object, ns.aml.vocabularies.apiContract.linkExpression, objectContext)
    if (linkExpression && typeof linkExpression === 'string') {
      result.linkExpression = linkExpression
    }
    return result
  }

  parametrizedSecurityScheme(
    object: IAmfParametrizedSecurityScheme,
    context?: Record<string, string>
  ): IApiParametrizedSecurityScheme {
    const objectContext = context || object['@context']
    const result: IApiParametrizedSecurityScheme = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    let scheme = object[
      this._getAmfKey(ns.aml.vocabularies.security.scheme, objectContext) as keyof IAmfParametrizedSecurityScheme
    ] as IAmfSecurityScheme | IAmfSecurityScheme[] | undefined
    if (scheme) {
      if (Array.isArray(scheme)) {
        ;[scheme] = scheme
      }
      result.scheme = this.securityScheme(scheme, objectContext)
    }
    let settings = object[
      this._getAmfKey(ns.aml.vocabularies.security.settings, objectContext) as keyof IAmfParametrizedSecurityScheme
    ] as IAmfSettings | IAmfSettings[] | undefined
    if (settings) {
      if (Array.isArray(settings)) {
        ;[settings] = settings
      }
      result.settings = this.securitySettings(settings, objectContext)
    }
    return result
  }

  securitySchemeListItem(object: IAmfSecurityScheme, context?: Record<string, string>): IApiSecuritySchemeListItem {
    const objectContext = context || object['@context']

    const result: IApiSecuritySchemeListItem = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      type: '',
    }

    const type = this._getValue(object, ns.aml.vocabularies.security.type, objectContext)
    if (type && typeof type === 'string') {
      result.type = type
    }
    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const displayName = this._getValue(object, ns.aml.vocabularies.core.displayName, objectContext)
    if (displayName && typeof displayName === 'string') {
      result.displayName = displayName
    }
    return result
  }

  securityScheme(object: IAmfSecurityScheme, context?: Record<string, string>): IApiSecurityScheme {
    const objectContext = context || object['@context']
    const result: IApiSecurityScheme = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
      headers: [],
      queryParameters: [],
      responses: [],
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const displayName = this._getValue(object, ns.aml.vocabularies.core.displayName, objectContext)
    if (displayName && typeof displayName === 'string') {
      result.displayName = displayName
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const type = this._getValue(object, ns.aml.vocabularies.security.type, objectContext)
    if (type && typeof type === 'string') {
      result.type = type
    }
    let settings = object[
      this._getAmfKey(ns.aml.vocabularies.security.settings, objectContext) as keyof IAmfSecurityScheme
    ] as IAmfSettings[] | IAmfSettings | undefined
    if (settings) {
      if (Array.isArray(settings)) {
        ;[settings] = settings
      }
      result.settings = this.securitySettings(settings, objectContext)
    }
    let queryString = object[
      this._getAmfKey(ns.aml.vocabularies.apiContract.queryString, objectContext) as keyof IAmfSecurityScheme
    ] as IAmfShape[] | IAmfShape | undefined
    if (queryString) {
      if (Array.isArray(queryString)) {
        ;[queryString] = queryString
      }
      result.queryString = this.unknownShape(queryString, undefined, objectContext)
    }
    const headers = this[getArrayItems](object, ns.aml.vocabularies.apiContract.header, objectContext)
    if (Array.isArray(headers) && headers.length) {
      result.headers = headers.map((p) => this.parameter(p, objectContext))
    }
    const queryParameters = this[getArrayItems](object, ns.aml.vocabularies.apiContract.parameter, objectContext)
    if (Array.isArray(queryParameters) && queryParameters.length) {
      result.queryParameters = queryParameters.map((p) => this.parameter(p, objectContext))
    }
    const responses = this[getArrayItems](object, ns.aml.vocabularies.apiContract.response, objectContext) as
      | IAmfResponse[]
      | undefined
    if (Array.isArray(responses) && responses.length) {
      result.responses = responses.map((p) => this.response(p, objectContext))
    }
    return result
  }

  securityRequirement(object: IAmfSecurityRequirement, context?: Record<string, string>): IApiSecurityRequirement {
    const objectContext = context || object['@context']
    const result: IApiSecurityRequirement = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
      schemes: [],
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const schemes = object[
      this._getAmfKey(ns.aml.vocabularies.security.schemes, objectContext) as keyof IAmfSecurityRequirement
    ] as IAmfParametrizedSecurityScheme[] | undefined
    if (Array.isArray(schemes) && schemes.length) {
      result.schemes = schemes.map((p) => this.parametrizedSecurityScheme(p, objectContext))
    }
    return result
  }

  securitySettings(object: IAmfSettings, context?: Record<string, string>): IApiSecuritySettingsUnion {
    const objectContext = context || object['@context']
    const types = this.readTypes(object['@type'], objectContext)
    if (types.includes(ns.aml.vocabularies.security.OAuth1Settings)) {
      return this.oAuth1Settings(object as IAmfOAuth1Settings, objectContext)
    }
    if (types.includes(ns.aml.vocabularies.security.OAuth2Settings)) {
      return this.oAuth2Settings(object as IAmfOAuth2Settings, objectContext)
    }
    if (types.includes(ns.aml.vocabularies.security.ApiKeySettings)) {
      return this.apiKeySettings(object as IAmfApiKeySettings, objectContext)
    }
    if (types.includes(ns.aml.vocabularies.security.HttpSettings)) {
      return this.httpSettings(object as IAmfHttpSettings, objectContext)
    }
    if (types.includes(ns.aml.vocabularies.security.OpenIdConnectSettings)) {
      return this.openIdConnectSettings(object as IAmfOpenIdConnectSettings, objectContext)
    }
    return this.settings(object, objectContext)
  }

  /**
   * @param {IAmfSettings} object
   * @param context
   * @returns {IApiSecuritySettings}
   */
  settings(object: IAmfSettings, context?: Record<string, string>): IApiSecuritySettings {
    const objectContext = context || object['@context']
    const result: IApiSecuritySettings = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }
    // if (additionalProperties && additionalProperties.id) {
    //   result.additionalProperties = this.unknownDataNode(additionalProperties);
    // }
    return result
  }

  oAuth1Settings(object: IAmfOAuth1Settings, context?: Record<string, string>): IApiSecurityOAuth1Settings {
    const objectContext = context || object['@context']
    const result = this.settings(object, objectContext) as IApiSecurityOAuth1Settings

    const authorizationUri = this._getValue(object, ns.aml.vocabularies.security.authorizationUri, objectContext)
    if (authorizationUri && typeof authorizationUri === 'string') {
      result.authorizationUri = authorizationUri
    }
    const requestTokenUri = this._getValue(object, ns.aml.vocabularies.security.requestTokenUri, objectContext)
    if (requestTokenUri && typeof requestTokenUri === 'string') {
      result.requestTokenUri = requestTokenUri
    }
    const tokenCredentialsUri = this._getValue(object, ns.aml.vocabularies.security.tokenCredentialsUri, objectContext)
    if (tokenCredentialsUri && typeof tokenCredentialsUri === 'string') {
      result.tokenCredentialsUri = tokenCredentialsUri
    }
    const signatures = this._getValueArray(object, ns.aml.vocabularies.security.signature, objectContext) as string[]
    if (Array.isArray(signatures) && signatures.length) {
      result.signatures = signatures
    } else {
      result.signatures = []
    }
    return result
  }

  oAuth2Settings(object: IAmfOAuth2Settings, context?: Record<string, string>): IApiSecurityOAuth2Settings {
    const objectContext = context || object['@context']
    const result = this.settings(object, objectContext) as IApiSecurityOAuth2Settings

    const grants = this._getValueArray(
      object,
      ns.aml.vocabularies.security.authorizationGrant,
      objectContext
    ) as string[]
    if (Array.isArray(grants) && grants.length) {
      result.authorizationGrants = grants
    } else {
      result.authorizationGrants = []
    }
    const flows = object[
      this._getAmfKey(ns.aml.vocabularies.security.flows, objectContext) as keyof IAmfOAuth2Settings
    ] as IAmfOAuth2Flow[] | undefined
    if (Array.isArray(flows) && flows.length) {
      result.flows = flows.map((p) => this.oAuth2Flow(p, objectContext))
    } else {
      result.flows = []
    }
    return result
  }

  oAuth2Flow(object: IAmfOAuth2Flow, context?: Record<string, string>): IApiSecurityOAuth2Flow {
    const objectContext = context || object['@context']
    const result: IApiSecurityOAuth2Flow = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      scopes: [],
      sourceMaps: this.sourceMap(object, objectContext),
    }

    const authorizationUri = this._getValue(object, ns.aml.vocabularies.security.authorizationUri, objectContext)
    if (authorizationUri && typeof authorizationUri === 'string') {
      result.authorizationUri = authorizationUri
    }
    const accessTokenUri = this._getValue(object, ns.aml.vocabularies.security.accessTokenUri, objectContext)
    if (accessTokenUri && typeof accessTokenUri === 'string') {
      result.accessTokenUri = accessTokenUri
    }
    const flow = this._getValue(object, ns.aml.vocabularies.security.flow, objectContext)
    if (flow && typeof flow === 'string') {
      result.flow = flow
    }
    const refreshUri = this._getValue(object, ns.aml.vocabularies.security.refreshUri, objectContext)
    if (refreshUri && typeof refreshUri === 'string') {
      result.refreshUri = refreshUri
    }
    const scopes = object[
      this._getAmfKey(ns.aml.vocabularies.security.scope, objectContext) as keyof IAmfOAuth2Flow
    ] as IAmfScope[] | undefined
    if (Array.isArray(scopes) && scopes.length) {
      result.scopes = scopes.map((p) => this.scope(p, objectContext))
    }
    return result
  }

  scope(object: IAmfScope, context?: Record<string, string>): IApiSecurityScope {
    const objectContext = context || object['@context']
    const result: IApiSecurityScope = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    return result
  }

  apiKeySettings(object: IAmfApiKeySettings, context?: Record<string, string>): IApiSecurityApiKeySettings {
    const objectContext = context || object['@context']
    const result = this.settings(object, objectContext) as IApiSecurityApiKeySettings

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const inParam = this._getValue(object, ns.aml.vocabularies.security.in, objectContext)
    if (inParam && typeof inParam === 'string') {
      result.in = inParam
    }
    return result
  }

  httpSettings(object: IAmfHttpSettings, context?: Record<string, string>): IApiSecurityHttpSettings {
    const objectContext = context || object['@context']
    const result = this.settings(object, objectContext) as IApiSecurityHttpSettings

    const scheme = this._getValue(object, ns.aml.vocabularies.security.scheme, objectContext)
    if (scheme && typeof scheme === 'string') {
      result.scheme = scheme
    }
    const bearerFormat = this._getValue(object, ns.aml.vocabularies.security.bearerFormat, objectContext)
    if (bearerFormat && typeof bearerFormat === 'string') {
      result.bearerFormat = bearerFormat
    }
    return result
  }

  openIdConnectSettings(
    object: IAmfOpenIdConnectSettings,
    context?: Record<string, string>
  ): IApiSecurityOpenIdConnectSettings {
    const objectContext = context || object['@context']
    const result = this.settings(object, objectContext) as IApiSecurityOpenIdConnectSettings

    const url = this._getValue(object, ns.aml.vocabularies.security.openIdConnectUrl, objectContext)
    if (url && typeof url === 'string') {
      result.url = url
    }
    return result
  }

  /**
   * Serializes source maps, when available.
   */
  sourceMap(object: IAmfDocumentSourceMaps, context?: Record<string, string>): IApiDocumentSourceMaps | undefined {
    const objectContext = context || object['@context']
    let sm = object[
      this._getAmfKey(ns.aml.vocabularies.docSourceMaps.sources, objectContext) as keyof IAmfDocumentSourceMaps
    ] as IAmfDocumentSourceMaps[] | IAmfDocumentSourceMaps | undefined
    if (!sm) {
      return undefined
    }
    if (Array.isArray(sm)) {
      ;[sm] = sm
    }
    const result: IApiDocumentSourceMaps = {
      id: sm['@id'],
      types: this.readTypes(sm['@type']),
    }
    const synthesizedField = sm[
      this._getAmfKey(ns.aml.vocabularies.docSourceMaps.synthesizedField, objectContext) as keyof IAmfDocumentSourceMaps
    ] as IAmfSynthesizedField[] | undefined
    if (Array.isArray(synthesizedField) && synthesizedField.length) {
      result.synthesizedField = synthesizedField.map((i) => this.synthesizedField(i, objectContext))
    }
    const lexical = sm[
      this._getAmfKey(ns.aml.vocabularies.docSourceMaps.lexical, objectContext) as keyof IAmfDocumentSourceMaps
    ] as IAmfSynthesizedField[] | undefined
    if (Array.isArray(lexical) && lexical.length) {
      result.lexical = lexical.map((i) => this.synthesizedField(i, objectContext))
    }
    const trackedElement = sm[
      this._getAmfKey(ns.aml.vocabularies.docSourceMaps.trackedElement, objectContext) as keyof IAmfDocumentSourceMaps
    ] as IAmfSynthesizedField[] | undefined
    if (Array.isArray(trackedElement) && trackedElement.length) {
      result.trackedElement = this.synthesizedField(trackedElement[0], objectContext)
    }
    const autoName = sm[
      this._getAmfKey(
        ns.aml.vocabularies.docSourceMaps.autoGeneratedName,
        objectContext
      ) as keyof IAmfDocumentSourceMaps
    ] as IAmfSynthesizedField[] | undefined
    if (Array.isArray(autoName) && autoName.length) {
      result.autoGeneratedName = autoName.map((i) => this.synthesizedField(i, objectContext))
    }
    const jsonSchema = sm[
      this._getAmfKey(ns.aml.vocabularies.docSourceMaps.parsedJsonSchema, objectContext) as keyof IAmfDocumentSourceMaps
    ] as IAmfSynthesizedField[] | undefined
    if (Array.isArray(jsonSchema) && jsonSchema.length) {
      result.parsedJsonSchema = this.synthesizedField(jsonSchema[0], objectContext)
    }
    const declaredElement = sm[
      this._getAmfKey(ns.aml.vocabularies.docSourceMaps.declaredElement, objectContext) as keyof IAmfDocumentSourceMaps
    ] as IAmfSynthesizedField[] | undefined
    if (Array.isArray(declaredElement) && declaredElement.length) {
      result.declaredElement = this.synthesizedField(declaredElement[0], objectContext)
    }
    return result
  }

  synthesizedField(object: IAmfSynthesizedField, context?: Record<string, string>): IApiSynthesizedField {
    // compact model
    if (typeof object === 'string') {
      return {
        id: 'synthesizedField/generated',
        value: object,
      } as IApiSynthesizedField
    }
    const result: IApiSynthesizedField = {
      id: object['@id'],
      value: '',
    }
    const element = this._getValue(object as IAmfDomainElement, ns.aml.vocabularies.docSourceMaps.element, context) as
      | string
      | undefined
    if (typeof element === 'string') {
      result.element = element
    }
    const value = this._getValue(object as IAmfDomainElement, ns.aml.vocabularies.docSourceMaps.value, context) as
      | string
      | undefined
    if (typeof value === 'string') {
      result.value = value
    }
    return result
  }

  parametrizedDeclaration(
    object: IAmfParametrizedDeclaration,
    context?: Record<string, string>
  ): IApiParametrizedDeclaration {
    const objectContext = context || object['@context']
    const result: IApiParametrizedDeclaration = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      variables: [],
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
    }

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext)
    if (name && typeof name === 'string') {
      result.name = name
    }
    const variables = object[
      this._getAmfKey(ns.aml.vocabularies.document.variable, objectContext) as keyof IAmfParametrizedDeclaration
    ] as IAmfVariableValue[] | undefined
    if (Array.isArray(variables)) {
      variables.forEach((item) => {
        result.variables.push(this.variableValue(item, objectContext))
      })
    }
    const targets = object[
      this._getAmfKey(ns.aml.vocabularies.document.target, objectContext) as keyof IAmfParametrizedDeclaration
    ] as IAmfAbstractDeclaration[] | undefined
    if (Array.isArray(targets) && targets.length) {
      const [target] = targets
      result.target = this.abstractDeclaration(target, objectContext)
    }
    return result
  }

  parametrizedTrait(object: IAmfParametrizedTrait, context?: Record<string, string>): IApiParametrizedTrait {
    const objectContext = context || object['@context']
    const result = this.parametrizedDeclaration(object, objectContext) as IApiParametrizedTrait
    return result
  }

  parametrizedResourceType(
    object: IAmfParametrizedResourceType,
    context?: Record<string, string>
  ): IApiParametrizedResourceType {
    const objectContext = context || object['@context']
    const result = this.parametrizedDeclaration(object, objectContext) as IApiParametrizedResourceType
    return result
  }

  variableValue(object: IAmfVariableValue, context?: Record<string, string>): IApiVariableValue {
    const objectContext = context || object['@context']

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext) as string
    const result: IApiVariableValue = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
      name,
    }
    const values = object[
      this._getAmfKey(ns.aml.vocabularies.document.value, objectContext) as keyof IAmfVariableValue
    ] as IAmfDataNode[] | undefined
    if (Array.isArray(values)) {
      const [item] = values
      result.value = this.unknownDataNode(item, objectContext)
    }
    return result
  }

  abstractDeclaration(object: IAmfAbstractDeclaration, context?: Record<string, string>): IApiAbstractDeclaration {
    const objectContext = context || object['@context']

    const name = this._getValue(object, ns.aml.vocabularies.core.name, objectContext) as string
    const result: IApiAbstractDeclaration = {
      id: object['@id'],
      types: this.readTypes(object['@type'], objectContext),
      customDomainProperties: this.customDomainProperties(object, objectContext),
      sourceMaps: this.sourceMap(object, objectContext),
      name,
      variables: [],
    }
    const variables = this._getValueArray(object, ns.aml.vocabularies.document.variable, objectContext) as string[]
    if (Array.isArray(variables)) {
      result.variables = variables
    }
    const description = this._getValue(object, ns.aml.vocabularies.core.description, objectContext)
    if (description && typeof description === 'string') {
      result.description = description
    }
    const dataNode = object[
      this._getAmfKey(ns.aml.vocabularies.document.dataNode, objectContext) as keyof IAmfAbstractDeclaration
    ] as IAmfDataNode[] | undefined
    if (Array.isArray(dataNode)) {
      const [item] = dataNode
      result.dataNode = this.unknownDataNode(item, objectContext)
    }
    return result
  }

  /**
   * @param object The EndPoint to serialize as a list item.
   * @returns Serialized EndPoint as a list item.
   */
  endPointWithOperationsListItem(
    object: IAmfEndPoint,
    context?: Record<string, string>
  ): IApiEndPointWithOperationsListItem {
    const path = this._getValue(object, ns.aml.vocabularies.apiContract.path, context) as string
    const result: IApiEndPointWithOperationsListItem = {
      id: object['@id'],
      path,
      operations: [],
    }
    const operations = this[getArrayItems](object, ns.aml.vocabularies.apiContract.supportedOperation, context) as
      | IAmfOperation[]
      | undefined
    if (Array.isArray(operations) && operations.length) {
      result.operations = operations.map((i) => this.operationListItem(i, context))
    }
    const name = this._getValue(object, ns.aml.vocabularies.core.name, context)
    if (name && typeof name === 'string') {
      result.name = name
    }
    return result
  }

  /**
   * @param object The Operation to serialize as a list item.
   * @param context A context to use. If not set, it looks for the context of the passed model
   * @returns Serialized Operation as a list item.
   */
  operationListItem(object: IAmfOperation, context?: Record<string, string>): IApiOperationListItem {
    const result: IApiOperationListItem = {
      id: object['@id'],
      method: '',
    }

    const method = this._getValue(object, ns.aml.vocabularies.apiContract.method, context)
    if (method && typeof method === 'string') {
      result.method = method
    }
    const name = this._getValue(object, ns.aml.vocabularies.core.name, context)
    if (name && typeof name === 'string') {
      result.name = name
    }
    return result
  }
}
