import {
  EtapeTypeEtapeStatutWithMainStep,
  EtapeId,
  GetEtapeDocumentsByEtapeId,
  ETAPE_IS_NOT_BROUILLON,
  GetEtapeAvisByEtapeId,
  EtapeBrouillon,
  etapeSlugValidator,
  EtapeSlug,
  ETAPE_IS_BROUILLON,
  getStatutId,
} from 'camino-common/src/etape'
import { DemarcheId } from 'camino-common/src/demarche'
import { HTTP_STATUS } from 'camino-common/src/http'
import { CaminoDate, firstEtapeDateValidator, getCurrent } from 'camino-common/src/date'
import { titreDemarcheGet } from '../../database/queries/titres-demarches'
import { userSuper } from '../../database/user-super'
import { titreEtapeGet, titreEtapeUpdate, titreEtapeUpsert } from '../../database/queries/titres-etapes'
import { User, isBureauDEtudes, isEntreprise } from 'camino-common/src/roles'
import { canCreateEtape, canDeposeEtape, canDeleteEtape, canEditEtape } from 'camino-common/src/permissions/titres-etapes'
import { canBeBrouillon, ETAPES_TYPES, isEtapeTypeId } from 'camino-common/src/static/etapesTypes'

import { getKeys, isNonEmptyArray, isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, isNullOrUndefined, isNullOrUndefinedOrEmpty, memoize, onlyUnique } from 'camino-common/src/typescript-tools'
import { Pool } from 'pg'
import { EntrepriseDocument, EntrepriseDocumentId, EtapeEntrepriseDocument } from 'camino-common/src/entreprise'
import {
  deleteTitreEtapeEntrepriseDocument,
  getDocumentsByEtapeId,
  GetDocumentsByEtapeIdErrors,
  getEntrepriseDocumentIdsByEtapeId,
  getEtapeAvisLargeObjectIdsByEtapeId,
  GetEtapeAvisLargeObjectIdsByEtapeIdErrors,
  GetEtapeDocumentLargeObjectIdsByEtapeIdErrors,
  insertEtapeAvis,
  InsertEtapeAvisErrors,
  insertEtapeDocuments,
  InsertEtapeDocumentsErrors,
  insertTitreEtapeEntrepriseDocuments,
  updateEtapeAvis,
  UpdateEtapeAvisErrors,
  updateEtapeDocuments,
  UpdateEtapeDocumentsErrors,
} from '../../database/queries/titres-etapes.queries'
import { getEtapeDataForEdition, GetEtapeDataForEditionErrors, hasTitreFrom } from './etapes.queries'
import { SDOMZoneId } from 'camino-common/src/static/sdom'
import { titreEtapeAdministrationsEmailsSend, titreEtapeUtilisateursEmailsSend } from '../graphql/resolvers/_titre-etape-email'
import { ConvertPointsErrors, GetGeojsonInformation, GetGeojsonInformationErrorMessages, convertPoints, getGeojsonInformation } from './perimetre.queries'
import { titreEtapeUpdateTask } from '../../business/titre-etape-update'
import { getSections } from 'camino-common/src/static/titresTypes_demarchesTypes_etapesTypes/sections'
import { TitreTypeId } from 'camino-common/src/static/titresTypes'
import { titreDemarcheUpdatedEtatValidate, getPossiblesEtapesTypes } from '../../business/validations/titre-demarche-etat-validate'
import { FlattenEtape, GraphqlEtape, RestEtapeCreation, RestEtapeModification } from 'camino-common/src/etape-form'
import { ApiFlattenEtape, iTitreEtapeToFlattenEtape, TitreEtapeToFlattenEtapeErrors } from '../_format/titres-etapes'
import { CommuneId } from 'camino-common/src/static/communes'
import { titreEtapeUpdationValidate } from '../../business/validations/titre-etape-updation-validate'
import { CaminoApiError, IHeritageContenu, IHeritageProps, ITitre, ITitreDemarche, ITitreEtape } from '../../types'
import { checkEntreprisesExist, CheckEntreprisesExistErrors, getEntrepriseDocuments } from './entreprises.queries'
import { ETAPE_HERITAGE_PROPS } from 'camino-common/src/heritage'
import { titreEtapeHeritageBuild } from '../graphql/resolvers/_titre-etape'
import { KM2 } from 'camino-common/src/number'
import { FeatureMultiPolygon, FeatureCollectionPoints } from 'camino-common/src/perimetre'
import { canHaveForages } from 'camino-common/src/permissions/titres'
import { SecteursMaritimes, getSecteurMaritime } from 'camino-common/src/static/facades'
import { shortCircuitError } from '../../tools/fp-tools'
import { RestNewDeleteCall, RestNewGetCall, RestNewPostCall, RestNewPutCall } from '../../server/rest'
import { Effect, Match, Option } from 'effect'
import { EffectDbQueryAndValidateErrors } from '../../pg-database'
import { CaminoError } from 'camino-common/src/zod-tools'
import { TitreEtapeForMachine } from '../../business/rules-demarches/machine-common'
import { MachineInfo } from 'camino-common/src/machines'
import { ApiMachineInfo, demarcheEnregistrementDemandeDateFind } from '../../business/rules-demarches/machines'
import { newEtapeId } from '../../database/models/_format/id-create'

type GetEtapeEntrepriseDocumentsErrors = EffectDbQueryAndValidateErrors
export const getEtapeEntrepriseDocuments: RestNewGetCall<'/rest/etapes/:etapeId/entrepriseDocuments'> = (
  rootPipe
): Effect.Effect<EtapeEntrepriseDocument[], CaminoApiError<GetEtapeEntrepriseDocumentsErrors>> =>
  rootPipe.pipe(
    Effect.flatMap(({ pool, user, params }) => getEntrepriseDocumentIdsByEtapeId({ titre_etape_id: params.etapeId }, pool, user)),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr("Impossible d'exécuter la requête dans la base de données", 'Les données en base ne correspondent pas à ce qui est attendu', () => ({
          ...caminoError,
          status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
        })),
        Match.exhaustive
      )
    )
  )

type GetEtapeDocumentsErrors = EffectDbQueryAndValidateErrors | GetEtapeDataForEditionErrors | GetDocumentsByEtapeIdErrors
export const getEtapeDocuments: RestNewGetCall<'/rest/etapes/:etapeId/etapeDocuments'> = (rootPipe): Effect.Effect<GetEtapeDocumentsByEtapeId, CaminoApiError<GetEtapeDocumentsErrors>> =>
  rootPipe.pipe(
    Effect.bind('etapeDataForEdition', ({ pool, params }) => getEtapeDataForEdition(pool, params.etapeId)),
    Effect.flatMap(({ pool, user, params, etapeDataForEdition }) =>
      getDocumentsByEtapeId(
        params.etapeId,
        pool,
        user,
        etapeDataForEdition.titreTypeId,
        etapeDataForEdition.administrationsLocales,
        etapeDataForEdition.entreprisesTitulairesOuAmodiataires,
        etapeDataForEdition.etapeData.etape_type_id,
        {
          demarche_type_id: etapeDataForEdition.etapeData.demarche_type_id,
          entreprises_lecture: etapeDataForEdition.etapeData.demarche_entreprises_lecture,
          public_lecture: etapeDataForEdition.etapeData.demarche_public_lecture,
          titre_public_lecture: etapeDataForEdition.etapeData.titre_public_lecture,
        }
      )
    ),
    Effect.map(result => ({
      etapeDocuments: result,
    })),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr("l'étape n'a pas été trouvée", () => ({
          ...caminoError,
          status: HTTP_STATUS.NOT_FOUND,
        })),
        Match.whenOr(
          "Impossible d'exécuter la requête dans la base de données",
          'Les données en base ne correspondent pas à ce qui est attendu',
          "une erreur s'est produite lors de la vérification des droits de lecture d'un document",
          "Impossible de transformer le document en base en document d'API",
          () => ({
            ...caminoError,
            status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
          })
        ),
        Match.exhaustive
      )
    )
  )

