import {createApp} from 'vue'

/**
 * Utility class used to handle Vue applications. It provides way to load
 * remote application and update history.
 */
export default class Builder {
    constructor(config={}) {
        this.config = config
        this.title = null
        this.app = null
        this.vm = null
    }

    /**
     * Fetch app from remote and mount application.
     */
    fetch(url, {el='#app', ...options}={}) {
        return fetch(url, options).then(response => response.text())
            .then(content => {
                let doc = new DOMParser().parseFromString(content, 'text/html')
                let app = doc.querySelector(el)
                content = app ? app.innerHTML : content
                return this.mount({content, title: doc.title, reset:true, url })
            })
    }

    /**
     * Mount application, using `create_app` if required.
     *
     * @param {String} options.content: replace app container content with it
     * @param {String} options.title: set DOM document title.
     * @param {String} [options.el=this.config.el]: mount application on this element (querySelector argument)
     * @param {Boolean} [reset=False]: if True, force application recreation.
     * @return `app.mount`'s result.
     */
    mount({content=null, title=null, el=null, reset=false, props=null}={}) {
        try {
            this.unmount()

            let config = this.config
            if(el === null)
                el = config.el
            if(reset || !this.app)
                this.app = this.createApp({title,content,el,...config}, props)

            this.vm = this.app.mount(el)
            window.scroll(0, 0)
            return this.vm
        } catch(error) {
            this.unmount()
            throw error
        }
    }

    createApp({el, title=null, content=null, ...config}, props) {
        const container = document.querySelector(el)
        if(!container)
            return
        if(content)
            container.innerHTML = content
        if(title)
            document.title = title
        return createApp(config, props)
    }

    unmount() {
        this.app && this.app.unmount()
        this.app = null
        this.vm = null
    }

    /**
     * Enable hot reload: catch page change in order to fetch them and
     * load page without actually leaving current one.
     */
    enableHotReload(node=null, historySave=true) {
        if(historySave)
            this.historySave(document.location, true)
        node.addEventListener('click', event => this.pageChanged(event), true)
        node.addEventListener('submit', event => this.pageChanged(event), true)
        node.addEventListener('popstate', event => this.statePopped(event), true)
    }

    pageChanged(event) {
        let submit = event.type == 'submit';
        let target = submit || event.target.tagName == 'A'
                        ? event.target : event.target.closest('a');
        if(!target || target.hasAttribute('target'))
            return;

        let url = submit ? target.getAttribute('action') || ''
                         : target.getAttribute('href');
        let domain = window.location.protocol + '//' + window.location.hostname
        let stay = (url === '' || url.startsWith('/') || url.startsWith('?') ||
                    url.startsWith(domain)) && url.indexOf('wp-admin') == -1
        if(url===null || !stay) {
            return;
        }

        let options = {};
        if(submit) {
            let formData = new FormData(event.target);
            if(target.method == 'get')
                url += '?' + (new URLSearchParams(formData)).toString();
            else
                options = {...options, method: target.method, body: formData}
        }
        this.fetch(url, options).then(() => this.historySave(url))
        event.preventDefault();
        event.stopPropagation();
    }

    statePopped(event) {
        const state = event.state
        if(state && state.content)
            // document.title = this.title;
            this.historyLoad(state);
    }

    /// Save application state into browser history
    historySave(url,replace=false) {
        const el = document.querySelector(this.config.el)
        const state = {
            content: el.innerHTML,
            title: document.title,
        }

        if(replace)
            history.replaceState(state, '', url)
        else
            history.pushState(state, '', url)
    }

    /// Load application from browser history's state
    historyLoad(state) {
        return this.mount({ content: state.content, title: state.title })
    }
}
