/** @import { Derived, Effect, Value } from '#client' */

import {
	BLOCK_EFFECT,
	BOUNDARY_EFFECT,
	BRANCH_EFFECT,
	CLEAN,
	DERIVED,
	EFFECT,
	ASYNC,
	MAYBE_DIRTY,
	RENDER_EFFECT,
	ROOT_EFFECT
} from '#client/constants';
import { snapshot } from '../../shared/clone.js';
import { untrack } from '../runtime.js';

/**
 *
 * @param {Effect} effect
 */
export function root(effect) {
	while (effect.parent !== null) {
		effect = effect.parent;
	}

	return effect;
}

/**
 *
 * @param {Effect} effect
 */
export function log_effect_tree(effect, depth = 0) {
	const flags = effect.f;

	let label = '(unknown)';

	if ((flags & ROOT_EFFECT) !== 0) {
		label = 'root';
	} else if ((flags & BOUNDARY_EFFECT) !== 0) {
		label = 'boundary';
	} else if ((flags & BLOCK_EFFECT) !== 0) {
		label = 'block';
	} else if ((flags & ASYNC) !== 0) {
		label = 'async';
	} else if ((flags & BRANCH_EFFECT) !== 0) {
		label = 'branch';
	} else if ((flags & RENDER_EFFECT) !== 0) {
		label = 'render effect';
	} else if ((flags & EFFECT) !== 0) {
		label = 'effect';
	}

	let status =
		(flags & CLEAN) !== 0 ? 'clean' : (flags & MAYBE_DIRTY) !== 0 ? 'maybe dirty' : 'dirty';

	// eslint-disable-next-line no-console
	console.group(`%c${label} (${status})`, `font-weight: ${status === 'clean' ? 'normal' : 'bold'}`);

	if (depth === 0) {
		const callsite = new Error().stack
			?.split('\n')[2]
			.replace(/\s+at (?: \w+\(?)?(.+)\)?/, (m, $1) => $1.replace(/\?[^:]+/, ''));

		// eslint-disable-next-line no-console
		console.log(callsite);
	} else {
		// eslint-disable-next-line no-console
		console.groupCollapsed(`%cfn`, `font-weight: normal`);
		// eslint-disable-next-line no-console
		console.log(effect.fn);
		// eslint-disable-next-line no-console
		console.groupEnd();
	}

	if (effect.deps !== null) {
		// eslint-disable-next-line no-console
		console.groupCollapsed('%cdeps', 'font-weight: normal');

		for (const dep of effect.deps) {
			log_dep(dep);
		}

		// eslint-disable-next-line no-console
		console.groupEnd();
	}

	if (effect.nodes_start && effect.nodes_end) {
		// eslint-disable-next-line no-console
		console.log(effect.nodes_start);

		if (effect.nodes_start !== effect.nodes_end) {
			// eslint-disable-next-line no-console
			console.log(effect.nodes_end);
		}
	}

	let child = effect.first;
	while (child !== null) {
		log_effect_tree(child, depth + 1);
		child = child.next;
	}

	// eslint-disable-next-line no-console
	console.groupEnd();
}

/**
 *
 * @param {Value} dep
 */
function log_dep(dep) {
	if ((dep.f & DERIVED) !== 0) {
		const derived = /** @type {Derived} */ (dep);

		// eslint-disable-next-line no-console
		console.groupCollapsed(
			`%c$derived %c${dep.label ?? '<unknown>'}`,
			'font-weight: bold; color: CornflowerBlue',
			'font-weight: normal',
			untrack(() => snapshot(derived.v))
		);

		if (derived.deps) {
			for (const d of derived.deps) {
				log_dep(d);
			}
		}

		// eslint-disable-next-line no-console
		console.groupEnd();
	} else {
		// eslint-disable-next-line no-console
		console.log(
			`%c$state %c${dep.label ?? '<unknown>'}`,
			'font-weight: bold; color: CornflowerBlue',
			'font-weight: normal',
			untrack(() => snapshot(dep.v))
		);
	}
}
