import { test } from '@japa/runner'
import { DataDomain, DomainPropertyKind, DomainProperty, Thing, DataFormat } from '../../../src/index.js'
import { SemanticType } from '../../../src/modeling/Semantics.js'

test.group('DomainProperty.createSchema()', () => {
  test('creates a schema with default values', ({ assert }) => {
    const schema = DomainProperty.createSchema()
    assert.equal(schema.kind, DomainPropertyKind)
    assert.typeOf(schema.key, 'string')
    assert.isNotEmpty(schema.key)
    assert.deepInclude(schema.info, { name: 'new_property' })
    assert.equal(schema.type, 'string')
    assert.isUndefined(schema.multiple)
    assert.isUndefined(schema.required)
    assert.isUndefined(schema.index)
    assert.isUndefined(schema.primary)
    assert.isUndefined(schema.readOnly)
    assert.isUndefined(schema.writeOnly)
    assert.isUndefined(schema.deprecated)
    assert.isUndefined(schema.tags)
    assert.isUndefined(schema.semantics)
    assert.isUndefined(schema.schema)
    assert.deepEqual(schema.bindings, [])
  })

  test('creates a schema with provided values', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      key: 'test-property',
      info: { name: 'Test Property', description: 'A test property' },
      required: true,
      multiple: true,
      primary: true,
      index: true,
      readOnly: true,
      writeOnly: true,
      deprecated: true,
      tags: ['tag1', 'tag2'],
      semantics: [{ id: SemanticType.CreatedTimestamp }],
      type: 'number',
      schema: { minimum: 0 },
      bindings: [{ type: 'web', schema: { format: 'float' } }],
    })
    assert.equal(schema.kind, DomainPropertyKind)
    assert.equal(schema.key, 'test-property')
    assert.deepInclude(schema.info, { name: 'Test Property', description: 'A test property' })
    assert.equal(schema.type, 'number')
    assert.isTrue(schema.required)
    assert.isTrue(schema.multiple)
    assert.isTrue(schema.primary)
    assert.isTrue(schema.index)
    assert.isTrue(schema.readOnly)
    assert.isTrue(schema.writeOnly)
    assert.isTrue(schema.deprecated)
    assert.deepEqual(schema.tags, ['tag1', 'tag2'])
    assert.deepEqual(schema.semantics, [{ id: SemanticType.CreatedTimestamp }])
    assert.deepEqual(schema.schema, { minimum: 0 })
    assert.deepEqual(schema.bindings, [{ type: 'web', schema: { format: 'float' } }])
  })

  test('creates a schema with partial info', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      info: { name: 'Test Property' },
    })
    assert.deepInclude(schema.info, { name: 'Test Property' })
  })

  test('creates a schema with a specific type', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      type: 'boolean',
    })
    assert.equal(schema.type, 'boolean')
  })

  test('throws an error with an invalid type', ({ assert }) => {
    assert.throws(() => {
      // @ts-expect-error Testing invalid type
      DomainProperty.createSchema({ type: 'invalid-type' })
    }, 'Invalid data property type invalid-type')
  })

  test('creates a schema with all properties undefined', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      multiple: undefined,
      required: undefined,
      index: undefined,
      primary: undefined,
      readOnly: undefined,
      writeOnly: undefined,
      deprecated: undefined,
      tags: undefined,
      semantics: undefined,
      schema: undefined,
      bindings: undefined,
    })
    assert.equal(schema.kind, DomainPropertyKind)
    assert.typeOf(schema.key, 'string')
    assert.isNotEmpty(schema.key)
    assert.deepInclude(schema.info, { name: 'new_property' })
    assert.equal(schema.type, 'string')
    assert.isUndefined(schema.multiple)
    assert.isUndefined(schema.required)
    assert.isUndefined(schema.index)
    assert.isUndefined(schema.primary)
    assert.isUndefined(schema.readOnly)
    assert.isUndefined(schema.writeOnly)
    assert.isUndefined(schema.deprecated)
    assert.isUndefined(schema.tags)
    assert.isUndefined(schema.semantics)
    assert.isUndefined(schema.schema)
    assert.deepEqual(schema.bindings, [])
  })

  test('creates a schema with all properties null', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      // @ts-expect-error Testing null values
      multiple: null,
      // @ts-expect-error Testing null values
      required: null,
      // @ts-expect-error Testing null values
      index: null,
      // @ts-expect-error Testing null values
      primary: null,
      // @ts-expect-error Testing null values
      readOnly: null,
      // @ts-expect-error Testing null values
      writeOnly: null,
      // @ts-expect-error Testing null values
      deprecated: null,
      // @ts-expect-error Testing null values
      tags: null,
      // @ts-expect-error Testing null values
      semantics: null,
      // @ts-expect-error Testing null values
      schema: null,
      // @ts-expect-error Testing null values
      bindings: null,
    })
    assert.isUndefined(schema.multiple)
    assert.isUndefined(schema.required)
    assert.isUndefined(schema.index)
    assert.isUndefined(schema.primary)
    assert.isUndefined(schema.readOnly)
    assert.isUndefined(schema.writeOnly)
    assert.isUndefined(schema.deprecated)
    assert.isUndefined(schema.tags)
    assert.isUndefined(schema.semantics)
    assert.isUndefined(schema.schema)
    assert.deepEqual(schema.bindings, [])
  })

  test('creates a schema with all properties empty array', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      tags: [],
      semantics: [],
      bindings: [],
    })
    assert.deepEqual(schema.tags, [])
    assert.deepEqual(schema.semantics, [])
    assert.deepEqual(schema.bindings, [])
  })

  test('creates a schema with all boolean properties set to false', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      required: false,
      multiple: false,
      primary: false,
      index: false,
      readOnly: false,
      writeOnly: false,
      deprecated: false,
    })
    assert.isFalse(schema.required)
    assert.isFalse(schema.multiple)
    assert.isFalse(schema.primary)
    assert.isFalse(schema.index)
    assert.isFalse(schema.readOnly)
    assert.isFalse(schema.writeOnly)
    assert.isFalse(schema.deprecated)
  })

  test('creates a schema with all array properties set to empty arrays', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      tags: [],
      semantics: [],
      bindings: [],
    })
    assert.deepEqual(schema.tags, [])
    assert.deepEqual(schema.semantics, [])
    assert.deepEqual(schema.bindings, [])
  })

  test('creates a schema with all object properties set to empty objects', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      info: {},
      schema: {},
    })
    assert.deepInclude(schema.info, { name: 'new_property' })
    assert.deepEqual(schema.schema, {})
  })

  test('creates a schema with all array properties set to null', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      // @ts-expect-error Testing null values
      tags: null,
      // @ts-expect-error Testing null values
      semantics: null,
      // @ts-expect-error Testing null values
      bindings: null,
    })
    assert.isUndefined(schema.tags)
    assert.isUndefined(schema.semantics)
    assert.deepEqual(schema.bindings, [])
  })

  test('creates a schema with all object properties set to null', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      // @ts-expect-error Testing null values
      info: null,
      // @ts-expect-error Testing null values
      schema: null,
    })
    assert.deepInclude(schema.info, { name: 'new_property' })
    assert.isUndefined(schema.schema)
  })
})

