<template>
  <div class="task-detail">
    <!-- 页面头部(吸顶):标题 + 状态 + 优先级 在左,推进按钮在右(滚动始终可见)。
         Problem4 ① 推进按钮从面板内移到 header 右侧,与 refresh 同区。-->
    <header class="page-header task-detail-header">
      <div class="header-left">
        <router-link to="/tasks" class="back-link">{{ $t('taskDetail.backToList') }}</router-link>
        <h1>{{ task?.title ?? '...' }}</h1>
        <span v-if="task" class="status-tag" :class="taskStatusClass(task.status)">{{ $t(taskStatusLabel(task.status)) }}</span>
        <!-- F-04 review 轮次显示(>0 才显示,in_review→in_progress / testing→in_review 退回时后端 +1) -->
        <span v-if="task && task.review_rounds > 0" class="review-rounds-badge">
          {{ $t('taskDetail.reviewRounds', { n: task.review_rounds }) }}
        </span>
        <span v-if="task" class="priority-badge" :class="priorityClass(task.priority)">{{ priorityLabel(task.priority) }}</span>
      </div>
      <div class="header-actions">
        <button class="btn btn-ghost btn-sm" type="button" @click="refresh">{{ $t('taskDetail.refresh') }}</button>
        <!-- F-260616-06 ①-1 / B-41 工作流推进按钮(联动任务,按 target_status 后端自动选推进链模板)
             与手动 advance 并存:仅当当前态的 primary 前向推进目标 ∈ {in_progress, testing, done}
             (即 template_for 有对应模板)时显示。手动 advance 仍走 advance_task 直 IPC;工作流推进
             走 run_workflow,经 AiNode/HumanNode 执行后再由后端回调推进任务。
             独立 loading 标志 wfAdvancing 与 advancing 互不干扰。 -->
        <button
          v-if="wfAdvanceAction"
          type="button"
          class="btn btn-sm btn-primary"
          :disabled="wfAdvancing || advancing"
          @click="handleWorkflowAdvance(wfAdvanceAction.target)"
        >{{ wfAdvancing ? $t('taskDetail.workflowAdvancing') : $t('taskDetail.workflowAdvance') }}</button>
        <!-- F-05 推进按钮:按当前 status 显示状态机合法下一态(todo/done/cancelled 终态无按钮) -->
        <button
          v-for="act in advanceActions"
          :key="act.target"
          type="button"
          class="btn btn-sm"
          :class="act.variant"
          :disabled="advancing || wfAdvancing"
          @click="handleAdvance(act.target)"
        >{{ advancing ? $t('taskDetail.advancing') : $t(act.label) }}</button>
      </div>
    </header>

    <!-- 加载/错误态 -->
    <div v-if="loading" class="empty-hint">{{ $t('taskDetail.loading') }}</div>
    <div v-else-if="errorMsg" class="empty-hint error-hint">⚠ {{ errorMsg }}</div>

    <!-- 主体:两栏布局(Problem4 ⑤)。左栏=描述+关联信息+时间戳,右栏=任务产出+工作流。
         窄屏自动单列。-->
    <div v-else-if="task" class="detail-grid">
      <!-- ============ 左栏 ============ -->
      <div class="left-column">
        <!-- P2-9 子任务提示条(仅子任务显示,指明隶属父任务) -->
        <div v-if="task.parent_id" class="subtask-hint">{{ $t('taskDetail.subtaskHint', { title: parentTaskTitle || task.parent_id }) }}</div>

        <!-- 描述(可编辑:有描述渲染+折叠 / 无描述空态;编辑态 textarea,对齐 IdeaDetail 模式) -->
        <section class="panel">
          <div class="panel-header"><h2>{{ $t('taskDetail.description') }}</h2></div>
          <template v-if="editingDesc">
            <textarea v-model="editDesc" class="detail-desc-edit" rows="4" :placeholder="$t('taskDetail.descPlaceholder')"></textarea>
            <div class="desc-edit-actions">
              <button class="btn btn-primary btn-sm" :disabled="savingDesc" @click="saveEditDesc">{{ $t('taskDetail.saveDesc') }}</button>
              <button class="btn btn-ghost btn-sm" :disabled="savingDesc" @click="cancelEditDesc">{{ $t('taskDetail.cancelEdit') }}</button>
            </div>
          </template>
          <template v-else>
            <div v-if="task.description" class="description-wrap" :style="descWrapStyle">
              <span ref="descEl" class="value description ai-md" v-html="renderedDesc"></span>
              <button
                v-if="descCollapsible"
                class="btn btn-ghost btn-sm desc-toggle"
                type="button"
                @click="descExpanded = !descExpanded"
              >{{ descExpanded ? $t('taskDetail.collapse') : $t('taskDetail.expand') }}</button>
              <div v-if="descCollapsible && !descExpanded" class="desc-fade"></div>
            </div>
            <div v-else class="empty-hint desc-empty">{{ $t('taskDetail.descEmpty') }}</div>
            <button class="btn btn-ghost btn-sm desc-edit-btn" :disabled="savingDesc" @click="startEditDesc">
              {{ task.description ? $t('taskDetail.editDesc') : $t('taskDetail.addDesc') }}
            </button>
          </template>
        </section>

        <!-- 关联信息(Problem4 ③④ 去重 + 空值不显行)。
             标题/状态/优先级已在 header 显示,这里不重复。-->
        <section class="panel">
          <div class="panel-header"><h2>{{ $t('taskDetail.relatedTitle') }}</h2></div>
          <div class="task-info">
            <!-- F-260805 父子任务:父面包屑,仅子任务(parent_id 有值)显示,跳 /tasks/{parent_id} -->
            <div v-if="task.parent_id" class="info-item">
              <span class="label">{{ $t('taskDetail.parentTask') }}</span>
              <span class="value">
                <router-link :to="`/tasks/${task.parent_id}`" class="project-link">{{ parentTaskTitle || task.parent_id }}</router-link>
              </span>
            </div>
            <div class="info-item">
              <span class="label">{{ $t('taskDetail.project') }}</span>
              <span class="value">
                <router-link v-if="task.project_id" :to="`/projects/${task.project_id}`" class="project-link">
                  {{ projectName }}
                </router-link>
                <span v-else>—</span>
              </span>
            </div>
            <!-- F-260619-01:关联灵感(1对1 单向,idea_id 解析为友好 title,非裸 id) -->
            <div v-if="task.idea_id" class="info-item">
              <span class="label">{{ $t('taskDetail.relatedIdea') }}</span>
              <span class="value">
                <router-link :to="`/ideas/${task.idea_id}`" class="project-link">{{ ideaTitle }}</router-link>
              </span>
            </div>
            <!-- 分支(有值才显) -->
            <div v-if="task.branch_name" class="info-item">
              <span class="label">{{ $t('taskDetail.branch') }}</span>
              <span class="value">
                <span class="branch-tag"><span class="branch-icon">⑂</span>{{ task.branch_name }}</span>
              </span>
            </div>
            <!-- 负责人(空值不显行,Problem4 ③) -->
            <div v-if="task.assignee" class="info-item">
              <span class="label">{{ $t('taskDetail.assignee') }}</span>
              <span class="value">{{ task.assignee }}</span>
            </div>
            <!-- 基础分支(空值不显行) -->
            <div v-if="task.base_branch" class="info-item">
              <span class="label">{{ $t('taskDetail.baseBranch') }}</span>
              <span class="value mono">{{ task.base_branch }}</span>
            </div>
            <!-- 工作流定义(空值不显行) -->
            <div v-if="task.workflow_def_id" class="info-item">
              <span class="label">{{ $t('taskDetail.workflowDef') }}</span>
              <span class="value mono">{{ task.workflow_def_id }}</span>
            </div>
            <!-- 创建/更新时间:最底小字(Problem4 ⑥) -->
            <div class="info-item timestamps">
              <span class="timestamp">{{ $t('taskDetail.createdAt') }}: {{ formatDate(task.created_at) }}</span>
              <span class="timestamp">{{ $t('taskDetail.updatedAt') }}: {{ formatDate(task.updated_at) }}</span>
            </div>
          </div>
        </section>

        <!-- F-260805 父子任务:子任务面板。仅顶层任务(无 parent_id)可作为父任务(1 级嵌套约束),
             顶层无子时显示空态 + 添加入口(空树创建)。 -->
        <section v-if="!task.parent_id" class="panel">
          <div class="panel-header">
            <h2>{{ $t('taskDetail.childrenTitle') }}</h2>
            <span class="subtask-count">{{ $t('taskDetail.subtaskCount', { n: children.length }) }}</span>
            <!-- 全选(勾选后触发底部批量操作栏) -->
            <label v-if="children.length > 0" class="select-all" :title="$t('tasks.batch.selectHint')">
              <input
                type="checkbox"
                :checked="selection.allSelected"
                :indeterminate.prop="selection.someSelected"
                @change="selection.toggleAll(($event.target as HTMLInputElement).checked)"
              />
              {{ $t('tasks.filter.all') }}
            </label>
          </div>
          <!-- 顶部进度条 + 计数(done+cancelled / total) -->
          <div v-if="children.length > 0" class="subtask-progress">
            <div class="mini-progress">
              <div class="mini-progress-fill" :style="{ width: subtaskProgress.pct + '%' }"></div>
            </div>
            <span class="subtask-progress-text">{{ $t('taskDetail.progressTitle') }}: {{ subtaskProgress.done }}/{{ subtaskProgress.total }}</span>
          </div>
          <div v-if="children.length > 0" class="subtask-list">
            <div
              v-for="child in children"
              :key="child.id"
              class="subtask-item"
              @click="router.push(`/tasks/${child.id}`)"
            >
              <input
                type="checkbox"
                class="subtask-checkbox"
                :checked="selection.isSelected(child.id)"
                @click.stop
                @change="selection.toggle(child.id, ($event.target as HTMLInputElement).checked)"
              />
              <span class="subtask-title">{{ child.title }}</span>
              <span class="status-tag" :class="taskStatusClass(child.status)">{{ $t(taskStatusLabel(child.status)) }}</span>
              <span class="priority-badge" :class="priorityClass(child.priority)">{{ priorityLabel(child.priority) }}</span>
              <button class="subtask-quick-btn" :title="$t('tasks.quickActions')" @click.stop="toggleChildMenu(child.id)">⚙️</button>
              <!-- 子任务行快捷菜单(复用列表页 quickStatuses/quickPriorities 模式,advance 后刷新子列表) -->
              <div v-if="childMenuId === child.id" class="quick-menu" @click.stop>
                <div class="quick-menu-section">
                  <div class="quick-menu-label">{{ $t('tasks.quickStatus') }}</div>
                  <button v-for="s in quickStatusesFor(child)" :key="s.key" class="quick-menu-item" @click="advanceChild(child, s.key)">
                    <span>{{ s.icon }}</span>{{ s.label }}
                  </button>
                </div>
                <div class="quick-menu-divider"></div>
                <div class="quick-menu-section">
                  <div class="quick-menu-label">{{ $t('tasks.quickPriority') }}</div>
                  <button v-for="p in quickPriorities" :key="p.value" class="quick-menu-item" @click="setChildPriority(child, p.value)">
                    <span :class="p.cls">●</span>{{ p.label }}
                  </button>
                </div>
              </div>
            </div>
          </div>
          <!-- 空态:父任务无子任务 -->
          <div v-else class="empty-hint subtask-empty">{{ $t('taskDetail.childEmpty') }}</div>
          <button class="btn btn-ghost btn-sm subtask-add" type="button" @click="openSubtaskModal">{{ $t('taskDetail.addSubtask') }}</button>
          <!-- 批量操作栏(选中子任务后浮现) -->
          <TaskBatchBar
            v-if="selection.count > 0"
            :count="selection.count"
            :busy="batch.busy"
            :advance-targets="batchAdvanceTargets"
            :cancel-available="batchAvail.cancel"
            :defer-available="batchAvail.defer"
            :resume-available="batchAvail.resume"
            :cascade-estimate="batchCascadeEstimate"
            :toast="batch.toast"
            :assignee-suggestions="assigneeSuggestions"
            @advance="batch.advanceMany([...selection.selected], $event).then(afterBatch)"
            @cancel="batch.cancelMany([...selection.selected]).then(afterBatch)"
            @defer="batch.deferMany([...selection.selected]).then(afterBatch)"
            @resume="batch.resumeMany([...selection.selected]).then(afterBatch)"
            @priority="batch.updateMany([...selection.selected], 'priority', $event).then(afterBatch)"
            @assignee="batch.updateMany([...selection.selected], 'assignee', $event).then(afterBatch)"
            @delete="batch.deleteMany([...selection.selected]).then(afterBatch)"
            @clear="selection.clear()"
          />
        </section>
      </div>

      <!-- ============ 右栏:任务产出 + 工作流进度 ============ -->
      <div class="right-column">
        <!-- F-AiNodeSelfReview: 任务产出 + AI 自审结果展示(抽至 TaskOutputCard 子组件,零行为变更) -->
        <section class="panel">
          <div class="panel-header"><h2>{{ $t('taskDetail.output') }}</h2></div>
          <!-- 无产出时占位(任务未跑过 AiNode 产出),右栏保留产出栏框架便于后续产出出现即填入 -->
          <div v-if="!task.output_json" class="empty-hint output-empty">—</div>
          <TaskOutputCard v-else :output-json="task.output_json" />
        </section>

        <!-- B-41 工作流推进轻量进度(从面板内 advance-row 提取,推进按钮已移 header) -->
        <section v-if="wfAdvancing || wfProgressHint" class="panel">
          <div class="panel-header"><h2>{{ $t('taskDetail.workflowAdvanceTitle') }}</h2></div>
          <div class="wf-progress">
            <span v-if="wfRunningNode">{{ $t('taskDetail.workflowStepRunning', { node: wfRunningNode }) }}</span>
            <span v-else-if="wfDoneTotal > 0" class="wf-progress-count">
              {{ $t('taskDetail.workflowStepsProgress', { done: wfDoneCount, total: wfDoneTotal }) }}
            </span>
            <span v-if="wfCompletedHint" class="wf-progress-hint">{{ $t('taskDetail.workflowCompletedHint') }}</span>
            <span v-if="wfFailedHint" class="wf-progress-hint wf-progress-hint-fail">{{ $t('taskDetail.workflowFailedHint') }}</span>
          </div>
          <!-- 工作流 DAG 结构 -->
          <WorkflowDagDisplay v-if="wfDagJson" :dag-json="wfDagJson" :node-statuses="wfNodeStatuses" />
        </section>
      </div>
    </div>

    <!-- F-260805:子任务新建小弹窗(标题 + 优先级 + 可选描述),project_id/parent_id 继承当前任务 -->
    <div class="modal-overlay" v-if="showSubtaskModal" @click.self="showSubtaskModal = false">
      <div class="modal-box">
        <h3>{{ $t('taskDetail.addSubtask') }}</h3>
        <div class="modal-field">
          <label>{{ $t('taskDetail.title') }}</label>
          <input v-model="subtaskTitle" :placeholder="$t('tasks.modal.titlePlaceholder')" @keyup.enter="confirmSubtask" />
        </div>
        <div class="modal-field">
          <label>{{ $t('taskDetail.description') }}</label>
          <input v-model="subtaskDesc" :placeholder="$t('tasks.modal.descPlaceholder')" />
        </div>
        <div class="modal-field">
          <label>{{ $t('taskDetail.priority') }}</label>
          <select v-model="subtaskPriority">
            <option :value="0">{{ $t('tasks.modal.priorityCritical') }}</option>
            <option :value="1">{{ $t('tasks.modal.priorityHigh') }}</option>
            <option :value="2">{{ $t('tasks.modal.priorityMedium') }}</option>
            <option :value="3">{{ $t('tasks.modal.priorityLow') }}</option>
          </select>
        </div>
        <div class="modal-actions">
          <button class="btn btn-ghost" @click="showSubtaskModal = false">{{ $t('common.cancel') }}</button>
          <button class="btn btn-primary" @click="confirmSubtask" :disabled="subtaskSubmitting || !subtaskTitle.trim()">{{ $t('common.confirm') }}</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, reactive, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { listen } from '@tauri-apps/api/event'
