import { Theme, WithStyles, withStyles } from '@signatu/styles';
import { Consent, CONSENT_TEXTS, ConsentActionType, ConsentEvent } from '@signatu/consent';
import PropTypes from 'prop-types';
import React from 'react';

import { CONSENT_BASE_PROPTYPES, ConsentProps } from '../lib/consentBase';
import { defaultButton } from '../styles/button';

/** Make higher-order component for Button and Link */

export interface ConsentActionProps extends ConsentProps {
    id?: string
    /** Overrides for accept and deny text */
    acceptText?: string
    refuseText?: string
}

export interface ConsentActionState {
    consent?: ConsentEvent
    checked: boolean
    language: string
}

const styles = (theme: Theme) => ({
    root: {
    }, 
    button: defaultButton(theme), 
    checkboxContainer: {
        display: "flex", 
        alignItems: "center"
    }, 
    checkbox: {
        marginRight: theme.spacing.unit * 2
    },
    checkboxLabel: {
    },
    textContainer: {
        color: theme.palette.text.main, 
        userSelect: "none", 
        '-moz-user-select': "none"
    }, 
    buttonContainer: {
        marginTop: theme.spacing.unit * 2
    }, 
    acceptButton: {
        border: `1px solid ${theme.palette.balanced.main}`, 
        color: theme.palette.balanced.main, 
        background: theme.palette.backgroundColor
    }, 
    acceptButtonActive: {
        color: theme.palette.balanced.contrastText, 
        background: theme.palette.balanced.main 
    }, 
    denyButton: {
        color: theme.palette.attention.main, 
        border: `1px solid ${theme.palette.attention.main}`, 
        background: theme.palette.backgroundColor
    }, 
    denyButtonActive: {
        color: theme.palette.attention.contrastText,  
        background: theme.palette.attention.main 
    }
})

class ConsentAction extends React.Component<ConsentActionProps & WithStyles, ConsentActionState> {

    /** PropTypes for non-Typescript environments */
    static propTypes = {
        ...CONSENT_BASE_PROPTYPES, 
        /** See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/27973 */
        id: PropTypes.string as PropTypes.Validator<string | undefined>, 
        type: PropTypes.string as PropTypes.Validator<ConsentActionType | undefined>, 
        acceptText: PropTypes.string as PropTypes.Validator<string | undefined>, 
        refuseText: PropTypes.string as PropTypes.Validator<string | undefined>, 
        classes: PropTypes.object as PropTypes.Validator<any>
    }

    consentService?: Consent
    state: ConsentActionState = { checked: false, language: 'en' }
    
    /** 
     * 
     * See https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html 
     * 
     * We track this because our loadConsent may complete after the component has been unloaded. 
     */
    
    componentIsMounted = false
    
    componentWillUnmount() {
        this.componentIsMounted = false
    }

    async componentDidMount() {
        this.componentIsMounted = true
        this.loadConsent(this.props)
    }

    componentDidUpdate(prevProps: ConsentActionProps) {
        const { config, subject, issuer, target } = this.props
        /** Support changing config */
        if (prevProps.config !== config || 
            prevProps.issuer !== issuer || 
            prevProps.target !== target || 
            prevProps.subject !== subject ) {
                this.loadConsent(this.props)
        }
    }

    updateState = (consent?: ConsentEvent, userAction = true) => {
        this.setState({consent: consent, checked: this.checkedState(consent)})
        if (userAction && consent && this.props.onAction) {
            this.props.onAction(consent)
        }
    }
    
    checkedState = (event?: ConsentEvent): boolean => {
        if ( ! event) {
            // No event found - if we have a optOut model => checked by default
            if (this.props.optOut) {
                return !!this.props.optOut
            }
        }
        return event ? !!event.action : false
    }

    /* Has the user actively accepted or denied? That requires a consent event */
    activeAccept = (): boolean => this.state.consent ? this.state.consent.action === true : false
    activeDeny = (): boolean => this.state.consent ? this.state.consent.action === false : false