test.group('DomainProperty.constructor()', () => {
  test('creates a DomainProperty with default values', ({ assert }) => {
    const root = new DataDomain()
    const parent = 'test-parent'
    const property = new DomainProperty(root, parent)
    assert.equal(property.kind, DomainPropertyKind)
    assert.typeOf(property.key, 'string')
    assert.isNotEmpty(property.key)
    assert.instanceOf(property.info, Thing)
    assert.deepInclude(property.info.toJSON(), { name: 'new_property' })
    assert.equal(property.type, 'string')
    assert.isUndefined(property.multiple)
    assert.isUndefined(property.required)
    assert.isUndefined(property.index)
    assert.isUndefined(property.primary)
    assert.isUndefined(property.readOnly)
    assert.isUndefined(property.writeOnly)
    assert.isUndefined(property.deprecated)
    assert.deepEqual(property.tags, [])
    assert.deepEqual(property.semantics, [])
    assert.isUndefined(property.schema)
    assert.deepEqual(property.bindings, [])
    assert.equal(property['parent'], parent)
    assert.equal(property.domain, root)
  })

  test('creates a DomainProperty with provided values', ({ assert }) => {
    const root = new DataDomain()
    const parent = 'test-parent'
    const property = new DomainProperty(root, parent, {
      key: 'test-property',
      info: { name: 'Test Property', description: 'A test property' },
      required: true,
      multiple: true,
      primary: true,
      index: true,
      readOnly: true,
      writeOnly: true,
      deprecated: true,
      tags: ['tag1', 'tag2'],
      semantics: [{ id: SemanticType.CreatedTimestamp }],
      type: 'number',
      schema: { minimum: 0 },
      bindings: [{ type: 'web', schema: { format: 'float' } }],
    })
    assert.equal(property.kind, DomainPropertyKind)
    assert.equal(property.key, 'test-property')
    assert.instanceOf(property.info, Thing)
    assert.deepInclude(property.info.toJSON(), { name: 'Test Property', description: 'A test property' })
    assert.equal(property.type, 'number')
    assert.isTrue(property.required)
    assert.isTrue(property.multiple)
    assert.isTrue(property.primary)
    assert.isTrue(property.index)
    assert.isTrue(property.readOnly)
    assert.isTrue(property.writeOnly)
    assert.isTrue(property.deprecated)
    assert.deepEqual(property.tags, ['tag1', 'tag2'])
    assert.deepEqual(property.semantics, [{ id: SemanticType.CreatedTimestamp }])
    assert.deepEqual(property.schema, { minimum: 0 })
    assert.deepEqual(property.bindings, [{ type: 'web', schema: { format: 'float' } }])
    assert.equal(property['parent'], parent)
    assert.equal(property.domain, root)
  })

  test('creates a DomainProperty with partial info', ({ assert }) => {
    const root = new DataDomain()
    const parent = 'test-parent'
    const property = new DomainProperty(root, parent, {
      info: { name: 'Test Property' },
    })
    assert.equal(property.kind, DomainPropertyKind)
    assert.typeOf(property.key, 'string')
    assert.isNotEmpty(property.key)
    assert.instanceOf(property.info, Thing)
    assert.deepInclude(property.info.toJSON(), { name: 'Test Property' })
  })

  test('creates a DomainProperty with a specific type', ({ assert }) => {
    const root = new DataDomain()
    const parent = 'test-parent'
    const property = new DomainProperty(root, parent, {
      type: 'boolean',
    })
    assert.equal(property.kind, DomainPropertyKind)
    assert.typeOf(property.key, 'string')
    assert.isNotEmpty(property.key)
    assert.instanceOf(property.info, Thing)
    assert.deepInclude(property.info.toJSON(), { name: 'new_property' })
    assert.equal(property.type, 'boolean')
  })

  test('throws an error with an invalid type', ({ assert }) => {
    const root = new DataDomain()
    const parent = 'test-parent'
    assert.throws(() => {
      // @ts-expect-error Testing invalid type
      new DomainProperty(root, parent, { type: 'invalid-type' })
    }, 'Invalid data property type invalid-type')
  })
})

