import { test } from '@japa/runner'
import sinon from 'sinon'
import { AmfMixin } from '../../../src/amf/AmfMixin.js'
import { AmfNamespace } from '../../../src/amf/definitions/Namespace.js'
import { AmfLoader } from './AmfLoader.js'
import {
  IAmfDocument,
  IAmfDomainElement,
  IAmfEndPoint,
  IAmfOperation,
  IAmfServer,
  IAmfShape,
  IAmfWebApi,
} from '../../../src/amf/definitions/Amf.js'
import { nextFrame } from '../../../src/lib/timers/Timers.js'

class Helper extends AmfMixin(Object) {}

const loader = new AmfLoader()
const asyncApi = 'async-api'
const helperApiApi = 'amf-helper-api'

test.group('amf setter/getter', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(async () => {
    element = new Helper()
  })

  test('sets _amf property', ({ assert }) => {
    element.amf = model
    assert.isTrue(element._amf === model)
  })
})

test.group('_amfChanged()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(async () => {
    element = new Helper()
  })

  test('calls the function when amf property change', ({ assert }) => {
    const spy = sinon.spy(element, '_amfChanged')
    element.amf = model
    assert.isTrue(spy.args[0][0] === model)
  })

  test('calls the function only once', ({ assert }) => {
    const spy = sinon.spy(element, '_amfChanged')
    element.amf = model
    element.amf = model
    assert.equal(spy.callCount, 1)
  })
})

test.group('_getAmfKey()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(async () => {
    element = new Helper()
    element.amf = model
  })

  test('returns empty string when no argument', ({ assert }) => {
    const result = element._getAmfKey(undefined)
    assert.equal(result, '')
  })

  test('returns the passed property when no amf', ({ assert }) => {
    element.amf = undefined
    const result = element._getAmfKey(AmfNamespace.aml.vocabularies.core.description)
    assert.equal(result, AmfNamespace.aml.vocabularies.core.description)
  })

  test('returns a value for a property', ({ assert }) => {
    const result = element._getAmfKey(AmfNamespace.aml.vocabularies.core.description)
    assert.equal(result.split(':')[1], 'description')
  })
})

test.group('_getValue()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('returns undefined if no arguments', ({ assert }) => {
    // @ts-expect-error Used in testing
    assert.isUndefined(element._getValue(undefined, undefined))
  })

  test('returns undefined if no model argument', ({ assert }) => {
    // @ts-expect-error Used in testing
    assert.isUndefined(element._getValue(undefined, 'test'))
  })

  test('returns undefined if no key argument', ({ assert }) => {
    assert.isUndefined(
      element._getValue(
        {
          '@id': 'amf://1',
          '@type': ['test'],
        },
        // @ts-expect-error Used in testing
        undefined
      )
    )
  })

  test('returns undefined if no key in object', ({ assert }) => {
    assert.isUndefined(
      element._getValue(
        /** @type DomainElement */ {
          // @ts-expect-error Used in testing
          'a': [],
          'b': [],
          '@id': 'amf://1',
          '@type': ['test'],
        },
        'c'
      )
    )
  })

  test('returns undefined if no value in value array', ({ assert }) => {
    assert.isUndefined(
      element._getValue(
        /** @type DomainElement */ {
          '@id': '1',
          '@type': ['test'],
          // @ts-expect-error Used in testing
          'a': [],
        },
        'a'
      )
    )
  })

  test('returns the value', ({ assert }) => {
    assert.equal(
      element._getValue(
        /** @type DomainElement */ {
          '@id': 'amf://1',
          '@type': ['test'],
          // @ts-expect-error Used in testing
          'a': [
            {
              '@value': 'test',
            },
          ],
        },
        'a'
      ),
      'test'
    )
  })

  test('returns primitive value from compact model', ({ assert }) => {
    assert.equal(
      element._getValue(
        /** @type DomainElement */ {
          '@id': 'amf://1',
          '@type': ['test'],
          // @ts-expect-error Used in testing
          'a': 'test',
        },
        'a'
      ),
      'test'
    )
  })
})

test.group('_getValueArray()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('returns undefined if no arguments', ({ assert }) => {
    // @ts-expect-error Used in testing
    assert.isUndefined(element._getValueArray(undefined, undefined))
  })

  test('returns undefined if no model argument', ({ assert }) => {
    // @ts-expect-error Used in testing
    assert.isUndefined(element._getValueArray(undefined, 'test'))
  })

  test('returns undefined if no key argument', ({ assert }) => {
    assert.isUndefined(
      element._getValueArray(
        {
          '@id': 'amf://1',
          '@type': ['test'],
        },
        // @ts-expect-error Used in testing
        undefined
      )
    )
  })

  test('returns undefined if no key in object', ({ assert }) => {
    assert.isUndefined(
      element._getValueArray(
        /** @type DomainElement */ {
          // @ts-expect-error Used in testing
          'a': [],
          'b': [],
          '@id': 'amf://1',
          '@type': ['test'],
        },
        'c'
      )
    )
  })

  test('returns empty array if no value in value array', ({ assert }) => {
    assert.deepEqual(
      element._getValueArray(
        /** @type DomainElement */ {
          // @ts-expect-error Used in testing
          'a': [],
          '@id': 'amf://1',
          '@type': ['test'],
        },
        'a'
      ),
      []
    )
  })

  test('returns the values', ({ assert }) => {
    assert.deepEqual(
      element._getValueArray(
        /** @type DomainElement */ {
          // @ts-expect-error Used in testing
          'a': [
            {
              '@value': 'test',
            },
            {
              '@value': 'test2',
            },
          ],
          '@id': 'amf://1',
          '@type': ['test'],
        },
        'a'
      ),
      ['test', 'test2']
    )
  })

  test('returns values for non object values', ({ assert }) => {
    assert.deepEqual(
      element._getValueArray(
        /** @type DomainElement */ {
          // @ts-expect-error Used in testing
          'a': ['test', 'test2'],
          '@id': 'amf://1',
          '@type': ['test'],
        },
        'a'
      ),
      ['test', 'test2']
    )
  })
})

