import {
    transition_in,
    transition_out
} from './transitions.js';
import {
    run_all
} from './utils.js';

// general each functions:

export function ensure_array_like(array_like_or_iterator) {
    return array_like_or_iterator ? .length !== undefined ?
        array_like_or_iterator :
        Array.from(array_like_or_iterator);
}

// keyed each functions:

/** @returns {void} */
export function destroy_block(block, lookup) {
    block.d(1);
    lookup.delete(block.key);
}

/** @returns {void} */
export function outro_and_destroy_block(block, lookup) {
    transition_out(block, 1, 1, () => {
        lookup.delete(block.key);
    });
}

/** @returns {void} */
export function fix_and_destroy_block(block, lookup) {
    block.f();
    destroy_block(block, lookup);
}

/** @returns {void} */
export function fix_and_outro_and_destroy_block(block, lookup) {
    block.f();
    outro_and_destroy_block(block, lookup);
}

/** @returns {any[]} */
export function update_keyed_each(
    old_blocks,
    dirty,
    get_key,
    dynamic,
    ctx,
    list,
    lookup,
    node,
    destroy,
    create_each_block,
    next,
    get_context
) {
    let o = old_blocks.length;
    let n = list.length;
    let i = o;
    const old_indexes = {};
    while (i--) old_indexes[old_blocks[i].key] = i;
    const new_blocks = [];
    const new_lookup = new Map();
    const deltas = new Map();
    const updates = [];
    i = n;
    while (i--) {
        const child_ctx = get_context(ctx, list, i);
        const key = get_key(child_ctx);
        let block = lookup.get(key);
        if (!block) {
            block = create_each_block(key, child_ctx);
            block.c();
        } else if (dynamic) {
            // defer updates until all the DOM shuffling is done
            updates.push(() => block.p(child_ctx, dirty));
        }
        new_lookup.set(key, (new_blocks[i] = block));
        if (key in old_indexes) deltas.set(key, Math.abs(i - old_indexes[key]));
    }
    const will_move = new Set();
    const did_move = new Set();
    /** @returns {void} */
    function insert(block) {
        transition_in(block, 1);
        block.m(node, next);
        lookup.set(block.key, block);
        next = block.first;
        n--;
    }
    while (o && n) {
        const new_block = new_blocks[n - 1];
        const old_block = old_blocks[o - 1];
        const new_key = new_block.key;
        const old_key = old_block.key;
        if (new_block === old_block) {
            // do nothing
            next = new_block.first;
            o--;
            n--;
        } else if (!new_lookup.has(old_key)) {
            // remove old block
            destroy(old_block, lookup);
            o--;
        } else if (!lookup.has(new_key) || will_move.has(new_key)) {
            insert(new_block);
        } else if (did_move.has(old_key)) {
            o--;
        } else if (deltas.get(new_key) > deltas.get(old_key)) {
            did_move.add(new_key);
            insert(new_block);
        } else {
            will_move.add(old_key);
            o--;
        }
    }
    while (o--) {
        const old_block = old_blocks[o];
        if (!new_lookup.has(old_block.key)) destroy(old_block, lookup);
    }
    while (n) insert(new_blocks[n - 1]);
    run_all(updates);
    return new_blocks;
}

/** @returns {void} */
export function validate_each_keys(ctx, list, get_context, get_key) {
    const keys = new Map();
    for (let i = 0; i < list.length; i++) {
        const key = get_key(get_context(ctx, list, i));
        if (keys.has(key)) {
            let value = '';
            try {
                value = `with value '${String(key)}' `;
            } catch (e) {
                // can't stringify
            }
            throw new Error(
                `Cannot have duplicate keys in a keyed each: Keys at index ${keys.get(
					key
				)} and ${i} ${value}are duplicates`
            );
        }
        keys.set(key, i);
    }
}