<script setup lang="ts">
import { BaseButtonVariant } from 'bootstrap-vue-next'
import HapticCopy from '@/components/HapticCopy/HapticCopy.vue'
import { computed, nextTick, type ShallowRef, useTemplateRef } from 'vue'

import { AdvancedLinkTab } from '@/enums'

export interface AdvancedLinkFormTabProps {
  title: string
  link?: string
  type?: AdvancedLinkTab
  compact?: boolean
  variant?: keyof BaseButtonVariant
}

interface TextRange {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
  moveToElementText: Function
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
  select: Function
}

interface HTMLElementSupportingCreateRange extends HTMLElement {
  createTextRange(): TextRange
}

const props = withDefaults(defineProps<AdvancedLinkFormTabProps>(), {
  type: AdvancedLinkTab.raw,
  compact: false,
  variant: 'primary'
})

// bootstrap-vue-next's `size` prop doesn't accept our 'md' sentinel (it only
// covers 'sm' | 'lg', the classes it applies).
const size = computed(() => (props.compact ? 'sm' : undefined))

const inputRef = useTemplateRef<HTMLInputElement>('input')

const text = computed(() => {
  switch (props.type) {
    case AdvancedLinkTab.rich:
    case AdvancedLinkTab.html:
      return linkAsHtml.value
    case AdvancedLinkTab.markdown:
      return linkAsMarkdown.value
    case AdvancedLinkTab.raw:
    default:
      return props.link
  }
})

const titleOrLink = computed(() => props.title || props.link)
const linkAsHtml = computed(
  () => `<a href="${props.link}" target="_blank">${titleOrLink.value}</a>`
)
const linkAsMarkdown = computed(() => `[${titleOrLink.value}](${props.link})`)
const isRich = computed(() => props.type === AdvancedLinkTab.rich)
const isHTML = computed(() => props.type === AdvancedLinkTab.html)

function select() {
  if (props.type === AdvancedLinkTab.rich) {
    selectRich(inputRef)
  }
  else {
    selectInput(inputRef)
  }
}

async function selectInput(
  target: Readonly<ShallowRef<HTMLInputElement | null>>
) {
  // wait for the copy to finish to select text
  await nextTick()
  // The ref is on <b-form-input>, which exposes the real input as `element`;
  // the component instance itself has no select().
  const input = (
    target.value as unknown as { element?: HTMLInputElement | null }
  )?.element
  input?.select()
}

async function selectRich(
  target: Readonly<ShallowRef<HTMLInputElement | null>>
) {
  if (!target.value) {
    return
  }
  // wait for the copy to finish to select text
  await nextTick()
  const selection = window.getSelection ? window.getSelection() : null
  if (selection) {
    const range = document.createRange()
    range.selectNodeContents(target.value)
    selection.removeAllRanges()
    selection.addRange(range)
  }
  else if (
    (document.body as HTMLElementSupportingCreateRange).createTextRange
  ) {
    const range = (
      document.body as HTMLElementSupportingCreateRange
    ).createTextRange()
    range.moveToElementText(target.value)
    range.select()
  }
}
</script>

<template>
  <div
    class="advanced-link-form-tab"
    :class="{ small: compact }"
  >
    <b-input-group :size="size">
      <a
        v-if="isRich"
        ref="input"
        :href="link"
        class="form-control advanced-link-form-tab__input advanced-link-form-tab__input--rich"
        @click.prevent="select"
      >
        {{ titleOrLink }}
      </a>
      <b-form-input
        v-else
        ref="input"
        readonly
        :model-value="text"
        class="advanced-link-form-tab__input"
        @click.prevent="select"
      />
      <haptic-copy
        :html="isHTML"
        :text="text"
        :plain="link"
        :variant="variant"
        @attempt="select"
      />
    </b-input-group>
  </div>
</template>

<style scoped lang="scss">
.advanced-link-form-tab {
  text-align: left;

  &__input {
    background: $input-bg;
    &--rich {
      text-align: center;
      text-decoration: underline;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      cursor: text;
    }
  }
}
</style>
