import type { DataSource, RequestBase, WorkerAction } from './squared';

import type { IExternalConfig, IExternalFunction, IHost, IModule, ModuleConstructor } from './index';
import type { QueryResult, TimeoutAction } from './db';
import type { StatusType } from '@e-mc/types/lib/logger';
import type { Settings } from './node';
import type { ClientDbSettings, ClientModule, DbCacheValue, DbCoerceSettings, DbSourceOptions } from './settings';

import type { Transferable, Worker, WorkerOptions } from 'node:worker_threads';

import type * as EventEmitter from 'node:events';

export interface IdentifierAction {
    uuidKey?: string;
}

export interface AddEventListenerOptions {
    capture?: boolean;
    once?: boolean;
    passive?: boolean;
    signal?: AbortSignal;
}

export interface IClient<T extends IHost, U extends ClientModule, V extends FunctionType<any, any> = FunctionType> extends IModule<T>, IExternalConfig<U, U extends ClientModule<infer W> ? W : unknown>, IExternalFunction<V> {
    init(...args: unknown[]): this;
    getUserSettings<X>(): X | null;
    set cacheDir(value: string);
    get cacheDir(): string;
}

export interface ClientConstructor<T extends IHost = IHost, U extends ClientModule = ClientModule> extends ModuleConstructor {
    readonly prototype: IClient<T, U>;
    new(module?: U): IClient<T, U>;
}

export interface IClientDb<T extends IHost, U extends ClientModule<ClientDbSettings>, V extends DataSource = DataSource, W extends DbSourceOptions = DbSourceOptions, X extends DbCoerceSettings = DbCoerceSettings> extends IClient<T, U> {
    database: V[];
    cacheExpires: number;
    add(item: V, state?: number): void;
    hasCache(source: string, sessionKey?: string): boolean;
    hasCoerce(source: string, component: keyof DbCoerceSettings, uuidKey: string | undefined): boolean;
    hasCoerce(source: string, component: keyof DbCoerceSettings, credential?: unknown): boolean;
    getQueryResult(source: string, credential: unknown, queryString: string, options: CacheOptions): QueryResult | undefined;
    getQueryResult(source: string, credential: unknown, queryString: string, renewCache: boolean): QueryResult | undefined;
    getQueryResult(source: string, credential: unknown, queryString: string, sessionKey?: string | CacheOptions, renewCache?: boolean): QueryResult | undefined;
    setQueryResult(source: string, credential: unknown, queryString: string, result: unknown, options: CacheOptions): QueryResult;
    setQueryResult(source: string, credential: unknown, queryString: string, result: unknown, sessionKey?: string | CacheOptions): QueryResult;
    getCacheResult(source: string, credential: unknown, queryString: string, cacheValue: CacheOptions, ignoreCache?: IntBool | FirstOf<number, number>): QueryResult | undefined;
    applyState(items: V | V[], value: number, as?: boolean): void;
    commit(items?: V[]): Promise<boolean>;
    valueOfKey(credential: unknown, name: keyof W, component?: keyof X): unknown;
    settingsOf(source: string, name: keyof W, component?: keyof X): unknown;
    settingsKey(uuidKey: string, name: keyof W, component?: keyof X): unknown;
    get pending(): V[];
    get committed(): V[];
    get failed(): V[];
}

export interface ClientDbConstructor<T extends IHost = IHost, U extends ClientModule = ClientModule, V extends DataSource = DataSource> extends ClientConstructor<T, U> {
    STORE_RESULT_PARTITION_SIZE: number;
    STORE_RESULT_PARTITION_MULT: number;
    readonly TRANSACTION_ACTIVE: number;
    readonly TRANSACTION_PARTIAL: number;
    readonly TRANSACTION_COMMIT: number;
    readonly TRANSACTION_TERMINATE: number;
    readonly TRANSACTION_ABORT: number;
    readonly TRANSACTION_FAIL: number;
    loadSettings(settings: Pick<Settings, "process" | "memory">, password?: string): boolean;
    getTimeout(value: number | string | TimeoutAction | undefined): number;
    convertTime(value: number | string): number;
    findResult(source: string, credential: unknown, queryString: string, timeout: number, renewCache: boolean): QueryResult | undefined;
    findResult(source: string, credential: unknown, queryString: string, timeout: number, sessionKey?: string | boolean, renewCache?: boolean): QueryResult | undefined;
    storeResult(source: string, credential: unknown, queryString: string, result: QueryResult, options: StoreResultOptions): QueryResult;
    /** @deprecated */
    storeResult(source: string, credential: unknown, queryString: string, result: QueryResult, sessionKey: string, sessionExpires: number): QueryResult;
    storeResult(source: string, credential: unknown, queryString: string, result: QueryResult, cache: DbCacheValue): QueryResult;
    storeResult(source: string, credential: unknown, queryString: string, result: QueryResult, cache: DbCacheValue | undefined, options: Omit<StoreResultOptions, "cache">): QueryResult;
    findSession<W = unknown>(source: string, user: string, key: string): W | undefined;
    storeSession(source: string, user: string, key: string, result: unknown, expires?: number): void;
    purgeResult(prefix?: string, lru?: boolean | number): Promise<number>;
    extractUUID(credential: unknown): string;
    setPoolConfig(value: unknown): void;
    getPoolConfig(source: string): unknown;
    keyOfResult(source: string, credential: unknown, uuidOnly?: boolean): string;
    readonly prototype: IClientDb<T, U, V>;
    new(module?: U, database?: V[]): IClientDb<T, U, V>;
}