type GetEtapeAvisErrors = EffectDbQueryAndValidateErrors | GetEtapeDataForEditionErrors | GetEtapeAvisLargeObjectIdsByEtapeIdErrors
export const getEtapeAvis: RestNewGetCall<'/rest/etapes/:etapeId/etapeAvis'> = (rootPipe): Effect.Effect<GetEtapeAvisByEtapeId, CaminoApiError<GetEtapeAvisErrors>> =>
  rootPipe.pipe(
    Effect.bind('etapeDataForEdition', ({ pool, params }) => getEtapeDataForEdition(pool, params.etapeId)),
    Effect.flatMap(({ pool, params, user, etapeDataForEdition }) =>
      getEtapeAvisLargeObjectIdsByEtapeId(
        params.etapeId,
        pool,
        user,
        etapeDataForEdition.titreTypeId,
        etapeDataForEdition.administrationsLocales,
        etapeDataForEdition.entreprisesTitulairesOuAmodiataires,
        etapeDataForEdition.etapeData.etape_type_id,
        {
          demarche_type_id: etapeDataForEdition.etapeData.demarche_type_id,
          entreprises_lecture: etapeDataForEdition.etapeData.demarche_entreprises_lecture,
          public_lecture: etapeDataForEdition.etapeData.demarche_public_lecture,
          titre_public_lecture: etapeDataForEdition.etapeData.titre_public_lecture,
        }
      )
    ),
    Effect.map(result => {
      const avis: GetEtapeAvisByEtapeId = result.map(a => ({ ...a, has_file: isNotNullNorUndefined(a.largeobject_id) }))
      return avis
    }),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr("l'étape n'a pas été trouvée", () => ({
          ...caminoError,
          status: HTTP_STATUS.NOT_FOUND,
        })),
        Match.whenOr(
          "Impossible d'exécuter la requête dans la base de données",
          'Les données en base ne correspondent pas à ce qui est attendu',
          "une erreur s'est produite lors de la vérification des droits de lecture d'un avis",
          () => ({
            ...caminoError,
            status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
          })
        ),
        Match.exhaustive
      )
    )
  )

const etapeFetchError = `Une erreur s'est produite lors de la récupération de l'étape` as const
const etapeIntrouvableError = `L'étape n'existe pas` as const
const etapeIncompletError = `L'étape n'a pas été chargée de manière complète` as const
const droitsManquantsError = `Les droits d'accès à cette étape ne sont pas suffisants` as const
const etapeUpdateFail = `La mise à jour de l'étape a échoué` as const
const etapeUpdateTaskFail = `Les tâches après mise à jour de l'étape ont échoué` as const
const demarcheFetchError = `Une erreur s'est produite lors de la récupération de la démarche` as const
const demarcheIntrouvableError = `La démarche associée à l'étape est vide` as const
const titreIntrouvableError = `Le titre associé à l'étape est vide` as const
const demarcheInvalideError = `La suppression de cette étape mène à une démarche invalide` as const
type DeleteEtapeErrors =
  | EffectDbQueryAndValidateErrors
  | typeof etapeFetchError
  | typeof etapeIntrouvableError
  | typeof etapeIncompletError
  | typeof droitsManquantsError
  | typeof etapeUpdateFail
  | typeof etapeUpdateTaskFail
  | typeof demarcheFetchError
  | typeof demarcheIntrouvableError
  | typeof titreIntrouvableError
  | typeof demarcheInvalideError
  | typeof incoherentMachineInfo
export const deleteEtape: RestNewDeleteCall<'/rest/etapes/:etapeIdOrSlug'> = (rootPipe): Effect.Effect<Option.Option<never>, CaminoApiError<DeleteEtapeErrors>> =>
  rootPipe.pipe(
    Effect.bind('etape', ({ params, user }) =>
      Effect.Do.pipe(
        () =>
          Effect.tryPromise({
            try: () =>
              titreEtapeGet(
                params.etapeIdOrSlug,
                {
                  fields: {
                    demarche: { titre: { pointsEtape: { id: {} } } },
                  },
                },
                user
              ),
            catch: error => ({ message: etapeFetchError, extra: error }),
          }),
        Effect.filterOrFail(
          (etape): etape is ITitreEtape => isNotNullNorUndefined(etape),
          () => ({ message: etapeIntrouvableError })
        )
      )
    ),
    Effect.bind('demarche', ({ etape }) =>
      Effect.Do.pipe(
        Effect.map(() => etape.demarche),
        Effect.filterOrFail(
          (demarche): demarche is ITitreDemarche => isNotNullNorUndefined(demarche),
          () => ({ message: etapeIncompletError, detail: 'demarche est manquant' })
        )
      )
    ),
    Effect.bind('titre', ({ demarche }) =>
      Effect.Do.pipe(
        Effect.map(() => demarche.titre),
        Effect.filterOrFail(
          (titre): titre is ITitre => isNotNullNorUndefined(titre) && titre.administrationsLocales !== undefined,
          () => ({ message: etapeIncompletError, detail: 'titre est manquant ou incomplet' })
        )
      )
    ),
    Effect.filterOrFail(
      ({ titre, demarche, etape, user }) =>
        canDeleteEtape(user, etape.typeId, etape.isBrouillon, etape.titulaireIds ?? [], titre.administrationsLocales ?? [], demarche.typeId, {
          typeId: titre.typeId,
          titreStatutId: titre.titreStatutId,
        }),
      () => ({ message: droitsManquantsError })
    ),
    Effect.bind('fullDemarche', ({ etape }) =>
      Effect.Do.pipe(
        () =>
          Effect.tryPromise({
            try: () =>
              titreDemarcheGet(
                etape.titreDemarcheId,
                {
                  fields: {
                    titre: {
                      demarches: { etapes: { id: {} } },
                    },
                    etapes: { id: {} },
                  },
                },
                userSuper
              ),
            catch: error => ({ message: demarcheFetchError, extra: error }),
          }),
        Effect.filterOrFail(
          (demarche): demarche is ITitreDemarche => isNotNullNorUndefined(demarche),
          () => ({ message: demarcheIntrouvableError })
        )
      )
    ),
    Effect.bind('fullTitre', ({ fullDemarche }) =>
      Effect.Do.pipe(
        Effect.map(() => fullDemarche.titre),
        Effect.filterOrFail(
          (titre): titre is ITitre => isNotNullNorUndefined(titre),
          () => ({ message: titreIntrouvableError, detail: 'titre est manquant ou incomplet' })
        )
      )
    ),
    Effect.bind('machineInfo', ({ fullDemarche, titre }) => {
      const machineInfo = MachineInfo.withMachineId(titre.typeId, fullDemarche.typeId, fullDemarche.id, fullDemarche.machineId)
      if (!machineInfo.valid) {
        return Effect.fail({ message: incoherentMachineInfo, extra: machineInfo.error })
      }

      return Effect.succeed(machineInfo.value)
    }),
    Effect.tap(({ fullDemarche, fullTitre, etape, machineInfo }) => {
      const { valid, errors: rulesErrors } = titreDemarcheUpdatedEtatValidate(machineInfo, fullTitre.demarches!, etape, fullDemarche.etapes!, true)
      if (!valid) {
        return Effect.fail({ message: demarcheInvalideError, detail: rulesErrors.join(', ') })
      }

      return Effect.succeed(true)
    }),
    Effect.tap(({ etape, fullDemarche, user }) =>
      Effect.tryPromise({
        try: () => titreEtapeUpdate(etape.id, { archive: true }, user, fullDemarche.titreId),
        catch: error => ({ message: etapeUpdateFail, extra: error }),
      })
    ),
    Effect.tap(({ pool, etape, user }) =>
      Effect.tryPromise({
        try: () => titreEtapeUpdateTask(pool, null, etape.titreDemarcheId, user),
        catch: error => ({ message: etapeUpdateTaskFail, extra: error }),
      })
    ),
    Effect.map(() => Option.none()),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.when("L'étape n'existe pas", () => ({
          ...caminoError,
          status: HTTP_STATUS.NOT_FOUND,
        })),
        Match.when("Les droits d'accès à cette étape ne sont pas suffisants", () => ({
          ...caminoError,
          status: HTTP_STATUS.FORBIDDEN,
        })),
        Match.when('La suppression de cette étape mène à une démarche invalide', () => ({
          ...caminoError,
          status: HTTP_STATUS.BAD_REQUEST,
        })),
        Match.whenOr(
          "L'étape n'a pas été chargée de manière complète",
          "La démarche associée à l'étape est vide",
          "La mise à jour de l'étape a échoué",
          "Le titre associé à l'étape est vide",
          "Les tâches après mise à jour de l'étape ont échoué",
          "Une erreur s'est produite lors de la récupération de l'étape",
          "Une erreur s'est produite lors de la récupération de la démarche",
          'La machine associée à la démarche est incohérente',
          () => ({
            ...caminoError,
            status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
          })
        ),
        Match.exhaustive
      )
    )
  )

type GetEtapeErrors =
  | EffectDbQueryAndValidateErrors
  | TitreEtapeToFlattenEtapeErrors
  | typeof etapeFetchError
  | typeof etapeIntrouvableError
  | typeof etapeIncompletError
  | typeof droitsManquantsError
  | typeof incoherentMachineInfo
