<script setup lang="ts">
/* eslint-disable vue/no-mutating-props, unicorn/prefer-dom-node-text-content, no-useless-escape, no-nested-ternary */
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
import { Plus } from '@element-plus/icons-vue';
import { RDMS_REQ_PRIORITY_DICT_CODE, RDMS_TASK_ITEM_TYPE_DICT_CODE } from '@/constants/dict';
import { fetchGetMyParticipatedProjectPage } from '@/service/api';
import { useDict } from '@/hooks/business/dict';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import DictSelect from '@/components/custom/dict-select.vue';
import {
  type WorkReportStructuredSection,
  type WorkReportStructuredTask,
  formatPeriodDateRange,
  formatPeriodLabel,
  formatWeeklyPeriodLabel,
  getStructuredSections,
  getStructuredTasks
} from '../../shared/types';

const props = defineProps<{
  reportType: string;
  period: string;
  mode?: 'add' | 'edit' | 'detail';
  scene?: 'fill' | 'detail' | 'approval';
  baseInfo?: Api.WorkReport.Weekly.WeeklyReport | null;
  model: Api.WorkReport.Weekly.WeeklyReportSaveParams;
}>();

const emit = defineEmits<{
  back: [];
  save: [];
  submit: [];
  viewAudit: [];
  requestApprove: [];
  requestReject: [];
  pullDefaultDraft: [];
}>();

interface ReviewItem {
  workItem: string;
  days: number;
  hours?: number;
  content: string;
  contentHtml: string;
  contentSections?: StructuredSection[];
  contentTasks?: StructuredTask[];
  travelTasks?: StructuredTask[];
  reflection: string;
  removable?: boolean;
  source?: Api.WorkReport.Common.PersonalReportReviewItem;
}

interface PlanItem {
  workItem: string;
  target: string;
  targetHtml: string;
  targetSections?: StructuredSection[];
  targetTasks?: StructuredTask[];
  supportNeed: string;
  removable?: boolean;
  source?: Api.WorkReport.Common.PersonalReportPlanItem;
  sourceIndex?: number;
}

interface StructuredTask {
  title: string;
  detail: string;
  priority?: string;
  progress?: number;
  hours?: number;
  kind?: string;
}

interface PlanTaskDraft {
  title: string;
  detail: string;
  priority: StructuredTask['priority'];
  progress: number;
}

interface PlanSectionDraft {
  category: string;
  tasks: StructuredTask[];
  draft: PlanTaskDraft;
}

interface StructuredSection {
  category: string;
  tasks: StructuredTask[];
}

interface TravelSegment {
  dateRange: [string, string] | null;
  days: number | null;
  workItem: string;
  /** 是否为用户新增的分段，新增的才显示删除按钮 */
  isNew?: boolean;
}

const activeEditField = ref('');
const travelDialogVisible = ref(false);
const isReadonly = computed(() => props.mode === 'detail' || props.scene === 'approval');
const { dictData: priorityDictData, getLabel: getPriorityLabel } = useDict(RDMS_REQ_PRIORITY_DICT_CODE);
const { dictData: taskItemTypeDictData, getLabel: getTaskItemTypeLabel } = useDict(RDMS_TASK_ITEM_TYPE_DICT_CODE);
const DEFAULT_SECTION_CATEGORY = '工作内容';
const TRAVEL_SECTION_CATEGORY = '差旅';

const mainForm = reactive({
  get reporter() {
    return props.baseInfo?.reporterName || '--';
  },
  get deptName() {
    return props.baseInfo?.reporterDeptName || '--';
  },
  get postName() {
    return props.baseInfo?.reporterPostName || '--';
  },
  get supervisor() {
    return props.baseInfo?.supervisorName || '--';
  }
});

/** 是否出差：由父级 model 作为唯一提交源，本地编辑态只承载未完成的出差分段。 */
const businessTripValue = computed({
  get: () => props.model.isBusinessTrip,
  set: value => updateBusinessTrip(value)
});

const EMPTY_HTML = '本周期内暂无数据';
const TRAVEL_REVIEW_ITEM_TITLE = '本周差旅';
/** 工作事项下拉里的"我的事项"固定项，用于区分纯个人事务。 */
const MY_AFFAIRS_TITLE = '我的事项';

const planForm = ref({
  workItem: '',
  supportNeed: '',
  sections: [] as PlanSectionDraft[]
});
const activePlanSectionIndex = ref(-1);

function createPlanTaskDraft(): PlanTaskDraft {
  return { title: '', detail: '', priority: '2', progress: 0 };
}

const travelSegments = ref<TravelSegment[]>([]);
const planDialogVisible = ref(false);

/** 「我参与的项目」项目名清单，供“新增计划”工作事项下拉使用。 */
const participatedProjectNames = ref<string[]>([]);

async function loadParticipatedProjectNames() {
  // pageSize=-1 一次拉全部（不分页），由后端按"进行中 + 创建时间升序"过滤排序。
  const { data, error } = await fetchGetMyParticipatedProjectPage({ pageNo: 1, pageSize: -1 });
  if (error || !data) {
    participatedProjectNames.value = [];
    return;
  }
  participatedProjectNames.value = data.list
    .map(project => project.name?.trim())
    .filter((name): name is string => Boolean(name));
}

onMounted(loadParticipatedProjectNames);

const reviewWorkItemOptions = computed(() => {
  // 下拉数据来自「我参与的项目」API 拉取的项目名 + 「我的事项」固定项，
  // 供“新增计划-工作事项”下拉使用。
  const names = new Set<string>(participatedProjectNames.value);
  return [MY_AFFAIRS_TITLE, ...Array.from(names).filter(name => name !== MY_AFFAIRS_TITLE)];
});

const reviewItems = computed<ReviewItem[]>(() => {
  return (props.model.reviewItems || []).map(toReviewItem);
});

const travelWorkItemOptions = computed(() => {
  const names = reviewItems.value
    .map(item => item.workItem.trim())
    .filter(name => Boolean(name) && name !== TRAVEL_REVIEW_ITEM_TITLE && name !== MY_AFFAIRS_TITLE);

  return [...new Set(names), MY_AFFAIRS_TITLE];
});

/** 选中"我的事项"时，事项不参与项目优先级，需要隐藏优先级相关展示。 */
const isMyAffairsPlanItem = (workItem: string) => workItem.trim() === MY_AFFAIRS_TITLE;

function normalizeTravelDays(value: unknown) {
  const numberValue = Number(value);
  if (!Number.isFinite(numberValue) || numberValue <= 0) return null;
  return Math.round(numberValue * 2) / 2;
}

function isValidTravelDays(value: unknown) {
  const normalizedValue = normalizeTravelDays(value);
  return normalizedValue !== null && normalizedValue >= 0.5 && Number.isInteger(normalizedValue * 2);
}

function normalizeTravelDateText(value?: string | null) {
  if (!value) return '';
  const commaDateMatch = value.match(/^(\d{4}),(\d{1,2}),(\d{1,2})$/);

  if (commaDateMatch) {
    const [, year, month, day] = commaDateMatch;
    return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
  }

  return value;
}

function toTravelSegments(segments: Api.WorkReport.Weekly.WeeklyReportTravelSegment[] | undefined): TravelSegment[] {
  return (segments || []).map(item => ({
    dateRange:
      item.startDate && item.endDate
        ? [normalizeTravelDateText(item.startDate), normalizeTravelDateText(item.endDate)]
        : null,
    days: normalizeTravelDays(item.travelDays),
    workItem: ''
    /* 默认带入的分段不带 isNew，不可删除 */
  }));
}

function isCompleteTravelSegment(segment: TravelSegment) {
  return Boolean(
    segment.dateRange?.[0] && segment.dateRange?.[1] && isValidTravelDays(segment.days) && segment.workItem.trim()
  );
}

function toModelTravelSegments(segments: TravelSegment[]): Api.WorkReport.Weekly.WeeklyReportTravelSegment[] {
  return segments.filter(isCompleteTravelSegment).map((item, index) => ({
    sort: index + 1,
    startDate: normalizeTravelDateText(item.dateRange?.[0]) || '',
    endDate: normalizeTravelDateText(item.dateRange?.[1]) || '',
    travelDays: normalizeTravelDays(item.days) || 0
  }));
}

function isSameTravelSegments(
  source: TravelSegment[],
  target: Api.WorkReport.Weekly.WeeklyReportTravelSegment[] | undefined
) {
  const sourceSegments = toModelTravelSegments(source);
  const targetSegments = target || [];
  if (sourceSegments.length !== targetSegments.length) return false;

  return sourceSegments.every((item, index) => {
    const targetItem = targetSegments[index];
    return (
      item.startDate === normalizeTravelDateText(targetItem.startDate) &&
      item.endDate === normalizeTravelDateText(targetItem.endDate) &&
      Number(item.travelDays || 0) === Number(targetItem.travelDays || 0)
    );
  });
}

function syncTravelSegmentsToModel() {
  const nextSegments = toModelTravelSegments(travelSegments.value);
  if (isSameTravelSegments(travelSegments.value, props.model.travelSegments)) return;
  props.model.travelSegments = nextSegments;
}

watch(
  () => props.model.travelSegments,
  segments => {
    if (isSameTravelSegments(travelSegments.value, segments)) return;
    travelSegments.value = toTravelSegments(segments);
  },
  { immediate: true, deep: true }
);

watch(travelSegments, () => syncTravelSegmentsToModel(), { deep: true });

function createStructuredText(tasks: StructuredTask[]) {
  return tasks
    .map(task => {
      const title = task.title.trim();
      const detail = task.detail.trim();
      if (!detail || detail === title) return title;
      return `${title}\n    ${detail}`;
    })
    .join('\n');
}

