import { test } from '@japa/runner'
import {
  IApiArrayShape,
  IApiDataExample,
  IApiFileShape,
  IApiNodeShape,
  IApiScalarNode,
  IApiScalarShape,
  IApiUnionShape,
  IApiXmlSerializer,
  IShapeUnion,
} from '../../../../src/amf/definitions/Shapes.js'
import {
  IAmfAnyTypes,
  IAmfArrayTypes,
  IAmfExampleTypes,
  IAmfFileTypes,
  IAmfNodeTypes,
  IAmfPropertyTypes,
  IAmfScalarTypes,
  IAmfUnionTypes,
} from '../../../../src/amf/AmfTypes.js'
import { AmfNamespace } from '../../../../src/amf/definitions/Namespace.js'
import { ShapeGenerator } from '../../../../src/modeling/amf/ShapeGenerator.js'
import {
  type PropertyWebBindings,
  DataDomain,
  DomainEntity,
  DomainModel,
  DomainAssociation,
  DomainProperty,
} from '../../../../src/index.js'

test.group('entity()', (group) => {
  let root: DataDomain
  let m1: DomainModel
  let e1: DomainEntity

  group.each.setup(() => {
    root = new DataDomain()
    m1 = root.addModel({ key: 'm1' })
    e1 = m1.addEntity({ key: 'e1', info: { name: 'e1' } })
  })

  test('returns the basic shape', ({ assert }) => {
    const result = e1.toApiShape() as IApiNodeShape

    assert.equal(result.id, e1.key, 'has the id')
    assert.deepEqual(result.types, IAmfNodeTypes, 'has the types')
    assert.deepEqual(result.values, [], 'has the values')
    assert.deepEqual(result.inherits, [], 'has the inherits')
    assert.deepEqual(result.or, [], 'has the or')
    assert.deepEqual(result.and, [], 'has the and')
    assert.deepEqual(result.xone, [], 'has the xone')
    assert.deepEqual(result.examples, [], 'has the examples')
    assert.deepEqual(result.properties, [], 'has the properties')
    assert.typeOf(result.xmlSerialization, 'object', 'has the xmlSerialization')
    assert.equal(result.name, 'e1', 'has the name')
  })

  test('returns the display name', ({ assert }) => {
    e1.info.displayName = 'e 1 name'
    const result = e1.toApiShape() as IApiNodeShape

    assert.equal(result.displayName, 'e 1 name')
  })

  test('returns the description', ({ assert }) => {
    e1.info.description = 'e 1 description'
    const result = e1.toApiShape() as IApiNodeShape

    assert.equal(result.description, 'e 1 description')
  })

  test('returns the deprecated', ({ assert }) => {
    e1.deprecated = true
    const result = e1.toApiShape() as IApiNodeShape

    assert.isTrue(result.deprecated)
  })

  test('returns empty properties', ({ assert }) => {
    e1.info.description = 'e 1 description'
    const result = e1.toApiShape() as IApiNodeShape

    assert.deepEqual(result.properties, [])
  })

  test('returns defined properties', ({ assert }) => {
    e1.addProperty({ key: 'test', info: { name: 'test' } })
    const result = e1.toApiShape() as IApiNodeShape

    assert.lengthOf(result.properties, 1)
  })

  test('adds associations to the properties', ({ assert }) => {
    e1.addProperty({ key: 'test', info: { name: 'test' } })
    e1.addAssociation({}, { info: { name: 'test association' } })
    const result = e1.toApiShape() as IApiNodeShape

    assert.lengthOf(result.properties, 2)
    assert.equal(result.properties[1].name, 'test association')
  })

  test('adds parents to the inherits', ({ assert }) => {
    const e2 = m1.addEntity({ key: 'e2', info: { name: 'e2' } })
    e1.addParent(e2.key)
    const result = e1.toApiShape() as IApiNodeShape

    assert.lengthOf(result.inherits, 1)
    assert.equal(result.inherits[0].name, 'e2')
  })
})

