<script setup lang="ts">
import { computed, markRaw, reactive, ref, watch } from 'vue';
import { List, Plus, Star } from '@element-plus/icons-vue';
import {
  fetchChangeProjectTaskStatus,
  fetchCreateProjectTask,
  fetchCreateProjectTaskAssignee,
  fetchDeleteProjectTask,
  fetchGetProjectExecutionAssignees,
  fetchGetProjectMembers,
  fetchGetProjectTask,
  fetchGetProjectTaskAssignees,
  fetchGetProjectTaskBoardPageCross,
  fetchGetProjectTaskPageCross,
  fetchGetProjectTaskStatusBoardCross,
  fetchInactiveProjectTaskAssignee,
  fetchPrecheckDeleteProjectTask,
  fetchUpdateProjectTask
} from '@/service/api/project';
import { useAuthStore } from '@/store/modules/auth';
import { useUIPaginatedTable } from '@/hooks/common/table';
import { shouldRequireTaskProgressBeforeComplete } from '../shared';
import type { TaskWorkspaceSearchModel, WorklogChangedPayload } from '../shared';
import { useTaskCompletionCascade } from '../composables/use-task-completion-cascade';
import { useTaskPermissions } from '../composables/use-task-permissions';
import type { ViewContext } from '../composables/use-task-view-context';
import type { BoardFetcherParams } from '../composables/use-task-board-columns';
import ObjectDeleteDialog from './object-delete-dialog.vue';
import StatusActionDialog from './status-action-dialog.vue';
import TaskAssigneeDialog from './task-assignee-dialog.vue';
import TaskBoardView from './task-board-view.vue';
import TaskDetailDialog from './task-detail-dialog.vue';
import TaskOperateDialog from './task-operate-dialog.vue';
import TaskSearch from './task-search.vue';
import TaskTableView from './task-table-view.vue';
import TaskWorklogDialog from './task-worklog-dialog.vue';
import IconMdiViewColumnOutline from '~icons/mdi/view-column-outline';
import IconMdiTableLarge from '~icons/mdi/table-large';

defineOptions({ name: 'ProjectExecutionTaskWorkspace' });

type ViewMode = 'table' | 'board';
type OperateMode = 'create' | 'edit';
type TaskPageResponse = Awaited<ReturnType<typeof fetchGetProjectTaskPageCross>>;
type TaskStatusAction = Api.Project.LifecycleAction<Api.Project.ProjectTaskActionCode>;

interface Props {
  projectId: string;
  viewContext: ViewContext;
  execution: Api.Project.ProjectExecution | null;
  executionOptions: { id: string; name: string }[];
  /**
   * 当前左侧执行池锚定的"具体执行 id"(由 index 算):
   * - [id] → 锚定到这个具体执行
   * - undefined → 未锚定具体执行(范围由 scopedExecutionInvolveUserId / scopedExecutionStatusCodes 或后端默认决定)
   */
  scopedExecutionIds?: string[];
  /**
   * "我参与的执行"范围(由 index 算:执行视角 = 我参与 且未锚定具体执行时 = 当前用户 id):
   * 直接下传后端 executionInvolveUserId,限定到该用户参与(owner / 活跃协办)的执行;未参与任何执行时后端返空。
   * undefined → 不按执行成员收窄(all 视角 / 已锚定具体执行)。
   */
  scopedExecutionInvolveUserId?: string;
  /**
   * 当前左侧 chip 选中的"执行状态白名单"(由 index 算):
   * - undefined → 左侧选了"全部" / 锚定具体执行,不按执行状态过滤
   * - [statusCode] → 仅查所属执行状态匹配的任务,后端在任务可见性之上叠加该过滤
   *
   * 历史上前端用"filter 出 ids 再下传"实现按执行状态过滤,会漏掉"用户对执行无成员权限但对其下任务有权限"
   * 的任务。改成把状态码直接下传给后端处理。详见 docs/debt/跨执行任务接口-按执行状态过滤-契约调整.html
   */
  scopedExecutionStatusCodes?: Api.Project.ProjectExecutionStatusCode[];
  /** 跨执行视角下不展示新建任务入口;单执行视角下根据该 prop 决定 */
  canCreate: boolean;
  title: string;
  /** 视角按钮计数:从 index 拉的项目层级 status-board total */
  myCount: number;
  allCount: number;
  /** 「所有任务」视角按钮可见度(权限码 project:task:query) */
  showAll: boolean;
}

interface Emits {
  (e: 'executionChanged'): void;
  (e: 'selectPerspective', type: 'my' | 'all'): void;
}

const props = defineProps<Props>();
const emit = defineEmits<Emits>();

const { canManageTaskAssignee } = useTaskPermissions();

const authStore = useAuthStore();
const currentUserId = computed(() => authStore.userInfo.userId || '');

const executionAssigneeOptions = ref<Api.SystemManage.UserSimple[]>([]);

const viewMode = ref<ViewMode>('table');
const viewModeOptions = [
  { label: '表格', value: 'table', icon: markRaw(IconMdiTableLarge) },
  { label: '看板', value: 'board', icon: markRaw(IconMdiViewColumnOutline) }
];