test.group('_hasType()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('returns false if no arguments', ({ assert }) => {
    assert.isFalse(element._hasType(undefined, undefined))
  })

  test('returns false if no model argument', ({ assert }) => {
    assert.isFalse(element._hasType(undefined, 'test'))
  })

  test('returns false if no key argument', ({ assert }) => {
    assert.isFalse(
      element._hasType(
        {
          '@id': 'amf://1',
          '@type': ['test'],
        },
        undefined
      )
    )
  })

  test('returns false if type does not match', ({ assert }) => {
    assert.isFalse(
      element._hasType(
        {
          '@type': ['a', 'b'],
          '@id': 'amf://1',
        },
        'c'
      )
    )
  })

  test('returns true if type does match', ({ assert }) => {
    assert.isTrue(
      element._hasType(
        {
          '@type': ['a', 'b', 'c'],
          '@id': 'amf://1',
        },
        'c'
      )
    )
  })
})

test.group('_hasProperty()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('returns false if no arguments', ({ assert }) => {
    // @ts-expect-error Used in testing
    assert.isFalse(element._hasProperty(undefined, undefined))
  })

  test('returns false if no model argument', ({ assert }) => {
    // @ts-expect-error Used in testing
    assert.isFalse(element._hasProperty(undefined, 'test'))
  })

  test('returns false if no key argument', ({ assert }) => {
    assert.isFalse(
      element._hasProperty(
        {
          '@id': 'amf://1',
          '@type': ['test'],
        },
        // @ts-expect-error Used in testing
        undefined
      )
    )
  })

  test('returns false if type does not have property', ({ assert }) => {
    assert.isFalse(
      element._hasProperty(
        /** @type DomainElement */ {
          // @ts-expect-error Used in testing
          'a': 'test',
          'b': 'test',
          '@id': 'amf://1',
          '@type': ['test'],
        },
        'c'
      )
    )
  })

  test('returns true if have a property', ({ assert }) => {
    assert.isTrue(
      element._hasProperty(
        /** @type DomainElement */ {
          // @ts-expect-error Used in testing
          'a': 'test',
          'b': 'test',
          'c': 'test',
          '@id': 'amf://1',
          '@type': ['test'],
        },
        'c'
      )
    )
  })
})

test.group('_computePropertyArray()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('returns undefined if no arguments', ({ assert }) => {
    assert.isUndefined(element._computePropertyArray(undefined, undefined))
  })

  test('returns undefined if no model argument', ({ assert }) => {
    assert.isUndefined(element._computePropertyArray(undefined, 'test'))
  })

  test('returns undefined if no key argument', ({ assert }) => {
    assert.isUndefined(
      element._computePropertyArray(
        {
          '@id': 'amf://1',
          '@type': ['test'],
        },
        undefined
      )
    )
  })

  test('returns array', ({ assert }) => {
    assert.deepEqual(
      element._computePropertyArray(
        /** @type DomainElement */ {
          // @ts-expect-error Used in testing
          'test': ['a', 'b', 'c'],
          '@id': 'amf://1',
          '@type': ['test'],
        },
        'test'
      ),
      ['a', 'b', 'c']
    )
  })
})

test.group('_computeEncodes()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('returns undefined if no argument', ({ assert }) => {
    assert.isUndefined(element._computeEncodes(undefined))
  })

  test('returns undefined if no encodes', ({ assert }) => {
    assert.isUndefined(
      element._computeEncodes({
        '@id': 'amf://1',
        '@type': ['test'],
      })
    )
  })

  test('returns an array from AMF model', ({ assert }) => {
    const result = element._computeEncodes(model)
    assert.typeOf(result, 'object')
  })
})

test.group('_computeDeclares()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('returns undefined if no argument', ({ assert }) => {
    assert.isUndefined(element._computeDeclares(undefined))
  })

  test('returns undefined if no declares', ({ assert }) => {
    assert.isUndefined(
      element._computeDeclares({
        '@id': 'amf://1',
        '@type': ['test'],
      })
    )
  })

  test('returns undefined argument is empty array', ({ assert }) => {
    // @ts-expect-error Used in testing
    assert.isUndefined(element._computeDeclares([]))
  })

  test('returns an array from AMF model', ({ assert }) => {
    const result = element._computeDeclares(model)
    assert.typeOf(result, 'array')
  })

  test('returns all items in the array', ({ assert }) => {
    const result = element._computeDeclares(model) as IAmfDomainElement[]
    assert.isAbove(result.length, 1)
  })
})

