<template>

  <v-dialog :model-value="modelValue" max-width="520" @update:model-value="emit('update:modelValue', $event)">

    <v-card border>

      <v-card-title>修改权限</v-card-title>

      <v-card-text>

        <p v-if="fileName" class="text-body-2 mb-3 text-medium-emphasis">

          {{ fileName }}

        </p>

        <p v-if="currentSummary" class="text-caption mb-4">

          当前：{{ currentSummary }}

        </p>



        <div class="d-flex flex-wrap ga-2 mb-4">

          <v-chip

            v-for="preset in visiblePresets"

            :key="preset.mode + preset.label"

            size="small"

            variant="tonal"

            color="primary"

            @click="applyPreset(preset.mode)"

          >

            {{ preset.label }}

          </v-chip>

        </div>



        <v-text-field

          v-model="mode"

          label="八进制权限"

          variant="outlined"

          density="compact"

          placeholder="644 / 755"

          :rules="[required('权限'), octalRule]"

          class="mb-2"

          @keydown.enter="submit"

        />

        <v-text-field

          v-model="owner"

          label="属主（可选）"

          variant="outlined"

          density="compact"

          placeholder="www"

          :rules="[ownerRule]"

          class="mb-2"

        />

        <v-text-field

          v-model="group"

          label="属组（可选）"

          variant="outlined"

          density="compact"

          placeholder="www"

          :rules="[groupRule]"

          class="mb-2"

        />

        <v-checkbox
          v-if="isDirectory"
          v-model="recursive"
          label="同时修改子项（递归 chmod/chown）"
          density="compact"
          hide-details
          color="warning"
          class="mb-2"
        />

        <v-divider class="my-4" />



        <div class="d-flex align-center flex-wrap ga-2 mb-2">

          <span class="text-caption text-medium-emphasis">提权能力</span>

          <v-btn

            size="small"

            variant="tonal"

            :loading="capabilityLoading"

            :disabled="!serverId"

            @click="runCapabilityCheck"

          >

            检测提权能力

          </v-btn>

        </div>



        <v-alert

          v-if="capability"

          :type="capabilityAlertType"

          variant="tonal"

          density="compact"

          class="mb-2"

        >

          <div class="text-body-2">{{ capability.message }}</div>

          <div v-if="capability.ssh_user" class="text-caption mt-1">

            SSH 用户：{{ capability.ssh_user }}

            · 策略：{{ elevationLabel(capability.files_elevation) }}

          </div>

        </v-alert>



        <p class="text-caption text-medium-emphasis mb-0">

          非 root 用户写/chmod 失败时将按服务器策略尝试 <code>sudo -n</code>。

          示例配置见仓库 <code>{{ docsPath }}</code>（由运维审阅后安装到目标机）。

        </p>

      </v-card-text>

      <v-card-actions>

        <v-spacer />

        <v-btn variant="text" @click="emit('update:modelValue', false)">取消</v-btn>

        <v-btn color="primary" variant="flat" :loading="loading" @click="submit">应用</v-btn>

      </v-card-actions>

    </v-card>

  </v-dialog>

</template>



<script setup lang="ts">

import { computed, ref, watch } from 'vue'

import type { FileEntry, FilesCapabilityResult, FilesElevationMode } from '@/types/api'

import { http } from '@/api'

import { CHMOD_PRESETS, permissionTooltip } from '@/utils/fileMode'



const props = defineProps<{

  modelValue: boolean

  file: FileEntry | null

  serverId?: number | null

  loading?: boolean

}>()



const emit = defineEmits<{

  'update:modelValue': [boolean]

  apply: [payload: { mode: string; owner?: string; group?: string; recursive?: boolean }]

}>()



const mode = ref('')

const owner = ref('')

const group = ref('')

const capability = ref<FilesCapabilityResult | null>(null)

const capabilityLoading = ref(false)
const recursive = ref(false)

const isDirectory = computed(() => props.file?.type === 'directory')

const docsPath = computed(() => capability.value?.docs_path ?? 'docs/deploy/nexus-files-sudoers.example')



const fileName = computed(() => props.file?.name ?? '')

const currentSummary = computed(() => (props.file ? permissionTooltip(props.file) : ''))



const visiblePresets = computed(() => {

  const t = props.file?.type === 'directory' ? 'directory' : 'file'

  return CHMOD_PRESETS.filter((p) => p.for === t || p.for === 'file')

})



const capabilityAlertType = computed(() => {

  if (!capability.value) return 'info'

  if (capability.value.is_root || (capability.value.can_sudo_nopasswd && capability.value.whitelist_ok)) {

    return 'success'

  }

  if (capability.value.can_sudo_nopasswd) return 'warning'

  return 'error'

})



watch(

  () => [props.modelValue, props.file] as const,

  ([open, file]) => {

    if (!open || !file) return

    mode.value = file.mode_octal || ''

    owner.value = file.owner || ''

    group.value = file.group && file.group !== file.owner ? file.group : ''
    recursive.value = false

    capability.value = null

    if (props.serverId) {

      void runCapabilityCheck()

    }

  },

)



function elevationLabel(mode: FilesElevationMode | undefined) {

  if (mode === 'off') return '关闭提权'

  if (mode === 'always_sudo') return '始终 sudo'

  return '自动 sudo'

}



async function runCapabilityCheck() {

  if (!props.serverId) return

  capabilityLoading.value = true

  try {

    capability.value = await http.get<FilesCapabilityResult>(

      `/servers/${props.serverId}/files-capability`,

    )

  } catch (e: unknown) {

    const msg = e instanceof Error ? e.message : '检测失败'

    capability.value = {

      ssh_user: '',

      is_root: false,

      files_elevation: 'auto_sudo',

      can_sudo_nopasswd: false,

      whitelist_ok: false,

      message: msg,

      docs_path: 'docs/deploy/nexus-files-sudoers.example',

    }

  } finally {

    capabilityLoading.value = false

  }

}



function required(label: string) {

  return (v: string) => !!String(v || '').trim() || `${label}不能为空`

}



function octalRule(v: string) {

  return /^[0-7]{3,4}$/.test(String(v || '').trim()) || '格式: 3–4 位八进制'

}



const IDENT = /^[a-zA-Z0-9_.-]+$/



function ownerRule(v: string) {

  const s = String(v || '').trim()

  if (!s) return true

  return IDENT.test(s) || '仅允许字母、数字、._-'

}



function groupRule(v: string) {

  const s = String(v || '').trim()

  if (!s) return true

  if (!owner.value.trim()) return '请先填写属主'

  return IDENT.test(s) || '仅允许字母、数字、._-'

}



function applyPreset(m: string) {

  mode.value = m

}



function submit() {
  const m = mode.value.trim()
  if (!/^[0-7]{3,4}$/.test(m)) return
  const o = owner.value.trim()
  const g = group.value.trim()
  const rec = isDirectory.value && recursive.value
  if (rec) {
    const name = props.file?.name ?? '该目录'
    const ok = window.confirm(
      `确认对目录「${name}」及其全部子项递归应用权限 ${m}？此操作影响范围大且不可自动撤销。`,
    )
    if (!ok) return
  }
  emit('apply', {
    mode: m,
    owner: o || undefined,
    group: g || undefined,
    recursive: rec || undefined,
  })
}

</script>