import { taskApi, projectApi, ideaApi } from '@/api'
import { workflowApi } from '@/api'
import { useProjectStore } from '@/stores/project'
import { formatDate } from '@/utils/time'
import { useRendered } from '@/composables/useMarkdown'
import {
  taskStatusLabel,
  taskStatusClass,
  priorityLabel,
  priorityClass,
  TASK_STATUS_TRANSITIONS,
  TASK_STATUS_ORDER,
  ADVANCE_MAP,
} from '../constants/project'
import type { AdvanceAction } from '../constants/project'
import type { TaskRecord, ProjectRecord, IdeaRecord, DfDataChangedPayload } from '@/api/types'
import TaskOutputCard from '@/components/task/TaskOutputCard.vue'
import TaskBatchBar from '@/components/task/TaskBatchBar.vue'
import WorkflowDagDisplay from '@/components/workflow/WorkflowDagDisplay.vue'
import { useTaskBatchSelection } from '@/composables/task/useTaskBatchSelection'
import { useTaskBatchActions } from '@/composables/task/useTaskBatchActions'

const { t } = useI18n()
const route = useRoute()
const router = useRouter()
// B-41: 共享 workflow store(liveEvents 全局收集 + workflowProgress 派生进度)
const store = useProjectStore()

const loading = ref(true)
const errorMsg = ref('')
const task = ref<TaskRecord | null>(null)
const projects = ref<ProjectRecord[]>([])
// F-260619-01:灵感列表用于解析 task.idea_id → 灵感 title(独立入口,同 projects 模式)
const ideas = ref<IdeaRecord[]>([])
const advancing = ref(false)