test.group('_computeReferences()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('returns undefined if no argument', ({ assert }) => {
    assert.isUndefined(element._computeReferences(undefined))
  })

  test('returns undefined argument is empty array', ({ assert }) => {
    // @ts-expect-error Used in testing
    assert.isUndefined(element._computeReferences([]))
  })

  test('returns undefined if no references', ({ assert }) => {
    assert.isUndefined(
      element._computeReferences({
        '@id': 'amf://1',
        '@type': ['test'],
      })
    )
  })

  test('returns an array from AMF model', ({ assert }) => {
    const result = element._computeReferences(model)
    assert.typeOf(result, 'array')
  })

  test('returns all items in the array', ({ assert }) => {
    const result = element._computeReferences(model)
    assert.isNotEmpty(result)
  })
})

test.group('_computeWebApi()', (group) => {
  let model: IAmfDocument
  let asyncModel: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
    asyncModel = await loader.getGraph(asyncApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('returns undefined if no argument', ({ assert }) => {
    assert.isUndefined(element._computeWebApi(undefined))
  })

  test('returns undefined if no encodes', ({ assert }) => {
    assert.isUndefined(
      element._computeWebApi({
        '@id': 'amf://1',
        '@type': ['test'],
      })
    )
  })

  test('returns undefined if no WebApi', ({ assert }) => {
    const key = element._getAmfKey(AmfNamespace.aml.vocabularies.document.encodes)
    const amfModel = {
      '@id': 'amf://1',
      '@type': ['test'],
    }
    // @ts-expect-error Used in testing
    amfModel[key] = {}
    assert.isUndefined(element._computeWebApi(amfModel))
  })

  test('returns an object from AMF model', ({ assert }) => {
    const result = element._computeWebApi(model)
    assert.typeOf(result, 'object')
  })

  test('should return undefined for AsyncAPI model', async ({ assert }) => {
    element.amf = asyncModel
    assert.isUndefined(element._computeWebApi(asyncModel))
  })
})

test.group('_computeApi()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('should return undefined if no argument', ({ assert }) => {
    assert.isUndefined(element._computeApi(undefined))
  })

  test('should return undefined if no encodes', ({ assert }) => {
    assert.isUndefined(
      element._computeApi({
        '@id': 'amf://1',
        '@type': ['test'],
      })
    )
  })

  test('should return undefined if not API', ({ assert }) => {
    const key = element._getAmfKey(AmfNamespace.aml.vocabularies.document.encodes)
    const amfModel = {
      '@id': 'amf://1',
      '@type': ['test'],
    }
    // @ts-expect-error Used in testing
    amfModel[key] = {}
    assert.isUndefined(element._computeApi(amfModel))
  })
})

test.group('_computeApi() / WebAPI', (group) => {
  let model: IAmfDocument
  let asyncModel: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
    asyncModel = await loader.getGraph(asyncApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('should return encodes node from AMF model', ({ assert }) => {
    const result = element._computeApi(asyncModel)
    assert.typeOf(result, 'object')
  })

  test('should return encodes if API type is missing but WebAPI type is present', ({ assert }) => {
    const key = element._getAmfKey(AmfNamespace.aml.vocabularies.document.encodes)
    const webApiKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.WebAPI)
    const amfModel = {
      '@id': 'amf://1',
      '@type': ['test'],
    }
    // @ts-expect-error Used in testing
    amfModel[key] = {
      '@type': [webApiKey],
    }
    assert.typeOf(element._computeApi(amfModel), 'object')
  })
})

test.group('_computeApi() / AsyncAPI', (group) => {
  let asyncModel: IAmfDocument

  group.setup(async () => {
    asyncModel = await loader.getGraph(asyncApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = asyncModel
  })

  test('should return encodes node from AMF model', ({ assert }) => {
    const result = element._computeApi(asyncModel)
    assert.typeOf(result, 'object')
  })

  test('should return encodes if API type is missing but WebAPI type is present', ({ assert }) => {
    const key = element._getAmfKey(AmfNamespace.aml.vocabularies.document.encodes)
    const asyncApiKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.AsyncAPI)
    const amfModel = {
      '@id': 'amf://1',
      '@type': ['test'],
    }
    // @ts-expect-error Used in testing
    amfModel[key] = {
      '@type': [asyncApiKey],
    }
    assert.typeOf(element._computeApi(amfModel), 'object')
  })
})

test.group('_isWebAPI()', (group) => {
  let model: IAmfDocument
  let asyncModel: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
    asyncModel = await loader.getGraph(asyncApi)
  })

  test('should return false if no argument', ({ assert }) => {
    const element = new Helper()
    assert.isFalse(element._isWebAPI(undefined))
  })

  test('should return false if no encodes', ({ assert }) => {
    const element = new Helper()
    assert.isFalse(
      element._isWebAPI({
        '@id': 'amf://1',
        '@type': ['test'],
      })
    )
  })

  test('should return false for AsyncAPI', async ({ assert }) => {
    const element = new Helper()
    element.amf = asyncModel
    assert.isFalse(element._isWebAPI(asyncModel))
  })

  test('should return true for WebAPI', async ({ assert }) => {
    const element = new Helper()
    element.amf = model
    assert.isTrue(element._isWebAPI(model))
  })
})

