import type { IntrospectedNamespace } from "./gir/namespace.ts";

/**
 * A list of possible type conflicts.
 *
 * The format is CHILD_PARENT_CONFLICT so
 * ACCESSOR_PROPERTY_CONFLICT means there
 * is an accessor on a child class and a
 * property on the parent class, which is a
 * conflict.
 *
 * Starts at '1' because the value is often
 * used as truthy.
 */
export enum ConflictType {
	PROPERTY_NAME_CONFLICT = 1,
	FIELD_NAME_CONFLICT,
	FUNCTION_NAME_CONFLICT,
	ACCESSOR_PROPERTY_CONFLICT,
	PROPERTY_ACCESSOR_CONFLICT,
	VFUNC_SIGNATURE_CONFLICT,
}

import { type ConsoleReporter, LazyReporter } from "@ts-for-gir/reporter";
import type { IntrospectedField, IntrospectedProperty } from "./gir/property.ts";
import type { OptionsGeneration } from "./types/index.ts";
import { isInvalid, sanitizeIdentifierName, sanitizeNamespace } from "./utils/naming.ts";

export abstract class TypeExpression {
	isPointer = false;

	abstract equals(type: TypeExpression): boolean;
	abstract unwrap(): TypeExpression;

	deepUnwrap(): TypeExpression {
		return this.unwrap();
	}

	abstract rewrap(type: TypeExpression): TypeExpression;
	abstract resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression;

	abstract print(namespace: IntrospectedNamespace, options: OptionsGeneration): string;
	rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		return this.print(namespace, options);
	}
}

export class TypeIdentifier extends TypeExpression {
	readonly name: string;
	readonly namespace: string;

	private static readonly reporter = new LazyReporter("TypeIdentifier");

	static configureReporter(enabled: boolean, output?: string) {
		TypeIdentifier.reporter.configure(enabled, output);
	}

	get log(): ConsoleReporter {
		return TypeIdentifier.reporter.get();
	}

	constructor(name: string, namespace: string) {
		super();
		this.name = name;
		this.namespace = namespace;
	}

	equals(type: TypeExpression): boolean {
		return type instanceof TypeIdentifier && type.name === this.name && type.namespace === this.namespace;
	}

	is(namespace: string, name: string) {
		return this.namespace === namespace && this.name === name;
	}

	unwrap() {
		return this;
	}

	rewrap(type: TypeExpression): TypeExpression {
		return type;
	}

	/**
	 * TODO: gi.ts didn't deal with sanitizing types but probably should have to avoid
	 * invalid names such as "3gppProfile"
	 */
	sanitize() {
		return new TypeIdentifier(sanitizeIdentifierName(this.namespace, this.name), sanitizeNamespace(this.namespace));
	}