// ============================================================
// F-260805 父子任务:父面包屑 + 子任务面板
// ------------------------------------------------------------
// 本视图绕 store 直调 taskApi,父子数据本地维护:
// - 子任务(parent_id 有值):额外取父任务标题供面包屑
// - 顶层任务(无 parent_id):拉子任务列表 + 父进度(前端计算),advance/改优先级后手动刷新
const children = ref<TaskRecord[]>([])
const parentTaskTitle = ref<string | null>(null)
// 子任务行快捷菜单(当前打开的子任务 id)
const childMenuId = ref<string | null>(null)
function toggleChildMenu(id: string) {
  childMenuId.value = childMenuId.value === id ? null : id
}
function closeChildMenu() { childMenuId.value = null }

// 复用列表页 quickStatuses/quickPriorities 模式(子任务行快捷推进)。
// 基础列表 = TASK_STATUS_ORDER 全 8 态(含 deferred/cancelled,快捷菜单经 ADVANCE_MAP 过滤后只显合法项)。
const STATUS_FILTER_META: Record<string, { icon: string; labelKey: string }> = {
  todo: { icon: '📝', labelKey: 'tasks.statusFilter.todo' },
  in_progress: { icon: '🔨', labelKey: 'tasks.statusFilter.in_progress' },
  in_review: { icon: '👀', labelKey: 'tasks.statusFilter.in_review' },
  testing: { icon: '🧪', labelKey: 'tasks.statusFilter.testing' },
  done: { icon: '✅', labelKey: 'tasks.statusFilter.done' },
  blocked: { icon: '🚫', labelKey: 'tasks.statusFilter.blocked' },
  deferred: { icon: '⏰', labelKey: 'tasks.statusFilter.deferred' },
  cancelled: { icon: '🗑️', labelKey: 'tasks.statusFilter.cancelled' },
}
const quickStatuses = computed(() =>
  TASK_STATUS_ORDER.map(key => ({ key, icon: STATUS_FILTER_META[key].icon, label: t(STATUS_FILTER_META[key].labelKey) })),
)
/** 子任务快捷推进目标 = 状态机合法下一态(ADVANCE_MAP 过滤,与顶部手动按钮同源) */
function quickStatusesFor(child: TaskRecord) {
  const valid = new Set((ADVANCE_MAP[child.status] ?? []).map((a) => a.target))
  return quickStatuses.value.filter((s) => valid.has(s.key))
}
const quickPriorities = computed(() => [
  { value: 0, label: t('tasks.modal.priorityCritical'), cls: 'priority-critical' },
  { value: 1, label: t('tasks.modal.priorityHigh'), cls: 'priority-high' },
  { value: 2, label: t('tasks.modal.priorityMedium'), cls: 'priority-medium' },
  { value: 3, label: t('tasks.modal.priorityLow'), cls: 'priority-low' },
])

