// @flow
import * as React from 'react';
import {
	Container,
	Row,
	Col,
	Button,
	Form,
	FormGroup,
	Label,
	Input,
	InputGroup,
	FormText,
	Collapse,
	Card,
	CardHeader,
	CardBody,
	TabContent, TabPane, Nav, NavItem, NavLink, CardTitle, CardText,
	FormFeedback,
	Alert,
	InputGroupAddon,
	InputGroupText,
} from 'reactstrap';

import { connect } from 'react-redux';

import * as Selectors from '@store/selectors.js';

import ValidSchemaBadge from '../ValidSchemaBadge.jsx';
import CacheInput from '../CacheInput.jsx';
import DeleteInputBtn from '../DeleteInputBtn.jsx';
import TypedField from '../TypedField.jsx';

import * as Actions from '@store/actions.js';
import { BUILTIN_TYPES, getValidDataformatObj as getValidObj } from '@helpers/beat';
import type {
	BeatObject,
} from '@helpers/beat.js';

import { changeObjFieldName, generateNewKey, copyObj } from '@helpers';

type Props = {
	data: BeatObject,
	dataformats: BeatObject[],
	saveFunc: (BeatObject) => any,
	updateFunc: (BeatObject) => any,
};

const isObj = (obj): boolean => !Array.isArray(obj) && typeof obj === 'object' && obj !== null;

const shownType = (obj: any): string => {
	if(Array.isArray(obj))
		return 'array';
	if(isObj(obj))
		return 'object';
	return obj;
};

const RecursiveObj = ({ obj, dfs, updateFunc }: {obj: any, dfs: string[], updateFunc: (any) => any}) => (
	<div className='ml-3 pl-3 border border-top-0 border-right-0 border-bottom-0 my-1 dfLevel'>
		{
			(Object.entries(obj): [string, any][]).map(([name, val], i, entries) =>
				<div key={i} className={`field${ i }`}>
					<TypedField
						className='fieldNameType'
						name={name}
						type={shownType(val)}
						types={dfs}
						existingFields={Object.keys(obj)}
						nameUpdateFunc={str => {
							const newObj = changeObjFieldName(obj, name, str);
							updateFunc(newObj);
						}}
						typeUpdateFunc={str => {
							let newTypeObj;
							switch(str){
								case 'array':
									newTypeObj = [0, 'string'];
									break;
								case 'object':
									newTypeObj = {};
									break;
								default:
									newTypeObj = str;
									break;
							}

							updateFunc({ ...obj, [name]: newTypeObj });
						}}
						deleteFunc={() => {
							const newObj = { ...obj };
							delete newObj[name];
							updateFunc(newObj);
						}}
					/>
					{
						Array.isArray(val) &&
							<FormGroup className='ml-3 pl-3 border border-top-0 border-right-0 border-bottom-0'>
								<FormGroup row>
									<Col sm='3'>
										<InputGroup className='mb-1 dimensions'>
											<InputGroupAddon addonType='prepend'>Dimensions</InputGroupAddon>
											<Input
												type='number'
												min='1'
												max='5'
												value={val.filter(v => Number.isInteger(v)).length}
												onChange={(e) => {
													const newLength = Number.parseInt(`${ e.target.value }`);
													const currVals = val.filter(v => Number.isInteger(v));
													const newVals = Array.apply(null, Array(newLength))
													.map((n, i) => currVals[i] !== undefined ? currVals[i] : 0);
													newVals.push(val[val.length - 1]);
													obj[name] = newVals;
													updateFunc(obj);
												}}
											/>
										</InputGroup>
									</Col>
									<Col sm='auto' className='d-flex my-auto p-0'>
										<h5>Sizes:</h5>
									</Col>
									{
										val.filter(v => Number.isInteger(v)).map((v, i) =>
											<Col key={i} className={`dimension${ i }`}>
												<InputGroup className='mb-1'>
													<InputGroupAddon addonType='prepend'>
														<InputGroupText>
															Dim. {i}
														</InputGroupText>
													</InputGroupAddon>
													<Input
														key={i}
														type='number'
														min='0'
														value={v}
														size='3'
														onChange={(e) => {
															obj[name][i] = Number.parseInt(`${ e.target.value }`);
															updateFunc(obj);
														}}
													/>
												</InputGroup>
											</Col>
										)
									}
								</FormGroup>
								<InputGroup className='mb-3'>
									<InputGroupAddon addonType='prepend'>Subtype</InputGroupAddon>
									<Input
										type='select'
										value={typeof val[val.length - 1] === 'string' ? val[val.length - 1] : 'object'}
										className='custom-select subtype'
										onChange={(e) => {
											let newTypeObj;
											switch(e.target.value){
												case 'array':
													newTypeObj = [0, 'string'];
													break;
												case 'object':
													newTypeObj = {};
													break;
												default:
													newTypeObj = e.target.value;
													break;
											}

											obj[name][val.length - 1] = newTypeObj;
											updateFunc(obj);
										}}
									>
										<option disabled value=''>Type...</option>
										{
											dfs.map((d, i) => (
												<option key={i} value={d}>{d === 'object' ? 'dict' : d}</option>
											))
										}
									</Input>
								</InputGroup>
								{
									shownType(val[val.length - 1]) === 'object' &&
										<RecursiveObj
											dfs={dfs}
											obj={val[val.length - 1]}
											updateFunc={(subobj) => {
												obj[name][val.length - 1] = subobj;
												updateFunc(obj);
											}}
										/>
								}

							</FormGroup>
					}
					{
						isObj(val) &&
							<RecursiveObj
								dfs={dfs}
								obj={val}
								updateFunc={(subobj) => {
									obj[name] = subobj;
									updateFunc(obj);
								}}
							/>
					}
				</div>
			)
		}
		<Button
			color='success'
			onClick={e => {
				let newKey = generateNewKey('field', Object.keys(obj));

				updateFunc({
					...obj,
					[newKey]: 'string'
				});
			}}
		>
			+
		</Button>
	</div>
);

