<template>
  <div v-if="request" ref="curlViewContainer" class="space-y-2">
    <div class="rounded-md border border-divider overflow-hidden">
      <div
        class="flex items-center justify-between p-2 bg-divider/30 border-b border-divider"
      >
        <div class="text-sm font-medium text-secondaryDark flex items-center">
          <icon-lucide-terminal class="mr-2" size="14" />
          {{ t("documentation.curl.title") }}
        </div>
        <div class="flex items-center space-x-2">
          <div>
            <tippy
              interactive
              trigger="click"
              theme="popover"
              placement="bottom"
              :on-shown="() => tippyActions?.focus()"
            >
              <HoppSmartSelectWrapper>
                <HoppButtonSecondary
                  :label="
                    CodegenDefinitions.find((x) => x.name === codegenType)!
                      .caption
                  "
                  outline
                  class="flex-1 pr-8"
                />
              </HoppSmartSelectWrapper>
              <template #content="{ hide }">
                <div class="flex flex-col space-y-2">
                  <div class="sticky top-0 z-10 flex-shrink-0 overflow-x-auto">
                    <input
                      v-model="searchQuery"
                      type="search"
                      autocomplete="off"
                      class="input flex w-full !bg-primaryContrast p-4 py-2"
                      :placeholder="`${t('action.search')}`"
                    />
                  </div>
                  <div
                    ref="tippyActions"
                    class="flex flex-col focus:outline-none"
                    tabindex="0"
                    @keyup.escape="hide()"
                  >
                    <HoppSmartItem
                      v-for="codegen in filteredCodegenDefinitions"
                      :key="codegen.name"
                      :label="codegen.caption"
                      :info-icon="
                        codegen.name === codegenType ? IconCheck : undefined
                      "
                      :active-info-icon="codegen.name === codegenType"
                      @click="
                        () => {
                          codegenType = codegen.name
                          codegenMode = codegen.lang
                          hide()
                        }
                      "
                    />

                    <HoppSmartPlaceholder
                      v-if="
                        searchQuery.length !== 0 &&
                        filteredCodegenDefinitions.length === 0
                      "
                      :text="`${t('state.nothing_found')} ‟${searchQuery}”`"
                    >
                      <template #icon>
                        <icon-lucide-search class="svg-icons opacity-75" />
                      </template>
                    </HoppSmartPlaceholder>
                  </div>
                </div>
              </template>
            </tippy>
          </div>
          <div class="flex space-x-2">
            <HoppSmartItem
              :icon="curlCopied ? IconCheck : IconCopy"
              :title="t('documentation.curl.copy_to_clipboard')"
              @click="copyCurlCommand"
            />
          </div>
        </div>
      </div>

      <div class="curl-editor flex-1 w-full p-4 overflow-auto">
        <!-- Lazy load the code view -->
        <div v-if="!isVisible" class="text-secondary text-center py-8">
          <div class="text-sm">{{ t("documentation.curl.click_to_load") }}</div>
          <HoppButtonSecondary
            :label="t('documentation.curl.load')"
            class="mt-2"
            @click="loadCurl"
          />
        </div>
        <pre
          v-else-if="highlightedCode"
          ref="curlCodeContainer"
          class="hljs-container max-w-[35rem]"
        ><code
          class="hljs language-bash break-words overflow-auto"
          v-html="highlightedCode"
        ></code></pre>
        <!-- XSS Note: highlightedCode is safely generated by highlight.js library -->
        <div v-else-if="isLoading" class="text-secondary text-center py-4">
          <div class="animate-pulse">
            {{ t("documentation.curl.generating") }}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import {
  Environment,
  HoppCollectionVariable,
  HoppRESTRequest,
  makeRESTRequest,
} from "@hoppscotch/data"
import { ref, computed, onMounted, onUnmounted, watch } from "vue"
import { useIntersectionObserver } from "@vueuse/core"
import {
  CodegenDefinitions,
  CodegenLang,
  CodegenName,
  generateCode,
} from "~/helpers/new-codegen"
import IconCheck from "~icons/lucide/check"
import IconCopy from "~icons/lucide/copy"
import { useI18n } from "~/composables/i18n"
import * as O from "fp-ts/Option"
import { useToast } from "~/composables/toast"
import { useService } from "dioc/vue"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import {
  getEffectiveRESTRequest,
  resolvesEnvsInBody,
} from "~/helpers/utils/EffectiveURL"
import {
  AggregateEnvironment,
  getCurrentEnvironment,
} from "~/newstore/environments"
import { CurrentValueService } from "~/services/current-environment-value.service"
import hljs from "highlight.js/lib/core"
import javascript from "highlight.js/lib/languages/javascript"
import python from "highlight.js/lib/languages/python"
import php from "highlight.js/lib/languages/php"
import go from "highlight.js/lib/languages/go"
import ruby from "highlight.js/lib/languages/ruby"
import java from "highlight.js/lib/languages/java"
import csharp from "highlight.js/lib/languages/csharp"
import powershell from "highlight.js/lib/languages/powershell"
import http from "highlight.js/lib/languages/http"
import r from "highlight.js/lib/languages/r"
import rust from "highlight.js/lib/languages/rust"
import swift from "highlight.js/lib/languages/swift"
import kotlin from "highlight.js/lib/languages/kotlin"
import clojure from "highlight.js/lib/languages/clojure"
import hljsCurl from "highlightjs-curl"