/** 子任务父进度 = children 中 done+cancelled / total(仅顶层任务有 children) */
const subtaskProgress = computed(() => {
  const total = children.value.length
  const done = children.value.filter(c => c.status === 'done' || c.status === 'cancelled').length
  return { done, total, pct: total > 0 ? Math.round((done / total) * 100) : 0 }
})

/** 拉取当前任务的直接子任务(仅顶层任务可作父,1 级嵌套) */
async function loadChildren() {
  const cur = task.value
  if (!cur || cur.parent_id) { children.value = []; return }
  try {
    children.value = await taskApi.list({ project_id: cur.project_id, parent_id: cur.id })
  } catch (e) {
    console.error('[TaskDetail] 拉取子任务失败:', e)
    children.value = []
  }
}

// ── 子任务批量操作;reactive 包装使模板属性自动解包 ──
const selection = reactive(useTaskBatchSelection({
  scopeIds: computed(() => children.value.map(c => c.id)),
}))
const batch = reactive(useTaskBatchActions({
  getTask: id => children.value.find(c => c.id === id),
  onRefresh: () => loadChildren(),
}))
async function afterBatch() {
  selection.prune()
}
const batchAdvanceTargets = computed(() => {
  const ids = [...selection.selected]
  if (ids.length === 0) return []
  let common: Set<string> | null = null
  for (const id of ids) {
    const task = children.value.find(c => c.id === id)
    if (!task) continue
    const legal = new Set(TASK_STATUS_TRANSITIONS[task.status] ?? [])
    if (common === null) {
      common = legal
    } else {
      const next = new Set<string>()
      for (const s of common) if (legal.has(s)) next.add(s)
      common = next
    }
    if (common.size === 0) return []
  }
  return quickStatuses.value.filter(s => common?.has(s.key))
})
const RESUME_STATES = new Set(['deferred', 'cancelled', 'blocked'])
const batchAvail = computed(() => {
  let cancel = false
  let defer = false
  let resume = false
  for (const id of selection.selected) {
    const task = children.value.find(c => c.id === id)
    if (!task) continue
    if (TASK_STATUS_TRANSITIONS[task.status]?.includes('cancelled')) cancel = true
    if (TASK_STATUS_TRANSITIONS[task.status]?.includes('deferred')) defer = true
    if (RESUME_STATES.has(task.status)) resume = true
  }
  return { cancel, defer, resume }
})
// 子任务均为叶子(无孙任务),级联删除数为 0
const batchCascadeEstimate = computed(() => 0)
const assigneeSuggestions = computed(() => {
  const set = new Set<string>()
  for (const c of children.value) if (c.assignee) set.add(c.assignee)
  return [...set]
})

/** 取父任务标题(父面包屑用;取不到时回退显示父 id) */
async function loadParentTitle() {
  const cur = task.value
  if (!cur || !cur.parent_id) { parentTaskTitle.value = null; return }
  try {
    const parent = await taskApi.get(cur.parent_id)
    parentTaskTitle.value = parent.title
  } catch (e) {
    console.error('[TaskDetail] 拉取父任务标题失败:', e)
    parentTaskTitle.value = null
  }
}

/** 子任务快捷推进(advance 后刷新子列表,父进度随之更新) */
async function advanceChild(child: TaskRecord, target: string) {
  childMenuId.value = null
  try {
    await taskApi.advance(child.id, target)
    await loadChildren()
  } catch (e: any) {
    errorMsg.value = t('taskDetail.advanceFailed', { msg: e?.toString() ?? t('common.unknownError') })
  }
}

/** 子任务快捷改优先级(update 后刷新子列表) */
async function setChildPriority(child: TaskRecord, priority: number) {
  childMenuId.value = null
  try {
    await taskApi.update(child.id, 'priority', String(priority))
    await loadChildren()
  } catch (e: any) {
    errorMsg.value = t('taskDetail.advanceFailed', { msg: e?.toString() ?? t('common.unknownError') })
  }
}

// F-260805:子任务新建小弹窗
const showSubtaskModal = ref(false)
const subtaskTitle = ref('')
const subtaskDesc = ref('')
const subtaskPriority = ref(2)
const subtaskSubmitting = ref(false)
function openSubtaskModal() {
  subtaskTitle.value = ''
  subtaskDesc.value = ''
  subtaskPriority.value = 2
  showSubtaskModal.value = true
}
async function confirmSubtask() {
  const cur = task.value
  if (!cur || !subtaskTitle.value.trim() || subtaskSubmitting.value) return
  const title = subtaskTitle.value.trim()
  if (title.length > 128) { errorMsg.value = t('taskDetail.subtaskTitleTooLong'); return }
  if (children.value.some((c) => c.title === title)) { errorMsg.value = t('taskDetail.subtaskDuplicate'); return }
  subtaskSubmitting.value = true
  try {
    const r = await taskApi.create({
      project_id: cur.project_id,
      title,
      description: subtaskDesc.value.trim() || undefined,
      priority: subtaskPriority.value,
      // project_id 继承当前任务、parent_id = 当前任务 id
      parent_id: cur.id,
    })
    if (!r) return
    showSubtaskModal.value = false
    errorMsg.value = ''
    await loadChildren()
  } catch (e: any) {
    errorMsg.value = t('taskDetail.addSubtaskFailed', { msg: e?.toString() ?? t('common.unknownError') })
  } finally {
    subtaskSubmitting.value = false
  }
}