test.group('_isAsyncAPI()', (group) => {
  let model: IAmfDocument
  let asyncModel: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
    asyncModel = await loader.getGraph(asyncApi)
  })

  test('should return false if no argument', ({ assert }) => {
    const element = new Helper()
    assert.isFalse(element._isAsyncAPI(undefined))
  })

  test('should return false if no encodes', ({ assert }) => {
    const element = new Helper()
    assert.isFalse(
      element._isAsyncAPI({
        '@id': 'amf://1',
        '@type': ['test'],
      })
    )
  })

  test('should return true for AsyncAPI', async ({ assert }) => {
    const element = new Helper()
    element.amf = asyncModel
    assert.isTrue(element._isAsyncAPI(asyncModel))
  })

  test('should return false for WebAPI', async ({ assert }) => {
    const element = new Helper()
    element.amf = model
    assert.isFalse(element._isAsyncAPI(model))
  })
})

test.group('_isAPI()', (group) => {
  let model: IAmfDocument
  let asyncModel: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
    asyncModel = await loader.getGraph(asyncApi)
  })

  test('should return false if no argument', ({ assert }) => {
    const element = new Helper()
    assert.isFalse(element._isAPI(undefined))
  })

  test('should return false if no encodes', ({ assert }) => {
    const element = new Helper()
    assert.isFalse(
      element._isAPI({
        '@id': 'amf://1',
        '@type': ['test'],
      })
    )
  })

  test('should return true for AsyncAPI', async ({ assert }) => {
    const element = new Helper()
    element.amf = asyncModel
    assert.isTrue(element._isAPI(asyncModel))
  })

  test('should return true for WebAPI', async ({ assert }) => {
    const element = new Helper()
    element.amf = model
    assert.isTrue(element._isAPI(model))
  })
})

test.group('_computeEndpoints()', (group) => {
  let model: IAmfDocument
  let element: Helper
  let webApi: IAmfWebApi

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
    element = new Helper()
    element.amf = model
    webApi = element._computeWebApi(model) as IAmfWebApi
  })

  test('returns a list of endpoints', ({ assert }) => {
    const result = element._computeEndpoints(webApi) as IAmfEndPoint[]
    assert.typeOf(result, 'array')
    assert.isAbove(result.length, 1)
  })
})

test.group('_computeExpects()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  let operation: IAmfOperation
  let noExpectsOperation: IAmfOperation
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
    const endpoint = loader.lookupEndpoint(model, '/changes/watch')
    const key = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.supportedOperation)
    // @ts-expect-error Used in testing
    operation = endpoint[key][0]
    // @ts-expect-error Used in testing
    noExpectsOperation = endpoint[key][1]
  })

  test('returns undefined if no argument', ({ assert }) => {
    assert.isUndefined(element._computeExpects(undefined))
  })

  test('returns undefined if operation does not have "expects"', ({ assert }) => {
    assert.isUndefined(element._computeExpects(noExpectsOperation))
  })

  test('returns object for an operation', ({ assert }) => {
    const result = element._computeExpects(operation)
    assert.typeOf(result, 'object')
  })
})

test.group('_computeEndpointModel()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  let webApi: IAmfWebApi
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
    webApi = element._computeWebApi(model) as IAmfWebApi
  })

  test('returns undefined if no arguments', ({ assert }) => {
    assert.isUndefined(element._computeEndpointModel(undefined, undefined))
  })

  test('returns undefined if no model argument', ({ assert }) => {
    assert.isUndefined(element._computeEndpointModel(undefined, 'test'))
  })

  test('returns undefined if no selected argument', ({ assert }) => {
    assert.isUndefined(element._computeEndpointModel(webApi, undefined))
  })

  test('returns undefined if selection does not exists', ({ assert }) => {
    assert.isUndefined(element._computeEndpointModel(webApi, 'hello'))
  })

  test('returns object for endpoint', ({ assert }) => {
    const endpoint = loader.lookupEndpoint(model, '/changes/watch')
    const id = endpoint['@id']
    const result = element._computeEndpointModel(webApi, id) as IAmfEndPoint
    assert.typeOf(result, 'object')
    const type = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.EndPoint)
    assert.equal(result['@type'][0], type)
  })
})

test.group('_computeMethodModel()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  let webApi: IAmfWebApi
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
    webApi = element._computeWebApi(model) as IAmfWebApi
  })

  test('returns undefined if no arguments', ({ assert }) => {
    assert.isUndefined(element._computeMethodModel(undefined, undefined))
  })

  test('returns undefined if no model argument', ({ assert }) => {
    assert.isUndefined(element._computeMethodModel(undefined, 'test'))
  })

  test('returns undefined if no selected argument', ({ assert }) => {
    assert.isUndefined(element._computeMethodModel(webApi, undefined))
  })

  test('returns undefined if selection does not exists', ({ assert }) => {
    assert.isUndefined(element._computeMethodModel(webApi, 'hello'))
  })

  test('returns object for an operation', ({ assert }) => {
    const endpoint = loader.lookupEndpoint(model, '/permissionIds/{email}')
    // @ts-expect-error Used in testing
    const operation = endpoint[loader._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.supportedOperation)][0]

    const result = element._computeMethodModel(webApi, operation['@id']) as IAmfOperation
    assert.typeOf(result, 'object')
    const type = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.Operation)
    assert.equal(result['@type'][0], type)
  })
})