export const getEtape: RestNewGetCall<'/rest/etapes/:etapeIdOrSlug'> = (rootPipe): Effect.Effect<FlattenEtape, CaminoApiError<GetEtapeErrors>> =>
  rootPipe.pipe(
    Effect.bind('etape', ({ user, params }) =>
      Effect.Do.pipe(
        () =>
          Effect.tryPromise({
            try: () => titreEtapeGet(params.etapeIdOrSlug, { fields: { demarche: { titre: { pointsEtape: { id: {} } } } }, fetchHeritage: true }, user),
            catch: error => ({ message: etapeFetchError, extra: error }),
          }),
        Effect.filterOrFail(
          (etape): etape is ITitreEtape => isNotNullNorUndefined(etape),
          () => ({ message: etapeIntrouvableError })
        ),
        Effect.filterOrFail(
          etape => isNotNullNorUndefined(etape.titulaireIds),
          () => ({ message: etapeIncompletError, detail: 'titulaireIds est manquant' })
        )
      )
    ),
    Effect.bind('demarche', ({ etape }) =>
      Effect.Do.pipe(
        Effect.map(() => etape.demarche),
        Effect.filterOrFail(
          (demarche): demarche is ITitreDemarche => isNotNullNorUndefined(demarche),
          () => ({ message: etapeIncompletError, detail: 'demarche est manquant' })
        )
      )
    ),
    Effect.bind('titre', ({ demarche }) =>
      Effect.Do.pipe(
        Effect.map(() => demarche.titre),
        Effect.filterOrFail(
          (titre): titre is ITitre => isNotNullNorUndefined(titre) && titre.administrationsLocales !== undefined,
          () => ({ message: etapeIncompletError, detail: 'titre est manquant ou incomplet' })
        )
      )
    ),
    Effect.filterOrFail(
      ({ titre, demarche, etape, user }) =>
        canEditEtape(user, etape.typeId, etape.isBrouillon, etape.titulaireIds ?? [], titre.administrationsLocales ?? [], demarche.typeId, {
          typeId: titre.typeId,
          titreStatutId: titre.titreStatutId,
        }),
      () => ({ message: droitsManquantsError })
    ),
    Effect.bind('machineInfo', ({ demarche, titre }) => {
      const machineInfo = MachineInfo.withMachineId(titre.typeId, demarche.typeId, demarche.id, demarche.machineId)
      if (!machineInfo.valid) {
        return Effect.fail({ message: incoherentMachineInfo, extra: machineInfo.error })
      }

      return Effect.succeed(machineInfo.value)
    }),
    Effect.flatMap(({ etape, machineInfo }) => iTitreEtapeToFlattenEtape(machineInfo, etape)),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.when("L'étape n'existe pas", () => ({
          ...caminoError,
          status: HTTP_STATUS.NOT_FOUND,
        })),
        Match.when("Les droits d'accès à cette étape ne sont pas suffisants", () => ({
          ...caminoError,
          status: HTTP_STATUS.FORBIDDEN,
        })),
        Match.whenOr(
          "L'étape n'a pas été chargée de manière complète",
          'Problème de validation de données',
          "Une erreur s'est produite lors de la récupération de l'étape",
          "pas d'héritage chargé",
          'pas de slug',
          'La machine associée à la démarche est incohérente',
          () => ({
            ...caminoError,
            status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
          })
        ),
        Match.exhaustive
      )
    )
  )

const documentDEntrepriseIncorrects = "document d'entreprise incorrects" as const
type ValidateAndGetEntrepriseDocumentsErrors = typeof documentDEntrepriseIncorrects | EffectDbQueryAndValidateErrors
const validateAndGetEntrepriseDocuments = (
  pool: Pool,
  etape: Pick<FlattenEtape, 'titulaires' | 'amodiataires'>,
  entrepriseDocumentIds: EntrepriseDocumentId[],
  user: User
): Effect.Effect<EntrepriseDocument[], CaminoError<ValidateAndGetEntrepriseDocumentsErrors>> =>
  Effect.Do.pipe(
    Effect.filterOrFail(
      () => isNotNullNorUndefinedNorEmpty(entrepriseDocumentIds),
      () => shortCircuitError("pas de documents d'entreprise")
    ),
    Effect.map(() => {
      return [...etape.titulaires.value, ...etape.amodiataires.value].filter(isNotNullNorUndefined).filter(onlyUnique)
    }),
    Effect.filterOrFail(
      entrepriseIds => isNonEmptyArray(entrepriseIds),
      () => shortCircuitError("pas de documents d'entreprise")
    ),
    Effect.flatMap(entrepriseIds => getEntrepriseDocuments(entrepriseDocumentIds, entrepriseIds, pool, user)),
    Effect.filterOrFail(
      entrepriseDocuments => entrepriseDocumentIds.length === entrepriseDocuments.length,
      () => ({ message: documentDEntrepriseIncorrects, detail: "Tous les documents d'entreprises n'ont pas pu être retrouvés en base" })
    ),
    Effect.catchTag("pas de documents d'entreprise", _myError => Effect.succeed([]))
  )

export const arePointsOnPerimeter = (perimetre: FeatureMultiPolygon, points: FeatureCollectionPoints): boolean => {
  const coordinatesSet = new Set()

  perimetre.geometry.coordinates.forEach(geometry => geometry.forEach(sub => sub.forEach(coordinate => coordinatesSet.add(`${coordinate[0]}-${coordinate[1]}`))))

  return points.features.every(point => {
    return coordinatesSet.has(`${point.geometry.coordinates[0]}-${point.geometry.coordinates[1]}`)
  })
}
const getForagesProperties = (
  machineInfo: MachineInfo,
  geojsonOrigineGeoSystemeId: GraphqlEtape['geojsonOrigineGeoSystemeId'],
  geojsonOrigineForages: GraphqlEtape['geojsonOrigineForages'],
  pool: Pool
): Effect.Effect<Pick<GraphqlEtape, 'geojson4326Forages' | 'geojsonOrigineForages'>, CaminoError<ConvertPointsErrors>> =>
  Effect.Do.pipe(
    Effect.flatMap(() => {
      if (canHaveForages(machineInfo) && isNotNullNorUndefined(geojsonOrigineForages) && isNotNullNorUndefined(geojsonOrigineGeoSystemeId)) {
        return convertPoints(pool, geojsonOrigineGeoSystemeId, geojsonOrigineForages)
      }
      return Effect.succeed(null)
    }),
    Effect.map(value => ({ geojson4326Forages: value, geojsonOrigineForages }))
  )

const lespointsdoiventetresurleperimetreerror = 'les points doivent être sur le périmètre' as const
type PerimetreInfosError = typeof lespointsdoiventetresurleperimetreerror | ConvertPointsErrors | GetGeojsonInformationErrorMessages
type PerimetreInfos = {
  secteursMaritime: SecteursMaritimes[]
  sdomZones: SDOMZoneId[]
  surface: KM2 | null
} & Pick<GraphqlEtape, 'geojson4326Forages' | 'geojsonOrigineForages'> &
  Pick<GetGeojsonInformation, 'communes' | 'forets' | 'departements'>