// ============================================================
// F-260616-06 ①-1 / B-41 工作流推进状态(与手动 advance 的 advancing 独立,互不干扰)
// ------------------------------------------------------------
// wfAdvancing:推进中态;wfExecId:当前监听的工作流执行 ID(过滤 workflow-event);
// wfTotalNodes/wfDoneCount/wfRunningNode:轻量进度(由共享 store 按 execId 派生);
// wfResult: 'completed' | 'failed' | null —— 终态提示,完成/失败后自动清空(由新一次推进重置)。
const wfAdvancing = ref(false)
const wfExecId = ref<string | null>(null)
const wfResult = ref<'completed' | 'failed' | null>(null)
// SW-260618-21: 终态提示 timer 引用,卸载时清理防写已销毁 ref(对齐 Projects.vue _toastTimer 模式)
let _wfResultTimer: ReturnType<typeof setTimeout> | null = null

/** 重置工作流推进相关 UI 态(切任务/refresh/手动推进成功后调用。
    注意:df-data-changed 触发的 load() 不调此函数,避免工作流推进中 AI 工具改任务触发刷新时进度面板被误清) */
function resetWorkflowUi() {
  wfExecId.value = null
  wfDagJson.value = ''
  wfResult.value = null
  if (_wfResultTimer) { clearTimeout(_wfResultTimer); _wfResultTimer = null }
}

// B-41: 共享工作流进度推导。workflow store 已全局收集 liveEvents,此处按 execId
// 派生节点状态/进度,与 ProjectDetail 同源(store.workflowProgress),替代原本地 onEvent 逐事件累加。
const wfProgress = computed(() => store.workflowProgress(wfExecId.value))
const wfNodeStatuses = computed(() => wfProgress.value.nodeStatuses)
const wfDoneCount = computed(() => wfProgress.value.doneCount)
const wfTotalNodes = computed(() => wfProgress.value.totalNodes)
const wfRunningNode = computed(() => wfProgress.value.runningNode)
const wfDoneTotal = computed(() => wfTotalNodes.value)

const wfProgressHint = computed(() => wfResult.value !== null)
const wfCompletedHint = computed(() => wfResult.value === 'completed')
const wfFailedHint = computed(() => wfResult.value === 'failed')

// 终态 → 瞬态提示:派生 result 置位时收 wfAdvancing + 3s 后清 wfResult(仅 UI hint,数据不改)
watch(() => wfProgress.value.result, (r) => {
  if (!r) return
  wfResult.value = r
  wfAdvancing.value = false
  if (_wfResultTimer) clearTimeout(_wfResultTimer)
  _wfResultTimer = setTimeout(() => {
    if (wfResult.value === r) wfResult.value = null
    _wfResultTimer = null
  }, 3000)
})

// 工作流 DAG 结构展示(执行记录读回,节点状态由共享派生)
const wfDagJson = ref('')
async function refreshWorkflowDag(execId: string) {
  try {
    const record = await workflowApi.getExecution(execId)
    if (record?.dag_json) wfDagJson.value = record.dag_json
  } catch { /* 静默 */ }
}

const taskId = computed(() => route.params.id as string)

// B-24:任务描述 Markdown 渲染(复用 AiChat 同款渲染器,模块级单例),useRendered 封装
// computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热)
const { rendered: renderedDesc, ensureLoaded } = useRendered(
  () => task.value?.description ?? '',
)

// ============================================================
// Problem4 ②:描述可折叠 — 超 400px 显展开按钮(测真实 DOM 高度)
// ------------------------------------------------------------
// 不用字符数近似(渲染后 Markdown 列表/标题行高不一,字符数与 px 无线性关系),
// 改用 descEl 实测 scrollHeight:首次渲染 + task 切换后 nextTick 测量。
// descCollapsible=true 时才显按钮;默认折叠(!descExpanded)即首次进入截 400px。
// 折叠态 max-height 由 descWrapStyle 内联注入(展开态不设上限,自适应)。
// .description-wrap 的 overflow:hidden + transition 让折叠/展开有过渡。
// 蒙层 .desc-fade 暗示折叠态下方还有内容。
const DESC_COLLAPSE_PX = 400
const descEl = ref<HTMLElement | null>(null)
const descExpanded = ref(false)
const descOverflow = ref(false) // 真实高度是否超阈值(控制按钮显隐)

const descCollapsible = computed(() => descOverflow.value)

// 折叠态 max-height 内联(过渡友好):展开态不设上限(自适应)
const descWrapStyle = computed(() =>
  descExpanded.value ? {} : { maxHeight: DESC_COLLAPSE_PX + 'px' },
)

async function measureDescHeight() {
  // descEl 是 <span>(描述内容),自身无 max-height 限制 → scrollHeight 即其真实渲染高度,
  // 不受父 .description-wrap 的 max-height 裁剪影响(span 在父内若被裁,其 scrollHeight
  // 仍反映完整内容高)。直接读无需临时改父样式。
  await nextTick()
  const el = descEl.value
  if (!el) { descOverflow.value = false; return }
  descOverflow.value = el.scrollHeight > DESC_COLLAPSE_PX
}

// P2-10: 描述编辑(对齐 IdeaDetail 模式;taskApi.update(id, 'description', value))
const editingDesc = ref(false)
const editDesc = ref('')
const savingDesc = ref(false)
function startEditDesc() {
  editDesc.value = task.value?.description ?? ''
  editingDesc.value = true
}
function cancelEditDesc() { editingDesc.value = false }
async function saveEditDesc() {
  if (!task.value || savingDesc.value) return
  savingDesc.value = true
  try {
    await taskApi.update(task.value.id, 'description', editDesc.value)
    task.value = { ...task.value, description: editDesc.value }
    editingDesc.value = false
    errorMsg.value = ''
  } catch (e: any) {
    errorMsg.value = t('taskDetail.descSaveFailed', { msg: e?.toString() ?? t('common.unknownError') })
  } finally {
    savingDesc.value = false
  }
}

