import { test } from '@japa/runner'
import {
  DomainAssociationKind,
  DataDomain,
  DomainEntityKind,
  DomainModelKind,
  DomainNamespaceKind,
  DomainPropertyKind,
} from '../../../src/index.js'

test.group('DataDomain Serialization and Deserialization', () => {
  test('should serialize and deserialize an empty DataDomain', ({ assert }) => {
    const domain = new DataDomain()
    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 0)
    assert.equal([...restored.graph.edges()].length, 0)
  }).tags(['@modeling', '@serialization'])

  test('should serialize and deserialize a DataDomain with namespaces', ({ assert }) => {
    const domain = new DataDomain()
    const ns1 = domain.addNamespace()
    const ns2 = ns1.addNamespace()

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 2)
    assert.equal([...restored.graph.edges()].length, 0)

    const rn1 = restored.findNamespace(ns1.key)
    assert.isDefined(rn1)
    assert.equal(rn1!.key, ns1.key)
    assert.equal(rn1!.kind, DomainNamespaceKind)

    const rn2 = restored.findNamespace(ns2.key)
    assert.isDefined(rn2)
    assert.equal(rn2!.key, ns2.key)
    assert.equal(rn2!.kind, DomainNamespaceKind)
    assert.equal(restored.graph.parent(ns2.key), ns1.key)
  }).tags(['@modeling', '@serialization'])

  test('should serialize and deserialize a DataDomain with models', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const m2 = domain.addModel()

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 2)
    assert.equal([...restored.graph.edges()].length, 0)

    const rm1 = restored.findModel(m1.key)
    assert.isDefined(rm1)
    assert.equal(rm1!.key, m1.key)
    assert.equal(rm1!.kind, DomainModelKind)

    const rm2 = restored.findModel(m2.key)
    assert.isDefined(rm2)
    assert.equal(rm2!.key, m2.key)
    assert.equal(rm2!.kind, DomainModelKind)
  }).tags(['@modeling', '@serialization'])

  test('should serialize and deserialize a DataDomain with entities', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    e2.addParent(e1.key)

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 3)
    assert.equal([...restored.graph.edges()].length, 1)

    const re1 = restored.findEntity(e1.key)
    assert.isDefined(re1)
    assert.equal(re1!.key, e1.key)
    assert.equal(re1!.kind, DomainEntityKind)

    const re2 = restored.findEntity(e2.key)
    assert.isDefined(re2)
    assert.equal(re2!.key, e2.key)
    assert.equal(re2!.kind, DomainEntityKind)
    assert.equal([...re2!.listParents()][0].key, e1.key)
  }).tags(['@modeling', '@serialization'])

  test('should serialize and deserialize a DataDomain with properties', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty({ type: 'string' })
    const p2 = e1.addProperty({ type: 'number' })

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 4)
    assert.equal([...restored.graph.edges()].length, 2)

    const rp1 = restored.findProperty(p1.key)
    assert.isDefined(rp1)
    assert.equal(rp1!.key, p1.key)
    assert.equal(rp1!.kind, DomainPropertyKind)

    const rp2 = restored.findProperty(p2.key)
    assert.isDefined(rp2)
    assert.equal(rp2!.key, p2.key)
    assert.equal(rp2!.kind, DomainPropertyKind)

    const re1 = restored.findEntity(e1.key)
    assert.isDefined(re1)
    const props = [...re1!.listProperties()]
    assert.equal(props.length, 2)
    assert.equal(props[0].key, p1.key)
    assert.equal(props[1].key, p2.key)
  }).tags(['@modeling', '@serialization'])

  test('should serialize and deserialize a DataDomain with associations', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const a1 = e1.addAssociation({ key: e2.key })

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 4)
    assert.equal([...restored.graph.edges()].length, 2)

    const ra1 = restored.findAssociation(a1.key)
    assert.isDefined(ra1)
    assert.equal(ra1!.key, a1.key)
    assert.equal(ra1!.kind, DomainAssociationKind)
    assert.equal([...ra1!.listTargets()][0].key, e2.key)
  }).tags(['@modeling', '@serialization'])

  test('should serialize and deserialize a complex DataDomain', ({ assert }) => {
    const domain = new DataDomain()
    const ns1 = domain.addNamespace()
    const ns2 = ns1.addNamespace()
    const m1 = ns1.addModel()
    const m2 = ns2.addModel()
    const e1 = m1.addEntity()
    const e2 = m2.addEntity()
    e2.addParent(e1.key)
    const p1 = e1.addProperty({ type: 'string' })
    const p2 = e2.addProperty({ type: 'number' })
    const a1 = e1.addAssociation({ key: e2.key })

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 9)
    assert.equal([...restored.graph.edges()].length, 5)

    const rn1 = restored.findNamespace(ns1.key)
    assert.isDefined(rn1)
    assert.equal(rn1!.key, ns1.key)
    assert.equal(rn1!.kind, DomainNamespaceKind)
    assert.equal([...rn1!.listNamespaces()][0].key, ns2.key)
    assert.equal([...rn1!.listModels()][0].key, m1.key)

    const rn2 = restored.findNamespace(ns2.key)
    assert.isDefined(rn2)
    assert.equal(rn2!.key, ns2.key)
    assert.equal(rn2!.kind, DomainNamespaceKind)
    assert.equal(restored.graph.parent(ns2.key), ns1.key)
    assert.deepEqual([...rn2!.listNamespaces()], [])
    assert.equal([...rn2!.listModels()][0].key, m2.key)

    const rm1 = restored.findModel(m1.key)
    assert.isDefined(rm1)
    assert.equal(rm1!.key, m1.key)
    assert.equal(rm1!.kind, DomainModelKind)
    assert.equal(restored.graph.parent(m1.key), ns1.key)

    const rm2 = restored.findModel(m2.key)
    assert.isDefined(rm2)
    assert.equal(rm2!.key, m2.key)
    assert.equal(rm2!.kind, DomainModelKind)
    assert.equal(restored.graph.parent(m2.key), ns2.key)

    const re1 = restored.findEntity(e1.key)
    assert.isDefined(re1)
    assert.equal(re1!.key, e1.key)
    assert.equal(re1!.kind, DomainEntityKind)
    assert.equal(restored.graph.parent(e1.key), m1.key)
    assert.equal([...re1!.listProperties()][0].key, p1.key)

    const re2 = restored.findEntity(e2.key)
    assert.isDefined(re2)
    assert.equal(re2!.key, e2.key)
    assert.equal(re2!.kind, DomainEntityKind)
    assert.equal(restored.graph.parent(e2.key), m2.key)
    assert.equal([...re2!.listParents()][0].key, e1.key)
    assert.equal([...re2!.listProperties()][0].key, p2.key)

    const rp1 = restored.findProperty(p1.key)
    assert.isDefined(rp1)
    assert.equal(rp1!.key, p1.key)
    assert.equal(rp1!.kind, DomainPropertyKind)

    const rp2 = restored.findProperty(p2.key)
    assert.isDefined(rp2)
    assert.equal(rp2!.key, p2.key)
    assert.equal(rp2!.kind, DomainPropertyKind)

    const ra1 = restored.findAssociation(a1.key)
    assert.isDefined(ra1)
    assert.equal(ra1!.key, a1.key)
    assert.equal(ra1!.kind, DomainAssociationKind)
    assert.equal([...ra1!.listTargets()][0].key, e2.key)
  }).tags(['@modeling', '@serialization'])

  test('should serialize and deserialize a DataDomain with foreign dependencies', ({ assert }) => {
    const fd = new DataDomain()
    const fm1 = fd.addModel({ key: 'fm1' })
    const fe1 = fm1.addEntity({ key: 'fe1' })
    fe1.addProperty({ type: 'string', key: 'fp1' })
    fd.info.version = '1.0.0'

    const domain = new DataDomain()
    domain.registerForeignDomain(fd)
    const m1 = domain.addModel({ key: 'm1' })
    const e1 = m1.addEntity({ key: 'e1' })
    const a1 = e1.addAssociation({ key: fe1.key, domain: fd.key }, { key: 'a1' })
    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized, [fd])

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 6)
    assert.equal([...restored.graph.edges()].length, 3)
    const rfe1 = restored.findForeignEntity(fe1.key, fd.key)
    assert.isDefined(rfe1)
    assert.equal(rfe1!.key, fe1.key)
    assert.equal(rfe1!.kind, DomainEntityKind)
    // Associations look like: Entity -> (edge) -> Association -> (edge) -> ForeignEntity
    assert.isTrue(restored.graph.hasEdge(e1.key, a1.key), 'has edge from e1 to a1')
    assert.isTrue(restored.graph.hasEdge(a1.key, `${fd.key}:${fe1.key}`), 'has edge from a1 to fe1')
  }).tags(['@modeling', '@serialization'])

  test('should serialize and deserialize a DataDomain with missing foreign dependencies', ({ assert }) => {
    const fd = new DataDomain()
    fd.info.version = '1.0.0'
    const fm1 = fd.addModel()
    const fe1 = fm1.addEntity()
    fd.addProperty(fe1.key, { type: 'string' })

    const domain = new DataDomain()
    domain.registerForeignDomain(fd)
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const a1 = e1.addAssociation({ key: fe1.key, domain: fd.key })

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 3)
    assert.equal([...restored.graph.edges()].length, 1)
    assert.isTrue(restored.graph.hasEdge(e1.key, a1.key), 'has edge from e1 to a1')
    assert.isFalse(restored.graph.hasEdge(a1.key, `${fd.key}:${fe1.key}`), 'has no edge from a1 to fe1')
  }).tags(['@modeling', '@serialization'])

  test('should serialize and deserialize parent-child relationships in the graph', ({ assert }) => {
    const domain = new DataDomain()

    // Create hierarchical structure: root > ns1 > ns2 > m1 > e1, e2 (e2 inherits from e1)
    const ns1 = domain.addNamespace({ key: 'ns1' })
    const ns2 = ns1.addNamespace({ key: 'ns2' })
    const m1 = ns2.addModel({ key: 'm1' })
    const m2 = domain.addModel({ key: 'm2' }) // root level model
    const e1 = m1.addEntity({ key: 'e1' })
    const e2 = m1.addEntity({ key: 'e2' })
    const e3 = m2.addEntity({ key: 'e3' })

    // Add entity inheritance relationships
    e2.addParent(e1.key) // e2 inherits from e1
    e3.addParent(e2.key) // e3 inherits from e2 (multi-level inheritance)

    // Add properties and associations
    const p1 = e1.addProperty({ type: 'string', key: 'p1' })
    const p2 = e2.addProperty({ type: 'number', key: 'p2' })
    const a1 = e1.addAssociation({ key: e3.key }, { key: 'a1' })

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    // Verify basic domain properties
    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())

    // Verify all nodes are restored
    assert.equal([...restored.graph.nodes()].length, 10) // 2 ns + 2 models + 3 entities + 2 properties + 1 association

    // Verify namespace hierarchy: ns1 (root) -> ns2 (child of ns1)
    const rns1 = restored.findNamespace(ns1.key)
    const rns2 = restored.findNamespace(ns2.key)
    assert.isDefined(rns1)
    assert.isDefined(rns2)
    assert.isUndefined(restored.graph.parent(ns1.key), 'ns1 should be at root level')
    assert.equal(restored.graph.parent(ns2.key), ns1.key, 'ns2 should be child of ns1')

    // Verify model hierarchy: m1 (child of ns2), m2 (root level)
    const rm1 = restored.findModel(m1.key)
    const rm2 = restored.findModel(m2.key)
    assert.isDefined(rm1)
    assert.isDefined(rm2)
    assert.equal(restored.graph.parent(m1.key), ns2.key, 'm1 should be child of ns2')
    assert.isUndefined(restored.graph.parent(m2.key), 'm2 should be at root level')

    // Verify entity hierarchy: e1, e2 (children of m1), e3 (child of m2)
    const re1 = restored.findEntity(e1.key)
    const re2 = restored.findEntity(e2.key)
    const re3 = restored.findEntity(e3.key)
    assert.isDefined(re1)
    assert.isDefined(re2)
    assert.isDefined(re3)
    assert.equal(restored.graph.parent(e1.key), m1.key, 'e1 should be child of m1')
    assert.equal(restored.graph.parent(e2.key), m1.key, 'e2 should be child of m1')
    assert.equal(restored.graph.parent(e3.key), m2.key, 'e3 should be child of m2')

    // Verify entity inheritance relationships (via edges, not parent-child)
    assert.isTrue(restored.graph.hasEdge(e2.key, e1.key), 'e2 should inherit from e1')
    assert.equal(restored.graph.edge(e2.key, e1.key)?.type, 'parent', 'inheritance edge should have type parent')
    assert.isTrue(restored.graph.hasEdge(e3.key, e2.key), 'e3 should inherit from e2')
    assert.equal(restored.graph.edge(e3.key, e2.key)?.type, 'parent', 'inheritance edge should have type parent')

    // Verify entity inheritance works functionally
    const restoredParentsE2 = [...re2!.listParents()]
    const restoredParentsE3 = [...re3!.listParents()]
    assert.equal(restoredParentsE2.length, 1, 'e2 should have 1 parent')
    assert.equal(restoredParentsE2[0].key, e1.key, 'e2 parent should be e1')
    assert.equal(restoredParentsE3.length, 1, 'e3 should have 1 parent')
    assert.equal(restoredParentsE3[0].key, e2.key, 'e3 parent should be e2')

    // Verify property hierarchy: p1 (child of e1), p2 (child of e2)
    const rp1 = restored.findProperty(p1.key)
    const rp2 = restored.findProperty(p2.key)
    assert.isDefined(rp1)
    assert.isDefined(rp2)
    assert.isTrue(restored.graph.hasEdge(e1.key, p1.key), 'e1 should have edge to p1')
    assert.equal(restored.graph.edge(e1.key, p1.key)?.type, 'property', 'property edge should have type property')
    assert.isTrue(restored.graph.hasEdge(e2.key, p2.key), 'e2 should have edge to p2')
    assert.equal(restored.graph.edge(e2.key, p2.key)?.type, 'property', 'property edge should have type property')

    // Verify association hierarchy: a1 (child of e1) -> e3
    const ra1 = restored.findAssociation(a1.key)
    assert.isDefined(ra1)
    assert.isTrue(restored.graph.hasEdge(e1.key, a1.key), 'e1 should have edge to a1')
    assert.equal(
      restored.graph.edge(e1.key, a1.key)?.type,
      'association',
      'association edge should have type association'
    )
    assert.isTrue(restored.graph.hasEdge(a1.key, e3.key), 'a1 should have edge to e3')
    assert.equal(
      restored.graph.edge(a1.key, e3.key)?.type,
      'association',
      'association target edge should have type association'
    )
  }).tags(['@modeling', '@serialization'])
})