const operateVisible = ref(false);
const taskOperateDialogRef = ref<InstanceType<typeof TaskOperateDialog> | null>(null);
const statusActionVisible = ref(false);
const operateMode = ref<OperateMode>('create');
const currentTask = ref<Api.Project.ProjectTask | null>(null);
const presetParentTaskId = ref<string | null>(null);
const currentStatusAction = ref<TaskStatusAction | null>(null);
const taskStatusBoard = ref<Api.Project.StatusBoard | null>(null);

const pendingCascade = ref(false);

const assigneeDialogVisible = ref(false);
const currentAssigneeTask = ref<Api.Project.ProjectTask | null>(null);
const currentAssignees = ref<Api.Project.TaskAssigneeRef[]>([]);
const assigneesLoading = ref(false);

/** 编辑弹层内联失效协办人时使用的默认原因（无提示，统一审计文案） */
const TASK_ASSIGNEE_INLINE_INACTIVE_REASON = '编辑任务时调整协办人';

/** 编辑弹层是否允许内联管理协办人（与列表「协办人」按钮同权限口径） */
const canManageCurrentTaskAssignee = computed(() =>
  currentTask.value ? canManageTaskAssignee(currentTask.value) : false
);

const worklogDialogVisible = ref(false);
const worklogDialogTask = ref<Api.Project.ProjectTask | null>(null);

const detailDialogVisible = ref(false);
const detailDialogTask = ref<Api.Project.ProjectTask | null>(null);
const detailDialogDefaultTab = ref<'info' | 'worklog'>('info');

const deleteTaskDialogVisible = ref(false);
const deleteTaskTarget = ref<Api.Project.ProjectTask | null>(null);
const deleteTaskDependentSummary = ref<string | null>(null);

// 是否"跨执行展示":未锚定具体执行时 = true,影响"所属执行"列/筛选字段的可见度
const isCrossExecution = computed(() => !props.execution);

interface PerspectiveCard {
  type: 'my' | 'all';
  icon: any;
  name: string;
  count: number;
}

const perspectiveCards = computed<PerspectiveCard[]>(() => {
  const my: PerspectiveCard = { type: 'my', icon: markRaw(Star), name: '我参与的', count: props.myCount };
  if (props.showAll) {
    // 「所有任务」放最前,紧跟「我参与的」;无权限时只剩「我参与的」
    return [{ type: 'all', icon: markRaw(List), name: '所有任务', count: props.allCount }, my];
  }
  return [my];
});

type QuickFilterKey = 'overdue' | 'thisWeek';

interface QuickFilterChip {
  key: QuickFilterKey;
  label: string;
  count: number;
  emphasis?: 'danger';
}

// 快速过滤 chip 数字:对齐当前 scoped+viewContext,不带搜索框其他字段
// 两个数字:逾期 / 本周到期,均按 dueRange 拉
const quickFilterCounts = ref({ overdue: 0, thisWeek: 0 });

const quickFilterChips = computed<QuickFilterChip[]>(() => [
  { key: 'overdue', label: '逾期', count: quickFilterCounts.value.overdue, emphasis: 'danger' },
  { key: 'thisWeek', label: '本周到期', count: quickFilterCounts.value.thisWeek }
]);

// 「我参与的」视角下展示 chip,数字跟着 scope(全部/状态/锚定执行)变
const showQuickFilters = computed(() => props.viewContext.type === 'my');

const PAGE_SIZE_DEFAULT = 20;
const pageNo = ref(1);
const pageSize = ref(PAGE_SIZE_DEFAULT);

function buildInitialSearchModel(dueRange: TaskWorkspaceSearchModel['dueRange'] = undefined): TaskWorkspaceSearchModel {
  return {
    keyword: undefined,
    statusCode: undefined,
    priority: undefined,
    ownerId: undefined,
    executionId: undefined,
    dueRange,
    updateTime: undefined
  };
}

const searchModel = reactive<TaskWorkspaceSearchModel>(buildInitialSearchModel());

const executionId = computed(() => props.execution?.id || '');

const canLoadAnyTasks = computed(() => Boolean(props.projectId));

const statusActionTitle = computed(() =>
  currentStatusAction.value ? `任务状态变更：${currentStatusAction.value.actionName}` : '任务状态变更'
);

function resolveCrossExecutionIds(): string[] | undefined {
  // 搜索栏「所属执行」选了某个,优先用它(精确锁定到该执行)
  if (searchModel.executionId) return [searchModel.executionId];
  // 否则跟随左侧锚定:[id] 锚定具体执行 / undefined 未锚定(范围交给 executionInvolveUserId 或后端默认)
  return props.scopedExecutionIds;
}

function resolveCrossExecutionInvolveUserId(): string | undefined {
  // 搜索栏「所属执行」选了具体执行 → 精确锁定,不叠加"我参与的执行"范围
  if (searchModel.executionId) return undefined;
  return props.scopedExecutionInvolveUserId;
}

function resolveCrossExecutionStatusCodes(): Api.Project.ProjectExecutionStatusCode[] | undefined {
  // 搜索栏「所属执行」选了某个具体执行 → 优先精确锁定,不再叠加状态过滤
  if (searchModel.executionId) return undefined;
  return props.scopedExecutionStatusCodes;
}

// 「本周到期」语义统一:dueRange=thisWeek 且未显式选状态时,排除终态(completed/cancelled)
function resolveEffectiveStatusCodes(): Api.Project.ProjectTaskStatusCode[] | undefined {
  if (searchModel.statusCode) {
    return [searchModel.statusCode as Api.Project.ProjectTaskStatusCode];
  }
  if (searchModel.dueRange === 'thisWeek') {
    return ['pending', 'active', 'paused'];
  }
  return undefined;
}

