var Block;
(function (Block) {
    Block.empty = () => ({
        id: '',
        version: '',
        name: '',
        elements: [],
        settings: {
            linked: []
        }
    });
})(Block || (Block = {}));

var AudioElement;
(function (AudioElement) {
    AudioElement.empty = (id) => ({
        id,
        type: 'audio',
        location: [],
        autoplay: false,
        metadata: {}
    });
    AudioElement.ICON = 'fas:volume-up';
})(AudioElement || (AudioElement = {}));

var CheckboxElement;
(function (CheckboxElement) {
    CheckboxElement.empty = (id) => ({
        id,
        type: 'checkbox',
        question: '',
        required: false,
        orientation: 'vertical',
        options: [],
        other: false,
        answer: [],
        metadata: {}
    });
    CheckboxElement.ICON = 'fas:check-square';
})(CheckboxElement || (CheckboxElement = {}));

var DownloadElement;
(function (DownloadElement) {
    DownloadElement.empty = (id) => ({
        id,
        type: 'download',
        text: '',
        location: [],
        metadata: {}
    });
    DownloadElement.ICON = 'fas:download';
})(DownloadElement || (DownloadElement = {}));

var HeaderElement;
(function (HeaderElement) {
    HeaderElement.empty = (id) => ({
        id,
        type: 'header',
        text: '',
        metadata: {}
    });
    HeaderElement.ICON = 'fas:heading';
})(HeaderElement || (HeaderElement = {}));

var IconElement;
(function (IconElement) {
    IconElement.empty = (id) => ({
        id,
        type: 'icon',
        question: '',
        required: false,
        options: [
            { text: 'fas:sad-tear', color: '#ef8b54' },
            { text: 'fas:frown-open', color: '#f3ac4c' },
            { text: 'fas:meh', color: '#f6d24c' },
            { text: 'fas:grin', color: '#d7f18e' },
            { text: 'fas:laugh', color: '#aad77e' }
        ],
        metadata: {}
    });
    IconElement.ICON = 'fas:smile';
})(IconElement || (IconElement = {}));

var ImageElement;
(function (ImageElement) {
    ImageElement.empty = (id) => ({
        id,
        type: 'image',
        location: [],
        metadata: {}
    });
    ImageElement.ICON = 'fas:image';
})(ImageElement || (ImageElement = {}));

var InputElement;
(function (InputElement) {
    InputElement.empty = (id) => ({
        id,
        type: 'input',
        question: '',
        required: false,
        multiline: false,
        min: null,
        max: null,
        answer: '',
        hasAnswer: false,
        metadata: {}
    });
    InputElement.ICON = 'fas:font';
})(InputElement || (InputElement = {}));

var LinkElement;
(function (LinkElement) {
    LinkElement.empty = (id) => ({
        id,
        type: 'link',
        href: '',
        text: '',
        icon: '',
        metadata: {}
    });
    LinkElement.ICON = 'fas:link';
})(LinkElement || (LinkElement = {}));

var MatrixElement;
(function (MatrixElement) {
    MatrixElement.empty = (id) => ({
        id,
        type: 'matrix',
        question: '',
        required: false,
        rows: [],
        columns: [],
        orientation: 'vertical',
        metadata: {}
    });
    MatrixElement.ICON = 'fas:th';
})(MatrixElement || (MatrixElement = {}));

var RadioElement;
(function (RadioElement) {
    RadioElement.empty = (id) => ({
        id,
        type: 'radio',
        question: '',
        required: false,
        orientation: 'vertical',
        options: [],
        other: false,
        answer: null,
        metadata: {}
    });
    RadioElement.ICON = 'fas:dot-circle';
})(RadioElement || (RadioElement = {}));