test.group('property() / just data model', (group) => {
  let root: DataDomain
  let m1: DomainModel
  let e1: DomainEntity
  let p1: DomainProperty

  group.each.setup(() => {
    root = new DataDomain()
    m1 = root.addModel({ key: 'm1' })
    e1 = m1.addEntity({ key: 'e1', info: { name: 'e1' } })
    p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
  })

  test('returns the basic shape', ({ assert }) => {
    const result = p1.toApiShape()
    assert.equal(result.id, p1.key, 'has the id')
    assert.deepEqual(result.types, IAmfPropertyTypes, 'has the types')
    assert.deepEqual(result.values, [], 'has the values')
    assert.deepEqual(result.inherits, [], 'has the inherits')
    assert.deepEqual(result.or, [], 'has the or')
    assert.deepEqual(result.and, [], 'has the and')
    assert.deepEqual(result.xone, [], 'has the xone')
    assert.equal(result.path, AmfNamespace.aml.vocabularies.data.key + 'p1', 'has the path')
    assert.equal(result.name, 'p1', 'has the name')
  })

  test('sets the minCount', ({ assert }) => {
    p1.required = true
    const result = p1.toApiShape()

    assert.equal(result.minCount, 1)
  })

  test('has the default range for a string', ({ assert }) => {
    p1.info.displayName = 'string dn'
    p1.info.description = 'string desc'
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.id, `scalar-shape-${p1.key}`, 'has the id')
    assert.deepEqual(range.types, IAmfScalarTypes, 'has the types')
    assert.equal(range.dataType, AmfNamespace.w3.xmlSchema.string, 'has the dataType')
    assert.equal(range.name, 'p1', 'has the name')
    assert.equal(range.displayName, 'string dn', 'has the displayName')
    assert.equal(range.description, 'string desc', 'has the description')
  })

  test('handles examples', ({ assert }) => {
    p1.schema = { examples: ['example1', 'example2'] }
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape
    assert.lengthOf(range.examples, 2)
    assert.property(range.examples[0], 'structuredValue')
    assert.property(range.examples[1], 'structuredValue')
  })

  test('handles defaultValue', ({ assert }) => {
    p1.schema = { defaultValue: { value: 'defaultValue', type: 'literal' } }
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape
    assert.property(range, 'defaultValue')
    assert.containsSubset(range.defaultValue, {
      value: 'defaultValue',
    })
  })

  test('handles enum', ({ assert }) => {
    p1.schema = { enum: ['enum1', 'enum2'] }
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape
    assert.lengthOf(range.values, 2)
    assert.containsSubset(range.values[0], {
      dataType: AmfNamespace.w3.xmlSchema.string,
      value: `enum1`,
    })
    assert.containsSubset(range.values[1], {
      dataType: AmfNamespace.w3.xmlSchema.string,
      value: `enum2`,
    })
  })

  test('has the default range for a number', ({ assert }) => {
    p1.type = 'number'
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.dataType, AmfNamespace.w3.xmlSchema.number, 'has the dataType')
  })

  test('handles readOnly and writeOnly', ({ assert }) => {
    p1.readOnly = true
    p1.writeOnly = true
    const result = p1.toApiShape()
    assert.isTrue(result.range?.readOnly)
    assert.isTrue(result.range?.writeOnly)
  })

  test('handles deprecated', ({ assert }) => {
    p1.deprecated = true
    const result = p1.toApiShape()
    assert.isTrue(result.deprecated)
  })

  test('handles multiple properties', ({ assert }) => {
    p1.multiple = true
    const result = p1.toApiShape()
    const range = result.range as IApiArrayShape
    assert.property(range, 'items')
  })

  test('handles binary base64 format', ({ assert }) => {
    p1.type = 'binary'
    const bindings = p1.getWebBinding() as PropertyWebBindings
    bindings.format = 'base64'
    const result = p1.toApiShape()
    const range = result.range as IApiFileShape
    assert.equal(range.format, AmfNamespace.w3.xmlSchema.base64Binary)
  })

  test('has the default range for an int32 format', ({ assert }) => {
    p1.type = 'number'
    const bindings = p1.getWebBinding()
    bindings.format = 'int32'
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.dataType, AmfNamespace.w3.xmlSchema.integer, 'has the dataType')
  })

  test('has the default range for an int64 format', ({ assert }) => {
    p1.type = 'number'
    const bindings = p1.getWebBinding()
    bindings.format = 'int64'
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.dataType, AmfNamespace.w3.xmlSchema.integer, 'has the dataType')
  })

  test('has the default range for a date', ({ assert }) => {
    p1.type = 'date'
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.dataType, AmfNamespace.w3.xmlSchema.date, 'has the dataType')
  })

  test('has the default range for a datetime', ({ assert }) => {
    p1.type = 'datetime'
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.dataType, AmfNamespace.w3.xmlSchema.dateTime, 'has the dataType')
  })

  test('has the default range for a time', ({ assert }) => {
    p1.type = 'time'
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.dataType, AmfNamespace.aml.vocabularies.shapes.dateTimeOnly, 'has the dataType')
  })

  // test('has the default range for a nil', ({ assert }) => {
  //   p1.type = 'nil'
  //   const result = p1.toApiShape()
  //   const range = result.range as IApiScalarShape

  //   assert.typeOf(range, 'object', 'has the range')
  //   assert.equal(range.dataType, AmfNamespace.aml.vocabularies.shapes.nil, 'has the dataType')
  // })

  test('has the default range for a boolean', ({ assert }) => {
    p1.type = 'boolean'
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.dataType, AmfNamespace.aml.vocabularies.shapes.boolean, 'has the dataType')
  })

  // test('has the default range for an any', ({ assert }) => {
  //   p1.type = 'any'
  //   const result = p1.toApiShape()
  //   const range = result.range as IApiScalarShape

  //   assert.typeOf(range, 'object', 'has the range')
  //   assert.equal(range.dataType, AmfNamespace.aml.vocabularies.shapes.AnyShape, 'has the dataType')
  // })

  test('has the default range for a file', ({ assert }) => {
    p1.type = 'binary'
    p1.info.displayName = 'string dn'
    p1.info.description = 'string desc'
    const result = p1.toApiShape()
    const range = result.range as IApiFileShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.id, `file-shape-${p1.key}`, 'has the id')
    assert.deepEqual(range.types, IAmfFileTypes, 'has the types')
    assert.equal(range.name, 'p1', 'has the name')
    assert.equal(range.displayName, 'string dn', 'has the displayName')
    assert.equal(range.description, 'string desc', 'has the description')
  })

  test('has the default range for a string when a multiple', ({ assert }) => {
    p1.multiple = true
    p1.info.displayName = 'string dn'
    p1.info.description = 'string desc'
    const result = p1.toApiShape()
    const range = result.range as IApiArrayShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.id, `array-shape-${p1.key}`, 'has the id')
    assert.deepEqual(range.types, IAmfArrayTypes, 'has the types')

    const items = range.items as IApiScalarShape

    assert.typeOf(items, 'object', 'has the items')
    assert.equal(items.id, `scalar-shape-${p1.key}`, 'has the id')
    assert.deepEqual(items.types, IAmfScalarTypes, 'has the types')

    assert.equal(items.name, 'p1', 'has the name')
    assert.equal(items.displayName, 'string dn', 'has the displayName')
    assert.equal(items.description, 'string desc', 'has the description')
  })

  test('has the default range for a file when a multiple', ({ assert }) => {
    p1.multiple = true
    p1.type = 'binary'
    p1.info.displayName = 'string dn'
    p1.info.description = 'string desc'
    const result = p1.toApiShape()
    const range = result.range as IApiArrayShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.id, `array-shape-${p1.key}`, 'has the id')
    assert.deepEqual(range.types, IAmfArrayTypes, 'has the types')

    const items = range.items as IApiFileShape

    assert.typeOf(items, 'object', 'has the items')
    assert.equal(items.id, `file-shape-${p1.key}`, 'has the id')
    assert.deepEqual(items.types, IAmfFileTypes, 'has the types')

    assert.equal(items.name, 'p1', 'has the name')
    assert.equal(items.displayName, 'string dn', 'has the displayName')
    assert.equal(items.description, 'string desc', 'has the description')
  })
})

