"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateModelHook = generateModelHook;
const generator_1 = require("@postxl/generator");
function addCrudPayloadImports({ addPayloadType, standardActions, }) {
    const payloadImports = [
        { include: Boolean(standardActions.create) || Boolean(standardActions.createMany), name: 'CreatePayload' },
        { include: Boolean(standardActions.update) || Boolean(standardActions.updateMany), name: 'UpdatePayload' },
        { include: Boolean(standardActions.updateField), name: 'UpdateFieldPayload' },
        { include: Boolean(standardActions.updateFieldMany), name: 'UpdateFieldManyPayload' },
        { include: Boolean(standardActions.clone) || Boolean(standardActions.cloneMany), name: 'ClonePayload' },
    ];
    for (const payloadImport of payloadImports) {
        if (!payloadImport.include) {
            continue;
        }
        addPayloadType(payloadImport.name);
    }
}
function appendEnabledCrudOperations(state, definitions) {
    for (const definition of definitions) {
        if (!definition.enabled) {
            continue;
        }
        state.types.push(definition.typeLine);
        state.hooks.push(definition.hookLine);
        state.exports.push(definition.exportLine);
    }
    return state;
}
function buildCrudHook(model) {
    const { itemsHook, trpcRoute } = model;
    const state = { types: [], hooks: [], exports: [] };
    return appendEnabledCrudOperations(state, [
        {
            enabled: !!itemsHook.update,
            typeLine: `${itemsHook.update?.name}: (data: UpdatePayload) => Promise<${model.types.viewModel.name}>`,
            hookLine: `const updateMutation = useMutation(trpc.${trpcRoute.name}.update.mutationOptions({ onSuccess: invalidate }))`,
            exportLine: `${itemsHook.update?.name}: updateMutation.mutateAsync,`,
        },
        {
            enabled: !!itemsHook.updateMany,
            typeLine: `${itemsHook.updateMany?.name}: (data: UpdatePayload[]) => Promise<${model.types.viewModel.name}[]>`,
            hookLine: `const updateManyMutation = useMutation(trpc.${trpcRoute.name}.updateMany.mutationOptions({ onSuccess: invalidate }))`,
            exportLine: `${itemsHook.updateMany?.name}: updateManyMutation.mutateAsync,`,
        },
        {
            enabled: !!itemsHook.updateField,
            typeLine: `${itemsHook.updateField?.name}: (data: UpdateFieldPayload) => Promise<${model.types.viewModel.name}>`,
            hookLine: `const updateFieldMutation = useMutation(trpc.${trpcRoute.name}.updateField.mutationOptions({ onSuccess: invalidate }))`,
            exportLine: `${itemsHook.updateField?.name}: updateFieldMutation.mutateAsync,`,
        },
        {
            enabled: !!itemsHook.updateFieldMany,
            typeLine: `${itemsHook.updateFieldMany?.name}: (data: UpdateFieldManyPayload) => Promise<${model.types.viewModel.name}[]>`,
            hookLine: `const updateFieldManyMutation = useMutation(trpc.${trpcRoute.name}.updateFieldMany.mutationOptions({ onSuccess: invalidate }))`,
            exportLine: `${itemsHook.updateFieldMany?.name}: updateFieldManyMutation.mutateAsync,`,
        },
        {
            enabled: !!itemsHook.create,
            typeLine: `${itemsHook.create?.name}: (data: CreatePayload) => Promise<${model.types.viewModel.name}>`,
            hookLine: `const createMutation = useMutation(trpc.${trpcRoute.name}.create.mutationOptions({ onSuccess: invalidate }))`,
            exportLine: `${itemsHook.create?.name}: createMutation.mutateAsync,`,
        },
        {
            enabled: !!itemsHook.createMany,
            typeLine: `${itemsHook.createMany?.name}: (data: CreatePayload[]) => Promise<${model.types.viewModel.name}[]>`,
            hookLine: `const createManyMutation = useMutation(trpc.${trpcRoute.name}.createMany.mutationOptions({ onSuccess: invalidate }))`,
            exportLine: `${itemsHook.createMany?.name}: createManyMutation.mutateAsync,`,
        },
        {
            enabled: !!itemsHook.delete,
            typeLine: `${itemsHook.delete?.name}: (id: ${model.types.id.name}) => Promise<${model.types.id.name}>`,
            hookLine: `const deleteMutation = useMutation(trpc.${trpcRoute.name}.delete.mutationOptions({ onSuccess: invalidate }))`,
            exportLine: `${itemsHook.delete?.name}: deleteMutation.mutateAsync,`,
        },
        {
            enabled: !!itemsHook.deleteMany,
            typeLine: `${itemsHook.deleteMany?.name}: (ids: ${model.types.id.name}[]) => Promise<${model.types.id.name}[]>`,
            hookLine: `const deleteManyMutation = useMutation(trpc.${trpcRoute.name}.deleteMany.mutationOptions({ onSuccess: invalidate }))`,
            exportLine: `${itemsHook.deleteMany?.name}: deleteManyMutation.mutateAsync,`,
        },
        {
            enabled: !!itemsHook.clone,
            typeLine: `${itemsHook.clone?.name}: (data: ClonePayload) => Promise<${model.types.viewModel.name}>`,
            hookLine: `const cloneMutation = useMutation(trpc.${trpcRoute.name}.clone.mutationOptions({ onSuccess: invalidate }))`,
            exportLine: `${itemsHook.clone?.name}: cloneMutation.mutateAsync,`,
        },
        {
            enabled: !!itemsHook.cloneMany,
            typeLine: `${itemsHook.cloneMany?.name}: (data: ClonePayload[]) => Promise<${model.types.viewModel.name}[]>`,
            hookLine: `const cloneManyMutation = useMutation(trpc.${trpcRoute.name}.cloneMany.mutationOptions({ onSuccess: invalidate }))`,
            exportLine: `${itemsHook.cloneMany?.name}: cloneManyMutation.mutateAsync,`,
        },
    ]);
}
function generateModelHook({ model, context }) {
    const { itemHook, itemsHook, trpcRoute } = model;
    const imports = generator_1.ImportGenerator
        //
        .from(model.itemHook.location)
        .addType(model.types.viewModel)
        .addType(model.types.id)
        .addType(model.types.filter.type)
        .addType(model.types.filter.fieldType)
        .add(context.trpcClient)
        .addImports({
        [(0, generator_1.toPackageName)('@tanstack/react-query')]: [(0, generator_1.toFunctionName)('useQuery'), (0, generator_1.toFunctionName)('useInfiniteQuery')],
        [(0, generator_1.toPackageName)('@types')]: [(0, generator_1.toTypeName)('SortState')],
    });
    let itemsCrudHook = { types: '', hooks: '', exports: '' };
    if (model.update.kind === 'exists') {
        const updateServiceLocation = model.update.service.location;
        addCrudPayloadImports({
            addPayloadType: (payloadName) => {
                imports.addType({ name: (0, generator_1.toTypeName)(payloadName), location: updateServiceLocation });
            },
            standardActions: model.update.action.standardActions,
        });
        imports.addImports({
            [(0, generator_1.toPackageName)('@tanstack/react-query')]: [(0, generator_1.toFunctionName)('useMutation')],
            [(0, generator_1.toPackageName)('@lib/query-client')]: [(0, generator_1.toFunctionName)('queryClient')],
        });
        const crudHookState = buildCrudHook(model);
        itemsCrudHook = {
            types: crudHookState.types.join('\n        '),
            hooks: `
        const invalidate = async () => {
          const paginatedPredicate = (query: { queryKey: unknown }) => {
            const key = query.queryKey
            // tRPC query keys are arrays where first element contains the procedure path
            // e.g., [['posts', 'getFilteredPaginated'], { input, type }]
            if (Array.isArray(key) && Array.isArray(key[0])) {
              const path = key[0]
              return path[0] === '${trpcRoute.name}' && path[1] === 'getFilteredPaginated'
            }
            return false
          }

          await Promise.all([
            queryClient.invalidateQueries({ queryKey: trpc.${trpcRoute.name}.getMap.queryKey() }),
            queryClient.invalidateQueries({ queryKey: trpc.${trpcRoute.name}.getFiltered.queryKey() }),
            // For infinite query, just refetch without resetting - keeps data visible during refetch
            queryClient.refetchQueries({ predicate: paginatedPredicate }),
          ])
        }

        ${crudHookState.hooks.join('\n        ')}
        `,
            exports: crudHookState.exports.join('\n        '),
        };
    }
    return `
import { useMemo } from 'react'
${imports.generate()}

/**
 * Options for the ${itemsHook.name} hook.
 */
export type ${itemsHook.type.name}Options = {
  /**
   * Optional filters to apply.
   */
  filters?: ${model.types.filter.type.name}
  /**
   * Optional sort configuration.
   */
  sort?: SortState<${model.types.filter.fieldType.name}>
  /**
   * When true, uses paginated fetching with infinite scroll support.
   * When false, fetches all filtered data at once (original behavior).
   * @default true
   */
  paginated?: boolean
  /**
   * Number of items per page when using paginated mode.
   * @default 100
   */
  pageSize?: number
  /**
   * When false, disables all data fetching. Useful when providing data externally.
   * @default true
   */
  enabled?: boolean
}

/**
 * Return type for the ${itemsHook.name} hook.
 */
export type ${itemsHook.type.name} = {
  /** All items (unfiltered) as an array */
  ${itemsHook.list.name}: ${model.types.viewModel.name}[]
  /** All items (unfiltered) as a Map keyed by ID */
  ${itemsHook.map.name}: Map<${model.types.id.name}, ${model.types.viewModel.name}>
  /** Filtered (+ sorted) items as an array. When paginated, contains all loaded pages flattened. */
  ${itemsHook.filteredList.name}: ${model.types.viewModel.name}[]
  /** Filtered (+ sorted) items as a Map keyed by ID */
  ${itemsHook.filteredMap.name}: Map<${model.types.id.name}, ${model.types.viewModel.name}>
  /** Whether the initial data has been loaded */
  ${itemsHook.isLoaded.name}: boolean
  /**
   * Total count of ALL matching items in the database (not just loaded ones).
   * Useful for displaying "Showing X of Y items".
   * When not paginated, equals filteredList.length.
   */
  total: number
  /**
   * Whether there are more pages available to load.
   * Only relevant when using paginated mode.
   */
  hasNextPage: boolean
  /**
   * Triggers loading the next page of data.
   * Call this when the user scrolls near the end of the list.
   * Only relevant when using paginated mode.
   */
  fetchNextPage: () => Promise<unknown>
  /**
   * Whether the next page is currently being fetched.
   * Use this to show a loading indicator during infinite scroll.
   */
  isFetchingNextPage: boolean

  ${itemsCrudHook.types}
}


export const ${itemHook.name} = (id: ${model.types.id.name} | null):  ${model.types.viewModel.name} | null => {
  const trpc = ${context.trpcClient.name}()

  const { data, isPending } = useQuery(trpc.${trpcRoute.name}.get.queryOptions(id!, { enabled: id !== null })) // NOSONAR

  return useMemo((): ${model.types.viewModel.name} | null => (isPending || !data) ? null : data, [data, isPending])
}

/**
 * Hook for fetching a single ${model.userFriendlyName} by its key field value.
 * Returns null if not found or while loading.
 */
export const ${itemHook.name}ByKey = (key: string | null):  ${model.types.viewModel.name} | null => {
  const trpc = ${context.trpcClient.name}()

  const { data, isPending } = useQuery(trpc.${trpcRoute.name}.getByKey.queryOptions(key!, { enabled: key !== null })) // NOSONAR

  return useMemo((): ${model.types.viewModel.name} | null => (isPending || !data) ? null : data, [data, isPending])
}

/**
 * Hook for fetching and managing ${model.userFriendlyNamePlural}.
 *
 * @param options - Configuration options
 * @param options.filters - Optional filters to apply
 * @param options.sort - Optional sort configuration
 * @param options.paginated - When true (default), uses infinite scroll pagination. When false, fetches all data at once.
 * @param options.pageSize - Items per page when paginated (default: 100)
 * @param options.enabled - When false, disables all data fetching (default: true)
 */
export const ${itemsHook.name} = (
  options: ${itemsHook.type.name}Options = {},
): ${itemsHook.type.name} => {
  const { filters, sort, paginated = true, pageSize = 100, enabled = true } = options
  const trpc = ${context.trpcClient.name}()

  // Determine if we need to fetch filtered data (when filters or sort are provided)
  const isFiltered = useMemo(
    () => (filters && Object.keys(filters).length > 0) || sort !== undefined,
    [filters, sort],
  )

  const mapQuery = useQuery({ ...trpc.${trpcRoute.name}.getMap.queryOptions(), enabled })

  // Non-paginated query: fetches all filtered data at once
  const filteredQuery = useQuery({
    ...trpc.${trpcRoute.name}.${model.trpcRoute.getFiltered.name}.queryOptions({ filters: filters ?? {}, sort }),
    enabled: enabled && !!isFiltered && !paginated,
  })

  // Paginated query: fetches data in pages for infinite scroll
  // Each page contains { data: T[], total: number, cursor: number | null }
  // Pages are accumulated and flattened into a single list
  // Note: cursor is at top level for tRPC infinite query support - tRPC adds cursor automatically
  const infiniteQuery = useInfiniteQuery({
    ...trpc.${trpcRoute.name}.${model.trpcRoute.getFilteredPaginated.name}.infiniteQueryOptions(
      { filters: filters ?? {}, sort, limit: pageSize },
      { getNextPageParam: (lastPage) => lastPage.cursor }
    ),
    initialPageParam: 1,
    enabled: enabled && !!isFiltered && paginated,
  })

  ${itemsCrudHook.hooks}

  const ${itemsHook.map.name} = useMemo((): Map<${model.types.id.name}, ${model.types.viewModel.name}> => (mapQuery.isPending || !mapQuery.data) ? new Map() : mapQuery.data, [mapQuery.data, mapQuery.isPending])

  const ${itemsHook.list.name} = useMemo((): ${model.types.viewModel.name}[] => Array.from(${itemsHook.map.name}.values()), [${itemsHook.map.name}])

  // Build filtered list based on mode:
  // - Not filtered: return full list
  // - Paginated: flatten all loaded pages into single array
  // - Non-paginated: return the single query result
  const ${itemsHook.filteredList.name} = useMemo((): ${model.types.viewModel.name}[] => {
    if (!isFiltered) {
      return ${itemsHook.list.name}
    }
    if (paginated) {
      return infiniteQuery.data?.pages.flatMap(page => page.data) ?? []
    }
    return filteredQuery.data ?? []
  }, [isFiltered, paginated, infiniteQuery.data, filteredQuery.data, ${itemsHook.list.name}])

  const ${itemsHook.filteredMap.name} = useMemo((): Map<${model.types.id.name}, ${model.types.viewModel.name}> => {
    if (!isFiltered) {
      return ${itemsHook.map.name}
    }
    return new Map(${itemsHook.filteredList.name}.map((i) => [i.${model.idField.name}, i]))
  }, [isFiltered, ${itemsHook.filteredList.name}, ${itemsHook.map.name}])

  // Total count: from paginated response, or just the list length
  const total = paginated
    ? (infiniteQuery.data?.pages[0]?.total ?? ${itemsHook.list.name}.length)
    : ${itemsHook.filteredList.name}.length

  return {
    ${itemsHook.list.name},
    ${itemsHook.map.name},
    ${itemsHook.filteredList.name},
    ${itemsHook.filteredMap.name},
    ${itemsHook.isLoaded.name}: !mapQuery.isPending && !mapQuery.isError,
    total,
    hasNextPage: paginated ? (infiniteQuery.hasNextPage ?? false) : false,
    fetchNextPage: paginated ? infiniteQuery.fetchNextPage : () => Promise.resolve(),
    isFetchingNextPage: paginated ? infiniteQuery.isFetchingNextPage : false,

    ${itemsCrudHook.exports}
  }
}`;
}
//# sourceMappingURL=model-hook.generator.js.map