<!--
Copyright (c) 2026 Sidero Labs, Inc.

Use of this software is governed by the Business Source License
included in the LICENSE file.
-->
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import WordHighlighter from 'vue-word-highlighter'

import { Runtime } from '@/api/common/omni.pb'
import type { Resource } from '@/api/grpc'
import type {
  ExtensionsConfigurationSpec,
  MachineExtensionsSpec,
  MachineExtensionsStatusSpec,
  MachineStatusSpec,
} from '@/api/omni/specs/omni.pb'
import { MachineExtensionsStatusSpecItemPhase } from '@/api/omni/specs/omni.pb'
import {
  DefaultNamespace,
  ExtensionsConfigurationLabel,
  ExtensionsConfigurationType,
  LabelCluster,
  LabelClusterMachine,
  LabelMachineSet,
  MachineExtensionsStatusType,
  MachineExtensionsType,
  MachineStatusType,
} from '@/api/resources'
import type { WatchOptions } from '@/api/watch'
import Watch from '@/api/watch'
import TButton from '@/components/Button/TButton.vue'
import TIcon from '@/components/Icon/TIcon.vue'
import TListItem from '@/components/List/TListItem.vue'
import UpdateExtensionsModal from '@/components/Modals/UpdateExtensionsModal.vue'
import PageContainer from '@/components/PageContainer/PageContainer.vue'
import TSpinner from '@/components/Spinner/TSpinner.vue'
import TAlert from '@/components/TAlert.vue'
import TInput from '@/components/TInput/TInput.vue'
import { useClusterPermissions } from '@/methods/auth'
import { useResourceWatch } from '@/methods/useResourceWatch'

definePage({ name: 'NodeExtensions' })

const machineExtensionsStatus = ref<Resource<MachineExtensionsStatusSpec>>()
const machineExtensionsStatusWatch = new Watch(machineExtensionsStatus)
const updateExtensionsModalOpen = ref(false)
const searchString = ref('')
const route = useRoute()

const machineExtensionsStatusWatchLoading = machineExtensionsStatusWatch.loading

const ready = computed(() => {
  return !machineExtensionsStatusWatchLoading.value
})

const { canUpdateTalos } = useClusterPermissions(computed(() => route.params.cluster))

const { data: machineStatus } = useResourceWatch<MachineStatusSpec>(() => ({
  resource: {
    id: route.params.machine as string,
    namespace: DefaultNamespace,
    type: MachineStatusType,
  },
  runtime: Runtime.Omni,
}))

const invalidSchematic = computed(() => machineStatus.value?.spec.schematic?.invalid === true)

machineExtensionsStatusWatch.setup(
  computed((): WatchOptions | undefined => {
    return {
      resource: {
        namespace: DefaultNamespace,
        type: MachineExtensionsStatusType,
        id: route.params.machine as string,
      },
      runtime: Runtime.Omni,
    }
  }),
)

const machineExtensions = ref<Resource<MachineExtensionsSpec>>()

const machineExtensionsWatch = new Watch(machineExtensions)

machineExtensionsWatch.setup(
  computed(() => {
    if (!machineExtensionsStatus.value) {
      return
    }

    return {
      resource: {
        id: machineExtensionsStatus.value.metadata.id!,
        namespace: DefaultNamespace,
        type: MachineExtensionsType,
      },
      runtime: Runtime.Omni,
    }
  }),
)

const extensionsConfiguration = ref<Resource<ExtensionsConfigurationSpec>>()

const configurationsWatch = new Watch(extensionsConfiguration)

configurationsWatch.setup(
  computed((): WatchOptions | undefined => {
    const id = machineExtensions.value?.metadata.labels?.[ExtensionsConfigurationLabel]
    if (!id) {
      return
    }

    return {
      resource: {
        namespace: DefaultNamespace,
        type: ExtensionsConfigurationType,
        id: id,
      },
      runtime: Runtime.Omni,
    }
  }),
)