test.group('property() / with web bindings', (group) => {
  let root: DataDomain
  let m1: DomainModel
  let e1: DomainEntity

  group.each.setup(() => {
    root = new DataDomain()
    m1 = root.addModel({ key: 'm1' })
    e1 = m1.addEntity({ key: 'e1', info: { name: 'e1' } })
  })

  test('updates the name when changed', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'string', info: { name: 'p1' } })
    p1.bindings = [
      {
        type: 'web',
        schema: {
          name: 'other',
        },
      },
    ]
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape

    assert.equal(string.name, 'other', 'the range has the changed name')
    assert.equal(result.name, 'other', 'the property has the changed name')
  })

  // test('sets the dataType', ({ assert }) => {
  //   const p1 = e1.addProperty({ key: 'p1', type: 'string', info: { name: 'p1' } })
  //   p1.bindings = [
  //     {
  //       type: 'web',
  //       schema: {
  //         // dataType: AmfNamespace.w3.xmlSchema.date,
  //       },
  //     },
  //   ]
  //   const result = p1.toApiShape()
  //   const string = result.range as IApiScalarShape
  //   assert.equal(string.dataType, AmfNamespace.w3.xmlSchema.date)
  // })

  test('sets the xml serialization', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'string', info: { name: 'p1' } })
    const value: IApiXmlSerializer = {
      id: '',
      types: [],
      customDomainProperties: [],
      name: 'test',
    }
    p1.bindings = [
      {
        type: 'web',
        schema: {
          xml: value,
        },
      },
    ]
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape
    assert.deepEqual(string.xmlSerialization, value)
  })

  test('sets the file types', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'binary', info: { name: 'p1' } })
    const value: string[] = ['a']
    p1.bindings = [
      {
        type: 'web',
        schema: {
          fileTypes: value,
        },
      },
    ]
    const result = p1.toApiShape()
    const file = result.range as IApiFileShape
    assert.deepEqual(file.fileTypes, value)
  })

  test('sets the pattern', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'string', info: { name: 'p1' } })
    p1.schema = {
      pattern: 'a-z',
    }
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape
    assert.equal(string.pattern, 'a-z')
  })

  test('sets the minLength', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'string', info: { name: 'p1' } })
    p1.schema = {
      minimum: 1,
    }
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape
    assert.equal(string.minLength, 1)
  })

  test('sets the maxLength', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'string', info: { name: 'p1' } })
    p1.schema = {
      maximum: 1,
    }
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape
    assert.equal(string.maxLength, 1)
  })

  test('sets the minimum', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      minimum: 1,
    }
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape
    assert.equal(string.minimum, 1)
  })

  test('sets the maximum', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      maximum: 1,
    }
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape
    assert.equal(string.maximum, 1)
  })

  test('sets the multipleOf', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      multipleOf: 1,
    }
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape
    assert.equal(string.multipleOf, 1)
  })

  test('sets the exclusiveMinimum', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      exclusiveMinimum: true,
    }
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape
    assert.isTrue(string.exclusiveMinimum)
  })

  test('sets the exclusiveMaximum to false', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      exclusiveMaximum: false,
    }
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape
    assert.isFalse(string.exclusiveMaximum)
  })

  test('sets the exclusiveMaximum to true', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      exclusiveMaximum: true,
    }
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape
    assert.isTrue(string.exclusiveMaximum)
  })

  test('sets the format', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.bindings = [
      {
        type: 'web',
        schema: {
          format: 'float',
        },
      },
    ]
    const result = p1.toApiShape()
    const string = result.range as IApiScalarShape
    assert.equal(string.format, AmfNamespace.w3.xmlSchema.float)
  })
})