	protected _resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeIdentifier | null {
		const name: string = sanitizeIdentifierName(null, this.name);
		const unresolvedNamespaceName = this.namespace;

		const ns = namespace.assertInstalledImport(unresolvedNamespaceName);

		if (ns.hasSymbol(name)) {
			const c = ns.getClass(name);

			// Some records are structs for other class types.
			// GirRecord.prototype.getType resolves this relationship.
			if (c) return c.getType();

			return new TypeIdentifier(name, ns.namespace);
		}

		// Handle "class callback" types (they're in a definition-merged module)
		let [cb, corrected_name] = ns.findClassCallback(name);
		let resolved_name: string | null = null;

		if (!cb) {
			resolved_name = ns.resolveSymbolFromTypeName(name);
		}

		let c_resolved_name: string | null = null;

		if (!c_resolved_name) {
			c_resolved_name = ns.resolveSymbolFromTypeName(`${unresolvedNamespaceName}${name}`);
		}

		if (!c_resolved_name) {
			c_resolved_name = ns.resolveSymbolFromTypeName(`${ns.namespace}${name}`);
		}

		if (!cb && !resolved_name && !c_resolved_name) {
			// Don't warn if a missing import is at fault, this will be dealt with later.
			if (namespace.namespace === ns.namespace) {
				this.log.reportTypeResolutionWarning(
					this.name,
					this.namespace,
					`Attempting to fall back on c:type inference for ${ns.namespace}.${name}`,
					`Fallback to c:type inference attempted`,
				);
			}

			[cb, corrected_name] = ns.findClassCallback(`${ns.namespace}${name}`);

			if (cb) {
				this.log.reportTypeResolutionWarning(
					this.name,
					this.namespace,
					`Falling back on c:type inference for ${ns.namespace}.${name} and found ${ns.namespace}.${corrected_name}`,
					`Successfully resolved using c:type fallback`,
				);
			}
		}

		if (cb) {
			if (options.verbose) {
				this.log.debug(`Callback found: ${cb}.${corrected_name}`);
			}

			return new ModuleTypeIdentifier(corrected_name, cb, ns.namespace);
		} else if (resolved_name) {
			return new TypeIdentifier(resolved_name, ns.namespace);
		} else if (c_resolved_name) {
			this.log.reportTypeResolutionWarning(
				this.name,
				this.namespace,
				`Fall back on c:type inference for ${ns.namespace}.${name} and found ${ns.namespace}.${corrected_name}`,
				`Using c:type as fallback for type resolution`,
			);

			return new TypeIdentifier(c_resolved_name, ns.namespace);
		} else if (namespace.namespace === ns.namespace) {
			this.log.reportTypeResolutionWarning(
				this.name,
				ns.namespace,
				`Unable to resolve type ${this.name} in same namespace ${ns.namespace}!`,
				`Type resolution failed within the same namespace`,
			);
			return null;
		}

		this.log.reportTypeResolutionWarning(
			this.name,
			this.namespace,
			`Type ${this.name} could not be resolved in ${namespace.namespace} ${namespace.version}`,
			`Referenced from ${namespace.namespace} ${namespace.version}`,
			`${namespace.namespace} ${namespace.version}`,
		);
		return null;
	}

	resolveIdentifier(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeIdentifier | null {
		return this._resolve(namespace, options);
	}

	resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
		const resolved = this._resolve(namespace, options);

		// Generally if we can't resolve a type it is not introspectable,
		// thus we annotate it as "never".
		return resolved ?? NeverType;
	}

	static new({ name, namespace }: { name: string; namespace: string }) {
		return new TypeIdentifier(name, namespace);
	}

	print(namespace: IntrospectedNamespace, _options: OptionsGeneration): string {
		if (namespace.hasSymbol(this.namespace) && this.namespace !== namespace.namespace) {
			// TODO: Move to TypeScript generator...
			// Libraries like zbar have classes named things like "Gtk"
			return `${this.namespace}__.${this.name}`;
		}

		if (namespace.namespace === this.namespace) {
			return `${this.name}`;
		} else {
			return `${this.namespace}.${this.name}`;
		}
	}
}

export class ModuleTypeIdentifier extends TypeIdentifier {
	readonly moduleName: string;
	declare readonly namespace: string;

	constructor(name: string, moduleName: string, namespace: string) {
		super(name, namespace);
		this.moduleName = moduleName;
		this.namespace = namespace;
	}

	equals(type: TypeExpression): boolean {
		return super.equals(type) && type instanceof ModuleTypeIdentifier && this.moduleName === type.moduleName;
	}

	is(namespace: string, moduleName: string, name?: string) {
		return this.namespace === namespace && this.moduleName === moduleName && this.name === name && name !== undefined;
	}

	unwrap() {
		return this;
	}

	rewrap(type: TypeExpression): TypeExpression {
		return type;
	}

	sanitize() {
		return new ModuleTypeIdentifier(
			sanitizeIdentifierName(this.namespace, this.name),
			sanitizeIdentifierName(this.namespace, this.moduleName),
			sanitizeNamespace(this.namespace),
		);
	}

	protected _resolve(_namespace: IntrospectedNamespace, _options: OptionsGeneration): ModuleTypeIdentifier | null {
		return this;
	}