// F-AiNodeSelfReview: output_json 解析 + AI 产出/自审渲染已抽至 TaskOutputCard 子组件
// (components/task/TaskOutputCard.vue),父级只传 output-json prop,零行为变更。

const projectName = computed(() => {
  const p = projects.value.find(p => p.id === task.value?.project_id)
  return p?.name ?? task.value?.project_id ?? '—'
})

// F-260619-01:解析 task.idea_id → 灵感 title(找不到时回退 id,保证非空可点击)
const ideaTitle = computed(() => {
  const ideaId = task.value?.idea_id
  if (!ideaId) return '—'
  const idea = ideas.value.find(i => i.id === ideaId)
  return idea?.title ?? ideaId
})

// ============================================================
// F-05 推进按钮 — 状态机合法下一态映射
// ------------------------------------------------------------
// 真相源:后端 df-nodes task_state_machine.rs can_transition(详见该文件注释矩阵)。
// 前端只做「按当前 status 显示哪些合法目标」的渲染决策,不做合法性判定;
// 点击调 advance_task,后端二次校验(can_transition)+ 原子写 + review_rounds 累加。
//
// 矩阵(行=from):
//   todo        → in_progress(开始), cancelled(取消)
//   in_progress → in_review(提交审查), blocked(阻塞), cancelled
//   in_review   → testing(通过进测试), in_progress(退回修改), blocked, cancelled
//   testing     → done(完成), in_review(退回重审), blocked, cancelled
//   blocked     → in_progress(恢复), cancelled
//   done/cancelled → 终态,无按钮
const advanceActions = computed<AdvanceAction[]>(() => {
  const s = task.value?.status
  if (!s) return []
  return ADVANCE_MAP[s] ?? []
})

// F-260616-06 ①-1: 工作流推进按钮的目标态。
// 仅当当前态的 primary(前向推进)目标 ∈ template_for 支持的三态 {in_progress, testing, done} 时显示。
// 非前向(退回/阻塞/取消)不走工作流(无对应模板,且语义上工作流只做前向推进)。
// ADVANCE_MAP 各行的首个 primary 项:todo→in_progress, in_review→testing, testing→done, blocked→in_progress
// in_progress 的 primary 是 in_review(无模板,不显示工作流推进按钮)。
const WF_SUPPORTED_TARGETS = new Set(['in_progress', 'testing', 'done'])
// CR-08-O1: blocked 是阻塞态(任务被阻塞),其 primary=in_progress 虽 ∈ WF_SUPPORTED_TARGETS,
// 但 template_for('in_progress') 是「todo→in_progress 从头执行」拓扑(AiNode 等重新跑一遍),
// blocked 恢复语义不该重跑 AiNode(任务已有上下文/进度,只需恢复非从头执行)。
// 最小修法:blocked 不显示工作流推进按钮,用户手动 advance(blocked→in_progress 走后端状态机)或先解除阻塞。
// (引入 blocked 恢复模板需后端 template_for 扩展,复杂度高,非阻断 med 项留待 UX 决策。)
const WF_EXCLUDED_FROM = new Set(['blocked'])
const wfAdvanceAction = computed<AdvanceAction | null>(() => {
  const s = task.value?.status
  if (!s) return null
  if (WF_EXCLUDED_FROM.has(s)) return null
  const actions = ADVANCE_MAP[s] ?? []
  const primary = actions.find(a => a.variant === 'btn-primary')
  if (!primary) return null
  return WF_SUPPORTED_TARGETS.has(primary.target) ? primary : null
})

async function handleAdvance(target: string) {
  if (!task.value || advancing.value) return
  advancing.value = true
  try {
    // advance_task 返回更新后的 TaskRecord(含新 status / review_rounds)
    const updated = await taskApi.advance(task.value.id, target)
    task.value = updated
    // P2-8: 手动推进成功后清工作流 UI 态(防上一轮工作流残留进度/终态提示污染新任务态)
    resetWorkflowUi()
    errorMsg.value = ''
  } catch (e: any) {
    errorMsg.value = t('taskDetail.advanceFailed', { msg: e?.toString() ?? t('common.unknownError') })
  } finally {
    advancing.value = false
  }
}

// F-260616-06 ①-1: 工作流推进 — 传空 dag + target_status,后端自动选推进链模板。
// B-41: 监听 workflow-event 显示轻量进度;完成/失败后任务态由后端回调推进(②-3/②-4),
//       df-data-changed 自动刷新 task(AR-11 已通),此处不直接改 task.value。
// name 用推进链标识(模板内未硬编码 task_id,运行时由后端 config 注入,前端只传 topology hint)。
async function handleWorkflowAdvance(target: string) {
  if (!task.value || wfAdvancing.value || advancing.value) return
  wfAdvancing.value = true
  wfResult.value = null
  try {
    const execId = await workflowApi.run(
      `task-advance:${target}`,
      {}, // 空 dag → 后端 template_for(target) 选模板
      {}, // 全局 config 留空(节点级 config 由模板 gate 注入 + 运行时 task_id 注入)
      task.value.id,
      target,
    )
    wfExecId.value = execId
    errorMsg.value = ''
    await refreshWorkflowDag(execId)
  } catch (e: any) {
    wfAdvancing.value = false
    wfExecId.value = null
    errorMsg.value = t('taskDetail.workflowAdvanceFailed', { msg: e?.toString() ?? t('common.unknownError') })
  }
}

// B-41: 进度状态已由共享 store 按 execId 派生(store.workflowProgress),不再本地逐事件累加。
// 原有 handleWorkflowEvent 已移除 —— 事件仍由 workflow store 全局收集,TaskDetail 只读派生结果。

async function load() {
  loading.value = true
  errorMsg.value = ''
  try {
    const [t, ps, ideasList] = await Promise.all([
      taskApi.get(taskId.value),
      // 项目列表用于解析 project_id → 项目名(独立入口,不依赖全局 store)
      projectApi.list(),
      // F-260619-01:灵感列表用于解析 idea_id → 灵感 title
      ideaApi.list(),
    ])
    task.value = t
    projects.value = ps
    ideas.value = ideasList
    // F-260805:父子数据(子任务取父标题 / 顶层任务取子任务列表),与主数据并行
    await Promise.all([loadParentTitle(), loadChildren()])
  } catch (e: any) {
    task.value = null
    errorMsg.value = t('taskDetail.loadFailed', { msg: e?.toString() ?? t('common.unknownError') })
  } finally {
    loading.value = false
  }
}