test.group('DomainProperty.validate()', () => {
  test('returns false for null input', ({ assert }) => {
    const result = DomainProperty.validate(null)
    assert.isFalse(result)
  })

  test('returns false for undefined input', ({ assert }) => {
    const result = DomainProperty.validate(undefined)
    assert.isFalse(result)
  })

  test('returns false for non-object input', ({ assert }) => {
    const result = DomainProperty.validate('string')
    assert.isFalse(result)
  })

  test('returns true for valid input', ({ assert }) => {
    const validInput = {
      kind: DomainPropertyKind,
      key: 'test-key',
      info: { name: 'Test Property' },
      type: 'string',
    }
    const result = DomainProperty.validate(validInput)
    assert.isTrue(result)
  })

  test('returns false for mismatched kind in strict mode', ({ assert }) => {
    const invalidInput = {
      kind: 'InvalidKind',
      key: 'test-key',
      info: { name: 'Test Property' },
      type: 'string',
    }
    const result = DomainProperty.validate(invalidInput, { strict: true })
    assert.isFalse(result)
  })

  test('returns false for missing kind in strict mode', ({ assert }) => {
    const invalidInput = {
      key: 'test-key',
      info: { name: 'Test Property' },
      type: 'string',
    }
    const result = DomainProperty.validate(invalidInput, { strict: true })
    assert.isFalse(result)
  })

  test('returns true for missing kind in non-strict mode', ({ assert }) => {
    const validInput = {
      key: 'test-key',
      info: { name: 'Test Property' },
      type: 'string',
    }
    const result = DomainProperty.validate(validInput)
    assert.isTrue(result)
  })
})

test.group('DomainProperty.toJSON()', () => {
  test('serializes with default values', ({ assert }) => {
    const root = new DataDomain()
    const parent = 'test-parent'
    const property = new DomainProperty(root, parent)
    const json = property.toJSON()

    assert.equal(json.kind, DomainPropertyKind)
    assert.typeOf(json.key, 'string')
    assert.isNotEmpty(json.key)
    assert.deepInclude(json.info, { name: 'new_property' })
    assert.equal(json.type, 'string')
    assert.isUndefined(json.multiple)
    assert.isUndefined(json.required)
    assert.isUndefined(json.index)
    assert.isUndefined(json.primary)
    assert.isUndefined(json.readOnly)
    assert.isUndefined(json.writeOnly)
    assert.isUndefined(json.deprecated)
    assert.isUndefined(json.tags)
    assert.isUndefined(json.semantics)
    assert.isUndefined(json.schema)
    assert.isUndefined(json.bindings)
  })

  test('serializes with custom values', ({ assert }) => {
    const root = new DataDomain()
    const parent = 'test-parent'
    const property = new DomainProperty(root, parent, {
      key: 'test-property',
      info: { name: 'Test Property', description: 'A test property' },
      required: true,
      multiple: true,
      primary: true,
      index: true,
      readOnly: true,
      writeOnly: true,
      deprecated: true,
      tags: ['tag1', 'tag2'],
      semantics: [{ id: SemanticType.CreatedTimestamp }],
      type: 'number',
      schema: { minimum: 0 },
      bindings: [{ type: 'web', schema: { format: 'float' } }],
    })
    const json = property.toJSON()

    assert.equal(json.kind, DomainPropertyKind)
    assert.equal(json.key, 'test-property')
    assert.deepInclude(json.info, { name: 'Test Property', description: 'A test property' })
    assert.equal(json.type, 'number')
    assert.isTrue(json.required)
    assert.isTrue(json.multiple)
    assert.isTrue(json.primary)
    assert.isTrue(json.index)
    assert.isTrue(json.readOnly)
    assert.isTrue(json.writeOnly)
    assert.isTrue(json.deprecated)
    assert.deepEqual(json.tags, ['tag1', 'tag2'])
    assert.deepEqual(json.semantics, [{ id: SemanticType.CreatedTimestamp }])
    assert.deepEqual(json.schema, { minimum: 0 })
    assert.deepEqual(json.bindings, [{ type: 'web', schema: { format: 'float' } }])
  })

  test('serializes with empty arrays', ({ assert }) => {
    const root = new DataDomain()
    const parent = 'test-parent'
    const property = new DomainProperty(root, parent, {
      tags: [],
      semantics: [],
      bindings: [],
    })
    const json = property.toJSON()

    assert.isUndefined(json.tags)
    assert.isUndefined(json.semantics)
    assert.isUndefined(json.bindings)
  })

  test('excludes undefined properties', ({ assert }) => {
    const root = new DataDomain()
    const parent = 'test-parent'
    const property = new DomainProperty(root, parent, {
      required: undefined,
      multiple: undefined,
      index: undefined,
      primary: undefined,
      readOnly: undefined,
      writeOnly: undefined,
      deprecated: undefined,
      tags: undefined,
      semantics: undefined,
      schema: undefined,
      bindings: undefined,
    })
    const json = property.toJSON()

    assert.isUndefined(json.required)
    assert.isUndefined(json.multiple)
    assert.isUndefined(json.index)
    assert.isUndefined(json.primary)
    assert.isUndefined(json.readOnly)
    assert.isUndefined(json.writeOnly)
    assert.isUndefined(json.deprecated)
    assert.isUndefined(json.tags)
    assert.isUndefined(json.semantics)
    assert.isUndefined(json.schema)
    assert.isUndefined(json.bindings)
  })

  test('serializes nested objects', ({ assert }) => {
    const root = new DataDomain()
    const parent = 'test-parent'
    const property = new DomainProperty(root, parent, {
      info: { name: 'Nested Info', description: 'Nested description' },
      schema: { minimum: 10, maximum: 100 },
    })
    const json = property.toJSON()

    assert.deepInclude(json.info, { name: 'Nested Info', description: 'Nested description' })
    assert.deepEqual(json.schema, { minimum: 10, maximum: 100 })
  })
})

test.group('DomainProperty.remove()', () => {
  test('removes the property from the parent entity', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ key: 'test-property', type: 'string' })
    property.remove()
    assert.isFalse(dataDomain.graph.hasNode(property.key))
    assert.deepEqual(entity.fields, [])
  })
})