test.group('_computeType()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  let references: IAmfDomainElement[]
  let declares: IAmfDomainElement[]
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
    declares = element._computeDeclares(model) as IAmfDomainElement[]
    references = element._computeReferences(model) as IAmfDomainElement[]
  })

  test('returns undefined if no arguments', ({ assert }) => {
    assert.isUndefined(element._computeType(undefined, undefined, undefined))
  })

  test('returns undefined if no model argument', ({ assert }) => {
    assert.isUndefined(element._computeType(undefined, undefined, 'test'))
  })

  test('returns undefined if no selected argument', ({ assert }) => {
    assert.isUndefined(element._computeType(declares, references, undefined))
  })

  test('returns undefined if selection does not exists', ({ assert }) => {
    assert.isUndefined(element._computeType(declares, references, 'not-here'))
  })

  // test('returns type in declarations', ({ assert }) => {
  //   const id = declares[1]['@id']; // Node shape.
  //   const result = element._computeType(declares, undefined, id);
  //   assert.typeOf(result, 'object');
  //   const type = element._getAmfKey(AmfNamespace.w3.shacl.NodeShape);
  //   assert.equal(result['@type'][0], type);
  // });

  // test('returns type for non-compact id', ({ assert }) => {
  //   if (!compact) {
  //     // This only affects compact model.
  //     return;
  //   }
  //   const id = `amf://id${  declares[1]['@id']}`;
  //   const result = element._computeType(declares, undefined, id);
  //   assert.typeOf(result, 'object');
  //   const type = element._getAmfKey(AmfNamespace.w3.shacl.NodeShape);
  //   assert.equal(result['@type'][0], type);
  // });

  test('returns type in references (library)', ({ assert }) => {
    const dKey = element._getAmfKey(AmfNamespace.aml.vocabularies.document.declares)
    const library = references.find((unit) =>
      unit['@type'].find((t) => t.indexOf('Module') !== -1)
    ) as IAmfDomainElement
    // let ref = references[4][dKey][0];
    // @ts-expect-error Used in testing
    let ref = library[dKey][0]
    if (ref instanceof Array) {
      ref = ref[0]
    }
    const id = ref['@id']
    const result = element._computeType(declares, references, id) as IAmfShape
    assert.typeOf(result, 'object')
    const type = element._getAmfKey(AmfNamespace.w3.shacl.NodeShape)
    assert.equal(result['@type'][0], type)
  })

  test('returns type in references (library) when no declarations', ({ assert }) => {
    const dKey = element._getAmfKey(AmfNamespace.aml.vocabularies.document.declares)
    const library = references.find((unit) =>
      unit['@type'].find((t) => t.indexOf('Module') !== -1)
    ) as IAmfDomainElement
    // @ts-expect-error Used in testing
    let ref = library[dKey][0]
    if (ref instanceof Array) {
      ref = ref[0]
    }
    const id = ref['@id']
    const result = element._computeType(undefined, references, id) as IAmfShape
    assert.typeOf(result, 'object')
    const type = element._getAmfKey(AmfNamespace.w3.shacl.NodeShape)
    assert.isTrue(result['@type'].includes(type))
  })
})

test.group('_getLinkTarget()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  let schemaId: string
  let resolved: IAmfDomainElement
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
    const declares = element._computeDeclares(model) as IAmfDomainElement[]
    schemaId = declares[0]['@id']
    resolved = element._getLinkTarget(model, schemaId) as IAmfDomainElement
  })

  test('computes a reference', ({ assert }) => {
    assert.typeOf(resolved, 'object')
  })

  test('resolves a reference', ({ assert }) => {
    const itemsKey = element._getAmfKey(AmfNamespace.aml.vocabularies.shapes.items)
    const nameKey = element._getAmfKey(AmfNamespace.aml.vocabularies.core.name)
    // @ts-expect-error Used in testing
    const shape = resolved[itemsKey][0]
    assert.equal(shape[nameKey][0]['@value'], 'Pic')
  }).skip()

  test('returns undefined when no amf argument', ({ assert }) => {
    const result = element._getLinkTarget(undefined, undefined)
    assert.isUndefined(result)
  })

  test('returns undefined when no id argument', ({ assert }) => {
    const result = element._getLinkTarget(model, undefined)
    assert.isUndefined(result)
  })

  test('returns undefined when id is not found', ({ assert }) => {
    const result = element._getLinkTarget(model, 'other-test')
    assert.isUndefined(result)
  })
})