function escapeHtml(value: string) {
  return value
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function normalizeTask(task: WorkReportStructuredTask): StructuredTask {
  return {
    title: task.title || '',
    detail: task.detail || '',
    priority: normalizePriorityCode(task.priority),
    progress: typeof task.progress === 'number' ? task.progress : undefined,
    hours: typeof task.hours === 'number' ? task.hours : undefined,
    kind: task.kind ? String(task.kind) : undefined
  };
}

function normalizeSectionCategory(value?: string | null, fallback = DEFAULT_SECTION_CATEGORY) {
  const category = resolveTaskItemTypeLabel(value).trim();
  return category || fallback;
}

function normalizeSection(section: WorkReportStructuredSection): StructuredSection {
  return {
    category: normalizeSectionCategory(section.category, '未分类'),
    tasks: section.tasks.map(normalizeTask)
  };
}

function mergeSectionsByCategory(sections: StructuredSection[]) {
  const sectionMap = new Map<string, StructuredSection>();

  sections.forEach(section => {
    const category = normalizeSectionCategory(section.category, '未分类');
    const existing = sectionMap.get(category);
    if (existing) {
      existing.tasks.push(...section.tasks);
    } else {
      sectionMap.set(category, {
        category,
        tasks: [...section.tasks]
      });
    }
  });

  return Array.from(sectionMap.values()).filter(section => section.tasks.length);
}

function normalizePriorityCode(value?: string | number | null) {
  const text = String(value ?? '').trim();
  if (!text) return undefined;

  const matchedByValue = priorityDictData.value.find(item => String(item.value) === text);
  if (matchedByValue) return String(matchedByValue.value);

  const matchedByLabel = priorityDictData.value.find(item => item.label === text);
  if (matchedByLabel) return String(matchedByLabel.value);

  const priorityLabelMatch = text.match(/^P(\d+)$/iu);
  if (priorityLabelMatch) return priorityLabelMatch[1];

  return text;
}

function resolvePriorityLabel(value?: string | number | null) {
  const priorityCode = normalizePriorityCode(value);
  if (!priorityCode) return '';
  return getPriorityLabel(priorityCode, {
    fallback: /^P\d+$/iu.test(priorityCode) ? priorityCode : `P${priorityCode}`
  });
}

function resolveTaskItemTypeLabel(value?: string | null) {
  return getTaskItemTypeLabel(value, { fallback: value || '工作内容' });
}

const STRUCTURED_TASK_PREFIX_RE = /^(?:(?:\d+[.．、])|(?:\d+\s+)|(?:[一二三四五六七八九十百千万]+[、.．]))\s*/u;
const WORKLOG_DAY_SEPARATOR = '[[WR_DAY_SPLIT]]';

function stripStructuredTaskPrefixV2(value: string) {
  return value.trim().replace(STRUCTURED_TASK_PREFIX_RE, '');
}

function stripStructuredTaskSuffixV2(value: string) {
  return value.trim().replace(/[。.!！？?]+$/u, '');
}

function formatStructuredTaskLineV2(task: StructuredTask, showHours = false) {
  const title = stripStructuredTaskSuffixV2(stripStructuredTaskPrefixV2(task.title));
  const metrics = [
    task.priority ? resolvePriorityLabel(task.priority) : '',
    typeof task.progress === 'number' ? `${task.progress}%` : '',
    showHours && typeof task.hours === 'number' ? `${task.hours}h` : ''
  ].filter(Boolean);

  return `${title}${metrics.length ? `（${metrics.join(' / ')}）` : ''}`;
}

function formatStructuredTaskDisplayLine(task: StructuredTask, index: number, showHours = false) {
  return `${index + 1}、${formatStructuredTaskLineV2(task, showHours)}`;
}

function getWorkLogEntries(detail: string): string[] {
  if (!detail) return [];
  // 仅按显式分隔标记切分，避免正文里的中文分号被误判成跨天分隔。
  return detail
    .split(WORKLOG_DAY_SEPARATOR)
    .map(item => item.trim())
    .filter(Boolean);
}

function createStructuredTextV2(sections: StructuredSection[], showHours = false) {
  return sections
    .map(section => {
      const categoryLabel = normalizeSectionCategory(section.category);
      return [
        `#${categoryLabel}`,
        ...section.tasks.map((task, index) => `${index + 1}、${formatStructuredTaskLineV2(task, showHours)}`)
      ]
        .filter(Boolean)
        .join('\n');
    })
    .join('\n');
}

function createStructuredHtmlV2(sections: StructuredSection[], showHours = false) {
  return sections
    .map(section => {
      const categoryLabel = normalizeSectionCategory(section.category);
      const tasks = section.tasks
        .map(
          (task, index) =>
            `<div class="rich-task-line">${escapeHtml(formatStructuredTaskDisplayLine(task, index, showHours))}</div>`
        )
        .join('');

      return `
        <div class="rich-section">
          <div class="rich-category-line"># ${escapeHtml(categoryLabel)}</div>
          ${tasks ? `<div class="rich-section-tasks">${tasks}</div>` : ''}
        </div>
      `;
    })
    .join('');
}

function flattenSectionTasks(sections: StructuredSection[]) {
  return sections.flatMap(section => section.tasks.map(task => ({ ...task })));
}

function createSectionsJson(sections: StructuredSection[]) {
  return sections.length ? JSON.stringify({ sections }) : null;
}

function createTaskMetrics(task: StructuredTask, showHours: boolean) {
  const metrics = [
    task.priority
      ? `<span class="rich-task-meta priority">${escapeHtml(resolvePriorityLabel(task.priority))}</span>`
      : '',
    typeof task.progress === 'number' ? `<span class="rich-task-meta">进度 ${task.progress}%</span>` : '',
    showHours && typeof task.hours === 'number' ? `<span class="rich-task-meta">${task.hours}h</span>` : ''
  ].filter(Boolean);
  return metrics.length ? `<div class="rich-task-metas" contenteditable="false">${metrics.join('')}</div>` : '';
}

function isTravelTask(task: StructuredTask) {
  return task.kind === 'travel';
}

function normalizeEditorText(value: string) {
  return value
    .replace(/\u00A0/g, ' ')
    .split('\n')
    .map(line => line.trim())
    .filter(Boolean)
    .join('\n');
}

function createStructuredTasksFromText(text: string, defaultTask?: Partial<StructuredTask>) {
  const lines = normalizeEditorText(text).split('\n').filter(Boolean);
  if (!lines.length || lines.join('\n') === EMPTY_HTML) return [];
  const defaultTitle = String(defaultTask?.title || '').trim();
  const tasks = lines
    .map(line => {
      const structuredMatch = line.match(/^(.+?)[（(]([^）)]*)[）)](?:\s*[：:]\s*(.*))?$/u);
      if (!structuredMatch) return null;
      const [, title, metricsText, detail = ''] = structuredMatch;

      return {
        ...defaultTask,
        title: title.trim(),
        detail: detail.trim(),
        ...resolveTaskMetrics(metricsText, defaultTask)
      };
    })
    .filter(Boolean) as StructuredTask[];

  if (tasks.length) return tasks;

  const detailLines = defaultTitle && lines[0] === defaultTitle ? lines.slice(1) : lines;

  return [
    {
      ...defaultTask,
      title: defaultTitle || lines[0],
      detail: defaultTitle ? detailLines.join('\n') : lines.slice(1).join('\n')
    }
  ];
}

function stripStructuredTaskPrefix(value: string) {
  return value.trim().replace(STRUCTURED_TASK_PREFIX_RE, '');
}

function stripStructuredTaskSuffix(value: string) {
  return value.trim().replace(/[。.!！？?]+$/u, '');
}

function getElementText(element: Element | null) {
  return normalizeEditorText((element as HTMLElement | null)?.innerText || '');
}

function resolveTaskMetrics(metricsText: string, fallback?: Partial<StructuredTask>, useFallback = true) {
  const metricParts = metricsText
    .split(/[\/、•]/u)
    .map(item => item.trim())
    .filter(Boolean);
  const priorityText = metricsText.match(/\bP\d+\b/iu)?.[0] || metricParts.find(item => /^P?\d+$/iu.test(item));
  const priority = normalizePriorityCode(priorityText || (useFallback ? fallback?.priority : undefined));
  // 同时支持 "进度 XX%" 与裸 "XX%"，避免失焦后丢失进度。
  const progressText =
    metricsText.match(/进度\s*(\d+(?:\.\d+)?)%/u)?.[1] ||
    metricParts.find(item => /^\d+(?:\.\d+)?%$/u.test(item))?.replace(/%$/u, '');
  const hoursText = metricsText.match(/(\d+(?:\.\d+)?)h/u)?.[1];

  return {
    priority,
    progress: progressText === undefined ? (useFallback ? fallback?.progress : undefined) : Number(progressText),
    hours: hoursText === undefined ? (useFallback ? fallback?.hours : undefined) : Number(hoursText)
  };
}

function isStructuredMetricsText(text: string) {
  return /(?:\bP\d+\b|进度\s*\d+(?:\.\d+)?%|\d+(?:\.\d+)?%|\d+(?:\.\d+)?h)/iu.test(text.trim());
}

function extractStructuredTaskParts(normalizedText: string) {
  const colonIndex = normalizedText.search(/[：:]/u);
  const mainText = colonIndex >= 0 ? normalizedText.slice(0, colonIndex).trim() : normalizedText.trim();
  const detailText = colonIndex >= 0 ? normalizedText.slice(colonIndex + 1).trim() : '';

  const bracketMatches = Array.from(mainText.matchAll(/[（(]([^（）()]*)[）)]/gu));
  const completeMetricsMatch = [...bracketMatches].reverse().find(match => isStructuredMetricsText(match[1] || ''));

  if (completeMetricsMatch && completeMetricsMatch.index !== undefined) {
    const fullMatch = completeMetricsMatch[0] || '';
    const metricsText = completeMetricsMatch[1] || '';
    const startIndex = completeMetricsMatch.index;
    const endIndex = startIndex + fullMatch.length;

    return {
      rawTitle: `${mainText.slice(0, startIndex)}${mainText.slice(endIndex)}`.trim(),
      metricsText,
      detail: detailText
    };
  }

  const lastOpenIndex = Math.max(mainText.lastIndexOf('（'), mainText.lastIndexOf('('));
  const lastCloseIndex = Math.max(mainText.lastIndexOf('）'), mainText.lastIndexOf(')'));

  if (lastOpenIndex > lastCloseIndex) {
    const metricsText = mainText.slice(lastOpenIndex + 1).trim();
    if (isStructuredMetricsText(metricsText)) {
      return {
        rawTitle: mainText.slice(0, lastOpenIndex).trim(),
        metricsText,
        detail: detailText
      };
    }
  }

  return {
    rawTitle: mainText,
    metricsText: '',
    detail: detailText
  };
}

