<script setup lang="ts">
import type { FormInstance } from 'element-plus';

import type { ApprovalAssigneeType as AssigneeType } from './approval-flow-restriction';

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

import { EasyFlowFormModal } from '@easyflow/common-ui';

import {
  ElButton,
  ElDivider,
  ElForm,
  ElFormItem,
  ElInput,
  ElInputNumber,
  ElMessage,
  ElMessageBox,
  ElOption,
  ElSelect,
  ElSwitch,
  ElTreeSelect,
} from 'element-plus';

import { api } from '#/api/request';
import { $t } from '#/locales';

import {
  applyApplicantDeptRestriction,
  isApplicantDeptAssigneeTypeDisabled,
  needsApplicantDeptRestrictionConfirmation,
  normalizeApplicantDeptRestriction,
  serializeApplicantDeptRestriction,
} from './approval-flow-restriction';

const emit = defineEmits<{
  reload: [];
}>();

defineExpose({
  openDialog,
});

type ResourceType = '' | 'AGENT' | 'BOT' | 'KNOWLEDGE' | 'WORKFLOW';
type ActionType = '' | 'DELETE' | 'OFFLINE' | 'PUBLISH';
type ScopeType = 'CATEGORY' | 'DEPT';
type FlowStatus = 'DISABLED' | 'ENABLED';

interface SelectOption {
  id: number | string;
  label: string;
}

interface ScopeItem {
  id?: number | string;
  includeChildren: boolean;
  scopeType: ScopeType;
  scopeValue?: number | string;
}

interface StepItem {
  assigneeTargetIds: Array<number | string>;
  assigneeTargets: AssigneeTarget[];
  assigneeType: AssigneeType;
  includeChildrenByTarget: Record<string, boolean>;
  id?: number | string;
  restrictToApplicantDept: boolean;
  stepName: string;
}

interface AssigneeTarget {
  includeChildren: boolean;
  targetCode?: string;
  targetId: number | string;
  targetName?: string;
}

interface FlowFormModel {
  actionType: ActionType;
  id?: number | string;
  name: string;
  priority: number;
  remark: string;
  resourceType: ResourceType;
  scopes: ScopeItem[];
  status: FlowStatus;
  steps: StepItem[];
}

const RESOURCE_OPTIONS = [
  { label: $t('approval.resource.agent'), value: 'AGENT' },
  { label: $t('approval.resource.workflow'), value: 'WORKFLOW' },
  { label: $t('approval.resource.knowledge'), value: 'KNOWLEDGE' },
];

const ACTION_OPTIONS = [
  { label: $t('approval.action.publish'), value: 'PUBLISH' },
  { label: $t('approval.action.offline'), value: 'OFFLINE' },
  { label: $t('approval.action.delete'), value: 'DELETE' },
];

const STATUS_OPTIONS = [
  { label: $t('approval.status.enabled'), value: 'ENABLED' },
  { label: $t('approval.status.disabled'), value: 'DISABLED' },
];

const SCOPE_TYPE_OPTIONS = [
  { label: $t('approval.scope.category'), value: 'CATEGORY' },
  { label: $t('approval.scope.dept'), value: 'DEPT' },
];

const ASSIGNEE_TYPE_OPTIONS: Array<{ label: string; value: AssigneeType }> = [
  { label: $t('approval.assignee.role'), value: 'ROLE' },
  { label: $t('approval.assignee.user'), value: 'USER' },
  { label: $t('approval.assignee.dept'), value: 'DEPT' },
];
const ENABLED_DATA_STATUS = 1;
const ASSIGNEE_VISIBLE_TAG_COUNT = 2;

const saveForm = ref<FormInstance>();
const dialogVisible = ref(false);
const isAdd = ref(true);
const btnLoading = ref(false);
const categoryLoaded = ref(false);
const roleLoaded = ref(false);
const accountLoading = ref(false);
const deptTreeOptions = ref<any[]>([]);
const roleOptions = ref<SelectOption[]>([]);
const accountOptions = ref<SelectOption[]>([]);
const categoryOptions = ref<Record<Exclude<ResourceType, ''>, SelectOption[]>>({
  AGENT: [],
  BOT: [],
  KNOWLEDGE: [],
  WORKFLOW: [],
});
const deptOptionMap = computed(() => {
  const result = new Map<string, string>();
  const visit = (items: any[]) => {
    for (const item of items) {
      result.set(String(item.id), item.deptName || String(item.id));
      if (Array.isArray(item.children)) {
        visit(item.children);
      }
    }
  };
  visit(deptTreeOptions.value);
  return result;
});