export class DataformatEditor extends React.Component<Props> {
	constructor(props: Props) {
		super(props);
	}

	setContents = (newContents: any) => {
		this.props.updateFunc({
			...this.props.data,
			contents: {
				'#description': this.props.data.contents['#description'],
				...newContents,
			}
		});
	}

	allDfs = () => this.props.dataformats.map(d => d.name).concat(BUILTIN_TYPES);

	filteredContents = (obj: any = this.props.data.contents) => Object.entries(obj).filter(([n, v]) => n !== '#description').reduce((o, [n, v]) => ({...o, [n]: v}), {});

	render = () => (
		<div>
			<div className='d-flex'>
				<Button
					className='mx-auto'
					outline
					color='secondary'
					onClick={() => this.props.saveFunc(this.props.data)}
				>
					Save Changes (Changes are <ValidSchemaBadge entity='dataformat' obj={this.props.data} />)
				</Button>
			</div>
			<Form onSubmit={(e) => e.preventDefault()}>
				<FormGroup tag='fieldset'>
					<FormGroup>
						<Label for='description'>Short Description</Label>
						<Input
							type='text'
							name='description'
							placeholder='Dataformat description...'
							value={this.props.data.contents['#description']}
							onChange={e => this.setContents({ ...this.props.data.contents, '#description': e.target.value})}
						/>
					</FormGroup>
				</FormGroup>
				<h4>Fields</h4>
				<RecursiveObj obj={this.filteredContents()} dfs={this.allDfs()} updateFunc={(obj) => this.setContents(obj)} />
			</Form>
		</div>
	);
}

const mapStateToProps = (state, ownProps) => {
	const dfs = Selectors.dataformatGet(state);
	const obj = {
		dataformats: dfs,
		data: dfs[ownProps.index] || getValidObj()
	};
	return obj;
};

const mapDispatchToProps = (dispatch, ownProps) => ({
	// replace the obj in the Redux store with the new object
	updateFunc: (obj) => {
		console.log(`dispatching for ${ obj.name }`);
		dispatch(Actions[`dataformatUpdate`](obj.name, obj));
	},
});

export default connect(mapStateToProps, mapDispatchToProps)(DataformatEditor);