test.group('_getReferenceId()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  let refId: string
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
    const refs = element._computeReferences(model) as IAmfDomainElement[]
    const ref = refs.find((unit) =>
      (unit['@type'] || []).find((t) => t.indexOf('ExternalFragment') !== -1)
    ) as IAmfDomainElement
    const enc = element._computeEncodes(ref) as IAmfDomainElement
    refId = enc['@id']
  })

  test('computes a reference', ({ assert }) => {
    const result = element._getReferenceId(model, refId) as IAmfDomainElement
    assert.typeOf(result, 'object')
    const type = element._getAmfKey(AmfNamespace.aml.vocabularies.document.ExternalDomainElement)
    assert.equal(result['@type'][0], type)
  })

  test('returns undefined when no amf argument', ({ assert }) => {
    const result = element._getReferenceId(undefined, undefined)
    assert.isUndefined(result)
  })

  test('returns undefined when no id argument', ({ assert }) => {
    const result = element._getReferenceId(model, undefined)
    assert.isUndefined(result)
  })

  test('returns undefined when no references in the model', ({ assert }) => {
    const result = element._getReferenceId(
      {
        '@id': 'amf://1',
        '@type': ['test'],
      },
      undefined
    )
    assert.isUndefined(result)
  })
})

test.group('_resolve()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('resolves link target', ({ assert }) => {
    const endpoint = loader.lookupEndpoint(model, '/referenceId')
    const opKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.supportedOperation)
    const exKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.expects)
    const plKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.payload)
    const scKey = element._getAmfKey(AmfNamespace.aml.vocabularies.shapes.schema)
    const nameKey = element._getAmfKey(AmfNamespace.w3.shacl.name)
    // @ts-expect-error Used in testing
    const op = (element._ensureArray(endpoint[opKey]) as IAmfEndPoint[])[0]
    // @ts-expect-error Used in testing
    const expects = (element._ensureArray(op[exKey]) as IAmfEndPoint[])[0]
    // @ts-expect-error Used in testing
    const payload = (element._ensureArray(expects[plKey]) as IAmfEndPoint[])[0]
    // @ts-expect-error Used in testing
    const schema = (element._ensureArray(payload[scKey]) as IAmfEndPoint[])[0]
    const result = element._resolve(schema)
    assert.typeOf(result[nameKey], 'array')
  })

  test('resolves link target for external fragment', ({ assert }) => {
    const endpoint = loader.lookupEndpoint(model, '/external-data-type')
    const opKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.supportedOperation)
    const exKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.expects)
    const plKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.payload)
    const scKey = element._getAmfKey(AmfNamespace.aml.vocabularies.shapes.schema)
    const nameKey = element._getAmfKey(AmfNamespace.w3.shacl.name)
    // @ts-expect-error Used in testing
    const op = (element._ensureArray(endpoint[opKey]) as IAmfEndPoint[])[0]
    // @ts-expect-error Used in testing
    const expects = (element._ensureArray(op[exKey]) as IAmfEndPoint[])[0]
    // @ts-expect-error Used in testing
    const payload = (element._ensureArray(expects[plKey]) as IAmfEndPoint[])[0]
    // @ts-expect-error Used in testing
    const schema = (element._ensureArray(payload[scKey]) as IAmfEndPoint[])[0]
    const result = element._resolve(schema)
    assert.typeOf(result[nameKey], 'array')
  })
})

test.group('_computeDocument()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  let obj: IAmfDomainElement
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
    const key = element._getAmfKey(AmfNamespace.aml.vocabularies.core.documentation)
    // @ts-expect-error Used in testing
    obj = {}
    // @ts-expect-error Used in testing
    obj[key] = [
      {
        '@id': 'a',
      },
    ]
  })

  test('returns undefined when no webApi', ({ assert }) => {
    const result = element._computeDocument(undefined, undefined)
    assert.isUndefined(result)
  })

  test('returns undefined when no id', ({ assert }) => {
    const result = element._computeDocument(obj, undefined)
    assert.isUndefined(result)
  })

  test('returns undefined when id not found', ({ assert }) => {
    const result = element._computeDocument(obj, 'b')
    assert.isUndefined(result)
  })

  test('returns undefined when no documents key', ({ assert }) => {
    const result = element._computeDocument(
      {
        '@id': 'amf://1',
        '@type': ['test'],
      },
      'b'
    )
    assert.isUndefined(result)
  })

  test('returns model for id', ({ assert }) => {
    const result = element._computeDocument(obj, 'a')
    assert.typeOf(result, 'object')
  })
})

test.group('_computeApiVersion()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('Computes version of the API', ({ assert }) => {
    const result = element._computeApiVersion(model)
    assert.equal(result, 'v2')
  })

  test('returns undefined when no WebApi', ({ assert }) => {
    const result = element._computeApiVersion({
      '@id': 'amf://1',
      '@type': ['test'],
    })
    assert.isUndefined(result)
  })
})

test.group('_ensureArray()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('returns undefined when no argument', ({ assert }) => {
    const result = element._ensureArray(undefined)
    assert.isUndefined(result)
  })

  test('returns the same array', ({ assert }) => {
    const arr = ['a']
    const result = element._ensureArray(arr)
    assert.isTrue(result === arr)
  })

  test('returns array value from not array argument', ({ assert }) => {
    const arr = 'a'
    const result = element._ensureArray(arr)
    assert.deepEqual(result, ['a'])
  })
})

