<template>
  <button
    type="button"
    class="vm-switch"
    :class="{ 'is-on': on, 'is-loading': loading }"
    :disabled="loading || disabled"
    role="switch"
    :aria-checked="on"
    @click="toggle"
  >
    <span class="vm-switch__knob" />
  </button>
</template>

<script setup lang="ts">
import { crudLabel } from '../style'
import { ref, computed, watch } from 'vue'
import { toast } from 'vue-sonner'
import { vmConfirm } from '../confirm'
import { useCrud } from '../useCrud'

defineOptions({ name: 'vm-switch' })

type DictOpt = { label?: string; value?: number | string | boolean }

const props = withDefaults(
  defineProps<{
    modelValue?: number | string | boolean
    activeValue?: number | string | boolean
    inactiveValue?: number | string | boolean
    /** 二值字典（dict.get），优先于 active/inactive */
    options?: DictOpt[]
    /** 行数据（表格内） */
    scope?: Record<string, unknown>
    column?: { property?: string; prop?: string }
    check?: boolean
    disabled?: boolean
    api?: (data: Record<string, unknown>) => Promise<unknown>
  }>(),
  {
    activeValue: true,
    inactiveValue: false,
    check: false,
  },
)

const emit = defineEmits<{
  'update:modelValue': [v: number | string | boolean]
  change: [v: number | string | boolean]
}>()

const crud = useCrud()
const loading = ref(false)
const status = ref(props.modelValue)

watch(
  () => props.modelValue,
  (v) => {
    status.value = v
  },
)

/** options 按 value 排序：小=关、大=开（status/yes_no_number 0|1、yes_no_boolean false|true） */
const pair = computed(() => {
  const opts = props.options
  if (opts && opts.length >= 2) {
    const sorted = [...opts].sort((a, b) => {
      const na = Number(a.value)
      const nb = Number(b.value)
      if (Number.isFinite(na) && Number.isFinite(nb)) return na - nb
      return String(a.value).localeCompare(String(b.value))
    })
    return {
      inactive: sorted[0]!.value as number | string | boolean,
      active: sorted[sorted.length - 1]!.value as number | string | boolean,
    }
  }
  return {
    inactive: props.inactiveValue as number | string | boolean,
    active: props.activeValue as number | string | boolean,
  }
})

const on = computed(
  () =>
    status.value === pair.value.active ||
    status.value === true ||
    status.value === 1,
)

async function toggle() {
  if (loading.value || props.disabled) return
  const next = on.value ? pair.value.inactive : pair.value.active
  if (props.check && !(await vmConfirm(crudLabel('switchConfirm')))) return

  const prev = status.value
  status.value = next
  emit('update:modelValue', next!)
  emit('change', next!)

  const prop = props.column?.property || props.column?.prop
  if (props.scope && prop) {
    loading.value = true
    try {
      const params = { id: props.scope.id, [prop]: next }
      if (props.api) await props.api(params)
      else if (crud.service.update) await crud.service.update(params)
      else throw new Error(crudLabel('noUpdateApi'))
    } catch (e) {
      status.value = prev
      emit('update:modelValue', prev as number | string | boolean)
      toast.error(e instanceof Error ? e.message : crudLabel('updateFailed'))
    } finally {
      loading.value = false
    }
  }
}
</script>

<style lang="scss" scoped>
.vm-switch {
  position: relative;
  width: 40px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: var(--muted);
  cursor: pointer;
  transition: background 0.15s ease;

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

  &.is-loading,
  &:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }
}

.vm-switch__knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  transition: transform 0.15s ease;
  box-shadow: 0 1px 2px rgb(0 0 0 / 12%);

  .is-on & {
    transform: translateX(18px);
  }
}
</style>