var SliderElement;
(function (SliderElement) {
    SliderElement.empty = (id) => ({
        id,
        type: 'slider',
        question: '',
        required: false,
        step: 1,
        min: 1,
        max: 5,
        minText: '',
        maxText: '',
        metadata: {}
    });
    SliderElement.ICON = 'fas:sliders-h';
})(SliderElement || (SliderElement = {}));

var SortElement;
(function (SortElement) {
    SortElement.empty = (id) => ({
        id,
        type: 'sort',
        question: '',
        required: false,
        dual: false,
        options: [],
        feedbackCorrect: '',
        feedbackIncorrect: '',
        metadata: {}
    });
    SortElement.ICON = 'fas:sort';
})(SortElement || (SortElement = {}));

var TableElement;
(function (TableElement) {
    TableElement.empty = (id) => ({
        id,
        type: 'table',
        question: '',
        required: false,
        columns: [],
        orientation: 'horizontal',
        maxRows: 0,
        metadata: {}
    });
    TableElement.ICON = 'fas:table';
})(TableElement || (TableElement = {}));

var TextElement;
(function (TextElement) {
    TextElement.empty = (id) => ({
        id,
        type: 'text',
        text: '',
        metadata: {}
    });
    TextElement.ICON = 'fas:paragraph';
})(TextElement || (TextElement = {}));

var VideoElement;
(function (VideoElement) {
    VideoElement.empty = (id) => ({
        id,
        type: 'video',
        location: [],
        autoplay: false,
        metadata: {}
    });
    VideoElement.ICON = 'fas:video';
})(VideoElement || (VideoElement = {}));

var BlockElement;
(function (BlockElement) {
    BlockElement.NamespaceMap = {
        audio: AudioElement,
        checkbox: CheckboxElement,
        download: DownloadElement,
        header: HeaderElement,
        icon: IconElement,
        image: ImageElement,
        input: InputElement,
        link: LinkElement,
        matrix: MatrixElement,
        radio: RadioElement,
        slider: SliderElement,
        sort: SortElement,
        table: TableElement,
        text: TextElement,
        video: VideoElement
    };
    BlockElement.empty = (id, type) => {
        return BlockElement.NamespaceMap[type].empty(id);
    };
    BlockElement.icon = (type) => BlockElement.NamespaceMap[type].ICON;
    BlockElement.TYPES = Object.keys(BlockElement.NamespaceMap);
})(BlockElement || (BlockElement = {}));

var ModuleFlow;
(function (ModuleFlow) {
    // prettier-ignore
    ModuleFlow.DEFAULT_CONDITION = function condition(context) { return true; }.toString();
    ModuleFlow.empty = () => ({
        start: { id: '', version: '' },
        blocks: {},
        orphans: []
    });
})(ModuleFlow || (ModuleFlow = {}));
var Module;
(function (Module) {
    Module.empty = () => ({
        id: '',
        version: '',
        name: '',
        description: '',
        category: '',
        image: [],
        flow: ModuleFlow.empty(),
        settings: {
            active: false,
            locked: false,
            createdAt: new Date().toISOString(),
            createdBy: ''
        }
    });
})(Module || (Module = {}));

