<template>
  <div>
    <my-header :index-name="indexName" :small-format="true" />

    <main>
      <form id="search-anchor" role="search" onsubmit="return false;">

        <div class="card position-relative">
          <button class="warning-button popover-button">
            <div class="visually-hidden">{{ $gettext('Toggle warning information') }}</div>

            <icon-warning></icon-warning>
          </button>

          <search-warning class="d-none popover-content" :index-name="indexName" :is-block-mode="false" />

          <search-input class="search-input" :label="true"></search-input>
        </div>


        <div class="button-rows">
          <button
            class="filters-button peertube-button peertube-primary-button"
            :aria-expanded="displayFilters" aria-controls="filters" @click="toggleFilters()"
          >
            {{ $gettext('Filters') }}
            <template v-if="activeFilters">({{ activeFilters }})</template>

            <div class="arrow-down" />
          </button>

          <sort-button></sort-button>
        </div>

        <filters v-if="displayFilters" id="filters"></filters>
      </form>

      <div v-if="searched" class="results">
        <div class="results-summary">
          <span v-if="totalResults === 0">{{ $gettext('No results found.') }}</span>

          <span v-if="totalResults">{{ $ngettext('%{totalResults} result found:', '%{totalResults} results found:', totalResults, { totalResults: totalResults.toLocaleString() }) }}</span>
        </div>

        <div v-if="(!hasResultTypeFilter() || getResultTypeFilter() === 'videos') && totalResults !== 0">
          <h2>
            <strong>
              {{ $ngettext('%{totalVideos} video', '%{totalVideos} videos', totalVideos, { totalVideos: totalVideos + '' }) }}
            </strong>
          </h2>

          <template v-if="totalVideos">
            <div v-for="video in videos" :key="video.url" class="mb-4">
              <video-result :video="video"></video-result>
            </div>

            <div v-if="!hasResultTypeFilter()" class="mb-5 text-center">
              <router-link
                v-if="!hasResultTypeFilter() && totalVideos > summaryResultsCount.videos"
                class="peertube-button peertube-button-link peertube-secondary-button"
                :to="{ path: '/search', query: { ...getMoreResultsQuery('videos') } }"
              >
                {{ $gettext('Display more videos') }}
              </router-link>
            </div>
          </template>
        </div>

        <div v-if="(!hasResultTypeFilter() || getResultTypeFilter() === 'channels') && totalResults !== 0">
          <h2>
            <strong>
              {{ $ngettext('%{totalChannels} channel', '%{totalChannels} channels', totalChannels, { totalChannels: totalChannels + '' }) }}
            </strong>
          </h2>

          <template v-if="totalChannels">
            <div v-for="channel in channels" :key="channel.url" class="mb-4">
              <channel-result :channel="channel"></channel-result>
            </div>

            <div class="mb-5 text-center">
              <router-link
                v-if="!hasResultTypeFilter() && totalChannels > summaryResultsCount.channels"
                class="peertube-button peertube-button-link peertube-secondary-button"
                :to="{ path: '/search', query: { ...getMoreResultsQuery('channels') } }"
              >
                {{ $gettext('Display more channels') }}
              </router-link>
            </div>
          </template>
        </div>

        <div v-if="(!hasResultTypeFilter() || getResultTypeFilter() === 'playlists') && totalResults !== 0">
          <h2>
            <strong>
              {{ $ngettext('%{totalPlaylists} playlist', '%{totalPlaylists} playlists', totalPlaylists, { totalPlaylists: totalPlaylists + '' }) }}
            </strong>
          </h2>

          <template v-if="totalPlaylists">
            <div v-for="playlist in playlists" :key="playlist.url" class="mb-4">
              <playlist-result :playlist="playlist"></playlist-result>
            </div>

            <div class="mb-5 text-center">
              <router-link
                v-if="!hasResultTypeFilter() && totalPlaylists > summaryResultsCount.playlists"
                class="peertube-button peertube-button-link peertube-secondary-button"
                :to="{ path: '/search', query: { ...getMoreResultsQuery('playlists') } }"
              >
                {{ $gettext('Display more playlists') }}
              </router-link>
            </div>
          </template>
        </div>
      </div>

      <pagination v-if="hasResultTypeFilter()" v-model="currentPage" :max-page="getMaxPage()" :searched="searched" :pages="pages" />
    </main>
  </div>
