import type { AbiParameter } from 'abitype'
import type { Narrow } from 'abitype'
import type { Error, Filter } from './types.js'
import { InvalidAbiParameterError } from './errors.js'
import { isStructSignature, modifiers } from './runtime/signatures.js'
import { parseStructs } from './runtime/structs.js'
import { parseAbiParameter as parseAbiParameter_ } from './runtime/utils.js'
import type { IsStructSignature, Modifier } from './types/signatures.js'
import type { ParseStructs } from './types/structs.js'
import type { ParseAbiParameter as ParseAbiParameter_ } from './types/utils.js'

/**
 * Parses human-readable ABI parameter into `AbiParameter`.
 *
 * @param param - Human-readable ABI parameter
 * @returns Parsed `AbiParameter`.
 */
export type ParseAbiParameter<
  param extends string | readonly string[] | readonly unknown[],
> =
  | (param extends string
      ? param extends ''
        ? never
        : string extends param
          ? AbiParameter
          : ParseAbiParameter_<param, { modifier: Modifier }>
      : never)
  | (param extends readonly string[]
      ? string[] extends param
        ? AbiParameter // Return generic AbiParameter item since type was no inferrable
        : ParseStructs<param> extends infer structs
          ? {
              [key in keyof param]: param[key] extends string
                ? IsStructSignature<param[key]> extends true
                  ? never
                  : ParseAbiParameter_<
                      param[key],
                      { modifier: Modifier; structs: structs }
                    >
                : never
            } extends infer mapped extends readonly unknown[]
            ? Filter<mapped, never>[0] extends infer result
              ? result extends undefined
                ? never
                : result
              : never
            : never
          : never
      : never)

/**
 * Parses human-readable ABI parameter into `AbiParameter`.
 *
 * @param param - Human-readable ABI parameter
 * @returns Parsed `AbiParameter`.
 */
export function parseAbiParameter<
  param extends string | readonly string[] | readonly unknown[],
>(
  param: Narrow<param> &
    (
      | (param extends string
          ? param extends ''
            ? Error<'Empty string is not allowed.'>
            : unknown
          : never)
      | (param extends readonly string[]
          ? param extends readonly [] // empty array
            ? Error<'At least one parameter required.'>
            : string[] extends param
              ? unknown
              : unknown // TODO: Validate param string
          : never)
    ),
): ParseAbiParameter<param> {
  let abiParameter: AbiParameter | undefined
  if (typeof param === 'string')
    abiParameter = parseAbiParameter_(param, {
      modifiers,
    }) as ParseAbiParameter<param>
  else {
    const structs = parseStructs(param as readonly string[])
    const length = param.length as number
    for (let i = 0; i < length; i++) {
      const signature = (param as readonly string[])[i]!
      if (isStructSignature(signature)) continue
      abiParameter = parseAbiParameter_(signature, { modifiers, structs })
      break
    }
  }

  if (!abiParameter) throw new InvalidAbiParameterError({ param })

  return abiParameter as ParseAbiParameter<param>
}