test.group('Validation Tests', () => {
  test('should throw validation error when property has no parent entity edge', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty({ type: 'string' })

    // Manually break the graph by removing the property edge
    domain.graph.removeEdge(e1.key, p1.key)

    assert.throws(() => domain.toJSON())
  }).tags(['@modeling', '@serialization', '@validation'])

  test('should throw validation error when association has no parent entity edge', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const a1 = e1.addAssociation({ key: e2.key })

    // Manually break the graph by removing the association edge to parent
    domain.graph.removeEdge(e1.key, a1.key)

    assert.throws(() => domain.toJSON())
  }).tags(['@modeling', '@serialization', '@validation'])

  test('should throw validation error when property has multiple parent edges', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const p1 = e1.addProperty({ type: 'string' })

    // Manually add an extra edge to create multiple parents
    domain.graph.setEdge(e2.key, p1.key, { type: 'property' })

    assert.throws(() => domain.toJSON())
  }).tags(['@modeling', '@serialization', '@validation'])

  test('should throw validation error when property has non-entity parent', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty({ type: 'string' })

    // Remove the proper edge and add an edge from model to property (invalid)
    domain.graph.removeEdge(e1.key, p1.key)
    domain.graph.setEdge(m1.key, p1.key, { type: 'property' })

    assert.throws(() => domain.toJSON())
  }).tags(['@modeling', '@serialization', '@validation'])

  test('should throw validation error when entity has no model parent', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()

    // Remove the entity from its model parent
    domain.graph.setParent(e1.key, undefined)

    assert.throws(() => domain.toJSON())
  }).tags(['@modeling', '@serialization', '@validation'])

  test('should not throw validation error when association has no target entities', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const a1 = e1.addAssociation({ key: e2.key })

    // Remove the association target edge
    domain.graph.removeEdge(a1.key, e2.key)

    // Note, we specifically removed this from the validation as it would throw errors
    // when associations were created without targets. This is not a structural issue and because
    // of that, it shouldn't be considered a validation error in this context.
    // It is reported through the validator, though.
    assert.doesNotThrow(() => domain.toJSON())
  }).tags(['@modeling', '@serialization', '@validation'])

  test('should throw validation error when association references non-existent target', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const a1 = e1.addAssociation({ key: e2.key })

    // Remove the target entity but keep the edge pointing to it
    domain.graph.removeNode(e2.key)
    // Re-add the association with a broken target
    domain.graph.setEdge(a1.key, 'non-existent-entity', { type: 'association' })

    assert.throws(() => domain.toJSON())
  }).tags(['@modeling', '@serialization', '@validation'])

  test('should throw validation error when association references non-entity target', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const a1 = e1.addAssociation({ key: e2.key })

    // Remove the proper target and make it point to the model instead
    domain.graph.removeEdge(a1.key, e2.key)
    domain.graph.setEdge(a1.key, m1.key, { type: 'association' })

    assert.throws(() => domain.toJSON())
  }).tags(['@modeling', '@serialization', '@validation'])

  test('should pass validation for valid domain structure', ({ assert }) => {
    const domain = new DataDomain()

    // Create a valid structure with all elements
    const ns1 = domain.addNamespace()
    const m1 = ns1.addModel()
    const m2 = domain.addModel() // Root level model (valid)
    const e1 = m1.addEntity()
    const e2 = m2.addEntity()
    e1.addProperty({ type: 'string' })
    e1.addAssociation({ key: e2.key })

    // This should not throw any validation errors
    assert.doesNotThrow(() => {
      domain.toJSON()
    })
  }).tags(['@modeling', '@serialization', '@validation'])

  test('should pass validation for models at root level without namespaces', ({ assert }) => {
    const domain = new DataDomain()

    // Create models directly at domain level (should be valid)
    const m1 = domain.addModel()
    const m2 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m2.addEntity()
    e1.addProperty({ type: 'string' })
    e1.addAssociation({ key: e2.key })

    // This should not throw any validation errors
    assert.doesNotThrow(() => {
      domain.toJSON()
    })
  }).tags(['@modeling', '@serialization', '@validation'])

  test('should pass validation for foreign associations', ({ assert }) => {
    const fd = new DataDomain()
    const fm1 = fd.addModel()
    const fe1 = fm1.addEntity()
    fd.info.version = '1.0.0'

    const domain = new DataDomain()
    domain.registerForeignDomain(fd)
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    e1.addAssociation({ key: fe1.key, domain: fd.key })

    // Foreign associations should pass validation
    assert.doesNotThrow(() => {
      domain.toJSON()
    })
  }).tags(['@modeling', '@serialization', '@validation'])
})