const getPerimetreInfosInternal = (
  pool: Pool,
  geojson4326Perimetre: GraphqlEtape['geojson4326Perimetre'],
  geojsonOriginePerimetre: GraphqlEtape['geojsonOriginePerimetre'],
  geojsonOriginePoints: GraphqlEtape['geojsonOriginePoints'],
  machineInfo: MachineInfo,
  geojsonOrigineGeoSystemeId: GraphqlEtape['geojsonOrigineGeoSystemeId'],
  geojsonOrigineForages: GraphqlEtape['geojsonOrigineForages']
): Effect.Effect<PerimetreInfos, CaminoError<PerimetreInfosError>> => {
  return Effect.Do.pipe(
    Effect.map(() => geojson4326Perimetre),
    Effect.filterOrFail(
      (value): value is NonNullable<typeof value> => isNotNullNorUndefined(value),
      () => shortCircuitError('Pas de périmètre')
    ),
    Effect.filterOrFail(
      () => isNullOrUndefined(geojsonOriginePerimetre) || isNullOrUndefined(geojsonOriginePoints) || arePointsOnPerimeter(geojsonOriginePerimetre, geojsonOriginePoints),
      () => ({ message: lespointsdoiventetresurleperimetreerror })
    ),
    Effect.bind('geojsonInformation', value => getGeojsonInformation(pool, value.geometry)),
    Effect.bind('forage', () => getForagesProperties(machineInfo, geojsonOrigineGeoSystemeId, geojsonOrigineForages, pool)),
    Effect.let('secteursMaritime', ({ geojsonInformation: { secteurs } }) => secteurs.map(s => getSecteurMaritime(s))),
    Effect.map(({ secteursMaritime, geojsonInformation: { departements, surface, communes, forets, sdom }, forage: { geojson4326Forages } }) => ({
      surface,
      communes,
      forets,
      secteursMaritime,
      departements,
      sdomZones: sdom,
      geojson4326Forages,
      geojsonOrigineForages,
    })),
    Effect.catchTag('Pas de périmètre', _myError =>
      Effect.succeed({
        communes: [],
        forets: [],
        secteursMaritime: [],
        sdomZones: [],
        departements: [],
        surface: null,
        geojson4326Forages: null,
        geojsonOrigineForages: null,
      })
    )
  )
}
const incoherentMachineInfo = 'La machine associée à la démarche est incohérente' as const
type GetFlattenEtapeErrors = PerimetreInfosError | TitreEtapeToFlattenEtapeErrors | typeof incoherentMachineInfo
const getFlattenEtape = (
  etape: RestEtapeCreation | RestEtapeModification,
  demarche: ITitreDemarche,
  titreTypeId: TitreTypeId,
  isBrouillon: EtapeBrouillon,
  etapeSlug: EtapeSlug | undefined,
  etapeOldConcurrence: TitreEtapeForMachine['concurrence'],
  etapeOldHasTitreFrom: TitreEtapeForMachine['hasTitreFrom'],
  etapeOldDemarcheIdsConsentement: TitreEtapeForMachine['demarcheIdsConsentement'],
  pool: Pool
): Effect.Effect<{ flattenEtape: Partial<Pick<ApiFlattenEtape, 'id'>> & Omit<ApiFlattenEtape, 'id'>; perimetreInfos: PerimetreInfos }, CaminoError<GetFlattenEtapeErrors>> => {
  return Effect.Do.pipe(
    Effect.bind('machineInfo', () => {
      const machineInfo = MachineInfo.withMachineId(titreTypeId, demarche.typeId, demarche.id, demarche.machineId)
      if (!machineInfo.valid) {
        return Effect.fail({ message: incoherentMachineInfo, extra: machineInfo.error })
      }

      return Effect.succeed(machineInfo.value)
    }),
    Effect.let('titreEtapeHeritage', ({ machineInfo }) => titreEtapeHeritageBuild(etape.date, machineInfo, etape.typeId, demarche, 'id' in etape ? etape.id : null)),

    Effect.bind('perimetreInfos', ({ machineInfo }) =>
      getPerimetreInfosInternal(pool, etape.geojson4326Perimetre, etape.geojsonOriginePerimetre, etape.geojsonOriginePoints, machineInfo, etape.geojsonOrigineGeoSystemeId, etape.geojsonOrigineForages)
    ),

    Effect.bind('heritageProps', ({ titreEtapeHeritage }) =>
      Effect.succeed(
        ETAPE_HERITAGE_PROPS.reduce<IHeritageProps>((acc, propId) => {
          acc[propId] = {
            actif: etape.heritageProps[propId].actif,
            etape: titreEtapeHeritage.heritageProps?.[propId].etape,
          }

          return acc
        }, {} as IHeritageProps)
      )
    ),
    Effect.bind('heritageContenu', ({ machineInfo, titreEtapeHeritage }) => {
      const sections = getSections(machineInfo, etape.typeId)

      const heritageContenu = sections.reduce<IHeritageContenu>((accSections, section) => {
        accSections[section.id] = section.elements.reduce<NonNullable<IHeritageContenu[string]>>((accElements, element) => {
          accElements[element.id] = {
            actif: etape.heritageContenu?.[section.id]?.[element.id]?.actif ?? false, // eslint-disable-line @typescript-eslint/no-unnecessary-condition
            etape: titreEtapeHeritage.heritageContenu?.[section.id]?.[element.id]?.etape ?? undefined,
          }

          return accElements
        }, {})

        return accSections
      }, {})
      return Effect.succeed(heritageContenu)
    }),
    Effect.let('fakeEtapeId', () => newEtapeId('newId')),
    Effect.bind('flattenEtape', ({ perimetreInfos, heritageProps, heritageContenu, fakeEtapeId, machineInfo }) =>
      iTitreEtapeToFlattenEtape(machineInfo, {
        ...etape,
        demarche,
        ...perimetreInfos,
        isBrouillon,
        heritageProps,
        heritageContenu,
        // On ne voit pas comment mieux faire
        id: 'id' in etape ? etape.id : fakeEtapeId,
        slug: etapeSlug,
        concurrence: etapeOldConcurrence,
        demarcheIdsConsentement: etapeOldDemarcheIdsConsentement,
        hasTitreFrom: etapeOldHasTitreFrom,
      })
    ),
    Effect.map(({ flattenEtape, perimetreInfos, fakeEtapeId }) => ({
      flattenEtape: {
        ...flattenEtape,
        // On ne voit pas comment mieux faire
        id: flattenEtape.id !== fakeEtapeId ? flattenEtape.id : undefined,
      },
      perimetreInfos,
    }))
  )
}

const demarcheExistePas = "la démarche n'existe pas" as const
const titreExistePas = "le titre n'existe pas" as const
const etapeNonValide = "l'étape n'est pas valide" as const
const droitsInsuffisants = 'droits insuffisants pour créer cette étape' as const
const erreurLorsDeLaCreationDeLEtape = "Une erreur est survenue lors de la création de l'étape" as const
const tachesAnnexes = 'une erreur est survenue lors des tâches annexes' as const
const envoieMails = 'une erreur est survenue lors des envois de mail' as const
const etapesNonChargees = "la liste des étapes n'est pas chargée" as const
const demarchesNonChargees = "la liste des démarches n'est pas chargée" as const
type CreateEtapeError =
  | GetFlattenEtapeErrors
  | EffectDbQueryAndValidateErrors
  | ValidateAndGetEntrepriseDocumentsErrors
  | InsertEtapeDocumentsErrors
  | InsertEtapeAvisErrors
  | CheckEntreprisesExistErrors
  | typeof demarcheExistePas
  | typeof titreExistePas
  | typeof etapeNonValide
  | typeof droitsInsuffisants
  | typeof erreurLorsDeLaCreationDeLEtape
  | typeof tachesAnnexes
  | typeof envoieMails
  | typeof etapesNonChargees
  | typeof demarchesNonChargees
