import { Model } from "../../model";
import { union, intersection, difference, symmetric_difference } from "../../core/util/array";
import { merge } from "../../core/util/object";
import { Arrayable, Int, Mapping } from "../../core/kinds";
import { map } from "../../core/util/arrayable";
export const OpaqueIndices = Arrayable(Int);
export const MultiIndices = Mapping(Int, OpaqueIndices);
export class Selection extends Model {
    static __name__ = "Selection";
    constructor(attrs) {
        super(attrs);
    }
    get_view() {
        return this.view;
    }
    static {
        this.define(({ Int, List, Struct }) => ({
            indices: [OpaqueIndices, []],
            line_indices: [OpaqueIndices, []],
            multiline_indices: [MultiIndices, new Map()],
            image_indices: [List(Struct({ index: Int, i: Int, j: Int, flat_index: Int })), []],
        }));
        this.internal(({ List, AnyRef, Nullable }) => ({
            selected_glyphs: [List(AnyRef()), []],
            view: [Nullable(AnyRef()), null],
        }));
    }
    get selected_glyph() {
        return this.selected_glyphs.length > 0 ? this.selected_glyphs[0] : null;
    }
    add_to_selected_glyphs(glyph) {
        this.selected_glyphs.push(glyph);
    }
    update(selection, _final = true, mode = "replace") {
        switch (mode) {
            case "replace": {
                this.update_through_replacement(selection);
                break;
            }
            case "toggle": {
                this.update_through_toggle(selection);
                break;
            }
            case "append": {
                this.update_through_union(selection);
                break;
            }
            case "intersect": {
                this.update_through_intersection(selection);
                break;
            }
            case "subtract": {
                this.update_through_subtraction(selection);
                break;
            }
            case "xor": {
                this.update_through_symmetric_difference(selection);
                break;
            }
        }
    }
    // TODO `size` wouldn't be needed if `indices` was an instance of
    // `Indices` class, instead of an array of numbers. Then also we
    // could just call `.invert()` on the class.
    invert(size) {
        const indices = new Set(this.indices);
        const inversion = [];
        for (let i = 0; i < size; i++) {
            if (!indices.has(i)) {
                inversion.push(i);
            }
        }
        this.indices = inversion;
        // this.line_indices
        // this.multiline_indices
        // this.image_indices
    }
    clear() {
        this.indices = [];
        this.line_indices = [];
        this.multiline_indices = new Map();
        this.image_indices = [];
        this.view = null;
        this.selected_glyphs = [];
    }
    map(mapper) {
        return new Selection({
            ...this.attributes,
            indices: map(this.indices, mapper),
            // NOTE: line_indices don't support subset indexing
            multiline_indices: new Map(map([...this.multiline_indices.entries()], ([index, line_indices]) => [mapper(index), line_indices])),
            image_indices: this.image_indices.map((image_index) => ({ ...image_index, index: mapper(image_index.index) })),
        });
    }
    is_empty() {
        return this.indices.length == 0 && this.line_indices.length == 0 && this.image_indices.length == 0;
    }
    _union_image_indices(...collection) {
        const is = new Map();
        const js = new Map();
        const result = [];
        for (const indices of collection) {
            for (const image_index of indices) {
                const { index, i, j } = image_index;
                const iis = is.get(index);
                const ijs = js.get(index);
                if (iis != null && ijs != null) {
                    if (!iis.has(i) || !ijs.has(j)) {
                        result.push(image_index);
                        iis.add(i);
                        ijs.add(j);
                    }
                }
                else {
                    result.push(image_index);
                    is.set(index, new Set([i]));
                    js.set(index, new Set([j]));
                }
            }
        }
        return result;
    }
    update_through_replacement(other) {
        this.indices = other.indices;
        this.line_indices = other.line_indices;
        this.multiline_indices = other.multiline_indices;
        this.image_indices = other.image_indices;
        this.view = other.view;
        this.selected_glyphs = other.selected_glyphs;
    }
    update_through_toggle(other) {
        // note the order of arguments when comparing with update_through_subtraction()
        this.indices = difference(other.indices, this.indices);
        // TODO: think through and fix any logic below
        this.selected_glyphs = union(other.selected_glyphs, this.selected_glyphs);
        this.line_indices = union(other.line_indices, this.line_indices);
        this.image_indices = this._union_image_indices(this.image_indices, other.image_indices); // TODO
        this.view = other.view;
        this.multiline_indices = merge(other.multiline_indices, this.multiline_indices);
    }
    update_through_union(other) {
        this.indices = union(this.indices, other.indices);
        this.selected_glyphs = union(other.selected_glyphs, this.selected_glyphs);
        this.line_indices = union(other.line_indices, this.line_indices);
        this.image_indices = this._union_image_indices(this.image_indices, other.image_indices); // TODO
        this.view = other.view;
        this.multiline_indices = merge(other.multiline_indices, this.multiline_indices);
    }
    update_through_intersection(other) {
        this.indices = intersection(this.indices, other.indices);
        // TODO: think through and fix any logic below
        this.selected_glyphs = union(other.selected_glyphs, this.selected_glyphs);
        this.line_indices = union(other.line_indices, this.line_indices);
        this.image_indices = this._union_image_indices(this.image_indices, other.image_indices); // TODO
        this.view = other.view;
        this.multiline_indices = merge(other.multiline_indices, this.multiline_indices);
    }
    update_through_subtraction(other) {
        this.indices = difference(this.indices, other.indices);
        // TODO: think through and fix any logic below
        this.selected_glyphs = union(other.selected_glyphs, this.selected_glyphs);
        this.line_indices = union(other.line_indices, this.line_indices);
        this.image_indices = this._union_image_indices(this.image_indices, other.image_indices); // TODO
        this.view = other.view;
        this.multiline_indices = merge(other.multiline_indices, this.multiline_indices);
    }
    update_through_symmetric_difference(other) {
        this.indices = symmetric_difference(this.indices, other.indices);
        // TODO: think through and fix any logic below
        this.selected_glyphs = union(other.selected_glyphs, this.selected_glyphs);
        this.line_indices = union(other.line_indices, this.line_indices);
        this.image_indices = this._union_image_indices(this.image_indices, other.image_indices); // TODO
        this.view = other.view;
        this.multiline_indices = merge(other.multiline_indices, this.multiline_indices);
    }
}
//# sourceMappingURL=selection.js.map