    loadConsent = async (props: ConsentActionProps) => {
        if (!(
            props.config && 
            props.config.accessToken && 
            props.config.apiKey && 
            props.subject && 
            props.issuer && 
            props.target
        )) {
            return console.warn("ConsentAction: cannot mount without config, subject, issuer and target", props)
        }

        if (props.language) {
            this.setState({language: props.language})
        }
        
        let consent: ConsentEvent | undefined = undefined
        try {
            this.consentService = new Consent(
                props.config, 
                props.subject, 
                props.issuer, 
                props.target, 
                document && document.location && document.location.href, 
                props.delegate)
    
            const consents = await this.consentService.get(props.scope)
            consent = consents ? consents[0] : undefined
        } catch(e) {
            console.warn(`ConsentAction: Unable to load consent: `, e)
        } finally {
            /** This may complete after we've unmounted */
            if (! this.componentIsMounted) {
                return
            }
            if (props.onLoaded) {
                props.onLoaded(consent)
            }
            this.updateState(consent, false)
        }
    }

    componentWillReceiveProps(props: ConsentActionProps) {
        const { language, subject, issuer, target, scope } = props
        if (language && language !== this.state.language) {
            this.setState({language})
        }

        /** If subject, issuer, target or scope changes we need to reload consent */
        if (
            subject !== this.props.subject ||
            issuer !== this.props.issuer ||
            target !== this.props.target ||
            scope !== this.props.scope) {
                this.loadConsent(props)
            }
    }

    onChange = async (event: any) => {
        if (! (event && event.target && this.consentService)) {
            return
        }
        this.setAction(event.target.checked)
    }

    setAction = async (action: boolean) => {
        if (!this.consentService) {
            return console.warn(`setAction: consentService is not initialized`)
        }
        const previousCheckedState = this.state.checked
        /** First set the action immediately to update the UI */
        this.setState({checked: action})
        try {
            /** Store the consent event in Signatu */
            const consent = await this.consentService.set(action, this.props.scope)
            /** Update the state with the status from the consent event. */
            this.updateState(consent)    
        } catch (error) {
            /** E.g., API down, invalid access token. Set the previous state and rethrow the error */
            this.setState({checked: previousCheckedState})
            throw error
        }
    }

    render() {
        // CSS classes for the buttons
        const { classes } = this.props
        const { checked, consent } = this.state
        const acceptText = this.props.acceptText || CONSENT_TEXTS[this.state.language].BUTTON_ACCEPT
        const refuseText = this.props.refuseText || CONSENT_TEXTS[this.state.language].BUTTON_REFUSE
        
        return <div className={classes.root} data-type="ConsentAction">
            {this.props.type === ConsentActionType.CHECKBOX && <div className={classes.checkboxContainer} data-type="ConsentCheckbox">

                <input className={classes.checkbox} type="checkbox" checked={checked} onChange={this.onChange} id={this.props.id}/>
                <label className={classes.checkboxLabel} htmlFor={this.props.id}/>
                {/* This is a hack - use two labels in order to make the flex grid work */}
                <label className={classes.textContainer} htmlFor={this.props.id}>
                        {this.props.children}
                </label>
            </div>
            }
            {this.props.type === ConsentActionType.BUTTONS && <div data-type="ConsentButtons">
                <div className={classes.textContainer}>{this.props.children}</div>
                <div className={classes.buttonContainer}>
                    <button className={`${classes.button} ${classes.acceptButton} ${consent && checked ? classes.acceptButtonActive : ""}`} onClick={() => this.setAction(true)}>{acceptText}</button>
                    <button className={`${classes.button} ${classes.denyButton} ${consent && !checked ? classes.denyButtonActive : ""}`} onClick={() => this.setAction(false)}>{refuseText}</button>
                </div>
            </div>
            }
        </div>
    }
}

const styled = withStyles<ConsentActionProps>(styles)(ConsentAction)
export { styled as ConsentAction }