"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateBulkService = generateBulkService;
const Generator = __importStar(require("@postxl/generator"));
function generateBulkService(context) {
    const { bulk } = context.update;
    const { dto } = context.types;
    const imports = Generator.ImportGenerator
        //
        .from(bulk.service.location)
        .addType(context.actions.execution.interface)
        .addType(context.types.dto.genericModel)
        .addType(context.types.dto.idType)
        .addType(context.types.dto.create)
        .addType(context.types.dto.update)
        .addType(context.types.dto.upsert)
        .addType(context.repositories.interface);
    const constructor = [];
    const modelTypes = [];
    const creates = [];
    const updates = [];
    const upserts = [];
    const deletes = [];
    for (const model of Array.from(context.models.values()).toSorted((a, b) => a.name.localeCompare(b.name))) {
        if (model.update.kind === 'skipped') {
            continue;
        }
        if (model.repository.kind === 'NoRepository') {
            continue;
        }
        imports
            //
            .add(model.repository)
            .add(model.types)
            .add(model.types.id);
        modelTypes.push(`${model.update.bulkMutationFieldName}?: ${bulk.modelType.name}<${model.types.name}, ${model.types.id.name}>`);
        constructor.push(`private readonly ${model.repository.dataServiceVariable} :${model.repository.name}`);
        creates.push(`await this.create({ name: '${model.userFriendlyName}', data: data.${model.update.bulkMutationFieldName}?.create, repo: this.${model.repository.dataServiceVariable}, execution })`);
        updates.push(`await this.update({ name: '${model.userFriendlyName}', data: data.${model.update.bulkMutationFieldName}?.update, repo: this.${model.repository.dataServiceVariable}, execution })`);
        upserts.push(`await this.upsert({ name: '${model.userFriendlyName}', data: data.${model.update.bulkMutationFieldName}?.upsert, repo: this.${model.repository.dataServiceVariable}, execution })`);
        deletes.push(`await this.delete({ name: '${model.userFriendlyName}', data: data.${model.update.bulkMutationFieldName}?.delete, repo: this.${model.repository.dataServiceVariable}, execution })`);
    }
    return /* ts */ `
  import { Injectable, Logger } from '@nestjs/common'
  import { format, pluralize } from '@postxl/utils'

  ${imports.generate()}

  type Scalar = number | string | boolean

  /**
   * Generic type for BulkMutation for a model.
   */
  export type ${bulk.modelType.name}<
    Model extends ${dto.genericModel.name}<Id>, 
    Id extends ${dto.idType.name}
  > = {
    create?: ${dto.create.name}<Model, Id>[]
    update?: ${dto.update.name}<Model, Id>[]
    upsert?: ${dto.upsert.name}<Model, Id>[]
    delete?: Id[]
  }

  /**
   * Bulk changes for all models.
   */
  export type ${bulk.type.name} = {
    ${modelTypes.join('\n')}
  }

  const DEBUG = false

  @Injectable()
  export class ${bulk.service.name} {
    private readonly logger = new Logger(${bulk.service.name}.name)
    constructor(${constructor.join(',\n')}) {}
  
    public async ${bulk.service.executeMultipleStepsMethodName}({ steps, execution }: { steps: ${bulk.type.name}[]; execution: ${context.actions.execution.interface.name} }) {
      for (const step of steps) {
        await this.${bulk.service.executeSingleStepMethodName}({ data: step, execution })
      }
    }
  
    public async ${bulk.service.executeSingleStepMethodName}({ data, execution }: { data: ${bulk.type.name}; execution: ${context.actions.execution.interface.name} }) {
      // The order of these calls is important, because of foreign key constraints!
      // If it is called manually, make sure to adjust this based on the schema dependencies!
      // Alternatively/better, use the detect methods from the import module. This does a topological sort and 
      // splits bulk updates into multiple steps so that the order is correct!
      
      ${creates.join('\n')}

      ${updates.join('\n')}

      ${upserts.join('\n')}

      ${deletes.join('\n')}
    }

    private async create<T extends { id: Id }, Id extends Scalar>({
      name,
      data,
      repo,
      execution,
    }: {
      name: string
      data: ${context.types.dto.create.name}<T, Id>[] | undefined
      repo: ${context.repositories.interface.name}<T, Id>
      execution: ${context.actions.execution.interface.name}
    }): Promise<void> {
      if (!data || data.length === 0) {
        return
      }
      // prettier-ignore
      if (!DEBUG) { // NOSONAR - core functionality should come first
        await repo.createMany?.({ items: data, execution })
      } else {
        let i = 0
        for (const item of data) {
          i++
          this.logger.log(\`Creating \${name} \${i} - \${_getItemDescription(item)}\`)
          try {
            await repo.create?.({ item, execution })
          } catch (error) {
            this.logger.error(\`Error creating \${name} \${i} - \${_getItemDescription(item)}\`, error)
            throw error
          }
        }
      }
      this.logger.log(\`✅ Created \${format(data.length)} \${pluralize(name)}\`)
    }

    private async update<T extends { id: Id }, Id extends Scalar>({
      name,
      data,
      repo,
      execution,
    }: {
      name: string
      data: ${context.types.dto.update.name}<T, Id>[] | undefined
      repo: ${context.repositories.interface.name}<T, Id>
      execution: ${context.actions.execution.interface.name}
    }): Promise<void> {
      if (!data || data.length === 0) {
        return
      }
      // prettier-ignore
      if (!DEBUG) { // NOSONAR - core functionality should come first
        await repo.updateMany?.({ items: data, execution })
      } else {
        let i = 0
        for (const item of data) {
          i++
          this.logger.log(\`Updating \${name} \${i} - \${_getItemDescription(item)}\`)
          try {
            await repo.update?.({ item, execution })
          } catch (error) {
            this.logger.error(\`Error updating \${name} \${i} - \${_getItemDescription(item)}\`, error)
            throw error
          }
        }
      }
      this.logger.log(\`✅ Updated \${format(data.length)} \${pluralize(name)}\`)
    }

    private async upsert<T extends { id: Id }, Id extends Scalar>({
      name,
      data,
      repo,
      execution,
    }: {
      name: string
      data: (${context.types.dto.create.name}<T, Id> | ${context.types.dto.update.name}<T, Id>)[] | undefined
      repo: ${context.repositories.interface.name}<T, Id>
      execution: ${context.actions.execution.interface.name}
    }): Promise<void> {
      if (!data || data.length === 0) {
        return
      }
      // prettier-ignore
      if (!DEBUG) { // NOSONAR - core functionality should come first
        await repo.upsertMany?.({ items: data, execution })
      } else {
        let i = 0
        for (const item of data) {
          i++
          this.logger.log(\`Upserting \${name} \${i} - \${_getItemDescription(item)}\`)
          try {
            await repo.upsert?.({ item, execution })
          } catch (error) {
            this.logger.error(\`Error upserting \${name} \${i} - \${_getItemDescription(item)}\`, error)
            throw error
          }
        }
      }
      this.logger.log(\`✅ Upserted \${format(data.length)} \${pluralize(name)}\`)
    }

    private async delete<T extends { id: Id }, Id extends Scalar>({
      name,
      data,
      repo,
      execution,
    }: {
      name: string
      data: Id[] | undefined
      repo: ${context.repositories.interface.name}<T, Id>
      execution: ${context.actions.execution.interface.name}
    }): Promise<void> {
      if (!data || data.length === 0) {
        return
      }
      // prettier-ignore
      if (!DEBUG) { // NOSONAR - core functionality should come first
        await repo.deleteMany?.({ ids: data, execution })
      } else {
        let i = 0
        for (const id of data) {
          i++
          this.logger.log(\`Deleting \${name} \${i} - id: \${id.toString()}\`)
          try {
            await repo.delete?.({ id, execution })
          } catch (error) {
            this.logger.error(\`Error deleting \${name} \${i} - id: \${id.toString()}\`, error)
            throw error
          }
        }
      }
      this.logger.log(\`✅ Deleted \${format(data.length)} \${pluralize(name)}\`)
    }
  }

  function _getItemDescription<T extends Partial<{ id: Id }>, Id extends Scalar>(item: T): string {
    const id = \`id: \${item.id === undefined ? 'not provided' : item.id.toString()}\`
  
    // If the item has a name, we can assume it is a string and add it to the description
    if ('name' in item) {
      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
      return \`\${id}, name: \${item.name}\`
    }
    return id
  }
  `;
}
//# sourceMappingURL=bulk-service.generator.js.map