class FlowTree {
    constructor(flow) {
        this.orphans = [];
        this._nodes = new Map();
        this._indexes = new Map();
        this.flow = flow;
        this.root = this.parse(flow.start, flow);
        for (const orphan of flow.orphans || [])
            this.orphans.push(this.parse(orphan, flow));
        // Add indexes
        let i = 1;
        for (const node of this.nodes) {
            if (!node.id)
                continue;
            const id = FlowTree.encode(node.id, node.version);
            if (!this._indexes.has(id))
                this._indexes.set(id, i++);
        }
    }
    get depth() {
        return this.root.depth;
    }
    get nodes() {
        const nodes = [];
        const iterated = new Set();
        for (const node of this.root.nodes) {
            if (iterated.has(node))
                continue;
            iterated.add(node);
            nodes.push(node);
        }
        for (const orphan of this.orphans) {
            for (const node of orphan.nodes) {
                if (iterated.has(node))
                    continue;
                iterated.add(node);
                nodes.push(node);
            }
        }
        return nodes;
    }
    static encode(arg1, arg2) {
        const { id, version } = typeof arg1 === 'string' ? { id: arg1, version: arg2 } : arg1;
        return `${id}@${version}`;
    }
    static decode(encoded) {
        const [id, version] = encoded.split('@');
        return { id, version };
    }
    canLink(source, target, once = false) {
        const encoded = FlowTree.encode(source);
        const src = this._nodes.get(encoded);
        if (!src)
            return false;
        // Can not link nodes if the target node is already a child of the source
        // node or the other way around
        for (const node of src.nodes)
            if (node.id === target.id)
                return false;
        return once ? true : this.canLink(target, source, true);
    }
    getDepth(arg1, arg2) {
        const node = this.getNode(arg1, arg2);
        if (!node)
            return 0;
        const parents = this.nodes.filter(n => n.children.indexOf(node) >= 0);
        return parents.length
            ? 1 + Math.max(...parents.map(n => this.getDepth(n.id, n.version)))
            : 0;
    }
    getNode(arg1, arg2) {
        const encoded = arg2 != undefined ? FlowTree.encode(arg1, arg2) : arg1;
        const index = this._indexes.get(encoded);
        return index ? this.nodes[index - 1] : undefined;
    }
    getIndex(arg1, arg2) {
        const encoded = arg2 != undefined ? FlowTree.encode(arg1, arg2) : arg1;
        const index = this._indexes.get(encoded);
        return index == undefined ? -1 : index;
    }
    getLabel(arg1, arg2) {
        const index = this.getIndex(arg1, arg2);
        return index >= 0 ? ('000' + index).slice(-3) : '';
    }
    add(target, id) {
        const prev = this.flow.blocks[target.id] || [];
        const blocks = Object.assign(Object.assign({}, this.flow.blocks), { [target.id]: [...prev, { id, version: 'v1' }] });
        return Object.assign(Object.assign({}, this.flow), { blocks });
    }
    delete(target) {
        if (this.flow.start.id === target.id)
            return this.flow;
        // Remove every reference of the node from the blocks chain
        const blocks = Object.keys(this.flow.blocks).reduce((blocks, id) => {
            if (id === target.id)
                return blocks;
            const next = this.flow.blocks[id];
            blocks[id] =
                next && next.findIndex(n => n.id === target.id) >= 0
                    ? [
                        ...next.filter(n => n.id !== target.id),
                        ...(this.flow.blocks[target.id] || [])
                    ]
                    : next;
            return blocks;
        }, {});
        // Filter block from orphans
        const filtered = this.flow.orphans.filter(o => o.id !== target.id);
        if (this.flow.orphans.length === filtered.length)
            return Object.assign(Object.assign({}, this.flow), { blocks });
        // An orphaned block was deleted, gather the new orphans
        const newOrphans = (this.flow.blocks[target.id] || []).filter(n => {
            return Object.keys(blocks).every(id => blocks[id].every(c => c.id !== n.id));
        });
        const orphans = [...filtered, ...newOrphans];
        return Object.assign(Object.assign({}, this.flow), { blocks, orphans });
    }
    unlink(source, target) {
        // Unlink blocks
        const next = this.flow.blocks[source];
        const detached = next.find(n => n.id === target);
        const blocks = Object.assign(Object.assign({}, this.flow.blocks), { [source]: next.filter(n => n.id !== target) });
        // Add target to orphaned blocks in case there is no way to reach the block
        const orphaned = Object.keys(blocks).every(id => blocks[id].every(n => n.id !== target));
        const orphans = orphaned
            ? [...this.flow.orphans, detached]
            : this.flow.orphans;
        return Object.assign(Object.assign({}, this.flow), { blocks, orphans });
    }
    parse(current, flow) {
        const encoded = FlowTree.encode(current);
        const existing = this._nodes.get(encoded);
        if (existing)
            return existing;
        const { id, version } = current;
        const children = this.parseChildren(current, flow);
        const node = new FlowTreeNode(id, version, children);
        this._nodes.set(encoded, node);
        return node;
    }
    parseChildren(current, flow) {
        const next = (flow.blocks || {})[current.id];
        return next ? next.map(n => this.parse(n, flow)).filter(n => !!n) : [];
    }
}
class FlowTreeNode {
    constructor(id, version, children) {
        this.id = id;
        this.version = version;
        this.children = children;
    }
    get depth() {
        const depths = this.children.map(child => child.depth);
        return 1 + Math.max(0, ...depths);
    }
    get deepestPath() {
        const paths = this.children.map(child => child.deepestPath);
        let deepest = [];
        for (const path of paths)
            if (deepest.length < path.length)
                deepest = path;
        return [this, ...deepest];
    }
    get nodes() {
        const nestedNodes = this.children.map(n => n.nodes);
        const nodes = [].concat(...nestedNodes);
        return [this, ...nodes];
    }
}

