let pageCount = 1
let resetCount = 0

// contents is already defined, see [[html]]

// Replace line breaks with <p>
contents = contents
  .split("\n")
  .filter(line => line.length > 0)
  .map(line => {
    return $(`<p>${line}<${"/p"}>`) // to fix Wikidot's syntax highlighting
  })

// Convert contents from text to jQuery objects
contents = $($.map(contents, el => el.get()))

// Remove empty objects
contents = contents.filter(":not(:empty)")

// Add flex order to paragraphs containing positioned images
contents.map((index, element) => {
  element = $(element)
  if (
    element.is("p") &&
    element.text().length === 0 &&
    element.children().length === 1 &&
    element.children().first().is("img")
  ) {
    // Mark images
    element.addClass("is-image")
    if (
      ["top", "bottom"].includes(element.children().first().attr("position"))
    ) {
      // Order positioned images
      element.css(
        "order",
        { top: -1, bottom: 1 }[element.children().first().attr("position")]
      )
    }
  }
})

// Once everything is ready, render the pages
// This includes image loading, so this is window load instead of document ready
$(window).on("load", () => {
  resetCount = 0
  let thisDocument = $(".pages-container")
  resetPage(thisDocument)
  // Sometimes doesn't work after first load - try again
  setTimeout(() => resetPage(thisDocument), 2000)

  let windowWidth = $(window).width()

  // Rerender the pages when the window size is changed
  $(window).on(
    "resize",
    debounce(() => {
      if ($(window).width() !== windowWidth) {
        windowWidth = $(window).width()
        resetPage(thisDocument)
      }
    }, 200)
  )
})

/**
 * Debounces the execution of a function - it will only fire once its
 * triggering events have stopped.
 *
 * By David Walsh: https://davidwalsh.name/javascript-debounce-function (MIT)
 *
 * @param func - The function to debounce.
 * @param wait - Milliseconds after which to assume there'll be no more events.
 */
function debounce(func, wait) {
  let timeout
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout)
      func(...args)
    }
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
  }
}

function resetPage(thisDocument) {
  thisDocument.empty()
  pageCount = 1
  thisPage = preparePage(thisDocument)
  setTimeout(() => {
    makePage(contents.clone(), thisPage, thisDocument)
    checkPage(thisDocument)
  }, 1)
}

function preparePage(thisDocument) {
  const aspectBox = $(`<div class='aspect-ratio'></div>`).appendTo(thisDocument)
  return $(`<div class='page'></div>`).appendTo(aspectBox)
}

/**
 * Recursively creates pages and sorts text into them.
 *
 * @param contents - Array of (remaining) HTML paragraphs.
 * @param thisPage - The current page as a jQuery element.
 */
