"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.generateModelController = generateModelController;
const Generator = __importStar(require("@postxl/generator"));
function generateModelController({ model, context, }) {
    const imports = Generator.ImportGenerator.from(model.restApi.controller.location)
        .add(model.types)
        .add(model.types.id.decoderBase)
        .add(model.types.id.toBranded)
        .add(model.decoder.decoder.zodDecoder)
        .add(model.decoder.excel.encoder)
        .add(model.types.filter.decoder)
        .add(model.types.sort.decoder)
        .add(context.view.service);
    const { PascalCase, PascalCasePlural, camelCase } = model._conjugated;
    // Find all Date fields that need to be overridden for JSON Schema compatibility
    const dateFields = Array.from(model.fields.values()).filter((field) => field.kind === 'scalar' && field.type === 'Date');
    // Find all relation fields (foreign keys) that have transforms
    const relationFields = Array.from(model.fields.values()).filter((field) => field.kind === 'relation');
    // Find all enum fields that have transforms
    const enumFields = Array.from(model.fields.values()).filter((field) => field.kind === 'enum');
    // Find all discriminated union fields (they contain transforms internally)
    const discriminatedUnionFields = Array.from(model.fields.values()).filter((field) => field.kind === 'discriminatedUnion');
    // Fields to omit: id (has transform) + Date fields + relation fields + enum fields + discriminated unions
    const omitFields = [
        'id',
        ...dateFields.map((f) => f.name),
        ...relationFields.map((f) => f.name),
        ...enumFields.map((f) => f.name),
        ...discriminatedUnionFields.map((f) => f.name),
    ];
    const omitObject = omitFields.map((f) => `${f}: true`).join(', ');
    // Build extend fields for JSON Schema compatible replacements
    const extendFieldsList = [];
    // Add id field
    extendFieldsList.push(`id: ${model.types.id.decoderBase.name}`);
    // Add date fields
    for (const f of dateFields) {
        extendFieldsList.push(`${f.name}: z.iso.datetime()${f.isRequired ? '' : '.nullable()'}`);
    }
    // Add relation fields (use the base decoder without transform)
    for (const f of relationFields) {
        const referencedModel = context.models.get(f.referencedModelName);
        imports.add(referencedModel.types.id.decoderBase);
        extendFieldsList.push(`${f.name}: ${referencedModel.types.id.decoderBase.name}${f.isRequired ? '' : '.nullable()'}`);
    }
    // Add enum fields (use the base enum decoder without transform)
    for (const f of enumFields) {
        const enum_ = context.enums.get(f.enumName);
        imports.add(enum_.decoder.decoder.zodDecoder);
        extendFieldsList.push(`${f.name}: ${enum_.decoder.decoder.zodDecoder.name}${f.isRequired ? '' : '.nullable()'}`);
    }
    const extendObject = extendFieldsList.join(',\n  ');
    // Generate transform function for Date fields (relation and enum fields don't need transformation)
    const transformFields = dateFields
        .map((f) => `${f.name}: item.${f.name}${f.isRequired ? '' : '?'}.toISOString()${f.isRequired ? '' : ' ?? null'}`)
        .join(',\n  ');
    // Generate comment about omitted discriminated union fields
    const duComment = discriminatedUnionFields.length > 0
        ? `\n// Note: Discriminated union fields omitted from REST API: ${discriminatedUnionFields.map((f) => f.name).join(', ')}`
        : '';
    // Generate filter fields for the API description
    const filterableFields = Array.from(model.fields.values()).filter((f) => f.kind !== 'discriminatedUnion');
    const filterFieldNames = filterableFields.map((f) => f.name);
    const filterFieldsDescription = [...filterFieldNames, 'globalSearch'].join(', ');
    // Generate a good example with two fields if possible
    const stringField = filterableFields.find((f) => f.kind === 'scalar' && f.type === 'string' && f.name !== 'id');
    const booleanField = filterableFields.find((f) => f.kind === 'scalar' && f.type === 'boolean');
    const exampleParts = [];
    if (stringField) {
        exampleParts.push(`"${stringField.name}":{"contains":"test"}`);
    }
    else {
        exampleParts.push(`"id":{"contains":"test"}`);
    }
    if (booleanField) {
        exampleParts.push(`"${booleanField.name}":[true]`);
    }
    const filterExample = `{${exampleParts.join(',')}}`;
    return /* ts */ `
import '@fastify/cookie'
import * as csv from '@fast-csv/format'
import { Controller, Get, Logger, Param, Query, Res } from '@nestjs/common'
import { ApiQuery } from '@nestjs/swagger'
import { FastifyReply } from 'fastify'
import { createZodDto, ZodResponse } from 'nestjs-zod'
${imports.generate()}
import z from 'zod'

import { createFieldsDecoder, selectFields } from '@utils/fieldSelection'
import { paginatedResultDecoder, paginationDecoderFromQuery } from '@utils/pagination'
import { createWorkbookBuffer, jsonQueryParam } from './restApi.utils'

// REST API decoder safe for JSON Schema / OpenAPI generation
// Reuses ${model.decoder.decoder.zodDecoder.name} but overrides fields with transforms or non-JSON-representable types${duComment}
const z${PascalCase}RestApiDecoder = ${model.decoder.decoder.zodDecoder.name}.omit({ ${omitObject} }).extend({
  ${extendObject}
})

type ${PascalCase}RestApi = z.infer<typeof z${PascalCase}RestApiDecoder>

const ${camelCase}FieldsDecoder = createFieldsDecoder(z${PascalCase}RestApiDecoder)
type ${PascalCase}Fields = z.infer<typeof ${camelCase}FieldsDecoder>

const to${PascalCase}RestApi = (item: ${model.types.name}, fields: ${PascalCase}Fields): Partial<${PascalCase}RestApi> => {
  const transformed = {
    ...item,
    ${transformFields}
  }
  return selectFields(transformed, fields)
}

const get${PascalCase}ParamsDecoder = z.object({
  id: ${model.types.id.decoderBase.name},
})

const get${PascalCase}QueryDecoder = z.object({
  fields: ${camelCase}FieldsDecoder,
})

const get${PascalCase}ResponseDecoder = z.object({
  data: z${PascalCase}RestApiDecoder.partial().nullable(),
})

const get${PascalCasePlural}Decoder = paginationDecoderFromQuery.extend({
  fields: ${camelCase}FieldsDecoder,
  filters: jsonQueryParam(${model.types.filter.decoder.name}),
  sort: jsonQueryParam(${model.types.sort.decoder.name}),
})

const get${PascalCasePlural}ResponseDecoder = paginatedResultDecoder(z${PascalCase}RestApiDecoder.partial())

const get${PascalCasePlural}DownloadDecoder = paginationDecoderFromQuery.extend({
  filters: jsonQueryParam(${model.types.filter.decoder.name}),
  sort: jsonQueryParam(${model.types.sort.decoder.name}),
})

class Get${PascalCase}ParamsDto extends createZodDto(get${PascalCase}ParamsDecoder) {}
class Get${PascalCase}QueryDto extends createZodDto(get${PascalCase}QueryDecoder) {}
class Get${PascalCase}ResponseDto extends createZodDto(get${PascalCase}ResponseDecoder) {}
type Get${PascalCase}Response = z.infer<typeof get${PascalCase}ResponseDecoder>
class Get${PascalCasePlural}Dto extends createZodDto(get${PascalCasePlural}Decoder) {}
class Get${PascalCasePlural}ResponseDto extends createZodDto(get${PascalCasePlural}ResponseDecoder) {}
type Get${PascalCasePlural}Response = z.infer<typeof get${PascalCasePlural}ResponseDecoder>
class Get${PascalCasePlural}DownloadDto extends createZodDto(get${PascalCasePlural}DownloadDecoder) {}

@Controller('${camelCase}')
export class ${model.restApi.controller.name} {
  private readonly logger = new Logger(${model.restApi.controller.name}.name)

  constructor(private readonly viewService: ${context.view.service.name}) {}

  /**
   * Retrieve a ${model.userFriendlyName} by Id.
   */
  @ZodResponse({ type: Get${PascalCase}ResponseDto })
  @Get(':id')
  async get${PascalCase}(
    @Param() params: Get${PascalCase}ParamsDto,
    @Query() query: Get${PascalCase}QueryDto,
  ): Promise<Get${PascalCase}Response> {
    const data = await this.viewService.${model.view.service.variableName}.get({
      id: ${model.types.id.toBranded.name}(params.id),
      user: this.viewService.users.data.rootUser,
    })
    return {
      data: data ? to${PascalCase}RestApi(data, query.fields) : null,
    }
  }

  /**
   * Retrieve all ${model.userFriendlyNamePlural}.
   */
  @ApiQuery({
    name: 'filters',
    required: false,
    description: \`JSON filter object. Fields: ${filterFieldsDescription}. String fields accept: values (array), contains, startsWith, endsWith, exclude. Date fields accept: from, to. Boolean fields accept an array of true/false.\`,
    example: '${filterExample}',
  })
  @ApiQuery({
    name: 'sort',
    required: false,
    description: \`JSON array of sort objects. Each object has "field" (${filterFieldNames.join(', ')}) and "direction" (asc, desc).\`,
    example: '[{"field":"${stringField?.name ?? filterFieldNames[0] ?? 'id'}","direction":"asc"}]',
  })
  @ZodResponse({ type: Get${PascalCasePlural}ResponseDto })
  @Get('')
  async get${PascalCasePlural}(@Query() query: Get${PascalCasePlural}Dto): Promise<Get${PascalCasePlural}Response> {
    const result = await this.viewService.${model.view.service.variableName}.getFilteredPaginated({
      user: this.viewService.users.data.rootUser,
      filters: query.filters ?? {},
      sort: query.sort,
      cursor: query.cursor,
      limit: query.limit,
    })
    return {
      ...result,
      data: result.data.map((item) => to${PascalCase}RestApi(item, query.fields)),
    }
  }

  /**
   * Download ${model.userFriendlyNamePlural} as CSV.
   */
  @ApiQuery({
    name: 'filters',
    required: false,
    description: \`JSON filter object. Fields: ${filterFieldsDescription}. String fields accept: values (array), contains, startsWith, endsWith, exclude. Date fields accept: from, to. Boolean fields accept an array of true/false.\`,
    example: '${filterExample}',
  })
  @ApiQuery({
    name: 'sort',
    required: false,
    description: \`JSON array of sort objects. Each object has "field" (${filterFieldNames.join(', ')}) and "direction" (asc, desc).\`,
    example: '[{"field":"${stringField?.name ?? filterFieldNames[0] ?? 'id'}","direction":"asc"}]',
  })
  @Get('download/csv')
  async download${PascalCasePlural}Csv(
    @Query() query: Get${PascalCasePlural}DownloadDto,
    @Res() res: FastifyReply,
  ): Promise<void> {
    const result = await this.viewService.${model.view.service.variableName}.getFilteredPaginated({
      user: this.viewService.users.data.rootUser,
      filters: query.filters ?? {},
      sort: query.sort,
      cursor: query.cursor,
      limit: query.limit,
    })

    const csvStream = csv.format({ headers: true })
    for (const item of result.data) {
      csvStream.write(${model.decoder.excel.encoder.name}(item))
    }
    csvStream.end()

    res.header('Content-Type', 'text/csv')
    res.header('Content-Disposition', 'attachment; filename="${model._conjugated.kebabCasePlural}.csv"')
    res.send(csvStream)
  }

  /**
   * Download ${model.userFriendlyNamePlural} as XLSX.
   */
  @ApiQuery({
    name: 'filters',
    required: false,
    description: \`JSON filter object. Fields: ${filterFieldsDescription}. String fields accept: values (array), contains, startsWith, endsWith, exclude. Date fields accept: from, to. Boolean fields accept an array of true/false.\`,
    example: '${filterExample}',
  })
  @ApiQuery({
    name: 'sort',
    required: false,
    description: \`JSON array of sort objects. Each object has "field" (${filterFieldNames.join(', ')}) and "direction" (asc, desc).\`,
    example: '[{"field":"${stringField?.name ?? filterFieldNames[0] ?? 'id'}","direction":"asc"}]',
  })
  @Get('download/xlsx')
  async download${PascalCasePlural}Xlsx(
    @Query() query: Get${PascalCasePlural}DownloadDto,
    @Res() res: FastifyReply,
  ): Promise<void> {
    const result = await this.viewService.${model.view.service.variableName}.getFilteredPaginated({
      user: this.viewService.users.data.rootUser,
      filters: query.filters ?? {},
      sort: query.sort,
      cursor: query.cursor,
      limit: query.limit,
    })

    const firstEncoded = result.data[0] ? ${model.decoder.excel.encoder.name}(result.data[0]) : null
    const columns = firstEncoded ? Object.keys(firstEncoded) : []
    const rows = result.data.map((item) => Object.values(${model.decoder.excel.encoder.name}(item)))

    const buffer = await createWorkbookBuffer({
      wsName: '${PascalCasePlural}',
      tableName: '${PascalCasePlural}',
      columns,
      rows,
    })

    res.header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
    res.header('Content-Disposition', 'attachment; filename="${model._conjugated.kebabCasePlural}.xlsx"')
    res.send(buffer)
  }
}
`;
}
//# sourceMappingURL=model-controller.generator.js.map