<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import ElementParentLink from '@/components/ElementParentLink.vue'
import ElementSiblingNavigation from '@/components/ElementSiblingNavigation.vue'
import PdfViewerModal from '@/components/PdfViewerModal.vue'
import SiteHeader from '@/components/SiteHeader.vue'
import { useElementsStore } from '@/stores/elements'

type TimestampPart = string | undefined
type ElementPdfFolder = 'short' | 'long'

interface ActivePdf {
  openUrl: string
  sourceUrl: string
  title: string
}

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
const route = useRoute()
const elementsStore = useElementsStore()
const activePdf = ref<ActivePdf | null>(null)
const {
  element,
  childElements,
  parentElement,
  siblingElements,
  isLoading,
  error,
} = storeToRefs(elementsStore)

const youtubeIframeAllow = [
  'accelerometer',
  'autoplay',
  'clipboard-write',
  'encrypted-media',
  'gyroscope',
  'picture-in-picture',
  'web-share',
].join('; ')

const elementId = computed(() => {
  const routeElementId = route.params.id

  if (Array.isArray(routeElementId)) {
    return routeElementId.join('/')
  }

  return routeElementId
})

const youtubeEmbedUrl = computed(() => {
  const youtubeUrl = element.value?.youtubeUrl ?? null

  return getYoutubeEmbedUrl(youtubeUrl)
})

const activePdfTitle = computed(() => {
  return activePdf.value?.title ?? ''
})

const activePdfUrl = computed(() => {
  return activePdf.value?.sourceUrl ?? ''
})

const activePdfOpenUrl = computed(() => {
  return activePdf.value?.openUrl ?? ''
})

watch(
  elementId,
  (currentElementId) => {
    activePdf.value = null

    if (typeof currentElementId !== 'string' || currentElementId === '') {
      return
    }

    void elementsStore.fetchElement(currentElementId)
  },
  { immediate: true },
)

function openPdfViewer(pdfTitle: string, pdfUrl: string | null): void {
  if (pdfUrl === null) {
    return
  }

  activePdf.value = {
    openUrl: pdfUrl,
    sourceUrl: getPdfViewerUrl(pdfUrl),
    title: pdfTitle,
  }
}

function closePdfViewer(): void {
  activePdf.value = null
}

function getPdfViewerUrl(pdfUrl: string): string {
  let parsedPdfUrl: URL
  try {
    parsedPdfUrl = new URL(pdfUrl, window.location.origin)
  } catch {
    return pdfUrl
  }

  const pathSegments = parsedPdfUrl.pathname
    .split('/')
    .filter((pathSegment) => {
      return pathSegment !== ''
    })

  if (pathSegments.length !== 4) {
    return pdfUrl
  }

  if (pathSegments[0] !== 'storage' || pathSegments[1] !== 'element-pdfs') {
    return pdfUrl
  }

  const pdfFolder = pathSegments[2]
  const fileName = pathSegments[3]
  if (!isElementPdfFolder(pdfFolder) || fileName === undefined) {
    return pdfUrl
  }

  const encodedFileName = encodeURIComponent(fileName)

  return `${API_BASE_URL}/api/element-pdfs/${pdfFolder}/${encodedFileName}`
}

function isElementPdfFolder(
  pdfFolder: string | undefined,
): pdfFolder is ElementPdfFolder {
  return pdfFolder === 'short' || pdfFolder === 'long'
}

function getYoutubeEmbedUrl(youtubeUrl: string | null): string | null {
  if (youtubeUrl === null || youtubeUrl === '') {
    return null
  }

  let parsedUrl: URL
  try {
    parsedUrl = new URL(youtubeUrl)
  } catch {
    return null
  }

  const videoId = getYoutubeVideoId(parsedUrl)
  if (videoId === null) {
    return null
  }

  const embedUrl = new URL(`https://www.youtube.com/embed/${videoId}`)
  const startSeconds = getYoutubeStartSeconds(parsedUrl)
  if (startSeconds !== null) {
    embedUrl.searchParams.set('start', startSeconds.toString())
  }

  return embedUrl.toString()
}

