"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateModelTableComponent = generateModelTableComponent;
const generator_1 = require("@postxl/generator");
const utils_1 = require("@postxl/utils");
function generateModelTableComponent({ model, context, }) {
    const imports = generator_1.ImportGenerator
        //
        .from(model.table.componentWithoutFilters.location)
        .addType(model.types)
        .addType(model.types.id);
    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 filterType = `${model._conjugated.PascalCase}Filter`;
    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 { Button } from '@components/ui/button/button'
import { Checkbox } from '@components/ui/checkbox/shadcn-checkbox'
import { DataGrid } from '@components/ui/data-grid/data-grid'
import { DataGridViewMenu } from '@components/ui/data-grid/data-grid-view-menu'
import { useDataGrid } from '@components/ui/data-grid/hooks/use-data-grid'
import { ParentSize } from '@visx/responsive'

${imports.generate()}

/* type ${filterType} = {
  key: keyof ${model.types.name}
  label: string
  options: string[]
  selected: string | null
} */

/**
 * Component that renders a table for ${model.userFriendlyName} model.
  */
export function ${model.table.component.name}({
  tableHeight,
  data,
  onDataEdit,
  handleAddRow,
  handleRemoveRows,
  onCellChange,
  onRowSelectionChange,
  ${relatedModels.map((rm) => `${rm.table.propName}`).join(',\n  ')}
}: {
  tableHeight?: number
  data: ${model.types.name}[]
  ${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 [filters, _setFilters] = useState<${filterType}[]>([
    ${fieldDefinitions.map((def) => generateFilter(def)).join(',\n')}
  ]) */

  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
              checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate')}
              onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
            />
            {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 checked={row.getIsSelected()} onCheckedChange={(value) => row.toggleSelected(!!value)} />
        ),
        size: 70,
        enableSorting: false,
        enableHiding: false,
      },
      ${fieldDefinitions.map((def) => generateColumn(def)).join(',\n')}
    ],
    [handleRemoveRows, ${hookDependencies.join(', ')}],
  )

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

  /* const activeFilters = filters.filter(({ selected }) => selected !== null) */
  const filteredData = data /* activeFilters.length
    ? data.filter((row: ${model.types.name}) => activeFilters.every(({ key, selected }) => row[key] === selected))
    : data */

  const { table, ...dataGridProps } = useDataGrid({
    columns,
    data: filteredData,
    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) {
    return `'${str.replaceAll("'", String.raw `\'`)}'`;
}
function generateFilter(definition) {
    const options = definition.options?.kind === 'static'
        ? `[${definition.options.options.map((o) => `{ label: ${quote(o.label)}, value: ${quote(o.value)} }`).join(', ')}]`
        : '[]';
    return `{key: '${definition.column.id}', label: ${quote(definition.column.header)}, options: ${options}, selected: null }`;
}
function generateColumn({ column: { id, accessor, header, variant, size, readonly }, options, }) {
    const optionsString = options?.kind === 'static'
        ? `[${options.options.map((o) => `{ label: ${quote(o.label)}, value: ${quote(o.value)} }`).join(', ')}]`
        : options?.kind === 'dynamic'
            ? options.expression
            : undefined;
    const hasSearchString = variant === 'select' && options?.kind === 'dynamic' ? ', hasSearch: true' : '';
    return `{
    id: '${id}',
    ${accessor},
    header: ${quote(header)},
    meta: { cell: { variant: '${variant}'${optionsString ? `, options: ${optionsString}` : ''}${hasSearchString} }, ${readonly ? 'editable: false, ' : ''} },
    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 {
        filter: getFieldFilter(params),
        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),
        },
        options: getOptions(params),
    };
}
function getFieldFilter(params) {
    return { key: params.field.name, label: generateColumnHeader(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:
            return duParentField === undefined
                ? `accessorKey: '${field.name}'`
                : `accessorFn: (row: ${model.types.name}) => row.${duParentField.name}${duMember ? `?.type === '${duMember.type}'` : ''} ? 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