/**
 * @aedart/contracts
 * 
 * BSD-3-Clause, Copyright (c) 2023-present Alin Eugen Deac <aedart@gmail.com>.
 */

import { ConstructorLike } from '@aedart/contracts';
import { Throwable } from '@aedart/contracts/support/exceptions';
import { InjectionException as InjectionException$1 } from '@aedart/contracts/support/concerns';

/**
 * Concern
 *
 * A "concern" is component that can be injected into a target class (concern owner).
 * The concern itself is NOT responsible for performing the actual injection logic.
 *
 * @interface
 */
interface Concern {
    /**
     * The owner class instance this concern is injected into,
     * or `this` concern instance if no owner was set.
     *
     * @readonly
     *
     * @type {object}
     */
    get concernOwner(): object;
}

/**
 * Concern Constructor
 *
 * @template T extends Concern
 *
 * @see Concern
 */
interface ConcernConstructor<T extends Concern = Concern> {
    /**
     * Creates a new concern instance
     *
     * @param {object} [owner] The owner class instance this concern is injected into.
     *                         Defaults to `this` concern instance if none given.
     *
     * @throws {Error} When concern is unable to preform initialisation, e.g. caused
     *                 by the owner or other circumstances.
     */
    new (owner?: object): T;
    /**
     * Returns list of property keys that this concern class offers.
     *
     * **Note**: _Only properties and methods returned by this method can be aliased
     * into a target class._
     *
     * @static
     *
     * @return {PropertyKey[]}
     */
    [PROVIDES](): PropertyKey[];
}

/**
 * An alias for a property or method in a {@link Concern} class
 */
type Alias = PropertyKey;
/**
 * Key-value pair where the key corresponds to a property or method inside the {@link Concern} instance,
 * and value is an "alias" for that property or method.
 *
 * In this context an "alias" means a property or method that is added onto a target
 * class' prototype and acts as a proxy to the original property or method inside the
 * concern class instance.
 */
type Aliases<T extends Concern = Concern> = {
    [key in keyof T]: Alias;
} | {
    [key: PropertyKey]: Alias;
};
/**
 * Shorthand Concern Injection Configuration
 *
 * @see [Configuration]{@link import('@aedart/contracts/support/concerns').Configuration}
 */
type ShorthandConfiguration<T extends Concern = Concern> = [
    ConcernConstructor<T>
] | [ConcernConstructor<T>, Aliases<T>] | [ConcernConstructor<T>, boolean];

/**
 * Concern Injection Configuration
 *
 * Defines the Concern class that must be injected into a target class,
 * along with other aspects of the injection, such as what aliases to use
 * and what properties or methods should not be aliased.
 *
 * @template T extends Concern = Concern
 *
 * @see {Concern}
 * @see {Aliases}
 */
interface Configuration<T extends Concern = Concern> {
    /**
     * The Concern Class that must be injected into a target class
     *
     * @template T extends Concern = Concern
     *
     * @type {ConcernConstructor<T>}
     */
    concern: ConcernConstructor<T>;
    /**
     * Aliases for Concern's properties or methods.
     *
     * **Note**: _Defaults to same property and method names as "aliases", as those defined by
     * a concern class' [PROVIDES]{@link import('@aedart/contracts/support/concerns').PROVIDES},
     * if this property is empty or `undefined`._
     *
     * @template T extends Concern = Concern
     *
     * @type {Aliases<T>|undefined}
     */
    aliases?: Aliases<T>;
    /**
     * Flag that indicates whether an "injector" is allowed to create
     * "aliases" (proxy) properties and methods into a target class's prototype.
     *
     * **Note**: _Defaults to `true` if this property is `undefined`._
     *
     * **Note**: _If set to `false`, then {@link aliases} are ignored._
     *
     * @type {boolean}
     */
    allowAliases?: boolean;
}

/**
 * Concerns Owner
 *
 * An owner is an object, e.g. instance of a class, that offers a concerns {@link Container}.
 */
interface Owner {
    /**
     * The concerns container for this class
     *
     * @readonly
     *
     * @type {Container}
     */
    readonly [CONCERNS]: Container;
}

/**
 * Concerns Container
 */
