<template>
  <section class="section">
    <div class="container">
      <user-login
        class="is-justify-content-end"
        @login="(login) => (credentials = login)"
      />

      <div class="field is-flex is-justify-content-end" v-if="login_user">
        <div class="control has-icons-left">
          <div class="select is-small">
            <select v-model="filter_group" @change="search">
              <option selected value="">Me ({{ login_user.name }})</option>
              <option v-for="g in login_user.groups" :key="g.id" :value="g.id">
                {{ g.id }}
              </option>
            </select>
          </div>
          <div class="icon is-small is-left">
            <span class="fas fa-group">👥</span>
          </div>
        </div>
      </div>

      <div class="field has-addons mt-6">
        <div class="control is-expanded">
          <input
            class="input is-loading"
            type="text"
            placeholder="Movie title"
            v-model="query"
            @change="search"
          />
        </div>
        <p class="control">
          <span class="select">
            <select v-model="media_type" @change="search">
              <option value="">(any type)</option>
              <option v-for="t in media_types" :key="t" :value="t">{{ t }}</option>
            </select>
          </span>
        </p>
        <div class="control">
          <a class="button is-info" :disabled="!active"> Search </a>
        </div>
      </div>

      <movie-list :items="items" @reachBottom="loadMore" />

      <footer class="footer" v-if="is_end">
        <p class="subtitle content has-text-centered is-italic">～ fin ～</p>
      </footer>
    </div>
  </section>
</template>

<script lang="ts">
import { defineComponent } from "vue"
import config from "./config.ts"
import { is_object } from "./utils.ts"

const media_types = [
  "Movie",
  "Radio Episode",
  "Radio Series",
  "Short",
  "TV Episode",
  "TV Mini Series",
  "TV Movie",
  "TV Series",
  "TV Short",
  "TV Special",
  "Video",
  "Video Game",
]

function clean_obj(o) {
  return Object.fromEntries(Object.entries(o).filter(([k, v]) => v))
}

function api_url(resource, params = []) {
  const url = new URL(resource, config.api_url)

  if (is_object(params)) {
    params = clean_obj(params)
  }

  if (is_object(params) || params.length) {
    url.search = new URLSearchParams(params)
  }

  return url
}

function set_view_params(params) {
  history.replaceState(null, "", "#" + new URLSearchParams(clean_obj(params)))
}

function view_params() {
  const fragment = (window.location.hash || "").replace(/^#/, "")
  const params = new URLSearchParams(fragment)
  return {
    query: params.get("query") || "",
    type: params.get("type") || "",
  }
}

async function req(url, opts = {}, { user_id = "", secret = "" }, data = null) {
  opts.headers = opts.headers || {}

  if (data !== null) {
    opts.body = JSON.stringify(data)
    opts.headers["Content-Type"] = "application/json"
  }

  if (secret) {
    const credentials = btoa(`${user_id}:${secret}`)
    opts.headers["Authorization"] = `Basic ${credentials}`
    opts.mode = "cors"
    opts.credentials = "include"
  }

  const resp = await window.fetch(url, opts)
  if (!resp.ok) {
    throw resp
  }
  return await resp.json()
}

async function get(resource, params = [], user = {}) {
  const url = api_url(resource, params)
  const opts = {}
  return await req(url, opts, user)
}

async function patch(resource, params = [], user = {}, data = null) {
  const url = api_url(resource, params)
  const opts = { method: "PATCH" }
  return await req(url, opts, user, data)
}

function debounce_async(ms, func) {
  let abort

  return async (...args) => {
    if (abort) {
      abort()
    }

    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        resolve(func(...args))
      }, ms)

      abort = () => {
        reject("Function call aborted, replaced by a later call.")
        clearTimeout(timer)
        abort = null
      }
    })
  }
}

const debounced_get = debounce_async(100, get)

type ULID = string
type User = {
  id: ULID
  name: string
  groups: Array<{ id: ULID }>
}

function parse_query(query) {
  const match = /^(.*?)(?:\s+([<=>])?(\d+|\(\d+\)))?$/.exec(query)
  const title = match[1].replaceAll('"', "")
  const comp = match[2] || ""
  const year = match[3] ? /\d+/.exec(match[3])[0] : ""
  return { title, year: year ? `${comp}${year}` : null }
}

export default defineComponent({
  data: () => ({
    query: "",
    items: [],
    credentials: { user_id: "", secret: "" },
    login_user: null,
    page: 1,
    is_end: false,
    media_types,
    media_type: "",
    active: false,
    filter_group: "",
  }),
  mounted() {
    const { query, type } = view_params()
    this.query = query
    this.media_type = media_types.includes(type) ? type : ""
  },
  methods: {
    async search() {
      this.active = true
      set_view_params({ query: this.query, type: this.media_type })

      if (!this.query) {
        return
      }

      this.page = 1
      this.is_end = false
      this.items = []
      await this.loadMore()
    },
    async loadMore() {
      this.active = false

      if (!this.query || this.is_end) {
        return
      }

      const per_page = 100
      const media_type = this.media_type === "" ? null : this.media_type
      const user_id = (this.filter_group === "" && this.credentials.user_id) || null
      const group_id = user_id === null && this.filter_group ? this.filter_group : null
      const { title, year } = parse_query(this.query)
      const more = await debounced_get(
        "movies",
        {
          title,
          year,
          per_page,
          include_unrated: true,
          page: this.page,
          media_type,
          user_id,
          group_id,
        },
        this.credentials,
      )
      this.items = this.items.concat(more)

      if (more.length < per_page) {
        this.is_end = true
      } else {
        this.page += 1
      }
    },
  },
  watch: {
    async credentials(creds) {
      if (!creds.user_id) {
        return
      }

      const user: User = await get(`users/${creds.user_id}`, {}, creds)
      this.login_user = user
    },
  },
})
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
