"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.generateDataService = generateDataService;
const Generator = __importStar(require("@postxl/generator"));
function generateDataService(context) {
    const imports = Generator.ImportGenerator
        //
        .from(context.repositories.module.location)
        .addType(context.types.dto.create)
        .addType(context.types.dto.update)
        .addType(context.repositories.interface)
        .addType(context.repositories.bulkMutation)
        .addType(context.actions.execution.interface)
        .addImport({
        from: Generator.toPackageName('@postxl/utils'),
        items: [Generator.toFunctionName('format'), Generator.toFunctionName('pluralize')],
    });
    // Add DataModel import from decoders
    if ('decoders' in context) {
        imports.addType(context.decoders.dataModel.decodedType);
    }
    const constructor = [];
    const initializer = [];
    const isEmptyChecks = [];
    const getAllAsDataModelCalls = [];
    const creates = [];
    const updates = [];
    const upserts = [];
    const deletes = [];
    for (const model of context.models.values()) {
        if (model.repository.kind === 'NoRepository') {
            continue;
        }
        imports.add(model.repository);
        constructor.push(`public readonly ${model.repository.dataServiceVariable} :${model.repository.name}`);
        initializer.push(`await this.${model.repository.dataServiceVariable}.init(ignoreError)`);
        isEmptyChecks.push(`(await this.${model.repository.dataServiceVariable}.count()) === 0`);
        // Add to getAllAsDataModel method
        getAllAsDataModelCalls.push(`      ${model.repository.dataServiceVariable}: await this.${model.repository.dataServiceVariable}.getAllAsArray(),`);
        creates.push(`await this.create({ name: '${model.userFriendlyName}', data: data.${model.repository.dataServiceVariable}?.create, repo: this.${model.repository.dataServiceVariable}, execution })`);
        updates.push(`await this.update({ name: '${model.userFriendlyName}', data: data.${model.repository.dataServiceVariable}?.update, repo: this.${model.repository.dataServiceVariable}, execution })`);
        upserts.push(`await this.upsert({ name: '${model.userFriendlyName}', data: data.${model.repository.dataServiceVariable}?.upsert, repo: this.${model.repository.dataServiceVariable}, execution })`);
        deletes.push(`await this.delete({ name: '${model.userFriendlyName}', data: data.${model.repository.dataServiceVariable}?.delete, repo: this.${model.repository.dataServiceVariable}, execution })`);
    }
    return `
import { Injectable, Logger } from '@nestjs/common'

${imports.generate()}

// If true, the bulk mutation will be executed one by one, instead of all at once (which is the default).
// Also, each item will be logged to the console.
// This should help to quickly identify the item that causes an error in a bulk mutation.
const DEBUG = false

type Scalar = number | string | boolean

@Injectable()
export class ${context.repositories.dataService.name} {
  private readonly logger = new Logger(${context.repositories.dataService.name}.name)

  constructor(${constructor.join(',')}) {}

  public async init(ignoreError: boolean): Promise<void> {
    ${initializer.join('\n')}
  }

  public async isEmpty(): Promise<boolean> {
    return (
      ${isEmptyChecks.join(' && \n')}
    )
  }

  /**
   * Fetches all data from all repositories and returns it as a DataModel
   * This is useful for exporting or inspecting the entire database state
   */
  public async ${context.repositories.dataService.getAllAsDataModel}(): Promise<${'decoders' in context ? context.decoders.dataModel.decodedType.name : 'any'}> {
    return {
${getAllAsDataModelCalls.join('\n')}
    }
  }

  public async ${context.repositories.dataService.executeBulkMutations}(
    { steps, execution }: 
    { steps: ${context.repositories.bulkMutation.name}[]; execution: ${context.actions.execution.interface.name} }
  ) {
    for (const step of steps) {
      await this.${context.repositories.dataService.executeBulkMutation}({ data: step, execution })
    }
  }

  public async ${context.repositories.dataService.executeBulkMutation}(
    { data, execution }: 
    { data: ${context.repositories.bulkMutation.name}, execution: ${context.actions.execution.interface.name} }
  ) {
    // The order of these calls is important, because of foreign key constraints
    // The current order is based on the order of the models in the schema
    // Change the order based on your needs.
    // Attention: Depending on the dependencies in seed data, you may also have to take care of relations,
    // e.g. by removing any foreign key first - and then update these after all data is created.
    // Alternatively, you can also do this in multiple steps
    ${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=data-service.generator.js.map