<template>
  <Dialog :open="open" @update:open="(v) => emit('update:open', v)">
    <DialogContent
      shell="crud"
      class="vm-auto-perm"
      @pointer-down-outside="onOutside"
      @focus-outside="onOutside"
      @interact-outside="onOutside"
    >
      <DialogHeader class="vm-auto-perm__head">
        <DialogTitle>{{ crudLabel('autoPermTitle') }}</DialogTitle>
      </DialogHeader>

      <div class="vm-auto-perm__body">
        <label class="vm-auto-perm__label">{{ crudLabel('autoPermController') }}</label>
        <vm-group-cascader
          :model-value="selectedPrefix"
          :tree="entityTree"
          :placeholder="crudLabel('pickEntity')"
          :editable="false"
          :clearable="true"
          @update:model-value="onEntityPick"
        />

        <div v-if="rows.length" class="vm-auto-perm__list">
          <div v-for="(row, idx) in rows" :key="row.perm" class="vm-auto-perm__row">
            <button
              type="button"
              class="vm-auto-perm__switch"
              :class="{ 'is-on': row.enabled }"
              role="switch"
              :aria-checked="row.enabled"
              @click="row.enabled = !row.enabled"
            >
              <span class="vm-auto-perm__switch-knob" />
            </button>
            <input
              v-model="row.name"
              class="vm-auto-perm__name"
              :placeholder="crudLabel('routeRemark')"
            />
            <span class="vm-auto-perm__code">{{ row.perm }}</span>
            <button
              type="button"
              class="vm-auto-perm__remove"
              :title="crudLabel('remove')"
              @click="rows.splice(idx, 1)"
            >
              <i class="ri-close-line" />
            </button>
          </div>
        </div>
        <p v-else class="vm-auto-perm__hint">{{ crudLabel('autoPermHint') }}</p>
      </div>

      <DialogFooter class="vm-auto-perm__foot">
        <button type="button" class="vm-auto-perm__btn is-ghost" @click="emit('update:open', false)">
          {{ crudLabel('close') }}
        </button>
        <button
          type="button"
          class="vm-auto-perm__btn is-primary"
          :disabled="!enabledRows.length || submitting"
          @click="submit"
        >
          {{ crudLabel('addAll') }}
        </button>
      </DialogFooter>
    </DialogContent>
  </Dialog>
</template>

<script setup lang="ts">
import { crudFormat, crudLabel } from '../crud/style'
import { ref, computed, watch } from 'vue'
import { api } from '../api/client'
import { toast } from 'vue-sonner'
import { isDialogFloatTarget } from '../lib/dialog-float'
import type {
  AutoPermRow,
  EpsEntity,
  EpsModuleMap,
} from '../../../typings/admin/comm/eps'
import type { GroupCascaderNode } from '../../../typings/admin/comm/cascader'

defineOptions({ name: 'vm-auto-perm-dialog' })

const props = defineProps<{
  open: boolean
  /** 上级菜单 id；顶级为 null */
  parentId: number | null
  add: (
    data: Record<string, unknown> | Record<string, unknown>[],
  ) => Promise<unknown>
}>()

const emit = defineEmits<{
  'update:open': [value: boolean]
  done: []
}>()

const eps = ref<EpsModuleMap>({})
const selectedEntity = ref<EpsEntity | null>(null)
const rows = ref<AutoPermRow[]>([])
const submitting = ref(false)

const selectedPrefix = computed(() => selectedEntity.value?.prefix ?? '')

const entityTree = computed<GroupCascaderNode[]>(() => {
  return Object.keys(eps.value)
    .sort()
    .map((mod) => ({
      label: mod,
      value: `mod:${mod}`,
      isLeaf: false,
      children: (eps.value[mod] || []).map((ent) => ({
        label: entLabel(ent),
        value: ent.prefix,
        isLeaf: true,
      })),
    }))
})

const enabledRows = computed(() => rows.value.filter((r) => r.enabled && r.name.trim()))

function entLabel(ent: EpsEntity) {
  return (
    ent.info?.type?.name ||
    ent.prefix.split('/').filter(Boolean).pop() ||
    ent.prefix
  )
}