function getYoutubeVideoId(parsedUrl: URL): string | null {
  if (isShortYoutubeHost(parsedUrl.hostname)) {
    return normalizeYoutubeVideoId(getFirstPathSegment(parsedUrl))
  }

  if (!isYoutubeHost(parsedUrl.hostname)) {
    return null
  }

  if (parsedUrl.pathname === '/watch') {
    return normalizeYoutubeVideoId(parsedUrl.searchParams.get('v'))
  }

  if (parsedUrl.pathname.startsWith('/embed/')) {
    return normalizeYoutubeVideoId(getPathSegment(parsedUrl, 1))
  }

  return null
}

function getYoutubeStartSeconds(parsedUrl: URL): number | null {
  const startParam = parsedUrl.searchParams.get('start')
  if (startParam !== null) {
    return getPositiveSeconds(startParam)
  }

  const timestampParam = parsedUrl.searchParams.get('t')
  if (timestampParam === null) {
    return null
  }

  return getTimestampSeconds(timestampParam)
}

function getTimestampSeconds(timestampParam: string): number | null {
  if (/^\d+$/.test(timestampParam)) {
    return getPositiveSeconds(timestampParam)
  }

  const hourRegex = '^(?:(\\d+)h)?'
  const minuteRegex = '(?:(\\d+)m)?'
  const secondRegex = '(?:(\\d+)s)?$'
  const fullTimestampRegex = hourRegex + minuteRegex + secondRegex
  const timestampPattern = new RegExp(fullTimestampRegex)
  const timestampMatch = timestampParam.match(timestampPattern)
  if (timestampMatch === null) {
    return null
  }

  const hours = getTimestampHours(timestampMatch[1])
  const minutes = getTimestampMinutes(timestampMatch[2])
  const seconds = getTimestampSecondsPart(timestampMatch[3])
  const totalSeconds = hours + minutes + seconds

  return totalSeconds > 0 ? totalSeconds : null
}

function getTimestampHours(timestampPart: TimestampPart): number {
  return getPartSeconds(timestampPart, 3600)
}

function getTimestampMinutes(timestampPart: TimestampPart): number {
  return getPartSeconds(timestampPart, 60)
}

function getTimestampSecondsPart(timestampPart: TimestampPart): number {
  return getPartSeconds(timestampPart, 1)
}

function getPartSeconds(timestamp: TimestampPart, multiplier: number): number {
  if (timestamp === undefined) {
    return 0
  }

  return Number(timestamp) * multiplier
}

function getPositiveSeconds(secondsParam: string): number | null {
  const seconds = Number(secondsParam)
  if (!Number.isInteger(seconds) || seconds < 0) {
    return null
  }

  return seconds
}

function getFirstPathSegment(parsedUrl: URL): string | null {
  return getPathSegment(parsedUrl, 0)
}

function getPathSegment(parsedUrl: URL, segmentIndex: number): string | null {
  const pathSegments = parsedUrl.pathname.split('/').filter((pathSegment) => {
    return pathSegment !== ''
  })

  return pathSegments[segmentIndex] ?? null
}

function normalizeYoutubeVideoId(videoId: string | null): string | null {
  if (videoId === null || videoId === '') {
    return null
  }

  if (!/^[A-Za-z0-9_-]+$/.test(videoId)) {
    return null
  }

  return videoId
}

function isYoutubeHost(hostname: string): boolean {
  const normalizedHostname = hostname.toLowerCase()
  const isRootYoutubeHost = normalizedHostname === 'youtube.com'
  const isYoutubeSubdomain = normalizedHostname.endsWith('.youtube.com')

  return isRootYoutubeHost || isYoutubeSubdomain
}

function isShortYoutubeHost(hostname: string): boolean {
  return hostname.toLowerCase() === 'youtu.be'
}
</script>

