/* eslint-disable max-len */
import { test } from '@japa/runner'
// @ts-expect-error - No types for this package
import schemas from 'schema-org-json-schemas'
import { type InMemorySchema, JsonSchemaImporter } from '../../../../src/modeling/importers/JsonSchemaImporter.js'
import { DataDomain } from '../../../../src/modeling/DataDomain.js'
import type { JSONSchema7 } from 'json-schema'
import type { DomainEntity } from '../../../../src/modeling/DomainEntity.js'
import { DomainAssociation, DomainProperty } from '../../../../src/browser.js'
import Person from '../../../fixtures/schemas/person.json' with { type: 'json' }
import Arrays from '../../../fixtures/schemas/arrays.json' with { type: 'json' }
import EnumValues from '../../../fixtures/schemas/enumerated_values.json' with { type: 'json' }
import RegexpSchema from '../../../fixtures/schemas/regexp.json' with { type: 'json' }
import ComplexSchema from '../../../fixtures/schemas/complex.json' with { type: 'json' }

const input: InMemorySchema[] = []
for (const [key, value] of Object.entries(schemas)) {
  input.push({
    path: key,
    contents: value as JSONSchema7,
  })
}

function findEntityByName(domain: DataDomain, name: string): DomainEntity {
  for (const entity of domain.listEntities()) {
    if (entity.info.name === name || entity.info.displayName === name) {
      return entity
    }
  }
  throw new Error(`Entity with name "${name}" not found`)
}

function findPropertyByName(entity: DomainEntity, name: string): DomainProperty {
  for (const prop of entity.listProperties()) {
    if (prop.info.name === name || prop.info.displayName === name) {
      return prop
    }
  }
  throw new Error(`Property with name "${name}" not found`)
}

function findAssociationByName(entity: DomainEntity, name: string): DomainAssociation {
  for (const assoc of entity.listAssociations()) {
    if (assoc.info.name === name || assoc.info.displayName === name) {
      return assoc
    }
  }
  throw new Error(`Association with name "${name}" not found`)
}

async function importSchemas(input: InMemorySchema[]): Promise<DataDomain> {
  const domain = new DataDomain({ info: { name: 'Test Domain' } })
  const importer = new JsonSchemaImporter(domain)
  await importer.import(input, 'imported_model')
  return domain
}

