import { PhotoSwipeOptions } from 'photoswipe';
import PhotoSwipeLightbox from 'photoswipe/lightbox';

declare interface ItemOptions {
    lightbox?: boolean;
    selectable?: boolean;
    activable?: boolean;
    gap?: number;
    showLabels?: 'hover' | 'never' | 'always';
}
declare class Item<Model extends ModelAttributes> {
    private readonly document;
    private readonly options;
    readonly model: Model;
    /**
     * Cleaned title, used for label / button
     */
    readonly title: string;
    /**
     * Actual row index in the list
     */
    private _row;
    /**
     * If is actually the last element of a row
     */
    private _last;
    /**
     * Computed size (real used size)
     */
    private _width;
    private _height;
    private _cropped;
    /**
     * Wherever item is selected or not
     * @type {boolean}
     * @private
     */
    private _selected;
    /**
     * Item root element reference (figure)
     */
    private _element;
    /**
     * Image container reference (child div, containing the image)
     */
    private _image;
    /**
     * Reference to the select button
     */
    private _selectBtn;
    /**
     * Element referering the "button" containing the label
     */
    private label;
    /**
     *
     * @param {ItemOptions} options
     * @param model Contains the source data given for an item (e.g object instance from database with id etc..)
     */
    constructor(document: Document, options: ItemOptions, model: Model);
    /**
     * Cleans html, and returns only the text from all eventual tags
     * @param {string} term
     * @returns {ItemTitle}
     */
    private getTitleDetails;
    /**
     * Create DOM elements according to element raw data (thumbnail and enlarged urls)
     * Also apply border-radius at this level because it never changed threw time
     */
    init(): HTMLElement;
    setLabelHover(activate: boolean): void;
    /**
     * Use computed (organized) data to apply style (size and margin) to elements on DOM
     * Does not apply border-radius because is used to restyle data on browser resize, and border-radius don't change.
     */
    style(): void;
    /**
     * This function prepare loaded/loading status and return root element.
     * @returns {HTMLElement}
     */
    loadImage(): void;
    toggleSelect(): void;
    select(): void;
    unselect(): void;
    private getLinkElement;
    remove(): void;
    get last(): boolean;
    set last(value: boolean);
    get row(): number;
    set row(value: number);
    get height(): number;
    set height(value: number);
    get width(): number;
    set width(value: number);
    get cropped(): boolean;
    set cropped(value: boolean);
    get enlargedWidth(): number;
    get enlargedHeight(): number;
    get selected(): boolean;
    get element(): HTMLElement;
}

/**
 * A map of all possible event and the structure of their details
 */
interface CustomEventDetailMap<T> {
    'activate': {
        model: T;
        clickEvent: MouseEvent;
    };
    'item-added-to-dom': T;
    'item-displayed': T;
    'pagination': {
        offset: number;
        limit: number;
    };
    'select': T[];
}
/**
 * Augment the global namespace with our custom events
 * See: https://github.com/Microsoft/TypeScript/issues/28357
 */
