import type { HttpContext } from '@adonisjs/core/http';
import User from '#models/user';
import Dataset from '#models/dataset';
import License from '#models/license';
import Project from '#models/project';
import Title from '#models/title';
import Description from '#models/description';
import Language from '#models/language';
import Coverage from '#models/coverage';
import Collection from '#models/collection';
import CollectionRole from '#models/collection_role';
import dayjs from 'dayjs';
import Person from '#models/person';
import db from '@adonisjs/lucid/services/db';
import { TransactionClientContract } from '@adonisjs/lucid/types/database';
import Subject from '#models/subject';
// import CreateDatasetValidator from '#validators/create_dataset_validator';
import { createDatasetValidator, updateDatasetValidator } from '#validators/dataset';
// import UpdateDatasetValidator from '#validators/update_dataset_validator';
import {
    TitleTypes,
    DescriptionTypes,
    ContributorTypes,
    PersonNameTypes,
    ReferenceIdentifierTypes,
    RelationTypes,
    DatasetTypes,
    SubjectTypes,
} from '#contracts/enums';
import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
import DatasetReference from '#models/dataset_reference';
import { cuid } from '@adonisjs/core/helpers';
import File from '#models/file';
import ClamScan from 'clamscan';
// import { ValidationException } from '@adonisjs/validator';
// import Drive from '@ioc:Adonis/Core/Drive';
// import drive from '#services/drive';
import drive from '@adonisjs/drive/services/main';
import path from 'path';
import { Exception } from '@adonisjs/core/exceptions';
import { MultipartFile } from '@adonisjs/core/types/bodyparser';
import * as crypto from 'crypto';
// import MimeType from '#models/mime_type';
import { pipeline } from 'node:stream/promises';
import { createWriteStream } from 'node:fs';
import type { Multipart } from '@adonisjs/bodyparser';
import * as fs from 'fs';
import { join, isAbsolute } from 'node:path';
import type { BodyParserConfig } from '#models/types';
import { createId } from '@paralleldrive/cuid2';
import { tmpdir } from 'node:os';
import config from '@adonisjs/core/services/config';

interface Dictionary {
    [index: string]: string;
}
import vine, { SimpleMessagesProvider, errors } from '@vinejs/vine';

export default class DatasetController {
    /**
     * Bodyparser config
     */
    config: BodyParserConfig = config.get('bodyparser');

    public async index({ auth, request, inertia }: HttpContext) {
        const user = (await User.find(auth.user?.id)) as User;
        const page = request.input('page', 1);
        let datasets: ModelQueryBuilderContract<typeof Dataset, Dataset> = Dataset.query();

        // if (request.input('search')) {
        //     // users = users.whereRaw('name like %?%', [request.input('search')])
        //     const searchTerm = request.input('search');
        //     datasets.where('name', 'ilike', `%${searchTerm}%`);
        // }

        if (request.input('sort')) {
            type SortOrder = 'asc' | 'desc' | undefined;
            let attribute = request.input('sort');
            let sortOrder: SortOrder = 'asc';

            if (attribute.substr(0, 1) === '-') {
                sortOrder = 'desc';
                // attribute = substr(attribute, 1);
                attribute = attribute.substr(1);
            }
            datasets.orderBy(attribute, sortOrder);
        } else {
            // users.orderBy('created_at', 'desc');
            datasets.orderBy('id', 'asc');
        }

        // const results = await Database
        // .query()
        // .select(Database.raw("CONCAT('https://doi.org/', b.value) AS concatenated_value"))
        // .from('documents as doc')
        // .innerJoin('dataset_identifiers as b', 'doc.id', 'b.dataset_id')
        // .groupBy('a.id').toQuery();

        // const users = await User.query().orderBy('login').paginate(page, limit);
        const myDatasets = await datasets
            .whereIn('server_state', [
                'inprogress',
                'released',
                'editor_accepted',
                'approved',
                'reviewed',
                'rejected_editor',
                'rejected_reviewer',
            ])
            .where('account_id', user.id)
            .preload('titles')
            .preload('user', (query) => query.select('id', 'login'))
            // .preload('titles', (builder) => {
            //     // pull the actual preload data

            //     builder.where('type', 'Main');
            // })
            .paginate(page, 5);

        return inertia.render('Submitter/Dataset/Index', {
            // testing: 'this is a test',
            datasets: myDatasets.toJSON(),
            filters: request.all(),
            can: {
                // create: await auth.user?.can(['dataset-submit']),
                edit: await auth.user?.can(['dataset-edit']),
                delete: await auth.user?.can(['dataset-delete']),
            },
        });
    }

    public async create({ inertia }: HttpContext) {
        const licenses = await License.query().select('id', 'name_long').where('active', 'true').pluck('name_long', 'id');

        const projects = await Project.query().pluck('label', 'id');

        return inertia.render('Submitter/Dataset/Create', {
            licenses: licenses,
            doctypes: DatasetTypes,
            titletypes: Object.entries(TitleTypes)
                .filter(([value]) => value !== 'Main')
                .map(([key, value]) => ({ value: key, label: value })),
            descriptiontypes: Object.entries(DescriptionTypes)
                .filter(([value]) => value !== 'Abstract')
                .map(([key, value]) => ({ value: key, label: value })),
            // descriptiontypes: DescriptionTypes
            projects: projects,
            referenceIdentifierTypes: Object.entries(ReferenceIdentifierTypes).map(([key, value]) => ({ value: key, label: value })),
            relationTypes: Object.entries(RelationTypes).map(([key, value]) => ({ value: key, label: value })),
            contributorTypes: ContributorTypes,
            subjectTypes: SubjectTypes,
        });
    }

    public async firstStep({ request, response }: HttpContext) {
        // const newDatasetSchema = schema.create({
        //     language: schema.string({ trim: true }, [
        //         rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
        //     ]),
        //     licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset
        //     rights: schema.string([rules.equalTo('true')]),
        // });
        const newDatasetSchema = vine.object({
            // first step
            language: vine
                .string()
                .trim()
                .regex(/^[a-zA-Z0-9]+$/),
            licenses: vine.array(vine.number()).minLength(1), // define at least one license for the new dataset
            rights: vine.string().in(['true']),
        });
        // await request.validate({ schema: newDatasetSchema, messages: this.messages });
        try {
            //  Step 2 - Validate request body against  the schema
            // await request.validate({ schema: newDatasetSchema, messages: this.messages });
            const validator = vine.compile(newDatasetSchema);
            await request.validateUsing(validator, { messagesProvider: new SimpleMessagesProvider(this.messages) });
        } catch (error) {
            // Step 3 - Handle errors
            throw error;
        }
        return response.redirect().back();
    }