const formModel = ref<FlowFormModel>(buildDefaultForm());

const rules = {
  actionType: [
    { required: true, message: $t('message.required'), trigger: 'change' },
  ],
  name: [{ required: true, message: $t('message.required'), trigger: 'blur' }],
  priority: [
    { required: true, message: $t('message.required'), trigger: 'change' },
  ],
  resourceType: [
    { required: true, message: $t('message.required'), trigger: 'change' },
  ],
  status: [
    { required: true, message: $t('message.required'), trigger: 'change' },
  ],
};

watch(
  () => formModel.value.resourceType,
  (resourceType) => {
    formModel.value.scopes = formModel.value.scopes.map((scope) => {
      if (scope.scopeType === 'CATEGORY') {
        return {
          ...scope,
          scopeValue: undefined,
        };
      }
      return scope;
    });
    if (resourceType) {
      void ensureCategoryOptions();
    }
  },
);

function buildDefaultForm(): FlowFormModel {
  return {
    actionType: 'PUBLISH',
    name: '',
    priority: 100,
    remark: '',
    resourceType: 'AGENT',
    scopes: [],
    status: 'ENABLED',
    steps: [buildDefaultStep()],
  };
}

function buildDefaultStep(): StepItem {
  return {
    assigneeTargetIds: [],
    assigneeTargets: [],
    assigneeType: 'ROLE',
    includeChildrenByTarget: {},
    restrictToApplicantDept: false,
    stepName: '',
  };
}

async function openDialog(row: any = {}) {
  isAdd.value = !row?.id;
  formModel.value = buildDefaultForm();
  dialogVisible.value = true;
  await Promise.all([
    ensureCategoryOptions(),
    ensureDeptOptions(),
    ensureRoleOptions(),
  ]);
  if (!row?.id) {
    await nextTick();
    saveForm.value?.clearValidate();
    return;
  }
  btnLoading.value = true;
  try {
    const res = await api.get('/api/v1/approvalFlow/detail', {
      params: {
        id: row.id,
      },
    });
    if (res.errorCode !== 0) {
      return;
    }
    formModel.value = {
      actionType: res.data?.actionType || 'PUBLISH',
      id: res.data?.id,
      name: res.data?.name || '',
      priority: Number(res.data?.priority || 100),
      remark: res.data?.remark || '',
      resourceType: res.data?.resourceType || 'AGENT',
      scopes: (res.data?.scopes || []).map((item: any) => ({
        id: item.id,
        includeChildren: item.includeChildren === 1,
        scopeType: item.scopeType,
        scopeValue: item.scopeValue,
      })),
      status: res.data?.status || 'ENABLED',
      steps: (res.data?.steps || []).map((item: any) => {
        const targets = normalizeAssigneeTargets(item);
        return {
          assigneeTargetIds: targets.map((target) => target.targetId),
          assigneeTargets: targets,
          assigneeType: item.assigneeType || 'ROLE',
          id: item.id,
          includeChildrenByTarget: buildIncludeChildrenByTarget(targets),
          restrictToApplicantDept: normalizeApplicantDeptRestriction(
            item.restrictToApplicantDept,
          ),
          stepName: item.stepName,
        };
      }),
    };
    if (formModel.value.steps.length === 0) {
      formModel.value.steps = [buildDefaultStep()];
    }
    for (const step of formModel.value.steps) {
      registerAccountOption(step);
    }
    if (formModel.value.steps.some((item) => item.assigneeType === 'USER')) {
      await searchAccountOptions('');
    }
  } finally {
    btnLoading.value = false;
    await nextTick();
    saveForm.value?.clearValidate();
  }
}