interface Container {
    /**
     * The amount of concerns in this container
     *
     * @readonly
     *
     * @type {number}
     */
    readonly size: number;
    /**
     * Get the concerns container owner
     *
     * @type {Owner}
     */
    get owner(): Owner;
    /**
     * Determine if concern class is registered in this container
     *
     * @param {ConcernConstructor} concern
     *
     * @return {boolean}
     */
    has(concern: ConcernConstructor): boolean;
    /**
     * Retrieve concern instance for given concern class
     *
     * **Note**: _If concern class is registered in this container, but not yet
     * booted, then this method will boot it via the {@link boot} method, and return
     * the resulting instance._
     *
     * @template T extends {@link Concern}
     *
     * @param {ConcernConstructor<T>} concern
     *
     * @return {T} The booted instance of the concern class. If concern class was
     *             previously booted, then that instance is returned.
     *
     * @throws {ConcernException}
     */
    get<T extends Concern>(concern: ConcernConstructor<T>): T;
    /**
     * Determine if concern class has been booted
     *
     * @param {ConcernConstructor} concern
     *
     * @return {boolean}
     */
    hasBooted(concern: ConcernConstructor): boolean;
    /**
     * Boot concern class
     *
     * @template T extends {@link Concern}
     *
     * @param {ConcernConstructor<T>} concern
     *
     * @return {T} New concern instance
     *
     * @throws {NotRegisteredException} If concern class is not registered in this container
     * @throws {BootException} If concern is unable to be booted, e.g. if already booted
     */
    boot<T extends Concern>(concern: ConcernConstructor<T>): T;
    /**
     * Boots all registered concern classes
     *
     * @throws {ConcernException}
     */
    bootAll(): void;
    /**
     * Determine if this container is empty
     *
     * @return {boolean}
     */
    isEmpty(): boolean;
    /**
     * Opposite of {@link isEmpty}
     *
     * @return {boolean}
     */
    isNotEmpty(): boolean;
    /**
     * Returns all concern classes
     *
     * @return {IterableIterator<ConcernConstructor>}
     */
    all(): IterableIterator<ConcernConstructor>;
    /**
     * Invoke a method with given arguments in concern instance
     *
     * @param {ConcernConstructor} concern
     * @param {PropertyKey} method
     * @param {...any} [args]
     *
     * @return {any}
     *
     * @throws {ConcernException}
     * @throws {Error}
     */
    call(concern: ConcernConstructor, method: PropertyKey, ...args: any[]): any;
    /**
     * Set the value of given property in concern instance
     *
     * @param {ConcernConstructor} concern
     * @param {PropertyKey} property
     * @param {any} value
     *
     * @throws {ConcernException}
     * @throws {Error}
     */
    setProperty(concern: ConcernConstructor, property: PropertyKey, value: any): void;
    /**
     * Get value of given property in concern instance
     *
     * @param {ConcernConstructor} concern
     * @param {PropertyKey} property
     *
     * @return {any}
     *
     * @throws {ConcernException}
     * @throws {Error}
     */
    getProperty(concern: ConcernConstructor, property: PropertyKey): any;
}

/**
 * Uses Concerns
 *
 * A target class that uses one or more concern classes.
 *
 * @template T = object
 */
interface UsesConcerns<T = object> {
    /**
     * Constructor
     *
     * @template T = object
     *
     * @param {...any} [args]
     *
     * @returns {ConstructorLike<T> & Owner}
     */
    new (...args: any[]): ConstructorLike<T & Owner>;
    /**
     * A list of the concern classes to be used by this target class.
     *
     * **Note**: _If this class' parent class also uses concerns, then those
     * concern classes are included recursively in this list, in the order
     * that they have been registered._
     *
     * **Note**: _This property is usually automatically defined and populated
     * by an {@link Injector}, and used to prevent duplicate concern injections._
     *
     * @static
     *
     * @type {ConcernConstructor[]}
     */
    [CONCERN_CLASSES]: ConcernConstructor[];
    /**
     * List of aliases applied in this target class.
     *
     * **Note**: _This property is usually automatically defined and populated
     * by an {@link Injector}, and used to prevent alias conflicts._
     *
     * @static
     *
     * @type {Alias[]}
     */
    [ALIASES]: Alias[];
}

/**
 * Descriptors Repository
 *
 * Utility for obtaining property descriptors for a target class or concern.
 */
