"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.generateModelViewService = generateModelViewService;
exports.generateModelViewService_NoRepository = generateModelViewService_NoRepository;
const Generator = __importStar(require("@postxl/generator"));
const model_type_generator_1 = require("../types/generators/model-type.generator");
function generateModelViewService({ model, context }) {
    const imports = Generator.ImportGenerator.from(model.view.service.location);
    imports.addType(model.types);
    imports.addType({ name: Generator.toTypeName('User'), location: context.types.location });
    imports.addType(model.types.id);
    imports.addType(model.types.filter.type);
    imports.addType(model.types.filter.fieldType);
    imports.add(model.types.filter.config);
    imports.add(context.view.service);
    imports.addImport({
        items: [
            Generator.toFunctionName('applyRangeFilters'),
            Generator.toFunctionName('getMatches'),
            Generator.toFunctionName('getOptions'),
            Generator.toFunctionName('formatAndSortOptions'),
        ],
        from: Generator.toBackendModuleLocation('@view/filter.utils'),
    });
    imports.addImport({
        items: [Generator.toFunctionName('omit')],
        from: Generator.toBackendModuleLocation('@postxl/utils'),
    });
    if (model.repository.kind === 'NoRepository') {
        return generateModelViewService_NoRepository({ model, context, imports });
    }
    else {
        return generateModelViewService_HasRepository({ model, context, imports });
    }
}
function generateModelViewService_HasRepository({ model, context, imports, }) {
    if (model.repository.kind === 'NoRepository') {
        return '';
    }
    imports.add(model.repository);
    const filteringMethods = generateFilteringMethods({ model, imports });
    return `
import { Inject, Injectable, forwardRef } from '@nestjs/common'

${imports.generate()}

@Injectable()
export class ${model.view.service.name} {
  constructor(
    public readonly data: ${model.repository.name},
    @Inject(forwardRef(() => ${context.view.service.name})) 
        private readonly viewService: ${context.view.service.name},
  ) {}

  /**
   * Returns the raw ${model.userFriendlyName} with the given id or null if it does not exist.
   * Raw: The ${model.userFriendlyName} only contains linked Ids, not the linked items themselves.
   */
  public async get({ id, user }: { id: ${model.types.id.name}, user: User }): Promise<${model.types.name} | null> {
    const item = await this.data.get(id)
    if (!item) {
      return null
    }

    await this.authorize({ item, user })

    return item
  }

  /**
   * Returns a map of all ${model.userFriendlyNamePlural}.
   */
  public async getAll(user: User): Promise<Map<${model.types.id.name}, ${model.types.name}>> {
        const result = await this.data.getAll()

        // Removing non-authorized items from the result.
        for (const [key, value] of result.entries()) {
          try {
            await this.authorize({ item: value, user })
          } catch {
            result.delete(key)
          }
        }
    
        return result
  }

  /**
   * Returns a list of all ${model.userFriendlyNamePlural}.
   */
  public async getList(user: User): Promise<${model.types.name}[]> {
        const result: ${model.types.name}[] = []

        // Removing non-authorized items from the result.
        for (const item of (await this.data.getAll()).values()) {
          try {
            await this.authorize({ item, user })
            result.push(item)
          } catch {
            // Do nothing.
          }
        }
    
        return result
  }

  ${filteringMethods}

  ${generateAuthorizeMethod({ model, context })}
}
`;
}
function generateModelViewService_NoRepository({ model, context, imports, }) {
    if (model.repository.kind !== 'NoRepository') {
        return '';
    }
    // imports.addImports({
    //   [model.types.importPath]: [toAnnotatedTypeName(toTypeName('User'))],
    // })
    return `
/* eslint-disable @typescript-eslint/no-unused-vars */
import z from 'zod'

import { Inject, Injectable, forwardRef } from '@nestjs/common'

${imports.generate()}

@Injectable()
export class ${model.view.service.name} {
  constructor(
    @Inject(forwardRef(() => ${context.view.service.name})) 
        private readonly viewService: ${context.view.service.name},
  ) {}

  /**
   * Returns the raw ${model.userFriendlyName} with the given id or null if it does not exist.
   * Raw: The ${model.userFriendlyName} only contains linked Ids, not the linked items themselves.
   */
  public async get({ id, user }: { id: ${model.types.id.name}, user: User }): Promise<${model.types.name} | null> {
  
    // TODO: Implement this method.  
    const item = await Promise.resolve(null) // NOSONAR - adhere to the same interface
      
    if (!item) {
      return null
    }

    await this.authorize({ item, user })

    return item
  }

  /**
   * Returns a map of all ${model.userFriendlyNamePlural}.
   */
  public async getAll(user: User): Promise<Map<${model.types.id.name}, ${model.types.name}>> {
        // TODO: Implement this method.
        const result = await Promise.resolve(new Map()) // NOSONAR - adhere to the same interface

        // Removing non-authorized items from the result.
        for (const [key, value] of result.entries()) {
          try {
            await this.authorize({ item: value, user })
          } catch (e) {
            result.delete(key)
          }
        }
    
        return result
  }

  /**
   * Returns a filtered list of ${model.userFriendlyNamePlural}.
   */
  public async getFiltered({ filters, user }: { filters: ${model.types.filter.type.name}, user: User }): Promise<${model.types.name}[]> {
    // TODO: Implement this method.
    return this.getList(user)
  }

  /**
   * Returns the filter options for a given field.
   */
  public async getFilterOptions({ field, filters, user }: { field: ${model.types.filter.field.name}, filters: ${model.types.filter.type.name}, user: User }): Promise<any> {
    // TODO: Implement this method.
    return []
  }

  ${generateAuthorizeMethod({ model, context })}
}`;
}
function generateAuthorizeMethod({ model }) {
    return `/**
  * Authorizes access to ${model.userFriendlyName}.
  */
  public async authorize({ item: _item, user: _user}: { item: ${model.types.name}, user: User }): Promise<void> {
    // TODO: Implement authorization logic here.
    return Promise.resolve() // NOSONAR - adhere to the same interface
  }`;
}
function generateFilteringMethods({ model, imports, }) {
    const filterFields = [];
    const usedMatchMethods = new Set();
    // Generate filter conditions for all fields, including nested DU fields
    for (const field of model.fields.values()) {
        const conditions = getFilterConditionsForField({
            field,
            usedMatchMethods,
        });
        filterFields.push(...conditions);
    }
    if (usedMatchMethods.size > 0) {
        imports.addImport({
            items: Array.from(usedMatchMethods).map((m) => Generator.toFunctionName(m)),
            from: Generator.toBackendModuleLocation('@view/filter.utils'),
        });
    }
    return `
  /**
   * Returns a filtered list of ${model.userFriendlyNamePlural}.
   * For really large datasets, consider implementing server-side filtering in the repository instead.
   */
  public async getFiltered({ filters, user }: { filters: ${model.types.filter.type.name}, user: User }): Promise<${model.types.name}[]> {
    const allItems = await this.getList(user)
    return this.filter${model._conjugated.PascalCasePlural}(allItems, filters)
  }

  /**
   * Returns the filter options for a given field.
   * For really large datasets, consider implementing server-side filtering in the repository instead.
   */
  public async getFilterOptions({
    field,
    filters,
    user,
  }: {
    field: ${model.types.filter.fieldType.name}
    filters: ${model.types.filter.type.name}
    user: User
  }): Promise<{ value: string; label: string; hasMatches: boolean }[]> {
    const allItems = await this.getList(user)
    const reducedFilters = omit(filters, field)
    const filteredItems = this.filter${model._conjugated.PascalCasePlural}(allItems, reducedFilters)

    const config = ${model.types.filter.config.name}[field]
    const accessor = this.getFieldAccessor(field)

    // If we are getting options for a number/date field, we want to respect the range filter (so options outside the range are grayed out), but still ignore the values filter (so we can see all options).
    const itemsWithRange = applyRangeFilters(filteredItems, accessor, config, filters)

    let referencedItems: Map<string, any> | undefined
    if (config.kind === 'relation') {
      referencedItems = await (this.viewService as any)[config.referencedModelViewServiceVariableName]?.getAll(user)
    }

    const { matches, options } = {
      matches: getMatches(itemsWithRange, accessor),
      options: getOptions(allItems, accessor, config, referencedItems),
    }

    return formatAndSortOptions(options, matches)
  }

  /**
   * Helper method to filter ${model.userFriendlyNamePlural} based on provided filters.
   */
  private filter${model._conjugated.PascalCasePlural}(
    ${model._conjugated.camelCasePlural}: ${model.types.name}[],
    filters: ${model.types.filter.type.name},
  ): ${model.types.name}[] {
    return ${model._conjugated.camelCasePlural}.filter((item) => {
    ${filterFields.join('')}
      return true
    })
  }

  ${generateGetFieldAccessorMethod({ model })}
`;
}
function generateGetFieldAccessorMethod({ model }) {
    const accessors = [];
    // Generate accessor cases for all fields, including nested DU fields
    for (const field of model.fields.values()) {
        const cases = generateFieldAccessorCases({
            field,
            model,
        });
        accessors.push(...cases);
    }
    const defaultAccessor = `return (item: ${model.types.name}) => (item as any)[field]`;
    // Add default case
    accessors.push(`      default:
        ${defaultAccessor}`);
    return `/**
   * Returns an accessor function for the given filter field.
   */
  private getFieldAccessor(field: ${model.types.filter.fieldType.name}): (item: ${model.types.name}) => any {
  ${accessors.length > 1
        ? `switch (field) {
    ${accessors.join('\n')}
    }`
        : defaultAccessor}
  }`;
}
function generateFieldAccessorCases(params) {
    const { field, model, duParentFieldName, duMemberType } = params;
    const result = [];
    // Handle discriminated union: add the discriminant field plus all nested fields
    if (field.kind === 'discriminatedUnion') {
        // Add the discriminant field itself
        const caseString = generateFieldAccessorCase({ field, model, duParentFieldName, duMemberType });
        if (caseString) {
            result.push(caseString);
        }
        // Add common fields
        for (const commonField of field.commonFields.values()) {
            result.push(...generateFieldAccessorCases({
                field: commonField,
                model,
                duParentFieldName: field.name,
            }));
        }
        // Add member-specific fields
        for (const member of field.members.values()) {
            for (const memberField of member.fields.values()) {
                result.push(...generateFieldAccessorCases({
                    field: memberField,
                    model,
                    duParentFieldName: field.name,
                    duMemberType: member.type,
                }));
            }
        }
    }
    else {
        // Regular field
        const caseString = generateFieldAccessorCase({ field, model, duParentFieldName, duMemberType });
        if (caseString) {
            result.push(caseString);
        }
    }
    return result;
}
function generateFieldAccessorCase({ field, model, duParentFieldName, duMemberType, }) {
    // For discriminated union fields at top level, we need to return the discriminant (type) value
    if (field.kind === 'discriminatedUnion' && !duParentFieldName) {
        return `      case '${field.name}':
        return (item: ${model.types.name}) => item.${field.name}?.type`;
    }
    // Skip generating a case for simple top-level fields - the default case handles them
    if (!duParentFieldName) {
        return null;
    }
    const caseKey = `'${(0, model_type_generator_1.getFilterFieldName)(field.name, duParentFieldName)}'`;
    let accessorExpression;
    if (duMemberType) {
        // Member-specific field: only return value if the member type matches
        accessorExpression = `item.${duParentFieldName}?.type === '${duMemberType}' ? item.${duParentFieldName}?.${field.name} : undefined`;
    }
    else {
        // Common field in DU
        accessorExpression = `item.${duParentFieldName}?.${field.name}`;
    }
    return `      case ${caseKey}:
        return (item: ${model.types.name}) => ${accessorExpression}`;
}
/**
 * Returns all filter conditions for a field, including nested DU fields.
 */
