
	parseDefinition(def: unknown, opts: BaseParseOptions = {}): BaseRoot {
		if (hasArkKind(def, "root")) return this.bindReference(def)

		const ctxInputOrNode = this.preparseOwnDefinitionFormat(def, opts)
		if (hasArkKind(ctxInputOrNode, "root"))
			return this.bindReference(ctxInputOrNode)

		const ctx = this.createParseContext(ctxInputOrNode)
		nodesByRegisteredId[ctx.id] = ctx
		let node = this.bindReference(this.parseOwnDefinitionFormat(def, ctx))

		// if the node is recursive e.g. { box: "this" }, we need to make sure it
		// has the original id from context so that its references compile correctly
		if (node.isCyclic) node = withId(node, ctx.id)

		nodesByRegisteredId[ctx.id] = node

		return node
	}

	finalize<node extends BaseRoot>(node: node): node {
		bootstrapAliasReferences(node)
		if (!node.precompilation && !this.resolvedConfig.jitless)
			precompile(node.references)
		return node
	}

	protected abstract preparseOwnDefinitionFormat(
		def: unknown,
		opts: BaseParseOptions
	): BaseRoot | BaseParseContextInput

	abstract parseOwnDefinitionFormat(
		def: unknown,
		ctx: BaseParseContext
	): BaseRoot

	protected abstract preparseOwnAliasEntry(k: string, v: unknown): AliasDefEntry

	protected abstract normalizeRootScopeValue(resolution: unknown): unknown
}

export class SchemaScope<$ extends {} = {}> extends BaseScope<$> {
	parseOwnDefinitionFormat(def: unknown, ctx: NodeParseContext): BaseRoot {
		return parseNode(ctx) as never
	}

	protected preparseOwnDefinitionFormat(
		schema: RootSchema,
		opts: BaseParseOptions
	): BaseRoot | NodeParseContextInput {
		return this.preparseNode(schemaKindOf(schema), schema, opts) as never
	}

	protected preparseOwnAliasEntry(k: string, v: unknown): AliasDefEntry {
		return [k, v]
	}

	protected normalizeRootScopeValue(v: unknown): unknown {
		return v
	}
}

const bootstrapAliasReferences = (resolution: BaseRoot | GenericRoot) => {
	const aliases = resolution.references.filter(node => node.hasKind("alias"))
	for (const aliasNode of aliases) {
		Object.assign(aliasNode.referencesById, aliasNode.resolution.referencesById)
		for (const ref of resolution.references) {
			if (aliasNode.id in ref.referencesById)
				Object.assign(ref.referencesById, aliasNode.referencesById)