function buildCrossSearchParams(): Api.Project.ProjectTaskCrossSearchParams {
  return {
    pageNo: pageNo.value,
    pageSize: pageSize.value,
    keyword: searchModel.keyword?.trim() || undefined,
    executionIds: resolveCrossExecutionIds(),
    executionInvolveUserId: resolveCrossExecutionInvolveUserId(),
    executionStatusCodes: resolveCrossExecutionStatusCodes(),
    involveUserId: props.viewContext.type === 'my' ? currentUserId.value || undefined : undefined,
    ownerId: searchModel.ownerId || undefined,
    statusCodes: resolveEffectiveStatusCodes(),
    priority: searchModel.priority || undefined,
    dueRange: searchModel.dueRange || undefined,
    updateTime: searchModel.updateTime,
    sortBy: 'plannedEndDate',
    sortOrder: 'asc'
  };
}

function buildCrossStatusBoardParams(): Api.Project.ProjectTaskCrossStatusBoardParams {
  return {
    keyword: searchModel.keyword?.trim() || undefined,
    executionIds: resolveCrossExecutionIds(),
    executionInvolveUserId: resolveCrossExecutionInvolveUserId(),
    executionStatusCodes: resolveCrossExecutionStatusCodes(),
    involveUserId: props.viewContext.type === 'my' ? currentUserId.value || undefined : undefined,
    ownerId: searchModel.ownerId || undefined,
    priority: searchModel.priority || undefined,
    dueRange: searchModel.dueRange || undefined,
    updateTime: searchModel.updateTime
  };
}

function transformTaskPage(response: TaskPageResponse, currentPage: number, currentSize: number) {
  if (!response.error && response.data) {
    return {
      data: response.data.list,
      pageNum: currentPage,
      pageSize: currentSize,
      total: response.data.total
    };
  }
  return { data: [], pageNum: currentPage, pageSize: currentSize, total: 0 };
}

const { data, loading, getData, getDataByPage, mobilePagination } = useUIPaginatedTable<
  TaskPageResponse,
  Api.Project.ProjectTask
>({
  paginationProps: {
    currentPage: pageNo.value,
    pageSize: pageSize.value
  },
  api: () => {
    if (!canLoadAnyTasks.value) {
      return Promise.resolve({
        data: { total: 0, list: [] },
        error: null
      } as unknown as TaskPageResponse);
    }
    // 统一走跨执行接口:身份维度(my/all)与范围维度(全部/状态/具体执行)自由组合
    // 单执行场景下任务详情会带 executionOwnerId；跨执行接口缺失时前端只能识别任务/项目负责人身份
    return fetchGetProjectTaskPageCross(props.projectId, buildCrossSearchParams());
  },
  transform: response => transformTaskPage(response, pageNo.value, pageSize.value),
  onPaginationParamsChange: params => {
    pageNo.value = params.currentPage ?? 1;
    pageSize.value = params.pageSize ?? PAGE_SIZE_DEFAULT;
  },
  columns: () => [{ prop: 'taskTitle', label: '任务名称', minWidth: 160 }],
  immediate: false
});

const taskOptions = computed(() => data.value);

async function loadQuickFilterCounts() {
  // 仅在「我 + 跨执行视角」下有意义,其他时机直接清 0
  if (!showQuickFilters.value || !canLoadAnyTasks.value) {
    quickFilterCounts.value = { overdue: 0, thisWeek: 0 };
    return;
  }

  // scope 跟随左侧锚定:具体执行用 executionIds,"我参与的执行"用 executionInvolveUserId;
  // 我未参与任何执行时后端直接返空 → 计数自然为 0,无需空集合短路。
  const baseScope: Api.Project.ProjectTaskCrossSearchParams = {
    pageNo: 1,
    pageSize: 1,
    executionIds: props.scopedExecutionIds,
    executionInvolveUserId: props.scopedExecutionInvolveUserId,
    executionStatusCodes: props.scopedExecutionStatusCodes,
    involveUserId: currentUserId.value || undefined
  };

  const [overdueRes, thisWeekRes] = await Promise.all([
    fetchGetProjectTaskPageCross(props.projectId, { ...baseScope, dueRange: 'overdue' }),
    fetchGetProjectTaskPageCross(props.projectId, {
      ...baseScope,
      dueRange: 'thisWeek',
      // 「本周到期」= 本周需要做完且尚未完成,排除 completed/cancelled 两个终态
      statusCodes: ['pending', 'active', 'paused'] as Api.Project.ProjectTaskStatusCode[]
    })
  ]);

  quickFilterCounts.value = {
    overdue: overdueRes.error || !overdueRes.data ? 0 : overdueRes.data.total,
    thisWeek: thisWeekRes.error || !thisWeekRes.data ? 0 : thisWeekRes.data.total
  };
}

async function loadTaskStatusBoard() {
  if (!canLoadAnyTasks.value) {
    taskStatusBoard.value = null;
    return;
  }
  const { error, data: board } = await fetchGetProjectTaskStatusBoardCross(
    props.projectId,
    buildCrossStatusBoardParams()
  );
  taskStatusBoard.value = error || !board ? null : board;
}