async function ensureCategoryOptions() {
  if (categoryLoaded.value) {
    return;
  }
  const [agentRes, workflowRes, knowledgeRes] = await Promise.all([
    api.get('/api/v1/agentCategory/list', {
      params: { sortKey: 'sortNo', sortType: 'asc' },
    }),
    api.get('/api/v1/workflowCategory/list', {
      params: { sortKey: 'sortNo', sortType: 'asc' },
    }),
    api.get('/api/v1/documentCollectionCategory/list', {
      params: { sortKey: 'sortNo', sortType: 'asc' },
    }),
  ]);
  categoryOptions.value = {
    AGENT: normalizeCategoryOptions(agentRes.data, 'categoryName'),
    BOT: [],
    KNOWLEDGE: normalizeCategoryOptions(knowledgeRes.data, 'categoryName'),
    WORKFLOW: normalizeCategoryOptions(workflowRes.data, 'categoryName'),
  };
  categoryLoaded.value = true;
}

async function ensureDeptOptions() {
  const res = await api.get('/api/v1/sysDept/list', {
    params: {
      asTree: true,
      sortKey: 'sortNo',
      sortType: 'asc',
      status: ENABLED_DATA_STATUS,
    },
  });
  deptTreeOptions.value = Array.isArray(res.data) ? res.data : [];
}

async function ensureRoleOptions() {
  if (roleLoaded.value) {
    return;
  }
  const res = await api.get('/api/v1/approvalFlow/assigneeRoleOptions');
  roleOptions.value = normalizeSelectOptions(res.data);
  roleLoaded.value = true;
}

async function searchAccountOptions(keyword = '') {
  accountLoading.value = true;
  try {
    const res = await api.get('/api/v1/approvalFlow/assigneeAccountPage', {
      params: {
        keyword,
        pageNumber: 1,
        pageSize: 20,
      },
    });
    for (const item of normalizeSelectOptions(res?.data?.records)) {
      if (
        !accountOptions.value.some(
          (option) => String(option.id) === String(item.id),
        )
      ) {
        accountOptions.value.push(item);
      }
    }
  } finally {
    accountLoading.value = false;
  }
}

function normalizeCategoryOptions(data: any[] = [], labelKey: string) {
  return (Array.isArray(data) ? data : []).map((item) => ({
    id: item.id,
    label: item[labelKey],
  }));
}

function normalizeSelectOptions(data: any[] = []) {
  return (Array.isArray(data) ? data : []).map((item) => ({
    id: item.id,
    label: item.name || item.label || item.code || String(item.id),
  }));
}

function normalizeAssigneeTargets(item: any): AssigneeTarget[] {
  const targets = Array.isArray(item?.assigneeTargets)
    ? item.assigneeTargets
    : [];
  if (targets.length > 0) {
    return targets
      .filter((target: any) => target?.targetId)
      .map((target: any) => ({
        includeChildren: target.includeChildren === 1,
        targetCode: target.targetCode,
        targetId: target.targetId,
        targetName: target.targetName,
      }));
  }
  if (!item?.assigneeTargetId) {
    return [];
  }
  return [
    {
      includeChildren: false,
      targetCode: item.assigneeTargetCode,
      targetId: item.assigneeTargetId,
      targetName: item.assigneeTargetName,
    },
  ];
}

function buildIncludeChildrenByTarget(targets: AssigneeTarget[]) {
  const result: Record<string, boolean> = {};
  for (const target of targets) {
    result[String(target.targetId)] = target.includeChildren;
  }
  return result;
}

function addScope() {
  formModel.value.scopes.push({
    includeChildren: false,
    scopeType: 'CATEGORY',
    scopeValue: undefined,
  });
}

function removeScope(index: number) {
  formModel.value.scopes.splice(index, 1);
}

function addStep() {
  formModel.value.steps.push(buildDefaultStep());
}

function removeStep(index: number) {
  if (formModel.value.steps.length <= 1) {
    return;
  }
  formModel.value.steps.splice(index, 1);
}

function getCategoryScopeOptions() {
  const resourceType = formModel.value.resourceType;
  if (!resourceType) {
    return [];
  }
  return categoryOptions.value[resourceType];
}