	print(namespace: IntrospectedNamespace, _options: OptionsGeneration): string {
		if (namespace.namespace === this.namespace) {
			return `${this.moduleName}.${this.name}`;
		} else {
			return `${this.namespace}.${this.moduleName}.${this.name}`;
		}
	}
}

/**
 * This class overrides the default printing for types
 */
export class ClassStructTypeIdentifier extends TypeIdentifier {
	equals(type: TypeExpression): boolean {
		return type instanceof ClassStructTypeIdentifier && super.equals(type);
	}

	print(namespace: IntrospectedNamespace, _options: OptionsGeneration): string {
		if (namespace.namespace === this.namespace) {
			// TODO: Mapping to invalid names should happen at the generator level...
			return `typeof ${isInvalid(this.name) ? `__${this.name}` : this.name}`;
		} else {
			return `typeof ${this.namespace}.${isInvalid(this.name) ? `__${this.name}` : this.name}`;
		}
	}
}

export class GenerifiedTypeIdentifier extends TypeIdentifier {
	generics: TypeExpression[];

	constructor(name: string, namespace: string, generics: TypeExpression[] = []) {
		super(name, namespace);
		this.generics = generics;
	}

	print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		const Generics = this.generics.map((generic) => generic.print(namespace, options)).join(", ");

		if (namespace.namespace === this.namespace) {
			return `${this.name}${this.generics.length > 0 ? `<${Generics}>` : ""}`;
		} else {
			return `${this.namespace}.${this.name}${this.generics.length > 0 ? `<${Generics}>` : ""}`;
		}
	}

	_resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeIdentifier | null {
		const iden = super._resolve(namespace, options);

		if (iden) {
			return new GenerifiedTypeIdentifier(iden.name, iden.namespace, [...this.generics]);
		}

		return iden;
	}
}

export class NativeType extends TypeExpression {
	readonly expression: (options?: OptionsGeneration) => string;

	constructor(expression: ((options?: OptionsGeneration) => string) | string) {
		super();
		this.expression = typeof expression === "string" ? () => expression : expression;
	}

	rewrap(type: TypeExpression): TypeExpression {
		return type;
	}

	resolve(): TypeExpression {
		return this;
	}

	print(_namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		return this.expression(options);
	}

	equals(type: TypeExpression, options?: OptionsGeneration): boolean {
		return type instanceof NativeType && this.expression(options) === type.expression(options);
	}

	unwrap(): TypeExpression {
		return this;
	}

	static withGenerator(generator: (options?: OptionsGeneration) => string): TypeExpression {
		return new NativeType(generator);
	}

	static of(nativeType: string): NativeType {
		return new NativeType(nativeType);
	}
}

export class OrType extends TypeExpression {
	readonly types: ReadonlyArray<TypeExpression>;

	constructor(...types: TypeExpression[]) {
		super();
		this.types = [...types];
	}

	rewrap(type: TypeExpression): TypeExpression {
		return type;
	}

	unwrap(): TypeExpression {
		return this;
	}

	resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
		return makeUnion(...this.types.map((t) => t.resolve(namespace, options)));
	}

	print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		return `${this.types.map((t) => t.print(namespace, options)).join(" | ")}`;
	}

	rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		return this.print(namespace, options);
	}

	equals(type: TypeExpression) {
		if (type instanceof OrType) {
			return this.types.every((t) => type.types.some((type) => type.equals(t)));
		} else {
			return false;
		}
	}
}

export function makeUnion(...inputTypes: TypeExpression[]) {
	const types: Set<TypeExpression> = new Set();
	for (const type of inputTypes) {
		if (type instanceof BinaryType) {
			types.add(type.a);
			types.add(type.b);
		} else if (type instanceof OrType && !(type instanceof TupleType)) {
			for (const t of type.types) {
				types.add(t);
			}
		} else {
			types.add(type);
		}
	}
	if (types.size === 1) {
		return [...types][0];
	}
	if (types.size === 2) {
		const typesArray = [...types];
		if (typesArray[0] === NullType) {
			return new NullableType(typesArray[1]);
		}
		if (typesArray[1] === NullType) {
			return new NullableType(typesArray[0]);
		}
		return new BinaryType(...typesArray);
	}
	return new OrType(...types);
}