// Import highlight.js CSS for base styling
import "highlight.js/styles/github-dark.css"

// Initialize highlight.js languages only once globally
const initializeHighlightJS = (() => {
  let initialized = false
  return () => {
    if (initialized) return
    initialized = true

    // Register all languages for highlight.js
    hljs.registerLanguage("bash", hljsCurl)
    hljs.registerLanguage("shell", hljsCurl)
    hljs.registerLanguage("javascript", javascript)
    hljs.registerLanguage("python", python)
    hljs.registerLanguage("php", php)
    hljs.registerLanguage("go", go)
    hljs.registerLanguage("ruby", ruby)
    hljs.registerLanguage("java", java)
    hljs.registerLanguage("csharp", csharp)
    hljs.registerLanguage("powershell", powershell)
    hljs.registerLanguage("http", http)
    hljs.registerLanguage("r", r)
    hljs.registerLanguage("rust", rust)
    hljs.registerLanguage("swift", swift)
    hljs.registerLanguage("kotlin", kotlin)
    hljs.registerLanguage("clojure", clojure)
    hljs.registerLanguage("c", hljsCurl)
    hljs.registerLanguage("objc", hljsCurl)
    hljs.registerLanguage("ocaml", hljsCurl)
  }
})()

const t = useI18n()
const toast = useToast()

const props = withDefaults(
  defineProps<{
    request?: HoppRESTRequest | null
    collectionID?: string
    collectionPath?: string | null
    folderPath?: string | null
    requestIndex?: number | null
    teamID?: string
    inheritedProperties?: HoppInheritedProperty
  }>(),
  {
    request: null,
    collectionID: "",
    collectionPath: null,
    folderPath: null,
    requestIndex: null,
    teamID: undefined,
  }
)

const curlCodeContainer = ref<HTMLElement | null>(null)
const curlViewContainer = ref<HTMLElement | null>(null)
const curlCopied = ref<boolean>(false)
const codegenType = ref<CodegenName>("shell-curl")
const codegenMode = ref<CodegenLang>("shell")
const isVisible = ref(false)
const isLoading = ref(false)
const tippyActions = ref<HTMLElement | null>(null)
const searchQuery = ref("")

// Set up intersection observer to auto-load cURL when component enters viewport
const { stop } = useIntersectionObserver(
  curlViewContainer,
  ([{ isIntersecting }]) => {
    if (isIntersecting && !isVisible.value && !isLoading.value) {
      loadCurl()
    }
  },
  {
    threshold: 0.1,
    rootMargin: "50px",
  }
)

const filteredCodegenDefinitions = computed(() => {
  return CodegenDefinitions.filter((obj) =>
    Object.values(obj).some((val) =>
      val.toLowerCase().includes(searchQuery.value.toLowerCase())
    )
  )
})

const currentEnvironmentValueService = useService(CurrentValueService)

const getCurrentValue = (env: AggregateEnvironment) => {
  const currentSelectedEnvironment = getCurrentEnvironment()
  let value: string | undefined

  if (env && env.secret) {
    value = env.currentValue
  } else {
    value = currentEnvironmentValueService.getEnvironmentByKey(
      env?.sourceEnv !== "Global" ? currentSelectedEnvironment.id : "Global",
      env?.key ?? ""
    )?.currentValue
  }

  return value
}