function refresh() {
  resetWorkflowUi()
  load()
}

// 路由参数变化(id 变化)时重新加载
watch(taskId, () => { resetWorkflowUi(); load() })

// Problem4 ②:切换 task / 描述渲染完成 → 重置折叠态 + 重测高度。
// renderedDesc 异步(Markdown 经 marked 渲染),内容变化后 nextTick 测真实 px。
watch(() => task.value?.id, () => { descExpanded.value = false })
watch(renderedDesc, () => { measureDescHeight() })

// B-260616-18: 数据变更联动刷新 unlistener(onMounted 注册,onBeforeUnmount 释放)
// 对齐 AiChat _unlistenToolSlow 生命周期模式。本视图绕 store 直调 taskApi.get/projectApi.list,
// 不享受 store 全局 df-data-changed 监听(该监听只刷 store.tasks 列表,不含本视图的当前 task 单体),
// 故此本地监听 entity=task/project 时重载当前 task。
let _unlistenDataChanged: (() => void) | null = null

onMounted(async () => {
  ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat 共享),不阻塞 load
  load()
  // F-260805:点击子任务快捷菜单外关闭菜单
  document.addEventListener('click', closeChildMenu)
  // B-260616-18: 后端 AI 工具(create/update/delete 等)emit df-data-changed → 本视图重载当前 task
  // entity=task:任务本体字段(标题/状态/描述/分支…)被改时刷新;entity=project:项目名变更影响 projectName 解析时刷新
  try {
    _unlistenDataChanged = await listen<DfDataChangedPayload>('df-data-changed', (event) => {
      const { entity } = event.payload
      if (entity === 'task' || entity === 'project' || entity === 'idea') {
        load()
      }
    })
  } catch (e) {
    console.error('[TaskDetail] 启动 df-data-changed 监听失败:', e)
  }
  // B-41: 工作流进度由 workflow store 全局 liveEvents 推导(store.workflowProgress),
  // 此处只需确保 store 事件监听已启动(幂等,共享单例;按 execution_id 过滤由派生层完成)。
  try {
    await store.startEventListener()
  } catch (e) {
    console.error('[TaskDetail] 启动 workflow 事件监听失败:', e)
  }
})

onBeforeUnmount(() => {
  if (_unlistenDataChanged) { _unlistenDataChanged(); _unlistenDataChanged = null }
  // F-260805:移除子任务快捷菜单关闭监听
  document.removeEventListener('click', closeChildMenu)
  // SW-260618-21: 清终态提示 timer 防卸载后写已销毁 ref
  if (_wfResultTimer) { clearTimeout(_wfResultTimer); _wfResultTimer = null }
})
</script>

<style scoped>
.task-detail { padding: 16px 20px 20px; }

/* page-header / btn / back-link 等已提取到 global.css 全局 */
.header-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.header-left h1 {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
}

/* Problem4 ① 吸顶 header(推进按钮始终可见) */
.task-detail-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--df-bg);
  /* 留底部细分割线,与全局 page-header margin-bottom 协同(0.5px 边框规范) */
  padding-bottom: 8px;
  border-bottom: 0.5px solid var(--df-border);
  /* 顶负 padding 抵消 .task-detail 的 16px 顶 padding,吸顶贴顶 */
  margin: -16px -20px 0;
  padding-left: 20px;
  padding-right: 20px;
  padding-top: 16px;
  align-items: center;
}
/* header-actions 内多个推进按钮 + refresh 排列(对齐全局 .header-actions gap:10px) */
.task-detail-header .header-actions { flex-wrap: wrap; justify-content: flex-end; }

/* advance 操作区已移入 header-actions,旧 .advance-row 样式删除 */

/* B-41 工作流推进轻量进度提示 */
.wf-progress {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 6px;
  font-size: 12px;
  color: var(--df-text-secondary);
}
.wf-progress-count { color: var(--df-text-dim); }
.wf-progress-hint { color: var(--df-success); }
.wf-progress-hint-fail { color: var(--df-danger); }

/* F-04 review 轮次徽章 */
.review-rounds-badge {
  font-size: 11px;
  color: var(--df-warning);
  background: rgba(255,217,61,0.12);
  padding: 3px 8px;
  border-radius: var(--df-radius-xs);
  margin-left: 8px;
  white-space: nowrap;
}

/* 状态/优先级标签 — 尺寸/圆角由全局 components.css .status-tag 统一，scoped 不重复定义 */
/* 状态色类已全局化至 components.css(scoped 删除,消除漂移) */

