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

test.group('DataDomain change observers', (g) => {
  g.tests.forEach((test) => {
    test.tags(['@modeling', '@domain', '@change_observers'])
  })

  test('does not notifies a change when initializing a domain', async ({ assert }) => {
    const domain = new DataDomain()
    await assert.notDispatches(domain, 'change', { timeout: 1 })
  })

  test('notifies a change when changing info.name', async ({ assert }) => {
    const domain = new DataDomain()
    domain.info.name = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.description', async ({ assert }) => {
    const domain = new DataDomain()
    domain.info.description = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.displayName', async ({ assert }) => {
    const domain = new DataDomain()
    domain.info.displayName = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.version', async ({ assert }) => {
    const domain = new DataDomain()
    domain.info.version = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })
})

test.group('DomainNamespace change observers', (g) => {
  g.tests.forEach((test) => {
    test.tags(['@modeling', '@namespace', '@change_observers'])
  })

  test('notifies a change when changing info.name', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    await sleep(1)
    n1.info.name = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.description', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    await sleep(1)
    n1.info.description = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.displayName', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    await sleep(1)
    n1.info.displayName = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.version', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    await sleep(1)
    n1.info.version = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing deprecated', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    await sleep(1)
    n1.deprecated = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when adding a new namespace', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    await sleep(1)
    n1.addNamespace()
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when removing new namespace', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const n2 = n1.addNamespace()
    await sleep(1)
    n1.removeNamespace(n2.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when attaching a namespace', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const n2 = n1.addNamespace()
    const n3 = domain.addNamespace()
    await sleep(1)
    n2.attachNamespace(n3.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when adding a new model', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    await sleep(1)
    n1.addModel()
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when removing a model', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    await sleep(1)
    n1.removeModel(m1.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when attaching a model', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = domain.addModel()
    await sleep(1)
    n1.attachModel(m1.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })
})

test.group('DomainModel change observers', (g) => {
  g.tests.forEach((test) => {
    test.tags(['@modeling', '@model', '@change_observers'])
  })

  test('notifies a change when changing info.name', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    await sleep(1)
    m1.info.name = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.description', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    await sleep(1)
    m1.info.description = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.displayName', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    await sleep(1)
    m1.info.displayName = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.version', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    await sleep(1)
    m1.info.version = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing deprecated', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    await sleep(1)
    m1.deprecated = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when adding a new entity', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    await sleep(1)
    m1.addEntity()
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when removing an entity', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    await sleep(1)
    m1.removeEntity(e1.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when attaching an entity', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const m2 = n1.addModel()
    const e1 = m2.addEntity()
    await sleep(1)
    m1.attachEntity(e1.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })
})

test.group('DomainEntity change observers', (g) => {
  g.tests.forEach((test) => {
    test.tags(['@modeling', '@entity', '@change_observers'])
  })

  test('notifies a change when changing info.name', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    await sleep(1)
    e1.info.name = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.description', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    await sleep(1)
    e1.info.description = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.displayName', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    await sleep(1)
    e1.info.displayName = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.version', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    await sleep(1)
    e1.info.version = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing deprecated', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    await sleep(1)
    e1.deprecated = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when adding a tag', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    await sleep(1)
    e1.tags.push('test')
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when removing a tag', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    e1.tags.push('test')
    await sleep(1)
    e1.tags.pop()
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when adding a semantics', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    await sleep(1)
    e1.semantics.push({ id: SemanticType.User })
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when removing a semantics', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    e1.semantics.push({ id: SemanticType.User })
    await sleep(1)
    e1.semantics.pop()
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when adding a new property', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    await sleep(1)
    e1.addProperty()
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when removing a property', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    e1.removeProperty(p1.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when adding a new parent', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    await sleep(1)
    e1.addParent(e2.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when removing a parent', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    e1.addParent(e2.key)
    await sleep(1)
    e1.removeParent(e2.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when adding a new association', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    await sleep(1)
    e1.addAssociation()
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies when removing an association', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const a1 = e1.addAssociation()
    await sleep(1)
    e1.removeAssociation(a1.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })
})

test.group('DomainProperty change observers', (g) => {
  g.tests.forEach((test) => {
    test.tags(['@modeling', '@property', '@change_observers'])
  })

  test('notifies a change when changing info.name', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.info.name = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.description', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.info.description = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.displayName', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.info.displayName = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.version', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.info.version = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing required', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.required = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing multiple', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.multiple = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing primary', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.primary = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing index', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.index = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing readOnly', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.readOnly = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing writeOnly', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.writeOnly = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing deprecated', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.deprecated = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when adding a tag', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.tags.push('test')
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when removing a tag', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    p1.tags.push('test')
    await sleep(1)
    p1.tags.pop()
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when adding a semantics', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.semantics.push({ id: SemanticType.CreatedTimestamp })
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when removing a semantics', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    p1.semantics.push({ id: SemanticType.CreatedTimestamp })
    await sleep(1)
    p1.semantics.pop()
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing type', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.type = DataFormat.DomainPropertyList.number
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing schema', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.schema = { minimum: 1 }
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing bindings', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty()
    await sleep(1)
    p1.bindings.push({ type: 'web', schema: {} })
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })
})

test.group('DomainAssociation change observers', (g) => {
  g.tests.forEach((test) => {
    test.tags(['@modeling', '@association', '@change_observers'])
  })

  test('notifies a change when changing info.name', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const a1 = e1.addAssociation()
    await sleep(1)
    a1.info.name = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.description', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const a1 = e1.addAssociation()
    await sleep(1)
    a1.info.description = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.displayName', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const a1 = e1.addAssociation()
    await sleep(1)
    a1.info.displayName = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing info.version', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const a1 = e1.addAssociation()
    await sleep(1)
    a1.info.version = 'test'
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing required', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const a1 = e1.addAssociation()
    await sleep(1)
    a1.required = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing multiple', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const a1 = e1.addAssociation()
    await sleep(1)
    a1.multiple = true
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when changing schema', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const a1 = e1.addAssociation()
    await sleep(1)
    a1.schema = { linked: true }
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when adding a binding', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const a1 = e1.addAssociation()
    await sleep(1)
    a1.bindings.push({ type: 'web', schema: {} })
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when adding a target', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const a1 = e1.addAssociation()
    await sleep(1)
    a1.addTarget(e2.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })

  test('notifies a change when removing a target', async ({ assert, sleep }) => {
    const domain = new DataDomain()
    const n1 = domain.addNamespace()
    const m1 = n1.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const a1 = e1.addAssociation()
    a1.addTarget(e2.key)
    await sleep(1)
    a1.removeTarget(e2.key)
    await assert.dispatchCount(domain, 'change', 1, { timeout: 1 })
  })
})