export class TupleType extends OrType {
	print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		return `[${this.types.map((t) => t.print(namespace, options)).join(", ")}]`;
	}

	rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		return this.print(namespace, options);
	}

	resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
		const [type, ...types] = this.types;

		return new TupleType(type.resolve(namespace, options), ...types.map((t) => t.resolve(namespace, options)));
	}

	equals(type: TypeExpression) {
		if (type instanceof TupleType) {
			return this.types.length === type.types.length && this.types.every((t, i) => type.types[i].equals(t));
		} else {
			return false;
		}
	}
}

export class BinaryType extends OrType {
	unwrap(): TypeExpression {
		return this;
	}

	resolve(namespace: IntrospectedNamespace, options: OptionsGeneration) {
		return new BinaryType(this.a.resolve(namespace, options), this.b.resolve(namespace, options));
	}

	is() {
		return false;
	}

	get a() {
		return this.types[0];
	}

	get b() {
		return this.types[1];
	}
}

export class FunctionType extends TypeExpression {
	parameterTypes: { [name: string]: TypeExpression };
	returnType: TypeExpression;

	constructor(parameters: { [name: string]: TypeExpression }, returnType: TypeExpression) {
		super();

		this.parameterTypes = parameters;
		this.returnType = returnType;
	}

	equals(type: TypeExpression): boolean {
		if (type instanceof FunctionType) {
			return (
				Object.values(this.parameterTypes).every((t) =>
					Object.values(type.parameterTypes).some((tp) => t.equals(tp)),
				) &&
				Object.values(type.parameterTypes).every((t) =>
					Object.values(this.parameterTypes).some((tp) => t.equals(tp)),
				) &&
				this.returnType.equals(type.returnType)
			);
		}

		return false;
	}

	rewrap(type: TypeExpression): TypeExpression {
		return type;
	}

	unwrap(): TypeExpression {
		return this;
	}

	resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
		return new FunctionType(
			Object.fromEntries(
				Object.entries(this.parameterTypes).map(([k, p]) => {
					return [k, p.resolve(namespace, options)];
				}),
			),
			this.returnType.resolve(namespace, options),
		);
	}

	rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		const Parameters = Object.entries(this.parameterTypes)
			.map(([k, v]) => {
				return `${k}: ${v.rootPrint(namespace, options)}`;
			})
			.join(", ");

		return `(${Parameters}) => ${this.returnType.print(namespace, options)}`;
	}

	print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		return `(${this.rootPrint(namespace, options)})`;
	}
}

export class Generic {
	private _deriveFrom: TypeIdentifier | null;
	private _genericType: GenericType;
	private _defaultType: TypeExpression | null;
	private _constraint: TypeExpression | null;
	private _propagate: boolean;

	constructor(
		genericType: GenericType,
		defaultType?: TypeExpression,
		deriveFrom?: TypeIdentifier,
		constraint?: TypeExpression,
		propagate = true,
	) {
		this._deriveFrom = deriveFrom ?? null;
		this._genericType = genericType;
		this._defaultType = defaultType ?? null;
		this._constraint = constraint ?? null;
		this._propagate = propagate;
	}

	unwrap() {
		return this._genericType;
	}

	get propagate() {
		return this._propagate;
	}

	get type() {
		return this._genericType;
	}

	get defaultType() {
		return this._defaultType;
	}

	get constraint() {
		return this._constraint;
	}

	get parent() {
		return this._deriveFrom;
	}
}

export class GenerifiedType extends TypeExpression {
	type: TypeExpression;
	generic: GenericType;

	constructor(type: TypeExpression, generic: GenericType) {
		super();

		this.type = type;
		this.generic = generic;
	}

	resolve(namespace: IntrospectedNamespace, options: OptionsGeneration) {
		return new GenerifiedType(this.type.resolve(namespace, options), this.generic);
	}