export const createEtape: RestNewPostCall<'/rest/etapes'> = (rootPipe): Effect.Effect<{ id: EtapeId }, CaminoApiError<CreateEtapeError>> => {
  return rootPipe.pipe(
    Effect.tap(({ body: etape, user }) =>
      Effect.Do.pipe(
        Effect.flatMap(() =>
          Effect.tryPromise({
            try: async () => titreDemarcheGet(etape.titreDemarcheId, { fields: {} }, user),
            catch: e => ({ message: demarcheExistePas, extra: e }),
          })
        ),
        Effect.filterOrFail(
          titreDemarcheUser => isNotNullNorUndefined(titreDemarcheUser),
          () => ({ message: demarcheExistePas, detail: "L'utilisateur n'a pas accès à la démarche" })
        )
      )
    ),
    Effect.bind('titreDemarche', ({ body: etape }) =>
      Effect.tryPromise({
        try: async () =>
          titreDemarcheGet(
            etape.titreDemarcheId,
            {
              fields: {
                titre: {
                  demarches: { etapes: { id: {} } },
                  pointsEtape: { id: {} },
                  titulairesEtape: { id: {} },
                  amodiatairesEtape: { id: {} },
                },
                etapes: { id: {} },
              },
            },
            userSuper
          ),
        catch: e => ({ message: demarcheExistePas, extra: e }),
      }).pipe(
        Effect.filterOrFail(
          (demarche): demarche is ITitreDemarche => isNotNullNorUndefined(demarche),
          () => ({ message: demarcheExistePas })
        ),
        Effect.filterOrFail(
          (demarche): demarche is ITitreDemarche & { etapes: ITitreEtape[] } => isNotNullNorUndefined(demarche.etapes),
          () => ({ message: etapesNonChargees })
        )
      )
    ),
    Effect.bind('titre', ({ titreDemarche }) =>
      Effect.Do.pipe(
        Effect.map(() => titreDemarche.titre),
        Effect.filterOrFail(
          (value): value is ITitre => isNotNullNorUndefined(value),
          () => ({ message: titreExistePas })
        ),
        Effect.filterOrFail(
          (value): value is ITitre & { demarches: ITitreDemarche[] } => isNotNullNorUndefined(value.demarches),
          () => ({ message: demarchesNonChargees })
        )
      )
    ),
    Effect.let('isBrouillon', ({ body: etape }) => canBeBrouillon(etape.typeId)),
    Effect.filterOrFail(
      ({ isBrouillon, titreDemarche, titre, user, body: etape }) =>
        canCreateEtape(user, etape.typeId, isBrouillon, titre.titulaireIds ?? [], titre.administrationsLocales ?? [], titreDemarche.typeId, {
          typeId: titre.typeId,
          titreStatutId: titre.titreStatutId,
        }),
      () => ({ message: droitsInsuffisants })
    ),
    Effect.bind('hasTitreFrom', ({ titre, pool }) => hasTitreFrom(pool, titre.id)),
    Effect.bind('flattenEtapeAndPerimetreInfo', ({ titreDemarche, isBrouillon, titre, body: etape, pool, hasTitreFrom }) =>
      getFlattenEtape(
        etape as RestEtapeCreation, // TODO 2024-11-14 comment on fait là, si on met le deepReadonly ça transpire partout et c'est violent :(
        titreDemarche,
        titre.typeId,
        isBrouillon,
        etapeSlugValidator.parse('unknown'),
        etape.typeId === ETAPES_TYPES.demande ? { amIFirst: true } : 'non-applicable',
        hasTitreFrom,
        [],
        pool
      )
    ),
    Effect.bind('entrepriseDocuments', ({ flattenEtapeAndPerimetreInfo, body: etape, user, pool }) =>
      validateAndGetEntrepriseDocuments(pool, flattenEtapeAndPerimetreInfo.flattenEtape, etape.entrepriseDocumentIds, user)
    ),
    Effect.let('machineInfo', ({ body: etape, titre, titreDemarche }) => {
      const firstEtapeDate = demarcheEnregistrementDemandeDateFind(titreDemarche.etapes)
      const value = isNotNullNorUndefined(firstEtapeDate) ? firstEtapeDate : firstEtapeDateValidator.parse(etape.date)
      return MachineInfo.withDate(titre.typeId, titreDemarche.typeId, titreDemarche.id, value)
    }),
    Effect.tap(({ flattenEtapeAndPerimetreInfo, titreDemarche, titre, entrepriseDocuments, body: etape, user, machineInfo }) => {
      const rulesErrors = titreEtapeUpdationValidate(
        flattenEtapeAndPerimetreInfo.flattenEtape,
        titreDemarche.etapes,
        titre.demarches,
        etape.etapeDocuments,
        etape.etapeAvis,
        entrepriseDocuments,
        flattenEtapeAndPerimetreInfo.perimetreInfos.sdomZones,
        flattenEtapeAndPerimetreInfo.perimetreInfos.communes.map(({ id }) => id),
        user,
        machineInfo
      )
      if (isNotNullNorUndefinedNorEmpty(rulesErrors)) {
        return Effect.fail({ message: etapeNonValide, detail: rulesErrors.join(', ') })
      }
      return Effect.succeed(null)
    }),
    Effect.tap(({ pool, body: etape }) => checkEntreprisesExist(pool, [...etape.titulaireIds, ...etape.amodiataireIds])),
    Effect.bind('etapeUpdated', ({ flattenEtapeAndPerimetreInfo, isBrouillon, titreDemarche, body: etape, user }) =>
      Effect.tryPromise({
        try: () =>
          titreEtapeUpsert(
            { ...etape, statutId: getStatutId(flattenEtapeAndPerimetreInfo.flattenEtape, getCurrent()), ...flattenEtapeAndPerimetreInfo.perimetreInfos, isBrouillon, demarcheIdsConsentement: [] },
            user,
            titreDemarche.titreId
          ),
        catch: e => ({ message: erreurLorsDeLaCreationDeLEtape, extra: e }),
      })
    ),
    Effect.filterOrFail(
      (value): value is typeof value & { etapeUpdated: ITitreEtape } => isNotNullNorUndefined(value.etapeUpdated),
      () => ({ message: erreurLorsDeLaCreationDeLEtape })
    ),

    Effect.tap(({ etapeUpdated, pool, body }) => insertEtapeDocuments(pool, etapeUpdated.id, body.etapeDocuments)),
    Effect.tap(({ etapeUpdated, entrepriseDocuments, pool }) => insertTitreEtapeEntrepriseDocuments(pool, etapeUpdated.id, entrepriseDocuments)),
    Effect.tap(({ etapeUpdated, pool, body }) => insertEtapeAvis(pool, etapeUpdated.id, body.etapeAvis)),
    Effect.tap(({ etapeUpdated, pool, user }) =>
      Effect.tryPromise({
        try: () => titreEtapeUpdateTask(pool, etapeUpdated.id, etapeUpdated.titreDemarcheId, user),
        catch: e => ({ message: tachesAnnexes, extra: e }),
      })
    ),

    Effect.tap(({ etapeUpdated, titreDemarche, titre, user, pool }) =>
      Effect.tryPromise({
        try: async () => {
          await titreEtapeAdministrationsEmailsSend(etapeUpdated, titreDemarche.typeId, titreDemarche.titreId, titre.typeId, user)
          await titreEtapeUtilisateursEmailsSend(etapeUpdated, titreDemarche.titreId, pool)
        },
        catch: e => ({ message: envoieMails, extra: e }),
      })
    ),

    Effect.map(({ etapeUpdated }) => ({ id: etapeUpdated.id })),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr("la démarche n'existe pas", "le titre n'existe pas", () => ({ ...caminoError, status: HTTP_STATUS.NOT_FOUND })),
        Match.whenOr(
          "l'étape n'est pas valide",
          "certaines entreprises n'existent pas",
          'les points doivent être sur le périmètre',
          'Problème de validation de données',
          "document d'entreprise incorrects",
          'Problème de Système géographique (SRID)',
          'Le nombre de points est invalide',
          'La liste des points est vide',
          () => ({
            ...caminoError,
            status: HTTP_STATUS.BAD_REQUEST,
          })
        ),
        Match.when('droits insuffisants pour créer cette étape', () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
        Match.whenOr(
          "Impossible d'exécuter la requête dans la base de données",
          "Une erreur est survenue lors de la création de l'étape",
          'une erreur est survenue lors des envois de mail',
          'une erreur est survenue lors des tâches annexes',
          "impossible d'insérer un fichier en base",
          'Une erreur inattendue est survenue lors de la récupération des informations geojson en base',
          'Impossible de transformer la feature collection',
          "pas d'héritage chargé",
          'pas de slug',
          'Les données en base ne correspondent pas à ce qui est attendu',
          'La machine associée à la démarche est incohérente',
          "la liste des étapes n'est pas chargée",
          "la liste des démarches n'est pas chargée",
          () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
        ),
        Match.exhaustive
      )
    )
  )
}

const etapeNExistePas = "L'étape n'existe pas" as const
const demarcheNonChargee = "la démarche n'est pas chargée complètement" as const
const droitsInsuffisantsUpdate = 'droits insuffisant pour modifier cette étape' as const
const interditTypeEtapeEdition = "Il est interdit d'éditer le type d'étape" as const
const interditDeChangerDeDemarche = "Il est interdit de changer la démarche d'une étape" as const
const erreurEtapeUpdate = "Une erreur est survenue lors de la modification de l'étape" as const
type UpdateEtapeErrors =
  | EffectDbQueryAndValidateErrors
  | typeof etapeNExistePas
  | typeof demarcheNonChargee
  | typeof droitsInsuffisantsUpdate
  | typeof interditTypeEtapeEdition
  | typeof interditDeChangerDeDemarche
  | typeof demarcheExistePas
  | ValidateAndGetEntrepriseDocumentsErrors
  | CheckEntreprisesExistErrors
  | typeof erreurEtapeUpdate
  | typeof tachesAnnexes
  | typeof envoieMails
  | GetFlattenEtapeErrors
  | typeof etapeNonValide
  | InsertEtapeDocumentsErrors
  | UpdateEtapeDocumentsErrors
  | UpdateEtapeAvisErrors
  | typeof etapesNonChargees
  | typeof demarchesNonChargees
  | typeof titreExistePas