    public async secondStep({ request, response }: HttpContext) {
        const newDatasetSchema = vine.object({
            // first step
            language: vine
                .string()
                .trim()
                .regex(/^[a-zA-Z0-9]+$/),
            licenses: vine.array(vine.number()).minLength(1), // define at least one license for the new dataset
            rights: vine.string().in(['true']),
            // second step
            type: vine.string().trim().minLength(3).maxLength(255),
            creating_corporation: vine.string().trim().minLength(3).maxLength(255),
            titles: vine
                .array(
                    vine.object({
                        value: vine.string().trim().minLength(3).maxLength(255),
                        type: vine.enum(Object.values(TitleTypes)),
                        language: vine
                            .string()
                            .trim()
                            .minLength(2)
                            .maxLength(255)
                            .translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
                    }),
                )
                // .minLength(2)
                .arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
            descriptions: vine
                .array(
                    vine.object({
                        value: vine.string().trim().minLength(3).maxLength(2500),
                        type: vine.enum(Object.values(DescriptionTypes)),
                        language: vine
                            .string()
                            .trim()
                            .minLength(2)
                            .maxLength(255)
                            .translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
                    }),
                )
                // .minLength(1),
                .arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
            authors: vine
                .array(
                    vine.object({
                        email: vine
                            .string()
                            .trim()
                            .maxLength(255)
                            .email()
                            .normalizeEmail()
                            .isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
                        first_name: vine.string().trim().minLength(3).maxLength(255),
                        last_name: vine.string().trim().minLength(3).maxLength(255),
                    }),
                )
                .minLength(1)
                .distinct('email'),
            contributors: vine
                .array(
                    vine.object({
                        email: vine
                            .string()
                            .trim()
                            .maxLength(255)
                            .email()
                            .normalizeEmail()
                            .isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
                        first_name: vine.string().trim().minLength(3).maxLength(255),
                        last_name: vine.string().trim().minLength(3).maxLength(255),
                        pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
                    }),
                )
                .distinct('email')
                .optional(),
            project_id: vine.number().optional(),
        });

        try {
            //  Step 2 - Validate request body against  the schema
            // await request.validate({ schema: newDatasetSchema, messages: this.messages });
            const validator = vine.compile(newDatasetSchema);
            await request.validateUsing(validator, { messagesProvider: new SimpleMessagesProvider(this.messages) });
        } catch (error) {
            // Step 3 - Handle errors
            // return response.badRequest(error.messages);
            throw error;
        }
        return response.redirect().back();
    }
    public async thirdStep({ request, response }: HttpContext) {
        const newDatasetSchema = vine.object({
            // first step
            language: vine
                .string()
                .trim()
                .regex(/^[a-zA-Z0-9]+$/),
            licenses: vine.array(vine.number()).minLength(1), // define at least one license for the new dataset
            rights: vine.string().in(['true']),
            // second step
            type: vine.string().trim().minLength(3).maxLength(255),
            creating_corporation: vine.string().trim().minLength(3).maxLength(255),
            titles: vine
                .array(
                    vine.object({
                        value: vine.string().trim().minLength(3).maxLength(255),
                        type: vine.enum(Object.values(TitleTypes)),
                        language: vine
                            .string()
                            .trim()
                            .minLength(2)
                            .maxLength(255)
                            .translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
                    }),
                )
               // .minLength(2)
               .arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
            descriptions: vine
                .array(
                    vine.object({
                        value: vine.string().trim().minLength(3).maxLength(2500),
                        type: vine.enum(Object.values(DescriptionTypes)),
                        language: vine
                            .string()
                            .trim()
                            .minLength(2)
                            .maxLength(255)
                            .translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
                    }),
                )
                // .minLength(1),
                .arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
            authors: vine
                .array(
                    vine.object({
                        email: vine
                            .string()
                            .trim()
                            .maxLength(255)
                            .email()
                            .normalizeEmail()
                            .isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
                        first_name: vine.string().trim().minLength(3).maxLength(255),
                        last_name: vine.string().trim().minLength(3).maxLength(255),
                    }),
                )
                .minLength(1)
                .distinct('email'),
            contributors: vine
                .array(
                    vine.object({
                        email: vine
                            .string()
                            .trim()
                            .maxLength(255)
                            .email()
                            .normalizeEmail()
                            .isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
                        first_name: vine.string().trim().minLength(3).maxLength(255),
                        last_name: vine.string().trim().minLength(3).maxLength(255),
                        pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
                    }),
                )
                .distinct('email')
                .optional(),
            // third step
            project_id: vine.number().optional(),
            // embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
            embargo_date: vine
                .date({
                    formats: ['YYYY-MM-DD'],
                })
                .afterOrEqual((_field) => {
                    return dayjs().add(10, 'day').format('YYYY-MM-DD');
                })
                .optional(),
            coverage: vine.object({
                x_min: vine.number(),
                x_max: vine.number(),
                y_min: vine.number(),
                y_max: vine.number(),
                elevation_absolut: vine.number().positive().optional(),
                elevation_min: vine.number().positive().optional().requiredIfExists('elevation_max'),
                elevation_max: vine.number().positive().optional().requiredIfExists('elevation_min'),
                // type: vine.enum(Object.values(DescriptionTypes)),
                depth_absolut: vine.number().negative().optional(),
                depth_min: vine.number().negative().optional().requiredIfExists('depth_max'),
                depth_max: vine.number().negative().optional().requiredIfExists('depth_min'),
                time_abolute: vine.date({ formats: { utc: true } }).optional(),
                time_min: vine
                    .date({ formats: { utc: true } })
                    .beforeField('time_max')
                    .optional()
                    .requiredIfExists('time_max'),
                time_max: vine
                    .date({ formats: { utc: true } })
                    .afterField('time_min')
                    .optional()
                    .requiredIfExists('time_min'),
            }),
            references: vine
                .array(
                    vine.object({
                        // value: vine.string().trim().minLength(3).maxLength(255),
                        value: vine.string().trim().minLength(3).maxLength(255).validateReference({ typeField: 'type' }),
                        type: vine.enum(Object.values(ReferenceIdentifierTypes)),
                        relation: vine.enum(Object.values(RelationTypes)),
                        label: vine.string().trim().minLength(2).maxLength(255),
                    }),
                )
                .optional(),
            subjects: vine
                .array(
                    vine.object({
                        value: vine.string().trim().minLength(3).maxLength(255),
                        // pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
                        language: vine.string().trim().minLength(2).maxLength(255),
                    }),
                )
                .minLength(3)
                .distinct('value')
                .optional(),
        });

        try {
            //  Step 3 - Validate request body against  the schema
            // await request.validate({ schema: newDatasetSchema, messages: this.messages });
            const validator = vine.compile(newDatasetSchema);
            await request.validateUsing(validator, { messagesProvider: new SimpleMessagesProvider(this.messages) });
            // console.log({ payload });
        } catch (error) {
            // Step 3 - Handle errors
            // return response.badRequest(error.messages);
            throw error;
        }
        return response.redirect().back();
    }