function permOf(api: { perms?: string[]; method: string; path: string }) {
  return api.perms?.[0] || `${api.method}:${api.path}`
}

function pickEntity(ent: EpsEntity) {
  selectedEntity.value = ent
  rows.value = (ent.api || [])
    .map((api) => ({
      enabled: true,
      name: api.summary || api.path,
      perm: permOf(api),
    }))
    .filter((r) => r.perm)
}

function onEntityPick(prefix: string) {
  if (!prefix || prefix.startsWith('mod:')) {
    if (!prefix) {
      selectedEntity.value = null
      rows.value = []
    }
    return
  }
  for (const list of Object.values(eps.value)) {
    const ent = (list || []).find((e) => e.prefix === prefix)
    if (ent) {
      pickEntity(ent)
      return
    }
  }
}

function onOutside(event: Event) {
  if (isDialogFloatTarget(event)) event.preventDefault()
}

async function submit() {
  const list = enabledRows.value
  if (!list.length) {
    toast.warning(crudLabel('enableOnePerm'))
    return
  }
  submitting.value = true
  try {
    const rows = list.map((row) => ({
      type: 2,
      name: row.name.trim(),
      perms: row.perm,
      parentId: props.parentId,
      orderNum: 0,
      router: null,
      viewPath: null,
      icon: null,
      isShow: true,
      keepAlive: true,
    }))
    await props.add(rows)
    toast.success(crudFormat(crudLabel('addedPerms'), rows.length))
    emit('update:open', false)
    emit('done')
  } catch (e) {
    console.error(e)
    toast.error(crudLabel('addFailed'))
  } finally {
    submitting.value = false
  }
}

watch(
  () => props.open,
  async (v) => {
    if (!v) return
    selectedEntity.value = null
    rows.value = []
    try {
      eps.value = (await loadEps()) || {}
    } catch {
      eps.value = {}
    }
  },
)
</script>

<style lang="scss" scoped>
.vm-auto-perm__head {
  padding: 12px 22px;
  border-bottom: 1px solid var(--border);
}

.vm-auto-perm__body {
  display: flex;
  max-height: min(60vh, 480px);
  flex-direction: column;
  gap: 12px;
  overflow: auto;
  overscroll-behavior: contain;
  padding: 18px 22px;

  @media (max-width: 768px) {
    flex: 1;
    max-height: none;
  }
}

.vm-auto-perm__label {
  font-size: 12px;
  color: var(--muted-foreground);
}

.vm-auto-perm__list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.vm-auto-perm__row {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) minmax(140px, 0.9fr) 28px;
  align-items: center;
  gap: 8px;
}

.vm-auto-perm__switch {
  position: relative;
  width: 44px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: var(--muted);
  cursor: pointer;

  &.is-on {
    background: var(--brand);
  }
}

.vm-auto-perm__switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
  transition: transform 0.15s ease;

  .is-on & {
    transform: translateX(20px);
  }
}

.vm-auto-perm__name {
  height: 36px;
  padding: 0 10px;
  border: 1px solid var(--input);
  border-radius: 10px;
  background: var(--background);
  color: var(--foreground);
  font-size: 13px;
}

.vm-auto-perm__code {
  display: inline-flex;
  height: 36px;
  align-items: center;
  overflow: hidden;
  padding: 0 10px;
  border-radius: 8px;
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 13px;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.vm-auto-perm__remove {
  display: inline-flex;
  width: 28px;
  height: 28px;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--muted-foreground);
  cursor: pointer;

  &:hover {
    background: var(--muted);
    color: var(--foreground);
  }
}

.vm-auto-perm__hint {
  color: var(--muted-foreground);
  font-size: 12px;
}

.vm-auto-perm__foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 10px 22px;
  border-top: 1px solid var(--border);
}

.vm-auto-perm__btn {
  height: 36px;
  padding: 0 16px;
  border: none;
  border-radius: 12px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;

  &:disabled {
    cursor: not-allowed;
    opacity: 0.45;
  }

  &.is-primary {
    background: var(--brand);
    color: #fff;

    &:hover:not(:disabled) {
      background: var(--brand-deep);
    }
  }

  &.is-ghost {
    background: var(--muted);
    color: var(--foreground);
  }
}
</style>
