const listingTitle = 'Wikipedia:Corrector_ortogr%C3%A1fico/Listado';

/* Get the text of the Wikipedia page containing the misspellings */
async function getMisspellingListing() {
  const url = `https://es.wikipedia.org/w/index.php?title=${listingTitle}&action=raw`;
  return (await fetch(url)).text();
}

/* Parse the misspelling listing and get a map of misspelling objects */
function parseMisspellings(listing) {
  const misspellings = new Map();

  for (let line of listing.split('\n')) {
    // Take only into account lines starting with a whitespace
    if (!line.startsWith(' ')) {
      continue;
    }

    // Ignore misspelling comments
    if (line.trim().startsWith('#')) {
      continue;
    }

    const tokens = line.split('|');
    if (tokens.length !== 3) {
      console.warn('Invalid line: ' + line);
      continue;
    }

    const word = tokens.trim();
    const cs = tokens.trim() === 'cs';
    const description = tokens.trim();
    // We need to reduce as much as possible the size of the map when caching it as JSON
    // The word is used as a key, and it is not needed later as a misspelling value.
    // Store the case-sensitive with key "c" and boolean value "1" or "0".
    const misspelling = {
      c: cs ? 1 : 0,
      d: description
    };

    // By default, the misspellings are case-insensitive.
    // Use as a key the misspelling word in lowercase.
    // In case of a case-sensitive misspelling,
    // introduce it again using as a key the misspelling word as is.
    if (cs) {
      misspellings.set(word, misspelling);
    } else {
      misspellings.set(word.toLowerCase(), misspelling);
    }
  }
  return misspellings;
}

const storageMapKey = 'misspellings';
const storageTimestampKey = 'misspellingsTimestamp';
function needsCacheRefresh() {
  const lastLoad = localStorage.getItem(storageTimestampKey);
  if (lastLoad === null) {
    return true;
  } else {
    // Cache the entry list 1 day
    const diffInSeconds = (Date.now() - parseInt(lastLoad)) / 1000;
    if (diffInSeconds > 24 * 60 * 60) {
      return true;
    }
  }
  return false;
}

async function loadMisspellings() {
  const misspellings = localStorage.getItem(storageMapKey);
  if (misspellings !== null && !needsCacheRefresh()) {
    return new Map(Object.entries(JSON.parse(misspellings)));
  } else {
    // Load/refresh the misspellings
    getMisspellingListing().then(listing => {
      const misspellings = parseMisspellings(listing);
      const jsonMisspellings = JSON.stringify(Object.fromEntries(misspellings));
      console.debug('MISSPELLING LIST SIZE', jsonMisspellings.length);
      localStorage.setItem(storageMapKey, jsonMisspellings);
      localStorage.setItem(storageTimestampKey, JSON.stringify(Date.now()));
      return misspellings;
    });
  }
}

/* Return if the given character is a valid word character */
function isWordChar(ch) {
  // Trick: there are not both uppercase and lowercase versions of punctuation characters,
  // numbers, or any other non-alphabetic characters.
  return ch.toLowerCase() !== ch.toUpperCase();
}

/*
 * Return if the character is a valid word separator.
 * Currently, we consider as a valid separator any non-word character.
 */
function isValidSeparator(ch) {
  return !isWordChar(ch);
}

/* Find all the words in a text along with its position in the text */
function findAllWords(text) {
  const words = ;
  let start = 0;
  while (start >= 0 && start < text.length) {
    // Find start of the word
    let startWord = -1;
    for (let i = start; i < text.length; i++) {
      if (isWordChar(text.charAt(i))) {
        startWord = i;
        break; // Exit for loop
      }
    }
    if (startWord < 0) {
      break; // Exit while loop
    }

    // Find end of the word
    let endWord = text.length; // Default value
    for (let i = startWord + 1; i < text.length; i++) {
      if (isValidSeparator(text.charAt(i))) {
        endWord = i;
        break; // Exit for loop
      }
    }

    words.push({
      start: startWord,
      word: text.substring(startWord, endWord),
    });
    start = endWord + 1;
  }
  return words;
}