interface DescriptorsRepository {
    /**
     * Returns property descriptors for given target class (recursively)
     *
     * @param {ConstructorLike | UsesConcerns | ConcernConstructor} target The target class, or concern class
     * @param {boolean} [force=false] If `true` then method will not return evt. cached descriptors.
     * @param {boolean} [cache=false] Caches the descriptors if `true`.
     *
     * @returns {Record<PropertyKey, PropertyDescriptor>}
     */
    get(target: ConstructorLike | UsesConcerns | ConcernConstructor, force?: boolean, cache?: boolean): Record<PropertyKey, PropertyDescriptor>;
    /**
     * Caches property descriptors for target during the execution of callback.
     *
     * @param {ConstructorLike | UsesConcerns | ConcernConstructor} target The target class, or concern class
     * @param {() => any} callback Callback to invoke
     * @param {boolean} [forgetAfter=true] It `true`, cached descriptors are deleted after callback is invoked
     *
     * @return {any}
     */
    rememberDuring(target: ConstructorLike | UsesConcerns | ConcernConstructor, callback: () => any, /* eslint-disable-line @typescript-eslint/no-explicit-any */ forgetAfter?: boolean): any;
    /**
     * Retrieves the property descriptors for given target and caches them
     *
     * @param {ConstructorLike | UsesConcerns | ConcernConstructor} target The target class, or concern class
     * @param {boolean} [force=false] If `true` then evt. previous cached result is not used.
     *
     * @returns {Record<PropertyKey, PropertyDescriptor>}
     */
    remember(target: ConstructorLike | UsesConcerns | ConcernConstructor, force?: boolean): Record<PropertyKey, PropertyDescriptor>;
    /**
     * Deletes cached descriptors for target
     *
     * @param {ConstructorLike | UsesConcerns | ConcernConstructor} target
     *
     * @return {boolean}
     */
    forget(target: ConstructorLike | UsesConcerns | ConcernConstructor): boolean;
    /**
     * Clears all cached descriptors
     *
     * @return {this}
     */
    clear(): this;
}

/**
 * Concern Configuration Factory
 *
 * Able to make new {@link Configuration} from a given concern "entry".
 */
interface Factory {
    /**
     * Returns a new normalised concern configuration for given concern "entry"
     *
     * **Note**: _"normalised" in this context means:_
     *
     * _**A**: If a concern class is given, then a new concern configuration made._
     *
     * _**B**: If configuration is given, then a new concern configuration and given
     * configuration is merged into the new configuration._
     *
     * _**C**: Configuration's `aliases` are automatically populated. When a concern
     * configuration is provided, its evt. aliases merged with the default ones,
     * unless `allowAliases` is set to `false`, in which case all aliases are removed._
     *
     * @param {object} target
     * @param {ConcernConstructor | Configuration | ShorthandConfiguration} entry
     *
     * @returns {Configuration}
     *
     * @throws {InjectionException} If entry is unsupported or invalid
     */
    make(target: object, entry: ConcernConstructor | Configuration | ShorthandConfiguration): Configuration;
}

/**
 * Concerns Injector
 *
 * Able to inject concerns into a target class and create alias (proxy) properties
 * and methods to the provided concerns' properties and methods, in the target class.
 *
 * @template T = object The target class that concern classes must be injected into
 *
 * @see {Concern}
 */
