import { createViewer } from '@file-viewer/core';
import { DEFAULT_FILE_VIEWER_SOURCE_FILENAME, getExtension, normalizeFilename, readFileViewerBuffer, resolveFileViewerSourceFilename, wrapFileViewerFileRef, } from '@file-viewer/core';
const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';
const hasSource = (options = {}) => {
    return !!(options.url || options.file || options.buffer);
};
const toViewerSourceInput = (options = {}) => ({
    url: options.url,
    file: options.file,
    buffer: options.buffer,
    filename: options.filename || options.name,
    name: options.name,
    type: options.type,
    size: options.size,
});
const canUseFetch = () => typeof fetch === 'function';
const defaultFetchFile = async ({ url, signal }) => {
    if (!canUseFetch()) {
        throw new Error('fetch is not available in the current environment.');
    }
    const response = await fetch(url, { signal });
    if (!response.ok) {
        throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`);
    }
    return response.blob();
};
const resolveViewerSourceFilename = (source) => {
    return normalizeFilename(source.filename || source.name || resolveFileViewerSourceFilename({
        file: source.file,
        url: source.url,
        fallback: DEFAULT_FILE_VIEWER_SOURCE_FILENAME,
    }), source.type ? `preview.${source.type}` : DEFAULT_FILE_VIEWER_SOURCE_FILENAME);
};
const resolveViewerLoadSource = async (source, options = {}) => {
    var _a, _b, _c;
    const filename = resolveViewerSourceFilename(source);
    const type = source.type || getExtension(filename);
    if (source.buffer) {
        return {
            buffer: source.buffer,
            filename,
            type,
            size: (_a = source.size) !== null && _a !== void 0 ? _a : source.buffer.byteLength,
            url: source.url,
        };
    }
    if (source.file) {
        const file = wrapFileViewerFileRef(source.file, filename);
        return {
            file,
            buffer: await readFileViewerBuffer(file),
            filename: file.name || filename,
            type: type || getExtension(file.name),
            size: (_b = source.size) !== null && _b !== void 0 ? _b : file.size,
            url: source.url,
        };
    }
    if (source.url) {
        const fileRef = await (options.fetchFile || defaultFetchFile)({
            url: source.url,
            signal: options.signal,
            source,
        });
        if (!fileRef) {
            throw new Error('Downloaded file is empty.');
        }
        const file = wrapFileViewerFileRef(fileRef, filename);
        return {
            file,
            buffer: await readFileViewerBuffer(file),
            filename: file.name || filename,
            type: type || getExtension(file.name),
            size: (_c = source.size) !== null && _c !== void 0 ? _c : file.size,
            url: source.url,
        };
    }
    return {
        filename,
        type,
    };
};
export const createViewerControllerHandle = (getController, dispose) => ({
    load(options) {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.load(options)) !== null && _b !== void 0 ? _b : Promise.resolve();
    },
    update(options) {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.update(options)) !== null && _b !== void 0 ? _b : Promise.resolve();
    },
    reload() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.reload()) !== null && _b !== void 0 ? _b : Promise.resolve();
    },
    destroy() {
        dispose();
    },
    getController,
    getApi() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.getApi()) !== null && _b !== void 0 ? _b : null;
    },
    downloadOriginalFile() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.downloadOriginalFile()) !== null && _b !== void 0 ? _b : Promise.resolve();
    },
    printRenderedHtml() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.printRenderedHtml()) !== null && _b !== void 0 ? _b : Promise.resolve();
    },
    exportRenderedHtml() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.exportRenderedHtml()) !== null && _b !== void 0 ? _b : Promise.resolve();
    },
    zoomIn() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.zoomIn()) !== null && _b !== void 0 ? _b : Promise.resolve(null);
    },
    zoomOut() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.zoomOut()) !== null && _b !== void 0 ? _b : Promise.resolve(null);
    },
    resetZoom() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.resetZoom()) !== null && _b !== void 0 ? _b : Promise.resolve(null);
    },
    searchDocument(query) {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.searchDocument(query)) !== null && _b !== void 0 ? _b : Promise.resolve(null);
    },
    clearDocumentSearch() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.clearDocumentSearch()) !== null && _b !== void 0 ? _b : Promise.resolve(null);
    },
    nextSearchResult() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.nextSearchResult()) !== null && _b !== void 0 ? _b : Promise.resolve(null);
    },
    previousSearchResult() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.previousSearchResult()) !== null && _b !== void 0 ? _b : Promise.resolve(null);
    },
    collectDocumentAnchors() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.collectDocumentAnchors()) !== null && _b !== void 0 ? _b : Promise.resolve([]);
    },
    scrollToAnchor(anchor) {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.scrollToAnchor(anchor)) !== null && _b !== void 0 ? _b : Promise.resolve(false);
    },
    scrollToLine(line) {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.scrollToLine(line)) !== null && _b !== void 0 ? _b : Promise.resolve(false);
    },
    getDocumentTextChunks() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.getDocumentTextChunks()) !== null && _b !== void 0 ? _b : [];
    },
    getOperationAvailability() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.getOperationAvailability()) !== null && _b !== void 0 ? _b : null;
    },
    getZoomState() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.getZoomState()) !== null && _b !== void 0 ? _b : null;
    },
    getSearchState() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.getSearchState()) !== null && _b !== void 0 ? _b : null;
    },
    getState() {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.getState()) !== null && _b !== void 0 ? _b : null;
    },
    subscribe(listener) {
        var _a, _b;
        return (_b = (_a = getController()) === null || _a === void 0 ? void 0 : _a.subscribe(listener)) !== null && _b !== void 0 ? _b : (() => { });
    },
});
const callApi = async (api, action, fallback) => {
    if (!api) {
        return fallback;
    }
    return action(api);
};
const isAbortError = (error) => {
    return Boolean(error && typeof error === 'object' && error.name === 'AbortError');
};
export const mountViewer = (container, initialOptions = {}, coreOptions = {}) => {
    if (!isBrowser()) {
        throw new Error('Flyfish File Viewer can only be mounted in a browser DOM environment.');
    }
    let disposed = false;
    let currentOptions = initialOptions;
    let currentSource = hasSource(currentOptions)
        ? toViewerSourceInput(currentOptions)
        : null;
    let abortController = null;
    const listeners = new Set();
    const state = {
        loading: false,
        ready: false,
        error: null,
        lastEvent: null,
        lifecycle: null,
        availability: null,
        search: null,
        zoom: null,
        location: null,
    };
    const snapshotState = () => ({
        ...state,
        search: state.search
            ? { ...state.search, matches: [...state.search.matches] }
            : null,
    });
    const notifyState = (event) => {
        var _a;
        const snapshot = snapshotState();
        (_a = currentOptions.onStateChange) === null || _a === void 0 ? void 0 : _a.call(currentOptions, snapshot, event);
        listeners.forEach(listener => listener(snapshot, event));
    };
    const applyViewerEvent = (event) => {
        var _a;
        state.lastEvent = event;
        if (event.type === 'load-start') {
            state.loading = true;
            state.ready = false;
            state.error = null;
            state.lifecycle = event.payload;
        }
        else if (event.type === 'load-complete') {
            state.loading = false;
            state.ready = true;
            state.lifecycle = event.payload;
        }
        else if (event.type === 'unload-start') {
            state.loading = true;
            state.ready = false;
            state.lifecycle = event.payload;
        }
        else if (event.type === 'unload-complete') {
            state.loading = false;
            state.ready = false;
            state.lifecycle = event.payload;
        }
        else if (event.type === 'operation-availability-change') {
            state.availability = event.payload;
        }
        else if (event.type === 'search-change') {
            state.search = event.payload;
        }
        else if (event.type === 'location-change') {
            state.location = event.payload;
        }
        else if (event.type === 'zoom-change') {
            state.zoom = event.payload;
        }
        (_a = currentOptions.onEvent) === null || _a === void 0 ? void 0 : _a.call(currentOptions, event);
        notifyState(event);
    };
    const instance = createViewer(container, {
        registry: coreOptions.registry,
        options: currentOptions.options,
        onEvent: applyViewerEvent,
    });
    const cancel = () => {
        abortController === null || abortController === void 0 ? void 0 : abortController.abort();
        abortController = null;
    };
    const loadSource = async (nextSource) => {
        var _a;
        cancel();
        currentSource = nextSource;
        abortController = typeof AbortController !== 'undefined' ? new AbortController() : null;
        const controller = abortController;
        try {
            state.loading = true;
            state.error = null;
            notifyState();
            const resolvedSource = await resolveViewerLoadSource(nextSource, {
                fetchFile: coreOptions.fetchFile,
                signal: controller === null || controller === void 0 ? void 0 : controller.signal,
            });
            return await instance.load(resolvedSource);
        }
        catch (error) {
            if (isAbortError(error) && (controller === null || controller === void 0 ? void 0 : controller.signal.aborted)) {
                return null;
            }
            state.loading = false;
            state.ready = false;
            state.error = error;
            notifyState();
            (_a = coreOptions.onError) === null || _a === void 0 ? void 0 : _a.call(coreOptions, error, nextSource);
            throw error;
        }
        finally {
            if (abortController === controller) {
                abortController = null;
            }
        }
    };
    if (currentSource) {
        void loadSource(currentSource);
    }
    const controller = {
        container,
        async load(nextOptions) {
            if (disposed)
                return;
            currentOptions = nextOptions;
            instance.updateOptions(currentOptions.options || {});
            if (hasSource(currentOptions)) {
                await loadSource(toViewerSourceInput(currentOptions));
            }
        },
        async update(nextOptions = {}) {
            var _a;
            if (disposed)
                return;
            currentOptions = {
                ...currentOptions,
                ...nextOptions,
                options: (_a = nextOptions.options) !== null && _a !== void 0 ? _a : currentOptions.options,
            };
            instance.updateOptions(currentOptions.options || {});
            if (hasSource(currentOptions)) {
                await loadSource(toViewerSourceInput(currentOptions));
            }
            else {
                currentSource = null;
                await instance.load({ filename: DEFAULT_FILE_VIEWER_SOURCE_FILENAME });
            }
        },
        async reload() {
            if (disposed)
                return;
            if (currentSource) {
                await loadSource(currentSource);
            }
        },
        destroy() {
            if (disposed)
                return;
            disposed = true;
            cancel();
            void instance.destroy('component-unmount');
            container.innerHTML = '';
        },
        getApi() {
            return instance;
        },
        downloadOriginalFile() {
            return callApi(instance, api => api.download(), undefined);
        },
        printRenderedHtml() {
            return callApi(instance, api => api.print(), undefined);
        },
        exportRenderedHtml() {
            return callApi(instance, api => api.exportHtml({ download: true }).then(() => undefined), undefined);
        },
        zoomIn() {
            return callApi(instance, api => api.zoomIn(), null);
        },
        zoomOut() {
            return callApi(instance, api => api.zoomOut(), null);
        },
        resetZoom() {
            return callApi(instance, api => api.resetZoom(), null);
        },
        searchDocument(query) {
            return callApi(instance, api => api.search(query), null);
        },
        clearDocumentSearch() {
            return callApi(instance, api => api.clearSearch(), null);
        },
        nextSearchResult() {
            return callApi(instance, api => api.nextSearchResult(), null);
        },
        previousSearchResult() {
            return callApi(instance, api => api.previousSearchResult(), null);
        },
        collectDocumentAnchors() {
            return callApi(instance, api => api.collectDocumentAnchors(), []);
        },
        scrollToAnchor(anchor) {
            return callApi(instance, api => api.scrollToDocumentAnchor(anchor), false);
        },
        scrollToLine(line) {
            return callApi(instance, api => api.scrollToLine(line), false);
        },
        getDocumentTextChunks() {
            return instance.getDocumentTextChunks();
        },
        getOperationAvailability() {
            return instance.getCapabilities();
        },
        getZoomState() {
            return instance.getZoomState();
        },
        getSearchState() {
            return instance.getSearchState();
        },
        getState() {
            return snapshotState();
        },
        subscribe(listener) {
            listeners.add(listener);
            listener(snapshotState());
            return () => {
                listeners.delete(listener);
            };
        },
    };
    return controller;
};