async function handleAssigneeTypeChange(
  step: StepItem,
  assigneeType: AssigneeType,
) {
  if (
    step.assigneeType === assigneeType ||
    isApplicantDeptAssigneeTypeDisabled(
      step.restrictToApplicantDept,
      assigneeType,
    )
  ) {
    return;
  }
  if (step.assigneeTargetIds.length > 0) {
    try {
      await ElMessageBox.confirm(
        $t('approval.message.confirmAssigneeTypeChange'),
        $t('approval.fields.assigneeTarget'),
        {
          cancelButtonText: $t('button.cancel'),
          confirmButtonText: $t('button.confirm'),
          type: 'warning',
        },
      );
    } catch (error) {
      if (error === 'cancel' || error === 'close') {
        return;
      }
      throw error;
    }
  }
  step.assigneeType = assigneeType;
  step.assigneeTargetIds = [];
  step.assigneeTargets = [];
  step.includeChildrenByTarget = {};
  if (assigneeType === 'USER') {
    void searchAccountOptions('');
  }
}

async function handleApplicantDeptRestrictionChange(
  step: StepItem,
  enabled: boolean | number | string,
) {
  const restricted = normalizeApplicantDeptRestriction(enabled);
  if (needsApplicantDeptRestrictionConfirmation(step, restricted)) {
    try {
      await ElMessageBox.confirm(
        $t('approval.message.confirmApplicantDeptRestriction'),
        $t('approval.fields.restrictToApplicantDept'),
        {
          cancelButtonText: $t('button.cancel'),
          confirmButtonText: $t('button.confirm'),
          type: 'warning',
        },
      );
    } catch (error) {
      if (error === 'cancel' || error === 'close') {
        return;
      }
      throw error;
    }
  }
  applyApplicantDeptRestriction(step, restricted);
}

function handleAssigneeTargetChange(step: StepItem) {
  const selectedIds = new Set(step.assigneeTargetIds.map(String));
  step.assigneeTargets = step.assigneeTargets.filter((target) =>
    selectedIds.has(String(target.targetId)),
  );
  for (const targetId of Object.keys(step.includeChildrenByTarget)) {
    if (!selectedIds.has(targetId)) {
      delete step.includeChildrenByTarget[targetId];
    }
  }
  for (const targetId of step.assigneeTargetIds) {
    const targetKey = String(targetId);
    if (!(targetKey in step.includeChildrenByTarget)) {
      step.includeChildrenByTarget[targetKey] = false;
    }
  }
}

function removeAssigneeTarget(step: StepItem, targetId: number | string) {
  const targetKey = String(targetId);
  step.assigneeTargetIds = step.assigneeTargetIds.filter(
    (item) => String(item) !== targetKey,
  );
  handleAssigneeTargetChange(step);
}

function getDeptTargetName(targetId: number | string) {
  return deptOptionMap.value.get(String(targetId)) || String(targetId);
}

function registerAccountOption(step?: StepItem) {
  if (!step || step.assigneeType !== 'USER') {
    return;
  }
  for (const target of step.assigneeTargets) {
    if (
      !target.targetName ||
      accountOptions.value.some(
        (item) => String(item.id) === String(target.targetId),
      )
    ) {
      continue;
    }
    accountOptions.value.push({
      id: target.targetId,
      label: target.targetName,
    });
  }
}

async function save() {
  if (btnLoading.value) {
    return;
  }
  btnLoading.value = true;
  try {
    const valid = await saveForm.value?.validate().catch(() => false);
    if (!valid) {
      return;
    }
    const steps = formModel.value.steps
      .map((step, index) => ({
        assigneeTargets: step.assigneeTargetIds.map((targetId) => ({
          includeChildren:
            step.assigneeType === 'DEPT' &&
            step.includeChildrenByTarget[String(targetId)]
              ? 1
              : 0,
          targetId,
        })),
        assigneeType: step.assigneeType,
        id: step.id,
        restrictToApplicantDept: serializeApplicantDeptRestriction(
          step.restrictToApplicantDept,
        ),
        stepName: step.stepName?.trim(),
        stepNo: index + 1,
      }))
      .filter((step) => step.stepName);
    if (steps.length === 0) {
      ElMessage.warning($t('approval.message.needStep'));
      return;
    }
    if (
      steps.some(
        (step) => !step.assigneeType || step.assigneeTargets.length === 0,
      )
    ) {
      ElMessage.warning($t('approval.message.needStepAssignee'));
      return;
    }

    const scopes = formModel.value.scopes
      .filter((scope) => scope.scopeValue && scope.scopeType)
      .map((scope) => ({
        id: scope.id,
        includeChildren: scope.includeChildren ? 1 : 0,
        scopeType: scope.scopeType,
        scopeValue: scope.scopeValue,
      }));

    const payload = {
      actionType: formModel.value.actionType,
      id: formModel.value.id,
      name: formModel.value.name.trim(),
      priority: formModel.value.priority,
      remark: formModel.value.remark.trim(),
      resourceType: formModel.value.resourceType,
      scopes,
      status: formModel.value.status,
      steps,
    };

    const url = isAdd.value
      ? '/api/v1/approvalFlow/save'
      : '/api/v1/approvalFlow/update';
    const res = await api.post(url, payload);
    if (res.errorCode !== 0) {
      return;
    }
    ElMessage.success($t('approval.message.saveSuccess'));
    emit('reload');
    closeDialog();
  } finally {
    btnLoading.value = false;
  }
}

