package tech.easyflow.approval.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import tech.easyflow.approval.entity.ApprovalFlowStep;
import tech.easyflow.approval.entity.ApprovalFlowStepAssignee;
import tech.easyflow.approval.entity.ApprovalTask;
import tech.easyflow.approval.entity.ApprovalTaskAssignee;
import tech.easyflow.approval.entity.vo.ApprovalAssigneeOptionVo;
import tech.easyflow.approval.entity.vo.ApprovalAssigneeTargetVo;
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
import tech.easyflow.approval.enums.ApprovalAssigneeType;
import tech.easyflow.approval.enums.ApprovalTaskStatus;
import tech.easyflow.approval.mapper.ApprovalFlowStepAssigneeMapper;
import tech.easyflow.approval.mapper.ApprovalTaskAssigneeMapper;
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
import tech.easyflow.approval.service.ApprovalAssigneeService;
import tech.easyflow.common.constant.enums.EnumDataStatus;
import tech.easyflow.common.util.SearchKeywordUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.entity.SysAccountRole;
import tech.easyflow.system.entity.SysDept;
import tech.easyflow.system.entity.SysRole;
import tech.easyflow.system.service.SysAccountRoleService;
import tech.easyflow.system.service.SysAccountService;
import tech.easyflow.system.service.SysDeptService;
import tech.easyflow.system.service.SysRoleService;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 审批对象服务实现。
 */
