import type { Increment } from '../GracefulRecursion.d.ts';
import type { InstanceOrStatic, Integer } from '../miscTypes.ts';
import { getOwnPropertyDescriptors } from './getOwnPropertyDescriptors.ts';
import { getPrototypesChainOf } from './getPrototypeChainOf.ts';
import type {
  BaseClass,
  BaseClassProto as BaseClassPrototype,
  ClassLike,
  ConstructorConstraint,
  ConstructorLike,
  ProtoChainOfClass as PrototypeChainOfClass,
  SuperClassLike,
  WithProto as WithPrototype,
} from './inheritance.ts';
import type { InstanceTypeOrSelfPropertyDescriptorMap } from './InstanceTypeOrSelfPropertyDescriptorMap.d.ts';
import type { OwnPropertyDescriptorMap } from './OwnPropertyDescriptorMap.d.ts';
/**
 * JSDoc type imports
 */
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { getPrototypeOf as _getPrototypeOf } from './getPrototypeOf.ts';
import type { listOwnGetters as _listOwnGetters } from './listOwnGetters.ts';
import type { InstancePropertyDescriptorMap as _InstancePropertyDescriptorMap } from './InstancePropertyDescriptorMap.d.ts';
/* eslint-enable @typescript-eslint/no-unused-vars */

/**
 * A conditional wrapper for {@link InstanceTypeOrSelfPropertyDescriptorMap} and
 * {@link OwnPropertyDescriptorMap}.
 *
 * If {@link _InstanceOrStatic} is 'Instance', {@link InstanceTypeOrSelfPropertyDescriptorMap}.\
 * Else if {@link _InstanceOrStatic} is 'Static',
 * {@link OwnPropertyDescriptorMap }.\
 * Else, `never`.
 * @template {ConstructorConstraint<Class> & WithProto<SuperClassLike | BaseClassProto>} Class
 * A class definition cast to {@link ClassLike}.
 * @template {InstanceOrStatic} _InstanceOrStatic
 * 'Instance' or 'Static'. Determines the return type.
 * @since 3.0.0
 */
export type RecursedPropertyDescriptorMap<
  Class extends ConstructorConstraint<Class> & WithPrototype<SuperClassLike | BaseClassPrototype>,
  _InstanceOrStatic extends InstanceOrStatic,
> = [_InstanceOrStatic] extends ['Instance'] ? InstanceTypeOrSelfPropertyDescriptorMap<Class, Class['__proto__']>
  : [_InstanceOrStatic] extends ['Static'] ? OwnPropertyDescriptorMap<Class>
      : never;

/**
 * An
 * ordered array of {@link OwnPropertyDescriptorMap} or
 * {@link InstancePropertyDescriptorMap} starting with {@link classDefinition}
 * and ending with a {@link BaseClass}.
 * @template {ConstructorConstraint<Class> & WithProto<SuperClassLike | BaseClassProto>} Class
 * @template {'Instance' | 'Static'} InstanceOrStatic
 * @template {Integer<number>} [Limit=16]
 * @template {Integer<number>} [CurrentLevel=0]
 */
export type RecursedPropertyDescriptorMapArray<
  Class extends ConstructorConstraint<Class> & WithPrototype<SuperClassLike | BaseClassPrototype>,
  InstanceOrStatic extends 'Instance' | 'Static',
  Limit extends Integer<number> = 16,
  CurrentLevel extends Integer<number> = 0,
> = Class['__proto__'] extends BaseClassPrototype['__proto__'] | BaseClassPrototype['__proto__']['__proto__'] ? never
  : Class['__proto__'] extends BaseClassPrototype ? [RecursedPropertyDescriptorMap<BaseClass<Class>, InstanceOrStatic>]
    : Class['__proto__'] extends ConstructorLike<Class['__proto__']>
      ? [
          RecursedPropertyDescriptorMap<Class, InstanceOrStatic>,
          ...RecursedPropertyDescriptorMapArray<
            ClassLike<Exclude<Class['__proto__'], BaseClassPrototype> & WithPrototype<SuperClassLike | BaseClassPrototype>>,
            InstanceOrStatic,
            Limit,
            Increment<CurrentLevel>
          >,
        ]
      : never;
/**
 * Walks the class inheritance chain to get a PropertyDescriptorMap of each class.
 *
 * While you _can_ use this to get an array of getter names/keys, you should use {@link getPrototypeOf} and {@link listOwnGetters}, instead!
 * @template Class Any class definition type-cast to {@link ClassLike}
 * @template _InstanceOrStatic
 * @param classDefinition Any class definition type-cast to {@link ClassLike}
 * @param instanceOrStatic 'Instance' or 'Static'. Determines the return type.
 * @returns An
 * ordered array of {@link OwnPropertyDescriptorMap} or
 * {@link InstancePropertyDescriptorMap} starting with {@link classDefinition}
 * and ending with a {@link BaseClass}.
 * @since 3.0.0
 */
export function getOwnPropertyDescriptorsRecursively<
  // Class extends ClassLike<ConstructorConstraint<Class> & WithProto<SuperClassLike | BaseClassProto>>,
  Class extends ClassLike<ConstructorConstraint<Class> & WithPrototype<SuperClassLike | BaseClassPrototype>>,
  _InstanceOrStatic extends InstanceOrStatic,
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
  Limit extends Integer<number> = 16,
>(
  classDefinition: Class,
  instanceOrStatic: _InstanceOrStatic,
): RecursedPropertyDescriptorMapArray<Class, _InstanceOrStatic> {
  const staticPrototypeChain: PrototypeChainOfClass<Class> = getPrototypesChainOf(classDefinition, 'classes');
  if (isSingleTuple(staticPrototypeChain)) {
    return [
      getOwnPropertyDescriptors(
        staticPrototypeChain[0],
        instanceOrStatic,
      ) as RecursedPropertyDescriptorMap<Class, _InstanceOrStatic>,
    ] as unknown as RecursedPropertyDescriptorMapArray<Class, _InstanceOrStatic, Limit>;
  }

  const recursedPropertyDescriptorMapArray = staticPrototypeChain.map(classDefinition =>
    getOwnPropertyDescriptors(
      classDefinition,
      instanceOrStatic,
    ),
  );

  return recursedPropertyDescriptorMapArray as RecursedPropertyDescriptorMapArray<Class, _InstanceOrStatic>;
}

/**
 * Simple utility for single-tuple type assertion
 * @template {T} T T in T[]
 * @param array an array
 * @returns array is [T]
 */
function isSingleTuple<T>(array: T[]): array is [T] {
  return Array.isArray(array) && array.length === 1;
}
