import { CaminoValid, isNotNullNorUndefined } from 'camino-common/src/typescript-tools'
import { CaminoError, CaminoZodErrorReadableMessage, translateIssue } from 'camino-common/src/zod-tools'
import { Cause, Effect, Exit, Option, pipe } from 'effect'
import { ZodTypeAny } from 'zod'
import { fromError, isZodErrorLike } from 'zod-validation-error'

/**
 * @deprecated use more precise message
 */
export type ZodUnparseable = 'Problème de validation de données'

/**
 * @deprecated use zodParseEffectTyped
 */
export const zodParseEffectCallback =
  <T extends ZodTypeAny>(validator: T) =>
  (value: unknown): Effect.Effect<T['_output'], CaminoError<ZodUnparseable>> =>
    zodParseEffect(validator, value)

const zodErrorToReadableMessage = (myError: unknown) => fromError(myError).toString() as CaminoZodErrorReadableMessage

const zodErrorToDetail = (myError: unknown): string | undefined => {
  if (isZodErrorLike(myError)) {
    return translateIssue(myError.errors[0])
  }

  return undefined
}

/**
 * @deprecated use zodParseEffectTyped
 */
export const zodParseEffect = <T extends ZodTypeAny>(validator: T, item: unknown): Effect.Effect<T['_output'], CaminoError<ZodUnparseable>> => {
  return Effect.try({
    try: () => validator.parse(item),
    catch: myError => ({ message: 'Problème de validation de données', detail: zodErrorToDetail(myError), zodErrorReadableMessage: zodErrorToReadableMessage(myError) }),
  })
}

export const zodParseEffectTyped = <T extends ZodTypeAny, U extends string>(validator: T, item: T['_output'], errorMessage: U, detail?: string): Effect.Effect<T['_output'], CaminoError<U>> => {
  return Effect.try({
    try: () => validator.parse(item),
    catch: myError => ({ message: errorMessage, detail: isNotNullNorUndefined(detail) ? detail : zodErrorToDetail(myError), zodErrorReadableMessage: zodErrorToReadableMessage(myError) }),
  })
}

export const callAndExit = async <A>(toCall: Effect.Effect<A, CaminoError<string>, never>): Promise<A> => {
  const pipeline = await pipe(toCall, Effect.runPromiseExit)

  if (Exit.isSuccess(pipeline)) {
    return pipeline.value
  } else {
    if (Cause.isFailType(pipeline.cause)) {
      console.error(pipeline.cause.error)
      let errorMessage = pipeline.cause.error.message
      if (isNotNullNorUndefined(pipeline.cause.error.extra)) {
        errorMessage += `\n extra: ${pipeline.cause.error.extra}`
      }
      if (isNotNullNorUndefined(pipeline.cause.error.detail)) {
        errorMessage += `\n detail: ${pipeline.cause.error.detail}`
      }
      if (isNotNullNorUndefined(pipeline.cause.error.zodErrorReadableMessage)) {
        errorMessage += `\n zod: ${pipeline.cause.error.zodErrorReadableMessage}`
      }
      throw new Error(errorMessage)
    } else {
      throw new Error(`Unexpected error ${pipeline.cause}`)
    }
  }
}

export const shortCircuitError = <T extends string>(value: T, ...stuff: unknown[]): { _tag: T } => {
  console.debug(`shortCircuit ${value}`, ...stuff)
  return { _tag: value }
}

export const filterOrFailFromValid = <T extends string>(value: CaminoValid<T>): Effect.Effect<void, CaminoError<T>> =>
  value.valid ? Effect.succeed(Option.none) : Effect.fail({ message: value.errors[0], detail: value.errors.join(', ') })

export const filterOrFailFromValidWithError = <ErrorMessage extends string>(value: CaminoValid<string>, errorMessage: ErrorMessage): Effect.Effect<void, CaminoError<ErrorMessage>> =>
  value.valid ? Effect.succeed(Option.none) : Effect.fail({ message: errorMessage, detail: value.errors.join(', ') })
