/*
 * This is a copy of Gutenberg component https://github.com/WordPress/gutenberg/blob/master/packages/server-side-render/src/server-side-render.js
 * With the changes in our component:
 *  - callbacks before and after new content render
 *  - slightly different rendering process - don't remove previously rendered content. So, we will not see page jumps after every attribute change.
 */

/**
 * External dependencies
 */
import classnames from 'classnames';

/**
 * Internal dependencies
 */
import './style.scss';

/**
 * WordPress dependencies
 */
const {
	Component,
	RawHTML,
} = wp.element;

const { __, sprintf } = wp.i18n;

const apiFetch = wp.apiFetch;

const { addQueryArgs } = wp.url;

const {
	Placeholder,
	Spinner,
} = wp.components;

const {
	doAction,
} = wp.hooks;

export function rendererPath( block, attributes = null, urlQueryArgs = {} ) {
	return addQueryArgs( `/wp/v2/sight-renderer/${ block }`, {
		context: 'edit',
		...( null !== attributes ? { attributes } : {} ),
		...urlQueryArgs,
	} );
}

export class ServerSideRender extends Component {
	constructor( props ) {
		super( props );
		this.state = {
			response: null,
			prevResponse: null,
		};
	}

	componentDidMount() {
		this.isStillMounted = true;
		this.fetch( this.props );
		// Only debounce once the initial fetch occurs to ensure that the first
		// renders show data as soon as possible.
		this.fetch = _.debounce( this.fetch, 500 );
	}

	componentWillUnmount() {
		this.isStillMounted = false;
	}

	componentDidUpdate( prevProps ) {
		const prevAttributes = prevProps.attributes;
		const curAttributes = this.props.attributes;

		if ( ! _.isEqual( prevAttributes, curAttributes ) ) {
			this.fetch( this.props );
		}
	}

	fetch( props ) {
		if ( ! this.isStillMounted ) {
			return;
		}

		const {
			block,
			attributes = null,
			urlQueryArgs = {},
			onBeforeChange = () => {},
			onChange = () => {},
		} = props;

		if ( null !== this.state.response ) {
			this.setState( {
				response: null,
				prevResponse: this.state.response,
			} );
		}

		const path = rendererPath( block );

		// Store the latest fetch request so that when we process it, we can
		// check if it is the current request, to avoid race conditions on slow networks.
		const fetchRequest = this.currentFetchRequest = apiFetch( { method: 'POST', path, data: { attributes: attributes } } )
			.then( ( response ) => {
				if ( this.isStillMounted && fetchRequest === this.currentFetchRequest && response ) {
					onBeforeChange();
					doAction( 'sight.components.serverSideRender.onBeforeChange', this.props );

					this.setState( {
						response: response.rendered,
						prevResponse: null,
					}, () => {
						onChange();
						doAction( 'sight.components.serverSideRender.onChange', this.props );
					} );
				}
			} )
			.catch( ( error ) => {
				if ( this.isStillMounted && fetchRequest === this.currentFetchRequest ) {
					onBeforeChange();
					doAction( 'sight.components.serverSideRender.onBeforeChange', this.props );

					this.setState( {
						response: {
							error: true,
							errorMsg: error.message,
						},
						prevResponse: null,
					}, () => {
						onChange();
						doAction( 'sight.components.serverSideRender.onChange', this.props );
					} );
				}
			} );
		return fetchRequest;
	}

	render() {
		const {
			response,
			prevResponse,
		} = this.state;

		let { className } = this.props;

		className = classnames(
			className,
			'sight-component-server-side-render'
		);

		if ( response === '' ) {
			return (
				<Placeholder
					className={ className }
				>
					{ __( 'Block rendered as empty.' ) }
				</Placeholder>
			);
		} else if ( ! response && prevResponse ) {
			className = classnames(
				className,
				'sight-component-server-side-render-loading'
			);

			return (
				<div className={ className }>
					<Spinner />
					<RawHTML
						key="html"
						className="sight-component-server-side-render-content"
					>
						{ prevResponse }
					</RawHTML>
				</div>
			);
		} else if ( ! response ) {
			return (
				<Placeholder
					className={ className }
				>
					<Spinner />
				</Placeholder>
			);
		} else if ( response.error ) {
			// translators: %s: error message describing the problem
			const errorMessage = sprintf( __( 'Error loading block: %s' ), response.errorMsg );
			return (
				<Placeholder
					className={ className }
				>
					{ errorMessage }
				</Placeholder>
			);
		}

		return (
			<div className={ className }>
				<RawHTML
					key="html"
					className="sight-component-server-side-render-content"
				>
					{ response }
				</RawHTML>
			</div>
		);
	}
}

export default ServerSideRender;