function closeDialog() {
  formModel.value = buildDefaultForm();
  dialogVisible.value = false;
  saveForm.value?.resetFields();
}
</script>

<template>
  <EasyFlowFormModal
    v-model:open="dialogVisible"
    :title="
      isAdd ? $t('approval.action.addFlow') : $t('approval.action.editFlow')
    "
    :before-close="closeDialog"
    :confirm-loading="btnLoading"
    :submitting="btnLoading"
    :confirm-text="$t('button.save')"
    width="xl"
    @confirm="save"
  >
    <ElForm
      ref="saveForm"
      :model="formModel"
      :rules="rules"
      label-position="top"
      class="easyflow-modal-form"
    >
      <div class="grid grid-cols-1 gap-4 md:grid-cols-2">
        <ElFormItem prop="name" :label="$t('approval.fields.flowName')">
          <ElInput v-model.trim="formModel.name" maxlength="128" />
        </ElFormItem>
        <ElFormItem prop="priority" :label="$t('approval.fields.priority')">
          <ElInputNumber
            v-model="formModel.priority"
            :min="0"
            :step="10"
            class="!w-full"
          />
        </ElFormItem>
        <ElFormItem
          prop="resourceType"
          :label="$t('approval.fields.resourceType')"
        >
          <ElSelect v-model="formModel.resourceType">
            <ElOption
              v-for="item in RESOURCE_OPTIONS"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </ElSelect>
        </ElFormItem>
        <ElFormItem prop="actionType" :label="$t('approval.fields.actionType')">
          <ElSelect v-model="formModel.actionType">
            <ElOption
              v-for="item in ACTION_OPTIONS"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </ElSelect>
        </ElFormItem>
        <ElFormItem prop="status" :label="$t('approval.fields.status')">
          <ElSelect v-model="formModel.status">
            <ElOption
              v-for="item in STATUS_OPTIONS"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </ElSelect>
        </ElFormItem>
        <ElFormItem prop="remark" :label="$t('approval.fields.remark')">
          <ElInput v-model.trim="formModel.remark" maxlength="500" />
        </ElFormItem>
      </div>

      <ElDivider>{{ $t('approval.section.scope') }}</ElDivider>
      <div class="approval-flow-modal__section">
        <div class="approval-flow-modal__section-meta">
          <p>{{ $t('approval.helper.scope') }}</p>
          <ElButton text type="primary" class="w-fit" @click="addScope">
            {{ $t('approval.action.addScope') }}
          </ElButton>
        </div>

        <div class="approval-flow-modal__scope-list">
          <div
            v-if="formModel.scopes.length === 0"
            class="approval-flow-modal__scope-empty"
          >
            {{ $t('approval.helper.scopeEmpty') }}
          </div>

          <div
            v-for="(scope, index) in formModel.scopes"
            :key="`scope-${index}`"
            class="approval-flow-modal__scope-row"
          >
            <ElSelect
              v-model="scope.scopeType"
              class="approval-flow-modal__scope-control approval-flow-modal__scope-type"
            >
              <ElOption
                v-for="item in SCOPE_TYPE_OPTIONS"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </ElSelect>

            <ElSelect
              v-if="scope.scopeType === 'CATEGORY'"
              v-model="scope.scopeValue"
              class="approval-flow-modal__scope-control"
              filterable
              clearable
              :placeholder="$t('approval.placeholder.scopeValue')"
            >
              <ElOption
                v-for="item in getCategoryScopeOptions()"
                :key="item.id"
                :label="item.label"
                :value="item.id"
              />
            </ElSelect>

            <ElTreeSelect
              v-else
              v-model="scope.scopeValue"
              class="approval-flow-modal__scope-control"
              clearable
              check-strictly
              :data="deptTreeOptions"
              :props="{
                label: 'deptName',
                children: 'children',
                value: 'id',
              }"
              :placeholder="$t('approval.placeholder.scopeValue')"
            />

            <div class="approval-flow-modal__scope-switch">
              <span>{{ $t('approval.fields.includeChildren') }}</span>
              <ElSwitch v-model="scope.includeChildren" />
            </div>

            <ElButton text type="danger" @click="removeScope(index)">
              {{ $t('button.delete') }}
            </ElButton>
          </div>
        </div>
      </div>

      <ElDivider>{{ $t('approval.section.steps') }}</ElDivider>
      <div class="flex flex-col gap-3">
        <div
          v-for="(step, index) in formModel.steps"
          :key="`step-${index}`"
          class="approval-flow-modal__step-card"
        >
          <div class="approval-flow-modal__step-header">
            <span class="approval-flow-modal__step-index">
              {{ $t('approval.fields.stepNo', { value: index + 1 }) }}
            </span>
            <div class="approval-flow-modal__step-actions">
              <label class="approval-flow-modal__restriction-switch">
                <span>{{ $t('approval.fields.restrictToApplicantDept') }}</span>
                <ElSwitch
                  :model-value="step.restrictToApplicantDept"
                  @change="
                    (value) => handleApplicantDeptRestrictionChange(step, value)
                  "
                />
              </label>
              <ElButton
                text
                type="danger"
                :disabled="formModel.steps.length <= 1"
                @click="removeStep(index)"
              >
                {{ $t('button.delete') }}
              </ElButton>
            </div>
          </div>
          <div class="approval-flow-modal__step-grid">
            <div class="approval-flow-modal__step-field">
              <span class="approval-flow-modal__step-field-label">
                {{ $t('approval.fields.stepName') }}
              </span>
              <ElInput
                v-model.trim="step.stepName"
                :placeholder="$t('approval.placeholder.stepName')"
              />
            </div>

            <div class="approval-flow-modal__step-field">
              <span class="approval-flow-modal__step-field-label">
                {{ $t('approval.fields.assigneeType') }}
              </span>
              <div class="approval-flow-modal__assignee-type">
                <button
                  v-for="item in ASSIGNEE_TYPE_OPTIONS"
                  :key="item.value"
                  type="button"
                  class="approval-flow-modal__assignee-type-item"
                  :class="{
                    'is-active': step.assigneeType === item.value,
                    'is-disabled': isApplicantDeptAssigneeTypeDisabled(
                      step.restrictToApplicantDept,
                      item.value,
                    ),
                  }"
                  :disabled="
                    isApplicantDeptAssigneeTypeDisabled(
                      step.restrictToApplicantDept,
                      item.value,
                    )
                  "
                  :aria-pressed="step.assigneeType === item.value"
                  @click="handleAssigneeTypeChange(step, item.value)"
                >
                  {{ item.label }}
                </button>
              </div>
            </div>

            <div
              class="approval-flow-modal__step-field approval-flow-modal__target-field"
            >
              <div class="approval-flow-modal__target-heading">
                <span class="approval-flow-modal__step-field-label">
                  {{ $t('approval.fields.assigneeTarget') }}
                </span>
                <span
                  v-if="
                    step.assigneeType === 'DEPT' &&
                    step.assigneeTargetIds.length > 0
                  "
                  class="approval-flow-modal__target-count"
                >
                  {{
                    $t('approval.fields.selectedDeptCount', {
                      value: step.assigneeTargetIds.length,
                    })
                  }}
                </span>
              </div>

              <ElSelect
                v-if="step.assigneeType === 'ROLE'"
                v-model="step.assigneeTargetIds"
                filterable
                clearable
                multiple
                collapse-tags
                collapse-tags-tooltip
                :max-collapse-tags="ASSIGNEE_VISIBLE_TAG_COUNT"
                :placeholder="$t('approval.placeholder.assigneeTarget')"
                @change="handleAssigneeTargetChange(step)"
              >
                <ElOption
                  v-for="item in roleOptions"
                  :key="item.id"
                  :label="item.label"
                  :value="item.id"
                />
              </ElSelect>

              <ElSelect
                v-else-if="step.assigneeType === 'USER'"
                v-model="step.assigneeTargetIds"
                filterable
                clearable
                multiple
                collapse-tags
                collapse-tags-tooltip
                :max-collapse-tags="ASSIGNEE_VISIBLE_TAG_COUNT"
                remote
                reserve-keyword
                :loading="accountLoading"
                :placeholder="$t('approval.placeholder.assigneeTarget')"
                :remote-method="searchAccountOptions"
                @change="handleAssigneeTargetChange(step)"
                @visible-change="
                  (visible) => visible && searchAccountOptions('')
                "
              >
                <ElOption
                  v-for="item in accountOptions"
                  :key="item.id"
                  :label="item.label"
                  :value="item.id"
                />
              </ElSelect>

              <div v-else class="approval-flow-modal__dept-target">
                <ElTreeSelect
                  v-model="step.assigneeTargetIds"
                  clearable
                  multiple
                  collapse-tags
                  collapse-tags-tooltip
                  :max-collapse-tags="ASSIGNEE_VISIBLE_TAG_COUNT"
                  check-strictly
                  :data="deptTreeOptions"
                  :props="{
                    label: 'deptName',
                    children: 'children',
                    value: 'id',
                  }"
                  :placeholder="$t('approval.placeholder.assigneeTarget')"
                  @change="handleAssigneeTargetChange(step)"
                />
                <div
                  v-if="step.assigneeTargetIds.length > 0"
                  class="approval-flow-modal__dept-target-list"
                >
                  <div
                    v-for="targetId in step.assigneeTargetIds"
                    :key="String(targetId)"
                    class="approval-flow-modal__dept-target-option"
                  >
                    <span
                      class="approval-flow-modal__dept-target-name"
                      :title="getDeptTargetName(targetId)"
                    >
                      {{ getDeptTargetName(targetId) }}
                    </span>
                    <div class="approval-flow-modal__dept-target-actions">
                      <label>
                        {{ $t('approval.fields.includeChildren') }}
                        <ElSwitch
                          v-model="
                            step.includeChildrenByTarget[String(targetId)]
                          "
                        />
                      </label>
                      <ElButton
                        link
                        type="danger"
                        @click="removeAssigneeTarget(step, targetId)"
                      >
                        {{ $t('approval.action.removeTarget') }}
                      </ElButton>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <ElButton text type="primary" class="w-fit" @click="addStep">
          {{ $t('approval.action.addStep') }}
        </ElButton>
        <p class="approval-flow-modal__assignee-helper">
          {{ $t('approval.helper.assigneeAny') }}
        </p>
      </div>
    </ElForm>
  </EasyFlowFormModal>