	unwrap() {
		return this.type;
	}

	rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration) {
		return this.type.rootPrint(namespace, options);
	}

	print(namespace: IntrospectedNamespace, options: OptionsGeneration) {
		return `${this.type.print(namespace, options)}<${this.generic.print()}>`;
	}

	equals(type: TypeExpression): boolean {
		if (type instanceof GenerifiedType) {
			return this.type.equals(type.type) && this.generic.equals(type.generic);
		}

		return this.type.equals(type);
	}

	rewrap(type: TypeExpression): TypeExpression {
		return new GenerifiedType(this.type.rewrap(type), this.generic);
	}
}

export class GenericType extends TypeExpression {
	identifier: string;
	replacedType?: TypeExpression;

	constructor(identifier: string, replacedType?: TypeExpression) {
		super();

		this.identifier = identifier;
		this.replacedType = replacedType;
	}

	equals(type: TypeExpression): boolean {
		if (type instanceof GenericType) {
			const genericType = type;
			return this.identifier === genericType.identifier;
		}

		return false;
	}

	unwrap(): TypeExpression {
		return this;
	}

	rewrap(type: TypeExpression): TypeExpression {
		return type;
	}

	resolve(): GenericType {
		return this;
	}

	print(): string {
		return this.identifier;
	}
}

export class NullableType extends BinaryType {
	constructor(type: TypeExpression) {
		super(type, NullType);
	}

	unwrap() {
		return this.a;
	}

	rewrap(type: TypeExpression): TypeExpression {
		return makeNullable(this.a.rewrap(type));
	}

	get type() {
		return this.a;
	}
}

export function makeNullable(type: TypeExpression) {
	if (type instanceof NullableType) return type;
	if (type === RawPointerType) return NullType;
	if (type === AnyType) return AnyType;
	return makeUnion(type, NullType);
}

export class PromiseType extends TypeExpression {
	type: TypeExpression;

	constructor(type: TypeExpression) {
		super();
		this.type = type;
	}

	equals(type: TypeExpression): boolean {
		return type instanceof PromiseType && type.type.equals(this.type);
	}

	unwrap() {
		return this;
	}

	rewrap(type: TypeExpression): TypeExpression {
		return new PromiseType(this.type.rewrap(type));
	}

	resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
		return new PromiseType(this.type.resolve(namespace, options));
	}

	print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		if (this.type.equals(VoidType)) {
			return "globalThis.Promise<void>";
		}

		return `globalThis.Promise<${this.type.print(namespace, options)}>`;
	}

	rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		return this.print(namespace, options);
	}
}

/**
 * This is one of the more interesting usages of our type
 * system. To handle type conflicts we wrap conflicting types
 * in this class with a ConflictType to denote why they are a
 * conflict.
 *
 * TypeConflict will throw if it is printed or resolved, so generators
 * must unwrap it and "resolve" the conflict. Some generators like JSON
 * just disregard this info, other generators like DTS attempt to
 * resolve the conflicts so the typing stays sound.
 */
export class TypeConflict extends TypeExpression {
	readonly conflictType: ConflictType;
	readonly type: TypeExpression;

	constructor(type: TypeExpression, conflictType: ConflictType) {
		super();
		this.type = type;
		this.conflictType = conflictType;
	}

	rewrap(type: TypeExpression): TypeConflict {
		return new TypeConflict(this.type.rewrap(type), this.conflictType);
	}

	unwrap(): TypeExpression {
		return this.type;
	}

	// TODO: This constant "true" is a remnant of the Anyified type.
	equals(): boolean {
		return true;
	}

	resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
		const resolvedType = this.type.resolve(namespace, options);
		const typeString = resolvedType.print(namespace, options);
		throw new Error(`Type conflict was not resolved for ${typeString} in ${namespace.namespace}`);
	}

	print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		const resolvedType = this.type.resolve(namespace, options);
		const typeString = resolvedType.print(namespace, options);
		throw new Error(`Type conflict was not resolved for ${typeString} in ${namespace.namespace}`);
	}
}

