'use strict';

import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import {FormErrorAutoFocusContainer} from './FormErrorAutoFocusContainer';
import {HashMap} from '../../typings/common';

type InputConverter = () => any;
type OutputConverter = () => any;

type Props = {
    onChange?: Function;
    name?: string;
    inputConverter: InputConverter;
    outputConverter: OutputConverter;
    noContainer?: boolean;
    testAutomationId?: string;
    children: React.ReactNode;
    disableAutoFocus?: boolean;
    className?: string;
} & Partial<DefaultProps> &
    Partial<FormContext['form']>;

type DefaultProps = {
    // onChange: null,
    name: string;
    inputConverter: null;
    outputConverter: null;
    testAutomationId: string;
    noContainer: boolean;
    disableAutoFocus: boolean;
};

type FormValue = HashMap<any>;

export type FormContext = {
    form: {
        readOnly: boolean;
        value: FormValue;
        errors: HashMap<string | undefined>;
        setValue: Function;
        autoFocus?: Function;
    };
};

export class Form extends React.Component<Props>
    implements React.ChildContextProvider<FormContext> {
    static childContextTypes = {
        form: PropTypes.object
    };

    static contextTypes = {
        form: PropTypes.object
    };

    static propTypes = {
        onChange: PropTypes.func,
        name: PropTypes.string,
        inputConverter: PropTypes.func,
        outputConverter: PropTypes.func,
        noContainer: PropTypes.bool,
        testAutomationId: PropTypes.string,
        children: PropTypes.node,
        className: PropTypes.string
    };

    static defaultProps = {
        onChange: null,
        name: '',
        inputConverter: null,
        outputConverter: null,
        children: null,
        className: null,
        testAutomationId: 'form',
        noContainer: false
    };

    formErrorAutoFocusContainer: any;

    constructor(props, context) {
        super(props, context);
        this.setValue = this.setOutputValue.bind(this);
    }

    setValue(value: FormValue, diff: Partial<FormValue>): void {}

    getChildContext(): FormContext {
        return {
            form: {
                readOnly: this.extractFlatProperty('readOnly'),
                value: this.getInputValue(),
                errors: this.extractProperty('errors'),
                setValue: this.setValue
            }
        } as any;
    }

    getInputValue() {
        return this.inputConverter(this.extractProperty('value'));
    }

    getNeededAutoFocus: FormErrorAutoFocusContainer['getNeededAutoFocus'] = () => {
        return this.formErrorAutoFocusContainer.getNeededAutoFocus();
    };

    setOutputValue(value: FormValue, diff: Partial<FormValue>): void {
        value = this.outputConverter(
            {...this.getInputValue(), ...value},
            diff === undefined ? value : diff
        );
        if (diff === undefined) {
            diff = value;
        }

        if (this.props.onChange) {
            this.props.onChange(value, diff);
        } else if (this.props.name && this.context.form) {
            this.context.form.setValue({[this.props.name]: value}, {[this.props.name]: diff});
        }
    }

    setNeededAutoFocus: FormErrorAutoFocusContainer['setNeededAutoFocus'] = value => {
        this.formErrorAutoFocusContainer.setNeededAutoFocus(value);
    };

    inputConverter(value: FormValue): FormValue | ReturnType<InputConverter> {
        return this.props.inputConverter
            ? (this.props.inputConverter as any).call(this, value)
            : value;
    }

    outputConverter(
        value: FormValue,
        diff: Partial<FormValue>
    ): FormValue | ReturnType<OutputConverter> {
        return this.props.outputConverter
            ? (this.props.outputConverter as any).call(this, value, diff)
            : value;
    }

    extractProperty(propName: string, props: Props = this.props): any {
        let value = props[propName];
        let form = this.context.form;

        if (form && value === undefined) {
            value = form[propName][props.name as any];
        }

        return value || {};
    }

    extractFlatProperty(propName) {
        let value = this.props[propName];
        let form = this.context.form;

        if (form && value === undefined) {
            value = form[propName];
        }

        return value;
    }

    render() {
        return (
            <FormErrorAutoFocusContainer ref={el => (this.formErrorAutoFocusContainer = el)}>
                {this.props.noContainer ? (
                    this.props.children
                ) : (
                    <div
                        data-test-automation-id={this.props.testAutomationId}
                        className={classnames('rc-form', this.props.className)}>
                        {this.props.children}
                    </div>
                )}
            </FormErrorAutoFocusContainer>
        );
    }
}
