import { ChildProcess, Serializable as ChildSerializable } from 'child_process';
import { WorkerThreadOptions } from './classes/worker';
import { ClusterManager } from './core/clusterManager';
import { ChildProcessOptions } from './classes/child';
import { ClusterClient } from './core/clusterClient';
import { ProcessMessage } from './other/message';
import { Cluster } from './core/cluster';
import { Worker } from 'worker_threads';

/** Default options for fetching the bot gateway. */
export const DefaultOptions = {
	http: {
		api: 'https://discord.com/api',
		version: '10',
	},
};

/** Endpoints for the discord api. */
export const Endpoints = {
	botGateway: '/gateway/bot',
};

/** The types of data that can be sent. */
export enum MessageTypes {
	'MissingType' = 0,
	'CustomRequest' = 1,
	'CustomMessage' = 2,
	'CustomReply' = 3,
	'Heartbeat' = 4,
	'HeartbeatAck' = 5,
	'ClientBroadcast' = 6,
	'ClientBroadcastRequest' = 7,
	'ClientBroadcastResponse' = 8,
	'ClientBroadcastResponseError' = 9,
	'ClientRespawn' = 10,
	'ClientRespawnAll' = 11,
	'ClientSpawnNextCluster' = 16,
	'ClientReady' = 17,
	'ClientEvalRequest' = 18,
	'ClientEvalResponse' = 19,
	'ClientEvalResponseError' = 20,
	'ClientManagerEvalRequest' = 21,
	'ClientManagerEvalResponse' = 22,
	'ClientManagerEvalResponseError' = 23,
	'ManagerReady' = 24,
	'Kill' = 25,
	'ClientRespawnSpecific' = 26,
}

/** Recursive array of strings. */
export type RecursiveStringArray = (RecursiveStringArray | string)[];
/** Supported library packages. */
export type PackageType = 'discord.js' | '@discordjs/core';

/** Awaitable type. */
export type Awaitable<T> = T | PromiseLike<T>;
/** Mode for clustering. */
export type ClusteringMode = 'worker' | 'process';
/** Any function. */
export type UnknownFunction = (...args: unknown[]) => unknown;
/** Data for restart sysytem. */
export type HeartbeatData = { restarts: number; missedBeats: number; killing: boolean; };
/** Type that removes null and undefined from a type. */
export type DeepNonNullable<T> = T extends NonNullable<T> ? T : DeepNonNullable<NonNullable<T>>;
/** Check for function's outputs. */
export type ValidIfSerializable<T> = T extends NonNullable<Serializable> ? (T | undefined) : never;
/** Check if input is serializable. */
export type SerializableInput<T, U = false> = T extends Serializable ? T : T extends unknown ? U : never;
/** Output of guild function parser. */
export type DeconstructedFunction = { args: (string | string[])[], body: string, wrapScope: boolean, wrapArgs: boolean; isAsync: boolean; };
/** Any object or data. */
export type Serializable = string | number | boolean | null | undefined | Serializable[] | { [key: string]: Serializable } | object | ChildSerializable;
/** Already serialized data. */
export type Serialized<T> = T extends symbol | bigint | UnknownFunction ? never : T extends ValidIfSerializable<T> ? T : (T extends { toJSON(): infer R } ? R : T extends ReadonlyArray<infer V> ? Serialized<V>[] : (T extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> ? object : (T extends object ? { [K in keyof T]: Serialized<T[K]> } : T)));

/** Options for the cluster manager. */
export interface ClusterManagerCreateOptions<T extends ClusteringMode> {
	/** What mode to use for clustering. */
	mode?: T;
	/** The token of the discord bot. */
	token?: string;
	/** Number of total internal shards or -1. */
	totalShards?: number;
	/** Number of total Clusters/Process to spawn. */
	totalClusters?: number;
	/** Number of shards per cluster. */
	shardsPerClusters?: number;
	/** Arguments to pass to the clustered script when spawning (only available when using the `process` mode). */
	shardArgs?: string[];
	/** Arguments to pass to the clustered script executable when spawning. */
	execArgv?: string[];
	/** Whether clusters should automatically respawn upon exiting. */
	respawn?: boolean;
	/** Whether a cluster should acknowledge heartbeat while the internal client is not ready. Defaults to `true`. */
	respondToHeartbeatWhenNotReady?: boolean;
	/** Heartbeat options. */
	heartbeat?: ClusterHeartbeatOptions;
	/** Control the Spawn Queue. */
	queueOptions?: QueueOptions;
	/** Options to pass to the spawn, respawn method. */
	spawnOptions?: ClusterSpawnOptions;
	/** Data, which is passed to the Cluster. */
	clusterData?: object;
	/** Options, which is passed when forking a child or creating a thread. */
	clusterOptions?: T extends 'worker' ? WorkerThreadOptions : ChildProcessOptions;
	/** Advanced. */
	advanced?: Partial<ClusterManagerAdvancedOptions>;
}

/** Options for the cluster manager. */
export interface ClusterManagerOptions<T extends ClusteringMode> extends ClusterManagerCreateOptions<T> {
	/** Which mode to use for clustering. */
	mode: T;
	/** Number of total internal shards or -1. */
	totalShards: number;
	/** Number of total Clusters/Process to spawn. */
	totalClusters: number;
	/** Number of shards per cluster. */
	shardsPerClusters: number;
	/** Whether a cluster should acknowledge heartbeat while the internal client is not ready. */
	respondToHeartbeatWhenNotReady: boolean;
	/** An Array of Internal Shards Ids, which should get spawned. */
	shardList: number[];
	/** An Array of Ids to assign to the spawned Clusters, when the default id scheme is not wanted. */
	clusterList: number[];
	/** Options to pass to the spawn, respawn method. */
	spawnOptions: Required<ClusterSpawnOptions>;
	/** Heartbeat options. */
	heartbeat: Required<ClusterHeartbeatOptions>;
	/** Package type. */
	packageType: PackageType | null;
}