async function boardFetcher(params: BoardFetcherParams) {
  // statusCodes 只在"单列加载更多"时下传(对应那一列);首屏(params.statusCode 缺省)不下传,
  // 让后端返完整 5 列骨架——避免点 chip 后列消失。
  // chip 隐含的状态白名单(resolveEffectiveStatusCodes)在响应后客户端过滤:不在白名单的列保留列头但
  // 数据/总数置空,与表格 statusCodes 口径对齐("点已完成"只在 completed 列出数据,"点本周"完成/取消列置空)
  const result = await fetchGetProjectTaskBoardPageCross(props.projectId, {
    pageNo: params.pageNo,
    pageSize: params.pageSize,
    statusCodes: params.statusCode as Api.Project.ProjectTaskStatusCode[] | undefined,
    keyword: searchModel.keyword?.trim() || undefined,
    executionIds: resolveCrossExecutionIds(),
    executionInvolveUserId: resolveCrossExecutionInvolveUserId(),
    executionStatusCodes: resolveCrossExecutionStatusCodes(),
    involveUserId: props.viewContext.type === 'my' ? currentUserId.value || undefined : undefined,
    ownerId: searchModel.ownerId || undefined,
    priority: searchModel.priority || undefined,
    dueRange: searchModel.dueRange || undefined,
    updateTime: searchModel.updateTime
  });

  const allowed = resolveEffectiveStatusCodes();
  if (!allowed || result.error || !result.data?.items) return result;

  const allowedSet = new Set<string>(allowed);
  return {
    ...result,
    data: {
      ...result.data,
      items: result.data.items.map(item => (allowedSet.has(item.statusCode) ? item : { ...item, list: [], total: 0 }))
    }
  };
}

async function refreshTableData(resetToFirstPage = false) {
  // 看板模式下也要刷,让 table data 保持同步,切回表格无需再拉
  if (resetToFirstPage) pageNo.value = 1;
  await (resetToFirstPage ? getDataByPage(1) : getData());
}

async function handleCreate() {
  if (!props.execution) {
    window.$message?.warning('请先选择执行项');
    return;
  }

  await loadExecutionAssigneeOptions();
  operateMode.value = 'create';
  currentTask.value = null;
  presetParentTaskId.value = null;
  operateVisible.value = true;
}

async function getTaskDetail(row: Api.Project.ProjectTask) {
  if (!row.executionId) {
    return row;
  }
  const result = await fetchGetProjectTask(props.projectId, row.executionId, row.id);
  return result.error || !result.data ? row : result.data;
}

async function handleEdit(row: Api.Project.ProjectTask) {
  const [detail] = await Promise.all([getTaskDetail(row), loadExecutionAssigneeOptions(row.executionId)]);

  if (!detail.allowEdit) {
    window.$message?.warning('当前任务状态不允许编辑');
    return;
  }

  operateMode.value = 'edit';
  currentTask.value = detail;
  operateVisible.value = true;
}

async function handleDetail(row: Api.Project.ProjectTask) {
  const detail = await getTaskDetail(row);
  detailDialogDefaultTab.value = 'info';
  detailDialogTask.value = detail;
  detailDialogVisible.value = true;
  currentTask.value = detail;
}

async function handleStatusAction(row: Api.Project.ProjectTask, action: TaskStatusAction | null) {
  const detail = await getTaskDetail(row);
  const targetAction = action || detail.availableActions[0] || null;

  if (!targetAction) {
    window.$message?.warning('当前任务暂无可用状态操作');
    return;
  }

  if (shouldRequireTaskProgressBeforeComplete(targetAction, detail)) {
    window.$message?.warning('完成任务前请先将进度调整为 100%');
    return;
  }

  currentTask.value = detail;
  currentStatusAction.value = targetAction;

  // 完成动作:二次确认后直接提交(完成无需填原因,但需让用户确认这一状态变更)
  if (targetAction.actionCode === 'complete') {
    try {
      await window.$messageBox?.confirm(`确定要完成任务“${detail.taskTitle}”吗？`, '完成确认', {
        confirmButtonText: '完成任务',
        cancelButtonText: '取消',
        type: 'warning'
      });
    } catch {
      return;
    }
    await handleStatusSubmit(null);
    return;
  }

  // 其他非必填原因的动作(开始/暂停/恢复)直接提交,不弹原因弹层
  if (!targetAction.needReason) {
    await handleStatusSubmit(null);
    return;
  }

  statusActionVisible.value = true;
}

/**
 * 编辑态把弹层选中的协办人与任务原协办人做 diff，翻译成加入 / 失效调用。
 * 更新任务接口本身忽略 assigneeUserIds，协办人只能走独立端点；失效用默认原因、无提示。
 * 返回是否全部成功（含无变更）。
 */
async function applyTaskAssigneeDiff(task: Api.Project.ProjectTask, nextUserIds: string[]) {
  const originalAssignees = task.assignees ?? [];
  const nextSet = new Set(nextUserIds);
  const originalSet = new Set(originalAssignees.map(item => item.userId));

  const toAdd = nextUserIds.filter(userId => !originalSet.has(userId));
  const toInactivate = originalAssignees.filter(item => !nextSet.has(item.userId));

  if (!toAdd.length && !toInactivate.length) return true;

  const requests = [
    ...toAdd.map(userId => fetchCreateProjectTaskAssignee(props.projectId, task.executionId, task.id, { userId })),
    ...toInactivate.map(assignee =>
      fetchInactiveProjectTaskAssignee(props.projectId, task.executionId, task.id, assignee.id, {
        reason: TASK_ASSIGNEE_INLINE_INACTIVE_REASON
      })
    )
  ];

  const results = await Promise.all(requests);
  return results.every(item => !item.error);
}