    /**
     * Returns the tmp path for storing the files temporarly
     */
    private getTmpPath(config: BodyParserConfig['multipart']): string {
        if (typeof config.tmpFileName === 'function') {
            const tmpPath = config.tmpFileName();
            return isAbsolute(tmpPath) ? tmpPath : join(tmpdir(), tmpPath);
        }

        return join(tmpdir(), createId());
    }
    /**
     * Returns config for a given type
     */
    private getConfigFor<K extends keyof BodyParserConfig>(type: K): BodyParserConfig[K] {
        const config = this.config[type];
        return config;
    }

    private parseBytesSize(size: string): number {
        const units = {
            kb: 1024,
            mb: 1024 * 1024,
            gb: 1024 * 1024 * 1024,
            tb: 1024 * 1024 * 1024 * 1024,
        };

        const match = size.match(/^(\d+)(kb|mb|gb|tb)$/i); // Regex to match size format

        if (!match) {
            throw new Error('Invalid size format');
        }

        const [, value, unit] = match;
        return parseInt(value) * units[unit.toLowerCase()];
    }

    public async store({ auth, request, response, session }: HttpContext) {
        // At the top of the store() method, declare an array to hold temporary file paths
        const uploadedTmpFiles: string[] = [];
        // Aggregated limit example (adjust as needed)
        const multipartConfig = this.getConfigFor('multipart');
        const aggregatedLimit = multipartConfig.limit ? this.parseBytesSize(multipartConfig.limit) : 100 * 1024 * 1024;
        // const aggregatedLimit = 200 * 1024 * 1024;
        let totalUploadedSize = 0;

        // Helper function to format bytes as human-readable text
        function formatBytes(bytes: number): string {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }
        // const enabledExtensions = await this.getEnabledExtensions();
        const multipart: Multipart = request.multipart;

        multipart.onFile('files', { deferValidations: true }, async (part) => {
            // Attach an individual file size accumulator if needed
            let fileUploadedSize = 0;

            // Simply accumulate the size in on('data') without performing the expensive check per chunk
            part.on('data', (chunk) => {
                // reporter(chunk);
                // Increase counters using the chunk length
                fileUploadedSize += chunk.length;
            });

            // After the file is completely read, update the global counter and check aggregated limit
            part.on('end', () => {
                totalUploadedSize += fileUploadedSize;
                part.file.size = fileUploadedSize;
                // Record the temporary file path
                if (part.file.tmpPath) {
                    uploadedTmpFiles.push(part.file.tmpPath);
                }

                if (totalUploadedSize > aggregatedLimit) {
                    // Clean up all temporary files if aggregate limit is exceeded
                    uploadedTmpFiles.forEach((tmpPath) => {
                        try {
                            fs.unlinkSync(tmpPath);
                        } catch (cleanupError) {
                            console.error('Error cleaning up temporary file:', cleanupError);
                        }
                    });
                    const error = new errors.E_VALIDATION_ERROR({
                        'upload error': `Aggregated upload limit of ${formatBytes(aggregatedLimit)} exceeded. The total size of files being uploaded would exceed the limit.`,
                    });
                    request.multipart.abort(error);
                }
            });

            part.on('error', (error) => {
                // fileUploadError = error;
                request.multipart.abort(error);
            });

            // await pipeline(part, createWriteStream(filePath));
            // return { filePath };
            // Process file with error handling
            try {
                // Extract extension from the client file name, e.g. "Tethys 5 - Ampflwang_dataset.zip"
                const ext = path.extname(part.file.clientName).replace('.', '');
                // Attach the extracted extension to the file object for later use
                part.file.extname = ext;

                // part.file.sortOrder = part.file.sortOrder;

                const tmpPath = this.getTmpPath(multipartConfig);
                (part.file as any).tmpPath = tmpPath;

                const writeStream = createWriteStream(tmpPath);
                await pipeline(part, writeStream);
            } catch (error) {
                request.multipart.abort(new errors.E_VALIDATION_ERROR({ 'upload error': error.message }));
            }
        });

        try {
            await multipart.process();
            // // Instead of letting an error abort the controller, check if any error occurred
        } catch (error) {
            // This is where you'd expect to catch any errors.
            session.flash('errors', error.messages);
            return response.redirect().back();
        }

        let trx: TransactionClientContract | null = null;
        try {
            await request.validateUsing(createDatasetValidator);
            trx = await db.transaction();
            const user = (await User.find(auth.user?.id)) as User;

            await this.createDatasetAndAssociations(user, request, trx);

            await trx.commit();
            console.log('Dataset and related models created successfully');
        } catch (error) {
            // Clean up temporary files if validation or later steps fail
            uploadedTmpFiles.forEach((tmpPath) => {
                try {
                    fs.unlinkSync(tmpPath);
                } catch (cleanupError) {
                    console.error('Error cleaning up temporary file:', cleanupError);
                }
            });
            if (trx !== null) {
                await trx.rollback();
            }
            console.error('Failed to create dataset and related models:', error);
            // throw new ValidationException(true, { 'upload error': `failed to create dataset and related models. ${error}` });
            throw error;
        }

        session.flash('message', 'Dataset has been created successfully');
        return response.redirect().toRoute('dataset.list');
        // return response.redirect().back();
    }
    private async createDatasetAndAssociations(
        user: User,
        request: HttpContext['request'],
        trx: TransactionClientContract,
        // uploadedFiles: Array<MultipartFile>,
    ) {
        // Create a new instance of the Dataset model:
        const dataset = new Dataset();
        dataset.type = request.input('type');
        dataset.creating_corporation = request.input('creating_corporation');
        dataset.language = request.input('language');
        dataset.embargo_date = request.input('embargo_date');
        //await dataset.related('user').associate(user); // speichert schon ab
        // Dataset.$getRelation('user').boot();
        // Dataset.$getRelation('user').setRelated(dataset, user);
        // dataset.$setRelated('user', user);
        await user.useTransaction(trx).related('datasets').save(dataset);

        //store licenses:
        const licenses: number[] = request.input('licenses', []);
        await dataset.useTransaction(trx).related('licenses').sync(licenses);

        // save authors and contributors
        await this.savePersons(dataset, request.input('authors', []), 'author', trx);
        await this.savePersons(dataset, request.input('contributors', []), 'contributor', trx);

        //save main and additional titles
        const titles = request.input('titles', []);
        for (const titleData of titles) {
            const title = new Title();
            title.value = titleData.value;
            title.language = titleData.language;
            title.type = titleData.type;
            await dataset.useTransaction(trx).related('titles').save(title);
        }

        // save descriptions
        const descriptions = request.input('descriptions', []);
        for (const descriptionData of descriptions) {
            const description = new Description();
            description.value = descriptionData.value;
            description.language = descriptionData.language;
            description.type = descriptionData.type;
            await dataset.useTransaction(trx).related('descriptions').save(description);
        }

        //save references
        const references = request.input('references', []);
        for (const referencePayload of references) {
            const dataReference = new DatasetReference();
            dataReference.fill(referencePayload);
            // $dataReference = new DatasetReference($reference);
            dataset.related('references').save(dataReference);
        }

        //save keywords
        const keywords = request.input('subjects', []);
        for (const keywordData of keywords) {
            // $dataKeyword = new Subject($keyword);
            // $dataset->subjects()->save($dataKeyword);
            const keyword = await Subject.firstOrNew({ value: keywordData.value, type: keywordData.type }, keywordData);
            if (keyword.$isNew === true) {
                await dataset.useTransaction(trx).related('subjects').save(keyword);
            } else {
                await dataset.useTransaction(trx).related('subjects').attach([keyword.id]);
            }
        }

        // save collection
        const collection: Collection | null = await Collection.query().where('id', 594).first();
        collection && (await dataset.useTransaction(trx).related('collections').attach([collection.id]));

        // save coverage
        const coverageData = request.input('coverage');
        if (coverageData) {
            // const formCoverage = request.input('coverage');
            const coverage = new Coverage();
            coverage.fill(coverageData);
            // await dataset.coverage().save(coverageData);
            await dataset.useTransaction(trx).related('coverage').save(coverage);
            // Alternatively, you can associate the dataset with the coverage and then save it:
            // await coverage.dataset().associate(dataset).save();
            // await coverage.useTransaction(trx).related('dataset').associate(dataset);
        }

        // save data files
        const uploadedFiles: MultipartFile[] = request.files('files');
        for (const [index, file] of uploadedFiles.entries()) {
            try {
                await this.scanFileForViruses(file.tmpPath); //, 'gitea.lan', 3310);
                // await this.scanFileForViruses("/tmp/testfile.txt");
            } catch (error) {
                // If the file is infected or there's an error scanning the file, throw a validation exception
                throw error;
            }
            // clientName: 'Gehaltsschema.png'
            // extname: 'png'
            // fieldName: 'file'
            // const fileName = `file-${this.generateRandomString(32)}.${file.extname}`;
            const fileName = this.generateFilename(file.extname as string);
            const mimeType = file.headers['content-type'] || 'application/octet-stream'; // Fallback to a default MIME type
            const datasetFolder = `files/${dataset.id}`;
            const datasetFullPath = path.join(`${datasetFolder}`, fileName);
            // const size = file.size;
            // await file.move(drive.makePath(datasetFolder), {
            //     name: fileName,
            //     overwrite: true, // overwrite in case of conflict
            // });
            await file.moveToDisk(datasetFullPath, 'local', {
                name: fileName,
                overwrite: true, // overwrite in case of conflict
                disk: 'local',
            });

            // save file metadata into db
            const newFile = new File();
            newFile.pathName = `${datasetFolder}/${fileName}`;
            newFile.fileSize = file.size;
            newFile.mimeType = mimeType;
            newFile.label = file.clientName;
            newFile.sortOrder = index + 1;
            newFile.visibleInFrontdoor = true;
            newFile.visibleInOai = true;
            // let path = coverImage.filePath;
            await dataset.useTransaction(trx).related('files').save(newFile);
            await newFile.createHashValues(trx);
        }
    }