function parseStructuredSectionTaskText(
  text: string,
  fallback?: Partial<StructuredTask>,
  useFallback = false
): StructuredTask | null {
  const normalizedText = stripStructuredTaskPrefix(text);
  if (!normalizedText) return null;

  const { rawTitle, metricsText, detail } = extractStructuredTaskParts(normalizedText);
  const title = stripStructuredTaskSuffix(rawTitle);
  if (!title) return null;

  return {
    title,
    detail: detail.trim(),
    ...resolveTaskMetrics(metricsText, fallback, useFallback)
  };
}

function createStructuredSectionsFromTextV2(
  text: string,
  defaultCategory: string,
  fallbackTask?: Partial<StructuredTask>
): StructuredSection[] {
  const lines = normalizeEditorText(text).split('\n').filter(Boolean);
  if (!lines.length || lines.join('\n') === EMPTY_HTML) return [];

  const sections: StructuredSection[] = [];
  const ensureSection = (categoryText: string) => {
    const category = normalizeSectionCategory(categoryText, defaultCategory);
    let section = sections.find(item => item.category === category);
    if (!section) {
      section = { category, tasks: [] };
      sections.push(section);
    }
    return section;
  };

  let currentCategory = '';
  let previousTask: StructuredTask | null = null;
  const legacyTaskLineRe =
    /^(?!(?:(?:\d+[.．、])|(?:\d+\s+)|(?:[一二三四五六七八九十百千万]+[、.．]))\s*)(.+?)\s*[-－]\s*(.+)$/u;

  const isKnownSectionCategory = (category: string) => {
    const normalizedCategory = category.trim();
    if (!normalizedCategory) return false;
    if ([DEFAULT_SECTION_CATEGORY, TRAVEL_SECTION_CATEGORY, MY_AFFAIRS_TITLE].includes(normalizedCategory)) return true;
    return taskItemTypeDictData.value.some(item => item.label === normalizedCategory);
  };

  const isLegacyStructuredTaskLine = (line: string, strict = false) => {
    const legacyMatch = line.match(legacyTaskLineRe);
    if (!legacyMatch) return false;

    const [, rawCategory, rawTaskText] = legacyMatch;
    const category = rawCategory.trim();
    const normalizedTaskText = rawTaskText.trim();
    if (!category || !normalizedTaskText) return false;

    const { rawTitle, metricsText, detail } = extractStructuredTaskParts(normalizedTaskText);
    const title = stripStructuredTaskSuffix(rawTitle);
    if (!title) return false;

    const hasTaskStructureHint =
      Boolean(detail) ||
      isStructuredMetricsText(metricsText) ||
      normalizedTaskText.includes('（') ||
      normalizedTaskText.includes('(') ||
      normalizedTaskText.includes('：') ||
      normalizedTaskText.includes(':');

    if (isKnownSectionCategory(category) || hasTaskStructureHint) return true;

    if (strict) return false;

    return category.length <= 12;
  };

  const isExplicitStructuredTaskLine = (line: string) => {
    if (!STRUCTURED_TASK_PREFIX_RE.test(line)) return false;
    const normalizedLine = stripStructuredTaskPrefix(line);
    const { rawTitle, metricsText } = extractStructuredTaskParts(normalizedLine);
    return Boolean(stripStructuredTaskSuffix(rawTitle) && isStructuredMetricsText(metricsText));
  };

  const shouldAppendToPreviousTaskDetail = (line: string) => {
    if (!previousTask?.detail) return false;
    if (line.startsWith('#')) return false;
    if (isLegacyStructuredTaskLine(line, true)) return false;
    if (isExplicitStructuredTaskLine(line)) return false;
    return true;
  };

  lines.forEach(line => {
    const trimmedLine = line.trim();
    if (!trimmedLine) return;

    if (trimmedLine.startsWith('#')) {
      currentCategory = trimmedLine.replace(/^#\s*/u, '').trim();
      if (currentCategory) ensureSection(currentCategory);
      previousTask = null;
      return;
    }

    // 仅当行首不是结构化任务前缀（如 "3、"）时，才按旧式 "<分类> - <事项>" 解析；
    // 否则会把 "2026-06-12 - 2026-06-19" 这种含 " - " 的出差行误判为分类。
    const legacyMatch = isLegacyStructuredTaskLine(trimmedLine, Boolean(previousTask?.detail))
      ? trimmedLine.match(legacyTaskLineRe)
      : null;
    if (legacyMatch) {
      const [, rawCategory, rawTaskText] = legacyMatch;
      const category = rawCategory.trim();
      const task = parseStructuredSectionTaskText(rawTaskText, fallbackTask);
      if (!category || !task) return;
      ensureSection(category).tasks.push(task);
      currentCategory = category;
      previousTask = task;
      return;
    }

    if (shouldAppendToPreviousTaskDetail(trimmedLine)) {
      const lastTask = previousTask;
      if (!lastTask) return;
      lastTask.detail = lastTask.detail ? `${lastTask.detail}\n${trimmedLine}` : trimmedLine;
      return;
    }

    const task = parseStructuredSectionTaskText(trimmedLine, fallbackTask);
    if (!task) return;

    const hasStructuredHint =
      STRUCTURED_TASK_PREFIX_RE.test(trimmedLine) ||
      trimmedLine.includes('（') ||
      trimmedLine.includes('(') ||
      trimmedLine.includes('：') ||
      trimmedLine.includes(':');

    if (!hasStructuredHint && !currentCategory) return;

    ensureSection(currentCategory || defaultCategory).tasks.push(task);
    previousTask = task;
  });

  return mergeSectionsByCategory(sections);
}

function createFallbackTaskLookup(sections: StructuredSection[]) {
  const lookup = new Map<string, StructuredTask[]>();

  sections.forEach(section => {
    const categoryKey = resolveTaskItemTypeLabel(section.category).trim();
    section.tasks.forEach(task => {
      const titleKey = stripStructuredTaskSuffixV2(stripStructuredTaskPrefixV2(task.title));
      const key = `${categoryKey}||${titleKey}`;
      const list = lookup.get(key);
      if (list) {
        list.push(task);
      } else {
        lookup.set(key, [task]);
      }
    });
  });

  return lookup;
}

function parseStructuredSectionsFromEditorV2(
  editor: HTMLElement,
  fallbackSections: StructuredSection[] = [],
  defaultCategory = DEFAULT_SECTION_CATEGORY
) {
  const parsedSections = createStructuredSectionsFromTextV2(editor.innerText, defaultCategory);
  if (!parsedSections.length) return [];

  const fallbackLookup = createFallbackTaskLookup(fallbackSections);

  const merged: StructuredSection[] = parsedSections.map((section, sectionIndex) => {
    const fallbackSection = fallbackSections[sectionIndex];
    const categoryKey = resolveTaskItemTypeLabel(section.category).trim();

    return {
      category: section.category,
      tasks: section.tasks.map((task, taskIndex) => {
        const titleKey = stripStructuredTaskSuffixV2(stripStructuredTaskPrefixV2(task.title));
        const matchedByKey = fallbackLookup.get(`${categoryKey}||${titleKey}`)?.shift();
        const matchedByIndex = fallbackSection?.tasks[taskIndex];
        const fallbackTask = matchedByKey || matchedByIndex;

        return {
          ...task,
          detail: fallbackTask?.detail || '',
          kind: fallbackTask?.kind,
          hours: task.hours
        };
      })
    };
  });

  // 将出差任务从其他分类中抽离，统一归到独立的"差旅"段落，
  // 避免失焦后被错误归类到上一个分类下。
  return groupTravelTasksIntoSection(merged);
}

function groupTravelTasksIntoSection(sections: StructuredSection[]) {
  const travelTasks: StructuredTask[] = [];
  const remainingSections = sections.map(section => {
    const kept = section.tasks.filter(task => {
      if (isTravelTask(task)) {
        travelTasks.push(task);
        return false;
      }
      return true;
    });
    return { category: section.category, tasks: kept };
  });

  if (!travelTasks.length) return remainingSections;

  const existingTravelIndex = remainingSections.findIndex(section => section.category === TRAVEL_SECTION_CATEGORY);
  if (existingTravelIndex >= 0) {
    remainingSections[existingTravelIndex].tasks = [...remainingSections[existingTravelIndex].tasks, ...travelTasks];
  } else {
    remainingSections.push({ category: TRAVEL_SECTION_CATEGORY, tasks: travelTasks });
  }

  return remainingSections.filter(section => section.tasks.length);
}

function getReviewItemSections(item: Api.WorkReport.Common.PersonalReportReviewItem) {
  const structuredSections = mergeSectionsByCategory(getStructuredSections(item.contentJson).map(normalizeSection));
  if (structuredSections.length) return structuredSections;

  return createStructuredSectionsFromTextV2(item.contentText || '', item.itemTitle || '未分类', {
    hours: typeof item.workHours === 'number' ? item.workHours : Number(item.workHours || 0) || undefined
  });
}

function getPlanItemSections(item: Api.WorkReport.Common.PersonalReportPlanItem) {
  const structuredSections = mergeSectionsByCategory(getStructuredSections(item.targetJson).map(normalizeSection));
  if (structuredSections.length) return structuredSections;

  return createStructuredSectionsFromTextV2(item.targetText || '', item.itemTitle || DEFAULT_SECTION_CATEGORY);
}

function toReviewItem(item: Api.WorkReport.Common.PersonalReportReviewItem): ReviewItem {
  const contentSections = getReviewItemSections(item);
  const contentTasks = flattenSectionTasks(contentSections);
  const contentHtml = contentSections.length
    ? createStructuredHtmlV2(contentSections, true)
    : escapeHtml(item.contentText || '').replace(/\n/g, '<br>');

  return {
    workItem: item.itemTitle || '未命名工作',
    days: 0,
    hours: item.workHours || 0,
    content: item.contentText || '',
    contentHtml: contentHtml || EMPTY_HTML,
    contentSections,
    contentTasks,
    reflection: item.reflectionText || '',
    removable: true,
    source: item
  };
}

function toPlanItem(item: Api.WorkReport.Common.PersonalReportPlanItem, index: number): PlanItem {
  const targetSections = getPlanItemSections(item);
  const targetTasks = flattenSectionTasks(targetSections);
  const targetHtml = targetSections.length
    ? createStructuredHtmlV2(targetSections)
    : escapeHtml(item.targetText || '').replace(/\n/g, '<br>');

  return {
    workItem: item.itemTitle || '未命名计划',
    target: item.targetText || '',
    targetHtml: targetHtml || EMPTY_HTML,
    targetSections,
    targetTasks,
    supportNeed: item.supportNeed || '',
    /* 只有用户新增的计划才可删除，默认带入的不可删除 */
    removable: true,
    source: item,
    sourceIndex: index
  };
}

const nextPlans = computed<PlanItem[]>(() => {
  return (props.model.planItems || []).map(toPlanItem);
});

const totalHours = computed(() => {
  const baseTotalWorkHours = Number(props.baseInfo?.totalWorkHours ?? 0);
  if (Number.isFinite(baseTotalWorkHours) && baseTotalWorkHours > 0) return baseTotalWorkHours;

  return (props.model.reviewItems || []).reduce((sum, item) => sum + Number(item.workHours || 0), 0);
});

const periodText = computed(() => {
  const weeklyLabel = formatWeeklyPeriodLabel(
    props.model.periodStartDate || undefined,
    props.model.periodEndDate || undefined,
    props.model.periodLabel || props.period
  );
  if (weeklyLabel && weeklyLabel !== '--') return weeklyLabel;

  const rangeText = formatPeriodDateRange(
    props.model.periodStartDate || undefined,
    props.model.periodEndDate || undefined
  );
  if (rangeText !== '--') return rangeText;

  return formatPeriodLabel(props.model.periodLabel || props.period) || '--';
});

const totalTravelDays = computed(() => {
  const total = travelSegments.value.reduce((sum, segment) => sum + Number(segment.days || 0), 0);
  return Math.round(total * 10) / 10;
});

const hasCompleteTravelSegment = computed(() => travelSegments.value.some(isCompleteTravelSegment));

function calcSegmentDays(segment: TravelSegment) {
  if (!segment.dateRange || !segment.dateRange[0] || !segment.dateRange[1]) return segment.days || 0;
  const start = new Date(segment.dateRange[0]);
  const end = new Date(segment.dateRange[1]);
  if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || end < start) return segment.days || 0;
  const diffDays = (end.getTime() - start.getTime()) / 86400000 + 1;
  return Math.round(diffDays * 10) / 10;
}