test.group('DomainProperty.getParentInstance()', () => {
  test('returns the parent entity when the property is a child', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty()
    const parent = property.getParentInstance()
    assert.deepEqual(parent, entity)
  })

  test('returns undefined when the property has no parent', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'non-existent-entity')
    dataDomain.graph.setNode(property.key, property)
    const parent = property.getParentInstance()
    assert.isUndefined(parent)
  })

  test('returns the correct parent when there are multiple levels of nesting', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty()
    const parent = property.getParentInstance()
    assert.deepEqual(parent, entity)
  })

  test('returns undefined when the parent entity is removed from the graph', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty()
    dataDomain.graph.removeNode(entity.key)
    const parent = property.getParentInstance()
    assert.isUndefined(parent)
  })
})

test.group('DomainProperty.isValidType()', () => {
  test('returns true for valid data types', ({ assert }) => {
    DataFormat.DomainPropertyTypes.forEach((type) => {
      assert.isTrue(DomainProperty.isValidType(type), `Type "${type}" should be valid`)
    })
  })

  test('returns false for invalid data types', ({ assert }) => {
    assert.isFalse(DomainProperty.isValidType('invalid-type'))
    assert.isFalse(DomainProperty.isValidType(123))
    assert.isFalse(DomainProperty.isValidType(true))
    assert.isFalse(DomainProperty.isValidType({}))
    assert.isFalse(DomainProperty.isValidType([]))
    assert.isFalse(DomainProperty.isValidType(null))
    assert.isFalse(DomainProperty.isValidType(undefined))
  })

  test('returns false for non-string values', ({ assert }) => {
    assert.isFalse(DomainProperty.isValidType(123))
    assert.isFalse(DomainProperty.isValidType(true))
    assert.isFalse(DomainProperty.isValidType({}))
    assert.isFalse(DomainProperty.isValidType([]))
    assert.isFalse(DomainProperty.isValidType(null))
    assert.isFalse(DomainProperty.isValidType(undefined))
  })

  test('returns false for empty string', ({ assert }) => {
    assert.isFalse(DomainProperty.isValidType(''))
  })
})

test.group('DomainProperty.isValidAttribute()', () => {
  test('returns true for valid data property attributes', ({ assert }) => {
    const validAttributes: DataFormat.DomainAttributeAttribute[] = ['required', 'multiple']
    validAttributes.forEach((attribute) => {
      assert.isTrue(DomainProperty.isValidAttribute(attribute), `Attribute "${attribute}" should be valid`)
    })
  })

  test('returns false for invalid data property attributes', ({ assert }) => {
    assert.isFalse(DomainProperty.isValidAttribute('invalid-attribute'))
    assert.isFalse(DomainProperty.isValidAttribute(123))
    assert.isFalse(DomainProperty.isValidAttribute(true))
    assert.isFalse(DomainProperty.isValidAttribute({}))
    assert.isFalse(DomainProperty.isValidAttribute([]))
    assert.isFalse(DomainProperty.isValidAttribute(null))
    assert.isFalse(DomainProperty.isValidAttribute(undefined))
  })

  test('returns false for non-string values', ({ assert }) => {
    assert.isFalse(DomainProperty.isValidAttribute(123))
    assert.isFalse(DomainProperty.isValidAttribute(true))
    assert.isFalse(DomainProperty.isValidAttribute({}))
    assert.isFalse(DomainProperty.isValidAttribute([]))
    assert.isFalse(DomainProperty.isValidAttribute(null))
    assert.isFalse(DomainProperty.isValidAttribute(undefined))
  })

  test('returns false for empty string', ({ assert }) => {
    assert.isFalse(DomainProperty.isValidAttribute(''))
  })
})

test.group('DomainProperty.isValidFormat()', () => {
  test('returns true for valid data property formats', ({ assert }) => {
    const validFormats: DataFormat.DomainPropertyFormat[] = [...DataFormat.NumberFormats, ...DataFormat.BinaryFormats]
    validFormats.forEach((format) => {
      assert.isTrue(DomainProperty.isValidFormat(format), `Format "${format}" should be valid`)
    })
  })

  test('returns true for undefined format', ({ assert }) => {
    assert.isTrue(DomainProperty.isValidFormat(undefined), 'Undefined format should be valid')
  })

  test('returns false for invalid data property formats', ({ assert }) => {
    assert.isFalse(DomainProperty.isValidFormat('invalid-format'))
    assert.isFalse(DomainProperty.isValidFormat(123))
    assert.isFalse(DomainProperty.isValidFormat(true))
    assert.isFalse(DomainProperty.isValidFormat({}))
    assert.isFalse(DomainProperty.isValidFormat([]))
    assert.isFalse(DomainProperty.isValidFormat(null))
  })

  test('returns false for non-string values', ({ assert }) => {
    assert.isFalse(DomainProperty.isValidFormat(123))
    assert.isFalse(DomainProperty.isValidFormat(true))
    assert.isFalse(DomainProperty.isValidFormat({}))
    assert.isFalse(DomainProperty.isValidFormat([]))
    assert.isFalse(DomainProperty.isValidFormat(null))
  })
})

test.group('DomainProperty.ensureSchema()', () => {
  test('creates the schema object if it does not exist', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent')
    assert.isUndefined(property.schema)
    const schema = property.ensureSchema()
    assert.isDefined(property.schema)
    assert.deepEqual(schema, {})
    assert.deepEqual(schema, schema)
  })

  test('returns the existing schema object if it exists', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { schema: { minimum: 0 } })
    const originalSchema = property.schema
    const schema = property.ensureSchema()
    assert.deepEqual(schema, { minimum: 0 })
    assert.strictEqual(schema, originalSchema)
  })

  test('does not modify the existing schema object', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { schema: { minimum: 0 } })
    const originalSchema = property.schema
    property.ensureSchema()
    assert.deepEqual(property.schema, { minimum: 0 })
    assert.strictEqual(property.schema, originalSchema)
  })
})