/* Find the (optional) misspelling related to a given word */
function findWordMisspelling(word, misspellings) {
  // First we check the word as is
  let misspelling = misspellings.get(word);
  if (misspelling !== undefined) {
    return misspelling;
  } else {
    // If not, check with the word in lowercase.
    misspelling = misspellings.get(word.toLowerCase());
    if (misspelling !== undefined && misspelling.c === 0) {
      return misspelling;
    }
  }
  return undefined;
}

/*
 * Mark (highlight) the words in the given text node corresponding to misspellings.
 * For each found misspelling a new node is created. Return the number of new nodes created.
 */
function markMisspellings(node, misspellings) {
  const hits = ;
  for (let wordResult of findAllWords(node.data)) {
    const misspelling = findWordMisspelling(wordResult.word, misspellings);
    if (misspelling !== undefined) {
      hits.push({
        start: wordResult.start,
        text: wordResult.word,
        description: misspelling.d,
      });
    }
  }

  // Replace the misspelling words with span-nodes
  // Traverse the list backwards to keep the consistency of the text
  hits.reverse();
  let newNodes = 0;
  for (let hit of hits) {
    // Create new span-element
    const newNode = document.createElement("span");
    newNode.style.backgroundColor = "#FF9191";
    newNode.title = hit.description;

    // Split content in three parts: begin, middle and end node
    const middleNode = node.splitText(hit.start);
    middleNode.splitText(hit.text.length);
    // Append a copy of the middle to the new span-node
    newNode.appendChild(middleNode.cloneNode(true));
    // Replace the middle node with the new span-node
    middleNode.parentNode.replaceChild(newNode, middleNode);

    newNodes++;
  }
  return newNodes;
}

// Ignore some HTML elements when finding text-nodes
const nodeTagsIgnored = new Set();
nodeTagsIgnored.add('script').add('style').add('form');

function markNode(node, misspellings) {
  let newNodes = 0;
  if (node.nodeType === Node.TEXT_NODE) { // Text node
    newNodes = markMisspellings(node, misspellings);
  } else if ((node.nodeType === Node.ELEMENT_NODE)  // element node
      && (node.hasChildNodes()) // with child nodes
      && (!nodeTagsIgnored.has(node.tagName.toLowerCase()))) {
    for (let this_child = 0; this_child < node.childNodes.length; this_child++) {
      this_child += markNode(node.childNodes, misspellings);
    }
  }
  return newNodes;
}

function spellcheck() {
  const bodyContent = document.getElementById('mw-content-text');
  loadMisspellings().then(misspellings => {
    if (misspellings !== undefined) {
      markNode(bodyContent, misspellings);
    }
  });
}

function RP_load()
{
  // Variablenabfrage, ob '''keine''' automatische RP bei jedem Aufruf eines Artikels gewünscht ist.
  // wenn automatische RP nicht gewünscht ist, dann einfach "'''var DontAutorunRP = true;'''" vor die Einbindung schreiben
  // ansonsten einfach weg lassen.

  if ( !window.DontAutorunRP )
  {
    // Nur beim Betrachten, aber nicht auch unnötigerweise beim Bearbeiten, auf der Versionsgschichte
    // etc. laden: spart Wartezeit beim Benutzer und Ressourcen auf dem Toolserver
    // Standardmäßig RP nur auf Artikelseiten, wenn RPonAllPages "true" RP in allen Seiten

    if (mw.config.get( 'wgAction' ) == 'view' && (($.inArray( mw.config.get( 'wgNamespaceNumber' ), ) > -1 && !mw.config.get( 'wgPageName' ).match( "^(Anexo:Falsos_amigos|Wikipedia:(Café|Consultas|Corrector_ortográfico/Listado|Informes_de_error|Tablón_de_anuncios_de_los_bibliotecarios|Taller_idiomático|Vandalismo_en_curso))" ) ) || window.RPonAllPages ))
    {
      spellcheck();
    }
  }
}

$( RP_load );