function syncSegmentDays(index: number) {
  const segment = travelSegments.value[index];
  segment.days = normalizeTravelDays(calcSegmentDays(segment));
}

function buildGroupedTravelTasks() {
  const groupedMap = new Map<string, StructuredTask[]>();

  travelSegments.value.filter(isCompleteTravelSegment).forEach(segment => {
    const key = segment.workItem.trim();
    const task: StructuredTask = {
      title: `${normalizeTravelDateText(segment.dateRange![0])} - ${normalizeTravelDateText(segment.dateRange![1])} · 共出差${segment.days}天`,
      detail: '',
      kind: 'travel'
    };
    const existing = groupedMap.get(key);
    if (existing) {
      existing.push(task);
    } else {
      groupedMap.set(key, [task]);
    }
  });

  return Array.from(groupedMap.entries()).map(([workItem, tasks]) => ({ workItem, tasks }));
}

function handleTravelDaysChange(index: number, value: number | undefined) {
  const segment = travelSegments.value[index];
  if (!segment) return;
  segment.days = normalizeTravelDays(value);
}

function syncTravelReviewItem() {
  props.model.isBusinessTrip = true;
  syncTravelSegmentsToModel();
}

function removeTravelTasksFromSections(sections: StructuredSection[]) {
  return sections
    .map(section => ({
      category: section.category,
      tasks: section.tasks.filter(task => !isTravelTask(task))
    }))
    .filter(section => section.tasks.length);
}

function appendTasksToSections(
  sections: StructuredSection[],
  tasks: StructuredTask[],
  fallbackCategory = DEFAULT_SECTION_CATEGORY
) {
  const nextSections = sections.map(section => ({
    category: section.category,
    tasks: [...section.tasks]
  }));

  if (!tasks.length) return nextSections;

  // 出差任务始终归到独立的"差旅"段落，避免被误放到上一个普通分类下。
  const travelTasks = tasks.filter(isTravelTask);
  const otherTasks = tasks.filter(task => !isTravelTask(task));

  if (otherTasks.length) {
    const targetSection =
      nextSections[nextSections.length - 1] ||
      (() => {
        const section = { category: fallbackCategory, tasks: [] as StructuredTask[] };
        nextSections.push(section);
        return section;
      })();
    targetSection.tasks.push(...otherTasks);
  }

  if (travelTasks.length) {
    const travelSection =
      nextSections.find(section => section.category === TRAVEL_SECTION_CATEGORY) ||
      (() => {
        const section = { category: TRAVEL_SECTION_CATEGORY, tasks: [] as StructuredTask[] };
        nextSections.push(section);
        return section;
      })();
    travelSection.tasks.push(...travelTasks);
  }

  return nextSections;
}

function removeTravelTasksFromReviewItems() {
  (props.model.reviewItems || []).forEach(item => {
    const currentSections = getReviewItemSections(item);
    const nextSections = removeTravelTasksFromSections(currentSections);
    if (
      nextSections.length === currentSections.length &&
      flattenSectionTasks(nextSections).length === flattenSectionTasks(currentSections).length
    )
      return;
    item.contentJson = createSectionsJson(nextSections);
    item.contentText = createStructuredTextV2(nextSections, true);
  });
}

function updateBusinessTrip(value: boolean) {
  if (!value) {
    removeTravelReviewItem();
    return;
  }

  props.model.isBusinessTrip = true;
  travelDialogVisible.value = true;
  if (!travelSegments.value.length) addTravelSegment();
  syncTravelSegmentsToModel();
}

function removeTravelReviewItem() {
  props.model.isBusinessTrip = false;
  props.model.travelSegments = [];
  travelSegments.value = [];
  travelDialogVisible.value = false;
  removeTravelTasksFromReviewItems();
}

function addTravelSegment() {
  travelSegments.value.push({
    dateRange: null,
    days: null,
    workItem: travelWorkItemOptions.value[0] || MY_AFFAIRS_TITLE,
    isNew: true
  });
}

function removeTravelSegment(index: number) {
  travelSegments.value.splice(index, 1);
}

function resetPlanForm() {
  planForm.value = {
    workItem: '',
    supportNeed: '',
    sections: [{ category: '', tasks: [], draft: createPlanTaskDraft() }]
  };
  activePlanSectionIndex.value = 0;
}

function showInlinePlanForm() {
  planDialogVisible.value = true;
  resetPlanForm();
}

function cancelInlinePlan() {
  planDialogVisible.value = false;
  resetPlanForm();
}

function addPlanSection() {
  planForm.value.sections.push({ category: '', tasks: [], draft: createPlanTaskDraft() });
  activePlanSectionIndex.value = planForm.value.sections.length - 1;
}

function removePlanSection(index: number) {
  planForm.value.sections.splice(index, 1);
  if (activePlanSectionIndex.value === index) {
    activePlanSectionIndex.value = -1;
  } else if (activePlanSectionIndex.value > index) {
    activePlanSectionIndex.value -= 1;
  }
}

function addPlanTask(sectionIndex: number) {
  const section = planForm.value.sections[sectionIndex];
  if (!section?.draft.title.trim()) return;
  const task: StructuredTask = {
    title: section.draft.title.trim(),
    detail: section.draft.detail.trim(),
    progress: section.draft.progress
  };
  // 我的事项不参与项目优先级，存进任务前显式剔除，结构化展示括号里就不会出现优先级。
  if (!isMyAffairsPlanItem(planForm.value.workItem)) {
    task.priority = normalizePriorityCode(section.draft.priority);
  }
  section.tasks.push(task);
  section.draft = createPlanTaskDraft();
}

function removePlanTask(sectionIndex: number, taskIndex: number) {
  planForm.value.sections[sectionIndex]?.tasks.splice(taskIndex, 1);
}

function submitInlinePlan() {
  const sections: StructuredSection[] = planForm.value.sections
    .filter(section => section.category.trim() && section.tasks.length)
    .map(section => ({
      category: normalizeSectionCategory(section.category),
      tasks: section.tasks.map(task => ({ ...task }))
    }));
  if (!sections.length) return;
  const target = createStructuredTextV2(sections);
  const workItem = planForm.value.workItem.trim();
  const sameWorkItem = props.model.planItems.find(item => item.itemTitle.trim() === workItem);

  if (sameWorkItem) {
    const existingSections = getPlanItemSections(sameWorkItem);
    const mergedSections = mergeSectionsByCategory([...existingSections, ...sections]);
    sameWorkItem.targetText = createStructuredTextV2(mergedSections);
    sameWorkItem.targetJson = createSectionsJson(mergedSections);
    if (planForm.value.supportNeed.trim()) {
      sameWorkItem.supportNeed = [sameWorkItem.supportNeed, planForm.value.supportNeed.trim()]
        .filter(Boolean)
        .join('\n');
    }
  } else {
    props.model.planItems.push({
      itemNumber: props.model.planItems.length + 1,
      itemTitle: workItem,
      targetText: target,
      targetJson: createSectionsJson(sections),
      supportNeed: planForm.value.supportNeed,
      _isNew: true
    } as Api.WorkReport.Common.PersonalReportPlanItem & { _isNew: boolean });
  }
  resetPlanForm();
  planDialogVisible.value = false;
}

function removePlanItem(index: number) {
  const item = nextPlans.value[index];
  if (item?.sourceIndex !== undefined) props.model.planItems.splice(item.sourceIndex, 1);
}

function removeReviewItem(index: number) {
  const item = reviewItems.value[index];
  if (!item?.source) return;
  const sourceIndex = props.model.reviewItems.findIndex(source => source === item.source);
  if (sourceIndex >= 0) props.model.reviewItems.splice(sourceIndex, 1);
}

function confirmTravelReviewItem() {
  if (!hasCompleteTravelSegment.value) return;
  const groupedTravelTasks = buildGroupedTravelTasks();
  if (!groupedTravelTasks.length) return;

  removeTravelTasksFromReviewItems();

  groupedTravelTasks.forEach(({ workItem, tasks }) => {
    const targetItem = props.model.reviewItems.find(item => item.itemTitle.trim() === workItem);

    if (targetItem) {
      const existingSections = getReviewItemSections(targetItem);
      const mergedSections = appendTasksToSections(
        existingSections,
        tasks,
        existingSections.at(-1)?.category || TRAVEL_SECTION_CATEGORY
      );
      targetItem.contentJson = createSectionsJson(mergedSections);
      targetItem.contentText = createStructuredTextV2(mergedSections, true);
      return;
    }

    const travelSections: StructuredSection[] = [{ category: TRAVEL_SECTION_CATEGORY, tasks }];
    props.model.reviewItems.push({
      itemNumber: props.model.reviewItems.length + 1,
      itemTitle: workItem,
      workHours: 0,
      contentText: createStructuredTextV2(travelSections, true),
      contentJson: createSectionsJson(travelSections),
      reflectionText: ''
    });
  });

  syncTravelReviewItem();
  travelDialogVisible.value = false;
}