@Service
public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {

    @Resource
    private SysRoleService sysRoleService;

    @Resource
    private SysAccountService sysAccountService;

    @Resource
    private SysAccountRoleService sysAccountRoleService;

    @Resource
    private SysDeptService sysDeptService;

    @Resource
    private ApprovalTaskMapper approvalTaskMapper;

    @Resource
    private ApprovalFlowStepAssigneeMapper approvalFlowStepAssigneeMapper;

    @Resource
    private ApprovalTaskAssigneeMapper approvalTaskAssigneeMapper;

    /**
     * {@inheritDoc}
     */
    @Override
    public ApprovalFlowStepVo normalizeStepAssignee(ApprovalFlowStepVo step) {
        if (step == null) {
            throw new BusinessException("审批步骤不能为空");
        }
        boolean restricted = Integer.valueOf(1).equals(step.getRestrictToApplicantDept());
        step.setRestrictToApplicantDept(restricted ? 1 : 0);
        ApprovalAssigneeType assigneeType = ApprovalAssigneeType.from(step.getAssigneeType());
        if (restricted && ApprovalAssigneeType.DEPT == assigneeType) {
            throw new BusinessException("限定发起人部门后，审批对象类型不能选择部门");
        }
        List<ApprovalAssigneeTargetVo> requestedTargets = step.getAssigneeTargets();
        if (CollectionUtil.isEmpty(requestedTargets) && step.getAssigneeTargetId() != null) {
            requestedTargets = List.of(legacyTarget(
                    step.getAssigneeTargetId(),
                    step.getAssigneeTargetCode(),
                    step.getAssigneeTargetName()));
        }
        if (CollectionUtil.isEmpty(requestedTargets)) {
            throw new BusinessException("审批对象不能为空");
        }

        Map<BigInteger, ApprovalAssigneeTargetVo> normalizedTargets = new LinkedHashMap<>();
        for (ApprovalAssigneeTargetVo target : requestedTargets) {
            if (target == null || target.getTargetId() == null) {
                throw new BusinessException("审批对象不能为空");
            }
            ApprovalAssigneeOptionVo option = resolveAssigneeOption(assigneeType, target.getTargetId());
            ApprovalAssigneeTargetVo normalizedTarget = new ApprovalAssigneeTargetVo();
            normalizedTarget.setTargetId(option.getId());
            normalizedTarget.setTargetCode(option.getCode());
            normalizedTarget.setTargetName(option.getName());
            normalizedTarget.setIncludeChildren(ApprovalAssigneeType.DEPT == assigneeType
                    && Integer.valueOf(1).equals(target.getIncludeChildren()) ? 1 : 0);
            normalizedTargets.putIfAbsent(normalizedTarget.getTargetId(), normalizedTarget);
        }

        step.setAssigneeType(assigneeType.getCode());
        step.setAssigneeTargets(new ArrayList<>(normalizedTargets.values()));
        applyPrimaryTarget(step);
        return step;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void validateRestrictedStepCandidates(List<ApprovalFlowStepVo> steps, BigInteger requiredDeptId) {
        List<ApprovalFlowStepVo> restrictedSteps = safeList(steps).stream()
                .filter(Objects::nonNull)
                .filter(step -> Integer.valueOf(1).equals(step.getRestrictToApplicantDept()))
                .collect(Collectors.toList());
        if (restrictedSteps.isEmpty()) {
            return;
        }
        if (requiredDeptId != null) {
            requireAvailableDepartment(requiredDeptId, "发起人部门不存在或未启用");
        }

        Set<BigInteger> directAccountIds = new LinkedHashSet<>();
        Set<BigInteger> roleIds = new LinkedHashSet<>();
        for (ApprovalFlowStepVo step : restrictedSteps) {
            ApprovalAssigneeType type = ApprovalAssigneeType.from(step.getAssigneeType());
            if (ApprovalAssigneeType.DEPT == type) {
                throw new BusinessException("限定发起人部门后，审批对象类型不能选择部门");
            }
            Set<BigInteger> targetIds = stepTargetIds(step);
            if (ApprovalAssigneeType.USER == type) {
                directAccountIds.addAll(targetIds);
            } else {
                roleIds.addAll(targetIds);
            }
        }

        Set<BigInteger> availableRoleIds = roleIds.isEmpty() ? Set.of() : safeList(
                sysRoleService.list(QueryWrapper.create()
                        .in(SysRole::getId, roleIds)
                        .eq(SysRole::getStatus, EnumDataStatus.AVAILABLE.getCode())))
                .stream()
                .map(SysRole::getId)
                .collect(Collectors.toSet());
        List<SysAccountRole> roleRelations = availableRoleIds.isEmpty() ? List.of() : safeList(
                sysAccountRoleService.list(QueryWrapper.create()
                        .in(SysAccountRole::getRoleId, availableRoleIds)));
        Set<BigInteger> candidateAccountIds = new LinkedHashSet<>(directAccountIds);
        roleRelations.stream()
                .map(SysAccountRole::getAccountId)
                .filter(Objects::nonNull)
                .forEach(candidateAccountIds::add);

        QueryWrapper accountQuery = QueryWrapper.create()
                .in(SysAccount::getId, candidateAccountIds)
                .eq(SysAccount::getStatus, EnumDataStatus.AVAILABLE.getCode());
        if (requiredDeptId != null) {
            accountQuery.eq(SysAccount::getDeptId, requiredDeptId);
        }
        List<SysAccount> accounts = candidateAccountIds.isEmpty()
                ? List.of()
                : safeList(sysAccountService.list(accountQuery));
        Set<BigInteger> accountDeptIds = accounts.stream()
                .map(SysAccount::getDeptId)
                .filter(Objects::nonNull)
                .collect(Collectors.toCollection(LinkedHashSet::new));
        Set<BigInteger> availableDeptIds;
        if (requiredDeptId != null) {
            availableDeptIds = Set.of(requiredDeptId);
        } else if (accountDeptIds.isEmpty()) {
            availableDeptIds = Set.of();
        } else {
            availableDeptIds = safeList(sysDeptService.list(QueryWrapper.create()
                            .in(SysDept::getId, accountDeptIds)
                            .eq(SysDept::getStatus, EnumDataStatus.AVAILABLE.getCode())))
                    .stream()
                    .map(SysDept::getId)
                    .collect(Collectors.toSet());
        }
        Set<BigInteger> eligibleAccountIds = accounts.stream()
                .filter(account -> account.getDeptId() != null
                        && availableDeptIds.contains(account.getDeptId()))
                .map(SysAccount::getId)
                .collect(Collectors.toSet());
        Map<BigInteger, Set<BigInteger>> roleAccountMap = roleRelations.stream()
                .filter(relation -> eligibleAccountIds.contains(relation.getAccountId()))
                .collect(Collectors.groupingBy(
                        SysAccountRole::getRoleId,
                        Collectors.mapping(SysAccountRole::getAccountId, Collectors.toSet())));

        for (ApprovalFlowStepVo step : restrictedSteps) {
            ApprovalAssigneeType type = ApprovalAssigneeType.from(step.getAssigneeType());
            boolean hasCandidate = stepTargetIds(step).stream().anyMatch(targetId ->
                    ApprovalAssigneeType.USER == type
                            ? eligibleAccountIds.contains(targetId)
                            : CollectionUtil.isNotEmpty(roleAccountMap.get(targetId)));
            if (!hasCandidate) {
                throw new BusinessException(stepCandidateError(step, requiredDeptId));
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map<BigInteger, List<ApprovalAssigneeTargetVo>> loadStepAssigneeTargets(List<ApprovalFlowStep> steps) {
        if (CollectionUtil.isEmpty(steps)) {
            return Map.of();
        }
        List<BigInteger> stepIds = steps.stream()
                .map(ApprovalFlowStep::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        List<ApprovalFlowStepAssignee> assignees = stepIds.isEmpty() ? List.of() : safeList(
                approvalFlowStepAssigneeMapper.selectListByQuery(QueryWrapper.create()
                        .in(ApprovalFlowStepAssignee::getStepId, stepIds)
                        .orderBy("created asc, id asc")));
        Map<BigInteger, List<ApprovalFlowStepAssignee>> relationMap = assignees.stream()
                .collect(Collectors.groupingBy(
                        ApprovalFlowStepAssignee::getStepId,
                        LinkedHashMap::new,
                        Collectors.toList()));
        Map<BigInteger, List<ApprovalAssigneeTargetVo>> result = new LinkedHashMap<>();
        for (ApprovalFlowStep step : steps) {
            List<ApprovalAssigneeTargetVo> targets = relationMap.getOrDefault(step.getId(), List.of()).stream()
                    .filter(item -> Objects.equals(step.getAssigneeType(), item.getAssigneeType()))
                    .map(this::toTarget)
                    .collect(Collectors.toList());
            if (targets.isEmpty()) {
                targets = legacyTargets(step.getAssigneeTargetId(), step.getAssigneeTargetCode(),
                        step.getAssigneeTargetName());
            }
            result.put(step.getId(), targets);
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map<BigInteger, List<ApprovalAssigneeTargetVo>> loadTaskAssigneeTargets(List<ApprovalTask> tasks) {
        if (CollectionUtil.isEmpty(tasks)) {
            return Map.of();
        }
        List<BigInteger> taskIds = tasks.stream()
                .map(ApprovalTask::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        List<ApprovalTaskAssignee> assignees = taskIds.isEmpty() ? List.of() : safeList(
                approvalTaskAssigneeMapper.selectListByQuery(QueryWrapper.create()
                        .in(ApprovalTaskAssignee::getTaskId, taskIds)
                        .orderBy("created asc, id asc")));
        Map<BigInteger, List<ApprovalTaskAssignee>> relationMap = assignees.stream()
                .collect(Collectors.groupingBy(
                        ApprovalTaskAssignee::getTaskId,
                        LinkedHashMap::new,
                        Collectors.toList()));
        Map<BigInteger, List<ApprovalAssigneeTargetVo>> result = new LinkedHashMap<>();
        for (ApprovalTask task : tasks) {
            List<ApprovalAssigneeTargetVo> targets = relationMap.getOrDefault(task.getId(), List.of()).stream()
                    .filter(item -> Objects.equals(task.getAssigneeType(), item.getAssigneeType()))
                    .map(this::toTarget)
                    .collect(Collectors.toList());
            if (targets.isEmpty()) {
                targets = legacyTargets(task.getAssigneeTargetId(), task.getAssigneeTargetCode(),
                        task.getAssigneeTargetName());
            }
            result.put(task.getId(), targets);
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void saveStepAssigneeTargets(BigInteger stepId, String assigneeType,
                                        List<ApprovalAssigneeTargetVo> targets,
                                        BigInteger operatorId, Date now) {
        for (ApprovalAssigneeTargetVo target : requireTargets(targets)) {
            ApprovalFlowStepAssignee relation = new ApprovalFlowStepAssignee();
            relation.setStepId(stepId);
            relation.setAssigneeType(assigneeType);
            relation.setTargetId(target.getTargetId());
            relation.setTargetCode(target.getTargetCode());
            relation.setTargetName(target.getTargetName());
            relation.setIncludeChildren(normalizeIncludeChildren(assigneeType, target.getIncludeChildren()));
            relation.setCreated(now);
            relation.setCreatedBy(operatorId);
            relation.setModified(now);
            relation.setModifiedBy(operatorId);
            approvalFlowStepAssigneeMapper.insert(relation);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteStepAssigneeTargets(List<BigInteger> stepIds) {
        if (CollectionUtil.isEmpty(stepIds)) {
            return;
        }
        approvalFlowStepAssigneeMapper.deleteByQuery(
                QueryWrapper.create().in(ApprovalFlowStepAssignee::getStepId, stepIds));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void saveTaskAssigneeTargets(BigInteger taskId, String assigneeType,
                                        List<ApprovalAssigneeTargetVo> targets,
                                        BigInteger operatorId, Date now) {
        for (ApprovalAssigneeTargetVo target : requireTargets(targets)) {
            ApprovalTaskAssignee relation = new ApprovalTaskAssignee();
            relation.setTaskId(taskId);
            relation.setAssigneeType(assigneeType);
            relation.setTargetId(target.getTargetId());
            relation.setTargetCode(target.getTargetCode());
            relation.setTargetName(target.getTargetName());
            relation.setIncludeChildren(normalizeIncludeChildren(assigneeType, target.getIncludeChildren()));
            relation.setCreated(now);
            relation.setCreatedBy(operatorId);
            relation.setModified(now);
            relation.setModifiedBy(operatorId);
            approvalTaskAssigneeMapper.insert(relation);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<ApprovalAssigneeOptionVo> listRoleOptions() {
        return sysRoleService.list(QueryWrapper.create()
                        .eq(SysRole::getStatus, EnumDataStatus.AVAILABLE.getCode())
                        .orderBy("role_name asc, id asc"))
                .stream()
                .map(this::toRoleOption)
                .collect(Collectors.toList());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Page<ApprovalAssigneeOptionVo> pageAccountOptions(String keyword, Long pageNumber, Long pageSize) {
        long actualPageNumber = pageNumber == null || pageNumber < 1 ? 1L : pageNumber;
        long actualPageSize = pageSize == null || pageSize < 1 ? 20L : pageSize;
        QueryWrapper queryWrapper = QueryWrapper.create()
                .eq(SysAccount::getStatus, EnumDataStatus.AVAILABLE.getCode())
                .orderBy("nickname asc, login_name asc, id asc");
        if (StringUtils.hasText(keyword)) {
            String likeKeyword = SearchKeywordUtil.literalContainsPattern(keyword);
            queryWrapper.and("(`login_name` like ? or `nickname` like ?)", likeKeyword, likeKeyword);
        }
        Page<SysAccount> page = sysAccountService.page(new Page<>(actualPageNumber, actualPageSize), queryWrapper);
        Page<ApprovalAssigneeOptionVo> result = new Page<>(actualPageNumber, actualPageSize, page.getTotalRow());
        result.setRecords(page.getRecords().stream().map(this::toAccountOption).collect(Collectors.toList()));
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<BigInteger> getAvailableRoleIds(BigInteger accountId) {
        if (accountId == null) {
            return Set.of();
        }
        return sysRoleService.getRolesByAccountId(accountId).stream()
                .filter(role -> role != null && EnumDataStatus.AVAILABLE.getCode().equals(role.getStatus()))
                .map(SysRole::getId)
                .collect(Collectors.toCollection(LinkedHashSet::new));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean canHandleTask(ApprovalTask task, BigInteger operatorId, Set<BigInteger> roleIds) {
        if (task == null || operatorId == null) {
            return false;
        }
        ApprovalAssigneeType assigneeType = ApprovalAssigneeType.from(task.getAssigneeType());
        SysAccount operator = null;
        if (task.getRequiredDeptId() != null) {
            operator = sysAccountService.getById(operatorId);
            if (ApprovalAssigneeType.DEPT == assigneeType
                    || !Objects.equals(task.getRequiredDeptId(), resolveAvailableDirectDeptId(operator))) {
                return false;
            }
        }
        List<ApprovalAssigneeTargetVo> targets = loadTaskAssigneeTargets(List.of(task))
                .getOrDefault(task.getId(), List.of());
        if (ApprovalAssigneeType.USER == assigneeType) {
            return targets.stream().anyMatch(target -> operatorId.equals(target.getTargetId()));
        }
        if (ApprovalAssigneeType.ROLE == assigneeType) {
            return roleIds != null && targets.stream().anyMatch(target -> roleIds.contains(target.getTargetId()));
        }
        if (operator == null) {
            operator = sysAccountService.getById(operatorId);
        }
        return operator != null && matchesDepartment(targets, operator.getDeptId());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<BigInteger> listPendingInstanceIds(BigInteger operatorId, Set<BigInteger> roleIds,
                                                  List<BigInteger> instanceIds) {
        if (operatorId == null) {
            return Set.of();
        }
        Set<BigInteger> matchedTaskIds = new LinkedHashSet<>();
        matchedTaskIds.addAll(loadMatchingRelationTaskIds(
                ApprovalAssigneeType.USER, Set.of(operatorId), null, instanceIds));
        if (CollectionUtil.isNotEmpty(roleIds)) {
            matchedTaskIds.addAll(loadMatchingRelationTaskIds(
                    ApprovalAssigneeType.ROLE, roleIds, null, instanceIds));
        }

        SysAccount operator = sysAccountService.getById(operatorId);
        BigInteger availableDirectDeptId = resolveAvailableDirectDeptId(operator);
        Set<BigInteger> departmentIds = resolveDepartmentIds(operator == null ? null : operator.getDeptId());
        if (CollectionUtil.isNotEmpty(departmentIds)) {
            matchedTaskIds.addAll(loadMatchingRelationTaskIds(
                    ApprovalAssigneeType.DEPT, departmentIds, operator.getDeptId(), instanceIds));
        }

        Set<BigInteger> result = loadPendingTasks(matchedTaskIds, instanceIds).stream()
                .filter(task -> matchesRequiredDepartment(task, availableDirectDeptId))
                .map(ApprovalTask::getInstanceId)
                .collect(Collectors.toCollection(LinkedHashSet::new));

        // 旧单值任务仅在没有关联记录时参与兜底，避免旧字段覆盖新关联表的主数据语义。
        List<ApprovalTask> legacyCandidates = loadLegacyPendingCandidates(
                operatorId, roleIds, operator == null ? null : operator.getDeptId(), instanceIds);
        if (legacyCandidates.isEmpty()) {
            return result;
        }
        Set<BigInteger> candidateIds = legacyCandidates.stream()
                .map(ApprovalTask::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Set<BigInteger> relatedTaskIds = candidateIds.isEmpty() ? Set.of() : safeList(
                approvalTaskAssigneeMapper.selectListByQuery(
                        QueryWrapper.create().in(ApprovalTaskAssignee::getTaskId, candidateIds))).stream()
                .map(ApprovalTaskAssignee::getTaskId)
                .collect(Collectors.toSet());
        legacyCandidates.stream()
                .filter(task -> !relatedTaskIds.contains(task.getId()))
                .filter(task -> matchesRequiredDepartment(task, availableDirectDeptId))
                .map(ApprovalTask::getInstanceId)
                .forEach(result::add);
        return result;
    }

    /**
     * 判断任务的冻结部门限制是否命中当前账号直属部门。
     *
     * @param task 审批任务
     * @param availableDirectDeptId 当前账号的有效直属部门 ID
     * @return 是否命中
     */
    private boolean matchesRequiredDepartment(ApprovalTask task, BigInteger availableDirectDeptId) {
        return task.getRequiredDeptId() == null
                || Objects.equals(task.getRequiredDeptId(), availableDirectDeptId);
    }

    /**
     * 获取账号当前有效的直属部门 ID。
     *
     * @param account 账号
     * @return 有效直属部门 ID，无有效部门时返回空
     */
    private BigInteger resolveAvailableDirectDeptId(SysAccount account) {
        if (account == null
                || !EnumDataStatus.AVAILABLE.getCode().equals(account.getStatus())
                || account.getDeptId() == null) {
            return null;
        }
        SysDept dept = sysDeptService.getById(account.getDeptId());
        return dept != null && EnumDataStatus.AVAILABLE.getCode().equals(dept.getStatus())
                ? dept.getId()
                : null;
    }

    /**
     * 查询能命中当前主体的任务关联 ID。
     *
     * @param type 审批对象类型
     * @param targetIds 候选对象 ID
     * @param currentDeptId 当前部门 ID，部门审批时用于区分直属命中
     * @param instanceIds 可选实例过滤
     * @return 任务 ID 集合
     */
    private Set<BigInteger> loadMatchingRelationTaskIds(ApprovalAssigneeType type, Set<BigInteger> targetIds,
                                                        BigInteger currentDeptId,
                                                        List<BigInteger> instanceIds) {
        if (CollectionUtil.isEmpty(targetIds)) {
            return Set.of();
        }
        QueryWrapper pendingTaskQuery = QueryWrapper.create()
                .select(ApprovalTask::getId)
                .from(ApprovalTask.class)
                .eq(ApprovalTask::getStatus, ApprovalTaskStatus.PENDING.getCode());
        if (CollectionUtil.isNotEmpty(instanceIds)) {
            pendingTaskQuery.in(ApprovalTask::getInstanceId, instanceIds);
        }
        return safeList(approvalTaskAssigneeMapper.selectListByQuery(QueryWrapper.create()
                        .eq(ApprovalTaskAssignee::getAssigneeType, type.getCode())
                        .in(ApprovalTaskAssignee::getTargetId, targetIds)
                        .in(ApprovalTaskAssignee::getTaskId, pendingTaskQuery)))
                .stream()
                .filter(item -> type != ApprovalAssigneeType.DEPT
                        || Objects.equals(currentDeptId, item.getTargetId())
                        || Integer.valueOf(1).equals(item.getIncludeChildren()))
                .map(ApprovalTaskAssignee::getTaskId)
                .collect(Collectors.toCollection(LinkedHashSet::new));
    }

    /**
     * 按任务 ID 批量读取待审批任务。
     *
     * @param taskIds 任务 ID
     * @param instanceIds 可选实例过滤
     * @return 待审批任务
     */
    private List<ApprovalTask> loadPendingTasks(Set<BigInteger> taskIds, List<BigInteger> instanceIds) {
        if (CollectionUtil.isEmpty(taskIds)) {
            return List.of();
        }
        QueryWrapper query = QueryWrapper.create()
                .in(ApprovalTask::getId, taskIds)
                .eq(ApprovalTask::getStatus, ApprovalTaskStatus.PENDING.getCode());
        if (CollectionUtil.isNotEmpty(instanceIds)) {
            query.in(ApprovalTask::getInstanceId, instanceIds);
        }
        return safeList(approvalTaskMapper.selectListByQuery(query));
    }

    /**
     * 查询旧单值字段能命中的待审批任务候选。
     *
     * @param operatorId 操作人 ID
     * @param roleIds 角色 ID
     * @param deptId 当前部门 ID
     * @param instanceIds 可选实例过滤
     * @return 候选任务
     */
    private List<ApprovalTask> loadLegacyPendingCandidates(BigInteger operatorId, Set<BigInteger> roleIds,
                                                           BigInteger deptId, List<BigInteger> instanceIds) {
        List<ApprovalTask> result = new ArrayList<>();
        result.addAll(loadLegacyPendingCandidates(
                ApprovalAssigneeType.USER, Set.of(operatorId), instanceIds));
        if (CollectionUtil.isNotEmpty(roleIds)) {
            result.addAll(loadLegacyPendingCandidates(
                    ApprovalAssigneeType.ROLE, roleIds, instanceIds));
        }
        if (deptId != null) {
            result.addAll(loadLegacyPendingCandidates(
                    ApprovalAssigneeType.DEPT, Set.of(deptId), instanceIds));
        }
        return result.stream()
                .collect(Collectors.toMap(
                        ApprovalTask::getId,
                        Function.identity(),
                        (left, right) -> left,
                        LinkedHashMap::new))
                .values()
                .stream()
                .collect(Collectors.toList());
    }

    /**
     * 查询一种旧单值审批对象命中的待审批任务。
     *
     * @param type 审批对象类型
     * @param targetIds 对象 ID
     * @param instanceIds 可选实例过滤
     * @return 候选任务
     */
    private List<ApprovalTask> loadLegacyPendingCandidates(ApprovalAssigneeType type, Set<BigInteger> targetIds,
                                                           List<BigInteger> instanceIds) {
        QueryWrapper query = QueryWrapper.create()
                .eq(ApprovalTask::getStatus, ApprovalTaskStatus.PENDING.getCode())
                .eq(ApprovalTask::getAssigneeType, type.getCode())
                .in(ApprovalTask::getAssigneeTargetId, targetIds);
        if (CollectionUtil.isNotEmpty(instanceIds)) {
            query.in(ApprovalTask::getInstanceId, instanceIds);
        }
        return safeList(approvalTaskMapper.selectListByQuery(query));
    }

    /**
     * 判断当前部门是否命中任一部门审批对象。
     *
     * @param targets 部门审批对象
     * @param currentDeptId 当前部门 ID
     * @return 是否命中
     */
    private boolean matchesDepartment(List<ApprovalAssigneeTargetVo> targets, BigInteger currentDeptId) {
        Set<BigInteger> departmentIds = resolveDepartmentIds(currentDeptId);
        if (departmentIds.isEmpty()) {
            return false;
        }
        return targets.stream().anyMatch(target ->
                Objects.equals(currentDeptId, target.getTargetId())
                        || Integer.valueOf(1).equals(target.getIncludeChildren())
                        && departmentIds.contains(target.getTargetId()));
    }

    /**
     * 获取当前部门及其祖先部门。
     *
     * @param currentDeptId 当前部门 ID
     * @return 部门 ID 集合
     */
    private Set<BigInteger> resolveDepartmentIds(BigInteger currentDeptId) {
        return currentDeptId == null ? Set.of() : sysDeptService.getSelfAndAncestorDeptIds(currentDeptId);
    }

    /**
     * 校验部门存在且处于启用状态。
     *
     * @param deptId 部门 ID
     * @param message 校验失败消息
     * @return 有效部门
     */
    private SysDept requireAvailableDepartment(BigInteger deptId, String message) {
        SysDept dept = sysDeptService.getById(deptId);
        if (dept == null || !EnumDataStatus.AVAILABLE.getCode().equals(dept.getStatus())) {
            throw new BusinessException(message);
        }
        return dept;
    }

    /**
     * 提取步骤审批对象 ID。
     *
     * @param step 步骤
     * @return 审批对象 ID 集合
     */
    private Set<BigInteger> stepTargetIds(ApprovalFlowStepVo step) {
        List<ApprovalAssigneeTargetVo> targets = step.getAssigneeTargets();
        if (CollectionUtil.isEmpty(targets) && step.getAssigneeTargetId() != null) {
            targets = List.of(legacyTarget(
                    step.getAssigneeTargetId(),
                    step.getAssigneeTargetCode(),
                    step.getAssigneeTargetName()));
        }
        return safeList(targets).stream()
                .filter(Objects::nonNull)
                .map(ApprovalAssigneeTargetVo::getTargetId)
                .filter(Objects::nonNull)
                .collect(Collectors.toCollection(LinkedHashSet::new));
    }

    /**
     * 组装受限步骤无候选人的业务提示。
     *
     * @param step 步骤
     * @param requiredDeptId 指定部门 ID
     * @return 业务提示
     */
    private String stepCandidateError(ApprovalFlowStepVo step, BigInteger requiredDeptId) {
        String stepLabel = step.getStepNo() == null ? "审批步骤" : "第" + step.getStepNo() + "步";
        if (StringUtils.hasText(step.getStepName())) {
            stepLabel += "“" + step.getStepName().trim() + "”";
        }
        return requiredDeptId == null
                ? stepLabel + "没有可用审批账号，请检查角色成员及部门归属"
                : stepLabel + "在发起人部门内没有可用审批账号";
    }

    /**
     * 解析并校验单个审批对象。
     *
     * @param assigneeType 审批对象类型
     * @param targetId 审批对象 ID
     * @return 规范化选项
     */
    private ApprovalAssigneeOptionVo resolveAssigneeOption(ApprovalAssigneeType assigneeType, BigInteger targetId) {
        if (ApprovalAssigneeType.ROLE == assigneeType) {
            SysRole role = sysRoleService.getById(targetId);
            if (role == null || !EnumDataStatus.AVAILABLE.getCode().equals(role.getStatus())) {
                throw new BusinessException("审批角色不存在或未启用");
            }
            return toRoleOption(role);
        }
        if (ApprovalAssigneeType.DEPT == assigneeType) {
            SysDept dept = sysDeptService.getById(targetId);
            if (dept == null || !EnumDataStatus.AVAILABLE.getCode().equals(dept.getStatus())) {
                throw new BusinessException("审批部门不存在或未启用");
            }
            return toDeptOption(dept);
        }
        SysAccount account = sysAccountService.getById(targetId);
        if (account == null || !EnumDataStatus.AVAILABLE.getCode().equals(account.getStatus())) {
            throw new BusinessException("审批用户不存在或未启用");
        }
        return toAccountOption(account);
    }

    /**
     * 将角色转换为审批选项。
     *
     * @param role 角色
     * @return 审批选项
     */
    private ApprovalAssigneeOptionVo toRoleOption(SysRole role) {
        ApprovalAssigneeOptionVo option = new ApprovalAssigneeOptionVo();
        option.setId(role.getId());
        option.setCode(role.getRoleKey());
        option.setName(role.getRoleName());
        return option;
    }

    /**
     * 将账号转换为审批选项。
     *
     * @param account 账号
     * @return 审批选项
     */
    private ApprovalAssigneeOptionVo toAccountOption(SysAccount account) {
        ApprovalAssigneeOptionVo option = new ApprovalAssigneeOptionVo();
        option.setId(account.getId());
        option.setCode(account.getLoginName());
        option.setName(resolveAccountDisplayName(account));
        return option;
    }

    /**
     * 将部门转换为审批选项。
     *
     * @param dept 部门
     * @return 审批选项
     */
    private ApprovalAssigneeOptionVo toDeptOption(SysDept dept) {
        ApprovalAssigneeOptionVo option = new ApprovalAssigneeOptionVo();
        option.setId(dept.getId());
        option.setCode(dept.getDeptCode());
        option.setName(dept.getDeptName());
        return option;
    }

    /**
     * 解析账号展示名称。
     *
     * @param account 账号
     * @return 展示名称
     */
    private String resolveAccountDisplayName(SysAccount account) {
        if (StringUtils.hasText(account.getNickname())) {
            return account.getNickname().trim();
        }
        if (StringUtils.hasText(account.getLoginName())) {
            return account.getLoginName().trim();
        }
        return String.valueOf(account.getId());
    }

    /**
     * 将步骤的第一个审批对象同步到旧单值字段。
     *
     * @param step 步骤
     */
    private void applyPrimaryTarget(ApprovalFlowStepVo step) {
        ApprovalAssigneeTargetVo primary = step.getAssigneeTargets().get(0);
        step.setAssigneeTargetId(primary.getTargetId());
        step.setAssigneeTargetCode(primary.getTargetCode());
        step.setAssigneeTargetName(primary.getTargetName());
    }

    /**
     * 构造旧单值审批对象。
     *
     * @param targetId 对象 ID
     * @param targetCode 对象编码
     * @param targetName 对象名称
     * @return 审批对象
     */
    private ApprovalAssigneeTargetVo legacyTarget(BigInteger targetId, String targetCode, String targetName) {
        ApprovalAssigneeTargetVo target = new ApprovalAssigneeTargetVo();
        target.setTargetId(targetId);
        target.setTargetCode(targetCode);
        target.setTargetName(targetName);
        target.setIncludeChildren(0);
        return target;
    }

    /**
     * 构造旧单值审批对象列表。
     *
     * @param targetId 对象 ID
     * @param targetCode 对象编码
     * @param targetName 对象名称
     * @return 审批对象列表
     */
    private List<ApprovalAssigneeTargetVo> legacyTargets(BigInteger targetId, String targetCode, String targetName) {
        return targetId == null ? List.of() : List.of(legacyTarget(targetId, targetCode, targetName));
    }

    /**
     * 将步骤关联实体转换为审批对象。
     *
     * @param relation 步骤关联
     * @return 审批对象
     */
    private ApprovalAssigneeTargetVo toTarget(ApprovalFlowStepAssignee relation) {
        return target(relation.getTargetId(), relation.getTargetCode(), relation.getTargetName(),
                relation.getIncludeChildren());
    }

    /**
     * 将任务关联实体转换为审批对象。
     *
     * @param relation 任务关联
     * @return 审批对象
     */
    private ApprovalAssigneeTargetVo toTarget(ApprovalTaskAssignee relation) {
        return target(relation.getTargetId(), relation.getTargetCode(), relation.getTargetName(),
                relation.getIncludeChildren());
    }

    /**
     * 构造审批对象。
     *
     * @param targetId 对象 ID
     * @param targetCode 对象编码
     * @param targetName 对象名称
     * @param includeChildren 是否包含子部门
     * @return 审批对象
     */
    private ApprovalAssigneeTargetVo target(BigInteger targetId, String targetCode, String targetName,
                                            Integer includeChildren) {
        ApprovalAssigneeTargetVo target = new ApprovalAssigneeTargetVo();
        target.setTargetId(targetId);
        target.setTargetCode(targetCode);
        target.setTargetName(targetName);
        target.setIncludeChildren(Integer.valueOf(1).equals(includeChildren) ? 1 : 0);
        return target;
    }

    /**
     * 校验审批对象列表。
     *
     * @param targets 审批对象列表
     * @return 非空审批对象列表
     */
    private List<ApprovalAssigneeTargetVo> requireTargets(List<ApprovalAssigneeTargetVo> targets) {
        if (CollectionUtil.isEmpty(targets)) {
            throw new BusinessException("审批对象不能为空");
        }
        return targets;
    }

    /**
     * 规范化包含子部门标记。
     *
     * @param assigneeType 审批对象类型
     * @param includeChildren 原标记
     * @return 规范化标记
     */
    private int normalizeIncludeChildren(String assigneeType, Integer includeChildren) {
        return ApprovalAssigneeType.DEPT.getCode().equals(assigneeType)
                && Integer.valueOf(1).equals(includeChildren) ? 1 : 0;
    }

    /**
     * 将可能为空的 Mapper 查询结果转换为空列表。
     *
     * @param values 查询结果
     * @param <T> 元素类型
     * @return 非空列表
     */
    private <T> List<T> safeList(List<T> values) {
        return values == null ? List.of() : values;
    }
}