/** Advanced options for cluster manager. */
export interface ClusterManagerAdvancedOptions {
	/** Whether to spam when debugging. */
	logMessagesInDebug: boolean;
	/** Whether to ignore dead clusters, WARNING: If you have null-checks in your code, this might false trigger that whatever you want to do results in null (ei. getting guild from cluster that is dead). */
	proceedBroadcastIfClusterDead: boolean;
}

/** Data of ClusterClient. */
export interface ClusterClientData {
	/** List of shards that are assigned to this cluster. */
	ShardList: number[];
	/** The total amount of shards. */
	TotalShards: number;
	/** The total amount of clusters. */
	ClusterCount: number;
	/** The id of the cluster. */
	ClusterId: number;
	/** Mode of the manager. */
	ClusterManagerMode: ClusteringMode;
	/** Mode of the queue. */
	ClusterQueueMode?: 'auto' | 'manual';
	/** Whether the client should acknowledge heartbeat while not ready. */
	RespondToHeartbeatWhenNotReady: boolean;
	/** First shard id of the cluster. */
	FirstShardId: number;
	/** Last shard id of the cluster. */
	LastShardId: number;
}

/** Spawn options for the cluster. */
export interface ClusterSpawnOptions {
	/** How long to wait between spawning each cluster. */
	delay?: number;
	/** How long to wait for a cluster to become ready before killing it and retrying. */
	timeout?: number;
}

/** Data for the heartbeat system. */
export interface ClusterHeartbeatOptions {
	/** Whether the heartbeat system is enabled. */
	enabled: boolean;
	/** Maximum amount of missed heartbeats a cluster can have in the interval. */
	maxMissedHeartbeats?: number;
	/** Maximum amount of restarts a cluster can have in the interval. */
	maxRestarts?: number;
	/** Interval in milliseconds between each heartbeat. */
	interval?: number;
	/** Timeout in milliseconds after which a cluster will be considered as unresponsive. */
	timeout?: number;
}

/** Options for the queue. */
export interface QueueOptions {
	/** Whether the spawn queue be automatically managed. */
	mode?: 'auto' | 'manual';
	/** Time to wait until next item. */
	timeout?: number;
}

/** Kill options for the cluster. */
export interface ClusterKillOptions {
	/** The reason for killing the cluster. */
	reason: string;
}

/** Eval options for the cluster. */
export interface EvalOptions<T extends object = object> {
	/** Only run the script in a single cluster or set of clusters. */
	cluster?: number | number[];
	/** On what shard to run the script. */
	shard?: number | number[];
	/** On what guild to run the script. */
	guildId?: string;
	/** Context to use for the script. */
	context?: T;
	/** Timeout before the script is cancelled. */
	timeout?: number;
	/** Whether to continue running the script even if one of the clusters returns an error. */
	useAllSettled?: boolean;
}

/** Mode for reclustering. */
export type ReClusterRestartMode = 'gracefulSwitch' | 'rolling';

/** Options for reclustering. */
export interface ReClusterOptions {
	/** The new totalShards of the bot. */
	totalShards?: number;
	/** The amount of totalClusters to spread the shards over all clusters. */
	totalClusters?: number;
	/** The amount of shards per cluster. */
	shardsPerClusters?: number;

	/** The restartMode of the clusterManager, gracefulSwitch = waits until all new clusters have spawned, rolling = once the Cluster is ready, the old cluster will be killed. */
	restartMode?: ReClusterRestartMode;
}

/** Options for storing promises. */
export interface StoredPromise {
	/** Timeout before promise is canceled. */
	timeout?: NodeJS.Timeout;

	/** Resolves the promise. */
	resolve(value: unknown): void;
	/** Return an error if failed. */
	reject(error: Error): void;
}

/** Events that manager emits. */
export interface ClusterManagerEvents {
	/** Emits when client sends a request via IPC. */
	clientRequest: [message: ProcessMessage];
	/** Emits when cluster is created. */
	clusterCreate: [cluster: Cluster];
	/** Emits when cluster is ready. */
	clusterReady: [cluster: Cluster];
	/** Emits when any message is sent from IPC. */
	message: [message: ProcessMessage];
	/** Debug events. */
	debug: [debugMessage: string];
	/** When all manager's clsuters are ready. */
	ready: [manager: ClusterManager];
}

/** Events that cluster emits. */
export interface ClusterEvents {
	/** Emits when any message is sent from IPC. */
	message: [message: ProcessMessage];
	/** Emits when cluster dies. */
	death: [cluster: Cluster, thread: ChildProcess | Worker | null];
	/** Emits when cluster is spawned. */
	spawn: [cluster: Cluster, thread: ChildProcess | Worker | null];
	/** Emits when cluster is ready. */
	ready: [cluster: Cluster];
	/** Emits when debug message is sent. */
	debug: [message: string];
	/** Emits when there is an error. */
	error: [error: Error];
}

/** Events that cluster client emits. */
export interface ClusterClientEvents {
	/** Emits when all clusters are ready. */
	managerReady: [];
	/** Emits when message is sent from IPC. */
	message: [message: ProcessMessage];
	/** Emits when cluster is ready. */
	ready: [clusterClient: ClusterClient];
	/** Emits when debug message is sent. */
	debug: [message: string];
}
