/**
 * @name Reload-in-place v2.0
 * @desc A simple script to add to your pagedjs project. On reload, it will make the web browser scroll to the place it was before reload. 
 * Useful when styling or proof correcting your book. Multi docs compatible and doesn't wait for complete compilation to go.
 * @author Nicolas Taffin 
 * @author Sameh Chafik
 * @author (adapted by) Benjamin G. <ecrire@bnjm.eu>
 * @license MIT
 * @see { @link https://gitlab.com/csspageweaver/plugins/reloadInPlace }
 */


import { Handler } from '/csspageweaver/lib/paged.esm.js';


export default class reloadInPlace extends Handler {
	constructor(chunker, polisher, caller) {
		super(chunker, polisher, caller);
		this.parameters = cssPageWeaver.features.reloadInPlace.parameters || {}
		this.isScrollBlured = this.parameters.blur || true;
		this.scrollBehavior = this.parameters.scrollBehavior || 'instant';

		// set a "unique" filename based on title element, in case several books are opened
		this.fileTitle = document.getElementsByTagName("title")[0].text.replace(/ /g, "");
	}

	beforeParsed() {
		// separate human / machine scroll
		this.parameters.machineScroll = false;

		// check pagedJS ended compilation
		this.parameters.cssPageWeaverEnd = false;

		// Make it blur if needed
		this.isBlur()

		// Start saving scroll position early (don't wait for render to complete)
		let _ = this;
		var slowSave = this.debounce(() => {
			if(!_.parameters.machineScroll) {
				_.saveAmountScrolled();
			}
		}, 100);

		window.addEventListener('scroll', slowSave);
	}
	
	afterParsed() {
		this.moveFast();
	}

	afterRendered(pages) {
		this.parameters.cssPageWeaverEnd = true;
	}

	getDocHeight() {
		var D = document;
		return Math.max(
			D.body.scrollHeight, D.documentElement.scrollHeight,
			D.body.offsetHeight, D.documentElement.offsetHeight,
			D.body.clientHeight, D.documentElement.clientHeight
		)
	}

	saveAmountScrolled(){
		var scrollArray = [];
		var scrollTop = window.pageYOffset || (document.documentElement || document.body.parentNode || document.body).scrollTop
		if (!this.parameters.machineScroll) {
			var scrollLeft = window.pageXOffset || (document.documentElement || document.body.parentNode || document.body).scrollLeft
			scrollArray.push({ X: Math.round(scrollLeft), Y: Math.round(scrollTop) });
			//console.log("Saved ", scrollArray);
			localStorage['reloadInPlace-' + this.fileTitle] = JSON.stringify(scrollArray);
		}
	}

	isBlur(){
		// Apply a blur effect if scroll blurring is enabled
		if (this.isScrollBlured) {
			var styleEl = document.createElement('style');
			styleEl.setAttribute("data-reload-in-place", true)
			document.head.appendChild(styleEl);
			this.styleSheet = styleEl.sheet;
			this.styleSheet.insertRule('.pagedjs_pages { filter: blur(3px); }', 0);
			
		}
	}

	retrievePosition(){
		// Retrieve saved scroll data from localStorage
		var savedData = localStorage.getItem('reloadInPlace-' + this.fileTitle);

		if (savedData) {
			// Parse the saved data to get the scroll positions
			var scrollArray = JSON.parse(savedData);
			this.scrollTop = scrollArray[0].Y;
			this.scrollLeft = scrollArray[0].X;
		} else {
			// Default scroll positions if no saved data is found
			this.scrollTop = 0;
			this.scrollLeft = 0;
		}
	}


	/**
	 * Adjusts the scroll position of the window based on saved data.
	 * This function handles scrolling behavior when the document height changes,
	 * ensuring the view returns to a previously saved scroll position or the bottom of the document.
	 *
	 * @param {Object} _ - ReloadInPlace scope
	 */
	moveFast() {
		// Set the machine scroll flag to true
		this.parameters.machineScroll = true;

		this.retrievePosition()

		// Get the window height
		var winheight = window.innerHeight || (document.documentElement || document.body).clientHeight;

		let _ = this

		// Track last scroll position to detect user scroll
		let lastScrollTop = window.pageYOffset || document.documentElement.scrollTop;

		// Detect user manual scroll and stop auto-scroll
		const userScrollHandler = () => {
			if (!_.parameters.machineScroll) return;

			const currentScroll = window.pageYOffset || document.documentElement.scrollTop;
			const expectedScroll = _.scrollTop > _.docheight - winheight ? _.docheight : _.scrollTop;

			// If scroll position differs significantly from expected, user is scrolling
			if (Math.abs(currentScroll - expectedScroll) > 100 && Math.abs(currentScroll - lastScrollTop) > 50) {
				_.stopAutoScroll();
			}
			lastScrollTop = currentScroll;
		};

		window.addEventListener('wheel', () => _.stopAutoScroll(), { once: true });
		window.addEventListener('touchstart', () => _.stopAutoScroll(), { once: true });

		// Set up an interval to adjust the scroll position
		_.currentInterval = setInterval(() => {
			// Get the current document height
			_.docheight = _.getDocHeight();

			// Check if the saved scroll position is beyond the current document height
			if ( _.scrollTop > 0 && _.scrollTop > _.docheight - winheight && !_.parameters.cssPageWeaverEnd) {
				// Scroll to the bottom of the document
				window.scrollTo({ left: _.scrollLeft, top: _.docheight, behavior: _.scrollBehavior });
			} else {
				// Scroll to the saved position
				window.scrollTo({ left: _.scrollLeft, top: _.scrollTop, behavior: _.scrollBehavior });

				// Clear interval
				clearInterval(_.currentInterval);

				// set a timeout to finalize the scroll position
				setTimeout(function () {
					window.scrollTo({ left: _.scrollLeft, top: _.scrollTop, behavior: _.scrollBehavior });

					// Reset the machine scroll flag
					_.parameters.machineScroll = false;

					// Remove the blur effect if it was applied
					if (_.isScrollBlured) {
						_.styleSheet.deleteRule(0);
					}
				}, 50); // Delay to start
			}
		}, 50); // Refresh frequency
	}

	stopAutoScroll() {
		if (this.currentInterval) {
			clearInterval(this.currentInterval);
			this.currentInterval = null;
		}
		this.parameters.machineScroll = false;

		// Remove blur if applied
		if (this.isScrollBlured && this.styleSheet && this.styleSheet.cssRules.length > 0) {
			this.styleSheet.deleteRule(0);
		}
	}

	debounce(func, wait, immediate) {
		var timeout;
		return function() {
			var context = this, args = arguments;
			var later = function() {
				timeout = null;
				if (!immediate) func.apply(context, args);
			};
			var callNow = immediate && !timeout;
			clearTimeout(timeout);
			timeout = setTimeout(later, wait);
			if (callNow) func.apply(context, args);
		};
	};
}