import './ripple-e6ddbc19.js';

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 = (metadata) => ({
        id: '',
        version: '',
        name: '',
        description: '',
        category: '',
        image: '',
        flow: ModuleFlow.empty(),
        settings: {
            active: false,
            locked: false,
            createdAt: new Date().toISOString(),
            createdBy: ''
        },
        metadata: metadata || {}
    });
})(Module || (Module = {}));

function debounce(f, wait = 200) {
    let timeout = undefined;
    return function () {
        const context = this;
        const args = arguments;
        window.clearTimeout(timeout);
        timeout = window.setTimeout(() => f.apply(context, args), wait);
    };
}

class File {
    constructor(id, name, data) {
        this.id = id;
        this.name = name;
        this.type = data.type;
        this.blob = data;
    }
}

class UUID {
    static v4() {
        return ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => {
            const r = crypto.getRandomValues(new Uint8Array(1))[0];
            const n = parseInt(c, 10);
            return (n ^ (r & (15 >> (n / 4)))).toString(16);
        });
    }
}

class Validator {
    constructor() {
        this.rules = new Map();
    }
    validate(rule, value, el, meta) {
        const rules = rule.split('|').map(r => {
            const [rule, arg] = r.split(':');
            return { rule, arg };
        });
        return this.exec(value, rules, el, meta);
    }
    async exec(value, rules, el, meta) {
        let errors = [];
        for (const { rule, arg } of rules) {
            const error = await this.rule(value, rule, el, arg, meta);
            if (error)
                errors.push(error);
        }
        return errors;
    }
    async rule(value, rule, el, arg, meta) {
        const validator = this.rules.get(rule);
        if (!validator)
            throw new Error('Unknown validation rule: ' + rule);
        const validation = await validator(value, arg, el);
        if (validation.valid)
            return null;
        const message = this.translator
            ? await this.translator(rule, {
                arg,
                default: validation.message,
                meta,
                value
            })
            : validation.message;
        return { rule, message };
    }
}
const validator = new Validator();
validator.rules.set('alpha', async (value) => {
    const valid = /^[A-Z]*$/i.test(value);
    const message = 'This field should only contain alphabetic characters [A-Z].';
    return { valid, message };
});
validator.rules.set('alpha-dash', async (value) => {
    const valid = /^[0-9A-Z_-]*$/i.test(value);
    const message = 'This field should only contain alphanumeric characters or dashes [0-9A-Z_-].';
    return { valid, message };
});
validator.rules.set('alpha-num', async (value) => {
    const valid = /^[0-9A-Z]*$/i.test(value);
    const message = 'This field should only contain alphanumeric characters [0-9A-Z].';
    return { valid, message };
});
validator.rules.set('alpha-spaces', async (value) => {
    const valid = /^[A-Z\s]*$/i.test(value);
    const message = 'This field should only contain alphabetic characters or whitespace [A-Zs].';
    return { valid, message };
});
validator.rules.set('between', async (value, arg) => {
    const [min, max] = arg ? arg.split(',') : [0, 1];
    const valid = Number(min) <= Number(value) && Number(max) >= Number(value);
    const message = `This field should contain a value between ${min} and ${max}.`;
    return { valid, message };
});
validator.rules.set('digits', async (value, arg) => {
    const length = arg && Number(arg);
    const str = '' + value;
    const valid = /^[0-9]*$/.test(str) && (length ? str.length === length : true);
    const message = length
        ? `This field requires ${length} digits.`
        : 'This field should only contain numbers.';
    return { valid, message };
});
validator.rules.set('email', async (value) => {
    const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    const valid = re.test(value);
    const message = 'This is not a valid email.';
    return { valid, message };
});
validator.rules.set('in', async (value, arg) => {
    const options = arg ? arg.split(',') : [];
    const valid = options.indexOf('' + value) >= 0;
    const message = 'This value is not allowed.';
    return { valid, message };
});
validator.rules.set('is', async (value, arg) => {
    const valid = arg === value;
    const message = `The value should be: ${arg}.`;
    return { valid, message };
});
validator.rules.set('max', async (value, arg) => {
    const max = Number(arg);
    const size = typeof value === 'number' ? value : ('' + value).length;
    const valid = size <= max;
    const message = typeof value === 'number'
        ? `This field may not exceed ${max}.`
        : `This field may not exceed ${max} characters.`;
    return { valid, message };
});
validator.rules.set('min', async (value, arg) => {
    const min = Number(arg);
    const size = typeof value === 'number' ? value : ('' + value).length;
    const valid = size >= min;
    const message = typeof value === 'number'
        ? `This field should be ${min} or higher.`
        : `This field requires at least ${min} characters.`;
    return { valid, message };
});
validator.rules.set('numeric', async (value) => {
    const str = '' + value;
    const valid = /^[0-9]+$/.test(str);
    const message = 'This field should only contain numeric characters [0-9].';
    return { valid, message };
});
validator.rules.set('required', async (value) => {
    const valid = Array.isArray(value) ? !!value.length : value === 0 || !!value;
    const message = 'This field is required.';
    return { valid, message };
});

class File$1 extends File {
    constructor(id, name, path, blob) {
        super(id, name, blob);
        this.path = path;
    }
}

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 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);
    }
    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({}, this.flow.blocks, { [target.id]: [...prev, { id, version: 'v1' }] });
        return 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({}, 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({}, 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({}, 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({}, 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 {
    get nodes() {
        const nestedNodes = this.children.map(n => n.nodes);
        const nodes = [].concat(...nestedNodes);
        return [this, ...nodes];
    }
    constructor(id, version, children) {
        this.id = id;
        this.version = version;
        this.children = children;
    }
}

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, id = true) => {
    // prettier-ignore
    const files = 'flow' in arg
        ? storedModule(arg)
        : 'type' in arg ? storedElement(arg) : storedBlock(arg);
    return id ? files.map(file => file.match(/BCE:\/\/([^/]+)\//)[1]) : files;
};
const storedModule = (module) => {
    return stored(module.image) ? [module.image] : [];
};
const storedBlock = (block) => {
    return [].concat(...block.elements.map(storedElement));
};
const storedElement = (element) => {
    switch (element.type) {
        case 'audio':
        case 'download':
        case 'image':
        case 'image-map':
        case 'video':
            return stored(element.location) ? [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 && option.image && stored(option.image))
        .map(option => option.image);
};
const stored = (location) => {
    return !!location && location.startsWith('BCE://');
};

export { File$1 as F, ModuleFlow as M, Polyline as P, UUID as U, Module as a, FlowTree as b, FlowTreeNode as c, debounce as d, File as e, storedFiles as s, validator as v };