test.group('_findById()', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('Returns undefined when no argument', ({ assert }) => {
    const result = element._findById(undefined, undefined)
    assert.isUndefined(result)
  })

  test('Returns undefined when array does not contain id', ({ assert }) => {
    const arr = [
      { '@id': '1', '@type': ['test'] },
      { '@id': '2', '@type': ['test'] },
      { '@id': '3', '@type': ['test'] },
    ]
    const result = element._findById(arr, '0')
    assert.isUndefined(result)
  })

  test('Returns object when array contains id', ({ assert }) => {
    const arr = [
      { '@id': '1', '@type': ['test'] },
      { '@id': '2', '@type': ['test'] },
      { '@id': '3', '@type': ['test'] },
    ]
    const result = element._findById(arr, '1')
    assert.deepEqual(result, { '@id': '1', '@type': ['test'] })
  })
})

test.group('_isValidServerPartial() / with mock objects / not in arrays', (group) => {
  let model: IAmfDocument
  let element: Helper

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('should return true for endpoint type', ({ assert }) => {
    const endpointKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.EndPoint)
    const shape = { '@type': [endpointKey] } as IAmfDomainElement
    assert.isTrue(element._isValidServerPartial(shape))
  })

  test('should return true for method type', ({ assert }) => {
    const methodKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.Operation)
    const shape = { '@type': [methodKey] } as IAmfDomainElement
    assert.isTrue(element._isValidServerPartial(shape))
  })

  test('should return false for any other type', ({ assert }) => {
    const otherKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.WebAPI)
    const shape = { '@type': [otherKey] } as IAmfDomainElement
    assert.isFalse(element._isValidServerPartial(shape))
  })
})

test.group('_isValidServerPartial() / with mock objects / in arrays', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('should return true for endpoint type', ({ assert }) => {
    const endpointKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.EndPoint)
    const shape = { '@type': [endpointKey] } as IAmfDomainElement
    assert.isTrue(element._isValidServerPartial([shape]))
  })

  test('should return true for method type', ({ assert }) => {
    const methodKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.Operation)
    const shape = { '@type': [methodKey] } as IAmfDomainElement
    assert.isTrue(element._isValidServerPartial([shape]))
  })

  test('should return false for any other type', ({ assert }) => {
    const otherKey = element._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.WebAPI)
    const shape = { '@type': [otherKey] } as IAmfDomainElement
    assert.isFalse(element._isValidServerPartial([shape]))
  })
})

test.group('_isValidServerPartial() with real nodes', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('should return true for endpoint type', ({ assert }) => {
    const endpoint = loader.lookupEndpoint(model, '/files')
    assert.isTrue(element._isValidServerPartial(endpoint))
  })

  test('should return true for method type', ({ assert }) => {
    const method = loader.lookupOperation(model, '/files', 'get')
    assert.isTrue(element._isValidServerPartial(method))
  })

  test('should return false for any other type', ({ assert }) => {
    assert.isFalse(element._isValidServerPartial(model))
  })
})

test.group('_mergeShapes()', (group) => {
  let model: IAmfDocument
  let sourcesKey: string
  let element: Helper

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  group.each.setup(() => {
    element = new Helper()
    element.amf = model
    sourcesKey = element._getAmfKey(AmfNamespace.aml.vocabularies.docSourceMaps.sources)
  })

  test('should merge two objects together', ({ assert }) => {
    const a = { foo: 'foo', a: 1 }
    const b = { bar: 'bar', a: 2, b: 3 }
    const merged = element._mergeShapes(a, b)
    assert.deepEqual(merged, { foo: 'foo', bar: 'bar', a: 2, b: 3 })
  })

  test('should merge sources from both nodes', ({ assert }) => {
    const a = { foo: 'foo', a: 1, [sourcesKey]: [{ s1: 1, s2: 2 }] }
    const b = { bar: 'bar', a: 2, b: 3, [sourcesKey]: [{ s2: 20, s3: 30 }] }
    const merged = element._mergeShapes(a, b)
    assert.deepEqual(merged, {
      foo: 'foo',
      bar: 'bar',
      a: 2,
      b: 3,
      [sourcesKey]: [{ s1: 1, s2: 20, s3: 30 }],
    })
  })
})

test.group('special merges / _mergeSourceMapsSources()', (group) => {
  let model: IAmfDocument
  let element: Helper
  let sourcesKey: string

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  group.each.setup(() => {
    element = new Helper()
    element.amf = model
    sourcesKey = element._getAmfKey(AmfNamespace.aml.vocabularies.docSourceMaps.sources)
  })

  test('should merge sources from both nodes', ({ assert }) => {
    const a = { foo: 'foo', a: 1, [sourcesKey]: [{ s1: 1, s2: 2 }] }
    const b = { bar: 'bar', a: 2, b: 3, [sourcesKey]: [{ s2: 20, s3: 30 }] }
    const result = element._mergeSourceMapsSources(a, b)
    assert.deepEqual(result, [{ s1: 1, s2: 20, s3: 30 }])
  })

  test('should merge nodes when only A has sources', ({ assert }) => {
    const a = { foo: 'foo', a: 1, [sourcesKey]: [{ s2: 20, s3: 30 }] }
    const b = { bar: 'bar', a: 2, b: 3 }
    const merged = element._mergeSourceMapsSources(a, b)
    assert.deepEqual(merged, [{ s2: 20, s3: 30 }])
  })

  test('should merge nodes when only B has sources', ({ assert }) => {
    const a = { foo: 'foo', a: 1 }
    const b = { bar: 'bar', a: 2, b: 3, [sourcesKey]: [{ s2: 20, s3: 30 }] }
    const merged = element._mergeSourceMapsSources(a, b)
    assert.deepEqual(merged, [{ s2: 20, s3: 30 }])
  })

  test('should return empty object when neither node has sources', ({ assert }) => {
    const a = { foo: 'foo', a: 1 }
    const b = { bar: 'bar', a: 2, b: 3 }
    const merged = element._mergeSourceMapsSources(a, b)
    assert.deepEqual(merged, [{}])
  })
})