function getFilterConditionsForField(params) {
    const { field, usedMatchMethods, duParentFieldName, duMemberType } = params;
    const result = [];
    // Handle discriminated union: add the discriminant field plus all nested fields
    if (field.kind === 'discriminatedUnion') {
        // Add the discriminant field itself
        result.push(generateFilterCondition({ field, usedMatchMethods, duParentFieldName, duMemberType }));
        // Add common fields
        for (const commonField of field.commonFields.values()) {
            result.push(...getFilterConditionsForField({
                field: commonField,
                usedMatchMethods,
                duParentFieldName: field.name,
            }));
        }
        // Add member-specific fields
        for (const member of field.members.values()) {
            for (const memberField of member.fields.values()) {
                result.push(...getFilterConditionsForField({
                    field: memberField,
                    usedMatchMethods,
                    duParentFieldName: field.name,
                    duMemberType: member.type,
                }));
            }
        }
    }
    else {
        // Regular field
        result.push(generateFilterCondition(params));
    }
    return result;
}
/**
 * Generates a single filter condition for a field.
 */
function generateFilterCondition({ field, usedMatchMethods, duParentFieldName, duMemberType, }) {
    const valueType = (0, model_type_generator_1.getFilterValueType)(field);
    let matchMethod = 'matchFilter';
    if (valueType === 'number') {
        matchMethod = 'matchNumberFilter';
    }
    else if (valueType === 'date') {
        matchMethod = 'matchDateFilter';
    }
    usedMatchMethods.add(matchMethod);
    // Build the accessor path for the field value
    let accessor;
    if (duParentFieldName) {
        if (duMemberType) {
            // Member-specific field: only check if the member type matches
            accessor = `item.${duParentFieldName}?.type === '${duMemberType}' ? item.${duParentFieldName}?.${field.name} : undefined`;
        }
        else {
            // Common field in DU
            accessor = `item.${duParentFieldName}?.${field.name}`;
        }
    }
    else if (field.kind === 'discriminatedUnion') {
        // DU field at top level: filter by the discriminant (type) value
        accessor = `item.${field.name}?.type`;
    }
    else {
        accessor = `item.${field.name}`;
    }
    return `
      if (!${matchMethod}(${accessor}, filters.${(0, model_type_generator_1.getFilterFieldName)(field.name, duParentFieldName)})) {
        return false
      }`;
}
//# sourceMappingURL=model-view-service.generator.js.map