</template>

<style scoped>
.approval-flow-modal__section {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.approval-flow-modal__section-meta {
  display: flex;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
}

.approval-flow-modal__section-meta p {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: hsl(var(--text-muted));
}

.approval-flow-modal__scope-list {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.approval-flow-modal__scope-empty {
  padding: 4px 0 2px;
  font-size: 13px;
  color: hsl(var(--text-muted));
}

.approval-flow-modal__scope-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  padding: 10px 0;
}

.approval-flow-modal__scope-row + .approval-flow-modal__scope-row {
  border-top: 1px solid hsl(var(--divider-faint) / 72%);
}

.approval-flow-modal__scope-control {
  width: 100%;
}

.approval-flow-modal__scope-switch {
  display: flex;
  gap: 10px;
  align-items: center;
  font-size: 13px;
  color: hsl(var(--text-muted));
}

.approval-flow-modal__step-card {
  padding: var(--space-4);
  background: hsl(var(--surface-panel) / 78%);
  border: 1px solid hsl(var(--divider-faint) / 72%);
  border-radius: var(--radius-panel);
}

.approval-flow-modal__step-header {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  justify-content: space-between;
  min-height: 32px;
  margin-bottom: var(--space-3);
}

.approval-flow-modal__step-index {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 0 var(--space-3);
  font-size: 13px;
  font-weight: 600;
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 9%);
  border-radius: var(--radius-pill);
}