test.group('property() / with schema', (group) => {
  let root: DataDomain
  let m1: DomainModel
  let e1: DomainEntity

  group.each.setup(() => {
    root = new DataDomain()
    m1 = root.addModel({ key: 'm1' })
    e1 = m1.addEntity({ key: 'e1', info: { name: 'e1' } })
  })

  test('sets the default value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      defaultValue: { value: '123', type: 'literal' },
    }
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape
    assert.typeOf(range.defaultValue, 'object', 'has the defaultValue')
    const value = range.defaultValue as IApiScalarNode
    assert.strictEqual(value.value, '123', 'has the value')
    assert.equal(value.dataType, AmfNamespace.w3.xmlSchema.number, 'has the range data type')
  })

  test('sets the enum value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      enum: ['123'],
    }
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape
    assert.typeOf(range.values, 'array', 'has the values')
    const value = range.values[0] as IApiScalarNode
    assert.strictEqual(value.value, '123', 'has the value')
    assert.equal(value.dataType, AmfNamespace.w3.xmlSchema.number, 'has the range data type')
  })

  test('sets the examples value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'string', info: { name: 'p1' } })
    p1.schema = {
      examples: ['123'],
    }
    const result = p1.toApiShape()
    const range = result.range as IApiScalarShape
    assert.typeOf(range.examples, 'array', 'has the examples')
    const example = range.examples[0] as IApiDataExample

    assert.typeOf(example.id, 'string', 'has the example.id')
    assert.isNotEmpty(example.id, 'the example.id is not empty')
    assert.deepEqual(example.types, IAmfExampleTypes, 'the example.types')
    assert.typeOf(example.structuredValue, 'object', 'has the example.structuredValue')

    const value = example.structuredValue as IApiScalarNode
    assert.strictEqual(value.value, '123', 'has the value')
    assert.equal(value.dataType, AmfNamespace.w3.xmlSchema.string, 'has the range data type')
  })
})

test.group('property() / default value functions', (group) => {
  let root: DataDomain
  let m1: DomainModel
  let e1: DomainEntity
  let p1: DomainProperty

  group.each.setup(() => {
    root = new DataDomain()
    m1 = root.addModel({ key: 'm1' })
    e1 = m1.addEntity({ key: 'e1', info: { name: 'e1' } })
    p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
  })

  test('generates random string for "random" function', ({ assert }) => {
    p1.schema = { defaultValue: { value: 'random', type: 'function' } }
    const generator = new ShapeGenerator()
    const result = generator.property(p1)
    const range = result.range as IApiScalarShape
    assert.typeOf(range.defaultValue, 'object')
    const value = range.defaultValue as IApiScalarNode
    assert.typeOf(value.value, 'string')
    assert.lengthOf(value.value as string, 8)
  })

  test('generates uuid for "uuid-v4" function', ({ assert }) => {
    p1.schema = { defaultValue: { value: 'uuid-v4', type: 'function' } }
    const generator = new ShapeGenerator()
    const result = generator.property(p1)
    const range = result.range as IApiScalarShape
    assert.typeOf(range.defaultValue, 'object')
    const value = range.defaultValue as IApiScalarNode
    assert.typeOf(value.value, 'string')
    assert.lengthOf(value.value as string, 36)
    assert.match(value.value as string, /^[0-9a-f-]+$/)
  })

  test('generates incremental number for "incremental" function', ({ assert }) => {
    p1.type = 'number'
    p1.schema = { defaultValue: { value: 'incremental', type: 'function' } }
    const generator = new ShapeGenerator()
    const result1 = generator.property(p1)
    const range1 = result1.range as IApiScalarShape
    assert.typeOf(range1.defaultValue, 'object')
    const value1 = range1.defaultValue as IApiScalarNode
    assert.typeOf(value1.value, 'string')
    assert.equal(parseInt(value1.value as string), 0)

    const result2 = generator.property(p1)
    const range2 = result2.range as IApiScalarShape
    assert.typeOf(range2.defaultValue, 'object')
    const value2 = range2.defaultValue as IApiScalarNode
    assert.typeOf(value2.value, 'string')
    assert.equal(parseInt(value2.value as string), 1)
  })

  test('generates current date for "now" function (date type)', ({ assert }) => {
    p1.type = 'date'
    p1.schema = { defaultValue: { value: 'now', type: 'function' } }
    const generator = new ShapeGenerator()
    const result = generator.property(p1)
    const range = result.range as IApiScalarShape
    assert.typeOf(range.defaultValue, 'object')
    const value = range.defaultValue as IApiScalarNode
    assert.typeOf(value.value, 'string')
    assert.match(value.value as string, /^\d{4}-\d{2}-\d{2}$/)
  })

  test('generates current time for "now" function (time type)', ({ assert }) => {
    p1.type = 'time'
    p1.schema = { defaultValue: { value: 'now', type: 'function' } }
    const generator = new ShapeGenerator()
    const result = generator.property(p1)
    const range = result.range as IApiScalarShape
    assert.typeOf(range.defaultValue, 'object')
    const value = range.defaultValue as IApiScalarNode
    assert.typeOf(value.value, 'string')
    assert.match(value.value as string, /^\d{2}:\d{2}:\d{2}$/)
  })

  test('generates current datetime for "now" function (datetime type)', ({ assert }) => {
    p1.type = 'datetime'
    p1.schema = { defaultValue: { value: 'now', type: 'function' } }
    const generator = new ShapeGenerator()
    const result = generator.property(p1)
    const range = result.range as IApiScalarShape
    assert.typeOf(range.defaultValue, 'object')
    const value = range.defaultValue as IApiScalarNode
    assert.typeOf(value.value, 'string')
    assert.match(value.value as string, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/)
  })

  test('returns "noValue" for unsupported function', ({ assert }) => {
    p1.schema = { defaultValue: { value: 'unsupported', type: 'function' } }
    const generator = new ShapeGenerator()
    const result = generator.property(p1)
    const range = result.range as IApiScalarShape
    assert.isUndefined(range.defaultValue)
  })
})