const extensionsState = computed(() => {
  if (!machineExtensionsStatus.value?.spec.extensions) {
    return []
  }

  type Item = {
    name: string
    source: string | null
    phase: MachineExtensionsStatusSpecItemPhase
  }

  const res: Item[] = []

  for (const extension of machineExtensionsStatus.value.spec.extensions) {
    if (searchString.value !== '' && !extension.name?.includes(searchString.value)) {
      continue
    }

    res.push({
      name: extension.name!,
      source: extension.immutable ? 'Automatically installed by Omni' : extensionsLevel.value,
      phase: extension.phase ?? MachineExtensionsStatusSpecItemPhase.Installed,
    })
  }

  return res
})

const extensionsLevel = computed(() => {
  if (!extensionsConfiguration.value) {
    return null
  }

  for (const label of [LabelClusterMachine, LabelMachineSet, LabelCluster]) {
    const value = extensionsConfiguration.value.metadata.labels?.[label]
    if (value) {
      switch (label) {
        case LabelClusterMachine:
          return `Defined for the machine ${value}`
        case LabelMachineSet:
          return `Inherited from the machine set ${value}`
        case LabelCluster:
          return `Inherited from the cluster ${value}`
      }
    }
  }

  return null
})
</script>

<template>
  <div class="flex h-full flex-col">
    <PageContainer class="flex grow flex-col gap-4 overflow-y-auto">
      <TInput v-model="searchString" icon="search" />
      <div class="flex flex-1 flex-col overflow-y-auto">
        <template v-if="ready && extensionsState.length > 0">
          <div
            class="mb-1 grid grid-cols-3 items-center justify-center bg-naturals-n2 px-6 py-2 text-xs"
          >
            <div>Name</div>
            <div>State</div>
            <div>Level</div>
          </div>
          <TListItem v-for="item in extensionsState" :key="item.name">
            <div class="flex gap-2 px-3">
              <div class="grid flex-1 grid-cols-3 items-center justify-center text-naturals-n12">
                <WordHighlighter
                  :query="searchString"
                  :text-to-highlight="item.name"
                  highlight-class="bg-naturals-n14"
                  class="text-naturals-n14"
                />
                <div class="flex">
                  <div
                    class="flex items-center gap-2 rounded bg-naturals-n3 px-2 py-1 text-xs text-naturals-n13"
                  >
                    <template v-if="item.phase === MachineExtensionsStatusSpecItemPhase.Installing">
                      <TIcon icon="loading" class="h-4 w-4 animate-spin text-yellow-y1" />
                      <span>Installing</span>
                    </template>
                    <template
                      v-else-if="item.phase === MachineExtensionsStatusSpecItemPhase.Removing"
                    >
                      <TIcon icon="delete" class="h-4 w-4 animate-pulse text-red-r1" />
                      <span>Removing</span>
                    </template>

                    <template
                      v-else-if="item.phase === MachineExtensionsStatusSpecItemPhase.Installed"
                    >
                      <TIcon icon="check-in-circle" class="h-4 w-4 text-green-g1" />
                      <span>Installed</span>
                    </template>
                  </div>
                </div>
                <div v-if="item.source">
                  {{ item.source }}
                </div>
              </div>
            </div>
          </TListItem>
        </template>
        <div v-else-if="!ready" class="flex flex-1 items-center justify-center">
          <TSpinner class="h-6 w-6" />
        </div>
        <div v-else class="flex-1">
          <TAlert v-if="invalidSchematic" title="Non-factory Machine" type="warn">
            This machine was not provisioned using an image factory image. Extensions cannot be
            managed by Omni for this machine.
          </TAlert>
          <TAlert v-else title="No Extensions Found" type="info" />
        </div>
      </div>
    </PageContainer>

    <div
      class="flex h-16 shrink-0 items-center justify-end border-t border-naturals-n5 bg-naturals-n1 px-12"
    >
      <TButton
        variant="highlighted"
        :disabled="!canUpdateTalos || invalidSchematic"
        @click="updateExtensionsModalOpen = true"
      >
        Update Extensions
      </TButton>
    </div>

    <UpdateExtensionsModal
      v-model:open="updateExtensionsModalOpen"
      :cluster-id="$route.params.cluster"
      :machine-id="$route.params.machine"
    />
  </div>
</template>