test.group('DomainProperty.getWebBinding()', () => {
  test('creates web bindings if they do not exist', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent')
    const webBindings = property.getWebBinding()
    assert.isDefined(webBindings)
    assert.deepEqual(webBindings, {})
    assert.lengthOf(property.bindings, 1)
    assert.equal(property.bindings[0].type, 'web')
  })

  test('returns existing web bindings if they exist', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', {
      bindings: [{ type: 'web', schema: { format: 'float' } }],
    })
    const webBindings = property.getWebBinding()
    assert.isDefined(webBindings)
    assert.deepEqual(webBindings, { format: 'float' })
    assert.lengthOf(property.bindings, 1)
    assert.equal(property.bindings[0].type, 'web')
  })

  test('does not modify existing web bindings object', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', {
      bindings: [{ type: 'web', schema: { format: 'float' } }],
    })
    const originalBindings = property.bindings[0].schema
    property.getWebBinding()
    assert.deepEqual(property.bindings[0].schema, { format: 'float' })
    assert.deepEqual(property.bindings[0].schema, originalBindings)
  })

  test('notifies change when creating new bindings', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent')
    const webBindings = property.getWebBinding()
    assert.isDefined(webBindings)
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  })
})

test.group('DomainProperty.readBinding()', () => {
  test('returns undefined if no binding exists for the given type', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent')
    const binding = property.readBinding('web')
    assert.isUndefined(binding)
  })

  test('returns the web binding schema if it exists', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', {
      bindings: [{ type: 'web', schema: { format: 'float' } }],
    })
    const webBindings = property.readBinding('web')
    assert.deepEqual(webBindings, { format: 'float' })
  })

  test('returns the protobuf binding schema if it exists', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', {
      bindings: [{ type: 'protobuf', schema: { field: 1 } }],
    })
    const protobufBindings = property.readBinding('protobuf')
    assert.deepEqual(protobufBindings, { field: 1 })
  })

  test('returns undefined if the binding exists but has no schema', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', {
      // @ts-expect-error Testing undefined value
      bindings: [{ type: 'web', schema: undefined }],
    })
    const webBindings = property.readBinding('web')
    assert.isUndefined(webBindings)
  })

  test('returns undefined if the binding exists but the schema is null', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', {
      // @ts-expect-error Testing null value
      bindings: [{ type: 'web', schema: null }],
    })
    const webBindings = property.readBinding('web')
    assert.isUndefined(webBindings)
  })
})

test.group('DomainProperty.toApiShape()', () => {
  test('returns an object', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty()
    const result = property.toApiShape()
    assert.typeOf(result, 'object')
  })

  test('returns a shape with the correct kind', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty()
    const result = property.toApiShape()
    // While we can't directly test the kind (as it's an AMF object),
    // we can check for the presence of the @type property
    // which is a good indicator that it's a valid AMF shape.
    assert.isDefined(result)
  })

  test('returns a shape with the correct id', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ key: 'test-property' })
    const result = property.toApiShape()
    assert.isDefined(result)
  })

  test('returns a shape with the correct data type', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ type: 'number' })
    const result = property.toApiShape()
    assert.isDefined(result)
  })

  test('returns a shape with the correct data type and format', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ type: 'number' })
    property.getWebBinding().format = 'float'
    const result = property.toApiShape()
    assert.isDefined(result)
  })
})

test.group('DomainProperty.addSemantic()', () => {
  test('adds a new semantic to the property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'datetime' })
    const semantic = { id: SemanticType.CreatedTimestamp }
    property.addSemantic(semantic)
    assert.deepInclude(property.semantics, semantic)
  })

  test('updates an existing semantic', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'datetime' })
    const semantic1 = { id: SemanticType.CreatedTimestamp, config: { value: 'old' } }
    const semantic2 = { id: SemanticType.CreatedTimestamp, config: { value: 'new' } }
    property.addSemantic(semantic1)
    property.addSemantic(semantic2)
    assert.lengthOf(property.semantics, 1)
    assert.deepInclude(property.semantics, semantic2)
  })

  test('throws an error if the semantic is not a property semantic', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent')
    const semantic = { id: SemanticType.User } // User is an Entity semantic
    assert.throws(() => {
      property.addSemantic(semantic)
    }, `Invalid semantic type: ${SemanticType.User}. Expected a property semantic.`)
  })

  test('notifies change when adding a new semantic', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'datetime' })
    const semantic = { id: SemanticType.CreatedTimestamp }
    property.addSemantic(semantic)
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  })

  test('notifies change when updating an existing semantic', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'datetime' })
    const semantic1 = { id: SemanticType.CreatedTimestamp, config: { value: 'old' } }
    const semantic2 = { id: SemanticType.CreatedTimestamp, config: { value: 'new' } }
    property.addSemantic(semantic1)
    // Clear the event queue before the second add
    await new Promise((resolve) => setTimeout(resolve, 0))
    property.addSemantic(semantic2)
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  })
})

test.group('DomainProperty.removeSemantic()', () => {
  test('removes an existing semantic from the property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'datetime' })
    const semantic = { id: SemanticType.CreatedTimestamp }
    property.addSemantic(semantic)
    assert.deepInclude(property.semantics, semantic)
    property.removeSemantic(SemanticType.CreatedTimestamp)
    assert.notDeepInclude(property.semantics, semantic)
    assert.lengthOf(property.semantics, 0)
  })

  test('does nothing if the semantic to remove does not exist', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'datetime' })
    const semantic = { id: SemanticType.CreatedTimestamp }
    property.addSemantic(semantic)
    const initialSemantics = [...property.semantics]
    property.removeSemantic('non-existent-semantic-id' as SemanticType)
    assert.deepEqual(property.semantics, initialSemantics)
  })

  test('notifies change when a semantic is removed', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'datetime' })
    const semantic = { id: SemanticType.CreatedTimestamp }
    property.addSemantic(semantic)
    // Clear the event queue before removal
    await new Promise((resolve) => setTimeout(resolve, 0))
    property.removeSemantic(SemanticType.CreatedTimestamp)
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  })

  test('does not notify change if the semantic to remove does not exist', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent')
    // Clear the event queue before removal
    await new Promise((resolve) => setTimeout(resolve, 0))
    property.removeSemantic('non-existent-semantic-id' as SemanticType)
    await assert.notDispatches(dataDomain, 'change', { timeout: 20 })
  })
})