.approval-flow-modal__step-actions {
  display: inline-flex;
  gap: var(--space-3);
  align-items: center;
}

.approval-flow-modal__restriction-switch {
  display: inline-flex;
  gap: var(--space-2);
  align-items: center;
  font-size: 13px;
  color: hsl(var(--text-muted));
  white-space: nowrap;
}

.approval-flow-modal__step-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
}

.approval-flow-modal__step-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.approval-flow-modal__step-field-label {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.4;
  color: hsl(var(--text-strong));
}

.approval-flow-modal__assignee-type {
  display: flex;
  gap: 6px;
  align-items: center;
  width: 100%;
  min-height: 40px;
  padding: 4px;
  background: hsl(var(--surface-contrast-soft) / 82%);
  border: 1px solid hsl(var(--divider-faint) / 72%);
  border-radius: var(--radius-toolbar);
}

.approval-flow-modal__assignee-type-item {
  flex: 1;
  min-width: 0;
  height: 32px;
  padding: 0 var(--space-3);
  font-size: 13px;
  font-weight: 600;
  line-height: 32px;
  color: hsl(var(--text-muted));
  cursor: pointer;
  background: transparent;
  border: 0;
  border-radius: var(--radius-control);
  transition:
    background-color 0.18s ease,
    color 0.18s ease,
    box-shadow 0.18s ease;
}

