import * as React from 'react'
import {
    FormContext,
    FormFieldProps,
    FormFieldComponentProps,
    AbstractFormField,
} from '../interfaces'
import { Row, Column } from '.'
import * as cx from 'classnames'
import * as PropTypes from 'prop-types'
import { createEmptyFormField } from '../data'

const styles = require('../../src/styles/components/forms.scss')
const borderStyles = require('../../src/styles/common/borders.scss')

/**
 * A wrapper component for form fields that
 * reads the form name of parent <Form /> elements
 */
export abstract class FormFieldWrapper<T> extends React.Component<T, any> {

    public static contextTypes = {
        formName: PropTypes.string,
    }

    public context: FormContext

    public formName: string

    public componentWillMount() {
        // console.log('FORM FIELD WRAPPER WILL MOUNT', this)
        const name = this.context
            ? this.context.formName
            : (this.props as any).formName

        if (name) {
            this.formName = name
        }
    }
}

/**
 * The base form field component is responsible for:
 *
 * - initializing field data using a set of standard fields (AbstractFormField)
 * - dispatching events and updating the store
 * - running the validators prior to updating the store
 * - rendering error messages underneath the field
 * - styling success and errors in red/green
 *
 * All of these are intedend to be handled in a standard way and thus
 * have been delegated to this class. Every class that extends this one
 * must implement the renderField method to render a specific input type
 */
export abstract class BaseFormField<T> extends React.Component<FormFieldComponentProps<T>, any> {

    /**
     * A reference to the form element itself
     */
    public field: any

    constructor(props: FormFieldComponentProps<any>) {
        super(props)
        this.validate = this.props.validate ?
            this.props.validate.bind(this) : this.validate.bind(this)
        this.onChange = this.onChange.bind(this)
        this.onBlur = this.onBlur.bind(this)
        this.onFocus = this.onFocus.bind(this)
    }

    /**
     * Use React refs to set the field elem
     */
    public setFieldRef = (e: any) => this.field = e

    /**
     * Initialize the field
     * this will also trigger the validation
     * but will not mark the field as dirty (only onFocus does that)
     */
    public componentDidMount() {
        // console.log('FORM FIELD DID MOUNT', this)
        this.dispatchUpdate(this.getNextFieldState(this.getValueFromState()))

        if (this.props.autofocus) {
            this.field.focus()
        }
    }

    public render() {
        // console.log('RENDER FORM FIELD', this)
        // the field may not be initialized yet
        if (!this.props.field) {
            return null
        }

        return (
            <Column className={styles.fieldset}>
                {this.renderField()}
                <Row align="flex-start" className={styles.errorText}>
                    {this.renderErrors()}
                </Row>
            </Column>
        )
    }

    public abstract renderField(): JSX.Element

    public renderErrors() {
        if (this.shouldShowErrors()) {
            const { errors } = this.props.field
            return <span>{errors[0]}</span>
        }
    }

    /**
     * Default validation function
     * Checks to see if the field is required
     * And if a value is present
     *
     * @param value
     */
    public validate(value: T): string[] | boolean {
        if (this.props.field.required && !value) {
            return ['* required']
        }
        return []
    }

    /**
     * This function instructs the class in how to create a blank field
     * It may be different for different types of form fields
     * Since a lot of fields are based on text input a default version
     * has been provided that uses the empty string as a default value
     */
    public initializeField(): AbstractFormField<any> {
        const { value, required } = this.props

        return createEmptyFormField(
            value || '',
            required === false ? false : true,
            false,
        )
    }

    /**
     * Create an object representing the next state state of the field
     *
     * @param value
     */
    public getNextFieldState(value: T) {

        const field = { ...this.props.field, value }

        // do not mess with the validation if an async validation function is present
        if (!this.props.asyncValidate) {

            // people might think that the validation function is supposed
            // to return a boolean rather than an array of string errors
            // rather than leave this as a potential bug better to support it here
            const res = this.validate(value)
            field.errors = res === true ? [] : (res === false ? ['* error'] : res)

            field.valid = field.errors && field.errors.length ? false : true
        }

        return field
    }

    public onChange(e: React.SyntheticEvent<any>) {

        e.preventDefault()
        const { name, formName, onChange, asyncOnChange} = this.props

        // console.log('handling change', this.props)
        const field = this.getNextFieldState(this.getValueFromEvent(e))

        if (onChange) {
            onChange(name, formName, field)
        }

        if (asyncOnChange) {
            asyncOnChange(name, formName, field)
        }

        return this.dispatchUpdate(field)
    }

    public onBlur(e: React.SyntheticEvent<any>) {
        e.preventDefault()
        const field = { ...this.props.field, focused: false }
        if (this.props.onBlur) {
            this.props.onBlur(
                this.props.name,
                this.props.formName,
                field,
            )
        }
        return this.dispatchUpdate(field)
    }

    public onFocus(e: React.SyntheticEvent<any>) {
        e.preventDefault()
        const field = { ...this.props.field, focused: true, dirty: true }
        if (this.props.onFocus) {
            this.props.onFocus(
                this.props.name,
                this.props.formName,
                field,
            )
        }
        return this.dispatchUpdate(field)
    }

    public dispatchUpdate(field: AbstractFormField<T>) {
        this.props.setFormField(
            this.props.name,
            this.props.formName,
            field,
        )
    }

    public getValueFromEvent(e: React.SyntheticEvent<any>) {
        return e.currentTarget.value
    }

    /**
     * Decide which value to use - props.value or props.field.value
     * This distinction is there because some inputs are 'semi-controlled'
     * This happens when setting and updating the value is handled by the client
     * but fields such as dirty, valid, focused etc are handled by cosmo ui
     *
     * NOTE: this function should not be typed because in the case of some inputs
     * such as the number input, it is saved as a number in the state but it is
     * inputted as a string
     */
    public getValueFromState(): any {
        const { value, onChange, field } = this.props
        // if both a value and an onChange handler are provided
        // then the client wishes to control the field manually
        // otherwise assume the version provided by cosmo-ui formReducer is in play
        return value && onChange ? value : field.value
    }

    public shouldShowErrors() {
        const { valid, dirty, submitted, focused } = this.props.field
        // to show errors then the field must be invalid and either submitted or
        // dirty and not focused (if it's focused the user is changing stuff)
        return !valid && (submitted || (dirty && !focused))
    }

    public shouldShowValid() {
        const { valid, dirty, submitted, focused, required, errors } = this.props.field
        // obviously it becoes visible if its dirty or submitted
        // but if the field isn't required then we won't show any colour
        // at all whilst it's still pristine (hence the required part here)
        const visible = (required || dirty || submitted)
        return !this.props.disabled && visible && valid && !errors.length
    }

    public classNames(...args: string[]): string {
        const { valid, errors, focused, required } = this.props.field
        const res = cx(
            styles.field,
            ...args,
            this.props.className,
            {
                [styles.disabled]: this.props.disabled === true,
                [borderStyles.error]: !this.props.disabled && this.shouldShowErrors(),
                [borderStyles.success]:  this.shouldShowValid() ,
                [borderStyles.info]: !this.props.disabled && focused && !valid,
            },
        )
        // console.log('classnames', this.props, res)
        return res

    }
}
