"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.generateImportService = generateImportService;
const Generator = __importStar(require("@postxl/generator"));
function generateImportService(context) {
    const { import: { service, detectDelta, converterFunctions }, decoders, } = context;
    const imports = Generator.ImportGenerator.from(service.location);
    imports
        .addType(context.update.bulk.type)
        .add(context.repositories.dataService)
        .addType(context.actions.execution.interface)
        .addType(decoders.excelDataModel.decodedType)
        .addType(context.decoders.excelDataModel.decodedType)
        .addType(context.decoders.excelDataModel.encodedType)
        .add(context.decoders.excelDataModel.decoder.parseFunction)
        .add(converterFunctions.deltaToBulkMutations)
        .add(converterFunctions.importedDataToBulkMutations)
        .addType(detectDelta.allModels.type)
        .add(detectDelta.allModels);
    return `
import { Injectable, Logger } from '@nestjs/common'
import { XlPortService } from '@xlport/xlport.service'

import z from 'zod'

import { ExhaustiveSwitchCheck } from '@postxl/utils'
${imports.generate()}

export type ${context.import.actions.summary.action.name} = 
  | ${context.import.actions.excel.action.name}
  | ${context.import.actions.excelSeed.action.name}
  | ${context.import.actions.data.action.name}
  | ${context.import.actions.dataSeed.action.name}

export type ${context.import.actions.summary.result.name} = {
  excel: ${context.import.actions.excel.result.name}
  excelSeed: ${context.import.actions.excelSeed.result.name}
  data: ${context.import.actions.data.result.name}
  dataSeed: ${context.import.actions.dataSeed.result.name}
}

/**
 * Execute an import from Excel with a full delta detection
 */
export type ${context.import.actions.excel.action.name} = { scope: '${context.import.actionScope}'; type: 'excel'; payload: ImportExcelPayload }

/**
 * Execute an import from Excel during seeding, i.e with delta detection
 */
export type ${context.import.actions.excelSeed.action.name} = { scope: '${context.import.actionScope}'; type: 'excelSeed'; payload: ImportExcelPayload }

export const zImportExcelDecoder = z.object({
  filename: z.string(),
  data: z.instanceof(Buffer),
})
export type ImportExcelPayload = z.infer<typeof zImportExcelDecoder>

export type ${context.import.actions.excel.result.name} = ${detectDelta.allModels.type.name}

export type ${context.import.actions.excelSeed.result.name} = CreatedItemsCount

/**
 * Imports the provided data, performing a full delta detection.
 */
export type ${context.import.actions.data.action.name} = { scope: '${context.import.actionScope}'; type: 'data'; payload: ${decoders.excelDataModel.decodedType.name} }

/**
 * Imports the provided data without performing a full delta detection.
 * This is useful for seeding.
 */
export type ${context.import.actions.dataSeed.action.name} = { scope: '${context.import.actionScope}'; type: 'dataSeed'; payload: ${decoders.excelDataModel.decodedType.name} }

export type ${context.import.actions.data.result.name} = ${detectDelta.allModels.type.name}

export type ${context.import.actions.dataSeed.result.name} = CreatedItemsCount

type CreatedItemsCount = Partial<Record<keyof BulkMutation, number>>

@Injectable()
export class ${service.name} {
  private readonly logger = new Logger(ImportService.name)

  constructor(
    private readonly data: ${context.repositories.dataService.name},
    private readonly xlPort: XlPortService,
  ) {}

  public async dispatch<Action extends ${context.import.actions.summary.action.name}, Result extends ${context.import.actions.summary.result.name}>({
    action,
    execution,
  }: {
    action: Action
    execution: ${context.actions.execution.interface.name}
  }): Promise<Result> {
    switch (action.type) {
      case 'excel':
        return this.excelImport({ action, execution }) as Promise<Result>
      case 'excelSeed':
        return this.excelSeedImport({ action, execution }) as Promise<Result>
      case 'data':
        return this.dataImport({ action, execution }) as Promise<Result>
      case 'dataSeed':
        return this.dataSeedImport({ action, execution }) as Promise<Result>
      default:
        throw new ExhaustiveSwitchCheck(action)
    }
  }

  private async excelImport({
    action,
    execution,
  }: {
    action: ${context.import.actions.excel.action.name}
    execution: ${context.actions.execution.interface.name}
  }): Promise<${context.import.actions.excel.result.name}> {
    let deltaResult: ${context.import.actions.excel.result.name}
    await execution.executeTransformations(
      [
        ['readExcel', (input: Buffer) => this.readExcel(input)],
        ['decodeData', (data: ExcelDataModelFlattened) => ${context.decoders.excelDataModel.decoder.parseFunction.name}(data)],
        ['detectDelta', (data: DataModel) => ${detectDelta.allModels.name}(this.data, data)],
        [
          'convertToBulkMutations',
          (delta: Delta) => {
            const steps = ${converterFunctions.deltaToBulkMutations.name}(delta)
            deltaResult = delta
            return steps
          },
        ],
        ['bulkUpdate', (steps: BulkMutation[]) => this.data.${context.repositories.dataService.executeBulkMutations}({ steps, execution })],
      ],
      action.payload.data,
    )
    return deltaResult!
  }

  private async excelSeedImport({
    action,
    execution,
  }: {
    action: ${context.import.actions.excelSeed.action.name}
    execution: ${context.actions.execution.interface.name}
  }): Promise<${context.import.actions.excelSeed.result.name}> {
    const result = await execution.executeTransformations(
      [
        ['readExcel', (input: Buffer) => this.readExcel(input)],
        ['decodeData', (data: ExcelDataModelFlattened) => ${context.decoders.excelDataModel.decoder.parseFunction.name}(data)],
        ['convertToBulkMutations', (data: DataModel) => importedDataToBulkMutations(data)],
        [
          'bulkUpdate',
          async (steps: BulkMutation[]) => {
            await this.data.${context.repositories.dataService.executeBulkMutations}({ steps, execution })
            return this.countCreatedItems(steps)
          },
        ],
      ],
      action.payload.data,
    )
    return result
  }

  private async dataImport({
    action,
    execution,
  }: {
    action: ${context.import.actions.data.action.name}
    execution: ${context.actions.execution.interface.name}
  }): Promise<${context.import.actions.data.result.name}> {
    let deltaResult: ${context.import.actions.data.result.name}
    await execution.executeTransformations(
      [
        ['detectDelta', (data: DataModel) => ${detectDelta.allModels.name}(this.data, data)],
        [
          'convertToBulkMutations',
          (delta: Delta) => {
            const steps = ${converterFunctions.deltaToBulkMutations.name}(delta)
            deltaResult = delta
            return steps
          },
        ],
        ['bulkUpdate', (steps: BulkMutation[]) => this.data.${context.repositories.dataService.executeBulkMutations}({ steps, execution })],
      ],
      action.payload,
    )
    return deltaResult!
  }

  private async dataSeedImport({
    action,
    execution,
  }: {
    action: ${context.import.actions.dataSeed.action.name}
    execution: ${context.actions.execution.interface.name}
  }): Promise<${context.import.actions.dataSeed.result.name}> {
    const result = await execution.executeTransformations(
      [
        ['convertToBulkMutations', (data: DataModel) => importedDataToBulkMutations(data)],
        [
          'bulkUpdate',
          async (steps: BulkMutation[]) => {
            await this.data.${context.repositories.dataService.executeBulkMutations}({ steps, execution })
            return this.countCreatedItems(steps)
          },
        ],
      ],
      action.payload,
    )
    return result
  }

  private async readExcel(data: Buffer): Promise<ExcelDataModelFlattened> {
    const xlData = await this.xlPort.importFromFile(data)

    if (xlData.status !== 'success') {
      throw new Error(xlData.message)
    }

    return removeBlankEntries(xlData.data.tables as unknown as ExcelDataModelFlattened)
  }

  private countCreatedItems(bulkMutations: BulkMutation[]): CreatedItemsCount {
    const result: Record<string, number> = {}
    for (const bulkMutation of bulkMutations) {
      for (const [model, items] of Object.entries(bulkMutation)) {
        result[model] = (result[model] ?? 0) + (items.create?.length ?? 0)
      }
    }
    return result
  }
}
  

function removeBlankEntries(data: ExcelDataModelFlattened): ExcelDataModelFlattened {
  const result: Record<string, unknown[]> = {}
  for (const [key, modelEntries] of Object.entries(data)) {
    const modelResult: unknown[] = []
    for (const item of modelEntries?.values() ?? []) {
      if (!isBlank(item)) {
        modelResult.push(item)
      }
    }
    result[key] = modelResult
  }
  return result as ExcelDataModelFlattened
}

function isBlank(value: unknown): boolean {
  if (value === null || value === undefined) {
    return true
  }
  switch (typeof value) {
    case 'string':
    case 'number':
    case 'boolean':
      return false
    case 'object':
      if (Array.isArray(value)) {
        return false
      }
      for (const values of Object.values(value)) {
        if (!isBlank(values)) {
          return false
        }
      }
      return true
    default:
      return false
  }
}`;
}
//# sourceMappingURL=import-service.generator.js.map