</template>

<script lang="ts">
  import { defineComponent } from 'vue'
  import Header from '../components/Header.vue'
  import SearchWarning from '../components/SearchWarning.vue'
  import VideoResult from '../components/VideoResult.vue'
  import ChannelResult from '../components/ChannelResult.vue'
  import PlaylistResult from '../components/PlaylistResult.vue'
  import IconWarning from '../components/icons/IconWarning.vue'
  import Filters from '../components/Filters.vue'
  import { searchVideos, searchVideoChannels, searchVideoPlaylists } from '../shared/search'
  import { getConfig } from '../shared/config'
  import { pageToAPIParams, durationRangeToAPIParams, publishedDateRangeToAPIParams, extractTagsFromQuery, extractQueryToIntArray, extractQueryToStringArray, extractQueryToInt, extractQueryToBoolean } from '../shared/utils'
  import { SearchUrl } from '../models'
  import { APIVideo } from '../../../server/types/video.model'
  import { APIVideoChannel } from '../../../server/types/channel.model'
  import Pagination from '../components/Pagination.vue'
  import SearchInput from '../components/SearchInput.vue'
  import SortButton from '../components/SortButton.vue'
  import type { VideoChannelsSearchQuery, ResultList, VideosSearchQuery } from '@peertube/peertube-types'
  import Nprogress from 'nprogress'
  import { APIPlaylist } from '../../../server/types/playlist.model'
  import { PlaylistsSearchQuery } from '../../../server/types/search-query/playlist-search.model'

  export default defineComponent({
    components: {
      'my-header': Header,
      'search-warning': SearchWarning,
      'video-result': VideoResult,
      'channel-result': ChannelResult,
      'playlist-result': PlaylistResult,
      'search-input': SearchInput,
      'pagination': Pagination,
      'filters': Filters,
      'sort-button': SortButton,
      'icon-warning': IconWarning
    },

    data () {
      return {
        searched: false,

        indexName: '',

        totalResults: null as number,

        totalVideos: null as number,
        videos: null as APIVideo[],

        totalChannels: null as number,
        channels: null as APIVideoChannel[],

        totalPlaylists: null as number,
        playlists: null as APIPlaylist[],

        currentPage: 1,
        pages: [],

        summaryResultsCount: {
          videos: 5,
          channels: 2,
          playlists: 2
        },
        filteredTypeResultsPerPage: 10,

        activeFilters: 0,
        displayFilters: false
      }
    },

    computed: {
      boostLanguagesQuery (): string[] {
        const languages = new Set<string>()

        for (const completeLanguage of navigator.languages) {
          languages.add(completeLanguage.split('-')[0])
        }

        return Array.from(languages)
      }
    },

    watch: {
      $route() {
        this.loadUrl()
        this.doSearch()
      }
    },

    mounted () {
      // eslint-disable-next-line no-new
      import('bootstrap/js/dist/popover')
        .then(Popover => {
          new Popover.default(this.$el.querySelector('.popover-button'), {
            content: document.querySelector('.popover-content'),
            sanitize: false,
            trigger: 'focus'
          })
        })

      const config = getConfig()

      this.indexName = config.searchInstanceName

      if (Object.keys(this.$route.query).length !== 0) {
        this.loadUrl()
        this.doSearch()
      }
    },

    methods: {

      async doSearch () {
        this.videos = []
        this.channels = []
        this.playlists = []
        this.searched = false

        Nprogress.start()

        try {
          const [ videosResult, channelsResult, playlistsResult ] = await Promise.all([
            this.searchVideos(),
            this.searchChannels(),
            this.searchPlaylists()
          ])

          Nprogress.done()

          this.activeFilters = this.countActiveFilters()


          this.totalVideos = videosResult.total
          this.videos = videosResult.data

          this.totalChannels = channelsResult.total
          this.channels = channelsResult.data

          this.totalPlaylists = playlistsResult.total
          this.playlists = playlistsResult.data

          this.totalResults = videosResult.total + channelsResult.total + playlistsResult.total

          this.buildPages()
          this.searched = true
        } catch (err) {
          console.error('Cannot do search.', err)
        } finally {
          this.searched = true
          Nprogress.done()
        }
      },

      loadUrl () {
        const query = this.$route.query as SearchUrl
        const queryPage = extractQueryToInt(query.page)

        this.currentPage = queryPage && this.currentPage !== queryPage
          ? queryPage
          : 1
      },

      countActiveFilters () {
        const query = this.$route.query as SearchUrl

        let count = 0

        if (query.nsfw) count++
        if (query.host) count++
        if (query.publishedDateRange) count++
        if (query.durationRange) count++
        if (query.categoryOneOf) count++
        if (query.licenceOneOf) count++
        if (query.languageOneOf) count++
        if (query.isLive) count++
        if (query.resultType) count++
        if (Array.isArray(query.tagsAllOf) && query.tagsAllOf.length !== 0) count++
        if (Array.isArray(query.tagsOneOf) && query.tagsOneOf.length !== 0) count++

        return count
      },

      buildVideoSearchQuery () {
        const query = this.$route.query as SearchUrl

        const resultsPerPage = this.getResultTypeFilter() === 'videos'
          ? this.filteredTypeResultsPerPage
          : this.summaryResultsCount.videos

        const { start, count } = pageToAPIParams(this.currentPage, resultsPerPage)
        const { durationMin, durationMax } = durationRangeToAPIParams(query.durationRange)
        const { startDate, endDate } = publishedDateRangeToAPIParams(query.publishedDateRange)

        const boostLanguages = this.boostLanguagesQuery

        let sort: string
        if (query.sort === '-matched') sort = '-matched'
        else if (query.sort === '-createdAt') sort = '-publishedAt'
        else if (query.sort === 'createdAt') sort = 'publishedAt'

        return {
          search: query.search,

          durationMin,
          durationMax,

          startDate,
          endDate,

          boostLanguages,

          nsfw: query.nsfw || false,

          categoryOneOf: extractQueryToIntArray(query.categoryOneOf),
          licenceOneOf: extractQueryToIntArray(query.licenceOneOf),
          languageOneOf: extractQueryToStringArray(query.languageOneOf),

          tagsOneOf: extractTagsFromQuery(query.tagsOneOf).map(t => t.text),
          tagsAllOf: extractTagsFromQuery(query.tagsAllOf).map(t => t.text),

          isLive: extractQueryToBoolean(query.isLive),

          host: query.host || undefined,

          start,
          count,
          sort
        } as VideosSearchQuery
      },

      buildChannelSearchQuery () {
        const query = this.$route.query as SearchUrl

        const resultsPerPage = this.getResultTypeFilter() === 'channels'
          ? this.filteredTypeResultsPerPage
          : this.summaryResultsCount.channels

        const { start, count } = pageToAPIParams(this.currentPage, resultsPerPage)

        return {
          search: query.search,
          host: query.host || undefined,
          start,
          sort: query.sort,
          count
        } as VideoChannelsSearchQuery
      },

      buildPlaylistSearchQuery () {
        const query = this.$route.query as SearchUrl

        const resultsPerPage = this.getResultTypeFilter() === 'playlists'
          ? this.filteredTypeResultsPerPage
          : this.summaryResultsCount.playlists

        const { start, count } = pageToAPIParams(this.currentPage, resultsPerPage)

        return {
          search: query.search,
          host: query.host || undefined,
          start,
          sort: query.sort,
          count
        } as PlaylistsSearchQuery
      },

      searchVideos (): Promise<ResultList<APIVideo>> {
        if (this.isVideoSearchDisabled()) {
          return Promise.resolve({ total: 0, data: [] })
        }

        const query = this.buildVideoSearchQuery()

        return searchVideos(query)
      },

      searchChannels (): Promise<ResultList<APIVideoChannel>> {
        if (this.isChannelSearchDisabled()) {
          return Promise.resolve({ data: [], total: 0 })
        }

        const query = this.buildChannelSearchQuery()

        return searchVideoChannels(query)
      },

      searchPlaylists (): Promise<ResultList<APIPlaylist>> {
        if (this.isPlaylistSearchDisabled()) {
          return Promise.resolve({ data: [], total: 0 })
        }

        const query = this.buildPlaylistSearchQuery()

        return searchVideoPlaylists(query)
      },

      // ---------------------------------------------------------------------------

      getMaxPage () {
        const resultType = (this.$route.query as SearchUrl).resultType

        let maxPage: number

        if (resultType === 'videos') maxPage = Math.ceil(this.totalVideos / this.filteredTypeResultsPerPage)
        else if (resultType === 'channels') maxPage = Math.ceil(this.totalChannels / this.filteredTypeResultsPerPage)
        else if (resultType === 'playlists') maxPage = Math.ceil(this.totalPlaylists / this.filteredTypeResultsPerPage)

        // Limit to 10 pages
        return Math.min(10, maxPage)
      },

      buildPages () {
        this.pages = []

        for (let i = 1; i <= this.getMaxPage(); i++) {
          this.pages.push(i)
        }
      },

      toggleFilters () {
        this.displayFilters = !this.displayFilters
      },

      // ---------------------------------------------------------------------------

      getResultTypeFilter () {
        return (this.$route.query as SearchUrl).resultType
      },

      hasResultTypeFilter () {
        return !!this.getResultTypeFilter()
      },

      getMoreResultsQuery (type: 'videos' | 'channels' | 'playlists'): SearchUrl {
        return {
          ...this.$route.query,

          resultType: type
        }
      },

      // ---------------------------------------------------------------------------

      isVideoSearchDisabled () {
        const { resultType } = this.$route.query as SearchUrl

        if (resultType !== undefined && resultType !== 'videos') return true

        return false
      },

      isChannelSearchDisabled () {
        const { resultType, host } = this.$route.query as SearchUrl

        if (resultType !== undefined) {
          if (resultType !== 'channels') return true

          return false
        }

        // We can search against host for playlists and channels
        if (host) return this.countActiveFilters() > 1

        return this.countActiveFilters() > 0
      },

      isPlaylistSearchDisabled () {
        const { resultType, host } = this.$route.query as SearchUrl

        if (resultType !== undefined) {
          if (resultType !== 'playlists') return true

          return false
        }

        // We can search against host for playlists and channels
        if (host) return this.countActiveFilters() > 1

        return this.countActiveFilters() > 0
      }
    }
  })
</script>

<style scoped lang="scss">
  @use 'sass:math';

  @import '../scss/_variables';
  @import '../scss/bootstrap-mixins';

  .search-input {
    max-width: 700px;
    margin: auto;
  }

  .warning-button {
    background: transparent;
    border: none;
    position: absolute;
    top: 10px;
    right: 10px;

    svg {
      width: 30px;
      height: 30px;
    }

    &:hover {
      opacity: 0.8;
    }
  }

  .filters-button {
    position: relative;
    padding-left: 1rem;
    padding-right: 2rem;

    .arrow-down {
      $size: 4px;

      position: absolute;
      right: 0;
      top: 50%;
      margin-top: math.div(-$size, 2);
      margin-left: 1.5rem;
      margin-right: 0.75rem;
      width: 0;
      height: 0;
      border-left: $size solid transparent;
      border-right: $size solid transparent;

      border-top: $size solid #fff;
    }
  }

  .button-rows {
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;

    > * {
      @include margin-top(2rem);
    }
  }

  .results {
    @include margin(2rem 0);
  }

  .results-summary {
    @include margin-bottom(3rem);

    font-weight: $font-semibold;
    text-align: center;
  }

  h2 {
    @include font-size(1.125rem);
  }

</style>