    private generateRandomString(length: number): string {
        return crypto
            .randomBytes(Math.ceil(length / 2))
            .toString('hex')
            .slice(0, length);
    }

    private generateFilename(extension: string): string {
        const randomString1 = this.generateRandomString(8);
        const randomString2 = this.generateRandomString(4);
        const randomString3 = this.generateRandomString(4);
        const randomString4 = this.generateRandomString(4);
        const randomString5 = this.generateRandomString(12);

        return `file-${randomString1}-${randomString2}-${randomString3}-${randomString4}-${randomString5}.${extension}`;
    }

    private async scanFileForViruses(filePath: string | undefined, host?: string, port?: number): Promise<void> {
        // const clamscan = await (new ClamScan().init());
        const opts: ClamScan.Options = {
            removeInfected: true, // If true, removes infected files
            debugMode: false, // Whether or not to log info/debug/error msgs to the console
            scanRecursively: true, // If true, deep scan folders recursively
            clamdscan: {
                active: true, // If true, this module will consider using the clamdscan binary
                host,
                port,
                multiscan: true, // Scan using all available cores! Yay!
            },
            preference: 'clamdscan', // If clamdscan is found and active, it will be used by default
        };

        return new Promise(async (resolve, reject) => {
            try {
                const clamscan = await new ClamScan().init(opts);
                // You can re-use the `clamscan` object as many times as you want
                // const version = await clamscan.getVersion();
                // console.log(`ClamAV Version: ${version}`);
                const { file, isInfected, viruses } = await clamscan.isInfected(filePath);
                if (isInfected) {
                    console.log(`${file} is infected with ${viruses}!`);
                    // reject(new ValidationException(true, { 'upload error': `File ${file} is infected!` }));
                    reject(new errors.E_VALIDATION_ERROR({ 'upload error': `File ${file} is infected!` }));
                } else {
                    resolve();
                }
            } catch (error) {
                // If there's an error scanning the file, throw a validation exception
                // reject(new ValidationException(true, { 'upload error': `${error.message}` }));
                reject(new errors.E_VALIDATION_ERROR({ 'upload error': `${error.message}!` }));
            }
        });
    }