test.group('keys computation caching', (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph(helperApiApi)
  })

  let element: Helper
  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('caches a key value', ({ assert }) => {
    const prop = AmfNamespace.aml.vocabularies.document.encodes
    const key = element._getAmfKey(prop)
    // @ts-expect-error Used in testing
    assert.equal(element.__cachedKeys[prop], key)
  })

  test('returns the same value', ({ assert }) => {
    const prop = AmfNamespace.aml.vocabularies.document.encodes
    const key1 = element._getAmfKey(prop)
    const key2 = element._getAmfKey(prop)
    assert.equal(key1, key2)
  })

  test('uses cached value', ({ assert }) => {
    const prop = AmfNamespace.aml.vocabularies.document.encodes
    element._getAmfKey(prop)
    // @ts-expect-error Used in testing
    element.__cachedKeys[prop] = 'test'
    const key = element._getAmfKey(prop)
    assert.equal(key, 'test')
  })

  test('resets cache when AMF changes', ({ assert }) => {
    const prop = AmfNamespace.aml.vocabularies.document.encodes
    element._getAmfKey(prop)
    element.amf = undefined
    // @ts-expect-error Used in testing
    assert.deepEqual(element.__cachedKeys, {})
  })
})

test.group('_getServers() / RAML', (group) => {
  let model: IAmfDocument
  let element: Helper

  group.setup(async () => {
    model = await loader.getGraph('amf-helper-api')
  })

  group.each.setup(() => {
    element = new Helper()
    element.amf = model
  })

  test('Returns all servers', ({ assert }) => {
    const servers = element._getServers({}) as IAmfServer[]
    assert.typeOf(servers, 'array')
    assert.lengthOf(servers, 1)
  })
})

test.group('_getServers() / OAS / for operation', (group) => {
  let methodId: string
  // TODO uncomment this once AMF model has resolved servers on all levels
  // const endpointId = `${compact ? '' : 'amf://id'}#22`;

  let localModel: IAmfDocument
  let element: Helper
  group.setup(async () => {
    localModel = await loader.getGraph('multiple-servers')
    methodId = loader.lookupOperation(localModel, '/pets', 'get')['@id']
  })

  group.each.setup(() => {
    element = new Helper()
    element.amf = localModel
  })

  test('Returns all servers for method', ({ assert }) => {
    const servers = element._getServers({ methodId }) as IAmfServer[]
    assert.typeOf(servers, 'array')
    assert.lengthOf(servers, 2)
  })

  test('Returns all root servers if method not found and endpoint undefined', ({ assert }) => {
    const servers = element._getServers({ methodId: 'foo' }) as IAmfServer[]
    assert.typeOf(servers, 'array')
    assert.lengthOf(servers, 2)
  })

  // TODO uncomment this once AMF model has resolved servers on all levels
  /* test('Returns all endpoint servers if method not found and endpoint is defined', ({ assert }) => {
    const servers = element._getServers({ model, methodId: 'foo', endpointId });
    assert.typeOf(servers, 'array');
    assert.lengthOf(servers, 2);
  }); */

  test('Returns undefined if no model', async ({ assert }) => {
    const localElement = new Helper()
    assert.isUndefined(localElement._getServers({}))
  })

  test('Returns all method servers for partial model', ({ assert }) => {
    const operation = { ...loader.lookupOperation(localModel, '/pets', 'get') }
    operation['@context'] = localModel['@context']
    element.amf = operation
    const servers = element._getServers({ methodId }) as IAmfServer[]
    assert.typeOf(servers, 'array')
    assert.lengthOf(servers, 2)
  })
})

test.group('Expander', async (group) => {
  let localElement: Helper
  const flattenedApi = 'flattened-api'
  const expandedApi = 'expanded-api'
  let flattenedModel: IAmfDocument
  let expandedModel: IAmfDocument

  group.setup(async () => {
    flattenedModel = await loader.getGraph(flattenedApi, { noExpand: true })
    expandedModel = await loader.getGraph(expandedApi, { noExpand: true })
  })

  group.each.setup(async () => {
    localElement = new Helper()
  })

  test('should not call __amfChanged again if same flattened model is set', async ({ assert }) => {
    localElement.amf = flattenedModel
    await nextFrame()
    const spy = sinon.spy(localElement, '_amfChanged')
    localElement.amf = flattenedModel
    await nextFrame()
    assert.isTrue(spy.notCalled)
  })

  test('should create same object for flattened as original expanded', async ({ assert }) => {
    const expandedElement = new Helper()
    expandedElement.amf = expandedModel
    await nextFrame()
    localElement.amf = flattenedModel
    await nextFrame()
    // @ts-expect-error Used in testing
    assert.deepEqual(localElement.amf[0], expandedElement.amf[0])
  })
})