.priority-badge {
  font-size: 10px; font-weight: 500;
  padding: 1px 6px;
  border-radius: var(--df-radius-xs);
}
.priority-critical { background: rgba(255,107,107,0.2); color: var(--df-danger); }
.priority-high { background: rgba(255,152,0,0.2); color: #ff9800; }
.priority-medium { background: rgba(100,181,246,0.2); color: var(--df-info); }
.priority-low { background: rgba(90,99,128,0.2); color: var(--df-text-dim); }

/* ===== 面板 ===== */
/* Problem4 ⑤ 宽屏两栏:左栏(描述+关联信息+时间戳) / 右栏(任务产出+工作流进度)。
   窄屏(<960px)单列。对齐 ProjectDetail 两栏模式但阈值更宽(产出栏更窄)。
   不设 margin-top —— 全局 .page-header 已有 margin-bottom: var(--df-gap-page) 提供分隔。*/
.detail-grid {
  display: grid;
  grid-template-columns: 1fr 380px;
  gap: var(--df-gap-page);
  align-items: start; /* 两栏顶对齐,长栏不撑高短栏 */
}
.left-column, .right-column {
  display: flex;
  flex-direction: column;
  gap: var(--df-gap-grid);
  min-width: 0; /* 网格项防溢出(长 url/代码块) */
}

/* .panel / .panel-header 基础样式已收敛至全局 components.css(DRY 收口 B-260619)。 */

/* ===== 信息列表 ===== */
.task-info {
  display: flex;
  flex-direction: column;
  gap: var(--df-gap-grid);
}
/* B-260615-31:字段同行布局(label 固定宽 + value 占余),描述字段 info-block 保持块状)
   基础 .info-item/.label/.value 已收敛至全局 components.css(DRY 收口 B-260619),
   此处仅保留本组件特有覆盖。 */
/* B-24 漏 white-space 覆盖致 v-html 后 HTML 标签间 \n 被 pre-wrap 渲染为空行间距——移除 pre-wrap,描述由 .ai-md 接管(line-height 1.5 对齐 AiChat li)。
   Problem4 重设计:描述区从 .info-item 内移到独立 .description-wrap(可折叠容器),
   故选择器改 .description-wrap。display:block 让 <span> 有块盒模型,scrollHeight 可读。*/
.description-wrap .value.description { line-height: 1.5; display: block; }

/* Problem4 ② 描述可折叠:折叠态 max-height(内联注入 400px) + overflow hidden + 过渡。
   展开态 max-height none(内联不设),自然撑开。蒙层 .desc-fade 暗示下方有内容。*/
.description-wrap {
  position: relative;
  overflow: hidden;
  transition: max-height 0.25s var(--df-ease);
}
.desc-toggle {
  margin-top: 8px;
}
.desc-fade {
  /* 折叠态底部渐变蒙层,提示"下方还有内容"(展开态不渲染此元素) */
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 32px;
  background: linear-gradient(to bottom, transparent, var(--df-bg-card));
  pointer-events: none;
}

/* Problem4 ⑥ 创建/更新时间最底小字(独立行,非 label/value 对) */
.info-item.timestamps {
  flex-direction: row;
  gap: 16px;
  padding-top: var(--df-gap-grid);
  border-top: 0.5px solid var(--df-border);
}
.timestamp {
  font-size: 11px;
  color: var(--df-text-dim);
}

/* 右栏空产出占位提示 */
.output-empty { padding: 24px 12px; }

/* F-AiNodeSelfReview output_json 区块样式已随子组件移至 TaskOutputCard.vue */

/* ===== 任务描述 Markdown 渲染(B-24,基础样式收敛至全局 ai-md.css) ===== */

.project-link {
  color: var(--df-accent);
  text-decoration: none;
  font-weight: 500;
}
.project-link:hover { text-decoration: underline; }

.branch-tag {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 12px;
  color: var(--df-text-secondary);
  background: var(--df-accent-bg);
  padding: 2px 8px;
  border-radius: var(--df-radius-xs);
  font-family: monospace;
}
.branch-icon { color: var(--df-accent); }

.mono { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; }

.empty-hint {
  text-align: center; padding: 24px 12px;
  font-size: 13px; color: var(--df-text-dim);
}
.error-hint { color: var(--df-danger); }

/* ===== F-260805 父子任务:子任务面板 ===== */
.subtask-count {
  font-size: 11px;
  color: var(--df-text-dim);
  margin-left: auto;
  white-space: nowrap;
}
.subtask-progress {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 6px 0 10px;
}
.subtask-progress-text {
  font-size: 11px;
  color: var(--df-text-dim);
  white-space: nowrap;
}
/* 迷你进度条(与 Tasks.vue 树形列表同款,渐变填充) */
.mini-progress {
  flex: 1;
  height: 4px;
  background: var(--df-border);
  border-radius: var(--df-radius-xs);
  overflow: hidden;
}
.mini-progress-fill {
  height: 100%;
  border-radius: var(--df-radius-xs);
  background: linear-gradient(90deg, var(--df-accent), var(--df-success));
  transition: width 0.3s;
}
.subtask-list {
  display: flex;
  flex-direction: column;
}
/* 子任务批量勾选 + 全选 */
.subtask-checkbox { flex-shrink: 0; width: 14px; height: 14px; accent-color: var(--df-accent); cursor: pointer; margin: 0; }
.select-all {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
  font-size: 12px;
  color: var(--df-text-dim);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}
.select-all input { width: 14px; height: 14px; accent-color: var(--df-accent); cursor: pointer; }

.subtask-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: var(--df-radius-sm);
  cursor: pointer;
  position: relative; /* 快捷菜单绝对定位锚点 */
  transition: background 0.1s;
}
.subtask-item:hover { background: var(--df-accent-bg); }
.subtask-title {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  color: var(--df-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.subtask-quick-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 14px;
  padding: 2px 4px;
  opacity: 0;
  transition: opacity 0.15s;
  flex-shrink: 0;
}
.subtask-item:hover .subtask-quick-btn { opacity: 0.6; }
.subtask-quick-btn:hover { opacity: 1; }
/* 子任务快捷菜单(复用列表页 quick-menu 同款视觉) */
.quick-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  min-width: 160px;
  background: var(--df-bg-card);
  border: 0.5px solid var(--df-border);
  border-radius: var(--df-radius-sm);
  box-shadow: 0 6px 16px rgba(0,0,0,0.25);
  z-index: 20;
  padding: 6px 0;
}
.quick-menu-section { padding: 4px 0; }
.quick-menu-label {
  font-size: 10px;
  color: var(--df-text-dim);
  padding: 2px 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.quick-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 12px;
  background: none;
  border: none;
  color: var(--df-text);
  font-size: 12px;
  cursor: pointer;
  text-align: left;
}
.quick-menu-item:hover { background: rgba(255,255,255,0.06); }
.quick-menu-divider {
  height: 0.5px;
  background: var(--df-border);
  margin: 4px 0;
}
.subtask-empty { padding: 16px 8px; }
.subtask-add { margin-top: 8px; }

/* P2-9 子任务提示条 */
.subtask-hint {
  font-size: 12px;
  color: var(--df-text-secondary);
  background: var(--df-accent-bg);
  padding: 6px 12px;
  border-radius: var(--df-radius-sm);
  margin-bottom: var(--df-gap-grid);
}

/* P2-10 描述编辑(对齐 IdeaDetail .detail-desc-edit 模式) */
.detail-desc-edit {
  width: 100%;
  min-height: 80px;
  background: var(--df-bg);
  color: var(--df-text);
  border: 0.5px solid var(--df-border);
  border-radius: var(--df-radius-sm);
  padding: 8px 10px;
  font-size: 13px;
  line-height: 1.5;
  resize: vertical;
}
.desc-edit-actions { display: flex; gap: 8px; margin-top: 8px; }
.desc-empty { padding: 16px 8px; }
.desc-edit-btn { margin-top: 8px; }

/* Problem4 ⑤ 响应式:窄屏(<960px)两栏退单列,产出栏移到下方 */
@media (max-width: 960px) {
  .detail-grid { grid-template-columns: 1fr; }
}

/* 窄屏 header-actions 内推进按钮可能换行,允许并压缩间距 */
@media (max-width: 720px) {
  .task-detail-header { flex-wrap: wrap; }
  .task-detail-header .header-left { flex: 1 1 100%; margin-bottom: 8px; }
}
</style>