    private async savePersons(dataset: Dataset, persons: any[], role: string, trx: TransactionClientContract) {
        for (const [key, person] of persons.entries()) {
            const pivotData = {
                role: role,
                sort_order: key + 1,
                allow_email_contact: false,
                ...this.extractPivotAttributes(person), // Merge pivot attributes here
            };

            if (person.id !== undefined) {
                await dataset
                    .useTransaction(trx)
                    .related('persons')
                    .attach({
                        [person.id]: pivotData,
                    });
            } else {
                const dataPerson = new Person();
                dataPerson.fill(person);
                await dataset.useTransaction(trx).related('persons').save(dataPerson, false, pivotData);
            }
        }
    }

    // Helper function to extract pivot attributes from a person object
    private extractPivotAttributes(person: any) {
        const pivotAttributes: Dictionary = {};
        for (const key in person) {
            if (key.startsWith('pivot_')) {
                // pivotAttributes[key] = person[key];
                const cleanKey = key.replace('pivot_', ''); // Remove 'pivot_' prefix
                pivotAttributes[cleanKey] = person[key];
            }
        }
        return pivotAttributes;
    }

    public messages = {
        'minLength': '{{ field }} must be at least {{ min }} characters long',
        'maxLength': '{{ field }} must be less then {{ max }} characters long',
        'required': '{{ field }} is required',
        'unique': '{{ field }} must be unique, and this value is already taken',
        // 'confirmed': '{{ field }} is not correct',
        'licenses.array.minLength': 'at least {{ min }} licence must be defined',
        'licenses.*.number': 'Define roles as valid numbers',
        'rights.in': 'you must agree to continue',

        // 'titles.array.minLength': 'Main Title is required',
        'titles.0.value.minLength': 'Main Title  must be at least {{ min }} characters long',
        'titles.0.value.maxLength': 'Main Title  must be less than {{ max }} characters long',
        'titles.0.value.required': 'Main Title is required',
        'titles.*.value.required': 'Additional title is required, if defined',
        'titles.*.type.required': 'Additional title type is required',
        'titles.*.language.required': 'Additional title language is required',
        'titles.*.language.translatedLanguage': 'The language of the translated title must be different from the language of the dataset',

        'descriptions.0.value.minLength': 'Main Abstract  must be at least {{ min }} characters long',
        'descriptions.0.value.maxLength': 'Main Abstract  must be less than {{ max }} characters long',
        'descriptions.0.value.required': 'Main Abstract is required',
        'descriptions.*.value.required': 'Additional description is required, if defined',
        'descriptions.*.type.required': 'Additional description type is required',
        'descriptions.*.language.required': 'Additional description language is required',
        'descriptions.*.language.translatedLanguage':
            'The language of the translated description must be different from the language of the dataset',

        'authors.array.minLength': 'at least {{ min }} author must be defined',
        'authors.array.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',
        'authors.*.email.isUnique': 'the email of the new creator already exists in the database',
        'contributors.*.pivot_contributor_type.required': 'contributor type is required, if defined',
        'contributors.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',

        'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,

        'subjects.array.minLength': 'at least {{ min }} keywords must be defined',
        'subjects.*.value.required': 'keyword value is required',
        'subjects.*.value.minLength': 'keyword value  must be at least {{ min }} characters long',
        'subjects.*.type.required': 'keyword type is required',
        'subjects.*.language.required': 'language of keyword is required',
        'subjects.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',

        'references.*.value.required': 'Additional reference value is required, if defined',
        'references.*.type.required': 'Additional reference identifier type is required',
        'references.*.relation.required': 'Additional reference relation type is required',
        'references.*.label.required': 'Additional reference label is required',

        'files.array.minLength': 'At least {{ min }}  file upload is required.',
        'files.*.size': 'file size is to big',
        'files.*.extnames': 'file extension is not supported',
    };

    // public async release({ params, view }) {
    public async release({ request, inertia, response }: HttpContext) {
        const id = request.param('id');

        const dataset = await Dataset.query()
            .preload('user', (builder) => {
                builder.select('id', 'login');
            })
            .where('id', id)
            .firstOrFail();

        const validStates = ['inprogress', 'rejected_editor'];
        if (!validStates.includes(dataset.server_state)) {
            // session.flash('errors', 'Invalid server state!');
            return response
                .flash(
                    'warning',
                    `Invalid server state. Dataset with id ${id} cannot be released to editor. Datset has server state ${dataset.server_state}.`,
                )
                .redirect()
                .back();
        }

        return inertia.render('Submitter/Dataset/Release', {
            dataset,
        });
    }