async function handleOperateSubmit(payload: Api.Project.SaveProjectTaskParams) {
  let assigneeOk = true;

  if (operateMode.value === 'create') {
    // 新建必须锚定到具体执行
    if (!props.execution) return;
    const result = await fetchCreateProjectTask(props.projectId, props.execution.id, payload);
    if (result.error) return;
    window.$message?.success('任务创建成功');
  } else {
    // 编辑跟随任务自身的 executionId（跨执行视角下 props.execution 为 null）
    const task = currentTask.value;
    if (!task) return;

    // 协办人不走更新接口，单独 diff；其余字段照常更新
    const { assigneeUserIds = [], ...taskData } = payload;
    const result = await fetchUpdateProjectTask(props.projectId, task.executionId, { taskId: task.id, data: taskData });
    if (result.error) return;

    assigneeOk = !canManageTaskAssignee(task) || (await applyTaskAssigneeDiff(task, assigneeUserIds));
    if (assigneeOk) {
      window.$message?.success('任务更新成功');
    } else {
      window.$message?.warning('任务已更新，部分协办人调整未成功，请在「协办人」中确认');
    }
  }

  await taskOperateDialogRef.value?.commit();
  operateVisible.value = false;
  await Promise.all([refreshTableData(), loadTaskStatusBoard()]);
  // 任务条数变了 → 通知父级刷新跨执行任务计数(右上角"我参与的 / 所有任务"数字)
  emit('executionChanged');
}

const cascade = useTaskCompletionCascade({
  projectId: computed(() => props.projectId),
  openStatusActionDialog: async (task, action, fromCascade) => {
    currentTask.value = task;
    currentStatusAction.value = action;
    pendingCascade.value = fromCascade;
    // 级联入口已在前置 confirm 里确认过;完成 / 无需原因的动作直接提交,不再弹原因弹层
    if (action.actionCode === 'complete' || !action.needReason) {
      await handleStatusSubmit(null);
      return;
    }
    statusActionVisible.value = true;
  },
  resolveCompleteAction: task => task.availableActions.find(item => item.actionCode === 'complete') ?? null
});

async function handleStatusSubmit(reason: string | null) {
  if (!currentTask.value || !currentStatusAction.value) return;

  const targetExecutionId = currentTask.value.executionId;
  const result = await fetchChangeProjectTaskStatus(props.projectId, targetExecutionId, {
    taskId: currentTask.value.id,
    data: {
      actionCode: currentStatusAction.value.actionCode,
      reason
    }
  });

  if (result.error) return;

  window.$message?.success('任务状态已更新');
  statusActionVisible.value = false;

  const wasCascade = pendingCascade.value;
  const completedTask = currentTask.value;
  pendingCascade.value = false;

  await Promise.all([refreshTableData(), loadTaskStatusBoard()]);
  emit('executionChanged');

  if (wasCascade && completedTask) {
    await cascade.onTaskCompleted(completedTask);
  }
}

async function handleReport(row: Api.Project.ProjectTask) {
  const detail = await getTaskDetail(row);
  currentTask.value = detail;
  worklogDialogTask.value = detail;
  worklogDialogVisible.value = true;
}

async function handleWorklogChanged(payload: WorklogChangedPayload) {
  await Promise.all([refreshTableData(), loadTaskStatusBoard()]);

  if (currentTask.value?.id === payload.taskId) {
    const refreshed = await getTaskDetail(currentTask.value);
    if (refreshed.id === payload.taskId) {
      currentTask.value = refreshed;
      if (worklogDialogTask.value?.id === payload.taskId) {
        worklogDialogTask.value = refreshed;
      }
      if (detailDialogTask.value?.id === payload.taskId) {
        detailDialogTask.value = refreshed;
      }
    }
  }

  if (payload.mode === 'delete' || payload.progressRate !== 100) return;

  const task = currentTask.value;
  if (!task || task.id !== payload.taskId || task.ownerId !== currentUserId.value) return;

  await cascade.triggerAfterWorklog({ task, submittedProgress: payload.progressRate });
}

async function handleDetailWorklogChanged(payload: WorklogChangedPayload) {
  await handleWorklogChanged(payload);
  emit('executionChanged');
}

function handleWorklogDialogClosedAfterChange() {
  emit('executionChanged');
}

async function handleAssigneeAdd(payload: Api.Project.CreateTaskAssigneeParams) {
  if (!currentAssigneeTask.value) return;
  const { error } = await fetchCreateProjectTaskAssignee(
    props.projectId,
    currentAssigneeTask.value.executionId,
    currentAssigneeTask.value.id,
    payload
  );
  if (error) return;
  window.$message?.success('协办人已加入');
  await refreshAssigneesAfterMutation();
}

async function handleAssigneeInactive(
  assignee: Api.Project.TaskAssigneeRef,
  payload: Api.Project.InactiveTaskAssigneeParams
) {
  if (!currentAssigneeTask.value) return;
  const { error } = await fetchInactiveProjectTaskAssignee(
    props.projectId,
    currentAssigneeTask.value.executionId,
    currentAssigneeTask.value.id,
    assignee.id,
    payload
  );
  if (error) return;
  window.$message?.success('协办人已失效');
  await refreshAssigneesAfterMutation();
}