test.group('refactorArrayToShape()', (group) => {
  let root: DataDomain
  let m1: DomainModel
  let e1: DomainEntity
  let p1: DomainProperty

  group.each.setup(() => {
    root = new DataDomain()
    m1 = root.addModel({ key: 'm1' })
    e1 = m1.addEntity({ key: 'e1', info: { name: 'e1' } })
    p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
  })

  test('returns the array items', ({ assert }) => {
    p1.multiple = true
    const parameter = p1.toApiShape()
    const range = parameter.range as IApiArrayShape
    const shape = range.items as IApiScalarShape
    assert.deepEqual(range.items, shape)
  })
})

test.group('refactorShapeToArray()', (group) => {
  let root: DataDomain
  let m1: DomainModel
  let e1: DomainEntity
  let p1: DomainProperty

  group.each.setup(() => {
    root = new DataDomain()
    m1 = root.addModel({ key: 'm1' })
    e1 = m1.addEntity({ key: 'e1', info: { name: 'e1' } })
    p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
  })

  test('returns the array items', ({ assert }) => {
    const parameter = p1.toApiShape()
    const range = parameter.range as IApiScalarShape
    const generator = new ShapeGenerator()
    const result = generator['refactorShapeToArray'](p1.key, range)

    assert.typeOf(result, 'object', 'has the range')
    assert.equal(result.id, `array-shape-${p1.key}`, 'has the id')
    assert.deepEqual(result.types, IAmfArrayTypes, 'has the types')

    const items = result.items as IApiScalarShape
    assert.deepEqual(items, range)
  })
})