declare global {
    interface HTMLElementEventMap {
        activate: CustomEvent;
        'item-added-to-dom': CustomEvent;
        'item-displayed': CustomEvent;
        pagination: CustomEvent;
        select: CustomEvent;
    }
}
interface SizedModel {
    /**
     * Height in pixels of the enlarged version the image
     * If photoswipe is used, the size of the photoswipe enlarged image is required
     * If photoswipe is not used, any size that match the ratio is enough
     */
    enlargedWidth: number;
    /**
     * Width in pixels of the enlarged version the image
     * If photoswipe is used, the size of the photoswipe enlarged image is required
     * If photoswipe is not used, any size that match the ratio is enough
     */
    enlargedHeight: number;
}
interface ModelAttributes extends SizedModel {
    /**
     * Source link for thumbnail image
     */
    thumbnailSrc: string;
    /**
     * Source link for enlarged (photoswipe) image
     */
    enlargedSrc?: string;
    /**
     * Label of item (or button)
     */
    title?: string;
    /**
     * Href link
     */
    link?: string;
    /**
     * a href target attribute
     */
    linkTarget?: '_blank' | '_self' | '_parent' | '_top';
    /**
     * Hex color
     */
    color?: string;
    /**
     * If item is selected
     */
    selected?: boolean;
    /**
     * Background size, default : cover
     */
    backgroundSize?: string;
    /**
     * Background position, default : center
     */
    backgroundPosition?: string;
}
interface GalleryOptions extends ItemOptions {
    rowsPerPage?: number;
    minRowsAtStart?: number;
    infiniteScrollOffset?: number;
    photoSwipeOptions?: PhotoSwipeOptions;
    photoSwipePluginsInitFn?: ((lighbox: PhotoSwipeLightbox) => void) | null;
    ssr?: {
        /**
         * In SSR mode, if the gallery width cannot be computed, it will fallback to this value
         */
        galleryWidth: number;
    };
}
declare abstract class AbstractGallery<Model extends ModelAttributes> {
    protected elementRef: HTMLElement;
    protected scrollElementRef?: HTMLElement | null | undefined;
    /**
     * Default options
     */
    protected options: Required<GalleryOptions>;
    /**
     * Images wrapper container
     * If setted, serves as mark for "initialized status" of the gallery
     */
    protected bodyElementRef: HTMLElement | null;
    /**
     * Items for which container has been added to dom, but image has not been queries yet
     */
    protected scrollBufferedItems: Item<Model>[];
    /**
     * Debounce function
     * Runs a small delay after last image has been added to dom
     * When it runs, images are loaded (appear with fade) and more images are queries to preserve a buffer of
     * out-of-dom items
     */
    protected flushBufferedItems: () => void;
    /**
     * Number of items to query on buffer flushing
     */
    protected requiredItems: number;
    /**
     * Used to test the scroll direction
     * Avoid to load more images when scrolling up
     */
    private old_scroll_top;
    /**
     * Stores page index that have been emmited
     * Keeps a log of pages already asked to prevent to ask them multiple times
     */
    private requestedIndexesLog;
    /**
     * Reference to next button element
     */
    private nextButton;
    protected readonly document: Document;
    /**
     * PhotoSwipe Lightbox object
     */
    protected psLightbox: PhotoSwipeLightbox | null;
    /**
     * Get PhotoSwipe Lightbox
     */
    get photoSwipe(): PhotoSwipeLightbox | null;
    /**
     * Get currently selected PhotoSwipe image
     */
    get photoSwipeCurrentItem(): Model | null;
    /**
     *
     * @param elementRef
     * @param options
     * @param scrollElementRef
     */
    constructor(elementRef: HTMLElement, options: GalleryOptions, scrollElementRef?: HTMLElement | null | undefined);
    /**
     * Complete collection of images
     * @type {Array}
     */
    protected _collection: Item<Model>[];
    get collection(): Item<Model>[];
    /**
     * Partial set of items that represent the visible items
     * @type {Item[]}
     * @private
     */
    protected _domCollection: Item<Model>[];
    get domCollection(): Item<Model>[];
    get selectedItems(): Model[];
    get width(): number;
    get collectionLength(): number;
    get domCollectionLength(): number;
    /**
     * Initializes DOM manipulations
     */
    init(): void;
    /**
     * Initializes PhotoSwipe
     */
    protected photoSwipeInit(): void;
    addItemToPhotoSwipeCollection(item: Item<Model>): void;
    /**
     * Add items to collection
     * Transform given list of models into inner Items
     * @param models list of models
     */
    addItems(models: Model[]): void;
    setLabelHover(activate: boolean): void;
    /**
     * Select all items visible in the DOM
     * Ignores buffered items
     */
    selectVisibleItems(): Model[];
    /**
     * Unselect all selected elements
     */
    unselectAllItems(): void;
    /**
     * Allows to use the same approach and method name to listen as gallery events on DOM or on javascript gallery
     * object
     *
     * Gallery requests items when it's instantiated. But user may subscribe after creation, so we need to request
     * again if user subscribes by this function.
     *
     * @param name
     * @param callback
     * @param options An object that specifies characteristics about the event listener. The available options are, see
     *     addEventListener official documentation
     */
    addEventListener<K extends keyof CustomEventDetailMap<Model>>(name: K, callback: (evt: CustomEvent<CustomEventDetailMap<Model>[K]>) => void, options?: boolean | AddEventListenerOptions): void;
    /**
     * Public api for empty function
     * Emits a pagination event
     */
    clear(): void;
    /**
     * Return copy of options to prevent modification
     */
    getOptions(): GalleryOptions;
    /**
     * Override current collection
     * @param {Item[]} items
     */
    setItems(items: Model[]): void;
    /**
     *
     */
    abstract organizeItems(items: Item<Model>[], fromRow?: number, toRow?: number): void;
    /**
     * If gallery already has items on initialisation, set first page visible, load second page and query for more
     * items if needed.
     * If not, just query for items
     */
    protected initItems(): void;
    /**
     *
     */
    protected abstract getEstimatedColumnsPerRow(): number;
    /**
     * AbstractRowGallery + Masonry
     */
    protected abstract onScroll(): void;
    /**
     * AbstractRowGallery + Masonry
     */
    protected abstract onPageAdd(): void;
    /**
     * Return number of rows to show per page to fill the empty space until the bottom of the screen
     * Should grant all the space is used or more, but not less.
     * @returns {number}
     */
    protected abstract getEstimatedRowsPerPage(): number;
    /**
     * Fire pagination event
     * Information provided in the event allows to retrieve items from the server using given data :
     * "offset" and "limit" that have the same semantic that respective attributes in mySQL.
     *
     * The gallery asks for items it needs, including some buffer items that are not displayed when given but are
     * available to be added immediately to DOM when user scrolls.
     *
     */
    protected requestItems(): void;
    /**
     * Returns option.rowsPerPage is specified.
     * If not returns the estimated number of rows to fill the rest of the vertical space in the screen
     * @returns {number}
     */
    protected getRowsPerPage(): number;
    /**
     * Add given item to DOM and to domCollection
     * @param {Item} item
     * @param destination
     */
    protected addItemToDOM(item: Item<Model>, destination?: HTMLElement | null): void;
    protected updateNextButtonVisibility(): void;
    /**
     * If infinite scroll (no option.rowsPerPage provided), a minimum height is setted to force gallery to overflow
     * from viewport. This activates the scroll before adding items to dom. This prevents the scroll to fire new resize
     * event and recompute all gallery twice on start.
     */
    protected extendToFreeViewport(): void;
    /**
     * Space between the top of the gallery wrapper (parent of gallery root elementRef) and the bottom of the window
     */
    protected getGalleryVisibleHeight(): number;
    protected startResize(): void;
    protected endResize(): void;
    protected dispatchEvent<K extends keyof CustomEventDetailMap<Model>>(name: K, data: CustomEventDetailMap<Model>[K]): void;
    /**
     * Effectively empty gallery, and should prepare container to receive new items
     */
    protected empty(): void;
    /**
     * Listen to scroll event and manages rows additions for lazy load
     * @param {HTMLElement | Document} element
     */
    private bindScroll;
}