async function refreshAssigneesAfterMutation() {
  if (!currentAssigneeTask.value) return;
  assigneesLoading.value = true;
  const { error, data: assigneeData } = await fetchGetProjectTaskAssignees(
    props.projectId,
    currentAssigneeTask.value.executionId,
    currentAssigneeTask.value.id
  );
  assigneesLoading.value = false;
  currentAssignees.value = error || !assigneeData ? [] : assigneeData;
  await refreshTableData();
}

async function loadExecutionAssigneeOptions(specificExecutionId?: string) {
  const targetId = specificExecutionId || executionId.value;

  if (!props.projectId) {
    executionAssigneeOptions.value = [];
    return;
  }

  if (!targetId) {
    const { error, data: members } = await fetchGetProjectMembers(props.projectId);

    if (error || !members) {
      executionAssigneeOptions.value = [];
      return;
    }

    executionAssigneeOptions.value = members
      .filter(item => item.status === 0)
      .map(item => ({
        id: item.userId,
        nickname: item.userNickname || item.userId,
        username: null,
        deptName: item.roleName || item.roleCode || null
      }));
    return;
  }

  const { error, data: assignees } = await fetchGetProjectExecutionAssignees(props.projectId, targetId);

  if (error || !assignees) {
    executionAssigneeOptions.value = [];
    return;
  }

  executionAssigneeOptions.value = assignees.map(item => ({
    id: item.userId,
    nickname: item.userNickname || item.userId,
    username: null,
    deptName: null
  }));
}

async function openDeleteTaskDialog(task: Api.Project.ProjectTask) {
  const precheck = await fetchPrecheckDeleteProjectTask(task.projectId, task.executionId, task.id);
  const canDirectDelete = !precheck.error && precheck.data && !precheck.data.hasDependentData;

  if (!canDirectDelete) {
    deleteTaskTarget.value = task;
    if (precheck.data) {
      const parts: string[] = [];
      if (precheck.data.childTaskCount > 0) parts.push(`${precheck.data.childTaskCount} 个子任务`);
      if (precheck.data.worklogCount > 0) parts.push(`${precheck.data.worklogCount} 条工作日志`);
      deleteTaskDependentSummary.value = parts.length ? `下含 ${parts.join(' + ')}` : null;
    } else {
      deleteTaskDependentSummary.value = null;
    }
    deleteTaskDialogVisible.value = true;
    return;
  }

  try {
    await window.$messageBox?.confirm(
      `确定要删除任务“${task.taskTitle}”吗？删除后将不可见，如需恢复请联系管理员。`,
      '删除确认',
      {
        confirmButtonText: '确认删除',
        cancelButtonText: '取消',
        type: 'warning'
      }
    );
  } catch {
    return;
  }

  const { error } = await fetchDeleteProjectTask(task.projectId, task.executionId, task.id, {
    taskName: task.taskTitle,
    confirmText: 'DELETE',
    reason: '无下挂数据，用户已二次确认'
  });
  if (error) {
    await Promise.all([refreshTableData(), loadTaskStatusBoard()]);
    return;
  }
  window.$message?.success('删除成功');
  await Promise.all([refreshTableData(), loadTaskStatusBoard()]);
}

async function confirmDeleteTask(payload: { name: string; confirmText: string; reason: string }) {
  const target = deleteTaskTarget.value;
  if (!target) return;
  const { error } = await fetchDeleteProjectTask(target.projectId, target.executionId, target.id, {
    taskName: payload.name,
    confirmText: payload.confirmText,
    reason: payload.reason
  });
  // 成功=正常删除；失败=多为打开弹层后对象被并发改状态/删除，错误文案由全局 onError 弹 Toast。
  // 两种情况都关弹层 + 刷新列表：失败也要让用户离开已失效的弹层、看到最新数据。
  deleteTaskDialogVisible.value = false;
  deleteTaskTarget.value = null;
  if (!error) window.$message?.success('删除成功');
  await Promise.all([refreshTableData(), loadTaskStatusBoard()]);
}

function resetSearchModel(initialDueRange: TaskWorkspaceSearchModel['dueRange']) {
  Object.assign(searchModel, buildInitialSearchModel(initialDueRange));
  pageNo.value = 1;
  pageSize.value = PAGE_SIZE_DEFAULT;
}

async function handleSearch() {
  await Promise.all([refreshTableData(true), loadTaskStatusBoard()]);
}

async function handleReset() {
  resetSearchModel(undefined);
  await Promise.all([refreshTableData(true), loadTaskStatusBoard()]);
}

function isQuickFilterActive(key: QuickFilterKey) {
  return searchModel.dueRange === key;
}

async function toggleQuickFilter(key: QuickFilterKey) {
  // 两个 chip 互斥:再点已选中 → 取消;否则切到目标 dueRange
  searchModel.dueRange = isQuickFilterActive(key) ? undefined : key;
  await handleSearch();
}

async function handlePerspectiveClick(type: 'my' | 'all') {
  // 同一视角再点:手动清掉 quick filter chip(切到不同视角由 watch viewContext.type 触发 reset)
  if (props.viewContext.type === type) {
    if (!searchModel.dueRange) return;
    searchModel.dueRange = undefined;
    await handleSearch();
    return;
  }
  emit('selectPerspective', type);
}