export const updateEtape: RestNewPutCall<'/rest/etapes'> = (rootPipe): Effect.Effect<{ id: EtapeId }, CaminoApiError<UpdateEtapeErrors>> => {
  return rootPipe.pipe(
    Effect.let('etape', ({ body }) => body),
    Effect.bind('titreEtapeOld', ({ etape, user }) =>
      Effect.tryPromise({
        try: () =>
          titreEtapeGet(
            etape.id,
            {
              fields: {
                demarche: { titre: { pointsEtape: { id: {} } } },
              },
            },
            user
          ),
        catch: e => ({ message: etapeNExistePas, extra: e }),
      }).pipe(
        Effect.filterOrFail(
          (value): value is ITitreEtape => isNotNullNorUndefined(value),
          () => ({ message: etapeNExistePas })
        ),
        Effect.filterOrFail(
          (value): value is ITitreEtape & { demarche: ITitreDemarche & { titre: ITitre } } =>
            isNotNullNorUndefined(value.demarche) && isNotNullNorUndefined(value.demarche.titre) && value.demarche.titre.administrationsLocales !== undefined,
          () => ({ message: demarcheNonChargee })
        ),
        Effect.filterOrFail(
          titreEtapeOld => titreEtapeOld.typeId === etape.typeId,
          () => ({ message: interditTypeEtapeEdition })
        ),
        Effect.filterOrFail(
          titreEtapeOld => titreEtapeOld.titreDemarcheId === etape.titreDemarcheId,
          () => ({ message: interditDeChangerDeDemarche })
        )
      )
    ),

    Effect.bind('titreDemarche', ({ etape }) =>
      Effect.tryPromise({
        try: () =>
          titreDemarcheGet(
            etape.titreDemarcheId,
            {
              fields: {
                titre: {
                  demarches: { etapes: { id: {} } },
                  titulairesEtape: { id: {} },
                  amodiatairesEtape: { id: {} },
                },
                etapes: { id: {} },
              },
            },
            userSuper
          ),
        catch: e => ({ message: demarcheExistePas, extra: e }),
      }).pipe(
        Effect.filterOrFail(
          (demarche): demarche is ITitreDemarche => isNotNullNorUndefined(demarche),
          () => ({ message: demarcheExistePas })
        ),
        Effect.filterOrFail(
          (demarche): demarche is ITitreDemarche & { etapes: ITitreEtape[] } => isNotNullNorUndefined(demarche.etapes),
          () => ({ message: etapesNonChargees })
        )
      )
    ),
    Effect.bind('titre', ({ titreDemarche }) =>
      Effect.Do.pipe(
        Effect.map(() => titreDemarche.titre),
        Effect.filterOrFail(
          (value): value is ITitre => isNotNullNorUndefined(value),
          () => ({ message: titreExistePas })
        ),
        Effect.filterOrFail(
          (value): value is ITitre & { demarches: ITitreDemarche[] } => isNotNullNorUndefined(value.demarches),
          () => ({ message: demarchesNonChargees })
        )
      )
    ),
    Effect.filterOrFail(
      ({ titreEtapeOld, titre, user }) =>
        canEditEtape(user, titreEtapeOld.typeId, titreEtapeOld.isBrouillon, titre.titulaireIds ?? [], titreEtapeOld.demarche.titre.administrationsLocales ?? [], titreEtapeOld.demarche.typeId, {
          typeId: titreEtapeOld.demarche.titre.typeId,
          titreStatutId: titreEtapeOld.demarche.titre.titreStatutId,
        }),
      () => ({ message: droitsInsuffisantsUpdate })
    ),
    Effect.bind('flattenEtapeAndPerimetre', ({ etape, titreDemarche, titre, titreEtapeOld, pool }) =>
      getFlattenEtape(
        etape as RestEtapeModification, // TODO 2024-11-14 comment on fait là, si on met le deepReadonly ça transpire partout et c'est violent :(
        titreDemarche,
        titre.typeId,
        titreEtapeOld.isBrouillon,
        titreEtapeOld.slug,
        titreEtapeOld.concurrence,
        titreEtapeOld.hasTitreFrom,
        titreEtapeOld.demarcheIdsConsentement,
        pool
      )
    ),
    Effect.bind('entrepriseDocuments', ({ pool, flattenEtapeAndPerimetre, etape, user }) =>
      validateAndGetEntrepriseDocuments(pool, flattenEtapeAndPerimetre.flattenEtape, etape.entrepriseDocumentIds, user)
    ),
    Effect.let('machineInfo', ({ body: etape, titreDemarche, titre }) => {
      const firstEtapeDate = demarcheEnregistrementDemandeDateFind(titreDemarche.etapes)
      const value = isNotNullNorUndefined(firstEtapeDate) ? firstEtapeDate : firstEtapeDateValidator.parse(etape.date)
      return MachineInfo.withDate(titre.typeId, titreDemarche.typeId, titreDemarche.id, value)
    }),
    Effect.tap(({ flattenEtapeAndPerimetre, titre, titreDemarche, entrepriseDocuments, etape, user, titreEtapeOld, machineInfo }) => {
      const rulesErrors = titreEtapeUpdationValidate(
        flattenEtapeAndPerimetre.flattenEtape,
        titreDemarche.etapes,
        titre.demarches,
        etape.etapeDocuments,
        etape.etapeAvis,
        entrepriseDocuments,
        flattenEtapeAndPerimetre.perimetreInfos.sdomZones,
        flattenEtapeAndPerimetre.perimetreInfos.communes.map(({ id }) => id),
        user,
        machineInfo,
        titreEtapeOld
      )
      if (isNotNullNorUndefinedNorEmpty(rulesErrors)) {
        return Effect.fail({ message: etapeNonValide, detail: rulesErrors.join(', ') })
      }
      return Effect.succeed(null)
    }),
    Effect.tap(({ etape, pool }) => checkEntreprisesExist(pool, [...etape.titulaireIds, ...etape.amodiataireIds])),
    Effect.bind('etapeUpdated', ({ etape, flattenEtapeAndPerimetre, titreEtapeOld, user, titreDemarche }) =>
      Effect.tryPromise({
        try: async () => {
          const value = await titreEtapeUpsert(
            {
              ...etape,
              statutId: getStatutId(flattenEtapeAndPerimetre.flattenEtape, getCurrent()),
              ...flattenEtapeAndPerimetre.perimetreInfos,
              isBrouillon: titreEtapeOld.isBrouillon,
              demarcheIdsConsentement: titreEtapeOld.demarcheIdsConsentement,
            },
            user,
            titreDemarche.titreId
          )
          if (isNullOrUndefined(value)) {
            throw new Error('Etape vide')
          }
          return value
        },
        catch: e => ({ message: erreurEtapeUpdate, extra: e }),
      })
    ),
    Effect.tap(({ pool, etapeUpdated, etape }) => updateEtapeDocuments(pool, etapeUpdated.id, etape.etapeDocuments)),
    Effect.tap(({ pool, etapeUpdated }) => deleteTitreEtapeEntrepriseDocument(pool, { titre_etape_id: etapeUpdated.id })),
    Effect.tap(({ pool, etapeUpdated, entrepriseDocuments }) => insertTitreEtapeEntrepriseDocuments(pool, etapeUpdated.id, entrepriseDocuments)),
    Effect.tap(({ pool, titreEtapeOld, etapeUpdated, etape, flattenEtapeAndPerimetre, machineInfo }) =>
      updateEtapeAvis(
        pool,
        etapeUpdated.id,
        titreEtapeOld.isBrouillon,
        etape.etapeAvis,
        etape.typeId,
        flattenEtapeAndPerimetre.flattenEtape.contenu,
        machineInfo,
        flattenEtapeAndPerimetre.perimetreInfos.communes.map(({ id }) => id)
      )
    ),
    Effect.tap(({ pool, etapeUpdated, user }) =>
      Effect.tryPromise({
        try: () => titreEtapeUpdateTask(pool, etapeUpdated.id, etapeUpdated.titreDemarcheId, user),
        catch: e => ({ message: tachesAnnexes, extra: e }),
      })
    ),
    Effect.tap(({ titreDemarche, titreEtapeOld, flattenEtapeAndPerimetre, user, titre }) =>
      Effect.tryPromise({
        try: () => titreEtapeAdministrationsEmailsSend(flattenEtapeAndPerimetre.flattenEtape, titreDemarche.typeId, titreDemarche.titreId, titre.typeId, user, titreEtapeOld),
        catch: e => ({ message: envoieMails, extra: e }),
      })
    ),
    Effect.map(({ titreEtapeOld }) => ({ id: titreEtapeOld.id })),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr("la démarche n'existe pas", "L'étape n'existe pas", "le titre n'existe pas", () => ({ ...caminoError, status: HTTP_STATUS.NOT_FOUND })),
        Match.whenOr(
          "l'étape n'est pas valide",
          "certaines entreprises n'existent pas",
          'les points doivent être sur le périmètre',
          'Problème de validation de données',
          "document d'entreprise incorrects",
          'Problème de Système géographique (SRID)',
          'Le nombre de points est invalide',
          'La liste des points est vide',
          "Il est interdit d'éditer le type d'étape",
          "Il est interdit de changer la démarche d'une étape",
          'Impossible de mettre à jour les avis, car ils ne sont pas complets',
          () => ({
            ...caminoError,
            status: HTTP_STATUS.BAD_REQUEST,
          })
        ),
        Match.when('droits insuffisant pour modifier cette étape', () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
        Match.whenOr(
          "Impossible d'exécuter la requête dans la base de données",
          "la démarche n'est pas chargée complètement",
          "Une erreur est survenue lors de la modification de l'étape",
          'une erreur est survenue lors des envois de mail',
          'une erreur est survenue lors des tâches annexes',
          "impossible d'insérer un fichier en base",
          'Une erreur inattendue est survenue lors de la récupération des informations geojson en base',
          'Impossible de transformer la feature collection',
          "pas d'héritage chargé",
          'pas de slug',
          'Les données en base ne correspondent pas à ce qui est attendu',
          'La machine associée à la démarche est incohérente',
          "la liste des étapes n'est pas chargée",
          "la liste des démarches n'est pas chargée",
          () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
        ),
        Match.exhaustive
      )
    )
  )
}