test.group('associationProperty()', (group) => {
  let root: DataDomain
  let m1: DomainModel
  let e1: DomainEntity
  let e2: DomainEntity
  let a1: DomainAssociation
  let p2: DomainProperty

  group.each.setup(() => {
    root = new DataDomain()
    m1 = root.addModel({ key: 'm1' })
    e1 = m1.addEntity({ key: 'e1', info: { name: 'e1' } })
    e2 = m1.addEntity({ key: 'e2', info: { name: 'e2' } })
    a1 = e1.addAssociation({ key: e2.key }, { key: 'a1', info: { name: 'a1' } })
    e1.addProperty({ key: 'p1', type: 'boolean', info: { name: 'p1' } })
    p2 = e2.addProperty({ key: 'p2', type: 'number', info: { name: 'p2' } })
  })

  test('returns basic shape definition', ({ assert }) => {
    const result = a1.toApiShape()
    assert.typeOf(result, 'object')

    assert.equal(result.id, a1.key, 'has the id')
    assert.deepEqual(result.types, IAmfPropertyTypes, 'has the types')
    assert.deepEqual(result.values, [], 'has the values')
    assert.deepEqual(result.inherits, [], 'has the inherits')
    assert.deepEqual(result.or, [], 'has the or')
    assert.deepEqual(result.and, [], 'has the and')
    assert.deepEqual(result.xone, [], 'has the xone')
    assert.equal(result.path, AmfNamespace.aml.vocabularies.data.key + 'a1', 'has the path')

    const range = result.range as IApiNodeShape
    assert.typeOf(range, 'object', 'has the range')
    assert.deepEqual(range.types, IAmfNodeTypes, 'range has the types')
    assert.equal(range.name, 'e2', 'range has the name')
    assert.lengthOf(range.properties, 1, 'range has the properties')

    const [prop] = range.properties
    assert.equal(prop.id, p2.key, 'property has the id')
    assert.deepEqual(prop.types, IAmfPropertyTypes, 'property has the types')

    const pRange = prop.range as IApiScalarShape
    assert.typeOf(pRange, 'object', 'property range is an object')
    assert.deepEqual(pRange.types, IAmfScalarTypes, 'property range has the types')
    assert.equal(pRange.name, 'p2', 'property range has the name')
    assert.equal(pRange.dataType, AmfNamespace.w3.xmlSchema.number, 'property range has the dataType')
  })

  test('sets property description', ({ assert }) => {
    a1.info.displayName = 'dn a1'
    a1.info.description = 'desc a1'
    const result = a1.toApiShape()
    assert.equal(result.name, 'a1')
    assert.equal(result.displayName, 'dn a1')
    assert.equal(result.description, 'desc a1')
  })

  test('sets the minCount', ({ assert }) => {
    a1.required = true
    const result = a1.toApiShape()
    assert.equal(result.minCount, 1)
  })

  test('sets the path', ({ assert }) => {
    a1.required = true
    const result = a1.toApiShape()
    assert.equal(result.path, AmfNamespace.aml.vocabularies.data.key + 'a1', 'has the path')
  })

  test('returns a link definition when created on the schema', ({ assert }) => {
    a1.schema = { linked: true }
    const result = a1.toApiShape()
    const range = result.range as IApiScalarShape
    assert.equal(range.id, `link-${a1.key}`)
    assert.deepEqual(range.types, IAmfScalarTypes)
    assert.equal(range.dataType, AmfNamespace.w3.xmlSchema.string)
  })

  test('returns a default union type', ({ assert }) => {
    const e3 = m1.addEntity({ key: 'e3', info: { name: 'e3' } })
    a1.addTarget(e3)
    const result = a1.toApiShape()
    const range = result.range as IApiUnionShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.id, `union-shape-${a1.key}`, 'the range has the id')
    assert.deepEqual(range.types, IAmfUnionTypes, 'the range has the types')
    assert.lengthOf(range.anyOf, 2, 'has the anyOf array')
    const [p1, p2] = range.anyOf as IApiNodeShape[]

    assert.deepEqual(p1.types, IAmfNodeTypes, 'p1 has the types')
    assert.deepEqual(p2.types, IAmfNodeTypes, 'p2 has the types')
    assert.lengthOf(p1.properties, 1, 'p1 has the properties')
    assert.lengthOf(p2.properties, 0, 'p2 has the properties')
  })

  test('has no range when no associations', ({ assert }) => {
    a1.removeTarget(e2)
    const result = a1.toApiShape()
    assert.isUndefined(result.range)
  })

  test('returns an array shape when multiple', ({ assert }) => {
    a1.multiple = true
    const result = a1.toApiShape()
    const range = result.range as IApiArrayShape
    assert.deepEqual(range.types, IAmfArrayTypes)

    const arrayRange = range.items as IApiNodeShape
    assert.typeOf(arrayRange, 'object', 'has the range')
    assert.deepEqual(arrayRange.types, IAmfNodeTypes, 'range has the types')
    assert.equal(arrayRange.name, 'e2', 'range has the name')
    assert.lengthOf(arrayRange.properties, 1, 'range has the properties')
  })

  test('creates the "anyOf" union', ({ assert }) => {
    a1.schema = { unionType: 'anyOf' }

    const e3 = m1.addEntity({ key: 'e3', info: { name: 'e3' } })
    a1.addTarget(e3)
    const result = a1.toApiShape()
    const range = result.range as IApiUnionShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.id, `union-shape-${a1.key}`, 'the range has the id')
    assert.deepEqual(range.types, IAmfUnionTypes, 'the range has the types')
    assert.lengthOf(range.anyOf, 2, 'has the anyOf array')
    const [p1, p2] = range.anyOf as IApiNodeShape[]

    assert.deepEqual(p1.types, IAmfNodeTypes, 'p1 has the types')
    assert.deepEqual(p2.types, IAmfNodeTypes, 'p2 has the types')
    assert.lengthOf(p1.properties, 1, 'p1 has the properties')
    assert.lengthOf(p2.properties, 0, 'p2 has the properties')
  })

  test('creates the "allOf" union', ({ assert }) => {
    a1.schema = { unionType: 'allOf' }

    const e3 = m1.addEntity({ key: 'e3', info: { name: 'e3' } })
    a1.addTarget(e3)
    const result = a1.toApiShape()
    const range = result.range as IApiUnionShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.id, `union-shape-${a1.key}`, 'the range has the id')
    assert.deepEqual(range.types, IAmfUnionTypes, 'the range has the types')
    assert.lengthOf(range.and, 2, 'has the anyOf array')
    const [p1, p2] = range.and as IApiNodeShape[]

    assert.deepEqual(p1.types, IAmfNodeTypes, 'p1 has the types')
    assert.deepEqual(p2.types, IAmfNodeTypes, 'p2 has the types')
    assert.lengthOf(p1.properties, 1, 'p1 has the properties')
    assert.lengthOf(p2.properties, 0, 'p2 has the properties')
  })

  test('creates the "oneOf" union', ({ assert }) => {
    a1.schema = { unionType: 'oneOf' }

    const e3 = m1.addEntity({ key: 'e3', info: { name: 'e3' } })
    a1.addTarget(e3)
    const result = a1.toApiShape()
    const range = result.range as IApiUnionShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.id, `union-shape-${a1.key}`, 'the range has the id')
    assert.deepEqual(range.types, IAmfUnionTypes, 'the range has the types')
    assert.lengthOf(range.xone, 2, 'has the oneOf array')
    const [p1, p2] = range.xone as IApiNodeShape[]

    assert.deepEqual(p1.types, IAmfNodeTypes, 'p1 has the types')
    assert.deepEqual(p2.types, IAmfNodeTypes, 'p2 has the types')
    assert.lengthOf(p1.properties, 1, 'p1 has the properties')
    assert.lengthOf(p2.properties, 0, 'p2 has the properties')
  })

  test('creates the "not" union', ({ assert }) => {
    a1.schema = { unionType: 'not' }

    const e3 = m1.addEntity({ key: 'e3', info: { name: 'e3' } })
    a1.addTarget(e3)
    const result = a1.toApiShape()
    const range = result.range as IApiUnionShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.id, `union-shape-${a1.key}`, 'the range has the id')
    assert.deepEqual(range.types, IAmfUnionTypes, 'the range has the types')
    assert.typeOf(range.not, 'object', 'has the not object')

    const not = range.not as IApiNodeShape
    assert.deepEqual(not.types, IAmfNodeTypes, 'p1 has the types')
    assert.deepEqual(not.types, IAmfNodeTypes, 'p2 has the types')
    assert.lengthOf(not.properties, 1, 'p1 has the properties')
  })

  test('creates the "not" shape', ({ assert }) => {
    a1.schema = { unionType: 'not' }

    const result = a1.toApiShape()
    const range = result.range as IApiUnionShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.id, `not-shape-${a1.key}`, 'the range has the id')
    assert.deepEqual(range.types, IAmfAnyTypes, 'the range has the types')
    assert.typeOf(range.not, 'object', 'has the not object')

    const not = range.not as IApiNodeShape
    assert.deepEqual(not.types, IAmfNodeTypes, 'p1 has the types')
    assert.deepEqual(not.types, IAmfNodeTypes, 'p2 has the types')
    assert.lengthOf(not.properties, 1, 'p1 has the properties')
  })

  test('wraps the union in the array shape', ({ assert }) => {
    a1.multiple = true
    a1.schema = { unionType: 'anyOf' }

    const e3 = m1.addEntity({ key: 'e3', info: { name: 'e3' } })
    a1.addTarget(e3)
    const result = a1.toApiShape()
    const range = result.range as IApiArrayShape

    assert.typeOf(range, 'object', 'has the range')
    assert.equal(range.id, `array-shape-${a1.key}`, 'the range has the id')
    assert.deepEqual(range.types, IAmfArrayTypes, 'the range has the types')

    const items = range.items as IApiUnionShape

    assert.lengthOf(items.anyOf, 2, 'has the anyOf array')
    const [p1, p2] = items.anyOf as IApiNodeShape[]

    assert.deepEqual(p1.types, IAmfNodeTypes, 'p1 has the types')
    assert.deepEqual(p2.types, IAmfNodeTypes, 'p2 has the types')
    assert.lengthOf(p1.properties, 1, 'p1 has the properties')
    assert.lengthOf(p2.properties, 0, 'p2 has the properties')
  })

  test('handles linked schema', ({ assert }) => {
    a1.schema = { linked: true }
    const result = a1.toApiShape()
    const range = result.range as IApiScalarShape
    assert.equal(range.dataType, AmfNamespace.w3.xmlSchema.string)
  })

  test('handles multiple associations', ({ assert }) => {
    const e3 = m1.addEntity({ key: 'e3', info: { name: 'e3' } })
    a1.addTarget(e3)
    const result = a1.toApiShape()
    const range = result.range as IApiUnionShape
    assert.lengthOf(range.anyOf, 2)
  })

  test('handles unionType', ({ assert }) => {
    a1.schema = { unionType: 'allOf' }
    const e3 = m1.addEntity({ key: 'e3', info: { name: 'e3' } })
    a1.addTarget(e3)
    const result = a1.toApiShape()
    const range = result.range as IApiUnionShape
    assert.lengthOf(range.and, 2)
  })

  test('handles multiple and unionType', ({ assert }) => {
    a1.multiple = true
    a1.schema = { unionType: 'allOf' }
    const e3 = m1.addEntity({ key: 'e3', info: { name: 'e3' } })
    a1.addTarget(e3)
    const result = a1.toApiShape()
    const range = result.range as IApiArrayShape
    const items = range.items as IShapeUnion
    assert.lengthOf(items.and, 2)
  })
})