test.group('DomainProperty.hasSemantic()', () => {
  test('returns true if the property has the specified semantic', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'datetime' })
    const semantic = { id: SemanticType.CreatedTimestamp }
    property.addSemantic(semantic)
    assert.isTrue(property.hasSemantic(SemanticType.CreatedTimestamp))
  })

  test('returns false if the property does not have the specified semantic', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent')
    assert.isFalse(property.hasSemantic(SemanticType.CreatedTimestamp))
  })

  test('returns false for an empty semantics array', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent')
    assert.isFalse(property.hasSemantic(SemanticType.CreatedTimestamp))
  })
})

test.group('DomainProperty with GeospatialCoordinates semantic', () => {
  test('can add GeospatialCoordinates semantic to a string property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'string' })
    const semantic = { id: SemanticType.GeospatialCoordinates }
    property.addSemantic(semantic)
    assert.deepInclude(property.semantics, semantic)
    assert.isTrue(property.hasSemantic(SemanticType.GeospatialCoordinates))
  })

  test('can add GeospatialCoordinates semantic with configuration', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'string' })
    const semantic = {
      id: SemanticType.GeospatialCoordinates,
      config: { format: 'lat,lon' },
    }
    property.addSemantic(semantic)
    assert.deepInclude(property.semantics, semantic)
    assert.isTrue(property.hasSemantic(SemanticType.GeospatialCoordinates))
  })

  test('can remove GeospatialCoordinates semantic', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'string' })
    const semantic = { id: SemanticType.GeospatialCoordinates }
    property.addSemantic(semantic)
    assert.isTrue(property.hasSemantic(SemanticType.GeospatialCoordinates))

    property.removeSemantic(SemanticType.GeospatialCoordinates)
    assert.isFalse(property.hasSemantic(SemanticType.GeospatialCoordinates))
    assert.lengthOf(property.semantics, 0)
  })

  test('can update existing GeospatialCoordinates semantic', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'string' })
    const semantic1 = {
      id: SemanticType.GeospatialCoordinates,
      config: { format: 'lat,lon' },
    }
    const semantic2 = {
      id: SemanticType.GeospatialCoordinates,
      config: { format: 'postgis' },
    }

    property.addSemantic(semantic1)
    property.addSemantic(semantic2)

    assert.lengthOf(property.semantics, 1)
    assert.deepInclude(property.semantics, semantic2)
    assert.isTrue(property.hasSemantic(SemanticType.GeospatialCoordinates))
  })

  test('serializes GeospatialCoordinates semantic correctly', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', {
      type: 'string',
      semantics: [{ id: SemanticType.GeospatialCoordinates }],
    })

    const json = property.toJSON()
    assert.deepEqual(json.semantics, [{ id: SemanticType.GeospatialCoordinates }])
  })

  test('creates schema with GeospatialCoordinates semantic', ({ assert }) => {
    const schema = DomainProperty.createSchema({
      type: 'string',
      semantics: [{ id: SemanticType.GeospatialCoordinates }],
    })

    assert.deepEqual(schema.semantics, [{ id: SemanticType.GeospatialCoordinates }])
    assert.equal(schema.type, 'string')
  })

  test('constructor accepts GeospatialCoordinates semantic', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', {
      type: 'string',
      semantics: [{ id: SemanticType.GeospatialCoordinates }],
    })

    assert.deepEqual(property.semantics, [{ id: SemanticType.GeospatialCoordinates }])
    assert.isTrue(property.hasSemantic(SemanticType.GeospatialCoordinates))
  })

  test('notifies change when adding GeospatialCoordinates semantic', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'string' })
    const semantic = { id: SemanticType.GeospatialCoordinates }
    property.addSemantic(semantic)
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  })

  test('notifies change when removing GeospatialCoordinates semantic', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { type: 'string' })
    const semantic = { id: SemanticType.GeospatialCoordinates }
    property.addSemantic(semantic)
    // Clear the event queue before removal
    await new Promise((resolve) => setTimeout(resolve, 0))
    property.removeSemantic(SemanticType.GeospatialCoordinates)
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  })
})