const demarcheNonExistante = "La démarche n'existe pas" as const
const slugEtapeNonExistant = "Le slug de l'étape est obligatoire" as const
const titreNonExistant = "Le titre n'est pas chargé" as const
const administrationsLocalesNonChargees = 'Les administrations locales du titre ne sont pas chargées' as const
const titulairesNonExistants = 'Les titulaires du titre ne sont pas chargés' as const
const amodiatairesNonExistants = 'Les amodiataires du titre ne sont pas chargés' as const
const droitsInsuffisantsDeposeEtape = 'Droits insuffisants pour déposer cette étape' as const
const brouillonInterdit = "Cette étape n'est pas un brouillon et ne peut pas être redéposée" as const
const impossibleDeRecupererLEtapeApresMaj = "Impossible de récupérer l'étape après mise à jour" as const
const impossibleDeMajLEtape = "Impossible de mettre à jour l'étape" as const
type DeposeEtapeErrors =
  | typeof etapeNonExistante
  | typeof demarcheNonExistante
  | typeof slugEtapeNonExistant
  | typeof titreNonExistant
  | typeof administrationsLocalesNonChargees
  | typeof titulairesNonExistants
  | typeof amodiatairesNonExistants
  | typeof droitsInsuffisantsDeposeEtape
  | typeof brouillonInterdit
  | typeof tachesAnnexes
  | typeof impossibleDeRecupererLEtapeApresMaj
  | typeof impossibleDeMajLEtape
  | typeof envoieMails
  | typeof incoherentMachineInfo
  | EffectDbQueryAndValidateErrors
  | GetGeojsonInformationErrorMessages
  | TitreEtapeToFlattenEtapeErrors
  | GetEtapeDocumentLargeObjectIdsByEtapeIdErrors
  | GetEtapeAvisLargeObjectIdsByEtapeIdErrors
  | GetDocumentsByEtapeIdErrors
export const deposeEtape: RestNewPutCall<'/rest/etapes/:etapeId/depot'> = (rootPipe): Effect.Effect<{ id: EtapeId }, CaminoApiError<DeposeEtapeErrors>> => {
  return rootPipe.pipe(
    Effect.bind('titreEtape', ({ params, user }) =>
      Effect.tryPromise({
        try: () => titreEtapeGet(params.etapeId, { fields: { id: {} }, fetchHeritage: true }, user),
        catch: e => ({ message: etapeNonExistante, extra: e }),
      }).pipe(
        Effect.filterOrFail(
          (titreEtape: ITitreEtape | null): titreEtape is ITitreEtape => isNotNullNorUndefined(titreEtape),
          () => ({ message: etapeNonExistante })
        ),
        Effect.filterOrFail(
          titreEtape => isNotNullNorUndefined(titreEtape.slug),
          () => ({ message: slugEtapeNonExistant })
        )
      )
    ),
    Effect.bind('titreDemarche', ({ titreEtape }) =>
      Effect.tryPromise({
        try: () =>
          titreDemarcheGet(
            titreEtape.titreDemarcheId,
            {
              fields: {
                titre: { pointsEtape: { id: {} }, titulairesEtape: { id: {} }, amodiatairesEtape: { id: {} } },
                etapes: { id: {} },
              },
            },
            userSuper
          ),
        catch: e => ({ message: demarcheNonExistante, extra: e }),
      }).pipe(
        Effect.filterOrFail(
          (result: ITitreDemarche | undefined): result is ITitreDemarche => isNotNullNorUndefined(result),
          () => ({ message: demarcheNonExistante })
        )
      )
    ),
    Effect.bind('titre', ({ titreDemarche }) =>
      Effect.succeed(titreDemarche.titre).pipe(
        Effect.filterOrFail(
          (titre): titre is NonNullable<ITitre> => isNotNullNorUndefined(titre),
          () => ({ message: titreNonExistant })
        ),
        Effect.filterOrFail(
          titre => isNotNullNorUndefined(titre.administrationsLocales),
          () => ({ message: administrationsLocalesNonChargees })
        ),
        Effect.filterOrFail(
          titre => isNotNullNorUndefined(titre.titulaireIds),
          () => ({ message: titulairesNonExistants })
        ),
        Effect.filterOrFail(
          titre => isNotNullNorUndefined(titre.amodiataireIds),
          () => ({ message: amodiatairesNonExistants })
        )
      )
    ),
    Effect.let('titreProps', ({ titre }) => ({
      titreTypeId: memoize(() => Promise.resolve(titre.typeId)),
      administrationsLocales: memoize(() => Promise.resolve(titre.administrationsLocales ?? [])),
      entreprisesTitulairesOuAmodiataires: memoize(() => Promise.resolve([...(titre.titulaireIds ?? []), ...(titre.amodiataireIds ?? [])])),
    })),
    Effect.bind('etapeDocuments', ({ pool, user, titre, titreEtape, titreDemarche, titreProps }) => {
      return getDocumentsByEtapeId(titreEtape.id, pool, user, titreProps.titreTypeId, titreProps.administrationsLocales, titreProps.entreprisesTitulairesOuAmodiataires, titreEtape.typeId, {
        demarche_type_id: titreDemarche.typeId,
        entreprises_lecture: titreDemarche.entreprisesLecture ?? false,
        public_lecture: titreDemarche.publicLecture ?? false,
        titre_public_lecture: titre.publicLecture ?? false,
      })
    }),
    Effect.bind('entrepriseDocuments', ({ pool, titreEtape }) => getEntrepriseDocumentIdsByEtapeId({ titre_etape_id: titreEtape.id }, pool, userSuper)),
    Effect.bind('etapeAvis', ({ pool, titreEtape, titreDemarche, titreProps, titre }) =>
      getEtapeAvisLargeObjectIdsByEtapeId(
        titreEtape.id,
        pool,
        userSuper,
        titreProps.titreTypeId,
        titreProps.administrationsLocales,
        titreProps.entreprisesTitulairesOuAmodiataires,
        titreEtape.typeId,
        {
          demarche_type_id: titreDemarche.typeId,
          entreprises_lecture: titreDemarche.entreprisesLecture ?? false,
          public_lecture: titreDemarche.publicLecture ?? false,
          titre_public_lecture: titre.publicLecture ?? false,
        }
      )
    ),
    Effect.bind('machineInfo', ({ titreDemarche, titre }) => {
      const machineInfo = MachineInfo.withMachineId(titre.typeId, titreDemarche.typeId, titreDemarche.id, titreDemarche.machineId)
      if (!machineInfo.valid) {
        return Effect.fail({ message: incoherentMachineInfo, extra: machineInfo.error })
      }

      return Effect.succeed(machineInfo.value)
    }),
    Effect.bind('flattenEtape', ({ titreEtape, machineInfo }) => iTitreEtapeToFlattenEtape(machineInfo, titreEtape)),
    Effect.let('date', ({ user, titreEtape }) => (isEntreprise(user) || isBureauDEtudes(user) ? getCurrent() : titreEtape.date)),
    Effect.bind('sdomEtCommunes', ({ titreEtape, pool }) => {
      if (isNotNullNorUndefined(titreEtape.geojson4326Perimetre)) {
        return getGeojsonInformation(pool, titreEtape.geojson4326Perimetre.geometry).pipe(
          Effect.map(({ sdom, communes: communesFromGeoJson }) => ({ sdomZones: sdom, communes: communesFromGeoJson.map(({ id }) => id) }))
        )
      }
      const emptyResult: { sdomZones: SDOMZoneId[]; communes: CommuneId[] } = { sdomZones: [], communes: [] }
      return Effect.succeed(emptyResult)
    }),
    Effect.filterOrFail(
      ({ user, titre, flattenEtape, etapeDocuments, entrepriseDocuments, sdomEtCommunes, etapeAvis, machineInfo }) =>
        canDeposeEtape(
          user,
          machineInfo,
          { ...titre, titulaires: titre.titulaireIds ?? [], administrationsLocales: titre.administrationsLocales ?? [] },
          flattenEtape,
          etapeDocuments,
          entrepriseDocuments,
          sdomEtCommunes.sdomZones,
          sdomEtCommunes.communes,
          etapeAvis
        ),
      () => ({ message: droitsInsuffisantsDeposeEtape })
    ),
    Effect.filterOrFail(
      ({ titreEtape }) => canBeBrouillon(titreEtape.typeId) === ETAPE_IS_BROUILLON,
      () => ({ message: brouillonInterdit })
    ),
    Effect.tap(({ titreEtape, date, user, titreDemarche }) =>
      Effect.tryPromise({
        try: () =>
          titreEtapeUpdate(
            titreEtape.id,
            {
              date,
              isBrouillon: ETAPE_IS_NOT_BROUILLON,
            },
            user,
            titreDemarche.titreId
          ),
        catch: e => ({ message: impossibleDeMajLEtape, extra: e }),
      })
    ),
    Effect.bind('etapeUpdated', ({ titreEtape, user }) =>
      Effect.tryPromise({
        try: () =>
          titreEtapeGet(
            titreEtape.id,
            {
              fields: { id: {} },
            },
            user
          ),
        catch: e => ({ message: impossibleDeRecupererLEtapeApresMaj, extra: e }),
      }).pipe(
        Effect.filterOrFail(
          etapeUpdated => isNotNullNorUndefined(etapeUpdated),
          () => ({ message: impossibleDeRecupererLEtapeApresMaj })
        )
      )
    ),
    Effect.tap(({ pool, etapeUpdated, user }) =>
      Effect.tryPromise({
        try: () => titreEtapeUpdateTask(pool, etapeUpdated.id, etapeUpdated.titreDemarcheId, user),
        catch: e => ({ message: tachesAnnexes, extra: e }),
      })
    ),
    Effect.tap(({ etapeUpdated, titreDemarche, user, titreEtape }) =>
      Effect.tryPromise({
        try: () => titreEtapeAdministrationsEmailsSend(etapeUpdated, titreDemarche.typeId, titreDemarche.titreId, titreDemarche.titre!.typeId, user!, titreEtape),
        catch: e => ({ message: envoieMails, extra: e }),
      })
    ),
    Effect.map(({ titreEtape }) => ({ id: titreEtape.id })),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr('Droits insuffisants pour déposer cette étape', () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
        Match.whenOr("l'étape n'existe pas", () => ({ ...caminoError, status: HTTP_STATUS.NOT_FOUND })),
        Match.whenOr("Cette étape n'est pas un brouillon et ne peut pas être redéposée", () => ({ ...caminoError, status: HTTP_STATUS.BAD_REQUEST })),
        Match.whenOr(
          "La démarche n'existe pas",
          "Le slug de l'étape est obligatoire",
          "Le titre n'est pas chargé",
          'Les administrations locales du titre ne sont pas chargées',
          'Les titulaires du titre ne sont pas chargés',
          'Les amodiataires du titre ne sont pas chargés',
          "Impossible d'exécuter la requête dans la base de données",
          "Impossible de mettre à jour l'étape",
          "Impossible de récupérer l'étape après mise à jour",
          'Les données en base ne correspondent pas à ce qui est attendu',
          'Problème de validation de données',
          'Une erreur inattendue est survenue lors de la récupération des informations geojson en base',
          "pas d'héritage chargé",
          'pas de slug',
          'une erreur est survenue lors des envois de mail',
          'une erreur est survenue lors des tâches annexes',
          "une erreur s'est produite lors de la vérification des droits de lecture d'un avis",
          "une erreur s'est produite lors de la vérification des droits de lecture d'un document",
          "Impossible de transformer le document en base en document d'API",
          'La machine associée à la démarche est incohérente',
          () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
        ),
        Match.exhaustive
      )
    )
  )
}