export class ClosureType extends TypeExpression {
	type: TypeExpression;
	user_data: number | null = null;

	constructor(type: TypeExpression) {
		super();
		this.type = type;
	}

	equals(type: TypeExpression): boolean {
		if (type instanceof ClosureType) {
			const closureType = type;
			return this.type.equals(closureType.type);
		}

		return false;
	}

	deepUnwrap(): TypeExpression {
		return this.type;
	}

	rewrap(type: TypeExpression): TypeExpression {
		const closure = new ClosureType(this.type.rewrap(type));

		closure.user_data = this.user_data;

		return closure;
	}

	unwrap(): TypeExpression {
		return this;
	}

	resolve(namespace: IntrospectedNamespace, options: OptionsGeneration) {
		const { user_data, type } = this;

		return ClosureType.new({
			user_data,
			type: type.resolve(namespace, options),
		});
	}

	print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		return this.type.print(namespace, options);
	}

	static new({ type, user_data = null }: { type: TypeExpression; user_data?: number | null }) {
		const vt = new ClosureType(type);
		vt.user_data = user_data;
		return vt;
	}
}

export class ArrayType extends TypeExpression {
	type: TypeExpression;
	arrayDepth: number = 1;
	length: number | null = null;

	constructor(type: TypeExpression) {
		super();
		this.type = type;
	}

	deepUnwrap(): TypeExpression {
		return this.type;
	}

	unwrap(): TypeExpression {
		return this;
	}

	rewrap(type: TypeExpression): TypeExpression {
		const array = new ArrayType(this.type.rewrap(type));

		array.arrayDepth = this.arrayDepth;
		array.length = this.length;

		return array;
	}

	equals(type: TypeExpression) {
		if (type instanceof ArrayType) {
			const arrayType: ArrayType = type;

			return arrayType.type.equals(this.type) && type.arrayDepth === this.arrayDepth;
		}

		return false;
	}

	resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
		const { type, arrayDepth, length } = this;
		return ArrayType.new({
			type: type.resolve(namespace, options),
			arrayDepth,
			length,
		});
	}

	print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
		const depth = this.arrayDepth;
		let typeSuffix: string = "";

		if (depth === 0) {
			typeSuffix = "";
		} else if (depth === 1) {
			typeSuffix = "[]";
		} else {
			typeSuffix = "".padStart(2 * depth, "[]");
		}

		if (this.type instanceof OrType && !(this.type instanceof TupleType))
			return `(${this.type.print(namespace, options)})${typeSuffix}`;
		return `${this.type.print(namespace, options)}${typeSuffix}`;
	}

	static new({
		type,
		arrayDepth = 1,
		length = null,
	}: {
		type: TypeExpression;
		length?: number | null;
		arrayDepth?: number;
	}) {
		const vt = new ArrayType(type);
		vt.length = length;
		vt.arrayDepth = arrayDepth;
		return vt;
	}
}

/**
 * Common native types as constants.
 * These represent TypeScript types in generated .d.ts output for GJS runtime,
 * not internal tool types. Uses of `any` here are intentional — they produce
 * correct type definitions for GLib/GIO/GObject APIs that accept dynamic values.
 */
export const ThisType = new NativeType("this");
export const ObjectType = new NativeType("object");
export const AnyType = new NativeType("any");
export const NeverType = new NativeType("never");
export const Uint8ArrayType = new NativeType("Uint8Array");
export const BooleanType = new NativeType("boolean");
export const StringType = new NativeType("string");
export const NumberType = new NativeType("number");
export const BigintOrNumberType = new BinaryType(new NativeType("bigint"), NumberType);
export const NullType = new NativeType("null");
export const VoidType = new NativeType("void");
export const UnknownType = new NativeType("unknown");
export const AnyFunctionType = new NativeType("(...args: any[]) => any");
// Distinct from NeverType, so that we can transform it into NullType when
// marshalled from C to JS
export const RawPointerType = new NativeType("never");

export type GirClassField = IntrospectedProperty | IntrospectedField;