function focusEditField(key: string) {
  activeEditField.value = key;
}

function blurEditField(key: string) {
  if (activeEditField.value === key) activeEditField.value = '';
}

function isStructuredEditorUnchanged(text: string, sections: StructuredSection[] | undefined, showHours = false) {
  if (!sections?.length) return false;
  return normalizeEditorText(text) === createStructuredTextV2(sections, showHours);
}

/** 编辑态下是否显示"具体工作内容"的结构化预览（含 ElPopover 工作日志） */
function showContentStructuredView(index: number) {
  const item = reviewItems.value[index];
  if (!item?.contentSections?.length) return false;
  if (isReadonly.value) return true;
  // 编辑/新增模式下，仅在该字段未聚焦时显示结构化预览
  return activeEditField.value !== `content-${index}`;
}

/** 编辑态下是否显示"具体目标"的结构化预览（含 ElPopover 工作日志） */
function showTargetStructuredView(index: number) {
  const item = nextPlans.value[index];
  if (!item?.targetSections?.length) return false;
  if (isReadonly.value) return true;
  // 编辑/新增模式下，仅在该字段未聚焦时显示结构化预览
  return activeEditField.value !== `target-${index}`;
}

/** 点击结构化预览区域时切换到编辑态并聚焦 */
function handleStructuredViewClick(fieldKey: string) {
  if (isReadonly.value) return;
  activeEditField.value = fieldKey;
  nextTick(() => {
    const editor = document.querySelector(`[data-field-key="${fieldKey}"]`) as HTMLElement;
    editor?.focus();
  });
}

function syncRichContent(item: ReviewItem, event: Event) {
  const target = event.currentTarget as HTMLElement;
  if (!item.source) return;
  if (isStructuredEditorUnchanged(target.innerText, item.contentSections, true)) {
    item.source.contentJson = createSectionsJson(item.contentSections || []);
    item.source.contentText = createStructuredTextV2(item.contentSections || [], true);
    return;
  }
  const sections = parseStructuredSectionsFromEditorV2(
    target,
    item.contentSections || [],
    item.contentSections?.[0]?.category || DEFAULT_SECTION_CATEGORY
  );
  item.source.contentJson = createSectionsJson(sections);
  item.source.contentText = createStructuredTextV2(sections, true);
}

function syncRichTarget(item: PlanItem, event: Event) {
  const target = event.currentTarget as HTMLElement;
  if (!item.source) return;
  if (isStructuredEditorUnchanged(target.innerText, item.targetSections)) {
    item.source.targetJson = createSectionsJson(item.targetSections || []);
    item.source.targetText = createStructuredTextV2(item.targetSections || []);
    return;
  }
  const sections = parseStructuredSectionsFromEditorV2(
    target,
    item.targetSections || [],
    item.targetSections?.[0]?.category || DEFAULT_SECTION_CATEGORY
  );
  item.source.targetJson = createSectionsJson(sections);
  item.source.targetText = createStructuredTextV2(sections);
}

function syncRichReflection(item: ReviewItem, event: Event) {
  const target = event.currentTarget as HTMLElement;
  if (item.source) item.source.reflectionText = target.innerText.trim();
}

function syncRichSupport(item: PlanItem, event: Event) {
  const target = event.currentTarget as HTMLElement;
  if (item.source) item.source.supportNeed = target.innerText.trim();
}
</script>