<template>
  <div class="element-page">
    <SiteHeader />
    <main class="element-page__main" data-cy="element-page">
      <p v-if="isLoading" class="element-page__status">Loading element...</p>
      <p
        v-else-if="error"
        class="element-page__status element-page__status--error"
        data-cy="element-error"
      >
        {{ error }}
      </p>
      <section v-else-if="element" class="element-page__content">
        <img
          v-if="element.iconImageUrl !== null"
          :src="element.iconImageUrl"
          :alt="`${element.title} icon`"
          class="element-page__icon"
          data-cy="element-icon"
        />

        <div
          v-if="parentElement !== null"
          class="element-page__parent-navigation"
        >
          <ElementParentLink
            :parent-element="parentElement"
            data-cy="element-parent-link"
            route-name="element"
          />
        </div>

        <h1 class="element-page__heading">
          {{ element.title }}
        </h1>

        <div
          v-if="element.richText !== ''"
          class="element-page__rich-text"
          data-cy="element-rich-text"
          v-html="element.richText"
        />

        <div v-if="youtubeEmbedUrl !== null" class="element-page__youtube">
          <iframe
            :src="youtubeEmbedUrl"
            :allow="youtubeIframeAllow"
            class="element-page__youtube-iframe"
            data-cy="element-youtube-embed"
            title="YouTube video player"
            loading="lazy"
            allowfullscreen
          />
        </div>

        <div
          v-if="element.shortPdfPath !== null || element.longPdfPath !== null"
          class="element-page__actions"
        >
          <button
            v-if="element.shortPdfPath !== null"
            type="button"
            class="element-page__pdf-link"
            data-cy="element-short-pdf-viewer-button"
            @click="openPdfViewer('Short PDF', element.shortPdfPath)"
          >
            View Short PDF
          </button>
          <button
            v-if="element.longPdfPath !== null"
            type="button"
            class="element-page__pdf-link"
            data-cy="element-long-pdf-viewer-button"
            @click="openPdfViewer('Long PDF', element.longPdfPath)"
          >
            View Long PDF
          </button>
        </div>

        <nav
          v-if="childElements.length > 0"
          class="element-page__children"
          aria-label="Child elements"
        >
          <ul class="element-page__child-list" data-cy="child-element-list">
            <li
              v-for="childElement in childElements"
              :key="childElement.id"
              class="element-page__child-item"
            >
              <RouterLink
                :to="{ name: 'element', params: { id: childElement.id } }"
                class="element-page__child-link"
                data-cy="child-element-link"
              >
                <span class="element-page__child-title">
                  {{ childElement.title }}
                </span>
                <span
                  v-if="childElement.description !== ''"
                  class="element-page__child-description"
                >
                  {{ childElement.description }}
                </span>
              </RouterLink>
            </li>
          </ul>
        </nav>

        <ElementSiblingNavigation
          :current-element-id="element.id"
          :sibling-elements="siblingElements"
          data-cy-prefix="element"
          route-name="element"
        />
      </section>
    </main>
    <PdfViewerModal
      :is-open="activePdf !== null"
      :open-url="activePdfOpenUrl"
      :pdf-title="activePdfTitle"
      :pdf-url="activePdfUrl"
      @close="closePdfViewer"
    />
  </div>
</template>

<style scoped>
.element-page {
  min-height: 100vh;
  background: var(--color-white);
}

.element-page__main {
  min-height: 100vh;
  padding: 5.1rem 4.15rem 5rem;
}

.element-page__content {
  max-width: 48rem;
  margin: 0 auto;
}

.element-page__icon {
  display: block;
  width: 132px;
  height: 132px;
  margin: 0 auto 1.8rem;
  object-fit: contain;
}

.element-page__parent-navigation {
  display: flex;
  justify-content: center;
  margin: 0 0 0.5rem;
}

.element-page__heading {
  margin: 0;
  color: #2c2c2c;
  font-family: var(--font-serif);
  font-size: clamp(2.6rem, 4.8vw, 3.2rem);
  font-weight: 400;
  line-height: 1.1;
  text-align: center;
}

.element-page__rich-text {
  margin-top: 1.75rem;
  color: #333333;
  font-family: var(--font-sans);
  font-size: 1.05rem;
  line-height: 1.75;
}

.element-page__rich-text :deep(p) {
  margin: 0 0 1rem;
}

.element-page__rich-text :deep(p:last-child) {
  margin-bottom: 0;
}

.element-page__rich-text :deep(h2),
.element-page__rich-text :deep(h3),
.element-page__rich-text :deep(h4) {
  margin: 1.6rem 0 0.65rem;
  color: #2c2c2c;
  font-family: var(--font-serif);
  font-weight: 500;
  line-height: 1.25;
}

.element-page__rich-text :deep(h2) {
  font-size: 1.75rem;
}

.element-page__rich-text :deep(h3) {
  font-size: 1.45rem;
}

.element-page__rich-text :deep(h4) {
  font-size: 1.2rem;
}

