Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//TODO: Image links, transclusions
moveLinks = {};
(function ($, mw){
	
	if (mw.config.get("wgUserGroups").indexOf("autoconfirmed") === -1 && mw.config.get("wgUserGroups").indexOf("confirmed") === -1){
		return;
	}
	
	var currentIntervalID, left = 0, target, active = false, origin, limit;
	var templatequery, otherquery, transclusionquery;
	
	function confirm(nonredir, redir, transclusions){
		if (nonredir + redir + transclusions <= 50) {
			limit = 55;
			return true;
		}
		var groups = mw.config.get("wgUserGroups");
		if (groups.indexOf("extendedconfirmed") !== -1 || groups.indexOf("sysop") !== -1) {
			limit = 100000;
			var total = nonredir + redir + trans;
			var time = total / 12;
			if (nonredir >= 500 || redir >= 500 || transclusions >= 500) {
				time = Math.floor(time / 5) * 5;
				total = Math.floor(total / 10) * 10;
				return window.confirm("This page is linked from at least " +
					total + " pages, converting all links will take at least" + time + 
					" minutes. Some links may not be converted. Do you wish to continue?");
			}
			time = Math.round(time);
			return window.confirm("This page is linked from " + total + " pages, converting all links will take about " + time + " minutes. Do you wish to continue?");
		}
		limit = 50;
		return window.confirm("This page is linked from over 50 pages. As you are not extended confirmed, some links may not be converted. Do you wish to continue?");
	}
	
	function run(o, t, templates, redirects, articles, files, other, transclusions){
		if (active) {///only allow one copy per page, as we have state
			console.log("Only one instance of move links can be run at once.");
			return;
		}
		active = true;
		target = t;
		origin = o;
		var namespaces = [];
		if (other) {
			for (var ns in mw.config.get("wgFormattedNamespaces")) {
				if (ns <= 0 || ns === 6 || ns === 10) {
					continue;
				}
				namespaces.push(ns);
			}
		}
		if (articles) {
			namespaces.push(0);
		}
		if (templates) {
			namespaces.push(10);
		}
		if (files) {
			namespaces.push(6);
		}
		var redir, nonredir = namespaces.length ? undefined : 0, redirquery, trans;
		if (redirects) {
			$.ajax({
				url: mw.config.get("wgServer") + mw.config.get("wgScriptPath") + '/api.php?action=query&format=json&list=backlinks&blfilterredir=redirects&bllimit=500&bltitle=' +
					encodeURIComponent(origin),
				dataType: "json",
				success: function(data){
						redir = data.query.backlinks.length;
						redirquery = data.query.backlinks;
						if (nonredir !== undefined && trans !== undefined) {
							if (confirm(nonredir, redir, trans)) {
								doConversions(origin, templates, redirects, articles, files, other, transclusions, redirquery);
							}
						}
					},
				error: function() {
					redir = 0;
					console.log("Move links failed to load redirects.");
				}
			});
		}
		else {
			redir = 0;
		}
		
		if (transclusions) {
			$.ajax({
				url: mw.config.get("wgServer") + mw.config.get("wgScriptPath") + '/api.php?action=query&format=json&prop=transcludedin&tiprop=title&tilimit=500&titles=' +
					encodeURIComponent(origin),
				dataType: "json",
				success: function(data){
					for (var p in data.query.pages) {
						trans = data.query.pages[p].transcludedin.length;
						transclusionquery = data.query.pages[p].transcludedin;
					}
					left += trans;
					if (nonredir !== undefined && redir !== undefined) {
						if (confirm(nonredir, redir, trans)) {
							doConversions(origin, templates, redirects, articles, files, other, transclusions, redirquery);
						}
					}
				},
				error: function() {
					redir = 0;
					console.log("Move links failed to load transclusions.");
				}
			});
		}
		else {
			trans = 0;
		}
		
		if (namespaces.length) {
			$.ajax({
				url: mw.config.get("wgServer") + mw.config.get("wgScriptPath") + '/api.php?action=query&format=json&list=backlinks&blfilterredir=nonredirects&bllimit=500&bltitle=' +
					encodeURIComponent(origin) + "&blnamespace=" + namespaces.join("%7C"),
				dataType: "json",
				success: function(data){
						nonredir = data.query.backlinks.length;
						if (redir !== undefined && trans !== undefined) {
							if (confirm(nonredir, redir, trans)) {
								doConversions(origin, templates, redirects, articles, files, other, transclusions, redirquery);
							}
						}
					},
				error: function() {
					nonredir = 0;
					console.log("Move links failed to load non-redirects.");
				}
			});
		}
	}
	//these functions are used when we finish ging through a set of pages
	function templateNext(){
		if (templatequery) {
			left -= templatequery.length;
			currentIntervalID = window.setInterval(intervalConversions, 5000, templatequery, othersNext, "link");
		}
		else {
			othersNext();
		}
	}
	function othersNext(){
		if (otherquery) {
			left -= otherquery.length;
			currentIntervalID = window.setInterval(intervalConversions, 5000, otherquery, transclusionsNext, "link");
		}
		else {
			transclusionsNext();
		}
	}
	function transclusionsNext(){
		if (transclusionquery) {
			left -= transclusionquery.length;
			currentIntervalID = window.setInterval(intervalConversions, 5000, transclusionquery, done, "transclusion");
		}
		else {
			done();
		}
	}
	
	function doConversions(origin, templates, redirects, articles, files, other, transclusions, redirquery){
		function next(){
			if (redirects) {
				currentIntervalID = window.setInterval(intervalConversions, 5000, redirquery, templateNext, "redirect");
			}
			else {
				templateNext();
			}
		}
		$("#move-link-box").html('<span id="move-links-pages-left">Unknown</span> pages remaining (estimated time left: <span id="move-links-time-left">unknown</span>)');
		var namespaces = [];
		if (other) {
			for (var ns in mw.config.get("wgFormattedNamespaces")) {
				if (ns <= 0 || ns === 6 || ns === 10) {
					continue;
				}
				namespaces.push(ns);
			}
		}
		if (articles) {
			namespaces.push(0);
		}
		if (files) {
			namespaces.push(6);
		}
		if (articles || files || other) {
			$.ajax({
				url: mw.config.get("wgServer") + mw.config.get("wgScriptPath") + '/api.php?action=query&format=json&list=backlinks&blfilterredir=nonredirects&bllimit=500&bltitle=' +
					encodeURIComponent(origin) + "&blnamespace=" + namespaces.join("%7C"),
				dataType: "json",
				success: function(data){
						otherquery = data.query.backlinks;
						left += otherquery.length;
						if (templatequery !== undefined) {
							next();
						}
					},
				error: function() {
					nonredir = 0;
					console.log("Move links failed to load others, artciles and files.");
				}
			});
		}
		else{
			otherquery = null;
		}
		if (templates) {
			$.ajax({
				url: mw.config.get("wgServer") + mw.config.get("wgScriptPath") + '/api.php?action=query&format=json&list=backlinks&blfilterredir=nonredirects&bllimit=500&bltitle=' +
					encodeURIComponent(origin) + "&blnamespace=10",
				dataType: "json",
				success: function(data){
						templatequery = data.query.backlinks;
						left += templatequery.length;
						if (otherquery !== undefined) {
							next();
						}
					},
				error: function() {
					nonredir = 0;
					console.log("Move links failed to load templates.");
				}
			});
		}
		else{
			templatequery = null;
		}
	}
	
	function intervalConversions(links, next, type) {
		if (links.length === 0) {
			//complete
			window.clearInterval(currentIntervalID);
			next();
			return;
		}
		var pagesLeft = Math.min(links.length + left, limit);
		var timeLeft = Math.floor(pagesLeft/12) + ":" + (((pagesLeft % 12) * 5).toString().padStart(2, "0"));//format time as m:ss
		if (limit-- <= 0) {// we have done the maximum number of edits.
			window.clearInterval(currentIntervalID);
			done();
			return;
		}
		$("#move-links-pages-left").text(pagesLeft.toString());
		$("#move-links-time-left").text(timeLeft);
		var link = links.pop();
		var title = link.title;
		$.getJSON(
			mw.util.wikiScript('api'),
			{
				format: 'json',
				action: 'query',
				prop: 'revisions',
				rvprop: 'content',
				rvlimit: 1,
				titles: title
			}
		)
			.done(function ( data ) {//FIXME
				var page, wikitext;
				//try {
					for ( page in data.query.pages ) {
						wikitext = data.query.pages[page].revisions[0]['*'];
						convertWikitext(wikitext, title, type);
					}
				//} catch ( e ) {
				//	console.log("Failed to edit " + title);
				//}
			})
			.fail( function() {
				console.log("Failed to edit " + title);
			});
	}
	
	function done(){
		active = false;
		$("#move-link-box").html('<b>Done</b><br/>Check <b><a href="/wiki/Special:WhatLinksHere/' + encodeURIComponent(origin) + '">What links here</a></b>, as some links may not have been updated.');
	}
	
	function convertWikitext(wikitext, title, type){
		var head, tail;
		if (mw.config.get("wgNamespaceIds")[origin.split(":")[0].toLowerCase().replace(/ /g, "_")]) {// get namespace ids, check if namespae is valid (in lowercase, spaces replaced with _)
			head = origin.split(":")[0] + ":" + origin.split(":")[1].charAt();//first char and namespace is case insensitive 
		}
		else {
			head = origin.charAt();//first char is case insensitive
		}
		tail = origin.slice(head.length).replace(/[.*+?^${}()|[\]\\\/]/g, '\\$&').replace(/[ _]/g, "[ _]");//tail is not case insensitive, but is escaped,spaces and underscores are identical
		var safeHead = "";
		for (var i = 0; i < head.length; i++) {
			if (head.charAt(i).toLowerCase() === head.charAt(i).toUpperCase()) {
				safeHead = safeHead + head.charAt(i).replace(/[.*+?^${}()|[\]\\\/]/g, '\\$&').replace(/[ _]/g, "[ _]");//escape this, it could be bad, also do spaces
			}
			else {
				safeHead = safeHead + "[" + head.charAt(i).toLowerCase() + head.charAt(i).toUpperCase() + "]";//add both variants
			}
		}
		var newWikitext;
		switch (type) {//
			case "redirect":
				var re = new RegExp("(\\[\\[\\s*:?\\s*)" + safeHead + tail + "((#[^[\\]]*)?\\s*(\\|[^[\\n]+)?\\]\\])", "g");
				newWikitext = wikitext.replace(re, "$1" + target + "$2");//convert redirect
				break;
			case "transclusion":
				if (safeHead.len <= 4) {//mainspace needs :
					safeHead = ":" + safeHead;
				}
				else if (mw.config.get("wgNamespaceIds")[origin.split(":")[0].toLowerCase().replace(/ /g, "_")] === 10) {//templates do not need prefix
					safeHead = "(?::?" + safeHead.split(":")[0] + ":)?" + safeHead.split(":")[1];//use non-capturing group to not confuse replace()
				}
				else {//colon is optional otherwise
					safeHead = ":?" + safeHead;
				}
				var re = new RegExp("(\\{\\{\\s*)" + safeHead + tail + "(\\s*(\\||\\}\\}))", "g");
				newWikitext = wikitext.replace(re, "$1" + target + "$2");//convert template
				break;
			default:
				var pipeRe = new RegExp("(\\[\\[\\s*:?\\s*)" + safeHead + tail + "((#[^[\\]]*)?\\s*\\|[^[\\n]+\\]\\])", "g");
				var noPipeRe = new RegExp("(\\[\\[\\s*:?\\s*)(" + safeHead + tail + "(#[^[\\]|]*)?\\s*\\]\\])", "g");
				var fairUseRe = new RegExp("(\\{\\{\\s*([Nn]on-free (use rationale( 2| logo)?|(media|image) rationale)|[Ll]ogo fur)\\s*\\|([^}]}?)*?\\|\\s*[Aa]rticle\\s*=\\s*)" + safeHead + tail + "(\s*\|.*?\}\})", "gs");//match all fair-use templates with >1000 uses
				newWikitext = wikitext.replace(pipeRe, "$1" + target + "$2");//convert the links
				newWikitext = newWikitext.replace(noPipeRe, "$1" + target + "$3|$2");//convert the links
				newWikitext = newWikitext.replace(fairUseRe, "$1" + target + "$7");//convert fair use template
		}
		if (newWikitext === wikitext) {
			//no changes, purge the page
			$.ajax({
				url: mw.config.get("wgServer") + mw.config.get("wgScriptPath") + '/api.php?action=purge&format=json&forcelinkupdate=1&titles=' +
					encodeURIComponent(title),
				dataType: "json"
			});
		}
		else {
			//save changes
			$.ajax({
				url: mw.util.wikiScript( 'api' ),
				type: 'POST',
				dataType: 'json',
				data: {
					format: 'json',
					action: 'edit',
					title: title,
					text: newWikitext, 
					summary: "Converted links from [[" + origin + "]] to [[" + target + "]] ([[User:Danski454/move-links|move links]])",
					minor: true,
					token: mw.user.tokens.get( 'editToken' )
				}
			})
			.fail(function(){
				console.log("Could not save " + title);
			});
		}
	}
	
	$(document).ready(function(){
		if (mw.config.get("wgPageName") === "Special:MovePage" && $("h1").first().text() === "Move succeeded") {
			load();
		}
	});
	
	function start(){
		var t, o;
		o = $("#mw-content-text p").first().find("b a").first().text();
		t = $($("#mw-content-text p").first().find("b a")[1]).text();
		run(o, t, $('#move-link-templates').is(':checked'), $('#move-link-redir').is(':checked'), 
			$('#move-link-article').is(':checked'), $('#move-link-file').is(':checked'), $('#move-link-other').is(':checked'), $('#move-link-trans').is(':checked'));
	}
	
	function load(){
		$("#mw-content-text").append('<div id="move-link-box"><b>Update links from</b><br/><input type="checkbox" value="redirects" id="move-link-redir" checked /> Redirects <input type="checkbox" value="templates" id="move-link-templates" checked /> Templates <input type="checkbox" value="articles" id="move-link-article" checked /> Articles <input type="checkbox" value="files" id="move-link-file" checked /> Files <input type="checkbox" value="others" id="move-link-other" /> Others <input type="checkbox" value="others" id="move-link-trans" /> Transclusions<br/><input id="move-links-go" type="button" value="Submit" onclick="moveLinks.start();" /></div>');
	}
	
	moveLinks.start = start;
	moveLinks.run = run;
	moveLinks.load = load;
	
})(jQuery, mediaWiki);