const getFinalURL = (input: string): string => {
  if (!input) {
    return "https://"
  }

  let url = input.trim()

  url = url.replace(/^https?:\s*\/+\s*/i, (match) =>
    match.toLowerCase().startsWith("https") ? "https://" : "http://"
  )

  if (!/^https?:\/\//i.test(url) && !url.startsWith("<<")) {
    const endpoint = url
    const domain = endpoint.split(/[/:#?]+/)[0]

    const isLocalOrIP = /^(localhost|(\d{1,3}\.){3}\d{1,3})$/.test(domain)
    url = (isLocalOrIP ? "http://" : "https://") + endpoint
  }

  return url
}

const getEffectiveRequest = async () => {
  if (!props.request) return null

  let collectionVariables: HoppCollectionVariable[] = []

  if (props.inheritedProperties) {
    collectionVariables = props.inheritedProperties.variables.flatMap(
      (parentVar) =>
        parentVar.inheritedVariables.map((variable) => ({
          key: variable.key,
          initialValue: variable.initialValue,
          currentValue: variable.currentValue,
          secret: variable.secret,
        }))
    )
  }

  const requestVariables = props.request.requestVariables.map(
    (requestVariable) => {
      if (requestVariable.active)
        return {
          key: requestVariable.key,
          currentValue: requestVariable.value,
          initialValue: requestVariable.value,
          secret: false,
        }
      return {}
    }
  )

  const env: Environment = {
    v: 2,
    id: "env",
    name: "Env",
    variables: [
      ...(requestVariables as Environment["variables"]),
      ...collectionVariables.map((envVar) => ({
        ...envVar,
        currentValue:
          getCurrentValue({
            ...envVar,
            sourceEnv: "Global",
          } as AggregateEnvironment) || envVar.initialValue,
      })),
    ],
  }

  const effectiveReq = await getEffectiveRESTRequest(
    makeRESTRequest({
      ...props.request,
    }),
    env,
    true
  )

  const result = { effectiveRequest: effectiveReq, env }

  return result
}

// Lazy computed for cURL command
const curlCommand = ref<string>("")

const generateCurlCommand = async () => {
  if (!props.request) {
    curlCommand.value = "# No request data available"
    return
  }

  isLoading.value = true

  try {
    const lang = codegenType.value
    const res = await getEffectiveRequest()

    if (!res) {
      curlCommand.value = ""
      return
    }

    const { effectiveRequest, env } = res

    const result = generateCode(
      lang,
      makeRESTRequest({
        ...effectiveRequest,
        body: resolvesEnvsInBody(effectiveRequest.body, env),
        headers: effectiveRequest.effectiveFinalHeaders.map((header: any) => ({
          ...header,
          active: true,
        })),
        params: effectiveRequest.effectiveFinalParams.map((param: any) => ({
          ...param,
          active: true,
        })),
        endpoint: getFinalURL(effectiveRequest.effectiveFinalURL),
        requestVariables: effectiveRequest.effectiveFinalRequestVariables.map(
          (requestVariable: any) => ({
            ...requestVariable,
            active: true,
          })
        ),
      })
    )

    if (O.isSome(result)) {
      curlCommand.value = result.value
    } else {
      curlCommand.value = ""
    }
  } catch (error) {
    console.error("Error generating cURL command:", error)
    curlCommand.value = "# Error generating cURL command"
  } finally {
    isLoading.value = false
  }
}

const highlightedCode = computed(() => {
  if (!curlCommand.value || !isVisible.value) return ""

  try {
    // Map codegen language to highlight.js language
    const languageMap: Record<string, string> = {
      shell: "bash",
      javascript: "javascript",
      python: "python",
      php: "php",
      go: "go",
      ruby: "ruby",
      java: "java",
      csharp: "csharp",
      powershell: "powershell",
      http: "http",
      r: "r",
      rust: "rust",
      swift: "swift",
      kotlin: "kotlin",
      clojure: "clojure",
      c: "bash",
      objc: "bash",
      ocaml: "bash",
    }

    const language = languageMap[codegenMode.value] || "bash"

    const highlighted = hljs.highlight(curlCommand.value, {
      language,
      ignoreIllegals: true,
    })
    return highlighted.value
  } catch (error) {
    console.error("Syntax highlighting failed:", error)

    // Fallback to auto-detection
    try {
      const autoHighlighted = hljs.highlightAuto(curlCommand.value, [
        "bash",
        "shell",
      ])
      return autoHighlighted.value
    } catch (autoError) {
      console.error("Auto highlighting also failed:", autoError)
      // Final fallback to plain text with HTML escaping
      return curlCommand.value
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#39;")
    }
  }
})

// Load cURL command when needed
const loadCurl = async () => {
  if (!isVisible.value) {
    isVisible.value = true
    await generateCurlCommand()
  }
}

// Watch for codegen type changes and regenerate
watch([codegenType, codegenMode], () => {
  if (isVisible.value) {
    generateCurlCommand()
  }
})

const copyCurlCommand = async () => {
  if (!curlCommand.value) return
  try {
    await navigator.clipboard.writeText(curlCommand.value)
    curlCopied.value = true
    setTimeout(() => {
      curlCopied.value = false
    }, 2000)
    toast.success(t("documentation.curl.copied"))
  } catch (err) {
    console.error("Failed to copy cURL command: ", err)
  }
}

// Initialize highlight.js once
onMounted(() => {
  initializeHighlightJS()
})

onUnmounted(() => {
  // Stop intersection observer
  stop()
})
</script>

<style scoped>
/* Override highlight.js theme colors to match Hoppscotch theme */
:deep(.hljs) {
  background: rgb(var(--primary-light-color)) !important;
  color: rgb(var(--secondary-dark-color)) !important;
  padding: 0;
  margin: 0;
  overflow: visible;
  display: block;
  line-height: 1.5;
}

:deep(.hljs-container) {
  background-color: rgb(var(--primary-light-color));
  border-radius: 0;
  margin: 0;
  padding: 0;
}

/* for block of numbers */
.hljs-ln-numbers {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;

  text-align: center;
  color: #ccc;
  border-right: 1px solid #ccc;
  vertical-align: top;
  padding-right: 5px;

  /* your custom style here */
}

/* for block of code */
.hljs-ln-code {
  padding-left: 10px;
}
</style>
