import { effect, isReactive, isRef, stop, } from '@vue/reactivity';
import { hasChanged, isArray, isFunction, isObject, NOOP, isPromise } from '@vue/shared';
export function callWithErrorHandling(fn, type, args) {
    let res;
    try {
        res = args ? fn(...args) : fn();
    }
    catch (err) {
        handleError(err, type);
    }
    return res;
}
export function callWithAsyncErrorHandling(fn, type, args) {
    if (isFunction(fn)) {
        const res = callWithErrorHandling(fn, type, args);
        if (res && isPromise(res)) {
            res.catch(err => {
                handleError(err, type);
            });
        }
        return res;
    }
    const values = [];
    for (let i = 0; i < fn.length; i++)
        values.push(callWithAsyncErrorHandling(fn[i], type, args));
    return values;
}
function handleError(err, type) {
    console.error(new Error(`[@vue-reactivity/watch]: ${type}`));
    console.error(err);
}
export function warn(message) {
    console.warn(createError(message));
}
function createError(message) {
    return new Error(`[reactivue]: ${message}`);
}
const invoke = (fn) => fn();
const INITIAL_WATCHER_VALUE = {};
export function watchEffect(effect, options) {
    return doWatch(effect, null, options);
}
export function watch(source, cb, options) {
    return doWatch(source, cb, options);
}
function doWatch(source, cb, { immediate, deep, onTrack, onTrigger } = {}) {
    let getter;
    if (isArray(source) && !isReactive(source)) {
        getter = () => source.map(s => {
            if (isRef(s))
                return s.value;
            else if (isReactive(s))
                return traverse(s);
            else if (isFunction(s))
                return callWithErrorHandling(s, 'watch getter');
            else
                warn('invalid source');
        });
    }
    else if (isRef(source)) {
        getter = () => source.value;
    }
    else if (isReactive(source)) {
        getter = () => source;
        deep = true;
    }
    else if (isFunction(source)) {
        if (cb) {
            getter = () => callWithErrorHandling(source, 'watch getter');
        }
        else {
            getter = () => {
                if (cleanup)
                    cleanup();
                return callWithErrorHandling(source, 'watch callback', [onInvalidate]);
            };
        }
    }
    else {
        getter = NOOP;
    }
    if (cb && deep) {
        const baseGetter = getter;
        getter = () => traverse(baseGetter());
    }
    let cleanup;
    const onInvalidate = (fn) => {
        cleanup = runner.options.onStop = () => {
            callWithErrorHandling(fn, 'watch cleanup');
        };
    };
    let oldValue = isArray(source) ? [] : INITIAL_WATCHER_VALUE;
    const applyCb = cb
        ? () => {
            const newValue = runner();
            if (deep || hasChanged(newValue, oldValue)) {
                if (cleanup)
                    cleanup();
                callWithAsyncErrorHandling(cb, 'watch callback', [
                    newValue,
                    oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
                    onInvalidate,
                ]);
                oldValue = newValue;
            }
        }
        : undefined;
    const scheduler = invoke;
    const runner = effect(getter, {
        lazy: true,
        onTrack,
        onTrigger,
        scheduler: applyCb ? () => scheduler(applyCb) : scheduler,
    });
    if (applyCb) {
        if (immediate)
            applyCb();
        else
            oldValue = runner();
    }
    else {
        runner();
    }
    const stopWatcher = function () {
        stop(runner);
    };
    stopWatcher.effect = runner;
    return stopWatcher;
}
function traverse(value, seen = new Set()) {
    if (!isObject(value) || seen.has(value))
        return value;
    seen.add(value);
    if (isArray(value)) {
        for (let i = 0; i < value.length; i++)
            traverse(value[i], seen);
    }
    else if (value instanceof Map) {
        value.forEach((_, key) => {
            traverse(value.get(key), seen);
        });
    }
    else if (value instanceof Set) {
        value.forEach(v => {
            traverse(v, seen);
        });
    }
    else {
        for (const key of Object.keys(value))
            traverse(value[key], seen);
    }
    return value;
}
//# sourceMappingURL=watchReactivity.js.map