export const getEtapesTypesEtapesStatusWithMainStep: RestNewGetCall<'/rest/etapesTypes/:demarcheId/:date'> = (
  rootPipe
): Effect.Effect<EtapeTypeEtapeStatutWithMainStep, CaminoApiError<DemarcheEtapesTypesGetErrors>> => {
  return rootPipe.pipe(
    Effect.flatMap(({ params, searchParams, user }) => demarcheEtapesTypesGet(params.demarcheId, params.date, searchParams.etapeId ?? null, user)),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr("l'étape n'existe pas", "la démarche n'existe pas", () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
        Match.whenOr(
          "la démarche n'est pas complète",
          'les étapes ne sont pas chargées',
          "Impossible d'exécuter la requête dans la base de données",
          "Impossible de récupérer les types d'étapes",
          "impossible de récupérer l'étape",
          'impossible de récupérer la démarche',
          'Les données en base ne correspondent pas à ce qui est attendu',
          'La machine associée à la démarche est incohérente',
          () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
        ),
        Match.exhaustive
      )
    )
  )
}

const impossibleDeRecupererLesEtapesTypes = "Impossible de récupérer les types d'étapes" as const
const etapeNonExistante = "l'étape n'existe pas" as const
const etapeNonChargees = 'les étapes ne sont pas chargées' as const
const demarcheIncomplete = "la démarche n'est pas complète" as const
const demarcheInexistante = "la démarche n'existe pas" as const
const impossibleDeRecupererLaDemarche = 'impossible de récupérer la démarche' as const
const impossibleDeRecupererLEtape = "impossible de récupérer l'étape" as const
type DemarcheEtapesTypesGetErrors =
  | EffectDbQueryAndValidateErrors
  | typeof impossibleDeRecupererLaDemarche
  | typeof demarcheInexistante
  | typeof demarcheIncomplete
  | typeof etapeNonChargees
  | typeof impossibleDeRecupererLEtape
  | typeof etapeNonExistante
  | typeof impossibleDeRecupererLesEtapesTypes
  | typeof incoherentMachineInfo
const demarcheEtapesTypesGet = (
  titreDemarcheId: DemarcheId,
  date: CaminoDate,
  titreEtapeId: EtapeId | null,
  user: User
): Effect.Effect<EtapeTypeEtapeStatutWithMainStep, CaminoError<DemarcheEtapesTypesGetErrors>> => {
  return Effect.Do.pipe(
    Effect.bind('titreDemarche', () =>
      Effect.tryPromise({
        try: async () =>
          titreDemarcheGet(
            titreDemarcheId,
            {
              fields: {
                titre: {
                  demarches: { etapes: { id: {} } },
                  pointsEtape: { id: {} },
                  titulairesEtape: { id: {} },
                },
                etapes: { id: {} },
              },
            },
            userSuper
          ),
        catch: e => ({ message: impossibleDeRecupererLaDemarche, extra: e }),
      }).pipe(
        Effect.filterOrFail(
          (demarche): demarche is ITitreDemarche => isNotNullNorUndefined(demarche),
          () => ({ message: demarcheInexistante })
        )
      )
    ),
    Effect.bind('titre', ({ titreDemarche }) =>
      Effect.Do.pipe(
        Effect.map(() => titreDemarche.titre),
        Effect.filterOrFail(
          (titre): titre is ITitre => isNotNullNorUndefined(titre),
          () => ({ message: demarcheIncomplete })
        )
      )
    ),
    Effect.bind('etapes', ({ titreDemarche }) =>
      Effect.Do.pipe(
        Effect.map(() => titreDemarche.etapes),
        Effect.filterOrFail(
          (etapes): etapes is ITitreEtape[] => isNotNullNorUndefined(etapes),
          () => ({ message: etapeNonChargees })
        )
      )
    ),
    Effect.bind('etape', () =>
      Effect.tryPromise({
        try: async () => {
          if (isNotNullNorUndefined(titreEtapeId)) {
            return titreEtapeGet(titreEtapeId, {}, user)
          } else {
            return undefined
          }
        },
        catch: e => ({ message: impossibleDeRecupererLEtape, extra: e }),
      })
    ),
    Effect.filterOrFail(
      ({ etape }) => isNullOrUndefined(titreEtapeId) || (isNotNullNorUndefined(titreEtapeId) && isNotNullNorUndefined(etape)),
      () => ({ message: etapeNonExistante })
    ),
    Effect.bind('machine', ({ etapes, titre, titreDemarche }) => {
      if (isNullOrUndefinedOrEmpty(etapes)) {
        return Effect.succeed(new ApiMachineInfo(MachineInfo.withDate(titre.typeId, titreDemarche.typeId, titreDemarche.id, firstEtapeDateValidator.parse(date))))
      }
      const newDateMachine = demarcheEnregistrementDemandeDateFind(
        etapes.map(etape => {
          if (etape.id === titreEtapeId) {
            return { ...etape, date: date }
          }

          return etape
        })
      )
      if (isNotNullNorUndefined(newDateMachine)) {
        return Effect.succeed(new ApiMachineInfo(MachineInfo.withDate(titre.typeId, titreDemarche.typeId, titreDemarche.id, newDateMachine)))
      }

      const machineInfo = MachineInfo.withMachineId(titre.typeId, titreDemarche.typeId, titreDemarche.id, titreDemarche.machineId)
      if (!machineInfo.valid) {
        return Effect.fail({ message: incoherentMachineInfo, extra: machineInfo.error })
      }

      return Effect.succeed(new ApiMachineInfo(machineInfo.value))
    }),
    Effect.bind('etapesTypes', ({ machine, titreDemarche, etape }) =>
      Effect.try({
        try: () => {
          const etapesTypes: EtapeTypeEtapeStatutWithMainStep = getPossiblesEtapesTypes(machine, etape?.typeId, titreEtapeId ?? undefined, date, titreDemarche.etapes ?? [])
          return etapesTypes
        },
        catch: e => ({ message: impossibleDeRecupererLesEtapesTypes, extra: e }),
      })
    ),
    Effect.map(({ etapesTypes, titreDemarche, titre }) =>
      getKeys(etapesTypes, isEtapeTypeId).reduce<EtapeTypeEtapeStatutWithMainStep>((acc, etapeTypeId) => {
        if (
          canCreateEtape(user, etapeTypeId, ETAPE_IS_BROUILLON, titre.titulaireIds ?? [], titre.administrationsLocales ?? [], titreDemarche.typeId, {
            typeId: titre.typeId,
            titreStatutId: titre.titreStatutId,
          })
        ) {
          acc[etapeTypeId] = etapesTypes[etapeTypeId]
        }

        return acc
      }, {})
    )
  )
}