test.group('DomainProperty.toExample()', () => {
  test('generates a string example for string property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'name', type: 'string' })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'string')
    assert.isNotEmpty(result as string)
  })

  test('generates a number example for number property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'age', type: 'number' })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'number')
  })

  test('generates a boolean example for boolean property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'active', type: 'boolean' })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'boolean')
  })

  test('generates a string example for integer property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'count', type: 'number' })
    property.getWebBinding().format = 'int32'

    const result = property.toExample('application/json')
    assert.typeOf(result, 'number')
    assert.isTrue(Number.isInteger(result as number))
  })

  test('generates a string example for datetime property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'created', type: 'datetime' })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'string')
    // Should match ISO 8601 format
    const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
    assert.match(result as string, isoDateRegex)
  })

  test('generates a string example for date property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'birthdate', type: 'date' })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'string')
    // Should match YYYY-MM-DD format
    const dateRegex = /^\d{4}-\d{2}-\d{2}$/
    assert.match(result as string, dateRegex)
  })

  test('generates a string example for time property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'appointment', type: 'time' })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'string')
    // Should match time format
    const timeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/
    assert.match(result as string, timeRegex)
  })

  test('respects minimum constraint for number property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({
      key: 'score',
      type: 'number',
      schema: { minimum: 10 },
    })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'number')
    assert.isAtLeast(result as number, 10)
  })

  test('respects maximum constraint for number property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({
      key: 'score',
      type: 'number',
      schema: { maximum: 100 },
    })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'number')
    assert.isAtMost(result as number, 100)
  })

  test('respects minimum and maximum constraints for number property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({
      key: 'score',
      type: 'number',
      schema: { minimum: 10, maximum: 100 },
    })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'number')
    assert.isAtLeast(result as number, 10)
    assert.isAtMost(result as number, 100)
  })

  test('respects minimum length constraint for string property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({
      key: 'description',
      type: 'string',
      schema: { minimum: 20 },
    })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'string')
    assert.isAtLeast((result as string).length, 20)
  })

  test('respects maximum length constraint for string property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({
      key: 'name',
      type: 'string',
      schema: { maximum: 10 },
    })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'string')
    assert.isAtMost((result as string).length, 10)
  })

  test('respects enum constraint for string property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({
      key: 'status',
      type: 'string',
      schema: { enum: ['active', 'inactive', 'pending'] },
    })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'string')
    assert.oneOf(result as string, ['active', 'inactive', 'pending'])
  })

  test('uses default value when provided', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({
      key: 'status',
      type: 'string',
      schema: { defaultValue: { type: 'literal', value: 'default-value' } },
    })

    const result = property.toExample('application/json')
    assert.equal(result, 'default-value')
  })

  test('handles XML mime type', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'name', type: 'string' })

    const result = property.toExample('application/xml')
    assert.typeOf(result, 'string')
    assert.isNotEmpty(result as string)
  })

  test('handles unsupported mime type', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'name', type: 'string' })

    const result = property.toExample('application/octet-stream')
    assert.isUndefined(result)
  })

  test('returns undefined when property has no range', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'test-parent', { key: 'test-prop' })

    // Mock the toApiShape method to return a shape without range
    const originalToApiShape = property.toApiShape
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    property.toApiShape = () => ({ range: undefined }) as any

    const result = property.toExample('application/json')
    assert.isUndefined(result)

    // Restore original method
    property.toApiShape = originalToApiShape
  })

  test('respects renderExamples option', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'name', type: 'string' })
    property.schema = { examples: ['example1', 'example2'] }

    const result = property.toExample('application/json', { renderExamples: false })
    assert.typeOf(result, 'string')
    assert.notInclude(['example1', 'example2'], result as string)
  })

  test('respects renderMocked option', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'name', type: 'string' })

    const result = property.toExample('application/json', { renderMocked: false })
    // No examples and no mocked value, should return a valid empty example
    assert.typeOf(result, 'string')
    assert.isEmpty(result as string)
  })

  test('respects renderOptional option', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'name', type: 'string', schema: { examples: ['test'] } })

    const result = property.toExample('application/json', { renderOptional: false })
    assert.isUndefined(result, 'optional properties should not render examples when renderOptional is false')
  })

  test('handles binary property type', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'data', type: 'binary' })

    const result = property.toExample('application/json')
    assert.isUndefined(result, 'currently binary properties do not have examples')
  })

  test('handles array property type', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'tags', type: 'string', multiple: true })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'string')
    assert.isNotEmpty(result as string)
    assert.isArray(JSON.parse(result as string), 'returns an array for "multiple" properties')
  })

  test('handles property with web binding format', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'price', type: 'number' })

    const webBinding = property.getWebBinding()
    webBinding.format = 'float'

    const result = property.toExample('application/json')
    assert.typeOf(result, 'number')
  })

  test('generates consistent examples for same property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({
      key: 'count',
      type: 'number',
      schema: { minimum: 5, maximum: 10 },
    })

    const result1 = property.toExample('application/json')
    const result2 = property.toExample('application/json')

    // Both should be numbers within the range
    assert.typeOf(result1, 'number')
    assert.typeOf(result2, 'number')
    assert.isAtLeast(result1 as number, 5)
    assert.isAtMost(result1 as number, 10)
    assert.isAtLeast(result2 as number, 5)
    assert.isAtMost(result2 as number, 10)
  })

  test('handles property with pattern constraint', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({
      key: 'email',
      type: 'string',
      schema: { pattern: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$' },
    })

    const result = property.toExample('application/json')
    assert.typeOf(result, 'string')
    assert.isNotEmpty(result as string)
    // Should match email pattern
    const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
    assert.match(result as string, emailRegex)
  })
})