interface RatioLimits {
    min?: number;
    max?: number;
}

declare abstract class AbstractRowGallery<Model extends ModelAttributes> extends AbstractGallery<Model> {
    protected onScroll(): void;
    protected onPageAdd(): void;
    /**
     * Add given number of rows to DOM
     * @param rows
     */
    protected addRows(rows: number): void;
    protected endResize(): void;
}

interface NaturalGalleryOptions extends GalleryOptions {
    rowHeight: number;
    ratioLimit?: RatioLimits;
}
declare class Natural<Model extends ModelAttributes = ModelAttributes> extends AbstractRowGallery<Model> {
    /**
     * Options after having been defaulted
     */
    protected options: Required<NaturalGalleryOptions>;
    constructor(elementRef: HTMLElement, options: NaturalGalleryOptions, scrollElementRef?: HTMLElement | null);
    static organizeItems<T extends ModelAttributes>(gallery: Natural<T>, items: Item<T>[], fromRow?: number, toRow?: number | null, currentRow?: number | null): void;
    /**
     * Compute sizes for given images to fit in given row width
     * Items are updated
     */
    static computeSizes<T extends ModelAttributes>(chunk: Item<T>[], containerWidth: number | null, margin: number, row: number, maxRowHeight?: number | null, ratioLimits?: RatioLimits): void;
    static getRowWidth(models: SizedModel[], maxRowHeight: number, margin: number, ratioLimits?: RatioLimits): number;
    static getRowHeight(models: SizedModel[], containerWidth: number, margin: number, ratioLimits?: RatioLimits): number;
    /**
     * Return the ratio format of models as if they where a single image
     */
    static getRatios(models: SizedModel[], ratioLimits?: RatioLimits): number;
    addRows(rows: number): void;
    organizeItems(items: Item<Model>[], fromRow?: number, toRow?: number): void;
    protected endResize(): void;
    protected getEstimatedColumnsPerRow(): number;
    protected getEstimatedRowsPerPage(): number;
    private completeLastRow;
}