    public async releaseUpdate({ request, response }: HttpContext) {
        const id = request.param('id');
        const dataset = await Dataset.query().preload('files').where('id', id).firstOrFail();

        const validStates = ['inprogress', 'rejected_editor'];
        if (!validStates.includes(dataset.server_state)) {
            // throw new Error('Invalid server state!');
            // return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
            return response
                .flash(
                    'warning',
                    `Invalid server state. Dataset with id ${id} cannot be released to editor. Datset has server state ${dataset.server_state}.`,
                )
                .redirect()
                .toRoute('dataset.list');
        }

        if (dataset.files.length === 0) {
            return response.flash('warning', 'At least minimum one file is required.').redirect('back');
        }

        const preferation = request.input('preferation', '');
        const preferredReviewer = request.input('preferred_reviewer');
        const preferredReviewerEmail = request.input('preferred_reviewer_email');

        if (preferation === 'yes_preferation') {
            const newSchema = vine.object({
                preferred_reviewer: vine.string().alphaNumeric().trim().minLength(3).maxLength(255),
                preferred_reviewer_email: vine.string().maxLength(255).email().normalizeEmail(),
            });

            try {
                // await request.validate({
                //     schema: newSchema,
                //     // reporter: validator.reporters.vanilla,
                // });
                const validator = vine.compile(newSchema);
                await request.validateUsing(validator);
            } catch (error) {
                // return response.badRequest(error.messages);
                throw error;
            }
        }

        const input = {
            preferred_reviewer: preferredReviewer || null,
            preferred_reviewer_email: preferredReviewerEmail || null,
            server_state: 'released',
            editor_id: null,
            reviewer_id: null,
            reject_editor_note: null,
            reject_reviewer_note: null,
        };

        // Clear editor_id if it exists
        if (dataset.editor_id !== null) {
            input.editor_id = null;
        }

        // Clear reject_editor_note if it exists
        if (dataset.reject_editor_note !== null) {
            input.reject_editor_note = null;
        }

        // Clear reviewer_id if it exists
        if (dataset.reviewer_id !== null) {
            input.reviewer_id = null;
        }

        // Clear reject_reviewer_note if it exists
        if (dataset.reject_reviewer_note !== null) {
            input.reject_reviewer_note = null;
        }

        if (await dataset.merge(input).save()) {
            return response.toRoute('dataset.list').flash('You have released your dataset!', 'message');
        }
        // throw new GeneralException(trans('exceptions.publish.release.update_error'));
    }

    public async edit({ request, inertia, response }: HttpContext) {
        const id = request.param('id');
        const datasetQuery = Dataset.query().where('id', id);
        datasetQuery
            .preload('titles', (query) => query.orderBy('id', 'asc'))
            .preload('descriptions', (query) => query.orderBy('id', 'asc'))
            .preload('coverage')
            .preload('licenses')
            .preload('authors')
            .preload('contributors')
            // .preload('subjects')
            .preload('subjects', (builder) => {
                builder.orderBy('id', 'asc').withCount('datasets');
            })
            .preload('references')
            .preload('files', (query) => {
                query.orderBy('sort_order', 'asc'); // Sort by sort_order column
            });

        const dataset = await datasetQuery.firstOrFail();
        const validStates = ['inprogress', 'rejected_editor'];
        if (!validStates.includes(dataset.server_state)) {
            // session.flash('errors', 'Invalid server state!');
            return response
                .flash(
                    'warning',
                    `Invalid server state. Dataset with id ${id} cannot be edited. Datset has server state ${dataset.server_state}.`,
                )
                .redirect()
                .toRoute('dataset.list');
        }

        const titleTypes = Object.entries(TitleTypes)
            .filter(([value]) => value !== 'Main')
            .map(([key, value]) => ({ value: key, label: value }));

        const descriptionTypes = Object.entries(DescriptionTypes)
            .filter(([value]) => value !== 'Abstract')
            .map(([key, value]) => ({ value: key, label: value }));

        const languages = await Language.query().where('active', true).pluck('part1', 'part1');

        // const contributorTypes = Config.get('enums.contributor_types');
        const contributorTypes = Object.entries(ContributorTypes).map(([key, value]) => ({ value: key, label: value }));

        // const nameTypes = Config.get('enums.name_types');
        const nameTypes = Object.entries(PersonNameTypes).map(([key, value]) => ({ value: key, label: value }));

        // const messages = await Database.table('messages')
        //   .pluck('help_text', 'metadata_element');

        const projects = await Project.query().pluck('label', 'id');

        const currentDate = new Date();
        const currentYear = currentDate.getFullYear();
        const years = Array.from({ length: currentYear - 1990 + 1 }, (_, index) => 1990 + index);

        const licenses = await License.query().select('id', 'name_long').where('active', 'true').pluck('name_long', 'id');
        // const userHasRoles = user.roles;
        // const datasetHasLicenses = await dataset.related('licenses').query().pluck('id');
        // const checkeds = dataset.licenses.first().id;

        const doctypes = {
            analysisdata: { label: 'Analysis', value: 'analysisdata' },
            measurementdata: { label: 'Measurements', value: 'measurementdata' },
            monitoring: 'Monitoring',
            remotesensing: 'Remote Sensing',
            gis: 'GIS',
            models: 'Models',
            mixedtype: 'Mixed Type',
        };

        return inertia.render('Submitter/Dataset/Edit', {
            dataset,
            titletypes: titleTypes,
            descriptiontypes: descriptionTypes,
            contributorTypes,
            nameTypes,
            languages,
            // messages,
            projects,
            licenses,
            // datasetHasLicenses: Object.keys(datasetHasLicenses).map((key) => datasetHasLicenses[key]), //convert object to array with license ids
            // checkeds,
            years,
            // languages,
            subjectTypes: SubjectTypes,
            referenceIdentifierTypes: Object.entries(ReferenceIdentifierTypes).map(([key, value]) => ({ value: key, label: value })),
            relationTypes: Object.entries(RelationTypes).map(([key, value]) => ({ value: key, label: value })),
            doctypes,
        });
    }