function makePage(contents, thisPage, thisDocument) {
  console.log(`Making page ${pageCount++}`)

  let nextPageContents = []

  // Add the contents to the page
  thisPageContents = $("<div class='content'></div>").appendTo(thisPage)
  thisPageContents.empty().append(contents)

  // The page height is the maximum height of page content
  const pageHeight = thisPage.height()

  // The content height is the actual height of page content
  const contentHeight = thisPageContents.outerHeight()

  // If the content exceeds the page, create a new one
  if (
    contentHeight > pageHeight ||
    thisPageContents.has("br.page-break").length > 0
  ) {
    console.log(`Split needed (${contentHeight} > ${pageHeight})`)

    // Find the first element that can't fully appear on the page
    // (or a page break)
    const children = thisPageContents.children()
    let lastParagraphIndex = -1
    for (let index = 0; index < children.length; index++) {
      const child = $(children.eq(index))
      // First, check for a page break
      if (child.has("br.page-break").length > 0) {
        console.log("Splitting on page break")
        lastParagraphIndex = index
        break
      }
      // Second, check for an element that doesn't fit
      // Do this by summing the height of all children up to and this one,
      // then compare that to the desired page height
      const childBottom =
        children
          .slice(0, index)
          .toArray()
          .reduce((height, child) => {
            return height + $(child).outerHeight(true)
          }, 0) + child.outerHeight(true)
      console.log(index, childBottom, thisPage.height())
      if (childBottom > thisPage.height()) {
        lastParagraphIndex = index
        break
      }
    }

    if (lastParagraphIndex === -1) {
      // No elements can fit on this page!
      // Genuinely unsure what to do here
      // The default action is "move everything" so that's probably... fine...
      console.log("No elements can fit on this page")
    } else {
      // There's an element that either can't be on the page, or partially can
      console.log(`Splitting element with index ${lastParagraphIndex}`)

      // Trim away the content that we already know will fit
      nextPageContents = contents.splice(lastParagraphIndex)
      // The partial element is the first of nextPageContents.

      // Set the html to the contents we know will fit
      thisPageContents.empty().append(contents)

      // Extract a copy of the partial element from the next page
      let partialElement = $(nextPageContents[0]).clone()

      // If the partial element is p, it can be split up
      if (partialElement.is("p") && partialElement.text() != "") {
        // The element can be split across pages - move some of it back

        partialElement.appendTo(thisPageContents)

        const partialText = partialElement.text()
        let keepWords = partialText.split(" ")
        let moveWords = []

        // Remove words one by one until the element fits
        for (let index = keepWords.length; index > 0; index--) {
          if (thisPageContents.outerHeight(true) <= thisPage.height()) {
            // It fits! Declare split
            console.log(
              `Split successful after ${moveWords.length} words removed`
            )
            if (moveWords.length > 0) {
              partialElement.addClass("split-paragraph-start")
            }
            break
          }
          let word = keepWords.pop()
          moveWords.unshift(word)
          partialElement.text(keepWords.join(" "))
        }

        // Check if this was successful - might have just run out of words
        if (keepWords.length === 0) {
          // Not successful - undo everything
          console.log("Split unsuccessful; partial element removed")
          partialElement.remove()
        } else {
          // Success! Update the next page with the retained words
          nextPageContents[0].textContent = moveWords.join(" ")
        }
      } else {
        // The element can't be split, so don't move anything back
        // If it's the first element, it probably came from the previous page
        // If that's the case, jut dump it here
        if (lastParagraphIndex == 0) {
          console.log("Something couldn't be split, so it was dumped")
          partialElement.appendTo(thisPageContents)
          partialElement.css("height", pageHeight)
          partialElement.css("overflow", "hidden")
          nextPageContents.shift()
        }
      }
    }

    // If the last element of this page was split, so it the first of the next
    if (
      thisPageContents.children().last().is("p.split-paragraph-start") &&
      $(nextPageContents[0]).is("p")
    ) {
      nextPageContents[0].classList.add("split-paragraph-end")
    }

    // If the very first element of the next page is a page break, chuck it out
    if ($(nextPageContents[0]).has("br.page-break").length > 0) {
      console.log("Scrapping a page break")
      nextPageContents.shift()
    }

    // Slap an arbitrary limit on the recursion
    if (pageCount < 1000) {
      // Create a new page
      const nextPage = preparePage(thisDocument)
      makePage(nextPageContents, nextPage, thisDocument)
    } else {
      console.log("Pages stopped after 1000")
    }
  } else {
    console.log("No split needed")
  }
}

function checkPage(thisDocument) {
  if (resetCount > 5) {
    console.log("Overflow reset limit reached")
    return
  }
  let shouldReset = 0
  thisDocument.children().each((index, aspectBox) => {
    aspectBox = $(aspectBox)
    pageBox = $(aspectBox.children().first())
    contentBox = $(pageBox.children().first())
    if (contentBox.outerHeight(true) > pageBox.height()) {
      shouldReset++
    }
    if (shouldReset > 1) {
      console.log(`${shouldReset} pages overflowing, resetting`)
      resetCount++
      resetPage(thisDocument)
      checkPage(thisDocument)
    } else {
      console.log("No overflow reset needed")
    }
  })
}
