User:TomT0m/classification.js

From Wikidata
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/*jshint esversion: 8, esnext:false */


(function(mw, $, wb) {
	mw.hook('wikibase.entityPage.entityView.rendered').add(
	async function () {
		"use strict";
		
		let namespace = mw.config.get("wgNamespaceNumber");
		if (namespace !== mw.config.get("wgNamespaceIds").item){
			return;
		}

		
		var api = new mw.Api();
		var lang = mw.config.get( 'wgUserLanguage' );
		var gadget = "[[User:TomT0m/classification.js]]";
		var item_label = $( ".wikibase-title-label" ).text();

		
		// colors founds from https://davidmathlogic.com/colorblind/#%23332288-%23117733-%2344AA99-%2388CCEE-%23DDCC77-%23CC6677-%23AA4499-%23882255
		
		const violations_colors = {
			unique : ["E69F00", "0072B2", "56B4E9"], 
			several  : "D55E00",
			top : "009E73",
			bottom : "CC79A7", 
		};
		
		// Variable to store the tooltranslate object if it is loadable
		let tool_translate;
		// the object accessible anywhere to get the massages translations.
		var messages ;
		// factory for the message object
		let load_messages = function () {

			// default translations if tooltranslate could not load for some reason
	        var translations = {
	            en: {
	                'title': 'Classification gadget'
	                	/*'title': 'Classification gadget'*/,
	                'new_window': "(show in a new window)" 
	                	/* "(show in a new window)" */,
	                'paths_to_parent' : "Paths to some parent class" 
	                	/* "Paths to some parent class" */,
	                'path_to_parent' : "Path from this class to $parent" 
	                	/* param : $parent (a class label)*/,
	                'close_view' : "close view" 
	                	/* close an iframe containing a wdqs query visualisation*/,
	                'embedded_superclass_tree' : "embedded superclass tree " 
	                	/*:"embedded superclass tree "*/,
	                'embedded_classes_of_instance_tree' : "Embedded tree of the classes of this instance"
	                	/*:"Embedded tree of the classes of this instance"*/,
	                'weird_to_discuss' :"(This seems weird or incorrect, discuss it)" 
	                	/* :"(This seems weird or incorrect, discuss it)" */,
	                'and' : " and "
	                	/*" and "*/,
	                'instance_explanation' : 
	                	"$item_label is an explicit instance of $explicit_classes "
						+ " and it is/they are linked to $superclass this class by a subclass chain."/*
							"$item_label is an explicit instance of $explicit_classes  "
							+ " and it is/they are linked to $superclass this class by a subclass chain."
							
							parameters : 
							        $explicit classes : a list of classes our item is an explicit instance of
							        $superclass : the superclass to show the path to
							        $item_label : the instance (main item) label
							*/,
					'show_samplesize' : "(showing $num_sample / $total_available results)"
					        /*"(showing $num_sample / $total_available results" 
					        parameters : 
					            $num_sample : number of class instances to show 
					            $total_available : total number of classes the item is an instance of
					        */,
					'item_is_also_instanceof' : "$item_label is also a(n) …"
							/*"$item_label is also a(n) …"
							parameter:
								$item_label : the label of the main item
							*/,
	                'more': 'more',
	                'show_in_wdqs': "show class tree in wdqs (bigger)"
	                	/* "show class tree in wdqs (bigger)" */,
	                'choose_parent' : 'Choose some parent class'
	                	/* 'Choose some parent class' */,
	                'this_class' : 'Current class',
	                    /* Current class */
	                'loops' : 'There are loop(s) in the class tree !',
	                    /* There are loop(s) in the class tree ! */
	                'activate_gadget' : 'Activate classification gadget',
	                    /* Activate classification gadget */
	                'deactivate_gadget' : "Don't show classification from next time",
	                    /* Don't show classification from next time */
	            
					'disjointness_violation' : `Disjointness violation !`,
					    /* `Disjointness violation !` */
					'instance_of_several_classes' : `This item is an instance of two or more classes supposed to be disjoint, there are several ways to fix it <br> <ul><li>One is to remove one of the class of its <instance of> statement</li><li>one other is to fix the class tree. One of the class in the class tree is a subclass of conflicting classes, you are hinted which one(s) below</li></ul> You can expand the class tree for help, classes that are subclasses of both conflicting classes are colored <span style="background-color:#992211;">this way</span>.`,
					classes_are_disjoint :
					"On class $class it is stated that the following classes are disjoint, and this is an instance of them !",
						/* "On class $class it is stated that the following classes, and this is an instance of them !" */
					superclasses_are_disjoint : "On class $class it is stated that the following classes, and this is a subclass of them !",
						/* "On class $class it is stated that the following classes, and this is a subclass of them !" */
					probable_culprit :  "=> Probable culprit(s), highest classes in the tree to be subclasses of conflicting classes (if any) :",
					/* " => Probable culprit(s), highest classes in the tree to be subclasses of conflicting classes (if any) :", */
					see_conflict_tree : "See this disjointness conflict in its class tree … ",
						/* "See this instance disjointness conflict instance in its class tree … " */
					subclass_of_several_classes : "This class is a subclass of two or more classes that are supposed to be disjoint. Fixing this involves removing a \"subclass of\" statement either on this class or in one of its superclass. The informations on this box give a candidate (super)class for this. You can visualize the class tree by expanding the detail arrow below, a superclass tree colored according to which class is a subclass of which conflicting class.",
					parent_class_loop_title : "Loop in the parent class tree !",
					help_loop_removal	    : `here is a loop in the class tree. To solve this you need to either remove one 'subclass of
					' statement on this path, or maybe merge two items if they are both subclass of each other because they are identical.`,
					subclass_of_both : `subclass of both`,
					subclass_of_c : `subclass of <i>$class</i>`,
					disjoint_union_class : `Top class : <i>$class</i>`,
					this_class_ : `This class : <i>$class</i>`,
					this_instance : `This instance : <i>$instance</i>`
	            },
	        };
	        
	        

	        
	       

	        
	        let sanitizeHTML = function ( s ) {
				return s.replace(/<\s*script/i,'') ;
			};
	        
	        // the message object to return
	        let ret = {};
	        // load the english static defaults
	        mw.language.getFallbackLanguageChain().reverse().forEach(
	        	function(lang) {
		            if (translations.hasOwnProperty(lang)) {
		                $.extend(ret, translations[lang]);
		            }
	        	}
	        );
	        
	        if (tool_translate) {
	        	// if tool_translate is loaded
	        	ret.$ = (key) => {
	        		let val = tool_translate.t(key);
	        		if (val) 
	        			return sanitizeHTML(val);
	        		else
	        			// if tooltranslate fails in the dynamic messages for some reason, fallback to our in script messages
	        			return sanitizeHTML(messages[key]);
	        	}; 
	        } else {
				// if tool translate is not loaded, fallback to the static messages in script version
	        	ret.$ = (key) => sanitizeHTML(messages[key]); 
	        }
	        // messages is a global object
	        messages = ret;
	    };

		
		/* CSS

		*/
		function instance_details_style(){
			return 		`
		details {
		    border: 1px solid #aaa;
		    border-radius: 4px;
		    padding: .5em .5em 0;
		}
		
		summary {
		    font-weight: bold;
		    margin: -.5em -.5em 0;
		    padding: .5em;
		}
		
		details[open] {
		    padding: .5em;
		}
		
		details[open] summary {
		    margin-bottom: .5em;
		}
		
		.classification-gadget-property-section {
			background-color:#F1F1F1;
			border-bottom:1px solid;
			border-left:1px solid;
			border-right:1px solid;
			border-bottom-left-radius : 1em;
			border-bottom-right-radius : 1em;
			padding-bottom: 1em;
		}
		.classification-gadget-report-link {
			font-size:smaller;
		}
		.classification-gadget-embedded-tree{
		 	width: 100%; 
		 	height: 70vh; 
		 	border: none;
		}
		.classification-gadget-constraint-violation {
			border: 1px double #a12290;
		}
		.align-right {
			float:right;
		}
		
		ul.classification-gadget-horizontal-list li {
			display:inline;
			padding : 1rem .5rem;
		}
		
		.classification-gadget-violation-colors-unique-1 {
			background-color : #${violations_colors.unique[0]};
		}
		.classification-gadget-violation-colors-unique-2 {
			background-color : #${violations_colors.unique[1]};
		}
		.classification-gadget-violation-colors-unique-3 {
			background-color : #${violations_colors.unique[2]};
		}
		.classification-gadget-violation-colors-several {
			background-color : #${violations_colors.several};
		}
		.classification-gadget-violation-colors-top {
			background-color : #${violations_colors.top};
		}
		.classification-gadget-violation-colors-bottom {
			background-color : #${violations_colors.bottom};
		}
}
		
		`;
		}

		
		async function makeSPARQLQuery( endpointUrl, sparqlQuery, doneCallback ) {
					
			function fetchRetry(url, options = {}) {
			  return fetch(url, options).then(res => {
			    // handle rate limiting
			    if (res.status === 429) {
			      const retryAfter = res.headers.get("retry-after");
			      return sleep(+retryAfter * 1000).then(fetchRetry);
			    }
			    return res;
			  });
			}

			var settings = {
				headers: { Accept: 'application/sparql-results+json' },
				//method:"POST",
				//mode: "cors",
			};
			const params = { query: sparqlQuery };
			let answer = await fetchRetry( endpointUrl + "?" + new URLSearchParams( params ), settings );
				// .then( doneCallback );
			let json = await answer.json();
			if (doneCallback)
				return doneCallback(json);
			return json;
		}
		
		var endpointUrl = 'https://query.wikidata.org/sparql';
		// adding the gadget CSS classes to the page
		$(document.head).append($("<style>",{
			text: instance_details_style()
		}));
		
		
		
		// Utility function, create a container to add the gadget UI below the statements
		// of some given property in Wikidata UI
		function create_property_container(propertyid, content){
			var property_section = $("#" + propertyid);
			var id = "classification-gadget-" + propertyid + "-container";
			
			var new_property_section = $("<div>",{
				id:id,
				class:"wikibase-statementgroupview",
			});
			property_section.wrap(new_property_section).append(content);
			$("#"+id).append(content);
			return id;
		}
		
		/*        SPARQL utilities and queries             */
		
		function query_label_service_language(){
			return `bd:serviceParam wikibase:language "${mw.language.getFallbackLanguageChain().join(",")},[AUTO_LANGUAGE],en"`;
		}
		
		function query_label_service(){
			return `SERVICE wikibase:label { ${query_label_service_language()} . }\n`;
		}
		
		// a query to draw the class path between two classes
		function query_path(bottom, top) {
			return           "#defaultView:Graph\n"+
			  "#query_path\n"+
	          "select ?topSubclass ?parent ?parentLabel ?topSubclassLabel\n"+
	          "with {\n"+
	          "  select ?bottomSuperclass  {\n"+
	          "      bind ( wd:" + bottom + " as ?bottom) .\n"+
	          "      ?bottom wdt:P279* ?bottomSuperclass. \n"+
	          "  }\n"+
	          "} as %superclasses\n"+
	          "\n"+
	          "with {\n"+
	          "  select (?bottomSuperclass as ?topSubclass) ?top {\n"+
	          "    include %superclasses\n"+
	          "      bind ( wd:" + top + " as ?top) .\n"+
	          "      ?bottomSuperclass wdt:P279* ?top.\n"+
	          "  }\n"+
	          "} as %subclasses\n"+
	          "{\n"+
	          "  include %subclasses .\n"+
	          "  ?topSubclass wdt:P279 ?parent filter exists { { select (?topSubclass as ?class ){ include %subclasses} } filter (?class = ?parent) }\n"+
	          "   " + query_label_service() +
	          "}\n";
		}
		
		// a query to draw a path from an instance to one of its member class
		function query_inst_path(instance, top) {
			return           "#defaultView:Graph\n"+
			  "#query_inst_path\n"+
	          "select ?topSubclass ?parent ?parentLabel ?topSubclassLabel\n"+
	          "with {\n"+
	          "  select ?bottomSuperclass  {\n"+
	          "      bind ( wd:" + instance + " as ?bottom) .\n"+
	          "      ?bottom wdt:P31/wdt:P279* ?bottomSuperclass. \n"+
	          "  }\n"+
	          "} as %superclasses\n"+
	          "\n"+
	          "with {\n"+
	          "  select (?bottomSuperclass as ?topSubclass) ?top {\n"+
	          "    include %superclasses\n"+
	          "      bind ( <" + top + "> as ?top) .\n"+
	          "      ?bottomSuperclass wdt:P279* ?top.\n"+
	          "  }\n"+
	          "} as %subclasses\n"+
	          "{\n"+
	          "  include %subclasses .\n"+
	          "  ?topSubclass wdt:P279 ?parent filter exists { { select (?topSubclass as ?class ){ include %subclasses} } filter (?class = ?parent) }\n"+
	          "   " + query_label_service() +
	          "}\n";
		}
		// find all superclasses of some class
		function query_superclass_tree(bottom) {
			return `#defaultView:Graph
			  #query_superclass_tree
			
			  select ?bottomSuperclass ?rgb ?parent ?bottomSuperclassLabel ?parentLabel{
	             bind ( wd:${bottom} as ?bottom) .
	            ?bottom wdt:P279* ?bottomSuperclass. 
	            ?bottomSuperclass wdt:P279 ?parent .
	            OPTIONAL {
	              VALUES (?bottomSuperclass ?rgb) { (wd:${bottom} '${violations_colors.bottom}') }
	            }
	            ${query_label_service()} 
	          }`;
		}
	
		function query_instance_superclass_tree(bottomInstance) {
			return "#defaultView:Graph\n"+
			  "SELECT ?superClass ?rgb ?parent ?superClassLabel ?parentLabel\n"+
			  "WHERE {\n"+
			  "  {\n"+
			  "    bind ('FF4444' as ?rgb).\n"+
			  "    bind (wd:" + bottomInstance + " as ?superClass).\n"+
			  "    ?superClass wdt:P31 ?parent.\n"+
			  "  } UNION {\n"+
			  "    bind ('FFFFFF' as ?rgb).\n"+
			  "    wd:" + bottomInstance + " wdt:P31 ?class.\n"+
			  "    ?class wdt:P279* ?superClass.\n"+
			  "    ?superClass wdt:P279 ?parent.\n"+
			  "  }\n"+
	          "  " + query_label_service() +
	          "}\n";
		}
		// find all classes an instance is instance of
		function query_other_classes(instance) {
			return "#defaultView:Graph\n"+
			  "select distinct ?class ?classLabel {\n"+
	          "   bind ( wd:" + instance + " as ?instance) .\n"+
	          "  ?instance wdt:P31/wdt:P279* ?class. \n"+
	          "  " + query_label_service() +
	          "}\n";
		}
		
				// find all classes an instance is instance of
		function query_superclasses(class_) {
			return `#query_superclasses
			  select distinct ?class ?classLabel {
	             
	            wd:${class_} wdt:P279* ?class.
	            ${query_label_service()}
	          }`;
		}
		
		// find all classes in the path between an instance an a class it is an instance of
		function query_direct_classes_subclasses_of_superclass(instance, superclass) {
			return "select ?class ?classLabel\n" +
			    "#query_direct_classes_subclasses_of_superclass\n" +
		        "with {\n" +
		        "  select ?bottomSuperclass  {\n" +
		        "      bind ( wd:" + instance + " as ?bottom) .\n" +
		        "      ?bottom wdt:P31/wdt:P279* ?bottomSuperclass. \n" +
		        "  }\n" +
		        "} as %superclasses\n" +
		        "\n" +
		        "with {\n" +
		        "  select (?bottomSuperclass as ?class) ?top {\n" +
		        "    include %superclasses\n" +
		        "      bind ( <" + superclass + "> as ?top) .\n" +
		        "      ?bottomSuperclass wdt:P279* ?top.\n" +
		        "  }\n" +
		        "} as %subclasses\n" +
		        "{\n" +
		        "  include %subclasses .\n" +
		        "  wd:" + instance + " wdt:P31 ?class.\n" +
		        "  \n" +
	            "  " + query_label_service() +
		        "}";	
		}
	    
	    
	    
	    function query_disjoint_statements_base_query(select_baseclasses_sparql){
			return `#query_disjoint_statements_base_query
select ?class ?st  
       (count(distinct ?classes) as ?count) 
       (group_concat(distinct ?classes;separator=",") as ?class_set) 
{
  ?class p:P2738 ?st .
  ?st  pq:P642|pq:P11260 ?classes ;
       a wikibase:BestRank .
  
  ?classes wdt:P279* ?class .
  
  ${select_baseclasses_sparql}
  
  ?baseclass wdt:P279* ?classes .
  
}  group by ?class ?st 
  having (?count > 1)
  limit 3`;
	    }
	    
	    function query_disjoint_statements_violated_by_instance(instance){
			return query_disjoint_statements_base_query(`wd:${instance} wdt:P31 ?baseclass .\n    `);
	    }
	    
	    function query_disjoint_statements_violated_by_superclasses(baseclass){
			return query_disjoint_statements_base_query(`wd:${baseclass} wdt:P279 ?baseclass .\n    `);
	    }
	    
	    function query_conflicting_class_tree_visualisation(base, link_prop, classes_from_base_query,top, violated_1, violated_2){
	    	return `#query_conflicting_class_tree_visualisation
#defaultView:Graph

prefix violated_top: <${top.class.value}>
prefix violated_1:  <${violated_1.class.value}>
prefix violated_2: <${violated_2.class.value}>

prefix base: <http://www.wikidata.org/entity/${base}>

select ?class ?rgb ?classLabel ?img 
      ?parent   
      ?edgeLabel
      ?size
with {
  select distinct ?class 
                  (if(count(distinct ?rgb_)>=2,"992211",sample(?rgb_)) as ?rgb) # color selection : if instance of both classes, a different color
                  (concat(?classLabel_, " : ",group_concat(distinct ?violatedLabel_;separator=", ")) as ?classLabel) 
                  (group_concat(distinct ?violatedLabel_;separator=", ") as ?edgeLabel) 
                  {
  
    base: wdt:${link_prop} ?baseclass.
  
    ?baseclass wdt:P279* ?class .
    ?class wdt:P279* ?violated .
    values (?violated ?rgb_) {
      (violated_1: "${violations_colors.unique[0]}")
      (violated_2: "${violations_colors.unique[1]}")
    }
  
    SERVICE wikibase:label { 
      ?class rdfs:label ?classLabel_ .
      ?violated rdfs:label ?violatedLabel_ .
      ${query_label_service_language()} . 
    }
  
  } group by ?class ?classLabel_
} as %edges

{
  {
      include %edges .
    hint:Query hint:optimizer "None".
    {select (?class as ?parent) { include %edges} }
  ?class wdt:P279 ?parent .
  } union {
    bind (base: as ?class)
    ?class wdt:${link_prop} ?parent .
    bind ("${violations_colors.bottom}" as ?rgb) .
    bind (12 as ?size) .
    bind (wd:${link_prop} as ?prop) .
    service wikibase:label {
    	${query_label_service_language()} .
    	?prop rdfs:label ?edgeLabel .
    	?class rdfs:label ?classLabel
    }
  } union {
      values (?class ?rgb ?classLabel) {
        (violated_1: "${violations_colors.unique[0]}" "${violated_1.classLabel.value}")
        (violated_2: "${violations_colors.unique[1]}" "${violated_2.classLabel.value}")
      }
      
      # link the disjoint classes, helpful ?
      values (?class ?parent ?edgeLabel) {
        (violated_1: violated_top: "")
        (violated_2: violated_top: "")
      #  (violated_1: violated_2:   "disjoint with")
      #  (violated_2: violated_1:   "disjoint with")
      }
      
  } union {
    values (?class ?rgb ?size ?classLabel){
      (violated_top: "${violations_colors.top}" "13"^^xsd:number  "${top.classLabel.value}")
    }
  }
}`;
	    }
	    
	    function query_highest_subclass_of_both_conflicting_superclass_of(bottomclass, conf1, conf2){
	    	return query_upper_subclass_of_both_conflicting_class_base(
	    		`wd:${bottomclass} wdt:P279* ?class .`,
	    		conf1,
	    		conf2);
	    }	    
	    function query_highest_subclass_of_both_conflicting_class_of(instance, conf1, conf2){
	    	return query_upper_subclass_of_both_conflicting_class_base(
	    		`<http://www.wikidata.org/entity/${instance}> wdt:P31/wdt:P279* ?class .`,
	    		conf1,
	    		conf2);
	    }
	    
	    
		function query_upper_subclass_of_both_conflicting_class_base(base_subclasses_subquery, conf1, conf2){
			// this query won’t work if there is a loop involved in the path, but …
			// … whatever. Just fix the loop firt /o\
			
	return `#query_upper_subclass_of_both_conflicting_class_base
prefix violated_1:  <${conf1}>
prefix violated_2: <${conf2}>



select ?class
with {
select ?class 
  {

    ${base_subclasses_subquery}
    ?class wdt:P279+ violated_1: . hint:Prior hint:gearing  "forward" .
    ?class wdt:P279+ violated_2: . hint:Prior hint:gearing  "forward"

  } 
} as %classes

 {
  include %classes .

  minus { 
    { select (?class as ?parent) { include %classes } }
    ?class wdt:P279 ?parent
  }
 }`;
	
}	    
	    // Utility : create a frame that shows embedded results of
	    // a query by wdqs, a graph for example
    	function create_embedded_frame(query, additional={}){
 				var iframe = $("<iframe>",
	 				$.extend(
	 					{
	 						class:"classification-gadget-embedded-tree",
	 						src : "https://query.wikidata.org/embed.html#" + encodeURIComponent(query)
	 					},
	 					additional
	 				)
	 			);
	 			return iframe;
 		}   
 		
 		function create_constraint_violation_box(title, message, id) {
			return $("<div>")
						.append($("<h4>").append(title))
						.addClass("classification-gadget-constraint-violation")
						.append(new OO.ui.PopupButtonWidget( { 
			$overlay: true,
			label: '', 
			indicator: 'down',
			icon : "non-mandatory-constraint-violation",
			infusable:true,
			popup: {
				$content: $("<div>").append(message),
				padded: false,
				//align: align,
				width: 400,
			},
			//class:["mw-portlet-lang"],
			id : id
			} ).$element.addClass("align-right"));
		}


		function make_list_item(elem){
			return $("<li>",{append:elem});
		}
		
        function create_full_class_tree(tree_query, message, caption) {
			const wdqs_fullclasstree_link = $("<a>",{
						href:"https://query.wikidata.org/embed.html#" + encodeURIComponent(tree_query),
						text: messages.$("show_in_wdqs") /* "show class tree in wdqs (bigger)" */
					}
				),
				full_class_tree = $(
					"<details>",
					{
						class:"classification-gadget-full-superclass-tree",
						append:$("<summary>", {
							append:$("<span>", {
								append:[message," "]
							}).append(wdqs_fullclasstree_link)
						})
					}
				);
			
			full_class_tree.on("toggle", function() {
				if (!full_class_tree.find(".classification-gadget-embedded-tree").length){
					let wrapped_caption;
					
					if(caption !== undefined){
						wrapped_caption = $("<figcaption>").append(caption);
					}
					
					full_class_tree.append(
						$("<figure>")
							//.addClass("classification-gadget-embedded-tree")
							.append([create_embedded_frame(tree_query),wrapped_caption])
					);
				}
			});

			return full_class_tree;
        }

		//finds the Qid String from a entity url
		function urlToQid(url){
			return url.replace(/^.*entity\/(.*)$/,"$1");
		}
		
		
		//// Utility function for computing and rendering loops constraints.
		
		// converts a lists of result mapping to a dictionary
		// output keys : items uris ("bottomSuperclass" variable value)
		// output values : wdqs binding object
		
		function hashgraphFromResults(results){
			var map = {};
			for(var idx in results){
				var binding = results[idx];
				if (map[binding.bottomSuperclass.value]) {
					map[binding.bottomSuperclass.value].push(binding);
				} else {
					map[binding.bottomSuperclass.value] = [binding];
				}
			}
			return map;
		}
		
		// computes a path to an item value from 
		// in a graph as computed by the "hashgraphFromResults" function
		
		function pathTo(graph, bottomclass, aim, explored){

			for (var parentidx in graph[bottomclass]){
				var parentbinding = graph[bottomclass][parentidx] ;
				var parent = parentbinding.parent.value;
				
				if ( parent === aim ) {
					return [parentbinding];
				} else if (! explored.includes(parent)) {
					var explored_bis = [...explored];
					explored_bis.push(bottomclass) ;// do we need to make a fresh explored object at this point ?
					var search = pathTo(graph, parent, aim, explored_bis );
					if (search) {
						search.push(parentbinding);
						return search;
					}
				}
			}
		}
		function render_path(path){
			var loop_render = $("<span>");
								for (var pidx in path){
									loop_render.append(item_span(path[pidx], "parent","parentLabel"));
									loop_render.append(" → ");
								}
			return $("<li>",{
								append: $("<div>", { 
									append: [
										"[" + messages.$("this_class") +"]  → ",
										loop_render,
										" ↶ [" + messages.$("this_class") +"]"
									] 
								} )
							}
					);
		}
		//////// loops utilities 
		
		// loop section generation
		async function loops_infos(bottom){
			
			let data = await makeSPARQLQuery(endpointUrl, query_superclass_tree(bottom));
			var classes = data.results.bindings;
			
			var graph = hashgraphFromResults(classes);
			const parents = classes.filter(result=>result.parent.value).map(result => result.parent.value);
			
			let loops_endpoints = await makeSPARQLQuery(
				endpointUrl,
				`#loops_endpoints
				SELECT distinct ?parent { ?parent wdt:P279 wd:${bottom} . values ?parent { $values } } `
					.replace("$values", parents.map(parent=> "wd:" + urlToQid(parent)).join(" ")));
				
			var urlbottom = "http://www.wikidata.org/entity/" + bottom ;
			const endpoints = loops_endpoints.results.bindings;
			if (endpoints.length === 0) 
				return ;
				
			var show_class_loops = $("<div>", {
				id:"classification-gadget-loops"
			});
			
			var rpaths = endpoints
			  .map( endpoint => endpoint.parent.value )
			  .map( parent => {
			  	  var path = pathTo(graph, urlbottom, parent, []).reverse();
			  	  return render_path(path);
			  });
			if (rpaths.length > 0){
				show_class_loops.append(
					create_constraint_violation_box(
						messages.$("parent_class_loop_title"), // "Loop in the parent class tree !" 
						messages.$("help_loop_removal")         // "here is a loop in the class tree, please remove one."
						).append(
						$("<span>",{
						append: [
							$("<h4>").append(messages.$("loops")),
							$("<ul>").append(rpaths)
						]})
					)
				);
			}
			
			return show_class_loops;
		}
		
		function show_path_to_superclass_with_selector(selectid, bottom){
			// a selector to select a parent class, the class we want to find show path to
			var select = $("<input>",{id:selectid}).entityselector( {
	    		url: '/w/api.php',
	    		language: lang
			} ) ;
			
			// when a class is selected, we query and show the results of the query
			// replacing the view of an old path if any
			
			select.on("change", function() { 
				//var select = $("#classification-gadget-path-topclass-selector");
				var entity = select.data("entityselector").selectedEntity();
				
				var base_class = bottom;

				if (! entity ) return ; // invalid entity
				
				var now_entity = entity.id;
	 			
	 			var link_to_wdqs = $("<a>",{
						href: "https://query.wikidata.org/#" + encodeURIComponent(query_path(bottom, now_entity)),
						text: messages.$("new_window")// "(show in a new window)"
	 				}
				);
				var container = $("<div>", {
					append: $("<h5>",{
						text:messages.$("path_to_parent").replace("$parent", entity.label) /*"Path from this class to $parent"*/
					}
					).add(
						link_to_wdqs
					).add(
						create_embedded_frame(query_path(bottom, now_entity))
					)
				});
				var close_link = $("<a>", {
					text: messages.$("close_view") /*"close view"*/, 
					on:{
						click: function(){container.remove()}
					}
				});
				container.append(close_link);

				select.after(container);
			});
		
			return [
				$("<h4>", {
					text: messages.$("paths_to_parent") /* "Paths to some parent class"*/
				}),
				$("<label>",{
						for:selectid, 
						text:messages.$("choose_parent") /*"Choose some parent class"*/ }), 
				select
			];	
		}
				
		// intialize the gadget below the « subclass of » statements
		
		async function init_subclassof(bottom){
			var selectid = "classification-gadget-path-topclass-selector";
			
			var class_section = $("#P279");
			
			var classification_infos = $("<div>", {
				id:"classification-gadget-classes", 
				class:"wikibase-statementgroupview  classification-gadget-property-section"
			});
			
			var cont_id = create_property_container("P279", classification_infos);
			
			register_subsection(classification_infos,() => create_full_class_tree(
				query_superclass_tree(bottom), 
				messages.$("embedded_superclass_tree") /*:"embedded superclass tree "*/ 
			));
			
			register_subsection(classification_infos, () => get_subclass_disjointness_violations_infos(bottom));
			register_subsection(classification_infos, () => show_path_to_superclass_with_selector(selectid, bottom));
			register_subsection(classification_infos, () => loops_infos(bottom));
			
		}

		// utility : function to generate a html label from a 
		//           query result binding (as generated by a call to the query service)
		// res : a result object
		// name : a string that is the name of a sparql variable
		function label_span(res, name){
			return $("<span>",{
									lang:res[name]["xml:lang"],
									append:res[name].value
								});
		}
		// like item_span except takes also an item to generate a link to the item
		function item_span(res, url, name){
			return $("<a>",{
									lang:res[name]["xml:lang"],
									append:res[name].value,
									href:res[url].value
								});
		}
		
		async function get_upperclasses_disjointness_violations_infos(bottom, classes){
			
		}
		
		// renders a class list into a html horizontal list
		
		function display_as_list(class_list){
			return $("<ul>").addClass("classification-gadget-horizontal-list").append(
				class_list.map(
							class_=> make_list_item(item_span(class_,"class","classLabel"))
				)
			);
		}
		
		function get_html(jqobj){
			return jqobj[0].outerHTML;
		}
		
		function render_disjointness_violation_for_instance(instance, classes, violation, conflicting_classes, upper_conflicting_class){
			let violation_parent_class = classes.find(class_ => class_.class.value == violation.class.value );
			const class_label = get_html(label_span(violation_parent_class, "classLabel"));
			
			let [class1, class2] = conflicting_classes.map(c => get_html(classes.label_span(c.class.value)));
			
			return create_constraint_violation_box(
				messages.$("disjointness_violation"), // `Disjointness violation !`
				messages.$("instance_of_several_classes")
				//"This instance is an instance of two or more classes, there are several ways to fix it [TODO]"
				).append([
					messages.$("classes_are_disjoint")
					/* "On class $class it is stated that the following classes, and this is an instance of them !" */
						.replace("$class", `<a href="${violation.st.value}">${class_label}</a> `), "<br/>",
					display_as_list(conflicting_classes),
					"<br/>",
					messages.$("probable_culprit"),
					/* " => Probable culprit(s), highest classes in the tree to be subclasses of conflicting classes (if any) :", */
						display_as_list(upper_conflicting_class),
					create_full_class_tree(
						query_conflicting_class_tree_visualisation(instance, "P31",     "base: wdt:P31 ?baseclass.", violation_parent_class, conflicting_classes[0], conflicting_classes[1]),
						messages.$("see_conflict_tree"), /* "Visualize this disjointness conflict in its class tree … " */
						create_classtree_disjointness_caption(
							messages.$("disjoint_union_class").replace("$class", get_html(classes.label_span(violation.class.value))),
							messages.$("this_instance").replace("$instance",item_label),
							messages.$("subclass_of_c").replace("$class", class1),
							messages.$("subclass_of_c").replace("$class", class2),
							messages.$("subclass_of_both")
						)
					),
				]
			);
		}
		
		function array_join(array, sep){
			return [
				...array.slice(0, -1).flatMap(elem => [elem, sep]),
				...array.slice(-1)
			];
		}
		
		function create_classtree_disjointness_caption(top, bottom, unique1, unique2, several){
			let items = [
					[top, "classification-gadget-violation-colors-top"],
					[bottom, "classification-gadget-violation-colors-bottom"],
					[unique1, "classification-gadget-violation-colors-unique-1"],
					[unique2, "classification-gadget-violation-colors-unique-2"],
					[several, "classification-gadget-violation-colors-several"]
				].map(([msg, color]) => $("<span>").addClass(color).append(msg));
			return $("<div>").append(
				array_join(items," ")
			);
		}
		
		
		function render_disjointness_violation_for_subclass(subclass, classes, violation, conflicting_classes, upper_conflicting_class){
			let violation_parent_class = classes.find(class_ => class_.class.value == violation.class.value );
			const class_label = label_span(violation_parent_class, "classLabel");
			
			let [class1, class2] = conflicting_classes.map(c => get_html(classes.label_span(c.class.value)));
			
			return create_constraint_violation_box(
				messages.$("disjointness_violation"), // `Disjointness violation !`
				messages.$("subclass_of_several_classes")
				//"This instance is an instance of two or more classes, there are several ways to fix it [TODO]"
				).append([
					messages.$("superclasses_are_disjoint")
					/* "On class $class it is stated that the following classes, and this is a subclass of them !" */
						.replace("$class", `<a href="${violation.st.value}">${class_label.html()}</a> `), "<br/>",
					display_as_list(conflicting_classes),
					"<br/>",
					messages.$("probable_culprit"),
					/* " => Probable culprit(s), highest classes in the tree to be subclasses of conflicting classes (if any) :", */
						display_as_list(upper_conflicting_class),
					create_full_class_tree(
						query_conflicting_class_tree_visualisation(subclass,"P279", "base: wdt:P279 ?baseclass." ,violation_parent_class, conflicting_classes[0], conflicting_classes[1]),
						messages.$("see_conflict_tree"), /* "Visualize this disjointness conflict in its class tree … " */
						create_classtree_disjointness_caption(
							messages.$("disjoint_union_class").replace("$class", get_html(classes.label_span(violation.class.value))),
							messages.$("this_class_").replace("$class",item_label),
							messages.$("subclass_of_c").replace("$class", class1),
							messages.$("subclass_of_c").replace("$class", class2),
							messages.$("subclass_of_both")
						)
					),
				]
			);
		}
		
		
		async function get_disjointness_violation_infos(
			classes, 
			violation, 
			culprit_query_creator){
			
			const copy_classes = [...classes];
			
			const disjoint_classes = violation.class_set.value.split(",");

			let violation_parent_class = copy_classes.find(class_ => class_.class.value == violation.class.value );
			let conflicting_classes = copy_classes.filter(class_ => disjoint_classes.includes(class_.class.value)); 
			
			let probable_conflicting_class_results = await makeSPARQLQuery(
				endpointUrl,
				culprit_query_creator(
					conflicting_classes[0].class.value, 
					conflicting_classes[1].class.value
				)
			);
			let probable_conflicting_class = probable_conflicting_class_results.results.bindings;
			let upper_conflicting_class = probable_conflicting_class.map(
				(cclass) => {
					return copy_classes.find(
					   (pclass) => pclass.class.value === cclass.class.value
				    ); 
				}
			);
			return [conflicting_classes, upper_conflicting_class];
			
		}
		
		async function get_disjointness_violations_infos(classes, violations_results, culprit_query_creator, render_violation){
			let section = $("<div/>");
			
			let violations = violations_results.results.bindings;
			if (violations.length === 0){
				return;
			}
			

			for(const violation of violations){
				
				let [conflicting_classes, upper_conflicting_class]  = await get_disjointness_violation_infos(classes, violation, culprit_query_creator);
				
				section.append($("<span/>").append(
					render_violation( classes, violation, conflicting_classes, upper_conflicting_class)
				));
			}
			return section;
		}
		
		async function get_instance_disjointness_violations_infos(instance, classes){
			let get_query = (conf1, conf2) => 
				query_highest_subclass_of_both_conflicting_class_of(
					instance, 
					conf1, 
					conf2,
				);
			let violations_results = await makeSPARQLQuery(endpointUrl, query_disjoint_statements_violated_by_instance(instance));
			
			return (get_disjointness_violations_infos(classes, violations_results, get_query, ( ...args )=>render_disjointness_violation_for_instance(instance, ...args)));
		}
		
		async function get_subclass_disjointness_violations_infos(base_class){
			
			let data = await makeSPARQLQuery(endpointUrl, query_superclasses(base_class));
			var classes = data.results.bindings;
			augment_classlist(classes);
			let get_query = (conf1, conf2) => 
				query_highest_subclass_of_both_conflicting_superclass_of(
					base_class, 
					conf1, 
					conf2,
				);
			let violations_results = await makeSPARQLQuery(endpointUrl, query_disjoint_statements_violated_by_superclasses(base_class));
			
			return (get_disjointness_violations_infos(classes, violations_results, get_query, ( ...args ) => render_disjointness_violation_for_subclass(base_class, ...args)));
		}
		
		
		
		function create_report_link(sample, instance){
			return $("<a>",{
							href : "/wiki/Wikidata Talk:WikiProject Ontology?" + $.param(
								{
									action:"edit",
									section:"new",
									preloadtitle: "{{Q|$1}} is an instance of {{Q|$2}} and that is weird"
													.replace("$1",instance)
													.replace("$2",urlToQid(sample.class.value)),
									summary:"Reported Classtree Bug (gadget classification.js)",
									preload:"Template:ReportWeirdSubclassChain",
									preloadparams:[
										instance,
										urlToQid(sample.class.value),
										encodeURIComponent(query_inst_path(instance, sample.class.value)),
										gadget
									]
								}),
							class : "classification-gadget-report-link",
							text  : messages.$("weird_to_discuss") /* :"(This seems weird or incorrect, discuss it)" */
						}
			);
		}
		
		function create_inferred_instance_detail(sample, instance){
			return $("<details>",{
				append: [
					$("<summary>",{
						append:
						[
							$("<a>",{
								href:sample.class.value,
									append:label_span(sample, "classLabel")
								}
							),
							" ",
							create_report_link(sample, instance)
						]
					}),
					$("<div>")
				],
				one:{
					toggle:
						function(event){
							var self = event.target;
							
							makeSPARQLQuery(endpointUrl,
								query_direct_classes_subclasses_of_superclass(instance, sample.class.value),
								function(data) {
									
									$("summary", self).after(
										messages.$("instance_explanation") /*
										"$item_label is an explicit instance of $explicit_classes "
										+ " and it is/they are linked to $superclass this class by a subclass chain."*/
											.replace("$superclass", label_span(sample, "classLabel").prop('outerHTML'))
											.replace("$explicit_classes", 
												data.results.bindings.map(
													function(res) { return label_span(res,"classLabel").html() ; }
												).join(messages.$("and") /*" and "*/))
											.replace("$item_label", item_label)
									);
								}
							);
							$(self).append(
								create_embedded_frame(
									query_inst_path(instance, sample.class.value)
								)
							);
						}
					}
			});
		
		}
		
		
		function superclass_list(classes, instance, item_label){
			let section = $("<h4>",{
					 text: messages.$("item_is_also_instanceof") /*"$item_label is also a(n) …"*/
					       .replace("$item_label", item_label)
					});
			var num_classes = classes.length;
			var sample_classes = classes.filter(
				function(){
					return (num_classes < 4 ||  Math.random() < (4/num_classes) );
				});
			
			var sample_classes_links = sample_classes.map(
				(res) => create_inferred_instance_detail(res, instance)
			);
			
			return [
				section,
				messages.$("show_samplesize") /*"(showing $num_sample / $total_available results"*/
						.replace("$num_sample", sample_classes.length)
						.replace("$total_available", num_classes),
				$("<ul>",{
					append:sample_classes_links.map(make_list_item),
					style:"list-style:none;",
				}),
				$("<a>", {
					text:messages.$("more"),
					one:{
						click:function(event){
							var not_shown_yet = classes.filter(elem => !sample_classes.includes(elem));
		
							$("ul", event.target.parentNode).append(
								not_shown_yet
									.map((res) => create_inferred_instance_detail(res, instance))
									.map(make_list_item));
							event.target.remove();
						}
					}
				})
			];
		}
		
		// initialize the section below the « instance of » statements
		
		async function register_subsection(section, creation_function){
			let div = $("<div>");
			section.append(div);
			let res = await  creation_function();
			if(res){ 
				div.append(res);
			} 
		}
		
		function augment_classlist(classes){
			classes.get_class = function(searchclass){ 
				return (this.find(class_ => class_.class.value == searchclass ));
			};
			
			classes.label_span = function(searchclass){ 
				return label_span(this.get_class(searchclass), "classLabel");
			};
			
			classes.item_span =  function(searchclass){
				return item_span(this.get_class(searchclass), searchclass, "classLabel");
			};
		}
		
		async function init_instances(instance){
			
			var explain_id = "classification-gadget-instance-explain";
			var item_label = $( ".wikibase-title-label" ).text();
			
			var instances_info=$("<div>",{
					id:"classification-gadget-instances", 
					class:"wikibase-statementgroupview classification-gadget-property-section",});
					
			var cont_id = create_property_container("P31", instances_info);
			
			let full_class_tree = () => create_full_class_tree(
				query_instance_superclass_tree(instance), 
				messages.$("embedded_classes_of_instance_tree") /*:"Embedded tree of the classes of this instance"*/
			);
			
			register_subsection(instances_info, full_class_tree);
			// getting the necessary informations, the superclasses
			
			let classes_q = await makeSPARQLQuery( endpointUrl, query_other_classes(instance));
			var classes = classes_q.results.bindings;
			augment_classlist(classes);
			
			register_subsection(instances_info, () => superclass_list(classes, instance, item_label));		
			register_subsection(instances_info, () => get_instance_disjointness_violations_infos(instance, classes));
		}
		
		function show_classification_data(){
			const entity = mw.config.get( 'wbEntityId' );
			
			init_subclassof(entity);
			init_instances(entity);
			
		}
		
		
		// functions to deal with the activation / deactivation of the gadget
		
		function show_deactivate_link(confkey, timeout){
			var link = mw.util.addPortletLink(
			    'p-tb',
			    "#",
			    messages.$("deactivate_gadget")
			);
			$( link ).on( 'click', function ( e ) {
			    mw.storage.set(confkey, false, timeout);
			    e.preventDefault();
			    e.target.parentNode.remove();
			    show_activate_link(confkey, timeout, true);
			} );
		}
		
		function show_activate_link(confkey, timeout, loaded){
			var link = mw.util.addPortletLink(
			    'p-tb',
			    "#",
			    messages.$("activate_gadget")
			);
			$( link ).on( 'click', function ( e ) {

			    mw.storage.set(confkey, true, timeout);
			    
			    e.preventDefault();
			    e.target.parentNode.remove();
			    if(!loaded){
			    	show_classification_data();
			    }
			    show_deactivate_link(confkey, timeout);
			} );
		}
		
		function init(){
			var entity = mw.config.get( 'wbEntityId' );
			const config_save_timeout = 3*3600*24*30;
			
			const activated_key = "classification-gadget.activated";
			// gadget active by default
			
			if (mw.storage.get(activated_key) === null){
				mw.storage.set(activated_key, true, config_save_timeout);
			}
			const activated = mw.storage.get(activated_key);
			if ( activated === "false"){
				show_activate_link(activated_key, config_save_timeout, false);
			} else {
				// load translation object
				show_classification_data();
				show_deactivate_link(activated_key, config_save_timeout);
			}
		}
		
		
		// load tootranslate before anything, we cannot really do this asynchronously because we need the localised message in the toolbox to activate / deactivate the gadget loading anyway, so we have to load the translation. If not we could not be able to show a localized link to activate. (maybe load in english first and then update ?)
		
		try {
			await mw.loader.getScript("//tools-static.wmflabs.org/tooltranslate/tt.js");
		} catch(e) {
			mw.notify("Classification gadget : cannot load tooltranslate, using english messages");
			mw.log(e);
		}
		mw.loader.using(
			["oojs-ui.styles.icons-editing-advanced"]
		).then(
			// ensure that the messages are loaded before calling "init" because there might be asynchronous issues with loading
			() =>   {
				if(typeof(ToolTranslation) !== "undefined"){ 
					tool_translate = new ToolTranslation ( 
						{ 
							language: lang, 
							languages : mw.language.getFallbackLanguageChain().reverse() , 
							debug:1 , 
							tool: 'lassificationjs', 
							callback : () => { load_messages(); init() },
							no_interface_update : true
						}
					);
				} else {
					load_messages(); init();
				}
			}
		);
	});
})(mw, $, wb);