    public async update({ request, response, session }: HttpContext) {
        try {
            // await request.validate(UpdateDatasetValidator);
            await request.validateUsing(updateDatasetValidator);
        } catch (error) {
            // - Handle errors
            // return response.badRequest(error.messages);
            throw error;
            // return response.badRequest(error.messages);
        }
        // await request.validate(UpdateDatasetValidator);
        const id = request.param('id');

        let trx: TransactionClientContract | null = null;
        try {
            trx = await db.transaction();
            // const user = (await User.find(auth.user?.id)) as User;
            // await this.createDatasetAndAssociations(user, request, trx);
            const dataset = await Dataset.findOrFail(id);

            // save the licenses
            const licenses: number[] = request.input('licenses', []);
            // await dataset.useTransaction(trx).related('licenses').sync(licenses);
            await dataset.useTransaction(trx).related('licenses').sync(licenses);

            // save authors and contributors
            await dataset.useTransaction(trx).related('authors').sync([]);
            await dataset.useTransaction(trx).related('contributors').sync([]);
            await this.savePersons(dataset, request.input('authors', []), 'author', trx);
            await this.savePersons(dataset, request.input('contributors', []), 'contributor', trx);

            //save the titles:
            const titles = request.input('titles', []);
            // const savedTitles:Array<Title> = [];
            for (const titleData of titles) {
                if (titleData.id) {
                    const title = await Title.findOrFail(titleData.id);
                    title.value = titleData.value;
                    title.language = titleData.language;
                    title.type = titleData.type;
                    if (title.$isDirty) {
                        await title.useTransaction(trx).save();
                        // await dataset.useTransaction(trx).related('titles').save(title);
                        // savedTitles.push(title);
                    }
                } else {
                    const title = new Title();
                    title.fill(titleData);
                    // savedTitles.push(title);
                    await dataset.useTransaction(trx).related('titles').save(title);
                }
            }

            // save the abstracts
            const descriptions = request.input('descriptions', []);
            // const savedTitles:Array<Title> = [];
            for (const descriptionData of descriptions) {
                if (descriptionData.id) {
                    const description = await Description.findOrFail(descriptionData.id);
                    description.value = descriptionData.value;
                    description.language = descriptionData.language;
                    description.type = descriptionData.type;
                    if (description.$isDirty) {
                        await description.useTransaction(trx).save();
                        // await dataset.useTransaction(trx).related('titles').save(title);
                        // savedTitles.push(title);
                    }
                } else {
                    const description = new Description();
                    description.fill(descriptionData);
                    // savedTitles.push(title);
                    await dataset.useTransaction(trx).related('descriptions').save(description);
                }
            }

            // await dataset.useTransaction(trx).related('subjects').sync([]);
            const keywords = request.input('subjects');
            for (const keywordData of keywords) {
                if (keywordData.id) {
                    const subject = await Subject.findOrFail(keywordData.id);
                    // await dataset.useTransaction(trx).related('subjects').attach([keywordData.id]);
                    subject.value = keywordData.value;
                    subject.type = keywordData.type;
                    subject.external_key = keywordData.external_key;
                    if (subject.$isDirty) {
                        await subject.save();
                    }
                } else {
                    const keyword = new Subject();
                    keyword.fill(keywordData);
                    await dataset.useTransaction(trx).related('subjects').save(keyword, false);
                }
            }

            // save coverage
            const coverageData = request.input('coverage');
            if (coverageData) {
                if (coverageData.id) {
                    const coverage = await Coverage.findOrFail(coverageData.id);
                    coverage.merge(coverageData);
                    if (coverage.$isDirty) {
                        await coverage.useTransaction(trx).save();
                    }
                }
            }

            // Save already existing files
            const files = request.input('fileInputs', []);
            for (const fileData of files) {
                if (fileData.id) {
                    const file = await File.findOrFail(fileData.id);
                    file.label = fileData.label;
                    file.sortOrder = fileData.sort_order;
                    if (file.$isDirty) {
                        await file.useTransaction(trx).save();
                    }
                }
            }

            // handle new uploaded files:
            const uploadedFiles: MultipartFile[] = request.files('files');
            if (Array.isArray(uploadedFiles) && uploadedFiles.length > 0) {
                for (const [index, fileData] of uploadedFiles.entries()) {
                    try {
                        await this.scanFileForViruses(fileData.tmpPath); //, 'gitea.lan', 3310);
                        // await this.scanFileForViruses("/tmp/testfile.txt");
                    } catch (error) {
                        // If the file is infected or there's an error scanning the file, throw a validation exception
                        throw error;
                    }

                    // move to disk:
                    const fileName = `file-${cuid()}.${fileData.extname}`; //'file-ls0jyb8xbzqtrclufu2z2e0c.pdf'
                    const datasetFolder = `files/${dataset.id}`; // 'files/307'
                    const datasetFullPath = path.join(`${datasetFolder}`, fileName);
                    // await fileData.moveToDisk(datasetFolder, { name: fileName, overwrite: true }, 'local');
                    // await fileData.move(drive.makePath(datasetFolder), {
                    //     name: fileName,
                    //     overwrite: true, // overwrite in case of conflict
                    // });
                    await fileData.moveToDisk(datasetFullPath, 'local', {
                        name: fileName,
                        overwrite: true, // overwrite in case of conflict
                        disk: 'local',
                    });

                    //save to db:
                    const { clientFileName, sortOrder } = this.extractVariableNameAndSortOrder(fileData.clientName);
                    const mimeType = fileData.headers['content-type'] || 'application/octet-stream'; // Fallback to a default MIME type
                    const newFile = await dataset
                        .useTransaction(trx)
                        .related('files')
                        .create({
                            pathName: `${datasetFolder}/${fileName}`,
                            fileSize: fileData.size,
                            mimeType,
                            label: clientFileName,
                            sortOrder: sortOrder || index,
                            visibleInFrontdoor: true,
                            visibleInOai: true,
                        });

                    // save many related HashValue Instances to the file:
                    await newFile.createHashValues(trx);
                }
            }

            const filesToDelete = request.input('filesToDelete', []);
            for (const fileData of filesToDelete) {
                if (fileData.id) {
                    const file = await File.findOrFail(fileData.id);
                    await file.useTransaction(trx).delete();
                }
            }

            // save collection
            // const collection: Collection | null = await Collection.query().where('id', 21).first();
            // collection && (await dataset.useTransaction(trx).related('collections').attach([collection.id]));

            // // Save coverage
            // if (data.coverage && !this.containsOnlyNull(data.coverage)) {
            //     const formCoverage = request.input('coverage');
            //     const coverage = await dataset.related('coverage').updateOrCreate({ dataset_id: dataset.id }, formCoverage);
            // } else if (data.coverage && this.containsOnlyNull(data.coverage) && !dataset.coverage) {
            //     await dataset.coverage().delete();
            // }

            const input = request.only(['project_id', 'embargo_date', 'language', 'type', 'creating_corporation']);
            // dataset.type = request.input('type');
            dataset.merge(input);
            // let test: boolean = dataset.$isDirty;
            await dataset.useTransaction(trx).save();

            await trx.commit();
            console.log('Dataset and related models created successfully');

            session.flash('message', 'Dataset has been updated successfully');
            // return response.redirect().toRoute('user.index');
            return response.redirect().toRoute('dataset.edit', [dataset.id]);
        } catch (error) {
            if (trx !== null) {
                await trx.rollback();
            }
            console.error('Failed to create dataset and related models:', error);
            // throw new ValidationException(true, { 'upload error': `failed to create dataset and related models. ${error}` });
            throw error;
        }
    }