.approval-flow-modal__assignee-type-item:hover {
  color: hsl(var(--foreground));
}

.approval-flow-modal__assignee-type-item:disabled {
  color: hsl(var(--text-muted) / 48%);
  cursor: not-allowed;
}

.approval-flow-modal__assignee-type-item:disabled:hover {
  color: hsl(var(--text-muted) / 48%);
}

.approval-flow-modal__assignee-type-item:focus-visible {
  outline: 2px solid hsl(var(--primary) / 28%);
  outline-offset: 1px;
}

.approval-flow-modal__assignee-type-item.is-active {
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 12%);
  box-shadow: inset 0 0 0 1px hsl(var(--primary) / 14%);
}

.approval-flow-modal__target-heading {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  justify-content: space-between;
}

.approval-flow-modal__target-count {
  font-size: 12px;
  line-height: 1.4;
  color: hsl(var(--text-muted));
}

.approval-flow-modal__dept-target {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.approval-flow-modal__dept-target-list {
  max-height: 176px;
  padding: 0 var(--space-2);
  overflow-y: auto;
  background: hsl(var(--surface-subtle));
  border: 1px solid hsl(var(--divider-faint) / 72%);
  border-radius: var(--radius-toolbar);
}

.approval-flow-modal__dept-target-option {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: center;
  min-height: 48px;
  padding: var(--space-2) 0;
  font-size: 13px;
  color: hsl(var(--text-muted));
}

.approval-flow-modal__dept-target-option
  + .approval-flow-modal__dept-target-option {
  border-top: 1px solid hsl(var(--divider-faint) / 72%);
}

.approval-flow-modal__dept-target-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  color: hsl(var(--foreground));
  white-space: nowrap;
}

.approval-flow-modal__dept-target-actions {
  display: inline-flex;
  flex: none;
  gap: var(--space-3);
  align-items: center;
}

.approval-flow-modal__dept-target-actions label {
  display: inline-flex;
  gap: var(--space-2);
  align-items: center;
  white-space: nowrap;
}

.approval-flow-modal__assignee-helper {
  margin: -4px 0 0;
  font-size: 13px;
  color: hsl(var(--text-muted));
}

@media (min-width: 768px) {
  .approval-flow-modal__scope-row {
    grid-template-columns: 148px minmax(0, 1fr) 128px 72px;
    align-items: center;
  }

  .approval-flow-modal__step-grid {
    grid-template-columns: minmax(0, 1fr) minmax(216px, 0.8fr);
  }

  .approval-flow-modal__target-field {
    grid-column: 1 / -1;
  }
}

@media (min-width: 1200px) {
  .approval-flow-modal__step-grid {
    grid-template-columns:
      minmax(180px, 0.85fr) minmax(216px, 0.72fr)
      minmax(280px, 1.35fr);
  }

  .approval-flow-modal__target-field {
    grid-column: auto;
  }
}

@media (max-width: 560px) {
  .approval-flow-modal__dept-target-option {
    grid-template-columns: minmax(0, 1fr);
  }

  .approval-flow-modal__dept-target-actions {
    justify-content: space-between;
  }
}
</style>