test.group('DomainProperty.duplicate()', () => {
  test('duplicates a property successfully', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({
      info: { name: 'originalProperty' },
      type: 'string',
      required: true,
      multiple: false,
    })

    const duplicate = property.duplicate()

    assert.isDefined(duplicate)
    assert.notEqual(duplicate.key, property.key)
    assert.equal(duplicate.info.name, 'originalProperty_copy')
    assert.equal(duplicate.type, 'string')
    assert.equal(duplicate.required, true)
    assert.equal(duplicate.multiple, false)
  })

  test('places duplicate right after original property in fields list', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()

    // Add multiple properties to test ordering
    const property1 = entity.addProperty({ info: { name: 'first' } })
    const property2 = entity.addProperty({ info: { name: 'second' } })
    const property3 = entity.addProperty({ info: { name: 'third' } })

    const duplicate = property2.duplicate()

    // Check that duplicate is placed right after property2
    const fieldKeys = entity.fields.map((f) => f.key)
    const property2Index = fieldKeys.indexOf(property2.key)
    const duplicateIndex = fieldKeys.indexOf(duplicate.key)

    assert.equal(duplicateIndex, property2Index + 1)
    assert.deepEqual(fieldKeys, [property1.key, property2.key, duplicate.key, property3.key])
  })

  test('generates unique name when base name conflicts', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()

    const property = entity.addProperty({ info: { name: 'test' } })
    // Add a property that conflicts with the first generated name
    entity.addProperty({ info: { name: 'test_copy' } })

    const duplicate = property.duplicate()

    assert.equal(duplicate.info.name, 'test_copy_2')
  })

  test('uses fallback name when original property has no name', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()

    const property = entity.addProperty({ info: {} }) // No name

    // The property gets the default name "new_property" from schema creation
    assert.equal(property.info.name, 'new_property')

    const duplicate = property.duplicate()

    assert.equal(duplicate.info.name, 'new_property_copy')
  })

  test('uses field fallback when property name is explicitly undefined', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()

    const property = entity.addProperty({ info: { name: 'test' } })
    // Manually set name to undefined to test the fallback logic
    property.info.name = undefined

    const duplicate = property.duplicate()

    assert.equal(duplicate.info.name, 'field_copy')
  })

  test('does not copy key property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()

    const property = entity.addProperty({
      key: 'custom-key',
      info: { name: 'testProperty' },
    })

    const duplicate = property.duplicate()

    assert.notEqual(duplicate.key, property.key)
    assert.notEqual(duplicate.key, 'custom-key')
  })

  test('does not copy primary key flag', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()

    const property = entity.addProperty({
      info: { name: 'id' },
      primary: true,
    })

    const duplicate = property.duplicate()

    assert.isTrue(property.primary)
    assert.isUndefined(duplicate.primary)
  })

  test('copies all other property attributes', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()

    const property = entity.addProperty({
      info: { name: 'complexProperty', description: 'A complex property' },
      type: 'number',
      required: true,
      multiple: true,
      index: true,
      readOnly: true,
      writeOnly: false,
      deprecated: true,
      tags: ['tag1', 'tag2'],
      semantics: [{ id: SemanticType.CreatedTimestamp }],
      schema: { minimum: 0, maximum: 100 },
      bindings: [{ type: 'web', schema: { format: 'float' } }],
    })

    const duplicate = property.duplicate()

    assert.equal(duplicate.info.name, 'complexProperty_copy')
    assert.equal(duplicate.info.description, 'A complex property')
    assert.equal(duplicate.type, 'number')
    assert.equal(duplicate.required, true)
    assert.equal(duplicate.multiple, true)
    assert.equal(duplicate.index, true)
    assert.equal(duplicate.readOnly, true)
    assert.equal(duplicate.writeOnly, false)
    assert.equal(duplicate.deprecated, true)
    assert.deepEqual(duplicate.tags, ['tag1', 'tag2'])
    assert.deepEqual(duplicate.semantics, [{ id: SemanticType.CreatedTimestamp }])
    assert.deepEqual(duplicate.schema, { minimum: 0, maximum: 100 })
    assert.deepEqual(duplicate.bindings, [{ type: 'web', schema: { format: 'float' } }])
  })

  test('creates independent copy of schema and bindings', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()

    const property = entity.addProperty({
      info: { name: 'original' },
      schema: { minimum: 0 },
      bindings: [{ type: 'web', schema: { format: 'float' } }],
    })

    const duplicate = property.duplicate()

    // Modify original schema and bindings
    property.schema!.minimum = 10
    property.bindings[0].schema = { format: 'double' }

    // Duplicate should be unaffected
    assert.equal(duplicate.schema!.minimum, 0)
    assert.deepEqual(duplicate.bindings[0].schema, { format: 'float' })
  })

  test('notifies domain of changes', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ info: { name: 'test' } })

    let notificationCalled = false
    const originalNotify = dataDomain.notifyChange
    dataDomain.notifyChange = () => {
      notificationCalled = true
      originalNotify.call(dataDomain)
    }

    property.duplicate()

    assert.isTrue(notificationCalled)

    // Restore original method
    dataDomain.notifyChange = originalNotify
  })

  test('throws error when property has no parent entity', ({ assert }) => {
    const dataDomain = new DataDomain()
    const property = new DomainProperty(dataDomain, 'non-existent-parent', {
      info: { name: 'orphan' },
    })

    assert.throws(() => {
      property.duplicate()
    }, 'Cannot duplicate property')
  })

  test('throws error when property is not in parent entity fields list', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = new DomainProperty(dataDomain, entity.key, {
      info: { name: 'detached' },
    })

    // Add property to graph but not to entity fields
    dataDomain.graph.setNode(property.key, property)

    assert.throws(() => {
      property.duplicate()
    }, 'does not exist on the parent entity fields list')
  })

  test('works with empty entity fields list', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ info: { name: 'only' } })

    const duplicate = property.duplicate()

    assert.equal(entity.fields.length, 2)
    assert.equal(entity.fields[0].key, property.key)
    assert.equal(entity.fields[1].key, duplicate.key)
  })

  test('handles duplication of property with associations in between', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const targetEntity = model.addEntity()

    const property1 = entity.addProperty({ info: { name: 'prop1' } })
    const association = entity.addAssociation({ key: targetEntity.key })
    association.info.name = 'assoc1'
    const property2 = entity.addProperty({ info: { name: 'prop2' } })

    const duplicate = property1.duplicate()

    // Should maintain correct order
    const fieldKeys = entity.fields.map((f) => f.key)
    assert.deepEqual(fieldKeys, [property1.key, duplicate.key, association.key, property2.key])
  })

  test('preserves field types correctly', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ info: { name: 'test' } })

    const duplicate = property.duplicate()

    // Check that both original and duplicate have correct field type
    const originalField = entity.fields.find((f) => f.key === property.key)
    const duplicateField = entity.fields.find((f) => f.key === duplicate.key)

    assert.equal(originalField?.type, 'property')
    assert.equal(duplicateField?.type, 'property')
  })
})