export interface IWorkerGroup extends WorkerBase {
    [Symbol.iterator](): IterableIterator<IWorkerChannel>;
    add(name: string, item: IWorkerChannel, priority?: number): this;
    get(name: string, force?: boolean | number): IWorkerChannel | undefined;
    delete(name: string | IWorkerChannel): boolean;
    free(count?: number): number;
    print(format: "stats" | "errors"): void;
    clear(): void;
    set max(value);
    get max(): number;
    get workers(): IWorkerChannel[];
    get pending(): number;
    get available(): number;
    get errors(): WorkerChannelError[];
    get size(): number;
    get sizeOf(): number;
}

export interface WorkerGroupConstructor {
    checkTimeout(value: number, active?: boolean): number;
    readonly prototype: IWorkerGroup;
    new(max?: number, locked?: boolean): IWorkerGroup;
}

export interface IWorkerChannel<T = unknown> extends EventEmitter, Iterable<Worker>, WorkerInfo {
    [Symbol.iterator](): IterableIterator<Worker>;
    sendObject(data: unknown, transferList?: Transferable[], callback?: WorkerChannelResponse<T>, ...returnArgs: unknown[]): Worker;
    sendBuffer(data: Buffer | SharedArrayBuffer, callback: WorkerChannelResponse<T>, ...returnArgs: unknown[]): Worker | null;
    sendBuffer(data: Buffer | SharedArrayBuffer, shared?: boolean, callback?: WorkerChannelResponse<T>, ...returnArgs: unknown[]): Worker | null;
    send(data: unknown, transferList?: Transferable[]): Promise<T>;
    drop(count?: number): number;
    join(group: IWorkerGroup, label?: string): void;
    quit(): void;
    kill(count?: number): Promise<number>;
    lock(): void;
    addLog(err: unknown, type?: StatusType): void;
    isEmpty(): boolean;
    set min(value);
    get min(): number;
    set max(value);
    get max(): number;
    set idleTimeout(value);
    get idleTimeout(): number;
    get filename(): string;
    get workers(): Worker[];
    get detached(): boolean;
    get lastAccessed(): Date;
    get timesAccessed(): number;
    get frequencyAccessed(): number;
    get pending(): number;
    get available(): number;
    get errors(): WorkerChannelError[];
    get size(): number;

    /* EventEmitter */
    on(event: "error" | "messageerror" | "abort", listener: (err: Error) => void): this;
    on(event: "exit", listener: (exitCode: number) => void): this;
    on(event: "online", listener: () => void): this;
    on(event: "message", listener: (value: any) => void): this;
    on(event: "data", listener: (data: T) => void): this;
    on(event: "pass", listener: (data: unknown, transferList: Transferable[] | undefined) => void): this;
    once(event: "error" | "messageerror" | "abort", listener: (err: Error) => void): this;
    once(event: "exit", listener: (exitCode: number) => void): this;
    once(event: "online", listener: () => void): this;
    once(event: "message", listener: (value: any) => void): this;
    once(event: "data", listener: (data: T) => void): this;
    once(event: "pass", listener: (data: unknown, transferList: Transferable[] | undefined) => void): this;
}

export interface WorkerChannelConstructor<T = unknown> {
    create<U>(filename: string, name: string): IWorkerChannel<U>;
    create<U>(filename: string, options?: WorkerOptions, name?: string): IWorkerChannel<U>;
    hasPermission(options?: WorkerAction): boolean;
    readonly prototype: IWorkerChannel<T>;
    new(filename: string | URL, options?: WorkerOptions, init?: WorkerChannelInit): IWorkerChannel<T>;
}