test.group('JsonSchemaImporter', (g) => {
  let domain: DataDomain

  g.setup(async () => {
    domain = new DataDomain({ info: { name: 'Test Domain' } })
    const importer = new JsonSchemaImporter(domain)
    await importer.import(input, 'imported_model')
    // console.log('Import report:', result.messages)
  })

  // We won't test for all schemas, just a few to ensure the importer works

  test('Book schema is imported', ({ assert }) => {
    const bookEntity = findEntityByName(domain, 'Book')
    assert.exists(bookEntity, 'Book entity should exist')
    assert.equal(bookEntity.info.name, 'book')
    assert.equal(bookEntity.info.displayName, 'Book')
    assert.equal(bookEntity.info.description, 'A book.')
    const abridged = findPropertyByName(bookEntity, 'abridged')
    assert.exists(abridged, 'Book entity should have abridged property')
    assert.isUndefined(abridged.info.displayName, 'displayName should be undefined.')
    assert.equal(abridged.info.description, 'Indicates whether the book is an abridged edition.')
    assert.equal(abridged.type, 'boolean')
    assert.isFalse(abridged.required, 'Abridged property should not be required')
    assert.isFalse(abridged.multiple, 'Abridged property should not allow multiple values')
    const bookEdition = findPropertyByName(bookEntity, 'bookEdition')
    assert.exists(bookEdition, 'Book entity should have bookEdition property')
    assert.equal(bookEdition.info.displayName, 'bookEdition')
    assert.equal(bookEdition.info.description, 'The edition of the book.')
    assert.equal(bookEdition.type, 'string')
    assert.isFalse(bookEdition.required, 'Book edition property should not be required')
    assert.isFalse(bookEdition.multiple, 'Book edition property should not allow multiple values')
    const bookFormat = findPropertyByName(bookEntity, 'bookFormat')
    assert.exists(bookFormat, 'Book entity should have bookFormat property')
    assert.equal(bookFormat.info.name, 'book_format')
    assert.equal(bookFormat.info.displayName, 'bookFormat')
    assert.equal(bookFormat.info.description, 'The format of the book.')
    assert.isFalse(bookFormat.required, 'Book format property should not be required')
    assert.isFalse(bookFormat.multiple, 'Book format property should not allow multiple values')
    const illustrator = findAssociationByName(bookEntity, 'illustrator')
    assert.exists(illustrator, 'Book entity should have illustrator association')
    assert.equal(illustrator.info.name, 'illustrator')
    assert.isUndefined(illustrator.info.displayName, 'displayName should be undefined.')
    assert.equal(illustrator.info.description, 'The illustrator of the book.')
    assert.isFalse(illustrator.required, 'Illustrator association should not be required')
    assert.isFalse(illustrator.multiple, 'Illustrator association should not allow multiple values')
    assert.isNotEmpty(illustrator.targets, 'Illustrator association should have targets')
    assert.lengthOf([...illustrator.listTargets()], 1, 'Illustrator association should have exactly one target')
    const isbn = findPropertyByName(bookEntity, 'isbn')
    assert.exists(isbn, 'Book entity should have isbn property')
    assert.equal(isbn.info.name, 'isbn')
    assert.isUndefined(isbn.info.displayName, 'The ISBN display name is undefined.')
    assert.equal(isbn.info.description, 'The ISBN of the book.')
    assert.equal(isbn.type, 'string')
    assert.isFalse(isbn.required, 'ISBN property should not be required')
    assert.isFalse(isbn.multiple, 'ISBN property should not allow multiple values')
    const numberOfPages = findPropertyByName(bookEntity, 'numberOfPages')
    assert.exists(numberOfPages, 'Book entity should have numberOfPages property')
    assert.equal(numberOfPages.info.name, 'number_of_pages')
    assert.equal(numberOfPages.info.displayName, 'numberOfPages')
    assert.equal(numberOfPages.info.description, 'The number of pages in the book.')
    assert.equal(numberOfPages.type, 'number')
    assert.isFalse(numberOfPages.required, 'Number of pages property should not be required')
    assert.isFalse(numberOfPages.multiple, 'Number of pages property should not allow multiple values')
    const numberOfPagesWebBindings = numberOfPages.getWebBinding()
    assert.exists(numberOfPagesWebBindings, 'Number of pages should have web bindings')
    assert.equal(numberOfPagesWebBindings?.format, 'int64', 'Web binding format should be int64')
    const parents = [...bookEntity.parents]
    assert.lengthOf(parents, 1, 'Book entity should have one parent')
    assert.equal(parents[0].info.name, 'creative_work', 'Parent entity should be CreativeWork')
  })

  test('Canal schema is imported and inherits from BodyOfWater', ({ assert }) => {
    const canalEntity = findEntityByName(domain, 'Canal')
    assert.exists(canalEntity, 'Canal entity should exist')
    assert.equal(canalEntity.info.name, 'canal')
    assert.equal(canalEntity.info.displayName, 'Canal')
    assert.equal(canalEntity.info.description, 'A canal, like the Panama Canal.')
    // Should have one parent: BodyOfWater
    const parents = [...canalEntity.parents]
    assert.lengthOf(parents, 1, 'Canal entity should have one parent')
    assert.equal(parents[0].info.name, 'body_of_water', 'Parent entity should be BodyOfWater')
    assert.equal(parents[0].info.displayName, 'BodyOfWater')
    assert.equal(parents[0].info.description, 'A body of water, such as a sea, ocean, or lake.')
    // Canal should not have properties or associations of its own (unless inherited)
    assert.lengthOf([...canalEntity.listProperties()], 0, 'Canal should not have direct properties')
    assert.lengthOf([...canalEntity.listAssociations()], 0, 'Canal should not have direct associations')
  })

  test('DataFeedItem schema is imported and properties are mapped correctly', ({ assert }) => {
    const dataFeedItem = findEntityByName(domain, 'DataFeedItem')
    assert.exists(dataFeedItem, 'DataFeedItem entity should exist')
    assert.equal(dataFeedItem.info.name, 'data_feed_item')
    assert.equal(dataFeedItem.info.displayName, 'DataFeedItem')
    assert.equal(dataFeedItem.info.description, 'A single item within a larger data feed.')
    // Should have one parent: Intangible
    const parents = [...dataFeedItem.parents]
    assert.lengthOf(parents, 1, 'DataFeedItem should have one parent')
    assert.equal(parents[0].info.name, 'intangible', 'Parent entity should be Intangible')

    // dateCreated property
    const dateCreated = findPropertyByName(dataFeedItem, 'dateCreated')
    assert.exists(dateCreated, 'dateCreated property should exist')
    assert.equal(dateCreated.info.name, 'date_created')
    assert.equal(dateCreated.info.displayName, 'dateCreated')
    assert.equal(
      dateCreated.info.description,
      'The date on which the CreativeWork was created or the item was added to a DataFeed.'
    )
    assert.equal(dateCreated.type, 'datetime')
    assert.isFalse(dateCreated.required, 'dateCreated should not be required')
    assert.isFalse(dateCreated.multiple, 'dateCreated should not allow multiple values')

    // dateDeleted property
    const dateDeleted = findPropertyByName(dataFeedItem, 'dateDeleted')
    assert.exists(dateDeleted, 'dateDeleted property should exist')
    assert.equal(dateDeleted.info.name, 'date_deleted')
    assert.equal(dateDeleted.info.displayName, 'dateDeleted')
    assert.equal(dateDeleted.info.description, 'The datetime the item was removed from the DataFeed.')
    assert.equal(dateDeleted.type, 'datetime')
    assert.isFalse(dateDeleted.required, 'dateDeleted should not be required')
    assert.isFalse(dateDeleted.multiple, 'dateDeleted should not allow multiple values')

    // dateModified property
    const dateModified = findPropertyByName(dataFeedItem, 'dateModified')
    assert.exists(dateModified, 'dateModified property should exist')
    assert.equal(dateModified.info.name, 'date_modified')
    assert.equal(dateModified.info.displayName, 'dateModified')
    assert.equal(
      dateModified.info.description,
      "The date on which the CreativeWork was most recently modified or when the item's entry was modified within a DataFeed."
    )
    assert.equal(dateModified.type, 'datetime')
    assert.isFalse(dateModified.required, 'dateModified should not be required')
    assert.isFalse(dateModified.multiple, 'dateModified should not allow multiple values')

    // item association
    const itemAssoc = findAssociationByName(dataFeedItem, 'item')
    assert.exists(itemAssoc, 'item association should exist')
    assert.equal(itemAssoc.info.name, 'item')
    assert.isUndefined(itemAssoc.info.displayName, 'the item association display name should be undefined.')
    assert.equal(
      itemAssoc.info.description,
      "An entity represented by an entry in a list or data feed (e.g. an 'artist' in a list of 'artists')’."
    )
    assert.isFalse(itemAssoc.required, 'item association should not be required')
    assert.isTrue(itemAssoc.multiple, 'item association should allow multiple values (array or single)')
    assert.isNotEmpty(itemAssoc.targets, 'item association should have targets')
    assert.lengthOf([...itemAssoc.listTargets()], 1, 'item association should have exactly one target')
    // Target should be Thing
    const target = [...itemAssoc.listTargets()][0]
    assert.equal(target.info.name, 'thing', 'item association target should be Thing')
  })

  test('Drug schema is imported and properties are mapped correctly', ({ assert }) => {
    const drugEntity = findEntityByName(domain, 'Drug')
    assert.exists(drugEntity, 'Drug entity should exist')
    assert.equal(drugEntity.info.name, 'drug')
    assert.equal(drugEntity.info.displayName, 'Drug')
    assert.equal(
      drugEntity.info.description,
      'A chemical or biologic substance, used as a medical therapy, that has a physiological effect on an organism. Here the term drug is used interchangeably with the term medicine although clinical knowledge make a clear difference between them.'
    )
    // Should have one parent: Substance
    const parents = [...drugEntity.parents]
    assert.lengthOf(parents, 1, 'Drug should have one parent')
    assert.equal(parents[0].info.name, 'substance', 'Parent entity should be Substance')

    // activeIngredient property
    const activeIngredient = findPropertyByName(drugEntity, 'activeIngredient')
    assert.exists(activeIngredient, 'activeIngredient property should exist')
    assert.equal(activeIngredient.info.name, 'active_ingredient')
    assert.equal(activeIngredient.info.displayName, 'activeIngredient')
    assert.equal(
      activeIngredient.info.description,
      'An active ingredient, typically chemical compounds and/or biologic substances.'
    )
    assert.equal(activeIngredient.type, 'string')
    assert.isFalse(activeIngredient.required, 'activeIngredient should not be required')
    assert.isTrue(activeIngredient.multiple, 'activeIngredient should allow multiple values')

    // administrationRoute property
    const administrationRoute = findPropertyByName(drugEntity, 'administrationRoute')
    assert.exists(administrationRoute, 'administrationRoute property should exist')
    assert.equal(administrationRoute.info.name, 'administration_route')
    assert.equal(administrationRoute.info.displayName, 'administrationRoute')
    assert.equal(administrationRoute.info.description, "A route by which this drug may be administered, e.g. 'oral'.")
    assert.equal(administrationRoute.type, 'string')
    assert.isFalse(administrationRoute.required, 'administrationRoute should not be required')
    assert.isTrue(administrationRoute.multiple, 'administrationRoute should allow multiple values')

    // alcoholWarning property
    const alcoholWarning = findPropertyByName(drugEntity, 'alcoholWarning')
    assert.exists(alcoholWarning, 'alcoholWarning property should exist')
    assert.equal(alcoholWarning.info.name, 'alcohol_warning')
    assert.equal(alcoholWarning.info.displayName, 'alcoholWarning')
    assert.equal(
      alcoholWarning.info.description,
      'Any precaution, guidance, contraindication, etc. related to consumption of alcohol while taking this drug.'
    )
    assert.equal(alcoholWarning.type, 'string')
    assert.isFalse(alcoholWarning.required, 'alcoholWarning should not be required')
    assert.isFalse(alcoholWarning.multiple, 'alcoholWarning should not allow multiple values')

    // availableStrength association
    const availableStrength = findAssociationByName(drugEntity, 'availableStrength')
    assert.exists(availableStrength, 'availableStrength association should exist')
    assert.equal(availableStrength.info.name, 'available_strength')
    assert.equal(availableStrength.info.displayName, 'availableStrength')
    assert.equal(availableStrength.info.description, 'An available dosage strength for the drug.')
    assert.isFalse(availableStrength.required, 'availableStrength should not be required')
    assert.isTrue(availableStrength.multiple, 'availableStrength should allow multiple values')
    assert.isNotEmpty(availableStrength.targets, 'availableStrength should have targets')

    // isAvailableGenerically property
    const isAvailableGenerically = findPropertyByName(drugEntity, 'isAvailableGenerically')
    assert.exists(isAvailableGenerically, 'isAvailableGenerically property should exist')
    assert.equal(isAvailableGenerically.info.name, 'is_available_generically')
    assert.equal(isAvailableGenerically.info.displayName, 'isAvailableGenerically')
    assert.equal(
      isAvailableGenerically.info.description,
      'True if the drug is available in a generic form (regardless of name).'
    )
    assert.equal(isAvailableGenerically.type, 'boolean')
    assert.isFalse(isAvailableGenerically.required, 'isAvailableGenerically should not be required')
    assert.isFalse(isAvailableGenerically.multiple, 'isAvailableGenerically should not allow multiple values')

    // manufacturer association
    const manufacturer = findAssociationByName(drugEntity, 'manufacturer')
    assert.exists(manufacturer, 'manufacturer association should exist')
    assert.equal(manufacturer.info.name, 'manufacturer')
    assert.isUndefined(manufacturer.info.displayName, 'the manufacturer association display name should be undefined.')
    assert.equal(manufacturer.info.description, 'The manufacturer of the product.')
    assert.isFalse(manufacturer.required, 'manufacturer should not be required')
    assert.isFalse(manufacturer.multiple, 'manufacturer should not allow multiple values')
    assert.isNotEmpty(manufacturer.targets, 'manufacturer should have targets')

    // warning property
    const warning = findPropertyByName(drugEntity, 'warning')
    assert.exists(warning, 'warning property should exist')
    assert.equal(warning.info.name, 'warning')
    assert.isUndefined(warning.info.displayName, 'the warning property display name should be undefined.')
    assert.equal(warning.info.description, 'Any FDA or other warnings about the drug (text or URL).')
    assert.equal(warning.type, 'string')
    assert.isFalse(warning.required, 'warning should not be required')
    assert.isFalse(warning.multiple, 'warning should not allow multiple values')
  })

  test('Enumeration schema with recursive reference is imported', ({ assert }) => {
    const enumerationEntity = findEntityByName(domain, 'Enumeration')
    assert.exists(enumerationEntity, 'Enumeration entity should exist')
    assert.equal(enumerationEntity.info.name, 'enumeration')
    assert.equal(enumerationEntity.info.displayName, 'Enumeration')
    assert.equal(
      enumerationEntity.info.description,
      'Lists or enumerations—for example, a list of cuisines or music genres, etc.'
    )

    // Parent should be Intangible
    const parents = [...enumerationEntity.parents]
    assert.lengthOf(parents, 1, 'Enumeration entity should have one parent')
    assert.equal(parents[0].info.name, 'intangible', 'Parent entity should be Intangible')

    // supersededBy association
    const supersededBy = findAssociationByName(enumerationEntity, 'supersededBy')
    assert.exists(supersededBy, 'supersededBy association should exist')
    assert.equal(supersededBy.info.name, 'superseded_by')
    assert.equal(supersededBy.info.displayName, 'supersededBy')
    assert.equal(
      supersededBy.info.description,
      'Relates a term (i.e. a property, class or enumeration) to one that supersedes it.'
    )
    assert.isFalse(supersededBy.required, 'supersededBy should not be required')
    assert.isTrue(supersededBy.multiple, 'supersededBy should allow multiple values')

    const targets = [...supersededBy.listTargets()].map((t) => t.info.displayName).sort()
    assert.deepEqual(
      targets,
      ['Class', 'Enumeration', 'Property'],
      'Targets should be Class, Enumeration, and Property'
    )
  })

  test('Event schema with complex properties is imported', ({ assert }) => {
    const eventEntity = findEntityByName(domain, 'Event')
    assert.exists(eventEntity, 'Event entity should exist')
    assert.equal(eventEntity.info.name, 'event')
    assert.equal(eventEntity.info.displayName, 'Event')

    // Parent should be Thing
    const parents = [...eventEntity.parents]
    assert.lengthOf(parents, 1, 'Event entity should have one parent')
    assert.equal(parents[0].info.name, 'thing', 'Parent entity should be Thing')

    // Test a simple boolean property
    const isAccessibleForFree = findPropertyByName(eventEntity, 'isAccessibleForFree')
    assert.exists(isAccessibleForFree)
    assert.equal(isAccessibleForFree.type, 'boolean')
    assert.isFalse(isAccessibleForFree.multiple)

    // Test a property with a union of string with time formats
    const doorTime = findPropertyByName(eventEntity, 'doorTime')
    assert.exists(doorTime)
    assert.equal(doorTime.type, 'datetime')
    assert.equal(doorTime.info.description, 'The time admission will commence.')

    const endDate = findPropertyByName(eventEntity, 'endDate')
    assert.exists(endDate)
    assert.equal(endDate.type, 'datetime')

    // Test a simple association
    const about = findAssociationByName(eventEntity, 'about')
    assert.exists(about)
    assert.isFalse(about.multiple)
    const aboutTargets = [...about.listTargets()]
    assert.lengthOf(aboutTargets, 1)
    assert.equal(aboutTargets[0].info.displayName, 'Thing')

    // Test an association that can be single or an array
    const actor = findAssociationByName(eventEntity, 'actor')
    assert.exists(actor)
    assert.isTrue(actor.multiple, 'actor should allow multiple values')
    const actorTargets = [...actor.listTargets()]
    assert.lengthOf(actorTargets, 1)
    assert.equal(actorTargets[0].info.displayName, 'Person')

    // Test an association that can be one of multiple types (and also an array)
    const attendee = findAssociationByName(eventEntity, 'attendee')
    assert.exists(attendee)
    assert.isTrue(attendee.multiple, 'attendee should allow multiple values')
    const attendeeTargets = [...attendee.listTargets()].map((t) => t.info.displayName).sort()
    assert.deepEqual(attendeeTargets, ['Organization', 'Person'])

    // Test a recursive association
    const subEvent = findAssociationByName(eventEntity, 'subEvent')
    assert.exists(subEvent)
    assert.isTrue(subEvent.multiple, 'subEvent should allow multiple values')
    const subEventTargets = [...subEvent.listTargets()]
    assert.lengthOf(subEventTargets, 1)
    assert.equal(subEventTargets[0].info.displayName, 'Event', 'Target should be self (Event)')
  })

  test('imports MusicAlbum enum properties correctly', ({ assert }) => {
    const musicAlbumEntity = findEntityByName(domain, 'MusicAlbum')
    const albumProductionType = findPropertyByName(musicAlbumEntity, 'albumProductionType')
    assert.isTrue(albumProductionType.multiple, 'albumProductionType can be an array')
    assert.deepEqual(albumProductionType.schema?.enum?.sort(), [
      'CompilationAlbum',
      'DJMixAlbum',
      'DemoAlbum',
      'LiveAlbum',
      'MixtapeAlbum',
      'RemixAlbum',
      'SoundtrackAlbum',
      'SpokenWordAlbum',
      'StudioAlbum',
    ])

    const albumReleaseType = findPropertyByName(musicAlbumEntity, 'albumReleaseType')
    assert.isFalse(!!albumReleaseType.multiple, 'albumReleaseType is a single value')
    assert.deepEqual(albumReleaseType.schema?.enum?.sort(), [
      'AlbumRelease',
      'BroadcastRelease',
      'EPRelease',
      'SingleRelease',
    ])
  })

  test('imports Brewery correctly', ({ assert }) => {
    const breweryEntity = findEntityByName(domain, 'Brewery')
    assert.lengthOf([...breweryEntity.properties], 0, 'Brewery should not have direct properties')
    assert.lengthOf([...breweryEntity.associations], 0, 'Brewery should not have direct associations')
    assert.equal(breweryEntity.info.name, 'brewery')
    assert.equal(breweryEntity.info.displayName, 'Brewery')
    assert.lengthOf([...breweryEntity.parents], 1, 'Brewery should have one parent')
    assert.equal(
      [...breweryEntity.parents][0].info.name,
      'food_establishment',
      'Parent entity should be FoodEstablishment'
    )
  })

  test('imports ComedyEvent correctly', ({ assert }) => {
    const comedyEventEntity = findEntityByName(domain, 'ComedyEvent')
    assert.lengthOf([...comedyEventEntity.properties], 0, 'ComedyEvent should not have direct properties')
    assert.lengthOf([...comedyEventEntity.associations], 0, 'ComedyEvent should not have direct associations')
    assert.equal(comedyEventEntity.info.name, 'comedy_event')
    assert.equal(comedyEventEntity.info.displayName, 'ComedyEvent')
    assert.lengthOf([...comedyEventEntity.parents], 1, 'ComedyEvent should have one parent')
    assert.equal([...comedyEventEntity.parents][0].info.name, 'event', 'Parent entity should be Event')
  })

  test('imports ComicIssue correctly', ({ assert }) => {
    const comicIssueEntity = findEntityByName(domain, 'ComicIssue')
    assert.lengthOf([...comicIssueEntity.properties], 1, 'ComicIssue should have one direct property')
    assert.lengthOf([...comicIssueEntity.associations], 5, 'ComicIssue should have five direct associations')
    assert.equal(comicIssueEntity.info.name, 'comic_issue')
    assert.equal(comicIssueEntity.info.displayName, 'ComicIssue')
    assert.lengthOf([...comicIssueEntity.parents], 1, 'ComicIssue should have one parent')
    assert.equal(
      [...comicIssueEntity.parents][0].info.name,
      'publication_issue',
      'Parent entity should be PublicationIssue'
    )
  })
})

