// @flow
import { ImageProvider } from '@siteone/react-img-microservice'
import React from 'react'
import { EventsProvider } from '../EventsContext'
import type {
  Directory,
  FileCategory,
  Media,
  SearchParameters,
  StructureItem,
} from '../types'
import MediaLibrarySearch from './MediaLibrarySearch'

type Props = {
  filesCategories: Array<FileCategory>,
  onUpdateImage: Function,
  onUpload: Function,
  onRemoveFile: Function,
  onCreateFolder: Function,
  onRemoveFolder: Function,
  onLoadFolderList: Function,
  getPage: Function,
  onCancel: Function,
  onConfirm: Function,
  uploadUrl: string,
  uploadExtraData: any, // some object with keys and values, usually s1token etc
  imageProvider: ImageProvider,
  directoryList: Array<Directory>,
  config: {
    search: string,
    multiselect: boolean,
    limit: number,
  },
  withControls: boolean,
  withFolders: boolean,
  minSizeInfo: string,
}

type State = {
  searchParameters: SearchParameters,
  media: Array<Media>,
  page: number,
  count: number,
  selectedMedia: Array<Media>,
  selectedMediaItem: boolean,
  isLoading: boolean,
  hasNext: boolean,
  orderBy: string,
  selectedCat: FileCategory,
}