    private extractVariableNameAndSortOrder(inputString: string): { clientFileName: string; sortOrder?: number } {
        const regex = /^([^?]+)(?:\?([^=]+)=([^&]+))?/;
        const match = inputString.match(regex);

        if (match) {
            const clientFileName = match[1];

            const param = match[2];
            let sortOrder;
            if (param && param.toLowerCase() === 'sortorder') {
                sortOrder = parseInt(match[3], 10);
            }

            return { clientFileName, sortOrder };
        } else {
            return { clientFileName: '', sortOrder: undefined }; // Or handle as needed for no match
        }
    }

    public async delete({ request, inertia, response, session }: HttpContext) {
        const id = request.param('id');
        try {
            const dataset = await Dataset.query()
                .preload('user', (builder) => {
                    builder.select('id', 'login');
                })
                .where('id', id)
                .preload('files')
                .firstOrFail();
            const validStates = ['inprogress', 'rejected_editor'];
            if (!validStates.includes(dataset.server_state)) {
                // session.flash('errors', 'Invalid server state!');
                return response
                    .flash(
                        'warning',
                        `Invalid server state. Dataset with id ${id} cannot be deleted. Datset has server state ${dataset.server_state}.`,
                    )
                    .redirect()
                    .toRoute('dataset.list');
            }
            return inertia.render('Submitter/Dataset/Delete', {
                dataset,
            });
        } catch (error) {
            if (error.code == 'E_ROW_NOT_FOUND') {
                session.flash({ warning: 'Dataset is not found in database' });
            } else {
                session.flash({ warning: 'general error occured, you cannot delete the dataset' });
            }
            return response.redirect().toRoute('dataset.list');
        }
    }

    public async deleteUpdate({ params, session, response }: HttpContext) {
        try {
            const dataset = await Dataset.query().where('id', params.id).preload('files').firstOrFail();

            const validStates = ['inprogress', 'rejected_editor'];
            if (validStates.includes(dataset.server_state)) {
                if (dataset.files && dataset.files.length > 0) {
                    for (const file of dataset.files) {
                        // overwritten delete method also delets file on filespace and db object
                        await file.delete();
                    }
                }
                const datasetFolder = `files/${params.id}`;
                // const folderExists = await drive.use('local').exists(datasetFolder);
                // if (folderExists) {
                // const dirListing = drive.list(datasetFolder);
                // const folderContents = await dirListing.toArray();
                // if (folderContents.length === 0) {
                //     await drive.delete(datasetFolder);
                // }
                await drive.use('local').deleteAll(datasetFolder);
                // delete dataset wirh relation in db
                await dataset.delete();
                session.flash({ message: 'You have deleted 1 dataset!' });
                return response.redirect().toRoute('dataset.list');
                // } else {
                //     // session.flash({
                //     //     warning: `You cannot delete this dataset! Invalid server_state: "${dataset.server_state}"!`,
                //     // });
                //     return response
                //         .flash({ warning: `You cannot delete this dataset! Dataset folder "${datasetFolder}" doesn't exist!` })
                //         .redirect()
                //         .back();
                // }
            }
        } catch (error) {
            if (error instanceof errors.E_VALIDATION_ERROR) {
                // Validation exception handling
                throw error;
            } else if (error instanceof Exception) {
                // General exception handling
                session.flash({ error: error.message });
                return response.redirect().back();
            } else {
                session.flash({ error: 'An error occurred while deleting the dataset.' });
                return response.redirect().back();
            }
        }
    }

    public async categorize({ inertia, request, response }: HttpContext) {
        const id = request.param('id');
        // Preload dataset and its "collections" relation
        const dataset = await Dataset.query().where('id', id).preload('collections').firstOrFail();
        const validStates = ['inprogress', 'rejected_editor'];
        if (!validStates.includes(dataset.server_state)) {
            // session.flash('errors', 'Invalid server state!');
            return response
                .flash(
                    'warning',
                    `Invalid server state. Dataset with id ${id} cannot be edited. Datset has server state ${dataset.server_state}.`,
                )
                .redirect()
                .toRoute('dataset.list');
        }

        const collectionRoles = await CollectionRole.query()
            .preload('collections', (coll: Collection) => {
                // preloa only top level collection with noparent_id
                coll.whereNull('parent_id').orderBy('number', 'asc');
            })
            .exec();

        return inertia.render('Submitter/Dataset/Category', {
            collectionRoles: collectionRoles,
            dataset: dataset,
            relatedCollections: dataset.collections,
        });
    }

    public async categorizeUpdate({ request, response, session }: HttpContext) {
        // Get the dataset id from the route parameter
        const id = request.param('id');
        const dataset = await Dataset.query().preload('files').where('id', id).firstOrFail();

        const validStates = ['inprogress', 'rejected_editor'];
        if (!validStates.includes(dataset.server_state)) {
            return response
                .flash(
                    'warning',
                    `Invalid server state. Dataset with id ${id} cannot be categorized. Dataset has server state ${dataset.server_state}.`,
                )
                .redirect()
                .toRoute('dataset.list');
        }

        let trx: TransactionClientContract | null = null;
        try {
            trx = await db.transaction();
            // const user = (await User.find(auth.user?.id)) as User;
            // await this.createDatasetAndAssociations(user, request, trx);

            // Retrieve the selected collections from the request.
            // This should be an array of collection ids.
            const collections: number[] = request.input('collections', []);

            // Synchronize the dataset collections using the transaction.
            await dataset.useTransaction(trx).related('collections').sync(collections);

            // Commit the transaction.await trx.commit()
            await trx.commit();

            // Redirect with a success flash message.
            // return response.flash('success', 'Dataset collections updated successfully!').redirect().toRoute('dataset.list');

            session.flash('message', 'Dataset collections updated successfully!');
            return response.redirect().toRoute('dataset.list');
        } catch (error) {
            if (trx !== null) {
                await trx.rollback();
            }
            console.error('Failed tocatgorize dataset collections:', error);
            // throw new ValidationException(true, { 'upload error': `failed to create dataset and related models. ${error}` });
            throw error;
        }
    }
}