// 身份维度(my/all)切换:清搜索条件 + 重拉任务(execution 锚定下也能切身份)
watch(
  () => props.viewContext.type,
  async () => {
    resetSearchModel(undefined);
    if (!canLoadAnyTasks.value) {
      data.value = [];
      taskStatusBoard.value = null;
      return;
    }
    await Promise.all([getDataByPage(1), loadTaskStatusBoard()]);
  },
  { immediate: true }
);

// execution 锚定变化 → 拉/清"执行协办人选项"(操作弹层用),任务列表的重拉由 scopedExecutionIds watch 统一处理
watch(
  () => props.execution?.id,
  async () => {
    await loadExecutionAssigneeOptions();
  },
  { immediate: true }
);

// 范围维度变化(scopedExecutionIds / scopedExecutionInvolveUserId / scopedExecutionStatusCodes 引用变:
// 左侧 chip 切换 / 执行视角切换 / execution 锚定切换) → 重拉任务列表 + status-board
watch(
  [() => props.scopedExecutionIds, () => props.scopedExecutionInvolveUserId, () => props.scopedExecutionStatusCodes],
  async () => {
    if (!canLoadAnyTasks.value) return;
    await Promise.all([getDataByPage(1), loadTaskStatusBoard()]);
  }
);

// 切换项目(导航区换对象上下文) → projectId 变。父级把 selectedExecution/selectedStatus 重置回默认态时,
// scoped* 计算属性的输出值往往不变(都回到 undefined),上面那条 watcher 不会触发,任务列表/状态看板会停留在
// 上一个项目的旧数据。这里独立盯 projectId:清掉本组件内部搜索条件并重拉,确保任务内容跟着项目切换刷新。
// 非 immediate:首屏初始加载由 viewContext 那条 immediate watch 负责,避免挂载时重复请求。
watch(
  () => props.projectId,
  async () => {
    resetSearchModel(undefined);
    if (!canLoadAnyTasks.value) {
      data.value = [];
      taskStatusBoard.value = null;
      executionAssigneeOptions.value = [];
      return;
    }
    await Promise.all([getDataByPage(1), loadTaskStatusBoard(), loadExecutionAssigneeOptions()]);
  }
);

// quick filter chip 数字:跟着 scope / viewContext / 锚定执行 / projectId 变,独立于搜索框其他字段
watch(
  [
    () => props.scopedExecutionIds,
    () => props.scopedExecutionInvolveUserId,
    () => props.scopedExecutionStatusCodes,
    () => props.viewContext.type,
    () => props.execution?.id,
    () => props.projectId
  ],
  () => loadQuickFilterCounts(),
  { immediate: true }
);

defineExpose({
  refresh: () => Promise.all([refreshTableData(), loadTaskStatusBoard()]),
  openCreate: () => handleCreate()
});
</script>

