import { signals } from "./signals-common.js";
/** @type {{ scope: object, prevent: boolean, namespace: "html"|string, host: function }[]} */
const scopes= [ {
	scope: document.body,
	namespace: "html",
	host: c=> c ? c(document.body) : document.body,
	prevent: true
} ];
const namespaceHelper= ns=> ns==="svg" ? "http://www.w3.org/2000/svg" : ns;
export const scope= {
	get current(){ return scopes[scopes.length-1]; },
	get host(){ return this.current.host; },
	get namespace(){ return this.current.namespace; },
	set namespace(namespace){ return ( this.current.namespace= namespaceHelper(namespace)); },
	
	preventDefault(){
		const { current }= this;
		current.prevent= true;
		return current;
	},
	elNamespace(namespace){
		const ns= this.namespace;
		this.namespace= namespace;
		return {
			append(...els){
				scope.namespace= ns;
				if(els.length===1) return els[0];
				const f= document.createDocumentFragment();
				return f.append(...els);
			}
		};
	},
	
	get state(){ return [ ...scopes ]; },
	push(s= {}){
		if(s.namespace) s.namespace= namespaceHelper(s.namespace);
		return scopes.push(Object.assign({}, this.current, { prevent: false }, s));
	},
	pop(){ return scopes.pop(); },
};
export function createElement(tag, attributes, ...connect){
	const s= signals(this);
	const { namespace }= scope;
	let scoped= 0;
	let el, el_host;
	//TODO Array.isArray(tag) ⇒ set key (cache els)
	if(Object(attributes)!==attributes || s.isSignal(attributes))
		attributes= { textContent: attributes };
	switch(true){
		case typeof tag==="function": {
			scoped= 1;
			scope.push({ scope: tag, host: c=> c ? (scoped===1 ? connect.unshift(c) : c(el_host), undefined) : el_host });
			el= tag(attributes || undefined);
			const el_mark= document.createComment(``);
			el.prepend(el_mark);
			if(el instanceof DocumentFragment)
				el_host= el_mark;
			break;
		}
		case tag==="#text":      el= assign.call(this, document.createTextNode(""), attributes); break;
		case tag==="<>":         el= assign.call(this, document.createDocumentFragment(), attributes); break;
		case namespace!=="html": el= assign.call(this, document.createElementNS(namespace, tag), attributes); break;
		case !el:                el= assign.call(this, document.createElement(tag), attributes);
	}
	if(!el_host) el_host= el;
	connect.forEach(c=> c(el_host));
	if(scoped) scope.pop();
	scoped= 2;
	return el;
}
export { createElement as el };
import { prop_process } from './dom-common.js';
const { setDeleteAttr }= prop_process;
const assign_context= new WeakMap();
export function assign(element, ...attributes){
	if(!attributes.length) return element;
	assign_context.set(element, assignContext(element, this));
	
	for(const [ key, value ] of Object.entries(Object.assign({}, ...attributes)))
		assignAttribute.call(this, element, key, value);
	assign_context.delete(element);
	return element;
}
export function assignAttribute(element, key, value){
	/* jshint maxcomplexity:14 */
	const { setRemoveAttr, s }= assignContext(element, this);
	const _this= this;
	
	value= s.processReactiveAttribute(element, key, value,
		(key, value)=> assignAttribute.call(_this, element, key, value));
	const [ k ]= key;
	if("="===k) return setRemoveAttr(key.slice(1), value);
	if("."===k) return setDelete(element, key.slice(1), value);
	if(/(aria|data)([A-Z])/.test(key)){//TODO: temporal as aria* exists in Element for some browsers
		key= key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
		return setRemoveAttr(key, value);
	}
	if("className"===key) key= "class";//just optimalization, `isPropSetter` returns false immediately
	switch(key){
		case "xlink:href":
			return setRemoveAttr(key, value, "http://www.w3.org/1999/xlink");
		case "textContent": //just optimalization, its part of Node ⇒ deep for `isPropSetter`
			return setDeleteAttr(element, key, value);
		case "style":
			if(typeof value!=="object") break;
			/* falls through */
		case "dataset":
			return forEachEntries(s, value, setDelete.bind(null, element[key]));
		case "ariaset":
			return forEachEntries(s, value, (key, val)=> setRemoveAttr("aria-"+key, val));
		case "classList":
			return classListDeclarative.call(_this, element, value);
	}
	return isPropSetter(element, key) ? setDeleteAttr(element, key, value) : setRemoveAttr(key, value);
}
function assignContext(element, _this){
	if(assign_context.has(element)) return assign_context.get(element);
	const is_svg= element instanceof SVGElement;
	const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
	const s= signals(_this);
	return { setRemoveAttr, s };
}
export function classListDeclarative(element, toggle){
	const s= signals(this);
	forEachEntries(s, toggle,
		(class_name, val)=>
			element.classList.toggle(class_name, val===-1 ? undefined : Boolean(val)));
	return element;
}
export function empty(el){
	Array.from(el.children).forEach(el=> el.remove());
	return el;
}
import { isUndef } from "./helpers.js";
//TODO add cache? `Map`
function isPropSetter(el, key){
	if(!Reflect.has(el, key)) return false;
	const des= getPropDescriptor(el, key);
	return !isUndef(des.set);
}
function getPropDescriptor(p, key){
	p= Object.getPrototypeOf(p);
	if(!p) return {};
	const des= Object.getOwnPropertyDescriptor(p, key);
	if(!des) return getPropDescriptor(p, key);
	return des;
}
/** @template {Record} T @param {object} s @param {T} obj @param {(param: [ keyof T, T[keyof T] ])=> void} cb */
function forEachEntries(s, obj, cb){
	if(typeof obj !== "object" || obj===null) return;
	return Object.entries(obj).forEach(function process([ key, val ]){
		if(!key) return;
		val= s.processReactiveAttribute(obj, key, val, cb);
		cb(key, val);
	});
}
function attrArrToStr(attr){ return Array.isArray(attr) ? attr.filter(Boolean).join(" ") : attr; }
function setRemove(obj, prop, key, val){ return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, attrArrToStr(val)); }
function setRemoveNS(obj, prop, key, val, ns= null){ return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, attrArrToStr(val)); }
function setDelete(obj, key, val){ Reflect.set(obj, key, val); if(!isUndef(val)) return; return Reflect.deleteProperty(obj, key); }