class MediaLibrarySearchContainer extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props)
    this.state = {
      searchParameters: {},
      media: [],
      page: 0,
      count: 0,
      selectedMedia: [],
      selectedMediaItem: false,
      isLoading: true,
      hasNext: false,
      orderBy: 'DATETIME_DESC',
      selectedCat: props.filesCategories[0] || {},
    }
  }

  /**
   * reset pagination of media list
   * @param {string} category
   * @param {string} orderBy
   * reset all media and - you can keep category and orderBy or set new category and orderBy
   */
  reset = (
    category: string = this.state.selectedCat,
    orderBy: string = this.state.orderBy,
    folder: any = this.state.selectedFolder,
  ) => {
    this.setState({
      searchParameters: {},
      media: [],
      page: 0,
      count: 0,
      selectedMediaItem: false,
      selectedMedia: [],
      selectedCat: category,
      orderBy,
      isLoading: true,
    })
    return this.loadNextPage(category, 0, [], {}, orderBy, folder)
  }

  /**
   * @param {object} url
   * remove media from list, find by URL of media
   * @note - it assumes, that each media has uniq url
   */
  removeMedia = ({ url }: Media) => {
    this.setState({ media: this.state.media.filter(m => m.props.url !== url) })
  }

  /**
   * @param {string} url uniq identificator of media
   * @param {string} key key of new value
   * @param {string} newValue new value of media
   * @param {object} media object with information about media
   * @return {object} same object if url not equals, otherwise update image
   */
  updateMediaByUrl = (url, key, newValue) => m =>
    m.props.url === url
      ? {
        ...m,
        props: { ...m.props, [key]: newValue },
        details: {
          ...m.details,
          [key]: newValue,
        },
        selected: true,
      }
      : m

  /**
   * @param {string} url
   * @param {string} alt
   * @note - it assumes, that each media has uniq url
   * rename alt props of media from list, find by URL of media
   */
  renameFile = ({ newName, url }) => {
    const updateFunction = this.updateMediaByUrl(url, 'alt', newName)
    const newMediaList = this.state.media.map(updateFunction)
    const neMediaItem = newMediaList.find(m => m.props.url === url)

    // update image in state
    this.setState({
      media: newMediaList,
      selectedMediaItem: neMediaItem,
    })

    // update image on API
    if (this.props.onUpdateImage) {
      return this.props.onUpdateImage(url, neMediaItem)
    }

    console.warn(
      'Can`t update image in API, function onUpdateImage is not defined',
    )
    return new Promise(reject => reject())
  }

  /**
   * @param {string} url
   * @param {string} tags
   * @note - it assumes, that each media has uniq url
   * rename alt props of media from list, find by URL of media
   */
  updateTags = ({ tags, url }) => {
    const updateFunction = this.updateMediaByUrl(url, 'tags', tags)
    const newMediaList = this.state.media.map(updateFunction)
    const neMediaItem = newMediaList.find(m => m.props.url === url)
    this.setState({
      media: newMediaList,
      selectedMediaItem: neMediaItem,
    })

    this.props.onUpdateImage && this.props.onUpdateImage(url, neMediaItem)
  }

  setSelectedMediaItem = (selectedMediaItem: Object | false) => {
    this.setState({
      selectedMediaItem:
        this.state.selectedMediaItem !== selectedMediaItem
          ? selectedMediaItem
          : false,
    })
  }

  toggleSelectedMediaItem = (mediaItem: Object) => {
    const { selectedMedia } = this.state
    const {
      config: { multiselect, limit },
    } = this.props
    if (!multiselect && !this.isSelected(mediaItem)) {
      this.setState({
        selectedMedia: [mediaItem],
      })
    } else if (this.isSelected(mediaItem)) {
      this.setState({
        selectedMedia: selectedMedia.filter(
          selectedMediaItem => selectedMediaItem.key !== mediaItem.key,
        ),
      })
    } else if (selectedMedia.length < limit) {
      this.setState({
        selectedMedia: [...selectedMedia, mediaItem],
      })
    }
  }

  isSelected(mediaItem: Object) {
    const { selectedMedia } = this.state
    return selectedMedia.includes(mediaItem)
  }

  handleSerachParametersChange = (searchParameters: Object) => {
    // Unset selected if search parameters change
    this.setSelectedMediaItem(false)
    this.setSearchParameters(searchParameters)
  }

  addHandlers = (media: Array<Object>) =>
    media.map(item => ({
      ...item,
      onClick: () => {
        this.toggleSelectedMediaItem(item)
        this.setSelectedMediaItem(item)
      },
      selected: this.isSelected(item),
    }))

  /**
   * @param {FileCategory} category
   * @param {number} page
   * @param {array} media
   * @param {object} searchParameters
   * @param {string} orderBy
   */
  loadNextPage = (category, page, media = [], searchParameters, orderBy, folder) => {
    const {
      getPage,
      config: { limit },
    } = this.props
    console.log(
      'load next page | ',
      `page: ` + page,
      `limit: ` + limit,
      `searchParameters: ` + JSON.stringify(searchParameters),
      `category: ` + category.aplCode,
      `orderBy: ` + orderBy,
      `folder: `, folder,
    )

    return getPage({ page, limit, searchParameters, category: category.aplCode, orderBy, folderId: folder ? folder.id : null },
      (err, data) => {
        this.setState({ isLoading: false })
        if (err) {
          console.log(err)
          return
        }
        const dataMedia = data.media || []
        this.setState({ hasNext: data.hasNext })
        if (dataMedia.length > 0) {
          this.setState({
            media: [...media, ...dataMedia],
            page: page + 1,
            count: data.count,
          })
        }
      },
    )
  }

  loadMore = () => {
    this.loadNextPage(
      this.state.selectedCat,
      this.state.page,
      this.state.media,
      this.state.searchParameters,
      this.state.orderBy,
    )
  }

  setSearchParameters = (searchParameters: Object) => {
    // load first page instantly when searchParameters changes
    const {
      getPage,
      config: { limit },
    } = this.props
    getPage(
      {
        page: 0,
        limit,
        searchParameters,
        category: this.state.selectedCat.aplCode,
      },
      (err, data) => {
        if (err) return
        this.setState({
          page: 1,
          media: data.media,
          searchParameters,
          count: data.count,
          hasNext: data.hasNext,
        })
      },
    )
  }

  componentDidMount = () => {
    // load first page on mount
    this.loadNextPage(this.state.selectedCat, 0, [], {}, this.state.orderBy)
  }

  handleConfirm = () => {
    const { selectedMedia } = this.state
    const {
      onConfirm,
      config: { multiselect },
    } = this.props
    if (onConfirm) {
      if (multiselect) {
        const returnValues = selectedMedia.map(
          mediaItem => mediaItem.returnValue,
        )
        onConfirm(returnValues, selectedMedia)
      } else {
        onConfirm(selectedMedia[0].returnValue, selectedMedia[0])
      }
    } else {
      console.log('ERROR: no onConfirm in props')
    }
  }

  handleCancel = () => {
    const { onCancel } = this.props
    if (onCancel) onCancel()
  }

  setCat = selectedCat => {
    this.reset(selectedCat)
  }

  onSortChange = e => {
    this.reset(this.state.selectedCat, e.target.value)
  }

  onSelectFolder = (selectedFolder: StructureItem): Promise<any> => {
    this.setState({
      selectedFolder,
    })
    return this.reset(this.state.selectedCat, undefined, selectedFolder)
  }

  render = () => {
    const { orderBy } = this.state
    const { minSizeInfo, config, filesCategories, accept } = this.props

    // this events are passed to childs by context
    const events = {
      // upload file API, used after file is upload to uploadUrl
      onUpdateImage: this.props.onUpdateImage,
      // upload file API, used after file is upload to uploadUrl
      onUpload: this.props.onUpload,
      // add extra atributes to upload function
      uploadExtraData: this.props.uploadExtraData,
      // url for file upload, on this url is made XHTTP req UploadDialog/helpers.js
      uploadUrl: this.props.uploadUrl,
      // remove file API
      onRemoveFile: this.props.onRemoveFile,
      // rename file name API
      onRenameFile: this.renameFile,
      // create folder
      onCreateFolder: this.props.onCreateFolder,
      // remove folder
      onRemoveFolder: this.props.onRemoveFolder,
      // select folder and load images inside folder
      onSelectFolder: this.onSelectFolder,
      // load a new folder list
      onLoadFolderList: this.props.onLoadFolderList,
      // update tags of file
      onUpdateTags: this.updateTags,
      // remove file list local
      removeMedia: this.removeMedia,
      // on user select media
      handleConfirm: this.handleConfirm,
      // on user cancel selection
      handleCancel: this.handleCancel,
      // on user change search params
      handleSerachParametersChange: this.handleSerachParametersChange,
      // on sort change
      onSortChange: this.onSortChange,
      // on select media item from list
      setSelectedMediaItem: this.setSelectedMediaItem,
      // reset list of media (after upload new files)
      reset: this.reset,
      // use Image Provider for resizing image
      imageProvider: this.props.imageProvider,
      // directory list from API
      directoryList: this.props.directoryList,
      // select category
      setCat: this.setCat,
      // list of categories (usually loaded from API)
      filesCategories,
      // kind of sort of media list
      orderBy,
      // selected category
      selectedCat: this.state.selectedCat,
      // selected folder
      selectedFolder: this.state.selectedFolder,
      // list of file formats to accept
      accept,
    }

    return (
      <ImageProvider
        microserviceUrl={this.props.imageProvider.url}
        baseUrl={this.props.imageProvider.baseUrl}
      >
        <EventsProvider events={events}>
          <MediaLibrarySearch
            media={this.addHandlers(this.state.media)}
            count={this.state.count}
            hasNext={this.state.hasNext}
            hasSelected={this.state.selectedMedia.length > 0}
            loadMore={this.loadMore}
            minSizeInfo={minSizeInfo}
            selectedMediaItem={this.state.selectedMediaItem}
            serchConfig={config.search}
            multiselect={this.props.multiselect}
            isLoading={this.state.isLoading}
            withControls={this.props.withControls}
            withFolders={this.props.withFolders}
            isVideo={this.props.isVideo}
          />
        </EventsProvider>
      </ImageProvider>
    )
  }
}

MediaLibrarySearchContainer.defaultProps = {
  minSizeInfo: '',
  withControls: true,
  withFolders: false,
  config: {
    search: [],
    multiselect: false,
    limit: 30,
  },
  imageProvider: {},
  filesCategories: [
    {
      name: 'Všechny soubory',
      aplCode: 'all',
      isEditable: false,
      isSelected: true,
    },
  ],
}

export default MediaLibrarySearchContainer