test.group('DataDomain Lenient Mode Deserialization', () => {
  test('should handle malformed node entries in lenient mode', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    e1.addProperty({ type: 'string' })

    const serialized = domain.toJSON()

    // Corrupt a node entry by removing its kind
    const corruptedSerialized = structuredClone(serialized)
    if (corruptedSerialized.graph?.nodes?.[2]) {
      // Find the property node and corrupt it
      for (const node of corruptedSerialized.graph.nodes) {
        if (
          node.value &&
          typeof node.value === 'object' &&
          'kind' in node.value &&
          node.value.kind === DomainPropertyKind
        ) {
          delete node.value.kind
          break
        }
      }
    }

    // Should not throw in lenient mode
    const restored = new DataDomain(corruptedSerialized, [], 'lenient')

    // Should have issues recorded
    assert.isAbove(restored.issues.length, 0)
    const malformedIssues = restored.issues.filter((issue) => issue.type === 'malformed_entry')
    assert.isAbove(malformedIssues.length, 0)

    // Should have fewer nodes than original (corrupted node was skipped)
    assert.isBelow([...restored.graph.nodes()].length, [...domain.graph.nodes()].length)
  }).tags(['@modeling', '@serialization', '@lenient'])

  test('should handle missing parent edges in lenient mode', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty({ type: 'string' })

    const serialized = domain.toJSON()

    // Remove the edge from entity to property
    const corruptedSerialized = structuredClone(serialized)
    if (corruptedSerialized.graph?.edges) {
      corruptedSerialized.graph.edges = corruptedSerialized.graph.edges.filter(
        (edge) => !(edge.v === e1.key && edge.w === p1.key)
      )
    }

    // Should not throw in lenient mode
    const restored = new DataDomain(corruptedSerialized, [], 'lenient')

    // Should have issues recorded
    assert.isAbove(restored.issues.length, 0)
    const missingEdgeIssues = restored.issues.filter((issue) => issue.type === 'missing_edge')
    assert.isAbove(missingEdgeIssues.length, 0)

    // Property should be missing (was skipped due to missing parent edge)
    assert.isUndefined(restored.findProperty(p1.key))

    // But entity should still exist
    assert.isDefined(restored.findEntity(e1.key))
  }).tags(['@modeling', '@serialization', '@lenient'])

  test('should handle missing target nodes in lenient mode', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const a1 = e1.addAssociation({ key: e2.key })

    const serialized = domain.toJSON()

    // Remove the target entity node but keep the edge
    const corruptedSerialized = structuredClone(serialized)
    if (corruptedSerialized.graph?.nodes) {
      corruptedSerialized.graph.nodes = corruptedSerialized.graph.nodes.filter((node) => node.v !== e2.key)
    }

    // Should not throw in lenient mode
    const restored = new DataDomain(corruptedSerialized, [], 'lenient')

    // Should have issues recorded
    assert.isAbove(restored.issues.length, 0)
    const missingNodeIssues = restored.issues.filter((issue) => issue.type === 'missing_node')
    assert.isAbove(missingNodeIssues.length, 0)

    // Source entity and association should exist
    assert.isDefined(restored.findEntity(e1.key))
    assert.isDefined(restored.findAssociation(a1.key))

    // Target entity should be missing
    assert.isUndefined(restored.findEntity(e2.key))

    // Association should have no targets
    const restoredAssociation = restored.findAssociation(a1.key)
    assert.equal([...restoredAssociation!.listTargets()].length, 0)
  }).tags(['@modeling', '@serialization', '@lenient'])

  test('should handle invalid parent relationships in lenient mode', ({ assert }) => {
    const domain = new DataDomain()
    const ns1 = domain.addNamespace()
    const m1 = ns1.addModel()
    const e1 = m1.addEntity()

    const serialized = domain.toJSON()

    // Corrupt parent relationship - make entity point to non-existent parent
    const corruptedSerialized = structuredClone(serialized)
    if (corruptedSerialized.graph?.nodes) {
      for (const node of corruptedSerialized.graph.nodes) {
        if (node.v === e1.key && node.parents) {
          node.parents = ['non-existent-parent'] // Invalid - non-existent parent
          break
        }
      }
    }

    // Should not throw in lenient mode
    const restored = new DataDomain(corruptedSerialized, [], 'lenient')

    // Should have issues recorded about missing parent node
    assert.isAbove(restored.issues.length, 0)
    const missingNodeIssues = restored.issues.filter((issue) => issue.type === 'missing_node')
    assert.isAbove(missingNodeIssues.length, 0)

    // Entity should still exist but may not have proper parent relationship
    assert.isDefined(restored.findEntity(e1.key))
  }).tags(['@modeling', '@serialization', '@lenient'])

  test('should handle missing foreign dependencies gracefully in lenient mode', ({ assert }) => {
    const fd = new DataDomain()
    const fm1 = fd.addModel()
    const fe1 = fm1.addEntity()
    fd.info.version = '1.0.0'

    const domain = new DataDomain()
    domain.registerForeignDomain(fd)
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    e1.addAssociation({ key: fe1.key, domain: fd.key })

    const serialized = domain.toJSON()

    // Deserialize without providing the foreign dependency
    const restored = new DataDomain(serialized, [], 'lenient')

    // Should have issues recorded about missing dependency
    assert.isAbove(restored.issues.length, 0)
    const missingNodeIssues = restored.issues.filter(
      (issue) => issue.type === 'missing_node' && issue.message.includes('foreign')
    )
    assert.isAbove(missingNodeIssues.length, 0)

    // Local entities should still exist
    assert.isDefined(restored.findEntity(e1.key))

    // Foreign entity should not exist
    assert.isUndefined(restored.findForeignEntity(fe1.key, fd.key))
  }).tags(['@modeling', '@serialization', '@lenient'])

  test('should handle unknown node kinds in lenient mode', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()

    const serialized = domain.toJSON()

    // Add a node with unknown kind
    const corruptedSerialized = structuredClone(serialized)
    if (corruptedSerialized.graph?.nodes) {
      corruptedSerialized.graph.nodes.push({
        v: 'unknown-node',
        value: {
          kind: 'UnknownKind',
          key: 'unknown-node',
          info: { name: 'Unknown Node' },
        },
      })
    }

    // Should not throw in lenient mode
    const restored = new DataDomain(corruptedSerialized, [], 'lenient')

    // Should have issues recorded
    assert.isAbove(restored.issues.length, 0)
    const unknownKindIssues = restored.issues.filter((issue) => issue.type === 'unknown_kind')
    assert.isAbove(unknownKindIssues.length, 0)

    // Original model should still exist
    assert.isDefined(restored.findModel(m1.key))

    // Unknown node should not exist in graph
    assert.isFalse(restored.graph.hasNode('unknown-node'))
  }).tags(['@modeling', '@serialization', '@lenient'])

  test('should collect multiple issues and continue processing in lenient mode', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const p1 = e1.addProperty({ type: 'string' })
    e1.addAssociation({ key: e2.key })

    const serialized = domain.toJSON()

    // Introduce multiple types of corruption
    const corruptedSerialized = structuredClone(serialized)
    if (corruptedSerialized.graph?.nodes && corruptedSerialized.graph?.edges) {
      // 1. Remove kind from property node
      for (const node of corruptedSerialized.graph.nodes) {
        if (
          node.value &&
          typeof node.value === 'object' &&
          'kind' in node.value &&
          node.value.kind === DomainPropertyKind
        ) {
          delete node.value.kind
          break
        }
      }

      // 2. Remove target entity
      corruptedSerialized.graph.nodes = corruptedSerialized.graph.nodes.filter((node) => node.v !== e2.key)

      // 3. Add unknown kind node
      corruptedSerialized.graph.nodes.push({
        v: 'unknown-node',
        value: {
          kind: 'UnknownKind',
          key: 'unknown-node',
          info: { name: 'Unknown Node' },
        },
      })
    }

    // Should not throw in lenient mode
    const restored = new DataDomain(corruptedSerialized, [], 'lenient')

    // Should have multiple issues recorded
    assert.isAtLeast(restored.issues.length, 3)

    // Should have different types of issues
    const issueTypes = new Set(restored.issues.map((issue) => issue.type))
    assert.isTrue(issueTypes.has('malformed_entry') || issueTypes.has('unknown_kind'))
    assert.isTrue(issueTypes.has('missing_node'))

    // Should still have functional entities where possible
    assert.isDefined(restored.findEntity(e1.key))
    assert.isDefined(restored.findModel(m1.key))

    // Corrupted elements should be missing
    assert.isUndefined(restored.findProperty(p1.key)) // Property was corrupted
    assert.isUndefined(restored.findEntity(e2.key)) // Entity was removed
  }).tags(['@modeling', '@serialization', '@lenient'])

  test('should have empty issues array in strict mode on successful deserialization', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    e1.addProperty({ type: 'string' })

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized, [], 'strict')

    // Should have no issues in strict mode for valid data
    assert.equal(restored.issues.length, 0)
  }).tags(['@modeling', '@serialization', '@strict'])

  test('should throw in strict mode but collect issues in lenient mode for same corrupted data', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    e1.addProperty({ type: 'string' })

    const serialized = domain.toJSON()

    // Corrupt the data
    const corruptedSerialized = structuredClone(serialized)
    if (corruptedSerialized.graph?.nodes?.[2]) {
      for (const node of corruptedSerialized.graph.nodes) {
        if (
          node.value &&
          typeof node.value === 'object' &&
          'kind' in node.value &&
          node.value.kind === DomainPropertyKind
        ) {
          delete node.value.kind
          break
        }
      }
    }

    // Should throw in strict mode
    assert.throws(() => {
      new DataDomain(corruptedSerialized, [], 'strict')
    })

    // Should not throw in lenient mode
    const restored = new DataDomain(corruptedSerialized, [], 'lenient')
    assert.isAbove(restored.issues.length, 0)
  }).tags(['@modeling', '@serialization', '@lenient', '@strict'])

  test('should provide helpful issue messages and context in lenient mode', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty({ type: 'string', info: { name: 'testProperty' } })

    const serialized = domain.toJSON()

    // Remove the edge from entity to property
    const corruptedSerialized = structuredClone(serialized)
    if (corruptedSerialized.graph?.edges) {
      corruptedSerialized.graph.edges = corruptedSerialized.graph.edges.filter(
        (edge) => !(edge.v === e1.key && edge.w === p1.key)
      )
    }

    const restored = new DataDomain(corruptedSerialized, [], 'lenient')

    // Should have detailed issue information
    assert.isAbove(restored.issues.length, 0)
    const missingEdgeIssue = restored.issues.find((issue) => issue.type === 'missing_edge')
    assert.isDefined(missingEdgeIssue)

    // Should have helpful message
    assert.include(missingEdgeIssue!.message, 'testProperty')
    assert.include(missingEdgeIssue!.message.toLowerCase(), 'parent entity edge')

    // Should have affected key
    assert.equal(missingEdgeIssue!.affectedKey, p1.key)

    // Should have resolution info
    assert.include(missingEdgeIssue!.resolution!, 'skipped')

    // Should have appropriate severity
    assert.equal(missingEdgeIssue!.severity, 'error')
  }).tags(['@modeling', '@serialization', '@lenient'])
})
