var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import Clutter from 'gi://Clutter';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import GdkPixbuf from 'gi://GdkPixbuf';
import Gio from 'gi://Gio';
import Gst from 'gi://Gst';
import St from 'gi://St';
import { gettext as _, ngettext } from 'resource:///org/gnome/shell/extensions/extension.js';
import { enumParamSpec, registerClass } from '../../common/gjs.js';
import { Icon, loadIcon } from '../../common/icons.js';
import { FileType } from './contentPreview.js';
export const TextCountMode = {
    Characters: 0,
    Words: 1,
    Lines: 2,
};
function formatBytes(bytes) {
    const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    const exp = Math.floor(Math.log10(bytes) / 3);
    const value = (bytes / Math.pow(1000, exp)) % 1000;
    const formatted = value.toLocaleString('en', { maximumSignificantDigits: 2 });
    return [formatted, units[exp]];
}
function formatTime(seconds) {
    const h = Math.floor(seconds / 3600);
    const m = Math.floor((seconds % 3600) / 60);
    const s = Math.floor(seconds % 60);
    let res = '';
    if (h > 0)
        res += _('%dh').format(h) + ' ';
    if (m > 0)
        res += _('%dm').format(m) + ' ';
    res += _('%ds').format(s);
    return res;
}
let ContentInfo = class ContentInfo extends St.BoxLayout {
    left;
    right;
    constructor() {
        super({
            style_class: 'content-info',
            x_expand: true,
        });
        this.left = new St.BoxLayout({
            style_class: 'content-info-left',
            x_align: Clutter.ActorAlign.START,
            x_expand: true,
        });
        this.add_child(this.left);
        this.right = new St.BoxLayout({
            style_class: 'content-info-right',
            x_align: Clutter.ActorAlign.END,
        });
        this.add_child(this.right);
    }
};
ContentInfo = __decorate([
    registerClass()
], ContentInfo);
export { ContentInfo };
let TextInfo = class TextInfo extends ContentInfo {
    _countLabel;
    _text;
    _textCountMode;
    constructor(text, textCountMode) {
        super();
        this._countLabel = new St.Label();
        this.left.add_child(this._countLabel);
        this._text = text;
        this._textCountMode = textCountMode;
        this.updateCount();
    }
    set text(text) {
        if (this._text === text)
            return;
        this._text = text;
        this.updateCount();
    }
    set textCountMode(countMode) {
        if (this._textCountMode === countMode)
            return;
        this._textCountMode = countMode;
        this.updateCount();
    }
    updateCount() {
        if (this._textCountMode === TextCountMode.Characters) {
            let count = 0;
            const segments = new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(this._text);
            for (const _segment of segments)
                count++;
            this._countLabel.text = ngettext('%d char', '%d chars', count).format(count);
        }
        else if (this._textCountMode === TextCountMode.Words) {
            let count = 0;
            const segments = new Intl.Segmenter(undefined, { granularity: 'word' }).segment(this._text);
            for (const segment of segments) {
                if (segment.isWordLike)
                    count++;
            }
            this._countLabel.text = ngettext('%d word', '%d words', count).format(count);
        }
        else if (this._textCountMode === TextCountMode.Lines) {
            const count = this._text.split('\n').length;
            this._countLabel.text = ngettext('%d line', '%d lines', count).format(count);
        }
    }
};
TextInfo = __decorate([
    registerClass({
        Properties: {
            'text': GObject.ParamSpec.string('text', null, null, GObject.ParamFlags.WRITABLE, ''),
            'text-count-mode': enumParamSpec('text-count-mode', GObject.ParamFlags.WRITABLE, TextCountMode, 0),
        },
    })
], TextInfo);
export { TextInfo };
let CodeInfo = class CodeInfo extends TextInfo {
    _languageLabel;
    constructor(text, textCountMode, language) {
        super(text, textCountMode);
        this._languageLabel = new St.Label({
            style_class: 'language',
            text: language ?? '',
        });
        this.right.add_child(this._languageLabel);
    }
    set language(language) {
        this._languageLabel.text = language ?? '';
    }
};
CodeInfo = __decorate([
    registerClass({
        Properties: {
            language: GObject.ParamSpec.string('language', null, null, GObject.ParamFlags.WRITABLE, null),
        },
    })
], CodeInfo);
export { CodeInfo };
let FileInfo = class FileInfo extends ContentInfo {
    constructor(size = null) {
        super();
        this.add_style_class_name('file-info');
        if (size !== null) {
            const [value, unit] = formatBytes(size);
            const sizeBox = new St.BoxLayout({ style_class: 'file-info-size' });
            sizeBox.add_child(new St.Label({ style_class: 'content-info-number', text: value }));
            sizeBox.add_child(new St.Label({
                style_class: 'file-info-unit',
                text: unit,
                y_align: Clutter.ActorAlign.END,
            }));
            this.left.add_child(sizeBox);
        }
    }
};
FileInfo = __decorate([
    registerClass()
], FileInfo);
export { FileInfo };
let MissingFileInfo = class MissingFileInfo extends FileInfo {
    constructor(ext) {
        super();
        this.add_style_class_name('missing-info');
        this.left.add_child(new St.Icon({ gicon: loadIcon(ext, Icon.Warning) }));
        this.left.add_child(new St.Label({ text: _('Missing File') }));
    }
};
MissingFileInfo = __decorate([
    registerClass()
], MissingFileInfo);
export { MissingFileInfo };
let ErrorFileInfo = class ErrorFileInfo extends FileInfo {
    constructor(ext) {
        super();
        this.add_style_class_name('error-info');
        this.left.add_child(new St.Icon({ gicon: loadIcon(ext, Icon.Warning) }));
        this.left.add_child(new St.Label({ text: _('Error') }));
    }
};
ErrorFileInfo = __decorate([
    registerClass()
], ErrorFileInfo);
export { ErrorFileInfo };
let DirectoryInfo = class DirectoryInfo extends FileInfo {
    constructor(ext, directoryCount, fileCount) {
        super();
        this.add_style_class_name('directory-info');
        const directories = new St.BoxLayout({ style_class: 'directory-count' });
        directories.add_child(new St.Icon({ gicon: loadIcon(ext, Icon.Folder) }));
        directories.add_child(new St.Label({ style_class: 'content-info-number', text: directoryCount.toString() }));
        this.left.add_child(directories);
        const files = new St.BoxLayout({ style_class: 'file-count' });
        files.add_child(new St.Icon({ gicon: loadIcon(ext, Icon.File) }));
        files.add_child(new St.Label({ style_class: 'content-info-number', text: fileCount.toString() }));
        this.left.add_child(files);
    }
};
DirectoryInfo = __decorate([
    registerClass()
], DirectoryInfo);
export { DirectoryInfo };
let ImageInfo = class ImageInfo extends FileInfo {
    constructor(size, width, height) {
        super(size);
        this.add_style_class_name('image-info');
        const dimensions = new St.BoxLayout({ style_class: 'image-dimensions' });
        dimensions.add_child(new St.Label({ text: `${width}×${height}` }));
        this.right.add_child(dimensions);
    }
};
ImageInfo = __decorate([
    registerClass()
], ImageInfo);
export { ImageInfo };
let MediaInfo = class MediaInfo extends FileInfo {
    constructor(ext, size, seconds) {
        super(size);
        this.add_style_class_name('media-info');
        const duration = new St.BoxLayout({ style_class: 'media-duration', y_align: Clutter.ActorAlign.END });
        duration.add_child(new St.Icon({ gicon: loadIcon(ext, Icon.Duration) }));
        duration.add_child(new St.Label({ text: formatTime(seconds) }));
        this.right.add_child(duration);
    }
};
MediaInfo = __decorate([
    registerClass()
], MediaInfo);
export { MediaInfo };
Gio._promisify(Gio.File.prototype, 'enumerate_children_async');
export async function tryCreateDirectoryFileInfo(ext, file) {
    try {
        const enumerator = await file.enumerate_children_async('standard::*', Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null);
        let directoryCount = 0;
        let fileCount = 0;
        for await (const f of enumerator) {
            const fileType = f.get_file_type();
            if (fileType === Gio.FileType.DIRECTORY) {
                directoryCount++;
            }
            else if (fileType === Gio.FileType.REGULAR) {
                fileCount++;
            }
        }
        return new DirectoryInfo(ext, directoryCount, fileCount);
    }
    catch {
        return null;
    }
}
export function tryCreateImageInfo(file, size) {
    try {
        const [, width, height] = GdkPixbuf.Pixbuf.get_file_info(file.get_path());
        return new ImageInfo(size, width, height);
    }
    catch {
        return null;
    }
}
export async function tryCreateMediaFileInfo(ext, file, size, cancellable) {
    try {
        if (!Gst.is_initialized()) {
            Gst.init(null);
        }
        // https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/blob/ce69d1068af058425b083aaa1b8c268b1b2e5ddd/gst-libs/gst/pbutils/gstdiscoverer.c#L340
        const pipeline = Gst.parse_launch(`uridecodebin uri="${file.get_uri()}"`);
        pipeline.set_state(Gst.State.PAUSED);
        // https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/blob/ce69d1068af058425b083aaa1b8c268b1b2e5ddd/gst-libs/gst/pbutils/gstdiscoverer.c#L1417
        let [success, duration] = pipeline.query_duration(Gst.Format.TIME);
        if (!success) {
            pipeline.set_state(Gst.State.PLAYING);
            await new Promise((resolve) => {
                let i = 0;
                const timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => {
                    [success, duration] = pipeline.query_duration(Gst.Format.TIME);
                    if (success || i >= 5) {
                        resolve();
                        cancellable.disconnect(cancellableId);
                        return GLib.SOURCE_REMOVE;
                    }
                    i++;
                    cancellable.disconnect(cancellableId);
                    return GLib.SOURCE_CONTINUE;
                });
                const cancellableId = cancellable.connect(() => GLib.source_remove(timeoutId));
            });
        }
        pipeline.set_state(Gst.State.NULL);
        if (success && duration >= 0) {
            return new MediaInfo(ext, size, duration / Gst.SECOND);
        }
        else {
            return null;
        }
    }
    catch (err) {
        ext.logger.error(err);
        return null;
    }
}
export async function createFileInfo(ext, file, fileType, cancellable) {
    try {
        if (!file.query_exists(null)) {
            return new MissingFileInfo(ext);
        }
        const info = await file.query_info_async(Gio.FILE_ATTRIBUTE_STANDARD_SIZE, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null);
        const size = info.get_size();
        let fileInfo = null;
        switch (fileType) {
            case FileType.Directory:
                fileInfo = await tryCreateDirectoryFileInfo(ext, file);
                break;
            case FileType.Image:
                fileInfo = tryCreateImageInfo(file, size);
                break;
            case FileType.Audio:
            case FileType.Video:
                fileInfo = await tryCreateMediaFileInfo(ext, file, size, cancellable);
                break;
        }
        return fileInfo ?? new FileInfo(size);
    }
    catch {
        return new ErrorFileInfo(ext);
    }
}
