import React from 'react';
import PropTypes from 'prop-types';
import uuid from 'uuid/v5';
/*
  uuid v5 creates a consistent uuid for the same string with the same namespace

  ('tree', SPACE_1) => 'qwe'
  ('t r e e'.replace(/\s+/g, ''), SPACE_1) => 'qwe'
  ('tree', SPACE_2) => 'cxv'

  https://www.npmjs.com/package/uuid#version-5
*/
const BASE = '90123e1c-7512-523e-bb28-76fab9f2f73d';
function key(arr = []) {
  return uuid(arr.join('<(^o^)>'), BASE);
}

const displayErrorTime = 3000;

export default class PageProvider extends React.Component {
  static propTypes = {
    children: PropTypes.func.isRequired,
    defaultErrorMessage: PropTypes.string,
    initialData: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
    initialPath: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
    initialDisplay: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
    tierChildren: PropTypes.arrayOf(PropTypes.element).isRequired,
    root: PropTypes.string.isRequired
  };

  static defaultProps = {
    defaultErrorMessage: '',
    initialPath: [],
    initialDisplay: []
  };

  constructor(props) {
    super(props);

    const startPath = [props.root, ...props.initialPath];
    this.state = {
      idPath: startPath,
      displayPath: [props.root, ...props.initialDisplay],
      loading: false,
      pages: {
        [key(startPath)]: props.initialData
      }
    };
  }

  back = (howMany = 1, onComplete) => {
    const { idPath, displayPath, pages } = this.state;

    const sliceIndex = this.state.idPath.length - howMany;
    const nextPath = this.state.idPath.slice(0, sliceIndex);

    if (!pages[key(nextPath)]) {
      this.setState({ loading: true });
      this.props.tierChildren[sliceIndex - 1].props.onGet(
        nextPath[nextPath.length - 1],
        this.update(nextPath, this.state.displayPath.slice(0, sliceIndex), () => {
          this.setState({ loading: false }, onComplete);
        }),
        this.navigationDidError
      );
    } else {
      this.setState({
        idPath: idPath.slice(0, sliceIndex),
        displayPath: displayPath.slice(0, sliceIndex)
      }, onComplete);
    }
  }

  advance = (option, onComplete) => {
    const { idPath, displayPath, pages } = this.state;
    const tierProps = this.props.tierChildren[idPath.length - 1].props;
    const nextPath = idPath.concat(option[tierProps.id]);

    if (!pages[key(nextPath)]) {
      this.setState({ loading: true });
      tierProps.onDrilldown(
        option,
        this.push(option[tierProps.id], option[tierProps.label], () => {
          this.setState({ loading: false }, onComplete);
        }),
        this.navigationDidError
      );
    } else {
      this.setState({
        idPath: nextPath,
        displayPath: displayPath.concat(option[tierProps.label])
      }, onComplete);
    }
  }

  push = (next, nextDisplay, onComplete) => (data) => {
    const nextPath = this.state.idPath.concat(next);

    this.setState({
      idPath: nextPath,
      displayPath: this.state.displayPath.concat(nextDisplay),
      pages: Object.assign({}, this.state.pages, { [key(nextPath)]: data })
    }, onComplete);
  }

  update = (next, nextDisplay, onComplete) => (data) => {
    this.setState({
      idPath: next,
      displayPath: nextDisplay,
      pages: Object.assign({}, this.state.pages, { [key(next)]: data })
    }, onComplete);
  }

  navigationDidError = (errorMessage = this.props.defaultErrorMessage) => {
    this.setState({ errorMessage, loading: false });
    window.setTimeout(() => { this.setState({ errorMessage: null }); }, displayErrorTime);
  }

  render() {
    const { children, tierChildren } = this.props;
    const { idPath, displayPath, errorMessage, pages, loading } = this.state;

    return children(tierChildren[idPath.length - 1].props, {
      displayPath,
      data: pages[key(idPath)],
      didError: !!errorMessage,
      errorMessage,
      loading
    }, {
      back: this.back,
      advance: this.advance
    });
  }
}