interface Injector<T = object> {
    /**
     * The target class
     *
     * @returns {T}
     */
    get target(): T;
    /**
     * Injects concern classes into the target class and return the modified target.
     *
     * **Note**: _Method performs injection in the following way:_
     *
     * _**A**: Defines the concern classes in target class, via {@link defineConcerns}._
     *
     * _**B**: Defines a concerns container in target class' prototype, via {@link defineContainer}._
     *
     * _**C**: Defines "aliases" (proxy properties and methods) in target class' prototype, via {@link defineAliases}._
     *
     * @template T = object The target class that concern classes must be injected into
     *
     * @param {...ConcernConstructor | Configuration | ShorthandConfiguration} concerns List of concern classes / injection configurations
     *
     * @returns {UsesConcerns<T>} The modified target class
     *
     * @throws {InjectionException}
     */
    inject(...concerns: (ConcernConstructor | Configuration | ShorthandConfiguration)[]): UsesConcerns<T>;
    /**
     * Defines the concern classes that must be used by the target class.
     *
     * **Note**: _Method changes the target class, such that it implements and respects the
     * {@link UsesConcerns} interface._
     *
     * @template T = object
     *
     * @param {T} target The target class that must define the concern classes to be used
     * @param {Constructor<Concern>[]} concerns List of concern classes
     *
     * @returns {UsesConcerns<T>} The modified target class
     *
     * @throws {AlreadyRegisteredException}
     * @throws {InjectionException}
     */
    defineConcerns<T = object>(target: T, concerns: ConcernConstructor[]): UsesConcerns<T>;
    /**
     * Defines a concerns {@link Container} in target class' prototype.
     *
     * **Note**: _Method changes the target class, such that it implements and respects the
     * [Owner]{@link import('@aedart/contracts/support/concerns').Owner} interface!_
     *
     * @template T = object
     *
     * @param {UsesConcerns<T>} target The target in which a concerns container must be defined
     *
     * @returns {UsesConcerns<T>} The modified target class
     *
     * @throws {InjectionException} If unable to define concerns container in target class
     */
    defineContainer<T = object>(target: UsesConcerns<T>): UsesConcerns<T>;
    /**
     * Defines "aliases" (proxy properties and methods) in target class' prototype, such that they
     * point to the properties and methods available in the concern classes.
     *
     * **Note**: _Method defines each alias using the {@link defineAlias} method!_
     *
     * @template T = object
     *
     * @param {UsesConcerns<T>} target The target in which "aliases" must be defined in
     * @param {Configuration[]} configurations List of concern injection configurations
     *
     * @returns {UsesConcerns<T>} The modified target class
     *
     * @throws {AliasConflictException} If case of alias naming conflicts.
     * @throws {InjectionException} If unable to define aliases in target class.
     */
    defineAliases<T = object>(target: UsesConcerns<T>, configurations: Configuration[]): UsesConcerns<T>;
    /**
     * Defines an "alias" (proxy property or method) in target class' prototype, which points to a property or method
     * in the given concern.
     *
     * **Note**: _Method will do nothing, if a property or method already exists in the target class' prototype
     * chain, with the same name as given "alias"._
     *
     * @template T = object
     *
     * @param {UsesConcerns<T>} target The target in which "alias" must be defined in
     * @param {PropertyKey} alias Name of the "alias" in the target class (name of the proxy property or method)
     * @param {PropertyKey} key Name of the property or method that the "alias" points to, in the concern class (`source`)
     * @param {Constructor} source The source concern class that contains the property or methods that is pointed to (`key`)
     *
     * @returns {boolean} `true` if "alias" was in target class. `false` if a property or method already exists in the
     *                     target, with the same name as the "alias".
     *
     * @throws {UnsafeAliasException} If an alias points to an "unsafe" property or method in the source concern class.
     * @throws {InjectionException} If unable to define "alias" in target class.
     */
    defineAlias<T = object>(target: UsesConcerns<T>, alias: PropertyKey, key: PropertyKey, source: ConcernConstructor): boolean;
}

/**
 * Registration Aware
 *
 * Concern class is aware of when it is being registered by a target class
 * and is able to performs pre-/post-registration logic.
 */
interface RegistrationAware {
    /**
     * Perform pre-registration logic.
     *
     * **Note**: _This hook method is intended to be invoked by an
     * [Injector]{@link import('@aedart/contracts/support/concerns').Injector}, before
     * the concern container and aliases are defined in the target class._
     *
     * @static
     *
     * @param {UsesConcerns} target Target class constructor
     *
     * @return {void}
     *
     * @throws {Error}
     */
    [BEFORE](target: UsesConcerns): void;
    /**
     * Perform post-registration logic.
     *
     * **Note**: _This hook method is intended to be invoked by an
     * [Injector]{@link import('@aedart/contracts/support/concerns').Injector}, after
     * this concern class has been registered in a target class and aliases have been
     * defined in target's prototype._
     *
     * @static
     *
     * @param {UsesConcerns} target Target class constructor, after concerns and aliases defined
     *
     * @return {void}
     *
     * @throws {Error}
     */
    [AFTER](target: UsesConcerns): void;
}

/**
 * Alias Descriptor Factory
 */
interface AliasDescriptorFactory {
    /**
     * Makes a property descriptor to be used for an "alias" (proxy) property or method
     *
     * @param {PropertyKey} key The property key in `source` concern
     * @param {ConcernConstructor} source The concern that holds the property key
     * @param {PropertyDescriptor} keyDescriptor Descriptor of `key` in `source` concern
     *
     * @returns {PropertyDescriptor} Descriptor to be used for defining alias in a target class
     */
    make(key: PropertyKey, source: ConcernConstructor, keyDescriptor: PropertyDescriptor): PropertyDescriptor;
}

/**
 * Concern Exception
 *
 * To be thrown when a concern class is the cause of an error.
 */
interface ConcernException extends Throwable {
    /**
     * The Concern class that caused this error or exception
     *
     * @readonly
     *
     * @type {ConcernConstructor | null}
     */
    readonly concern: ConcernConstructor | null;
}

/**
 * Concern Injection Exception
 *
 * To be thrown when a concern class cannot be injected into a target class.
 */
interface InjectionException extends ConcernException {
    /**
     * The target class
     *
     * @readonly
     *
     * @type {ConstructorLike | UsesConcerns}
     */
    readonly target: ConstructorLike | UsesConcerns;
}