class Polyline {
    constructor(points) {
        this.sections = [];
        this.points = points.map(({ x, y }) => [x, y]);
        for (let i = 0; i < this.points.length - 1; i++) {
            const p1 = this.points[i];
            const p2 = this.points[i + 1];
            const size = this.distance(p1, p2);
            this.sections.push({ p1, p2, size });
        }
    }
    /**
     * Returns the size of the polyline.
     */
    get size() {
        return this.sections.reduce((total, { size }) => total + size, 0);
    }
    /**
     * Returns the middle of the polyline.
     */
    get middle() {
        // Middle of the line in terms of size
        const target = this.size / 2;
        // Move along sections until we find the section that exceeds our target,
        // from this section we can calculate the middle point's coordinated.
        let cur = 0;
        for (const section of this.sections) {
            const next = cur + section.size;
            if (next > target)
                return this.along(section, target - cur);
            cur = next;
        }
        // Polyline has no sections
        return [0, 0];
    }
    /**
     * Moves along the section by the specified distance.
     * @param section
     * @param distance
     */
    along(section, distance) {
        const [x1, y1] = section.p1;
        const [x2, y2] = section.p2;
        const k = distance / section.size;
        return [k * x2 + (1 - k) * x1, k * y2 + (1 - k) * y1];
    }
    /**
     * Calculate the distance between two points.
     * @param p1 Start point
     * @param p2 End point
     */
    distance([x1, y1], [x2, y2]) {
        const horDistance = Math.pow(x2 - x1, 2);
        const verDistance = Math.pow(y2 - y1, 2);
        return Math.sqrt(horDistance + verDistance);
    }
}

const storedFiles = (arg) => {
    if ('flow' in arg)
        return storedModule(arg);
    else if ('type' in arg)
        return storedElement(arg);
    else
        return storedBlock(arg);
};
const storedModule = (module) => {
    return module.image;
};
const storedBlock = (block) => {
    return [].concat(...block.elements.map(storedElement));
};
const storedElement = (element) => {
    switch (element.type) {
        case 'audio':
        case 'download':
        case 'image':
        case 'video':
            return element.location;
        case 'checkbox':
        case 'radio':
        case 'sort':
            return storedOptions(element.options);
        case 'matrix':
            const rows = storedOptions(element.rows);
            const columns = storedOptions(element.columns);
            return [...rows, ...columns];
        case 'table':
            return storedOptions(element.columns);
        default:
            return [];
    }
};
const storedOptions = (options) => {
    return options
        .filter(option => 'image' in option)
        .map(option => option.image);
};

export { Block as B, FlowTree as F, ModuleFlow as M, Polyline as P, BlockElement as a, Module as b, FlowTreeNode as c, storedFiles as s };
