import { UmbMediaDetailRepository } from '../repository/detail/index.js';
import { UMB_MEDIA_PROPERTY_VALUE_ENTITY_TYPE } from '../entity.js';
import { UMB_DROPZONE_MEDIA_TYPE_PICKER_MODAL } from './modals/index.js';
import { UmbDropzoneManager, UmbFileDropzoneItemStatus, } from '../../dropzone/index.js';
import { getFileExtension } from '../../../core/utils/index.js';
import { UmbMediaTypeStructureRepository, } from '../../media-types/index.js';
import { UmbArrayState } from '../../../../libs/observable-api/index.js';
import { TemporaryFileStatus } from '../../../core/temporary-file/index.js';
import { umbOpenModal } from '../../../core/modal/index.js';
import { UMB_NOTIFICATION_CONTEXT } from '../../../core/notification/index.js';
import { UmbLocalizationController } from '../../../../libs/localization-api/index.js';
import { UmbApiError } from '../../../core/resources/index.js';
export class UmbMediaDropzoneManager extends UmbDropzoneManager {
    // The available media types for a file extension.
    #availableMediaTypesOf = new UmbArrayState([], (x) => x.extension);
    // The media types that the parent will allow to be created under it.
    #allowedChildrenOf = new UmbArrayState([], (x) => x.mediaTypeUnique);
    #mediaTypeStructure = new UmbMediaTypeStructureRepository(this);
    #mediaDetailRepository = new UmbMediaDetailRepository(this);
    #notificationContext;
    #localization = new UmbLocalizationController(this);
    constructor(host) {
        super(host);
        this.consumeContext(UMB_NOTIFICATION_CONTEXT, (context) => {
            this.#notificationContext = context;
        });
    }
    /**
     * Uploads files and folders to the server and creates the media items with corresponding media type.\
     * Allows the user to pick a media type option if multiple types are allowed.
     * @param {UmbFileDropzoneDroppedItems} items - The files and folders to upload.
     * @param {string | null} parentUnique - Where the items should be uploaded.
     * @returns {Array<UmbUploadableItem>} - The items about to be uploaded.
     */
    createMediaItems(items, parentUnique = null) {
        const uploadableItems = this._setupProgress(items, parentUnique);
        if (!uploadableItems.length)
            return [];
        if (uploadableItems.length === 1) {
            // When there is only one item being uploaded, allow the user to pick the media type, if more than one is allowed.
            this.#createOneMediaItem(uploadableItems[0]);
        }
        else {
            // When there are multiple items being uploaded, automatically pick the media types for each item. We probably want to allow the user to pick the media type in the future.
            this.#createMediaItems(uploadableItems);
        }
        return uploadableItems;
    }
    async #createMediaItems(uploadableItems) {
        for (const item of uploadableItems) {
            const { options, availableMediaTypes } = await this.#getMediaTypeOptions(item);
            if (!options.length) {
                const message = this.#getDisallowedMessage(item, availableMediaTypes);
                this._updateStatus(item, UmbFileDropzoneItemStatus.NOT_ALLOWED, message);
                continue;
            }
            // Prefer a specific extension match over a fallback
            const specificMatch = options.find((x) => x.matchedFileExtension === true);
            const { unique: mediaTypeUnique, name: mediaTypeName } = specificMatch ?? options[0];
            if (!mediaTypeUnique) {
                throw new Error('Media type unique is not defined');
            }
            // Handle files and folders differently: a file is uploaded as temp then created as a media item, and a folder is created as a media item directly.
            // Awaiting ensures parent folders exist on the server before their children are processed.
            if (item.temporaryFile) {
                await this.#handleFile(item, mediaTypeUnique, mediaTypeName);
            }
            else if (item.folder) {
                await this.#handleFolder(item, mediaTypeUnique, mediaTypeName);
            }
        }
    }
    async #handleFile(item, mediaTypeUnique, mediaTypeName) {
        // Upload the file as a temporary file and update progress.
        const temporaryFile = await this._tempFileManager.uploadOne(item.temporaryFile);
        if (temporaryFile.status === TemporaryFileStatus.CANCELLED) {
            this._updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED);
            return;
        }
        if (temporaryFile.status !== TemporaryFileStatus.SUCCESS) {
            this._updateStatus(item, UmbFileDropzoneItemStatus.ERROR);
            return;
        }
        // Create the media item.
        const scaffold = await this.#getItemScaffold(item, mediaTypeUnique);
        const { data, error } = await this.#mediaDetailRepository.createSilently(scaffold, item.parentUnique);
        if (data) {
            this._updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE);
        }
        else if (UmbApiError.isUmbApiError(error) && error.problemDetails?.status === 400) {
            // Validation error — show as inline friendly message (same pattern as NOT_ALLOWED).
            const message = this.#localization.term('media_uploadValidationFailed', mediaTypeName);
            this._updateStatus(item, UmbFileDropzoneItemStatus.NOT_ALLOWED, message);
        }
        else {
            // Other server error — show ERROR status and a manual toast (auto-toast was suppressed).
            this._updateStatus(item, UmbFileDropzoneItemStatus.ERROR);
            if (error) {
                this.#notificationContext?.peek('danger', {
                    data: { headline: 'An error occurred', message: error.message },
                });
            }
        }
    }
    async #handleFolder(item, mediaTypeUnique, mediaTypeName) {
        const scaffold = await this.#getItemScaffold(item, mediaTypeUnique);
        const { data, error } = await this.#mediaDetailRepository.createSilently(scaffold, item.parentUnique);
        if (data) {
            this._updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE);
        }
        else if (UmbApiError.isUmbApiError(error) && error.problemDetails?.status === 400) {
            // Validation error — show as inline friendly message (same pattern as NOT_ALLOWED).
            const message = this.#localization.term('media_uploadValidationFailed', mediaTypeName);
            this._updateStatus(item, UmbFileDropzoneItemStatus.NOT_ALLOWED, message);
        }
        else {
            // Other server error — show ERROR status and a manual toast (auto-toast was suppressed).
            this._updateStatus(item, UmbFileDropzoneItemStatus.ERROR);
            if (error) {
                this.#notificationContext?.peek('danger', {
                    data: { headline: 'An error occurred', message: error.message },
                });
            }
        }
    }
    #getDisallowedMessage(item, availableMediaTypes) {
        const extension = item.temporaryFile ? getFileExtension(item.temporaryFile.file.name) : undefined;
        if (!extension) {
            return this.#localization.term('media_disallowedFileType');
        }
        if (availableMediaTypes.length === 0) {
            return this.#localization.term('media_disallowedFileExtension', extension);
        }
        const mediaTypeNames = availableMediaTypes.map((x) => x.name).join(', ');
        if (availableMediaTypes.length === 1) {
            return this.#localization.term('media_disallowedMediaTypeNotAllowedHere', extension, mediaTypeNames);
        }
        return this.#localization.term('media_disallowedMediaTypesNotAllowedHere', extension, mediaTypeNames);
    }
    // Determines which media types an item can be created as by intersecting two lists:
    // 1. Media types allowed as children at the upload location (e.g. a folder allowing [Article, File])
    // 2. Media types that support the file's extension (e.g. .pdf → [Article(specific), File(fallback)])
    // The result includes match info so callers can prefer specific matches over catch-all fallbacks.
    async #getMediaTypeOptions(item) {
        // Check the parent which children media types are allowed.
        const parent = item.parentUnique ? await this.#mediaDetailRepository.requestByUnique(item.parentUnique) : null;
        const allowedChildren = await this.#getAllowedChildrenOf(parent?.data?.mediaType.unique ?? null, item.parentUnique);
        const extension = item.temporaryFile ? getFileExtension(item.temporaryFile.file.name) ?? null : null;
        // Check which media types allow the file's extension.
        const availableMediaTypes = await this.#getAvailableMediaTypesOf(extension);
        if (!availableMediaTypes.length)
            return { options: [], availableMediaTypes: [] };
        // Intersect allowed children with available media types, carrying through whether each matched by specific
        // extension or as a catch-all fallback.
        // This flag is used downstream to auto-select the most appropriate type without showing a picker dialog.
        // For example, if both "File" (which allows any extension) and "Article" (which only allows .pdf) are allowed,
        // a .pdf file would match both but be auto-assigned to "Article" because it is a specific match, while a .docx
        // file would be assigned to "File" because it has no specific matches.
        const options = allowedChildren.reduce((acc, child) => {
            const match = availableMediaTypes.find((y) => y.unique === child.unique);
            if (match) {
                acc.push({ ...child, matchedFileExtension: match.matchedFileExtension });
            }
            return acc;
        }, []);
        return { options, availableMediaTypes };
    }
    async #getAvailableMediaTypesOf(extension) {
        // Check if we already have information on this file extension.
        const available = this.#availableMediaTypesOf
            .getValue()
            .find((x) => x.extension === extension)?.availableMediaTypes;
        if (available)
            return available;
        // Request information on this file extension
        const availableMediaTypes = extension
            ? await this.#mediaTypeStructure.requestMediaTypesOf({ fileExtension: extension })
            : await this.#mediaTypeStructure.requestMediaTypesOfFolders();
        this.#availableMediaTypesOf.appendOne({ extension, availableMediaTypes });
        return availableMediaTypes;
    }
    async #getAllowedChildrenOf(mediaTypeUnique, parentUnique) {
        //Check if we already got information on this media type.
        const allowed = this.#allowedChildrenOf
            .getValue()
            .find((x) => x.mediaTypeUnique === mediaTypeUnique)?.allowedChildren;
        if (allowed)
            return allowed;
        // Request information on this media type.
        const { data } = await this.#mediaTypeStructure.requestAllowedChildrenOf(mediaTypeUnique, parentUnique);
        if (!data)
            throw new Error('Parent media type does not exist');
        this.#allowedChildrenOf.appendOne({ mediaTypeUnique, allowedChildren: data.items });
        return data.items;
    }
    // Scaffold
    async #getItemScaffold(item, mediaTypeUnique) {
        // TODO: Use a scaffolding feature to ensure consistency. [NL]
        const name = item.temporaryFile ? item.temporaryFile.file.name : (item.folder?.name ?? '');
        const umbracoFile = {
            editorAlias: '',
            alias: 'umbracoFile',
            value: { temporaryFileId: item.temporaryFile?.temporaryUnique },
            culture: null,
            segment: null,
            entityType: UMB_MEDIA_PROPERTY_VALUE_ENTITY_TYPE,
        };
        const preset = {
            unique: item.unique,
            mediaType: { unique: mediaTypeUnique, collection: null },
            variants: [{ culture: null, segment: null, createDate: null, updateDate: null, flags: [], name }],
            values: item.temporaryFile ? [umbracoFile] : undefined,
        };
        const { data } = await this.#mediaDetailRepository.createScaffold(preset);
        return data;
    }
    async #showDialogMediaTypePicker(options) {
        const value = await umbOpenModal(this, UMB_DROPZONE_MEDIA_TYPE_PICKER_MODAL, { data: { options } }).catch(() => undefined);
        return value?.mediaTypeUnique;
    }
    async #createOneMediaItem(item) {
        const { options, availableMediaTypes } = await this.#getMediaTypeOptions(item);
        if (!options.length) {
            const message = this.#getDisallowedMessage(item, availableMediaTypes);
            const itemName = item.temporaryFile?.file.name ?? item.folder?.name;
            this.#notificationContext?.peek('warning', {
                data: {
                    message: itemName ? `${message} (${itemName}).` : `${message}.`,
                },
            });
            return this._updateStatus(item, UmbFileDropzoneItemStatus.NOT_ALLOWED, message);
        }
        let mediaTypeUnique;
        if (options.length > 1) {
            // When multiple options are available, prefer specific extension matches over fallbacks
            const specificMatches = options.filter((x) => x.matchedFileExtension === true);
            if (specificMatches.length === 1) {
                // Exactly one specific match — auto-select it (e.g., Article for .pdf when both Article and File are allowed)
                mediaTypeUnique = specificMatches[0].unique;
            }
            else if (specificMatches.length > 1) {
                // Multiple specific matches — let the user pick from those
                mediaTypeUnique = await this.#showDialogMediaTypePicker(specificMatches);
            }
            else {
                // All fallbacks — auto-select the first one
                mediaTypeUnique = options[0].unique;
            }
        }
        else {
            mediaTypeUnique = options[0].unique;
        }
        if (!mediaTypeUnique) {
            return this._updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED);
        }
        const mediaTypeName = options.find((o) => o.unique === mediaTypeUnique)?.name ?? '';
        if (item.temporaryFile) {
            await this.#handleFile(item, mediaTypeUnique, mediaTypeName);
        }
        else if (item.folder) {
            await this.#handleFolder(item, mediaTypeUnique, mediaTypeName);
        }
    }
}