.element-page__rich-text :deep(ul),
.element-page__rich-text :deep(ol) {
  margin: 0 0 1rem;
  padding-left: 1.45rem;
}

.element-page__rich-text :deep(li) {
  margin: 0.25rem 0;
}

.element-page__rich-text :deep(blockquote) {
  margin: 1.35rem 0;
  padding: 0.2rem 0 0.2rem 1.1rem;
  border-left: 3px solid var(--color-olive);
  color: var(--color-text-muted);
}

.element-page__rich-text :deep(strong) {
  color: #2c2c2c;
  font-weight: 700;
}

.element-page__rich-text :deep(mark) {
  padding: 0 0.16em;
  background: #fff3a7;
}

.element-page__rich-text :deep(a) {
  color: var(--color-slate);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 0.16em;
}

.element-page__rich-text :deep(hr) {
  margin: 1.5rem 0;
  border: 0;
  border-top: 1px solid var(--color-border);
}

.element-page__rich-text :deep(.tableWrapper) {
  max-width: 100%;
  margin: 1.4rem 0;
  overflow-x: auto;
}

.element-page__rich-text :deep(table) {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}

.element-page__rich-text :deep(th),
.element-page__rich-text :deep(td) {
  min-width: 4rem;
  padding: 0.55rem 0.65rem;
  border: 1px solid var(--color-border);
  vertical-align: top;
}

.element-page__rich-text :deep(th) {
  background: #f6f3eb;
  font-weight: 700;
}

.element-page__youtube {
  aspect-ratio: 16 / 9;
  margin-top: 1.75rem;
  overflow: hidden;
  background: #000000;
  border-radius: 8px;
}

.element-page__youtube-iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.element-page__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  margin-top: 1.75rem;
}

.element-page__pdf-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: 0.7rem 1.15rem;
  color: var(--color-white);
  background: var(--color-olive);
  border-radius: 8px;
  font-family: var(--font-sans);
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  transition:
    background-color 180ms ease,
    box-shadow 180ms ease,
    transform 180ms ease;
}

.element-page__pdf-link:hover,
.element-page__pdf-link:focus-visible {
  background: #4d5b3c;
  box-shadow: 0 10px 24px rgb(44 44 44 / 12%);
  transform: translateY(-1px);
}

.element-page__pdf-link:focus-visible {
  outline: 3px solid rgb(94 107 76 / 38%);
  outline-offset: 4px;
}

.element-page__children {
  margin-top: 3rem;
}

.element-page__child-list {
  display: grid;
  margin: 0;
  padding: 0;
  gap: 0.85rem;
  list-style: none;
}

.element-page__child-link {
  display: block;
  padding: 1rem 1.2rem;
  background: var(--color-white);
  border: 1px solid #e5cf9f;
  border-radius: 8px;
  color: #333333;
  font-family: var(--font-serif);
  font-size: 1.25rem;
  line-height: 1.25;
  text-align: center;
  text-decoration: none;
  transition:
    border-color 180ms ease,
    box-shadow 180ms ease,
    transform 180ms ease;
}

.element-page__child-title {
  display: block;
}

.element-page__child-description {
  display: block;
  margin-top: 0.4rem;
  color: var(--color-text-muted);
  font-family: var(--font-sans);
  font-size: 0.95rem;
  line-height: 1.45;
}

.element-page__child-link:hover,
.element-page__child-link:focus-visible {
  border-color: #d4ad5f;
  box-shadow: 0 10px 28px rgb(44 44 44 / 9%);
  transform: translateY(-1px);
}

.element-page__child-link:focus-visible {
  outline: 3px solid rgb(212 173 95 / 45%);
  outline-offset: 4px;
}

.element-page__status {
  max-width: 48rem;
  margin: 0 auto;
  padding: 1rem 1.25rem;
  color: var(--color-text-muted);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  font-size: 0.95rem;
}

.element-page__status--error {
  color: #7c2d2d;
  border-color: #e5b8b8;
}

@media (max-width: 768px) {
  .element-page__main {
    padding: 3rem 1rem;
  }

  .element-page__children {
    margin-top: 2rem;
  }

  .element-page__icon {
    width: 108px;
    height: 108px;
    margin-bottom: 1.4rem;
  }
}
</style>