test.group('small batch of schemas', () => {
  test('imports a single schema', async ({ assert }) => {
    const domain = await importSchemas(input.slice(0, 1))
    const models = [...domain.listModels()]
    assert.lengthOf(models, 1, 'Should import one model')
    const entities = [...domain.listEntities()]
    assert.lengthOf(entities, 1, 'Should import one entity')
    assert.equal(entities[0].info.name, '3_d_model', 'Entity should be 3_d_model')
  })

  test('imports two schemas only', async ({ assert }) => {
    const domain = await importSchemas(input.slice(0, 2))
    const models = [...domain.listModels()]
    assert.lengthOf(models, 1, 'Should import one model')
    const entities = [...domain.listEntities()]
    assert.lengthOf(entities, 2, 'Should import two entities')
    assert.equal(entities[0].info.name, '3_d_model', 'Entity should be 3_d_model')
    assert.equal(entities[1].info.name, 'am_radio_channel', 'Entity should be am_radio_channel')
  })
})

//
// From the JSON Schema website.
//
test.group('JSON Schema Examples', () => {
  test('imports Person schema correctly', async ({ assert }) => {
    const domain = await importSchemas([{ path: 'person.json', contents: Person as JSONSchema7 }])
    const models = [...domain.listModels()]
    assert.lengthOf(models, 1, 'Should import one model')
    const entities = [...domain.listEntities()]
    assert.lengthOf(entities, 1, 'Should import one entity')
    const [person] = entities
    const properties = [...person.listProperties()]
    assert.lengthOf(properties, 3, 'Person should have three properties')
    assert.equal(properties[0].info.name, 'first_name', 'First property should be first_name')
    assert.equal(properties[1].info.name, 'last_name', 'Second property should be last_name')
    assert.equal(properties[2].info.name, 'age', 'Third property should be age')
    assert.equal(properties[0].type, 'string', 'First property should be string')
    assert.equal(properties[1].type, 'string', 'Second property should be string')
    assert.equal(properties[2].type, 'number', 'Third property should be number')
    assert.strictEqual(properties[2].schema?.minimum, 0, 'Third property should have minimum 0')
  })

  test('imports Arrays schema correctly', async ({ assert }) => {
    const domain = await importSchemas([{ path: 'arrays.json', contents: Arrays as JSONSchema7 }])
    const models = [...domain.listModels()]
    assert.lengthOf(models, 1, 'Should import one model')
    const entities = [...domain.listEntities()]
    assert.lengthOf(entities, 2, 'Should import two entities')
    const arraysEntity = findEntityByName(domain, 'arrays')
    const veggieEntity = findEntityByName(domain, 'veggie')
    assert.exists(arraysEntity, 'arrays entity should exist')
    assert.exists(veggieEntity, 'veggie entity should exist')
    const arraysProperties = [...arraysEntity.listProperties()]
    const arraysAssociations = [...arraysEntity.listAssociations()]
    assert.lengthOf(arraysProperties, 1, 'Arrays entity should have one property')
    assert.lengthOf(arraysAssociations, 1, 'Arrays entity should have one association')
    const veggieProperties = [...veggieEntity.listProperties()]
    const veggieAssociations = [...veggieEntity.listAssociations()]
    assert.lengthOf(veggieProperties, 2, 'Veggie entity should have two properties')
    assert.lengthOf(veggieAssociations, 0, 'Veggie entity should have no associations')
    const [vp1, vp2] = veggieProperties
    assert.equal(vp1.info.name, 'veggie_name', 'First veggie property should be veggie_name')
    assert.equal(vp2.info.name, 'veggie_like', 'Second veggie property should be veggie_like')
    assert.isTrue(vp1.required, 'veggie_name should be required')
    assert.isTrue(vp2.required, 'veggie_like should not be required')
  })

  test('imports EnumValues schema correctly', async ({ assert }) => {
    const domain = await importSchemas([{ path: 'enumerated_values.json', contents: EnumValues as JSONSchema7 }])
    const models = [...domain.listModels()]
    assert.lengthOf(models, 1, 'Should import one model')
    const entities = [...domain.listEntities()]
    assert.lengthOf(entities, 1, 'Should import one entity')
    const enumEntity = findEntityByName(domain, 'enumerated_values')
    assert.exists(enumEntity, 'EnumeratedValues entity should exist')
    const properties = [...enumEntity.listProperties()]
    assert.lengthOf(properties, 1, 'EnumeratedValues entity should have one property')
    const [p1] = properties
    assert.equal(p1.info.name, 'data', 'Property should be data')
    assert.deepEqual(
      p1.schema?.enum,
      ['42', 'true', 'hello', 'null', '1,2,3'],
      'Property should have correct enum values'
    )
  })

  test('imports RegexpSchema schema correctly', async ({ assert }) => {
    const domain = await importSchemas([{ path: 'regexp.json', contents: RegexpSchema as JSONSchema7 }])
    const models = [...domain.listModels()]
    assert.lengthOf(models, 1, 'Should import one model')
    const entities = [...domain.listEntities()]
    assert.lengthOf(entities, 1, 'Should import one entity')
    const entity = findEntityByName(domain, 'regular_expression_pattern')
    assert.exists(entity, 'RegularExpressionPattern entity should exist')
    const properties = [...entity.listProperties()]
    assert.lengthOf(properties, 1, 'RegularExpressionPattern entity should have one property')
    const [p1] = properties
    assert.equal(p1.info.name, 'code', 'Property should be code')
    assert.equal(p1.schema?.pattern, '^[A-Z]{3}-\\d{3}$', 'Property should have correct pattern')
  })

  test('imports ComplexSchema schema correctly', async ({ assert }) => {
    const domain = await importSchemas([{ path: 'complex.json', contents: ComplexSchema as JSONSchema7 }])
    const models = [...domain.listModels()]
    assert.lengthOf(models, 1, 'Should import one model')
    const entities = [...domain.listEntities()]
    assert.lengthOf(entities, 2, 'Should import two entities')
    const entity = findEntityByName(domain, 'complex_object')
    assert.exists(entity, 'ComplexObject entity should exist')
    const properties = [...entity.listProperties()]
    assert.lengthOf(properties, 3, 'ComplexObject entity should have three properties')
    const [p1, p2, p3] = properties
    assert.equal(p1.info.name, 'name', 'First property should be name')
    assert.equal(p2.info.name, 'age', 'Second property should be age')
    assert.equal(p3.info.name, 'hobbies', 'Third property should be hobbies')
    const associations = [...entity.listAssociations()]
    assert.lengthOf(associations, 1, 'ComplexObject entity should have one association')
    const inlineEntity = findEntityByName(domain, 'complex_object_address')
    assert.exists(inlineEntity, 'ComplexObjectAddress entity should exist')
    const [a1] = associations
    assert.deepEqual(a1.targets, [{ key: inlineEntity.key }], 'Association should target ComplexObjectAddress')
  })
})
