"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateModelTableComponent = generateModelTableComponent;
const generator_1 = require("@postxl/generator");
const utils_1 = require("@postxl/utils");
const model_type_generator_1 = require("../../types/generators/model-type.generator");
function generateModelTableComponent({ model, context, }) {
    const filterFieldArrayName = `${model.types.filter.field.name}Array`;
    const imports = generator_1.ImportGenerator.from(model.table.componentWithoutFilters.location)
        .addType(model.types)
        .addType(model.types.id)
        .addType(model.types.filter.type)
        .addType(model.types.filter.fieldType)
        .add(model.types.filter.config)
        .add({
        name: model.types.filter.field.name,
        alias: filterFieldArrayName,
        location: model.types.filter.field.location,
    })
        .addImport({ from: (0, generator_1.toPackageName)('@types'), items: [(0, generator_1.toTypeName)('FilterValue')] });
    const hookDependencies = [];
    const relatedModels = Array.from(model.relatedModelNames).map((n) => context.models.get(n));
    for (const relatedModel of relatedModels) {
        imports.addType(relatedModel.types);
        imports.addType(relatedModel.types.id);
        hookDependencies.push(relatedModel.table.propName);
    }
    const fieldDefinitions = generateFieldsDefinitions({ model, context });
    return `
import { TrashIcon } from '@radix-ui/react-icons'
import { ColumnDef } from '@tanstack/react-table'

import { useMemo, useEffect, useRef } from 'react'

import { TableFilter } from '@components/admin/table-filter'
import { TableFilterHeaderIcon } from '@components/admin/table-filter-header-icon'
import { Button, Checkbox, DataGrid, DataGridViewMenu, useDataGrid } from '@postxl/ui-components'
import { useTRPC } from '@lib/trpc'
import { ParentSize } from '@visx/responsive'

${imports.generate()}

/**
 * Component that renders a table for ${model.userFriendlyName} model.
  */
export function ${model.table.component.name}({
  tableHeight,
  data,
  filters,
  onFilterChange,
  onDataEdit,
  handleAddRow,
  handleRemoveRows,
  onCellChange,
  onRowSelectionChange,
  ${relatedModels.map((rm) => `${rm.table.propName}`).join(',\n  ')}
}: Readonly<{
  tableHeight?: number
  data: ${model.types.name}[]
  filters: ${model.types.filter.type.name}
  onFilterChange: (filterKey: ${model.types.filter.fieldType.name}, value: FilterValue) => void
  ${model.isReadonly ? `onDataEdit?: (data: ${model.types.name}[]) => void` : `onDataEdit: (data: ${model.types.name}[]) => void`}
  ${relatedModels.map((rm) => `${rm.table.propName}?: Map<${rm.types.id.name}, ${rm.types.name}>`).join('\n  ')}
  handleAddRow?: () => void
  handleRemoveRows?: (countries: ${model.types.id.name}[]) => void
  onCellChange?: (args: { rowIndex: number; columnId: string }) => void
  onRowSelectionChange?: (rowIds: string[]) => void
}>) {
  const trpc = useTRPC()

  const columns = useMemo<ColumnDef<${model.types.name}>[]>(
    () => [
      {
        id: 'select',
        header: ({ table }) => (
          <div className="size-full p-0 m-0 flex items-center gap-2 ">
            <Checkbox
              variant={table.getIsSomePageRowsSelected() ? 'default' : 'simple'}
              iconStyle={table.getIsSomePageRowsSelected() ? 'default' : 'simple'}
              checkIcon="check"
              checked={table.getIsAllPageRowsSelected() || table.getIsSomePageRowsSelected()}
              onChange={(e) => table.toggleAllPageRowsSelected(!!e.target.checked)}
            />
            {handleRemoveRows && (
              <Button
                variant="ghost"
                className="size-5 hover:[&_svg]:text-background hover:bg-destructive/90"
                onClick={() => {
                  const toRemove = Array.from(table.getSelectedRowModel().rows.map((row) => row.original.id))
                  if (toRemove.length === 0) {
                    return
                  }
                  handleRemoveRows(toRemove)
                  table.toggleAllRowsSelected(false)
                }}
              >
                <TrashIcon />
              </Button>
            )}
          </div>
        ),
        cell: ({ row }) => (
          <Checkbox
            variant="simple"
            iconStyle="simple"
            checkIcon="check"
            checked={row.getIsSelected()}
            onChange={(e) => row.toggleSelected(!!e.target.checked)}
          />
        ),
        size: 70,
        enableSorting: false,
        enableHiding: false,
      },
      ${fieldDefinitions.map((def) => generateColumn({ ...def, filterFieldArrayName, filterConfigName: model.types.filter.config.name, trpcRouteName: model.trpcRoute.name })).join(',\n')}
    ],
    [filters, onFilterChange, handleRemoveRows, trpc.${model.trpcRoute.name}, ${hookDependencies.join(', ')}],
  )

  const onRowAdd = () => {
    handleAddRow?.()
    return {
      rowIndex: data.length,
      columnId: columns[0].id,
    }
  }

  const { table, ...dataGridProps } = useDataGrid({
    columns,
    data,
    onDataChange: onDataEdit, // TODO: how do we make sure filtered data is also directly updated, so the old data isn't shown again after editing until the update is confirmed?
    onCellFocus: onCellChange,
    onRowAdd: handleAddRow ? onRowAdd : undefined,
    enableSearch: true,
  })

  // Track row selection changes and notify parent
  const prevSelectedRowIds = useRef<string[]>([])
  const rowSelectionState = table.getState().rowSelection
  useEffect(() => {
    const selectedRowIds = Object.keys(rowSelectionState).filter(key => rowSelectionState[key])
    const prevIds = prevSelectedRowIds.current

    // Only notify if selection actually changed
    const hasChanged = selectedRowIds.length !== prevIds.length ||
      selectedRowIds.some(id => !prevIds.includes(id))

    if (hasChanged) {
      prevSelectedRowIds.current = selectedRowIds
      // Map row indices to entity IDs
      const entityIds: string[] = []
      for (const rowIndex of selectedRowIds) {
        const row = table.getRowModel().rows[Number(rowIndex)]
        if (row?.original?.id) {
          entityIds.push(row.original.id)
        }
      }
      onRowSelectionChange?.(entityIds)
    }
  }, [rowSelectionState, table, onRowSelectionChange])

  return (
    <div className="flex flex-col h-full min-h-0 gap-4">
      <DataGridViewMenu table={table} />
      <div className="h-full overflow-hidden">
        <ParentSize>
          {({ height }) => <DataGrid {...dataGridProps} table={table} height={tableHeight ?? height} />}
        </ParentSize>
      </div>
    </div>
  )
}
`;
}
function generateFieldsDefinitions({ model, context, }) {
    const result = [];
    for (const field of model.fields.values()) {
        result.push(...getFieldDefinitionsForField({ field, model, context }));
    }
    return result;
}
function quote(str) {
    const singleQuote = String.raw `\'`;
    return `'${str.replaceAll("'", singleQuote)}'`;
}
function generateColumn({ filterFieldArrayName, filterConfigName, trpcRouteName, column: { id, accessor, header, variant, size, readonly, duParentFieldName }, options, }) {
    let optionsString = undefined;
    switch (options?.kind) {
        case 'static': {
            optionsString = `[${options.options
                .map((o) => {
                const label = quote(o.label);
                const value = quote(o.value);
                return `{ label: ${label}, value: ${value} }`;
            })
                .join(', ')}]`;
            break;
        }
        case 'dynamic': {
            optionsString = options.expression;
            break;
        }
    }
    optionsString = optionsString ? `, options: ${optionsString}` : '';
    const hasSearchString = variant === 'select' && options?.kind === 'dynamic' ? ', hasSearch: true' : '';
    const cellString = `  cell: { variant: '${variant}'${optionsString}${hasSearchString} },\n`;
    const editableString = readonly ? '  editable: false, \n' : '';
    const filterFieldName = (0, model_type_generator_1.getFilterFieldName)(id, duParentFieldName);
    const filterString = `  ...${filterFieldArrayName}.includes('${filterFieldName}' as any) && {
      headerMenuFooter: ({ open }) => (
        <TableFilter
          open={open}
          field={'${filterFieldName}'}
          filters={filters}
          onChange={(val) => onFilterChange('${filterFieldName}', val)}
          config={${filterConfigName}}
          getFilterOptions={(args) => trpc.${trpcRouteName}.getFilterOptions.queryOptions(args)}
        />
      ),
      headerCustomComponent: <TableFilterHeaderIcon filterValue={filters['${filterFieldName}' as keyof typeof filters]} />,
    }\n`;
    return `{
    id: '${id}',
    ${accessor},
    header: ${quote(header)},
    meta: {${cellString}${editableString}${filterString}},
    size: ${size},
  }`;
}
function calculateWidth(title) {
    const minWidth = 150;
    const maxWidth = 300;
    const titleWidth = title.length * 8;
    return Math.min(maxWidth, Math.max(minWidth, titleWidth));
}
function generateColumnHeader({ field, duParentField, duMember }) {
    if (duParentField) {
        const memberLabel = duMember ? `${duMember.label}: ` : '';
        return `${duParentField.label ?? ''}: ${memberLabel}${field.label}`;
    }
    return field.label;
}
function generateCellVariant({ field, }) {
    switch (field.kind) {
        case 'id':
            return 'short-text';
        case 'scalar':
            return generateCellVariantForScalar(field);
        case 'relation':
            return 'select';
        case 'enum':
            return 'select';
        case 'discriminatedUnion':
            return 'short-text';
        default:
            throw new utils_1.ExhaustiveSwitchCheck(field);
    }
}
function generateCellVariantForScalar(field) {
    switch (field.type) {
        case 'number':
            return 'number';
        case 'string':
            return (field.validations.maxLength ?? 0) > 200 ? 'long-text' : 'short-text';
        case 'Date':
            return 'date';
        case 'boolean':
            return 'checkbox';
        default:
            throw new utils_1.ExhaustiveSwitchCheck(field);
    }
}
function getFieldDefinitionsForField(params) {
    if (params.field.kind === 'discriminatedUnion') {
        return getFieldDiscriminatedUnionColumns(params);
    }
    return [getFieldDefinitionForField(params)];
}
function getFieldDefinitionForField(params) {
    return {
        column: {
            id: params.field.name,
            accessor: generateFieldAccessor(params),
            header: generateColumnHeader(params),
            variant: generateCellVariant(params),
            readonly: params.model.isReadonly || (params.field.kind === 'scalar' && params.field.isReadonly),
            size: calculateWidth(params.field.name),
            duParentFieldName: params.duParentField?.name,
        },
        options: getOptions(params),
    };
}
function generateRelationAccessor({ field, model, duParentField, duMember }) {
    if (duParentField === undefined) {
        return `accessorKey: '${field.name}'`;
    }
    if (duMember) {
        // Field that only belongs to one DU member
        const memberCondition = `row.${duParentField.name}?.type === '${duMember.type}'`;
        return `accessorFn: (row: ${model.types.name}) => ${memberCondition} ? row.${duParentField.name}?.${field.name} ?? null : null`;
    }
    return `accessorFn: (row: ${model.types.name}) => row.${duParentField.name}?.${field.name} ?? null`;
}
function generateFieldAccessor({ field, model, context, duParentField, duMember }) {
    switch (field.kind) {
        case 'relation':
            return generateRelationAccessor({ field, model, duParentField, duMember, context });
        case 'discriminatedUnion':
            return `accessorFn: (row: ${model.types.name}) => row.${field.name}?.type ?? ''`;
        default: {
            const duMemberTypeString = duMember ? `?.type === '${duMember.type}'` : '';
            return duParentField === undefined
                ? `accessorKey: '${field.name}'`
                : `accessorFn: (row: ${model.types.name}) => row.${duParentField.name}${duMemberTypeString} ? row.${duParentField.name}?.${field.name} ?? '' : ''`;
        }
    }
}
function getOptions({ field, context }) {
    switch (field.kind) {
        case 'enum': {
            const enum_ = context.enums.get(field.enumName);
            return {
                kind: 'static',
                options: [...enum_.members.values()].map((m) => ({
                    label: String(m.value),
                    value: String(m.value),
                })),
            };
        }
        case 'discriminatedUnion': {
            const options = [];
            for (const member of field.members.values()) {
                options.push({
                    label: member.label,
                    value: member.type,
                });
            }
            return { kind: 'static', options };
        }
        case 'relation': {
            const relatedModel = context.models.get(field.referencedModelName);
            const optionsExpression = `${relatedModel.table.propName} ? Array.from(${relatedModel.table.propName}.values()).map((item) => ({ label: item.${relatedModel.labelField.name}, value: item.id })) : []`;
            return { kind: 'dynamic', expression: optionsExpression };
        }
        default:
            return undefined;
    }
}
function getFieldDiscriminatedUnionColumns(params) {
    const field = params.field;
    const fieldDefinitions = [getFieldDefinitionForField({ ...params, field })];
    for (const commonField of field.commonFields.values()) {
        fieldDefinitions.push(...getFieldDefinitionsForField({ ...params, field: commonField, duParentField: field }));
    }
    for (const member of field.members.values()) {
        for (const memberField of member.fields.values()) {
            fieldDefinitions.push(...getFieldDefinitionsForField({
                ...params,
                field: memberField,
                duParentField: field,
                duMember: member,
            }));
        }
    }
    return fieldDefinitions;
}
//# sourceMappingURL=model-table.generator.js.map