<script setup lang="ts">
import { onMounted, ref } from 'vue'
import type {
  GalleryEntry,
  GalleryList,
  GalleryListDefinition,
} from '@goldenwere/static-web-templates-types'
import { type RouteRecordRaw, useRouter } from 'vue-router'

import { amendVariantsWithDefaults } from './gallery-utilities'
import { fetchAndParseYaml, storage } from 'src/utilities/fetch'
import { getCurrentRoute } from 'src/utilities/vuetils'
import { useRouteStore } from 'src/routes'

import FilterPanel from 'src/components/shared/filter-panel.vue'
import GalleryTile from './gallery-tile.vue'

/**
 * A wrapper around {@link GalleryEntries} for the app's use only which adds additional fields
 * in order for the app to effectively display the entries.
 */
type GalleryDisplayedEntries = { [idOrTitle: string]: GalleryEntry & {
  /**
   * specifies whether the entry is hidden by the tags selected by a visitor
   */
  hidden?: boolean
}}

const props = defineProps<{
  variants: string[]
}>()

/**
 * Wraps around the path of variant ids to ensure there are not any issues
 * @param variants the array of variant ids
 */
const validateVariantPath = (variants?: string[]) => {
  return !!variants && variants[0] !== '' ? variants : null
}

const currentRoute = getCurrentRoute()
const routeStore = useRouteStore()
const routeConfig = routeStore._routes[currentRoute.path] as GalleryListDefinition & RouteRecordRaw
const globalConfig = routeStore._globals
const storageId = `${globalConfig.id}`
const router = useRouter()
let config: GalleryList = null!

const ready = ref(false)
const galleryReady = ref(false)
const filterPanelRef = ref(null as typeof FilterPanel | null)
const entries = ref({} as GalleryDisplayedEntries)
const variants = ref(validateVariantPath(props.variants))
const hasWarnings = ref(false)
const hideWarnings = defineModel('showWarnings', { type: Boolean })
const tagsByCategory = ref({} as { [category: string]: Record<string, string> })

/**
 * Handles updating the displayed tags in the list
 */
const onLoadTags = () => {
  if (!!config.tags) {
    tagsByCategory.value = { 'NoCategory': {}}
    Object.keys(config.tags).forEach(id => {
      const tag = config.tags![id]
      if (!!tag.category) {
        if (!tagsByCategory.value[tag.category]) {
          tagsByCategory.value[tag.category] = {}
        }
        tagsByCategory.value[tag.category][id] = tag.displayName || id
      } else {
        tagsByCategory.value['NoCategory'][id] = tag.displayName || id
      }
    })
  } else {
    tagsByCategory.value = null as any
  }
}

/**
 * Handles updating the displayed entries in the list
 */
const onDisplayEntries = () => {
  resetTags()
  galleryReady.value = false
  let currentEntries = config.entries
  if (!!variants.value) {
    variants.value.forEach((variant) => {
      currentEntries = amendVariantsWithDefaults(currentEntries[variant])!
    })
  }
  entries.value = currentEntries
  hasWarnings.value = !!Object.values(entries.value).find(other => !!other.warning)
  setTimeout(() => galleryReady.value = true)
}

/**
 * Handler for the tile clicked event;
 * handles navigating to the gallery-view route to display the entry selected
 * @param clickEvent.event the event context which invoked this handler
 * @param clickEvent.id the id of the tile that was clicked
 */
const onTileClicked = (clickEvent: { event: Event, id: string }) => {
  const { event, id } = clickEvent
  const entry = entries.value[id]

  event.preventDefault()
  if (!!entry.variants) {
    const newPath = !!variants.value
      ? `${(variants.value || []).join(';')};${id}`
      : id
    router.push({ name: routeConfig.name, query: { v: newPath }})
    variants.value = newPath.split(';')
    onDisplayEntries()
  } else if (!!variants.value) {
    router.push({ name: `${routeConfig.name?.toString()}: View Entry`, query: { v: `${(variants.value || []).join(';')};${id}` }})
  } else {
    router.push({ name: `${routeConfig.name?.toString()}: View Entry`, query: { v: `${id}` }})
  }
}

/**
 * Handler for the back button which appears when navigated into entry variants;
 * navigates upward one level from the currently displayed variants
 */
const onNavigateBack = (event: Event) => {
  event.preventDefault()
  let newPath: string | null = variants.value!.slice(0, variants.value!.length - 1).join(';')
  if (newPath === '') {
    router.push({ name: routeConfig.name})
    variants.value = null
  } else {
    router.push({ name: routeConfig.name, query: { v: newPath }})
    variants.value = validateVariantPath(newPath?.split(';'))
  }
  onDisplayEntries()
}

/**
 * Handler for the toggle for hiding/showing warnings;
 * updates localstorage with the state of the checkbox
 * so that it is saved between page loads
 * @param event the event context which invoked this handler
 */
const onHideWarningsToggled = (event: Event) => {
  storage.write(`${storageId}::hideWarnings`, (event.target as HTMLInputElement).checked)
}

/**
 * Handler for a tag being selected;
 * updates the visibility state of the current entries
 * @param tagsToggled: the tags currently toggled in the filter panel
 */
 const onToggledTagsChanged = (tagsToggled: string[]) => {
  if (tagsToggled.length < 1) {
    Object.keys(entries.value).forEach(entryId => {
      entries.value[entryId].hidden = false
    })
  } else {
    Object.keys(entries.value).forEach(entryId => {
      entries.value[entryId].hidden = !entries.value[entryId].tags?.some(own => tagsToggled.includes(own))
    })
  }
}

/**
 * Resets the `hidden` state of entries
 * and the `tagsToggled` array
 */
const resetTags = () => {
  if (!!filterPanelRef.value) {
    filterPanelRef.value.resetTags()
  }
  Object.keys(entries.value).forEach(entryId => {
    entries.value[entryId].hidden = false
  })
}

onMounted(async () => {
  ready.value = false
  config = await fetchAndParseYaml<GalleryList>(routeConfig.config)
  document.title = routeConfig.title
  hideWarnings.value = storage.read(`${storageId}::hideWarnings`) || false
  onLoadTags()
  onDisplayEntries()
  ready.value = true
})
</script>

<template lang="pug">
.template.gallery-list
  .navigation(
    v-if='ready'
  )
    button.input(
      v-if='variants?.length > 0'
      @click='onNavigateBack($event)'
    )
      span Back
    .input.labeled-checkbox(
      v-if='hasWarnings'
    )
      label(
        for='warning-toggle-checkbox'
      ) Hide Warnings
      input(
        type='checkbox'
        name='warning-toggle-checkbox'
        id='warning-toggle-checkbox'
        v-model='hideWarnings'
        @input='onHideWarningsToggled($event)'
      )
  Transition
    FilterPanel(
      v-if='galleryReady && !!tagsByCategory'
      :ref='filterPanelRef'
      :tagsByCategory='tagsByCategory'
      @toggledTagsChanged='onToggledTagsChanged($event)'
    )
  Transition
    .gallery(
      v-if='galleryReady'
    )
      Transition(
        v-for='(entry, id) in entries'
      )
        GalleryTile(
          v-if='!entry.hidden'
          :entry='entry'
          :id='id'
          :hideWarnings='hideWarnings'
          @click='onTileClicked($event)'
        )
</template>