<template>
  <section class="task-workspace">
    <header class="task-workspace__header">
      <div class="task-workspace__title-wrap">
        <h3 class="task-workspace__title">{{ title }}</h3>
        <div class="task-workspace__perspective" role="tablist" aria-label="任务视角与快速过滤切换">
          <ElTooltip
            v-for="card in perspectiveCards"
            :key="card.type"
            :content="card.name"
            placement="top"
            :show-after="120"
          >
            <button
              type="button"
              class="perspective-compact"
              :class="{ 'is-active': viewContext.type === card.type }"
              :aria-pressed="viewContext.type === card.type"
              @click="handlePerspectiveClick(card.type)"
            >
              <ElIcon><component :is="card.icon" /></ElIcon>
              <span class="perspective-compact__value">{{ card.count }}</span>
            </button>
          </ElTooltip>

          <button
            v-for="chip in showQuickFilters ? quickFilterChips : []"
            :key="chip.key"
            type="button"
            class="perspective-compact perspective-compact--text"
            :class="{
              'is-active': isQuickFilterActive(chip.key),
              'perspective-compact--danger': chip.emphasis === 'danger'
            }"
            :aria-pressed="isQuickFilterActive(chip.key)"
            @click="toggleQuickFilter(chip.key)"
          >
            <span>{{ chip.label }}</span>
            <span class="perspective-compact__value">{{ chip.count }}</span>
          </button>
        </div>
      </div>

      <div class="task-workspace__actions">
        <ElSegmented v-model="viewMode" class="task-view-toggle" size="default" :options="viewModeOptions">
          <template #default="{ item }">
            <span class="task-view-toggle__item">
              <component :is="item.icon" class="task-view-toggle__icon" />
              <span>{{ item.label }}</span>
            </span>
          </template>
        </ElSegmented>
        <ElTooltip v-if="canCreate" :disabled="Boolean(execution)" content="请先在左侧选择具体执行" placement="top">
          <ElButton type="primary" :icon="Plus" @click="handleCreate">新增</ElButton>
        </ElTooltip>
      </div>
    </header>

    <TaskSearch
      :model="searchModel"
      :user-options="executionAssigneeOptions"
      :status-options="taskStatusBoard?.items || []"
      :execution-options="executionOptions"
      :show-execution-field="isCrossExecution"
      :disabled="!canLoadAnyTasks"
      @search="handleSearch"
      @reset="handleReset"
    />

    <TaskTableView
      v-if="viewMode === 'table'"
      :data="data"
      :loading="loading"
      :pagination="mobilePagination"
      :cross-execution-mode="isCrossExecution"
      @detail="handleDetail"
      @edit="handleEdit"
      @report="handleReport"
      @status-action="handleStatusAction"
      @delete="openDeleteTaskDialog"
    />

    <TaskBoardView
      v-else
      :can-load="canLoadAnyTasks"
      :status-board="taskStatusBoard"
      :fetcher="boardFetcher"
      :cross-execution-mode="isCrossExecution"
      :status-action-visible="statusActionVisible"
      @detail="handleDetail"
      @edit="handleEdit"
      @report="handleReport"
      @status-action="handleStatusAction"
      @delete="openDeleteTaskDialog"
    />

    <TaskOperateDialog
      ref="taskOperateDialogRef"
      v-model:visible="operateVisible"
      :mode="operateMode"
      :row-data="currentTask"
      :execution-data="props.execution"
      :default-parent-task-id="presetParentTaskId"
      :user-options="executionAssigneeOptions"
      :task-options="taskOptions"
      :can-manage-assignee="canManageCurrentTaskAssignee"
      @submit="handleOperateSubmit"
    />

    <StatusActionDialog
      v-model:visible="statusActionVisible"
      :title="statusActionTitle"
      :action="currentStatusAction"
      @submit="handleStatusSubmit"
      @update:visible="
        value => {
          if (!value) pendingCascade = false;
        }
      "
    />

    <TaskAssigneeDialog
      v-model:visible="assigneeDialogVisible"
      :task="currentAssigneeTask"
      :assignees="currentAssignees"
      :user-options="executionAssigneeOptions"
      :loading="assigneesLoading"
      :can-manage="currentAssigneeTask ? canManageTaskAssignee(currentAssigneeTask) : false"
      @add="handleAssigneeAdd"
      @inactive="handleAssigneeInactive"
    />

    <TaskWorklogDialog
      v-model:visible="worklogDialogVisible"
      :task="worklogDialogTask"
      @changed="handleWorklogChanged"
      @closed-after-change="handleWorklogDialogClosedAfterChange"
    />

    <TaskDetailDialog
      v-model:visible="detailDialogVisible"
      :task="detailDialogTask"
      :user-options="executionAssigneeOptions"
      :task-options="taskOptions"
      :default-tab="detailDialogDefaultTab"
      @worklog-changed="handleDetailWorklogChanged"
    />

    <ObjectDeleteDialog
      v-model:visible="deleteTaskDialogVisible"
      object-type="task"
      :object-name="deleteTaskTarget?.taskTitle ?? ''"
      :dependent-summary="deleteTaskDependentSummary"
      :on-confirm="confirmDeleteTask"
    />
  </section>
</template>

<style scoped lang="scss">
.task-workspace {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
  border: 1px solid rgb(226 232 240 / 92%);
  border-radius: 8px;
  background-color: #fff;
  overflow: hidden;
}

.task-workspace__header {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 18px;
  border-bottom: 1px solid rgb(226 232 240 / 92%);
}

.task-workspace__title-wrap {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}

.task-workspace__title {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  color: rgb(15 23 42 / 94%);
  line-height: 1.4;
}

.task-workspace__perspective {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 4px;
}

.perspective-compact {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 26px;
  padding: 0 8px;
  border: 1px solid rgb(226 232 240);
  border-radius: 6px;
  background: #fff;
  color: rgb(100 116 139);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition:
    background 0.16s ease,
    border-color 0.16s ease,
    color 0.16s ease;
}

.perspective-compact:not(.is-active):hover {
  color: var(--el-color-primary);
  border-color: var(--el-color-primary);
}

.perspective-compact.is-active {
  background-color: var(--el-color-primary);
  border-color: var(--el-color-primary);
  color: #fff;
  box-shadow: 0 2px 6px rgb(64 158 255 / 28%);
}

.perspective-compact__value {
  font-size: 11px;
  background-color: rgb(255 255 255 / 70%);
  padding: 1px 5px;
  border-radius: 6px;
}

.perspective-compact.is-active .perspective-compact__value {
  background-color: #fff;
  color: var(--el-color-primary);
}

.perspective-compact--text {
  padding: 0 8px 0 10px;
}

.perspective-compact--danger:not(.is-active) {
  color: var(--el-color-danger);
  border-color: rgb(252 165 165 / 70%);
}

.perspective-compact--danger:not(.is-active):hover {
  color: var(--el-color-danger);
  border-color: var(--el-color-danger);
}

.perspective-compact--danger.is-active {
  background-color: var(--el-color-danger);
  border-color: var(--el-color-danger);
  color: #fff;
  box-shadow: 0 2px 6px rgb(245 108 108 / 28%);
}

.perspective-compact--danger.is-active .perspective-compact__value {
  background-color: #fff;
  color: var(--el-color-danger);
}

.task-workspace__actions {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin-left: auto;
  flex: 0 0 auto;
}

.task-view-toggle {
  --el-segmented-item-selected-bg-color: var(--el-color-primary);
  --el-segmented-item-selected-color: #fff;
}

.task-view-toggle__item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 4px;
  font-weight: 500;
}

.task-view-toggle__item.is-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.task-view-toggle__icon {
  font-size: 16px;
}
</style>