/**
 * Alias Conflict Exception
 *
 * To be thrown if an alias conflicts with another alias.
 */
interface AliasConflictException extends InjectionException {
    /**
     * The requested alias that conflicts with another alias
     * of the same name.
     *
     * @readonly
     *
     * @type {Alias}
     */
    readonly alias: Alias;
    /**
     * the property key that the conflicting alias points to
     *
     * @readonly
     *
     * @type {Alias}
     */
    readonly key: PropertyKey;
    /**
     * The source class (e.g. parent class) that defines that originally defined the alias
     *
     * @readonly
     *
     * @type {ConstructorLike | UsesConcerns}
     */
    readonly source: ConstructorLike | UsesConcerns;
}

/**
 * Already Registered Exception
 *
 * To be thrown when a concern class is attempted registered in a target, but
 * was previously registered in the target or target's parent.
 */
interface AlreadyRegisteredException extends InjectionException$1 {
    /**
     * The source, e.g. a parent class, in which a concern class
     * was already registered.
     *
     * @readonly
     *
     * @type {ConstructorLike | UsesConcerns}
     */
    readonly source: ConstructorLike | UsesConcerns;
}

/**
 * Concern Boot Exception
 *
 * To be thrown when a concern class is unable to boot.
 *
 * @see ConcernException
 */
interface BootException extends ConcernException {
}

/**
 * Concern Not Registered Exception
 *
 * To be thrown when a given concern is expected registered in a [Container]{@link import('@aedart/contracts/support/concerns').Container},
 * but isn't.
 *
 * @see ConcernException
 */
interface NotRegisteredException extends ConcernException {
}

/**
 * Unsafe Alias Exception
 *
 * To be thrown when an alias points to an "unsafe" property or method inside a concern.
 */
interface UnsafeAliasException extends InjectionException {
    /**
     * The alias that points to an "unsafe" property or method
     *
     * @readonly
     *
     * @type {PropertyKey}
     */
    readonly alias: PropertyKey;
    /**
     * The "unsafe" property or method that an alias points to
     *
     * @readonly
     *
     * @type {PropertyKey}
     */
    readonly key: PropertyKey;
}

/**
 * Support Concerns identifier
 *
 * @type {Symbol}
 */
declare const SUPPORT_CONCERNS: unique symbol;
/**
 * Symbol used by a [concern class]{@link ConcernConstructor} to indicate what properties
 * and methods can be aliased into a target class.
 *
 * **Note**: _Symbol MUST be used to as name for a "static" method in the desired Concern class._
 *
 * **Example**:
 * ```ts
 * class MyConcern implements Concern
 * {
 *      static [PROVIDES](): PropertyKey[]
 *      {
 *          // ...not shown...
 *      }
 *
 *      // ...remaining not shown...
 * }
 * ```
 *
 * @see ConcernConstructor
 *
 * @type {symbol}
 */
declare const PROVIDES: unique symbol;
/**
 * Symbol that can be used by a [concern class]{@link ConcernConstructor} to perform
 * pre-registration logic
 *
 * @see RegistrationAware
 * @see ConcernConstructor
 *
 * @type {symbol}
 */
declare const BEFORE: unique symbol;
/**
 * Symbol that can be used by a [concern class]{@link ConcernConstructor} to perform
 * post-registration logic.
 *
 * @see RegistrationAware
 * @see ConcernConstructor
 *
 * @type {symbol}
 */
declare const AFTER: unique symbol;
/**
 * Symbol used to define a list of the concern classes to be used by a target class.
 *
 * @see {UsesConcerns}
 *
 * @type {Symbol}
 */
declare const CONCERN_CLASSES: unique symbol;
/**
 * Symbol used to list the aliases applied in a target class
 *
 * @see {UsesConcerns}
 *
 * @type {Symbol}
 */
declare const ALIASES: unique symbol;
/**
 * Symbol used to define a "concerns container" property inside a target class' prototype
 *
 * @see {Owner}
 * @see {Container}
 *
 * @type {Symbol}
 */
declare const CONCERNS: unique symbol;

export { AFTER, ALIASES, type Alias, type AliasConflictException, type AliasDescriptorFactory, type Aliases, type AlreadyRegisteredException, BEFORE, type BootException, CONCERNS, CONCERN_CLASSES, type Concern, type ConcernConstructor, type ConcernException, type Configuration, type Container, type DescriptorsRepository, type Factory, type InjectionException, type Injector, type NotRegisteredException, type Owner, PROVIDES, type RegistrationAware, SUPPORT_CONCERNS, type ShorthandConfiguration, type UnsafeAliasException, type UsesConcerns };