<template>
  <div class="card form-page">
    <div class="section">
      <div class="section-title">
        <span>基础信息</span>
        <div v-if="mode === 'edit' && !isReadonly" class="section-title-right">
          <ElButton size="small" plain type="primary" @click="emit('pullDefaultDraft')">
            <template #icon>
              <icon-mdi-refresh class="text-icon" />
            </template>
            刷新
          </ElButton>
        </div>
      </div>
      <div class="compose-grid">
        <div class="field">
          <label>姓名</label>
          <ElInput v-model="mainForm.reporter" disabled />
        </div>
        <div class="field">
          <label>部门</label>
          <ElInput v-model="mainForm.deptName" disabled />
        </div>
        <div class="field">
          <label>岗位</label>
          <ElInput v-model="mainForm.postName" disabled />
        </div>
        <div class="field">
          <label>直接上级</label>
          <ElInput v-model="mainForm.supervisor" disabled />
        </div>
        <div class="field">
          <label>周期</label>
          <ElInput :model-value="periodText" disabled />
        </div>
        <div class="field">
          <label>是否出差</label>
          <ElRadioGroup v-model="businessTripValue" class="radio-group-full" :disabled="isReadonly">
            <ElRadio :value="false" border>否</ElRadio>
            <ElRadio :value="true" border>是</ElRadio>
          </ElRadioGroup>
        </div>
      </div>
    </div>

    <div class="section">
      <div class="section-title">
        <div class="section-title-left">
          <span>当期重点工作回顾</span>
          <span class="source-chip">{{ reviewItems.length }} 项工作</span>
          <span class="source-chip">共 {{ totalHours }}h</span>
        </div>
      </div>
      <div class="review-grid layout-row">
        <div v-if="!reviewItems.length">{{ EMPTY_HTML }}</div>
        <div v-for="(item, index) in reviewItems" :key="index" class="review-card">
          <div class="review-index-cell">
            <span class="row-index">{{ index + 1 }}</span>
          </div>
          <div class="review-name-cell">
            <div class="work-title-line">
              <strong>{{ item.workItem }}</strong>
              <span>共 {{ item.hours }}h</span>
            </div>
          </div>
          <div class="review-action-cell">
            <ElPopconfirm
              v-if="item.removable !== false && !isReadonly"
              title="确认删除这条数据吗？"
              confirm-button-text="删除"
              cancel-button-text="取消"
              width="220"
              @confirm="removeReviewItem(index)"
            >
              <template #reference>
                <button class="item-remove-btn" aria-label="删除数据">×</button>
              </template>
            </ElPopconfirm>
          </div>

          <div class="review-editor-grid">
            <div class="field">
              <label>具体工作内容及成果描述</label>
              <div
                v-if="showContentStructuredView(index)"
                class="rich-editor"
                :class="{ 'rich-editor--preview': !isReadonly }"
                @click="handleStructuredViewClick(`content-${index}`)"
              >
                <div
                  v-for="(section, sectionIndex) in item.contentSections"
                  :key="`${index}-${sectionIndex}`"
                  class="rich-section"
                >
                  <div class="rich-category-line"># {{ resolveTaskItemTypeLabel(section.category) }}</div>
                  <div class="rich-section-tasks">
                    <template v-for="(task, taskIndex) in section.tasks" :key="`${index}-${sectionIndex}-${taskIndex}`">
                      <ElPopover v-if="task.detail" placement="top-start" trigger="hover" :width="420">
                        <template #reference>
                          <div class="rich-task-line rich-task-line--interactive">
                            {{ formatStructuredTaskDisplayLine(task, taskIndex, true) }}
                          </div>
                        </template>
                        <div class="structured-preview__popover">
                          <template v-if="getWorkLogEntries(task.detail).length">
                            <div
                              v-for="(entry, entryIndex) in getWorkLogEntries(task.detail)"
                              :key="`${index}-${sectionIndex}-${taskIndex}-${entryIndex}`"
                              class="structured-preview__log-entry"
                            >
                              <div class="structured-preview__log-text">{{ entry }}</div>
                              <div
                                v-if="entryIndex < getWorkLogEntries(task.detail).length - 1"
                                class="structured-preview__log-divider"
                              />
                            </div>
                          </template>
                          <template v-else>暂无内容</template>
                        </div>
                      </ElPopover>
                      <div v-else class="rich-task-line">
                        {{ formatStructuredTaskDisplayLine(task, taskIndex, true) }}
                      </div>
                    </template>
                  </div>
                </div>
              </div>
              <div
                v-else
                class="rich-editor"
                :contenteditable="!isReadonly"
                spellcheck="false"
                :data-field-key="`content-${index}`"
                :data-placeholder="isReadonly ? undefined : '请输入具体工作内容及成果描述'"
                @focus="focusEditField(`content-${index}`)"
                @blur="
                  syncRichContent(item, $event);
                  blurEditField(`content-${index}`);
                "
                v-html="item.contentHtml"
              ></div>
            </div>
            <div class="field">
              <label>工作感悟</label>
              <div
                class="rich-editor"
                :contenteditable="!isReadonly"
                spellcheck="false"
                :data-placeholder="isReadonly ? undefined : '请输入工作感悟'"
                @focus="focusEditField(`reflection-${index}`)"
                @blur="
                  syncRichReflection(item, $event);
                  blurEditField(`reflection-${index}`);
                "
              >
                {{ item.reflection }}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <div class="section">
      <div class="section-title">
        <div class="section-title-left">
          <span>下周期重点工作计划</span>
          <span class="source-chip">{{ nextPlans.length }} 项计划</span>
        </div>
        <div class="section-title-right">
          <ElButton v-if="!isReadonly" size="small" :disabled="planDialogVisible" @click="showInlinePlanForm">
            新增计划
          </ElButton>
        </div>
      </div>
      <div class="plan-list layout-row">
        <div v-if="!nextPlans.length">{{ EMPTY_HTML }}</div>
        <div v-for="(item, index) in nextPlans" :key="index" class="plan-card">
          <div class="plan-index-cell">
            <span class="row-index">{{ index + 1 }}</span>
          </div>
          <div class="plan-name-cell">
            <strong>{{ item.workItem }}</strong>
          </div>
          <div class="plan-action-cell">
            <ElPopconfirm
              v-if="item.removable !== false && !isReadonly"
              title="确认删除这条计划吗？"
              confirm-button-text="删除"
              cancel-button-text="取消"
              width="220"
              @confirm="removePlanItem(index)"
            >
              <template #reference>
                <button class="item-remove-btn" aria-label="删除计划">×</button>
              </template>
            </ElPopconfirm>
          </div>
          <div class="plan-editor-grid">
            <div class="field">
              <label>具体目标</label>
              <div
                v-if="showTargetStructuredView(index)"
                class="rich-editor"
                :class="{ 'rich-editor--preview': !isReadonly }"
                @click="handleStructuredViewClick(`target-${index}`)"
              >
                <div
                  v-for="(section, sectionIndex) in item.targetSections"
                  :key="`${index}-${sectionIndex}`"
                  class="rich-section"
                >
                  <div class="rich-category-line"># {{ resolveTaskItemTypeLabel(section.category) }}</div>
                  <div class="rich-section-tasks">
                    <template v-for="(task, taskIndex) in section.tasks" :key="`${index}-${sectionIndex}-${taskIndex}`">
                      <ElPopover v-if="task.detail" placement="top-start" trigger="hover" :width="420">
                        <template #reference>
                          <div class="rich-task-line rich-task-line--interactive">
                            {{ formatStructuredTaskDisplayLine(task, taskIndex) }}
                          </div>
                        </template>
                        <div class="structured-preview__popover">
                          <template v-if="getWorkLogEntries(task.detail).length">
                            <div
                              v-for="(entry, entryIndex) in getWorkLogEntries(task.detail)"
                              :key="`${index}-${sectionIndex}-${taskIndex}-${entryIndex}`"
                              class="structured-preview__log-entry"
                            >
                              <div class="structured-preview__log-text">{{ entry }}</div>
                              <div
                                v-if="entryIndex < getWorkLogEntries(task.detail).length - 1"
                                class="structured-preview__log-divider"
                              />
                            </div>
                          </template>
                          <template v-else>暂无内容</template>
                        </div>
                      </ElPopover>
                      <div v-else class="rich-task-line">
                        {{ formatStructuredTaskDisplayLine(task, taskIndex) }}
                      </div>
                    </template>
                  </div>
                </div>
              </div>
              <div
                v-else
                class="rich-editor"
                :contenteditable="!isReadonly"
                spellcheck="false"
                :data-field-key="`target-${index}`"
                :data-placeholder="isReadonly ? undefined : '请输入具体目标'"
                @focus="focusEditField(`target-${index}`)"
                @blur="
                  syncRichTarget(item, $event);
                  blurEditField(`target-${index}`);
                "
                v-html="item.targetHtml"
              ></div>
            </div>
            <div class="field">
              <label>对他人协助的需求</label>
              <div
                class="rich-editor"
                :contenteditable="!isReadonly"
                spellcheck="false"
                :data-placeholder="isReadonly ? undefined : '请输入协助需求，没有可留空'"
                @focus="focusEditField(`support-${index}`)"
                @blur="
                  syncRichSupport(item, $event);
                  blurEditField(`support-${index}`);
                "
              >
                {{ item.supportNeed }}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <div v-if="!isReadonly" class="form-actions">
      <!--      <ElButton>重置表单</ElButton>-->
      <ElButton @click="emit('save')">保存草稿</ElButton>
      <ElButton type="primary" @click="emit('submit')">提交审批</ElButton>
    </div>
    <div v-else-if="scene === 'approval'" class="form-actions approval-form-actions">
      <ElButton @click="emit('back')">退出</ElButton>
      <ElButton type="primary" @click="emit('requestApprove')">审批</ElButton>
    </div>

    <BusinessFormDialog
      v-model="planDialogVisible"
      title="新增下周期重点工作计划"
      preset="md"
      confirm-text="确认新增"
      :confirm-disabled="
        !planForm.workItem.trim() || !planForm.sections.some(section => section.category.trim() && section.tasks.length)
      "
      @confirm="submitInlinePlan"
      @cancel="cancelInlinePlan"
    >
      <div class="plan-dialog-form">
        <div class="field">
          <label>工作事项</label>
          <ElSelect
            v-model="planForm.workItem"
            class="inline-plan-name-input"
            filterable
            allow-create
            default-first-option
            clearable
            placeholder="请选择项目名/我的事项"
          >
            <ElOption v-for="item in reviewWorkItemOptions" :key="item" :label="item" :value="item" />
          </ElSelect>
        </div>
        <div class="field">
          <label>具体目标</label>
          <div class="plan-sections">
            <div
              v-for="(section, sIdx) in planForm.sections"
              :key="sIdx"
              class="plan-section"
              :class="{ active: activePlanSectionIndex === sIdx }"
            >
              <div class="plan-section-head">
                <div class="field plan-section-head-field">
                  <label>类别</label>
                  <DictSelect
                    v-model="section.category"
                    :dict-code="RDMS_TASK_ITEM_TYPE_DICT_CODE"
                    size="small"
                    placeholder="选择类别"
                    style="width: 100%"
                    @focus="activePlanSectionIndex = sIdx"
                    @change="activePlanSectionIndex = sIdx"
                  />
                </div>
                <ElButton
                  v-if="planForm.sections.length > 1"
                  link
                  type="danger"
                  size="small"
                  @click="removePlanSection(sIdx)"
                >
                  删除类别
                </ElButton>
              </div>
              <div v-for="(task, tIdx) in section.tasks" :key="tIdx" class="plan-task">
                <div class="plan-task-head">
                  <span>{{ task.title }}</span>
                  <div class="plan-task-metas">
                    <template v-if="!isMyAffairsPlanItem(planForm.workItem)">
                      <em>{{ resolvePriorityLabel(task.priority) }}</em>
                    </template>
                    <em>进度 {{ task.progress }}%</em>
                  </div>
                  <ElButton link type="danger" size="small" @click="removePlanTask(sIdx, tIdx)">删除</ElButton>
                </div>
                <div v-if="task.detail" class="plan-task-detail">{{ task.detail }}</div>
              </div>
              <div class="plan-task-form">
                <div
                  class="inline-task-row"
                  :class="{ 'inline-task-row--my-affairs': isMyAffairsPlanItem(planForm.workItem) }"
                >
                  <div class="field">
                    <label>任务名/事项名</label>
                    <ElInput
                      v-model="section.draft.title"
                      size="small"
                      placeholder="请输入任务名或事项名"
                      @focus="activePlanSectionIndex = sIdx"
                    />
                  </div>
                  <div v-if="!isMyAffairsPlanItem(planForm.workItem)" class="field">
                    <label>优先级</label>
                    <DictSelect
                      v-model="section.draft.priority"
                      :dict-code="RDMS_REQ_PRIORITY_DICT_CODE"
                      :clearable="false"
                      size="small"
                      style="width: 100%"
                      @focus="activePlanSectionIndex = sIdx"
                    />
                  </div>
                  <div class="field">
                    <label>进度</label>
                    <ElInputNumber
                      v-model="section.draft.progress"
                      :min="0"
                      :max="100"
                      :step="5"
                      :precision="1"
                      controls-position="right"
                      size="small"
                      style="width: 100%"
                      @focus="activePlanSectionIndex = sIdx"
                    />
                  </div>
                </div>
                <div class="field">
                  <label>详细内容</label>
                  <ElInput
                    v-model="section.draft.detail"
                    size="small"
                    type="textarea"
                    :rows="3"
                    placeholder="详细内容（可选）"
                    @focus="activePlanSectionIndex = sIdx"
                  />
                </div>
                <ElButton
                  class="dialog-inline-action"
                  type="primary"
                  plain
                  size="small"
                  :disabled="!section.draft.title.trim()"
                  @click="addPlanTask(sIdx)"
                >
                  <ElIcon><Plus /></ElIcon>
                  <span>添加</span>
                </ElButton>
              </div>
            </div>
            <ElButton
              v-if="!planForm.sections.length || planForm.sections.every(s => s.category.trim())"
              class="dialog-inline-action dialog-inline-action--secondary"
              type="primary"
              plain
              size="small"
              @click="addPlanSection"
            >
              <ElIcon><Plus /></ElIcon>
              <span>类别</span>
            </ElButton>
          </div>
        </div>
        <div class="field">
          <label>对他人协助的需求</label>
          <ElInput
            v-model="planForm.supportNeed"
            class="styled-textarea"
            type="textarea"
            :autosize="{ minRows: 3, maxRows: 5 }"
            placeholder="请输入协助需求，没有可留空"
          />
        </div>
      </div>
    </BusinessFormDialog>

    <BusinessFormDialog
      v-model="travelDialogVisible"
      title="新增本周差旅"
      preset="md"
      confirm-text="确认新增"
      :confirm-disabled="!hasCompleteTravelSegment"
      @confirm="confirmTravelReviewItem"
      @cancel="businessTripValue = false"
    >
      <div class="travel-dialog-form">
        <div class="travel-summary">
          <span>出差总天数</span>
          <strong>{{ totalTravelDays }} 天</strong>
        </div>
        <div v-for="(segment, segmentIndex) in travelSegments" :key="segmentIndex" class="travel-segment">
          <div class="travel-segment-head">
            <span class="row-index">{{ segmentIndex + 1 }}</span>
            <strong>分段出差</strong>
            <ElButton v-if="segment.isNew" link type="danger" @click="removeTravelSegment(segmentIndex)">删除</ElButton>
          </div>
          <div class="travel-grid">
            <div class="field">
              <label>工作事项</label>
              <ElSelect
                v-model="segment.workItem"
                filterable
                allow-create
                default-first-option
                clearable
                placeholder="请选择或输入工作事项"
              >
                <ElOption v-for="item in travelWorkItemOptions" :key="item" :label="item" :value="item" />
              </ElSelect>
            </div>
            <div class="field travel-cycle-field">
              <label>出差周期</label>
              <ElDatePicker
                v-model="segment.dateRange"
                type="daterange"
                value-format="YYYY-MM-DD"
                range-separator="-"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                style="width: 100%"
                @change="syncSegmentDays(segmentIndex)"
              />
            </div>
            <div class="field">
              <label>天数</label>
              <ElInputNumber
                v-model="segment.days"
                :min="0.5"
                :step="0.5"
                step-strictly
                :precision="1"
                controls-position="right"
                style="width: 100%"
                @change="value => handleTravelDaysChange(segmentIndex, value)"
              />
            </div>
          </div>
        </div>
        <ElButton
          class="travel-add-btn dialog-inline-action"
          type="primary"
          plain
          size="small"
          @click="addTravelSegment"
        >
          <ElIcon><Plus /></ElIcon>
          <span>分段</span>
        </ElButton>
      </div>
    </BusinessFormDialog>
  </div>
