define(['base', 'main/project-list/services/project-list'], function(App) {
  App.controller('ProjectPageController', function(
    $scope,
    $modal,
    $window,
    queuedHttp,
    eventTracking, // eslint-disable-line camelcase
    $timeout,
    localStorage,
    ProjectListService
  ) {
    $scope.projects = window.data.projects
    $scope.tags = window.data.tags
    $scope.notifications = window.data.notifications
    $scope.notificationsInstitution = window.data.notificationsInstitution
    $scope.allSelected = false
    $scope.selectedProjects = []
    $scope.filter = 'all'
    $scope.predicate = 'lastUpdated'
    $scope.nUntagged = 0
    $scope.reverse = true
    $scope.searchText = { value: '' }
    $scope.$watch('predicate', function(newValue) {
      $scope.comparator =
        newValue === 'ownerName' ? ownerNameComparator : defaultComparator
    })

    $timeout(() => recalculateProjectListHeight(), 10)

    $scope.$watch(
      () =>
        $scope.projects.filter(
          project =>
            (project.tags == null || project.tags.length === 0) &&
            !project.archived
        ).length,
      newVal => ($scope.nUntagged = newVal)
    )

    var recalculateProjectListHeight = function() {
      const $projListCard = $('.project-list-card')
      if (!$projListCard || !$projListCard.offset()) return

      const topOffset = $projListCard.offset().top
      const cardPadding = $projListCard.outerHeight() - $projListCard.height()
      const bottomOffset = $('footer').outerHeight()
      const height =
        $window.innerHeight - topOffset - bottomOffset - cardPadding
      $scope.projectListHeight = height
    }

    function defaultComparator(v1, v2) {
      var result = 0
      var type1 = v1.type
      var type2 = v2.type

      if ($scope.predicate === 'ownerName') {
        return
      }

      if (type1 === type2) {
        var value1 = v1.value
        var value2 = v2.value

        if (type1 === 'string') {
          // Compare strings case-insensitively
          value1 = value1.toLowerCase()
          value2 = value2.toLowerCase()
        } else if (type1 === 'object') {
          // For basic objects, use the position of the object
          // in the collection instead of the value
          if (angular.isObject(value1)) value1 = v1.index
          if (angular.isObject(value2)) value2 = v2.index
        }

        if (value1 !== value2) {
          result = value1 < value2 ? -1 : 1
        }
      } else {
        result = type1 < type2 ? -1 : 1
      }

      return result
    }

    function ownerNameComparator(v1, v2) {
      if ($scope.predicate !== 'ownerName') {
        return
      }
      if (v1.value === 'You') {
        if (v2.value === 'You') {
          return v1.index < v2.index ? -1 : 1
        } else {
          return 1
        }
      } else if (v1.value === 'An Overleaf v1 User' || v1.value === 'None') {
        if (v2.value === 'An Overleaf v1 User' || v2.value === 'None') {
          return v1.index < v2.index ? -1 : 1
        } else {
          return -1
        }
      } else {
        if (v2.value === 'You') {
          return -1
        } else if (v2.value === 'An Overleaf v1 User' || v2.value === 'None') {
          return 1
        } else {
          return v1.value > v2.value ? -1 : 1
        }
      }
    }

    angular.element($window).bind('resize', function() {
      recalculateProjectListHeight()
      $scope.$apply()
    })

    $scope.$on('project-list:notifications-received', () =>
      $scope.$applyAsync(() => recalculateProjectListHeight())
    )

    // Allow tags to be accessed on projects as well
    const projectsById = {}
    for (let project of $scope.projects) {
      projectsById[project.id] = project
    }

    $scope.getProjectById = id => projectsById[id]

    for (let tag of $scope.tags) {
      for (let projectId of tag.project_ids || []) {
        let project = projectsById[projectId]
        if (project) {
          if (!project.tags) {
            project.tags = []
          }
          project.tags.push(tag)
        }
      }
    }

    $scope.changePredicate = function(newPredicate) {
      if ($scope.predicate === newPredicate) {
        $scope.reverse = !$scope.reverse
      }
      $scope.predicate = newPredicate
    }

    $scope.getSortIconClass = function(column) {
      if (column === $scope.predicate && $scope.reverse) {
        return 'fa-caret-down'
      } else if (column === $scope.predicate && !$scope.reverse) {
        return 'fa-caret-up'
      } else {
        return ''
      }
    }

    $scope.searchProjects = function() {
      eventTracking.send(
        'project-list-page-interaction',
        'project-search',
        'keydown'
      )
      $scope.updateVisibleProjects()
    }

    $scope.clearSearchText = function() {
      $scope.searchText.value = ''
      $scope.filter = 'all'
      $scope.$emit('search:clear')
      $scope.updateVisibleProjects()
    }

    $scope.setFilter = function(filter) {
      $scope.filter = filter
      $scope.updateVisibleProjects()
    }

    $scope.updateSelectedProjects = function() {
      $scope.selectedProjects = $scope.projects.filter(
        project => project.selected
      )
    }

    $scope.getSelectedProjects = () => $scope.selectedProjects

    $scope.getSelectedProjectIds = () =>
      $scope.selectedProjects.map(project => project.id)

    $scope.getFirstSelectedProject = () => $scope.selectedProjects[0]

    $scope.hasLeavableProjectsSelected = () =>
      _.some(
        $scope.getSelectedProjects(),
        project => project.accessLevel !== 'owner' && project.trashed
      )

    $scope.hasDeletableProjectsSelected = () =>
      _.some(
        $scope.getSelectedProjects(),
        project => project.accessLevel === 'owner' && project.trashed
      )

    $scope.updateVisibleProjects = function() {
      $scope.visibleProjects = []
      const selectedTag = $scope.getSelectedTag()
      for (let project of $scope.projects) {
        let visible = true
        // Only show if it matches any search text
        if ($scope.searchText.value !== '') {
          if (
            project.name
              .toLowerCase()
              .indexOf($scope.searchText.value.toLowerCase()) === -1
          ) {
            visible = false
          }
        }
        // Only show if it matches the selected tag
        if (
          $scope.filter === 'tag' &&
          selectedTag != null &&
          !selectedTag.project_ids.includes(project.id)
        ) {
          visible = false
        }

        // Hide tagged projects if we only want to see the uncategorized ones
        if (
          $scope.filter === 'untagged' &&
          (project.tags != null ? project.tags.length : undefined) > 0
        ) {
          visible = false
        }

        // Hide projects we own if we only want to see shared projects
        if ($scope.filter === 'shared' && project.accessLevel === 'owner') {
          visible = false
        }

        // Hide projects from V1 if we only want to see shared projects
        if ($scope.filter === 'shared' && project.isV1Project) {
          visible = false
        }

        // Hide projects we don't own if we only want to see owned projects
        if ($scope.filter === 'owned' && project.accessLevel !== 'owner') {
          visible = false
        }

        if ($scope.filter === 'archived') {
          // Only show archived projects
          if (!project.archived) {
            visible = false
          }
        } else {
          // Only show non-archived projects
          if (project.archived) {
            visible = false
          }
        }

        if ($scope.filter === 'trashed') {
          // Only show trashed projects
          if (!project.trashed) {
            visible = false
          }
        } else {
          // Only show non-trashed projects
          if (project.trashed) {
            visible = false
          }
        }

        if ($scope.filter === 'v1' && !project.isV1Project) {
          visible = false
        }

        if (visible) {
          $scope.visibleProjects.push(project)
        } else {
          // We don't want hidden selections
          project.selected = false
        }
      }

      localStorage(
        'project_list',
        JSON.stringify({
          filter: $scope.filter,
          selectedTagId: selectedTag != null ? selectedTag._id : undefined
        })
      )
      $scope.updateSelectedProjects()
    }

    $scope.getSelectedTag = function() {
      for (let tag of $scope.tags) {
        if (tag.selected) {
          return tag
        }
      }
      return null
    }

    $scope._removeProjectIdsFromTagArray = function(tag, removeProjectIds) {
      // Remove project_id from tag.project_ids
      const remainingProjectIds = []
      const removedProjectIds = []
      for (let projectId of tag.project_ids) {
        if (!removeProjectIds.includes(projectId)) {
          remainingProjectIds.push(projectId)
        } else {
          removedProjectIds.push(projectId)
        }
      }
      tag.project_ids = remainingProjectIds
      return removedProjectIds
    }

    $scope._removeProjectFromList = function(project) {
      const index = $scope.projects.indexOf(project)
      if (index > -1) {
        $scope.projects.splice(index, 1)
      }
    }

    $scope.removeSelectedProjectsFromTag = function(tag) {
      tag.showWhenEmpty = true

      const selectedProjectIds = $scope.getSelectedProjectIds()
      const selectedProjects = $scope.getSelectedProjects()

      const removedProjectIds = $scope._removeProjectIdsFromTagArray(
        tag,
        selectedProjectIds
      )

      // Remove tag from project.tags
      for (let project of selectedProjects) {
        if (!project.tags) {
          project.tags = []
        }
        const index = project.tags.indexOf(tag)
        if (index > -1) {
          project.tags.splice(index, 1)
        }
      }

      for (let projectId of removedProjectIds) {
        queuedHttp({
          method: 'DELETE',
          url: `/tag/${tag._id}/project/${projectId}`,
          headers: {
            'X-CSRF-Token': window.csrfToken
          }
        })
      }

      // If we're filtering by this tag then we need to remove
      // the projects from view
      $scope.updateVisibleProjects()
    }

    $scope.removeProjectFromTag = function(project, tag) {
      tag.showWhenEmpty = true

      if (!project.tags) {
        project.tags = []
      }
      const index = project.tags.indexOf(tag)

      if (index > -1) {
        $scope._removeProjectIdsFromTagArray(tag, [project.id])
        project.tags.splice(index, 1)
        queuedHttp({
          method: 'DELETE',
          url: `/tag/${tag._id}/project/${project.id}`,
          headers: {
            'X-CSRF-Token': window.csrfToken
          }
        })
        $scope.updateVisibleProjects()
      }
    }

    $scope.addSelectedProjectsToTag = function(tag) {
      const selectedProjects = $scope.getSelectedProjects()
      eventTracking.send(
        'project-list-page-interaction',
        'project action',
        'addSelectedProjectsToTag'
      )

      // Add project_ids into tag.project_ids
      const addedProjectIds = []
      for (let projectId of $scope.getSelectedProjectIds()) {
        if (!tag.project_ids.includes(projectId)) {
          tag.project_ids.push(projectId)
          addedProjectIds.push(projectId)
        }
      }

      // Add tag into each project.tags
      for (let project of selectedProjects) {
        if (!project.tags) {
          project.tags = []
        }
        if (!project.tags.includes(tag)) {
          project.tags.push(tag)
        }
      }

      for (let projectId of addedProjectIds) {
        queuedHttp.post(`/tag/${tag._id}/project/${projectId}`, {
          _csrf: window.csrfToken
        })
      }
    }

    $scope.openNewTagModal = function(e) {
      const modalInstance = $modal.open({
        templateUrl: 'newTagModalTemplate',
        controller: 'NewTagModalController'
      })

      modalInstance.result.then(function(tag) {
        const tagIsDuplicate = $scope.tags.find(function(existingTag) {
          return tag.name === existingTag.name
        })

        if (!tagIsDuplicate) {
          $scope.tags.push(tag)
          $scope.addSelectedProjectsToTag(tag)
        }
      })
    }

    $scope.createProject = function(name, template) {
      if (template == null) {
        template = 'none'
      }
      return queuedHttp
        .post('/project/new', {
          _csrf: window.csrfToken,
          projectName: name,
          template
        })
        .then(function(response) {
          const { data } = response
          $scope.projects.push({
            name,
            _id: data.project_id,
            accessLevel: 'owner',
            owner: {
              _id: window.user_id
            }
            // TODO: Check access level if correct after adding it in
            // to the rest of the app
          })
          $scope.updateVisibleProjects()
        })
    }

    $scope.openCreateProjectModal = function(template) {
      if (template == null) {
        template = 'none'
      }
      eventTracking.send(
        'project-list-page-interaction',
        'new-project',
        template
      )
      const modalInstance = $modal.open({
        templateUrl: 'newProjectModalTemplate',
        controller: 'NewProjectModalController',
        resolve: {
          template() {
            return template
          }
        },
        scope: $scope
      })

      modalInstance.result.then(
        projectId => (window.location = `/project/${projectId}`)
      )
    }

    $scope.renameProject = (project, newName) =>
      queuedHttp
        .post(`/project/${project.id}/rename`, {
          newProjectName: newName,
          _csrf: window.csrfToken
        })
        .then(() => (project.name = newName))

    $scope.openRenameProjectModal = function() {
      let project = $scope.getFirstSelectedProject()
      if (!project || project.accessLevel !== 'owner') {
        return
      }
      eventTracking.send(
        'project-list-page-interaction',
        'project action',
        'Rename'
      )
      $modal.open({
        templateUrl: 'renameProjectModalTemplate',
        controller: 'RenameProjectModalController',
        resolve: {
          project() {
            return project
          }
        },
        scope: $scope
      })
    }

    $scope.cloneProject = function(project, cloneName) {
      eventTracking.send(
        'project-list-page-interaction',
        'project action',
        'Clone'
      )
      return queuedHttp
        .post(`/project/${project.id}/clone`, {
          _csrf: window.csrfToken,
          projectName: cloneName
        })
        .then(function(response) {
          const { data } = response
          $scope.projects.push({
            name: data.name,
            id: data.project_id,
            accessLevel: 'owner',
            owner: {
              _id: data.owner_ref
            }
            // TODO: Check access level if correct after adding it in
            // to the rest of the app
          })
          $scope.updateVisibleProjects()
        })
    }

    $scope.openCloneProjectModal = function() {
      let project = $scope.getFirstSelectedProject()
      if (!project) {
        return
      }

      $modal.open({
        templateUrl: 'cloneProjectModalTemplate',
        controller: 'CloneProjectModalController',
        resolve: {
          project() {
            return project
          }
        },
        scope: $scope
      })
    }

    // Methods to create modals for archiving, trashing, leaving and deleting projects
    const _createArchiveTrashLeaveOrDeleteProjectsModal = function(
      action,
      projects
    ) {
      eventTracking.send(
        'project-list-page-interaction',
        'project action',
        action
      )
      return $modal.open({
        templateUrl: 'archiveTrashLeaveOrDeleteProjectsModalTemplate',
        controller: 'ArchiveTrashLeaveOrDeleteProjectsModalController',
        resolve: {
          projects() {
            return projects
          },
          action() {
            return action
          }
        }
      })
    }

    $scope.createArchiveProjectsModal = function(projects) {
      return _createArchiveTrashLeaveOrDeleteProjectsModal('archive', projects)
    }

    $scope.createTrashProjectsModal = function(projects) {
      return _createArchiveTrashLeaveOrDeleteProjectsModal('trash', projects)
    }

    $scope.createLeaveProjectsModal = function(projects) {
      return _createArchiveTrashLeaveOrDeleteProjectsModal('leave', projects)
    }

    $scope.createDeleteProjectsModal = function(projects) {
      return _createArchiveTrashLeaveOrDeleteProjectsModal('delete', projects)
    }

    $scope.createLeaveOrDeleteProjectsModal = function(projects) {
      return _createArchiveTrashLeaveOrDeleteProjectsModal(
        'leaveOrDelete',
        projects
      )
    }

    //
    $scope.openArchiveProjectsModal = function() {
      const modalInstance = $scope.createArchiveProjectsModal(
        $scope.getSelectedProjects()
      )
      modalInstance.result.then(() => $scope.archiveSelectedProjects())
    }

    $scope.openTrashProjectsModal = function() {
      const modalInstance = $scope.createTrashProjectsModal(
        $scope.getSelectedProjects()
      )

      modalInstance.result.then(() => $scope.trashSelectedProjects())
    }

    $scope.openLeaveProjectsModal = function() {
      const modalInstance = $scope.createLeaveProjectsModal(
        $scope.getSelectedProjects()
      )
      modalInstance.result.then(() => $scope.leaveSelectedProjects())
    }

    $scope.openDeleteProjectsModal = function() {
      const modalInstance = $scope.createDeleteProjectsModal(
        $scope.getSelectedProjects()
      )
      modalInstance.result.then(() => $scope.deleteSelectedProjects())
    }

    $scope.openLeaveOrDeleteProjectsModal = function() {
      const modalInstance = $scope.createLeaveOrDeleteProjectsModal(
        $scope.getSelectedProjects()
      )
      modalInstance.result.then(() => $scope.leaveOrDeleteSelectedProjects())
    }

    //
    $scope.archiveSelectedProjects = () =>
      $scope.archiveProjects($scope.getSelectedProjects())

    $scope.unarchiveSelectedProjects = () =>
      $scope.unarchiveProjects($scope.getSelectedProjects())

    $scope.trashSelectedProjects = () =>
      $scope.trashProjects($scope.getSelectedProjects())

    $scope.untrashSelectedProjects = () =>
      $scope.untrashProjects($scope.getSelectedProjects())

    $scope.leaveSelectedProjects = () =>
      $scope.leaveProjects($scope.getSelectedProjects())

    $scope.deleteSelectedProjects = () =>
      $scope.deleteProjects($scope.getSelectedProjects())

    $scope.leaveOrDeleteSelectedProjects = () =>
      $scope.leaveOrDeleteProjects($scope.getSelectedProjects())

    //
    $scope.archiveProjects = function(projects) {
      for (let project of projects) {
        project.archived = true
        project.trashed = false
        _archiveProject(project)
      }
      $scope.updateVisibleProjects()
    }

    $scope.unarchiveProjects = function(projects) {
      for (let project of projects) {
        project.archived = false
        _unarchiveProject(project)
      }
      $scope.updateVisibleProjects()
    }

    $scope.trashProjects = function(projects) {
      for (let project of projects) {
        project.trashed = true
        project.archived = false
        _trashProject(project)
      }
      $scope.updateVisibleProjects()
    }

    $scope.untrashProjects = function(projects) {
      for (let project of projects) {
        project.trashed = false
        _untrashProject(project)
      }
      $scope.updateVisibleProjects()
    }

    $scope.leaveProjects = function(projects) {
      _deleteOrLeaveProjectsLocally(projects)
      for (let project of projects) {
        _leaveProject(project)
      }
      $scope.updateVisibleProjects()
    }

    $scope.deleteProjects = function(projects) {
      _deleteOrLeaveProjectsLocally(projects)
      for (let project of projects) {
        _deleteProject(project)
      }
      $scope.updateVisibleProjects()
    }

    $scope.leaveOrDeleteProjects = function(projects) {
      _deleteOrLeaveProjectsLocally(projects)
      for (let project of projects) {
        if (project.accessLevel === 'owner') {
          _deleteProject(project)
        } else {
          _leaveProject(project)
        }
      }
      $scope.updateVisibleProjects()
    }

    // Actual interaction with the backend---we could move this into a service
    const _archiveProject = function(project) {
      return queuedHttp({
        method: 'POST',
        url: `/project/${project.id}/archive`,
        headers: {
          'X-CSRF-Token': window.csrfToken
        }
      })
    }

    const _unarchiveProject = function(project) {
      return queuedHttp({
        method: 'DELETE',
        url: `/project/${project.id}/archive`,
        headers: {
          'X-CSRF-Token': window.csrfToken
        }
      })
    }

    const _trashProject = function(project) {
      return queuedHttp({
        method: 'POST',
        url: `/project/${project.id}/trash`,
        headers: {
          'X-CSRF-Token': window.csrfToken
        }
      })
    }

    const _untrashProject = function(project) {
      return queuedHttp({
        method: 'DELETE',
        url: `/project/${project.id}/trash`,
        headers: {
          'X-CSRF-Token': window.csrfToken
        }
      })
    }

    const _leaveProject = function(project) {
      return queuedHttp({
        method: 'POST',
        url: `/project/${project.id}/leave`,
        headers: {
          'X-CSRF-Token': window.csrfToken
        }
      })
    }

    const _deleteProject = function(project) {
      return queuedHttp({
        method: 'DELETE',
        url: `/project/${project.id}?forever=true`,
        headers: {
          'X-CSRF-Token': window.csrfToken
        }
      })
    }

    const _deleteOrLeaveProjectsLocally = function(projects) {
      const projectIds = projects.map(p => p.id)
      for (let tag of $scope.tags || []) {
        $scope._removeProjectIdsFromTagArray(tag, projectIds)
      }
      for (let project of projects || []) {
        $scope._removeProjectFromList(project)
      }
    }

    $scope.getValueForCurrentPredicate = function(project) {
      if ($scope.predicate === 'ownerName') {
        return ProjectListService.getOwnerName(project)
      } else {
        return project[$scope.predicate]
      }
    }

    $scope.openUploadProjectModal = function() {
      $modal.open({
        templateUrl: 'uploadProjectModalTemplate',
        controller: 'UploadProjectModalController'
      })
    }

    $scope.downloadSelectedProjects = () =>
      $scope.downloadProjectsById($scope.getSelectedProjectIds())

    $scope.downloadProjectsById = function(projectIds) {
      let path
      eventTracking.send(
        'project-list-page-interaction',
        'project action',
        'Download Zip'
      )
      if (projectIds.length > 1) {
        path = `/project/download/zip?project_ids=${projectIds.join(',')}`
      } else {
        path = `/project/${projectIds[0]}/download/zip`
      }
      return (window.location = path)
    }

    const markTagAsSelected = id => {
      for (let tag of $scope.tags) {
        if (tag._id === id) {
          tag.selected = true
        } else {
          tag.selected = false
        }
      }
    }

    const storedUIOpts = JSON.parse(localStorage('project_list'))

    if (storedUIOpts && storedUIOpts.filter) {
      if (storedUIOpts.filter === 'tag' && storedUIOpts.selectedTagId) {
        markTagAsSelected(storedUIOpts.selectedTagId)
      }
      $scope.setFilter(storedUIOpts.filter)
    } else {
      $scope.updateVisibleProjects()
    }
  })

  App.controller('ProjectListItemController', function(
    $scope,
    $modal,
    queuedHttp,
    ProjectListService
  ) {
    $scope.projectLink = function(project) {
      if (
        project.accessLevel === 'readAndWrite' &&
        project.source === 'token'
      ) {
        return `/${project.tokens.readAndWrite}`
      } else if (
        project.accessLevel === 'readOnly' &&
        project.source === 'token'
      ) {
        return `/read/${project.tokens.readOnly}`
      } else {
        return `/project/${project.id}`
      }
    }

    $scope.isLinkSharingProject = project => project.source === 'token'

    $scope.hasGenericOwnerName = () => {
      /* eslint-disable camelcase */
      const { first_name, last_name, email } = $scope.project.owner
      return !first_name && !last_name && !email
      /* eslint-enable camelcase */
    }

    $scope.getOwnerName = ProjectListService.getOwnerName

    $scope.getUserName = ProjectListService.getUserName

    $scope.isOwner = () =>
      $scope.project.owner && window.user_id === $scope.project.owner._id

    $scope.$watch('project.selected', function(value) {
      if (value != null) {
        $scope.updateSelectedProjects()
      }
    })

    $scope.clone = function(e) {
      e.stopPropagation()
      $scope.project.isTableActionInflight = true
      return $scope
        .cloneProject($scope.project, `${$scope.project.name} (Copy)`)
        .then(() => ($scope.project.isTableActionInflight = false))
        .catch(function(response) {
          const { data, status } = response
          const error = status === 400 ? { message: data } : true
          $modal.open({
            templateUrl: 'showErrorModalTemplate',
            controller: 'ShowErrorModalController',
            resolve: {
              error() {
                return error
              }
            }
          })
          $scope.project.isTableActionInflight = false
        })
    }

    $scope.download = function(e) {
      e.stopPropagation()
      $scope.downloadProjectsById([$scope.project.id])
    }

    $scope.archive = function(e) {
      e.stopPropagation()
      $scope.createArchiveProjectsModal([$scope.project]).result.then(() => {
        $scope.archiveProjects([$scope.project])
      })
    }

    $scope.unarchive = function(e) {
      e.stopPropagation()
      $scope.unarchiveProjects([$scope.project])
    }

    $scope.trash = function(e) {
      e.stopPropagation()
      $scope.createTrashProjectsModal([$scope.project]).result.then(() => {
        $scope.trashProjects([$scope.project])
      })
    }

    $scope.untrash = function(e) {
      e.stopPropagation()
      $scope.untrashProjects([$scope.project])
    }

    $scope.leave = function(e) {
      e.stopPropagation()
      $scope.createLeaveProjectsModal([$scope.project]).result.then(() => {
        $scope.leaveProjects([$scope.project])
      })
    }

    $scope.delete = function(e) {
      e.stopPropagation()
      $scope.createDeleteProjectsModal([$scope.project]).result.then(() => {
        $scope.deleteProjects([$scope.project])
      })
    }
  })
})