test.group('AI Generated tests', (group) => {
  let root: DataDomain
  let m1: DomainModel
  let e1: DomainEntity
  let e2: DomainEntity
  let a1: DomainAssociation
  let p1: DomainProperty

  group.each.setup(() => {
    root = new DataDomain()
    m1 = root.addModel({ key: 'm1' })
    e1 = m1.addEntity({ key: 'e1', info: { name: 'e1' } })
    e2 = m1.addEntity({ key: 'e2', info: { name: 'e2' } })
    a1 = e1.addAssociation({ key: e2.key }, { key: 'a1', info: { name: 'a1' } })
    p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
  })

  test('does not add hidden properties to the properties array', ({ assert }) => {
    const p3 = e1.addProperty({ key: 'p3', info: { name: 'p3' } })
    const bindings = p3.getWebBinding()
    bindings.hidden = true
    const result = e1.toApiShape() as IApiNodeShape
    assert.lengthOf(result.properties, 2)
    assert.isFalse(result.properties.some((prop) => prop.id === p3.key))
  })

  test('handles foreign namespaces', ({ assert }) => {
    const fd1 = new DataDomain()
    const m2 = fd1.addModel({ key: 'm2' })
    const e3 = m2.addEntity({ key: 'e3', info: { name: 'e3' } })
    fd1.info.version = '1.0.0'
    root.registerForeignDomain(fd1)
    const a2 = e1.addAssociation({ key: e3.key, domain: fd1.key }, { key: 'a2', info: { name: 'a2' } })
    const result = e1.toApiShape() as IApiNodeShape
    assert.lengthOf(result.properties, 3)
    assert.isTrue(result.properties.some((prop) => prop.id === a2.key))
  })

  test('handles multiple properties with examples', ({ assert }) => {
    p1.multiple = true
    p1.schema = { examples: ['example1', 'example2'] }
    const result = p1.toApiShape()
    const range = result.range as IApiArrayShape
    assert.include(range.types, AmfNamespace.aml.vocabularies.shapes.ArrayShape)
    assert.lengthOf(range.examples, 1)
    assert.property(range.examples[0], 'structuredValue')
  })

  test('handles multiple properties with default values', ({ assert }) => {
    p1.multiple = true
    p1.schema = { defaultValue: { value: 'defaultValue', type: 'literal' } }
    const result = p1.toApiShape()
    const range = result.range as IApiArrayShape
    assert.include(range.types, AmfNamespace.aml.vocabularies.shapes.ArrayShape)
    assert.property(range, 'defaultValue')
    assert.containsSubset(range.defaultValue, {
      value: 'defaultValue',
    })
  })

  test('handles multiple properties with enum', ({ assert }) => {
    p1.multiple = true
    p1.schema = { enum: ['enum1', 'enum2'] }
    const result = p1.toApiShape()
    const range = result.range as IApiArrayShape
    assert.include(range.types, AmfNamespace.aml.vocabularies.shapes.ArrayShape)
    assert.lengthOf(range.values, 2)
    assert.containsSubset(range.values[0], {
      dataType: AmfNamespace.w3.xmlSchema.string,
      value: `enum1`,
    })
    assert.containsSubset(range.values[1], {
      dataType: AmfNamespace.w3.xmlSchema.string,
      value: `enum2`,
    })
  })

  test('handles multiple properties with schema', ({ assert }) => {
    p1.multiple = true
    p1.schema = { minimum: 1, maximum: 10 }
    const result = p1.toApiShape()
    const range = result.range as IApiArrayShape
    assert.include(range.types, AmfNamespace.aml.vocabularies.shapes.ArrayShape)
    const items = range.items as IApiScalarShape
    assert.equal(items.minLength, 1)
    assert.equal(items.maxLength, 10)
  })

  test('handles multiple properties with bindings', ({ assert }) => {
    p1.multiple = true
    p1.bindings = [
      {
        type: 'web',
        schema: {
          format: 'int32',
        },
      },
    ]
    const result = p1.toApiShape()
    const range = result.range as IApiArrayShape
    const items = range.items as IApiScalarShape
    assert.include(range.types, AmfNamespace.aml.vocabularies.shapes.ArrayShape)
    assert.equal(items.format, AmfNamespace.w3.xmlSchema.integer)
  })

  test('handles multiple associations with schema', ({ assert }) => {
    a1.multiple = true
    a1.schema = { linked: false }
    const result = a1.toApiShape()
    const range = result.range as IApiArrayShape
    assert.include(range.types, AmfNamespace.aml.vocabularies.shapes.ArrayShape)
    const items = range.items as IApiScalarShape
    assert.ok(items)
  })

  test('handles multiple associations with bindings', ({ assert }) => {
    a1.multiple = true
    a1.bindings = [
      {
        type: 'web',
        schema: {
          hidden: false,
        },
      },
    ]
    const result = a1.toApiShape()
    const range = result.range as IApiArrayShape
    assert.include(range.types, AmfNamespace.aml.vocabularies.shapes.ArrayShape)
    const items = range.items as IApiNodeShape
    assert.ok(items.properties)
  })
})