</template>

<style scoped>
.card {
  border: 1px solid rgba(216, 224, 232, 0.88);
  border-radius: 18px;
  background: rgba(255, 255, 255, 0.86);
  box-shadow: 0 18px 45px rgba(15, 23, 42, 0.12);
  padding-top: 20px;
}

.card-title {
  margin: 0;
  font-size: 18px;
  font-weight: 900;
}

.card-subtitle {
  margin-top: 5px;
  color: #667085;
  font-size: 12px;
}

.form-head {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  gap: 18px;
  padding: 20px;
}

.report-title {
  display: flex;
  gap: 12px;
  align-items: center;
}

.type-mark {
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  border-radius: 16px;
  background: var(--el-color-primary-light-8);
  color: var(--el-color-primary);
  font-weight: 900;
}

.section {
  margin: 0 20px 18px;
  border: 1px solid #d8e0e8;
  border-radius: 16px;
  overflow: hidden;
  background: #fff;
}

.section-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 13px 16px;
  background: #f8fbfc;
  border-bottom: 1px solid #d8e0e8;
  font-weight: 900;
}

.section-title-left {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.section-title-right {
  display: flex;
  align-items: center;
  gap: 10px;
}

.source-chip {
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 10px;
  border-radius: 999px;
  background: #f3f7f9;
  color: #475467;
  font-size: 12px;
  font-weight: 800;
}

.compose-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 14px;
  padding: 16px;
  align-items: start;
}