declare interface ColumnOptions {
    width: number;
    gap: number;
}
declare class Column<Model extends ModelAttributes> {
    private options;
    private readonly collection;
    private readonly _elementRef;
    constructor(document: Document, options: ColumnOptions);
    addItem(item: Item<Model>): void;
    get height(): number;
    get length(): number;
    get elementRef(): HTMLElement;
}

interface MasonryGalleryOptions extends GalleryOptions {
    columnWidth: number;
    ratioLimit?: RatioLimits;
}
declare class Masonry<Model extends ModelAttributes = ModelAttributes> extends AbstractGallery<Model> {
    /**
     * Options after having been defaulted
     */
    protected options: Required<MasonryGalleryOptions>;
    /**
     * Regroup the list of columns
     */
    protected columns: Column<Model>[];
    constructor(elementRef: HTMLElement, options: MasonryGalleryOptions, scrollElementRef?: HTMLElement | null);
    /**
     * Compute sides with 1:1 ratio
     */
    static organizeItems<T extends ModelAttributes>(gallery: Masonry<T>, items: Item<T>[], fromIndex?: number, toIndex?: number | null): void;
    init(): void;
    organizeItems(items: Item<Model>[], fromRow?: number, toRow?: number): void;
    protected initItems(): void;
    protected onScroll(): void;
    protected onPageAdd(): void;
    protected getEstimatedColumnsPerRow(): number;
    protected getEstimatedRowsPerPage(): number;
    /**
     * Use current gallery height as reference. To fill free space it add images until the gallery height changes, then are one more row
     */
    protected addUntilFill(): void;
    protected addItemToDOM(item: Item<Model>): void;
    protected endResize(): void;
    protected addColumns(): void;
    protected empty(): void;
    /**
     * Returns true if at least one columns doesn't overflow on the bottom of the viewport
     */
    private viewPortIsNotFilled;
    private addItemsToDom;
    /**
     * Return square side size
     */
    private getColumnWidth;
    private getShortestColumn;
}

interface SquareGalleryOptions extends GalleryOptions {
    itemsPerRow: number;
}
declare class Square<Model extends ModelAttributes = ModelAttributes> extends AbstractRowGallery<Model> {
    /**
     * Options after having been defaulted
     */
    protected options: Required<SquareGalleryOptions>;
    constructor(elementRef: HTMLElement, options: SquareGalleryOptions, scrollElementRef?: HTMLElement | null);
    /**
     * Compute sides with 1:1 ratio
     */
    static organizeItems<T extends ModelAttributes>(gallery: Square<T>, items: Item<T>[], firstRowIndex?: number, toRow?: number | null): void;
    protected getEstimatedColumnsPerRow(): number;
    protected getEstimatedRowsPerPage(): number;
    /**
     * Return square side size
     */
    protected getItemSideSize(): number;
    organizeItems(items: Item<Model>[], fromRow?: number, toRow?: number): void;
}

export { type CustomEventDetailMap, Masonry, type MasonryGalleryOptions, type ModelAttributes, Natural, type NaturalGalleryOptions, Square, type SquareGalleryOptions };