export interface WorkerBase {
    max: number;
    readonly pending: number;
    readonly available: number;
    readonly errors: WorkerChannelError[];
    readonly size: number;
}

export interface WorkerInfo extends WorkerBase {
    min: number;
    readonly filename: string;
    readonly workers: Worker[];
    readonly detached: boolean;
    readonly idleTimeout: number;
    readonly lastAccessed: Date;
    readonly timesAccessed: number;
    readonly frequencyAccessed: number;

}

export interface WorkerChannelInit {
    max?: number;
    idleTimeout?: number;
    verbose?: boolean;
}

export interface WorkerChannelError {
    timeStamp: Date;
    type: StatusType;
    value: unknown;
    label?: string;
}

export interface IAbortComponent extends AbortController {
    reset(): void;
    get aborted(): boolean;
}

export interface AbortComponentConstructor {
    attach(instance: IAbortComponent, signal: AbortSignal, options?: AddEventListenerOptions): void;
    detach(instance: IAbortComponent, signal: AbortSignal): void;
    readonly prototype: IAbortComponent;
    new(): IAbortComponent;
}

export interface IPermission {
    setDiskRead(enabled: boolean): void;
    setDiskRead(pathname?: string | string[], enabled?: boolean): void;
    setDiskWrite(enabled: boolean): void;
    setDiskWrite(pathname?: string | string[], enabled?: boolean): void;
    setUNCRead(enabled: boolean): void;
    setUNCRead(pathname?: string | string[], enabled?: boolean): void;
    setUNCWrite(enabled: boolean): void;
    setUNCWrite(pathname?: string | string[], enabled?: boolean): void;
    getDiskRead(): string | string[];
    getDiskWrite(): string | string[];
    getUNCRead(): string | string[];
    getUNCWrite(): string | string[];
    hasDiskRead(pathname: string): boolean;
    hasDiskWrite(pathname: string): boolean;
    hasUNCRead(pathname: string): boolean;
    hasUNCWrite(pathname: string): boolean;
    get diskRead(): boolean;
    get diskWrite(): boolean;
    get uncRead(): boolean;
    get uncWrite(): boolean;
}

export interface PermissionConstructor {
    create(settings: PermittedDirectories, freeze?: boolean): IPermission | null;
    create(settings: PermittedDirectories, parent: IPermission | null, freeze?: boolean): IPermission | null;
    validate(settings: unknown): settings is PermittedDirectories;
    clone(permission: IPermission, freeze?: boolean): IPermission;
    match(pathname: string, pattern: string | string[]): boolean;
    toPosix<T>(value: T): T extends Array<unknown> ? string | string[] : string;
    readonly prototype: IPermission;
    new(freeze?: boolean): IPermission;
}

export interface PermissionReadWrite<T = boolean> {
    disk_read?: T;
    disk_write?: T;
    unc_read?: T;
    unc_write?: T;
}

export interface PermissionAction {
    permission?: PermittedDirectories;
}

export interface HostInitConfig<T extends HostInitLog = HostInitLog> extends PlainObject, RequestBase<T> {
    username?: string;
    remoteIp?: string;
    ignoreModules?: string[];
}

export interface HostInitLog {
    enabled?: boolean;
    level?: number | string;
    exclude?: string | string[];
    useColor?: boolean;
    silent?: boolean;
    showProgress?: boolean;
}

export interface PermittedDirectories extends PermissionReadWrite<string | string[]> {
    inherit?: boolean;
}

export interface StoreResultOptions {
    cache?: DbCacheValue;
    cacheDir?: string;
    sessionKey?: string;
    sessionExpires?: number;
}

export interface CacheOptions {
    value?: DbCacheValue;
    renewCache?: boolean;
    exclusiveOf?: FirstOf<number, number>;
    sessionKey?: string;
}

export interface ThreadInfo {
    pid: number;
    startTime: number;
    username?: string;
    sessionId?: string;
    broadcastId?: string | string[];
}

export interface ThreadCountStat {
    count: number;
    pending: number;
    opened?: number;
    closed?: number;
    queued?: number;
    rejected?: number;
    killed?: number;
    startTime?: number;
    previousTime?: number;
    max?: number;
    admin?: ThreadInfo[];
}

export interface JoinQueueOptions {
    args?: unknown[];
    reject?: boolean;
}

export interface ResumeThreadOptions {
    args: unknown[];
    startTime: number;
    aborted?: boolean;
}

export interface WorkerMessage<T = PlainObject, U = Buffer> {
    data: U;
    options?: T;
}

export type WorkerChannelResponse<T = unknown> = (result: T, ...args: unknown[]) => void;