@media (max-width: 1180px) {
  .compose-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (max-width: 768px) {
  .compose-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

.field {
  display: grid;
  gap: 6px;
}

.field label {
  color: #667085;
  font-size: 12px;
  font-weight: 800;
}

.radio-group-full {
  width: 100%;
  display: flex;
  gap: 12px;
}

.radio-group-full :deep(.el-radio) {
  flex: 1;
  min-width: 0;
  justify-content: center;
  margin-right: 0;
}

.radio-group-full :deep(.el-radio.is-checked) {
  border-color: var(--el-color-primary);
  background: var(--el-color-primary-light-9);
}

.radio-group-full :deep(.el-radio__input.is-checked + .el-radio__label) {
  color: var(--el-color-primary);
}

.review-grid,
.plan-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  padding: 16px;
}

.review-grid.layout-row,
.plan-list.layout-row {
  grid-template-columns: 1fr;
}

.review-card,
.plan-card {
  display: grid;
  gap: 12px;
  padding: 14px;
  border: 1px solid #e5edf1;
  border-radius: 12px;
  background: #fbfdfe;
}

.review-card {
  grid-template-columns: 88px minmax(225px, 330px) minmax(0, 1.35fr) minmax(0, 1fr) 44px;
  gap: 0;
  padding: 0;
  overflow: hidden;
}

.plan-card {
  grid-template-columns: 88px minmax(225px, 330px) minmax(0, 1.35fr) minmax(0, 1fr) 44px;
  gap: 0;
  padding: 0;
  overflow: hidden;
}

.review-index-cell,
.review-name-cell,
.review-editor-grid,
.review-action-cell,
.plan-index-cell,
.plan-name-cell,
.plan-editor-grid,
.plan-action-cell {
  padding: 14px;
}

.review-index-cell,
.plan-index-cell {
  display: grid;
  place-items: center;
}

.review-name-cell,
.plan-name-cell {
  display: grid;
  align-items: center;
  min-width: 0;
  overflow: hidden;
  border-left: 1px solid #e5edf1;
}

.review-name-cell .work-title-line,
.plan-name-cell {
  justify-content: center;
  text-align: center;
}

.review-name-cell .work-title-line {
  min-width: 0;
  flex-direction: column;
  gap: 4px;
}

.review-name-cell strong,
.plan-name-cell strong {
  display: block;
  width: 100%;
  min-width: 0;
  flex: 0 1 auto;
  color: #606266;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.3;
  overflow: hidden;
  text-overflow: clip;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.review-action-cell,
.plan-action-cell {
  grid-column: 5;
  grid-row: 1;
  display: grid;
  justify-items: center;
  align-items: start;
  border-left: 1px solid #e5edf1;
  padding: 6px;
}

.review-card-head,
.plan-card-head {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) 28px;
  gap: 10px;
  align-items: start;
}

.row-index {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: var(--el-color-primary);
  color: #fff;
  font-size: 13px;
  font-weight: 900;
}

.review-title-fields,
.review-title-readonly,
.plan-title-readonly {
  display: grid;
  gap: 10px;
  align-items: center;
}

.review-title-readonly {
  grid-template-columns: 1fr;
}

.work-title-line {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.review-title-readonly strong,
.plan-title-readonly strong {
  color: #606266;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.3;
}

.work-title-line span,
.inline-metrics {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #667085;
  font-size: 12px;
  font-weight: 800;
}

.review-name-cell .work-title-line span {
  justify-content: center;
  width: 100%;
}

.inline-metrics :deep(.el-input-number) {
  width: 92px;
}

.review-editor-grid {
  display: grid;
  grid-column: 3 / span 2;
  grid-row: 1;
  grid-template-columns: minmax(0, calc(57.45% - 6px + 50px)) minmax(0, calc(42.55% - 6px - 50px));
  gap: 12px;
  border-left: 1px solid #e5edf1;
}

.plan-editor-grid {
  display: grid;
  grid-template-columns: minmax(0, calc(57.45% - 6px + 50px)) minmax(0, calc(42.55% - 6px - 50px));
  gap: 12px;
}

.plan-editor-grid {
  grid-column: 3 / span 2;
  grid-row: 1;
  border-left: 1px solid #e5edf1;
}

.item-remove-btn {
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 1px solid #fecaca;
  border-radius: 999px;
  background: #fff;
  color: #dc2626;
  font: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

.item-remove-btn:hover {
  background: #fef2f2;
}

.structured-input,
.fixed-textarea :deep(.el-textarea__inner),
.styled-textarea :deep(.el-textarea__inner) {
  scrollbar-width: thin;
  scrollbar-color: #94a3b8 transparent;
}

.structured-input::-webkit-scrollbar,
.fixed-textarea :deep(.el-textarea__inner::-webkit-scrollbar),
.styled-textarea :deep(.el-textarea__inner::-webkit-scrollbar) {
  width: 6px;
  height: 6px;
}

.structured-input::-webkit-scrollbar-track,
.fixed-textarea :deep(.el-textarea__inner::-webkit-scrollbar-track),
.styled-textarea :deep(.el-textarea__inner::-webkit-scrollbar-track) {
  background: transparent;
}

.structured-input::-webkit-scrollbar-thumb,
.fixed-textarea :deep(.el-textarea__inner::-webkit-scrollbar-thumb),
.styled-textarea :deep(.el-textarea__inner::-webkit-scrollbar-thumb) {
  border-radius: 999px;
  background: #94a3b8;
}

.structured-input::-webkit-scrollbar-button,
.fixed-textarea :deep(.el-textarea__inner::-webkit-scrollbar-button),
.styled-textarea :deep(.el-textarea__inner::-webkit-scrollbar-button) {
  width: 0;
  height: 0;
  display: none;
}

.structured-input {
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  background: #fff;
  padding: 5px 11px;
  cursor: pointer;
  transition: border-color 0.2s;
  height: 86px;
  overflow: auto;
  box-sizing: border-box;
}

.structured-input:hover {
  border-color: #c0c4cc;
}

.structured-input.disabled {
  background: #f5f7fa;
  color: #a8abb2;
  cursor: not-allowed;
}

.fixed-textarea :deep(.el-textarea__inner) {
  height: 86px;
  min-height: 86px;
  max-height: 86px;
  resize: none;
  overflow: auto;
  padding: 5px 11px;
  line-height: 1.6;
  white-space: pre-wrap;
}

.styled-textarea :deep(.el-textarea__inner) {
  overflow: auto;
}

.auto-textarea,
.fixed-textarea {
  width: 100%;
}

.auto-textarea :deep(.el-textarea__inner) {
  resize: none;
  overflow: hidden;
  padding: 5px 11px;
  line-height: 1.6;
  white-space: pre-wrap;
}

.rich-editor {
  width: 100%;
  min-height: 86px;
  padding: 5px 11px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  background: #fff;
  box-sizing: border-box;
  color: #334155;
  font-size: 13px;
  line-height: 1.6;
  outline: none;
  overflow: auto;
  transition:
    border-color 0.2s,
    box-shadow 0.2s;
}

.rich-editor:hover {
  border-color: #c0c4cc;
}

.rich-editor:focus {
  border-color: var(--el-color-primary);
  box-shadow: 0 0 0 2px var(--el-color-primary-light-8);
}

.rich-editor:empty::before {
  content: attr(data-placeholder);
  color: #a8abb2;
}

.rich-editor :deep(.rich-section) {
  display: grid;
  gap: 4px;
  min-width: 0;
}

.rich-editor :deep(.rich-section + .rich-section) {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed #cbd5e1;
}

.rich-editor :deep(.rich-category-line) {
  color: var(--el-color-primary);
  font-size: 13px;
  font-weight: 700;
  line-height: 1.6;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.rich-editor :deep(.rich-section-tasks) {
  display: grid;
  gap: 6px;
  padding-left: 0;
  color: #334155;
  font-size: 13px;
  line-height: 1.6;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.rich-editor :deep(.rich-task-line) {
  color: #334155;
  font-size: 13px;
  line-height: 1.6;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.rich-editor :deep(.rich-task-line--interactive) {
  cursor: pointer;
}

/* 编辑态下结构化预览区域：点击可切换到编辑模式 */
.rich-editor--preview {
  cursor: text;
}

.rich-editor--preview:hover {
  border-color: var(--el-color-primary);
}

.structured-preview__popover {
  max-width: 100%;
  color: #334155;
  font-size: 13px;
  line-height: 1.6;
  display: grid;
  gap: 0;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.structured-preview__log-entry {
  display: grid;
  gap: 10px;
}

.structured-preview__log-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.structured-preview__log-divider {
  width: 100%;
  height: 1px;
  background: #e2e8f0;
}

.rich-editor :deep(.rich-task) {
  display: grid;
  gap: 4px;
  min-width: 0;
}

.rich-editor :deep(.rich-task-head) {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  min-width: 0;
  flex-wrap: wrap;
}

.rich-editor :deep(.rich-task + .rich-task) {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed #cbd5e1;
}

.rich-editor :deep(.rich-task-title),
.rich-editor :deep(.rich-section-title) {
  position: relative;
  min-width: 0;
  flex: 1;
  padding-left: 14px;
  color: var(--el-color-primary);
  font-size: 13px;
  font-weight: 800;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.rich-editor :deep(.rich-task-metas) {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 5px;
  flex-wrap: wrap;
  flex: 0 0 auto;
  user-select: none;
  cursor: default;
}

.rich-editor :deep(.rich-task-meta) {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: #f3f7f9;
  color: #475467;
  font-size: 11px;
  font-weight: 800;
  white-space: nowrap;
}

.rich-editor :deep(.rich-task-meta.priority) {
  background: #fff7ed;
  color: #c2410c;
}

.rich-editor :deep(.rich-task-title::before),
.rich-editor :deep(.rich-section-title::before) {
  content: '';
  position: absolute;
  left: 0;
  top: 4px;
  width: 4px;
  height: 16px;
  border-radius: 999px;
  background: var(--el-color-primary);
}

.rich-editor :deep(.rich-task-detail) {
  padding-left: 14px;
  color: #334155;
  font-size: 13px;
  line-height: 1.6;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.review-editor-grid .field,
.plan-editor-grid .field {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.review-editor-grid .rich-editor,
.plan-editor-grid .rich-editor {
  flex: 1;
  height: 100%;
}

.review-editor-grid .fixed-textarea,
.plan-editor-grid .fixed-textarea {
  display: flex;
  flex: 1;
}

.review-editor-grid .fixed-textarea :deep(.el-textarea__inner),
.plan-editor-grid .fixed-textarea :deep(.el-textarea__inner) {
  height: 100%;
  max-height: none;
}

.structured-input-inner {
  color: #334155;
  line-height: 1.6;
  font-size: 13px;
}

.structured-input-inner.plain {
  white-space: pre-wrap;
}

.structured-task {
  display: grid;
  gap: 6px;
}

.structured-task + .structured-task {
  padding-top: 10px;
  border-top: 1px dashed #cbd5e1;
}

.structured-task-title {
  position: relative;
  padding-left: 14px;
  color: var(--el-color-primary);
  font-size: 13px;
  font-weight: 800;
}

.structured-task-title::before {
  content: '';
  position: absolute;
  left: 0;
  top: 4px;
  width: 4px;
  height: 16px;
  border-radius: 999px;
  background: var(--el-color-primary);
}

.structured-task-detail {
  color: #334155;
  padding-left: 14px;
  font-size: 13px;
  line-height: 1.6;
  white-space: pre-wrap;
}

.travel-summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--el-color-primary-light-9);
  color: var(--el-color-primary);
  font-size: 13px;
  font-weight: 900;
}

.plan-dialog-form,
.travel-dialog-form {
  display: grid;
  gap: 16px;
}

.plan-dialog-form > .field,
.travel-dialog-form > .field {
  gap: 8px;
}

.travel-segment {
  display: grid;
  gap: 14px;
  padding: 14px;
  border: 1px solid #e5edf1;
  border-radius: 12px;
  background: #fbfdfe;
}

.travel-segment-head {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
}

.travel-segment-head strong {
  color: #14213d;
}

.travel-grid {
  display: grid;
  grid-template-columns: minmax(200px, 1fr) minmax(280px, 1.2fr) 120px;
  gap: 16px;
  align-items: start;
}

.inline-task-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 132px 142px;
  align-items: end;
  gap: 12px;
}

.inline-task-row--my-affairs {
  grid-template-columns: minmax(0, 1fr) 142px;
}

.plan-sections {
  display: grid;
  gap: 10px;
}

.plan-section {
  display: grid;
  gap: 10px;
  padding: 12px;
  border: 1px solid #e5edf1;
  border-radius: 12px;
  background: #fafcfd;
  box-shadow: none;
}

.plan-section.active {
  border-color: var(--el-color-primary-light-7);
  background: var(--el-color-primary-light-9);
  box-shadow: inset 0 0 0 1px var(--el-color-primary-light-9);
}

.plan-section-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  align-items: end;
}

.plan-section-head :deep(.el-select__wrapper) {
  height: 36px;
  min-height: 36px;
  border-radius: 9px;
}

.plan-task {
  display: grid;
  gap: 4px;
  padding: 9px 12px;
  border: 1px solid #eef2f6;
  border-radius: 9px;
  background: #f8fbfc;
}

.plan-task-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.plan-task-head span {
  flex: 1;
  min-width: 0;
  color: #14213d;
  font-size: 13px;
  font-weight: 700;
}

.plan-task-metas {
  display: inline-flex;
  gap: 5px;
  flex: 0 0 auto;
}

.plan-task-metas em {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: #f3f7f9;
  color: #475467;
  font-size: 11px;
  font-style: normal;
  font-weight: 800;
  white-space: nowrap;
}

.plan-task-metas em:first-child {
  background: #fff7ed;
  color: #c2410c;
}

.plan-task-detail {
  color: #334155;
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
}

.plan-task-form {
  display: grid;
  gap: 12px;
  padding: 12px;
  border: 1px dashed #cbd5e1;
  border-radius: 10px;
  background: #f8fbfc;
}

.plan-dialog-form .inline-task-row .field {
  gap: 6px;
}

.plan-dialog-form .inline-task-row :deep(.el-input__wrapper),
.plan-dialog-form .inline-task-row :deep(.el-select__wrapper),
.plan-dialog-form .inline-task-row :deep(.el-input-number),
.plan-dialog-form .inline-task-row :deep(.el-input-number .el-input__wrapper) {
  height: 36px !important;
  min-height: 36px !important;
  overflow: hidden;
}

.plan-dialog-form .inline-task-row :deep(.el-input-number) {
  width: 100%;
  border-radius: 8px;
  box-sizing: border-box;
  background: #fff;
  border: 1px solid var(--el-border-color);
  overflow: hidden;
}

.plan-dialog-form .inline-task-row :deep(.el-input-number:focus-within) {
  border-color: var(--el-color-primary);
}

.plan-dialog-form .inline-task-row :deep(.el-input-number .el-input__wrapper) {
  box-shadow: none !important;
  background: transparent;
  border-radius: 0;
}

.plan-dialog-form .inline-task-row :deep(.el-input-number.is-controls-right .el-input-number__increase),
.plan-dialog-form .inline-task-row :deep(.el-input-number.is-controls-right .el-input-number__decrease) {
  right: 0;
  height: 18px;
}

.plan-dialog-form .inline-task-row :deep(.el-input-number.is-controls-right .el-input-number__increase) {
  top: 0;
}

.plan-dialog-form .inline-task-row :deep(.el-input-number.is-controls-right .el-input-number__decrease) {
  bottom: 0;
}

.travel-dialog-form :deep(.el-input__wrapper),
.travel-dialog-form :deep(.el-select__wrapper),
.travel-dialog-form :deep(.el-input-number),
.travel-dialog-form :deep(.el-input-number .el-input__wrapper),
.travel-dialog-form :deep(.el-date-editor.el-input__wrapper) {
  min-height: 36px;
  overflow: hidden;
}

.travel-dialog-form :deep(.el-input-number) {
  width: 100%;
  border-radius: 8px;
  box-sizing: border-box;
  background: #fff;
  border: 1px solid var(--el-border-color);
  overflow: hidden;
}

.travel-dialog-form :deep(.el-input-number:focus-within) {
  border-color: var(--el-color-primary);
}

.travel-dialog-form :deep(.el-input-number .el-input__wrapper) {
  box-shadow: none !important;
  background: transparent;
  border-radius: 0;
}

.travel-dialog-form :deep(.el-input-number.is-controls-right .el-input-number__increase),
.travel-dialog-form :deep(.el-input-number.is-controls-right .el-input-number__decrease) {
  right: 0;
  height: 18px;
}

.travel-dialog-form :deep(.el-input-number.is-controls-right .el-input-number__increase) {
  top: 0;
}

.travel-dialog-form :deep(.el-input-number.is-controls-right .el-input-number__decrease) {
  bottom: 0;
}

.inline-plan-name-input {
  width: 100%;
  min-width: 260px;
}

.travel-add-btn {
  justify-self: flex-end;
}

.dialog-inline-action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding-inline: 12px;
  border-radius: 999px;
}

.dialog-inline-action :deep(.el-icon) {
  font-size: 13px;
}

.plan-task-form > .dialog-inline-action {
  justify-self: flex-end;
}

.form-actions {
  position: sticky;
  z-index: 5;
  bottom: 0;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: auto;
  margin-bottom: 0;
  padding: 14px 20px;
  border-top: 1px solid #d8e0e8;
  border-bottom-left-radius: 18px;
  border-bottom-right-radius: 18px;
  background: #f5f7fa;
  box-shadow: 0 -8px 18px rgba(15, 23, 42, 0.06);
}

.approval-form-actions {
  position: sticky;
  z-index: 5;
  bottom: 0;
  margin-top: auto;
  margin-bottom: 0;
  border-bottom-left-radius: 18px;
  border-bottom-right-radius: 18px;
  background: #f5f7fa;
  box-shadow: 0 -8px 18px rgba(15, 23, 42, 0.06);
}

@media (max-width: 1180px) {
  .form-head,
  .compose-grid,
  .review-title-fields,
  .review-title-readonly,
  .plan-title-readonly,
  .review-editor-grid,
  .plan-editor-grid,
  .inline-task-row,
  .travel-grid {
    grid-template-columns: 1fr;
  }

  .review-card,
  .plan-card {
    grid-template-columns: 1fr;
  }

  .review-index-cell,
  .review-name-cell,
  .review-editor-grid,
  .review-action-cell,
  .plan-index-cell,
  .plan-name-cell,
  .plan-editor-grid,
  .plan-action-cell,
  .inline-plan-actions {
    grid-column: auto;
    grid-row: auto;
    border-left: 0;
  }

  .review-name-cell,
  .review-editor-grid,
  .review-action-cell,
  .plan-name-cell,
  .plan-editor-grid,
  .plan-action-cell {
    border-top: 1px solid #e5edf1;
  }

  .section-title {
    align-items: flex-start;
  }
}
</style>
