'use client';

/**
 * 템플릿 기반 중간검사 성적서 콘텐츠
 *
 * mng 미리보기(buildDocumentPreviewHtml) 레이아웃 기준:
 * - 헤더: KD + 회사명(좌) | 제목(중앙) | 결재라인(우)
 * - 기본필드: 2열 배치 (15:35:15:35)
 * - 이미지 섹션: items 없는 섹션 → 이미지 표시
 * - DATA 테이블: template.columns 기반 헤더, work items 행
 * - 푸터: 비고(좌) + 종합판정(우) 병렬 배치
 *
 * 컬럼 column_type별 셀 렌더링:
 * - text (일련번호/NO): 행 번호
 * - check: 양호/불량 토글
 * - complex (sub_labels): 기준값 표시 + 측정값 입력 / OK·NG 토글
 * - select (판정): 자동 계산 적/부
 */

import React, { useState, forwardRef, useImperativeHandle, useEffect, useMemo } from 'react';
import type { WorkOrder } from '../types';
import type { WorkItemData } from '@/components/production/WorkerScreen/types';
import type { InspectionDataMap } from './InspectionReportModal';
import type {
  InspectionTemplateFormat,
  InspectionTemplateSectionItem,
} from '@/components/production/WorkerScreen/types';
import {
  type InspectionContentRef,
  InspectionCheckbox,
  JudgmentCell,
  calculateOverallResult,
  getFullDate,
  getOrderInfo,
  INPUT_CLASS,
} from './inspection-shared';
import { formatNumber } from '@/lib/utils/amount';
import type { BendingInfoExtended } from './bending/types';
import { getInspectionConfig } from '../actions';
import type { InspectionConfigData } from '../actions';

export type { InspectionContentRef };

// ===== 셀 값 타입 =====
interface CellValue {
  status?: 'good' | 'bad' | null;
  measurements?: [string, string, string];
  value?: string;
  text?: string;
}

// ===== Props =====
interface TemplateInspectionContentProps {
  data: WorkOrder;
  template: InspectionTemplateFormat;
  readOnly?: boolean;
  workItems?: WorkItemData[];
  inspectionDataMap?: InspectionDataMap;
  /** 기존 document_data EAV 레코드 (문서 로딩 시 복원용) */
  documentRecords?: Array<{
    section_id: number | null;
    column_id: number | null;
    row_index: number;
    field_key: string;
    field_value: string | null;
  }>;
}

// ===== 유틸 =====

/** API 저장소 이미지 URL 생성 — R2 전환 후 프록시 사용 */
function getImageUrl(path: string | null | undefined, fileId?: number | null): string {
  if (!path && !fileId) return '';
  // file_id가 있으면 프록시 경로 사용
  if (fileId) return `/api/proxy/files/${fileId}/view`;
  if (!path) return '';
  if (path.startsWith('http://') || path.startsWith('https://')) return path;
  if (path.startsWith('/api/proxy/')) return path;
  // R2 전환 후 /storage/ 직접 접근 불가 — 경로만 반환 (fallback)
  return path;
}

/** field_values.reference_attribute에서 작업 아이템의 실제 치수를 resolve */
function resolveReferenceValue(
  item: InspectionTemplateSectionItem,
  workItem?: WorkItemData
): number | null {
  if (!item.field_values || !workItem) return null;
  const refAttr = item.field_values.reference_attribute;
  if (typeof refAttr !== 'string') return null;
  const mapping: Record<string, number | undefined> = {
    width: workItem.width,
    height: workItem.height,
    length: workItem.width,
  };
  return mapping[refAttr] ?? null;
}

function formatStandard(item: InspectionTemplateSectionItem, workItem?: WorkItemData): string {
  const refVal = resolveReferenceValue(item, workItem);
  if (refVal !== null) return formatNumber(refVal);
  const sc = item.standard_criteria;
  if (!sc) return item.standard || '-';
  if (typeof sc === 'object') {
    if ('nominal' in sc) return String(sc.nominal);
    if ('min' in sc && 'max' in sc) return `${sc.min} ~ ${sc.max}`;
    if ('max' in sc) return `≤ ${sc.max}`;
    if ('min' in sc) return `≥ ${sc.min}`;
  }
  return String(sc);
}

function getNominalValue(item: InspectionTemplateSectionItem, workItem?: WorkItemData): number | null {
  const refVal = resolveReferenceValue(item, workItem);
  if (refVal !== null) return refVal;
  const sc = item.standard_criteria;
  if (!sc || typeof sc !== 'object') {
    if (typeof sc === 'string') {
      const v = parseFloat(sc);
      return isNaN(v) ? null : v;
    }
    return null;
  }
  if ('nominal' in sc) return sc.nominal;
  return null;
}

/** 측정값이 공차 범위 내인지 판정 */
function isWithinTolerance(measured: number, item: InspectionTemplateSectionItem, workItem?: WorkItemData): boolean {
  const nominal = getNominalValue(item, workItem);
  const tol = item.tolerance;
  if (nominal === null || !tol) return true;

  switch (tol.type) {
    case 'symmetric':
      return Math.abs(measured - nominal) <= (tol.value ?? 0);
    case 'asymmetric':
      return measured >= nominal - (tol.minus ?? 0) && measured <= nominal + (tol.plus ?? 0);
    case 'range':
      return measured >= (tol.min ?? -Infinity) && measured <= (tol.max ?? Infinity);
    default:
      return true;
  }
}

/** 컬럼 라벨에서 번호 기호와 공백을 제거하여 비교용 키 생성 */
function normalizeLabel(label: string): string {
  return label.replace(/[①②③④⑤⑥⑦⑧⑨⑩\s]/g, '').trim();
}

function isSerialColumn(label: string): boolean {
  const l = label.trim().toLowerCase();
  return l === 'no' || l === 'no.' || l === '일련번호';
}

function isJudgmentColumn(label: string): boolean {
  return label.includes('판정');
}

// ===== Bending 검사 DATA 지원 (레거시 PHP 동기화) =====

interface BendingGapPoint {
  point: string;
  designValue: string;
}

interface BendingProduct {
  id: string;
  category: string;
  productName: string;
  productType: string;
  lengthDesign: string;
  widthDesign: string;
  gapPoints: BendingGapPoint[];
}

interface BendingExpandedRow {
  productIdx: number;
  product: BendingProduct;
  pointIdx: number;
  gapPoint: BendingGapPoint;
  isFirstRow: boolean;
  totalPoints: number;
}

// 절곡 제품별 기본 간격 POINT (단면 치수 - 제품 사양에 따른 고정값)
const DEFAULT_GAP_PROFILES: Record<string, BendingGapPoint[]> = {
  guideRailWall: [
    { point: '(1)', designValue: '30' },
    { point: '(2)', designValue: '78' },
    { point: '(3)', designValue: '25' },
    { point: '(4)', designValue: '45' },
  ],
  guideRailSide: [
    { point: '(1)', designValue: '28' },
    { point: '(2)', designValue: '75' },
    { point: '(3)', designValue: '42' },
    { point: '(4)', designValue: '38' },
    { point: '(5)', designValue: '32' },
  ],
  bottomBar: [{ point: '(1)', designValue: '60' }],
  caseBox: [
    { point: '(1)', designValue: '550' },
    { point: '(2)', designValue: '50' },
    { point: '(3)', designValue: '385' },
    { point: '(4)', designValue: '50' },
    { point: '(5)', designValue: '410' },
  ],
  smokeW50: [
    { point: '(1)', designValue: '50' },
    { point: '(2)', designValue: '12' },
  ],
  smokeW80: [
    { point: '(1)', designValue: '80' },
    { point: '(2)', designValue: '12' },
  ],
};

/** bending_info에서 제품 목록 생성, 없으면 기본값 */
function buildBendingProducts(order: WorkOrder): BendingProduct[] {
  const bi = order.bendingInfo as BendingInfoExtended | undefined;
  const productCode = bi?.productCode || 'KQTS01';
  const products: BendingProduct[] = [];

  // 가이드레일 벽면형
  if (!bi || bi.guideRail?.wall) {
    const len = bi?.guideRail?.wall?.lengthData?.[0]?.length || 3500;
    products.push({
      id: 'guide-wall', category: productCode, productName: '가이드레일',
      productType: '벽면형', lengthDesign: String(len), widthDesign: 'N/A',
      gapPoints: DEFAULT_GAP_PROFILES.guideRailWall,
    });
  }

  // 가이드레일 측면형
  if (bi?.guideRail?.side) {
    const len = bi.guideRail.side.lengthData?.[0]?.length || 3000;
    products.push({
      id: 'guide-side', category: productCode, productName: '가이드레일',
      productType: '측면형', lengthDesign: String(len), widthDesign: 'N/A',
      gapPoints: DEFAULT_GAP_PROFILES.guideRailSide,
    });
  }

  // 하단마감재
  products.push({
    id: 'bottom-bar', category: productCode, productName: '하단마감재',
    productType: '60×40', lengthDesign: '3000', widthDesign: 'N/A',
    gapPoints: DEFAULT_GAP_PROFILES.bottomBar,
  });

  // 케이스 (shutterBox)
  if (bi?.shutterBox?.length) {
    bi.shutterBox.forEach((box, boxIdx) => {
      (box.lengthData || []).forEach((ld, li) => {
        products.push({
          id: `case-${boxIdx}-${li}`, category: productCode, productName: '케이스',
          productType: `${box.size || '양면'}\n${box.direction || ''}`.trim(),
          lengthDesign: String(ld.length), widthDesign: 'N/A',
          gapPoints: DEFAULT_GAP_PROFILES.caseBox,
        });
      });
    });
  } else {
    products.push({
      id: 'case-0', category: productCode, productName: '케이스',
      productType: '양면', lengthDesign: '3000', widthDesign: 'N/A',
      gapPoints: DEFAULT_GAP_PROFILES.caseBox,
    });
  }

  // 연기차단재
  products.push({
    id: 'smoke-w50', category: productCode, productName: '연기차단재',
    productType: '화이바 W50\n가이드레일용', lengthDesign: '-', widthDesign: '50',
    gapPoints: DEFAULT_GAP_PROFILES.smokeW50,
  });
  products.push({
    id: 'smoke-w80', category: productCode, productName: '연기차단재',
    productType: '화이바 W80\n케이스용', lengthDesign: '-', widthDesign: '80',
    gapPoints: DEFAULT_GAP_PROFILES.smokeW80,
  });

  return products;
}

// ===== 이미지 섹션 컴포넌트 (onError 핸들링) =====
function SectionImage({ section }: { section: { id: number; title?: string; name?: string; image_path?: string | null } }) {
  const [imgError, setImgError] = useState(false);
  const title = section.title || section.name || '';
  const url = section.image_path ? getImageUrl(section.image_path) : '';

  return (
    <div className="mb-3">
      <p className="text-sm font-bold mb-1">■ {title}</p>
      {url && !imgError ? (
        <img
          src={url}
          alt={title}
          className="w-full border rounded"
          onError={() => setImgError(true)}
        />
      ) : (
        <div className="border border-dashed border-gray-300 rounded p-6 text-center text-gray-400 text-xs">
          이미지 미등록
        </div>
      )}
    </div>
  );
}

// ===== 컴포넌트 =====

export const TemplateInspectionContent = forwardRef<InspectionContentRef, TemplateInspectionContentProps>(
  function TemplateInspectionContent({ data: order, template, readOnly = false, workItems, inspectionDataMap, documentRecords }, ref) {
    const fullDate = getFullDate();
    const { primaryAssignee } = getOrderInfo(order);

    // 섹션 분류: 이미지 섹션(items 없음) vs 데이터 섹션(items 있음)
    const imageSections = useMemo(
      () => template.sections.filter(s => s.items.length === 0),
      [template.sections]
    );
    const dataSections = useMemo(
      () => template.sections.filter(s => s.items.length > 0),
      [template.sections]
    );

    // 모든 데이터 섹션의 아이템을 평탄화
    const allItems = useMemo(
      () => dataSections.flatMap(s => s.items),
      [dataSections]
    );

    // sectionItem.id → section.id 역매핑
    const itemSectionMap = useMemo(() => {
      const map = new Map<number, number>();
      for (const s of dataSections) {
        for (const item of s.items) {
          map.set(item.id, s.id);
        }
      }
      return map;
    }, [dataSections]);

    // 컬럼 → 섹션 아이템 매핑 (라벨 정규화 비교)
    const columnItemMap = useMemo(() => {
      const map = new Map<number, InspectionTemplateSectionItem>();
      for (const col of template.columns) {
        const colKey = normalizeLabel(col.label);
        const matched = allItems.find(item => {
          const itemKey = normalizeLabel(item.item || item.category || '');
          return itemKey === colKey;
        });
        if (matched) map.set(col.id, matched);
      }
      return map;
    }, [template.columns, allItems]);

    // 셀 값 상태: key = `${rowIdx}-${colId}`
    const [cellValues, setCellValues] = useState<Record<string, CellValue>>({});
    const [inadequateContent, setInadequateContent] = useState('');

    const effectiveWorkItems = workItems || [];

    // ===== Bending template detection & expanded rows =====
    const isBending = useMemo(() => {
      if (order.processType === 'bending') return true;
      return template.columns.some(col =>
        col.sub_labels?.some(sl => sl.toLowerCase().includes('point'))
      );
    }, [order.processType, template.columns]);

    const gapColumnId = useMemo(() => {
      if (!isBending) return null;
      return template.columns.find(col =>
        col.sub_labels?.some(sl => sl.toLowerCase().includes('point'))
      )?.id ?? null;
    }, [isBending, template.columns]);

    // ===== inspection-config API 연동 =====
    const [inspectionConfig, setInspectionConfig] = useState<InspectionConfigData | null>(null);

    useEffect(() => {
      if (!isBending || !order.id) return;
      let cancelled = false;
      getInspectionConfig(order.id).then(result => {
        if (!cancelled && result.success && result.data) {
          setInspectionConfig(result.data);
        }
      });
      return () => { cancelled = true; };
    }, [isBending, order.id]);

    const bendingProducts = useMemo(() => {
      if (!isBending) return [];

      // API 응답이 있고 items가 있으면 API 기반 구성품 사용
      if (inspectionConfig?.items?.length) {
        const productCode = inspectionConfig.product_code || '';
        // bending_info에서 dimension 보조 데이터 추출
        const bi = order.bendingInfo as BendingInfoExtended | undefined;
        const wallLen = bi?.guideRail?.wall?.lengthData?.[0]?.length;
        const sideLen = bi?.guideRail?.side?.lengthData?.[0]?.length;

        return inspectionConfig.items.map((item): BendingProduct => {
          // API id → 표시용 매핑 (이름, 타입, 치수)
          const displayMap: Record<string, { name: string; type: string; len: string; wid: string }> = {
            guide_rail_wall: { name: '가이드레일', type: '벽면형', len: String(wallLen || 3500), wid: 'N/A' },
            guide_rail_side: { name: '가이드레일', type: '측면형', len: String(sideLen || 3000), wid: 'N/A' },
            bottom_bar: { name: '하단마감재', type: '60×40', len: '3000', wid: 'N/A' },
            case_box: { name: '케이스', type: '양면', len: '3000', wid: 'N/A' },
            smoke_w50: { name: '연기차단재', type: '화이바 W50\n가이드레일용', len: '-', wid: '50' },
            smoke_w80: { name: '연기차단재', type: '화이바 W80\n케이스용', len: '-', wid: '80' },
          };
          const d = displayMap[item.id] || { name: item.name, type: '', len: '-', wid: 'N/A' };
          return {
            id: item.id,
            category: productCode,
            productName: d.name,
            productType: d.type,
            lengthDesign: d.len,
            widthDesign: d.wid,
            gapPoints: item.gap_points.map(gp => ({
              point: gp.point,
              designValue: gp.design_value,
            })),
          };
        });
      }

      // fallback: 기존 프론트 로직 사용
      return buildBendingProducts(order);
    }, [isBending, order, inspectionConfig]);

    const bendingExpandedRows = useMemo(() => {
      if (!isBending) return [];
      const rows: BendingExpandedRow[] = [];
      bendingProducts.forEach((product, productIdx) => {
        const total = product.gapPoints.length;
        product.gapPoints.forEach((gp, pointIdx) => {
          rows.push({
            productIdx, product, pointIdx, gapPoint: gp,
            isFirstRow: pointIdx === 0, totalPoints: total,
          });
        });
      });
      return rows;
    }, [isBending, bendingProducts]);

    // inspectionDataMap에서 초기값 복원
    // InspectionInputModal 저장 키: section_{sectionId}_item_{itemId} / 값: "ok"|"ng"|number
    // TemplateInspectionContent 내부 키: {rowIdx}-{colId} / 값: CellValue
    useEffect(() => {
      if (!inspectionDataMap || !workItems) return;
      const initial: Record<string, CellValue> = {};
      workItems.forEach((wi, rowIdx) => {
        const itemData = inspectionDataMap.get(wi.id);
        if (!itemData?.templateValues) return;
        for (const col of template.columns) {
          const sectionItem = columnItemMap.get(col.id);
          if (!sectionItem) continue;
          const cellKey = `${rowIdx}-${col.id}`;
          const sectionId = itemSectionMap.get(sectionItem.id);

          // InspectionInputModal 키 형식으로 조회
          const inputKey = sectionId != null
            ? `section_${sectionId}_item_${sectionItem.id}`
            : null;
          // 레거시 키 형식 폴백
          const legacyKey = `item_${sectionItem.id}`;

          const rawVal = (inputKey ? itemData.templateValues?.[inputKey] : undefined)
            ?? itemData.templateValues?.[legacyKey];

          if (rawVal == null) continue;

          // 값 형식 변환: InputModal 형식 → CellValue 형식
          // complex 컬럼(측정값)은 measurements 배열, check 컬럼은 status, 나머지는 value
          const isComplex = col.column_type === 'complex' && col.sub_labels && col.sub_labels.length > 0;

          if (typeof rawVal === 'object') {
            // 이미 CellValue 객체
            initial[cellKey] = rawVal as CellValue;
          } else if (rawVal === 'ok') {
            initial[cellKey] = { status: 'good' };
          } else if (rawVal === 'ng') {
            initial[cellKey] = { status: 'bad' };
          } else if (isComplex) {
            // complex 컬럼: 숫자/문자열을 첫 번째 측정값으로 매핑
            const strVal = typeof rawVal === 'number' ? String(rawVal) : String(rawVal);
            initial[cellKey] = { measurements: [strVal, '', ''] };
          } else if (typeof rawVal === 'number') {
            initial[cellKey] = { value: String(rawVal) };
          } else if (typeof rawVal === 'string') {
            initial[cellKey] = { value: rawVal };
          }
        }
      });
      // ★ 템플릿 버전 불일치 폴백: templateValues 키가 현재 템플릿과 맞지 않으면
      //   레거시 필드(processingStatus, sewingStatus 등)를 컬럼 라벨로 매칭하여 복원
      if (Object.keys(initial).length === 0) {
        const hasAnyData = workItems.some((wi) => inspectionDataMap.get(wi.id));
        if (hasAnyData) {
          // 컬럼 라벨 → 레거시 필드 매핑
          const labelToLegacy: Record<string, (d: Record<string, unknown>) => unknown> = {
            '가공상태': (d) => d.processingStatus === 'good' ? 'ok' : d.processingStatus === 'bad' ? 'ng' : null,
            '재봉상태': (d) => d.sewingStatus === 'good' ? 'ok' : d.sewingStatus === 'bad' ? 'ng' : null,
            '조립상태': (d) => d.assemblyStatus === 'good' ? 'ok' : d.assemblyStatus === 'bad' ? 'ng' : null,
            '절곡상태': (d) => d.bendingStatus === 'good' ? 'ok' : d.bendingStatus === 'bad' ? 'ng' : null,
            '길이': (d) => d.length,
            '높이': (d) => d.width,
            '간격': (d) => d.gapStatus === 'ok' ? 'ok' : d.gapStatus === 'ng' ? 'ng' : null,
          };

          workItems.forEach((wi, rowIdx) => {
            const itemData = inspectionDataMap.get(wi.id) as Record<string, unknown> | undefined;
            if (!itemData) return;
            for (const col of template.columns) {
              const cellKey = `${rowIdx}-${col.id}`;
              // 컬럼 라벨에서 번호 접두사 제거 후 매칭 (예: "① 길이" → "길이")
              const labelClean = col.label.replace(/[①②③④⑤⑥⑦⑧⑨⑩\s]/g, '');
              const matchEntry = Object.entries(labelToLegacy).find(([key]) => labelClean.includes(key));
              if (!matchEntry) continue;
              const val = matchEntry[1](itemData);
              if (val == null) continue;
              if (val === 'ok') initial[cellKey] = { status: 'good' };
              else if (val === 'ng') initial[cellKey] = { status: 'bad' };
              else if (typeof val === 'number') initial[cellKey] = { value: String(val) };
              else if (typeof val === 'string') initial[cellKey] = { value: val };
            }
          });
        }
      }

      if (Object.keys(initial).length > 0) setCellValues(initial);

      // 부적합 내용 복원: 각 개소의 nonConformingContent를 수집
      const remarks: string[] = [];
      workItems.forEach((wi) => {
        const itemData = inspectionDataMap.get(wi.id);
        if (itemData?.nonConformingContent) {
          remarks.push(itemData.nonConformingContent);
        }
      });
      if (remarks.length > 0) setInadequateContent(remarks.join('\n'));
     
    }, [inspectionDataMap, workItems]);

    // ===== Bending: document_data EAV 레코드에서 복원 =====
    useEffect(() => {
      if (!isBending || !documentRecords || documentRecords.length === 0 || bendingProducts.length === 0) return;

      const initial: Record<string, CellValue> = {};

      // field_key 패턴: b{productIdx}_ok, b{productIdx}_ng, b{productIdx}_p{pointIdx}_n1, b{productIdx}_n{n}, b{productIdx}_judgment
      for (const rec of documentRecords) {
        const fk = rec.field_key;
        if (!fk.startsWith('b')) continue;
        const val = rec.field_value;
        if (val == null) continue;

        // b{productIdx}_ok / b{productIdx}_ng → check status
        const checkMatch = fk.match(/^b(\d+)_(ok|ng)$/);
        if (checkMatch && rec.column_id) {
          const productIdx = parseInt(checkMatch[1], 10);
          const cellKey = `b-${productIdx}-${rec.column_id}`;
          if (checkMatch[2] === 'ok' && val === 'OK') {
            initial[cellKey] = { ...initial[cellKey], status: 'good' };
          } else if (checkMatch[2] === 'ng' && val === 'NG') {
            initial[cellKey] = { ...initial[cellKey], status: 'bad' };
          }
          continue;
        }

        // b{productIdx}_p{pointIdx}_n1 → gap measurement
        const gapMatch = fk.match(/^b(\d+)_p(\d+)_n(\d+)$/);
        if (gapMatch && rec.column_id) {
          const productIdx = parseInt(gapMatch[1], 10);
          const pointIdx = parseInt(gapMatch[2], 10);
          const cellKey = `b-${productIdx}-p${pointIdx}-${rec.column_id}`;
          initial[cellKey] = { measurements: [val, '', ''] };
          continue;
        }

        // b{productIdx}_n{n} → complex measurement (길이/너비)
        const complexMatch = fk.match(/^b(\d+)_n(\d+)$/);
        if (complexMatch && rec.column_id) {
          const productIdx = parseInt(complexMatch[1], 10);
          const mIdx = parseInt(complexMatch[2], 10) - 1;
          const cellKey = `b-${productIdx}-${rec.column_id}`;
          const prev = initial[cellKey]?.measurements || ['', '', ''];
          const m: [string, string, string] = [...prev] as [string, string, string];
          m[mIdx] = val;
          initial[cellKey] = { ...initial[cellKey], measurements: m };
          continue;
        }

        // b{productIdx}_judgment → skip (자동 계산, 복원 불필요)
        if (fk.match(/^b\d+_judgment$/)) continue;

        // b{productIdx}_value → fallback value
        const valMatch = fk.match(/^b(\d+)_value$/);
        if (valMatch && rec.column_id) {
          const productIdx = parseInt(valMatch[1], 10);
          const cellKey = `b-${productIdx}-${rec.column_id}`;
          initial[cellKey] = { value: val };
          continue;
        }
      }

      // overall_result, remark 복원
      for (const rec of documentRecords) {
        if (rec.field_key === 'overall_result' && rec.field_value) {
          // overallResult는 자동 계산이므로 별도 처리 불필요
        }
        if (rec.field_key === 'remark' && rec.field_value) {
          setInadequateContent(rec.field_value);
        }
      }

      if (Object.keys(initial).length > 0) {
        setCellValues(prev => ({ ...prev, ...initial }));
      }
     
    }, [documentRecords, isBending, bendingProducts]);

    // ===== Bending: inspectionDataMap의 products 배열에서 셀 값 복원 =====
    // InspectionInputModal이 저장한 products 배열 → bending 셀 키로 매핑
    // ★ inspectionDataMap의 products가 있으면 documentRecords(EAV)보다 우선
    //   (입력 모달에서 방금 저장한 신규 데이터가 이전 문서 데이터보다 최신)
    useEffect(() => {
      if (!isBending || !inspectionDataMap || !workItems || bendingProducts.length === 0) return;

      // inspectionDataMap에서 products 배열 찾기
      type SavedProduct = {
        id: string;
        bendingStatus: string | null;
        lengthMeasured: string;
        widthMeasured: string;
        gapPoints: Array<{ point: string; designValue: string; measured: string }>;
      };
      let savedProducts: SavedProduct[] | undefined;
      for (const wi of workItems) {
        const d = inspectionDataMap.get(wi.id) as Record<string, unknown> | undefined;
        if (d?.products && Array.isArray(d.products)) {
          savedProducts = d.products as SavedProduct[];
          break;
        }
      }
      if (!savedProducts || savedProducts.length === 0) return;

      const initial: Record<string, CellValue> = {};

      // 컬럼 분류
      const checkColId = template.columns.find(c => c.column_type === 'check')?.id;
      const complexCols = template.columns.filter(c =>
        c.column_type === 'complex' && c.id !== gapColumnId
      );

      // 각 template bendingProduct → 저장된 product 매핑
      bendingProducts.forEach((bp, productIdx) => {
        // 1. ID 정규화 매칭 (guide-rail-wall ↔ guide_rail_wall)
        const normalizedBpId = bp.id.replace(/[-_]/g, '').toLowerCase();
        let matched = savedProducts!.find(sp =>
          sp.id.replace(/[-_]/g, '').toLowerCase() === normalizedBpId
        );

        // 2. 이름 키워드 매칭
        if (!matched) {
          const bpKey = `${bp.productName}${bp.productType}`.replace(/\s/g, '').toLowerCase();
          matched = savedProducts!.find(sp => {
            const spId = sp.id.toLowerCase();
            if (bpKey.includes('가이드레일') && bpKey.includes('벽면') && spId.includes('guide') && spId.includes('wall')) return true;
            if (bpKey.includes('가이드레일') && bpKey.includes('측면') && spId.includes('guide') && spId.includes('side')) return true;
            if (bpKey.includes('케이스') && spId.includes('case')) return true;
            if (bpKey.includes('하단마감') && (spId.includes('bottom-finish') || spId.includes('bottom_bar'))) return true;
            if (bpKey.includes('연기차단') && bpKey.includes('w50') && spId.includes('w50')) return true;
            if (bpKey.includes('연기차단') && bpKey.includes('w80') && spId.includes('w80')) return true;
            return false;
          });
        }

        // 3. 인덱스 폴백
        if (!matched && productIdx < savedProducts!.length) {
          matched = savedProducts![productIdx];
        }
        if (!matched) return;

        // check 컬럼 (절곡상태)
        if (checkColId) {
          const cellKey = `b-${productIdx}-${checkColId}`;
          if (matched.bendingStatus === '양호') {
            initial[cellKey] = { status: 'good' };
          } else if (matched.bendingStatus === '불량') {
            initial[cellKey] = { status: 'bad' };
          }
        }

        // 간격 컬럼
        if (gapColumnId && matched.gapPoints) {
          matched.gapPoints.forEach((gp, pointIdx) => {
            if (gp.measured) {
              const cellKey = `b-${productIdx}-p${pointIdx}-${gapColumnId}`;
              initial[cellKey] = { measurements: [gp.measured, '', ''] };
            }
          });
        }

        // complex 컬럼 (길이/너비)
        // bending 렌더링은 measurements[si] (si = sub_label raw index)를 읽으므로
        // 측정값 sub_label의 실제 si 위치에 값을 넣어야 함
        for (const col of complexCols) {
          const label = col.label.trim();
          const cellKey = `b-${productIdx}-${col.id}`;

          // 측정값 sub_label의 si 인덱스 찾기
          let measurementSi = 0;
          if (col.sub_labels) {
            for (let si = 0; si < col.sub_labels.length; si++) {
              const sl = col.sub_labels[si].toLowerCase();
              if (!sl.includes('도면') && !sl.includes('기준')) {
                measurementSi = si;
                break;
              }
            }
          }

          const measurements: [string, string, string] = ['', '', ''];
          if (label.includes('길이') && matched.lengthMeasured) {
            measurements[measurementSi] = matched.lengthMeasured;
            initial[cellKey] = { measurements };
          } else if ((label.includes('너비') || label.includes('폭') || label.includes('높이')) && matched.widthMeasured) {
            measurements[measurementSi] = matched.widthMeasured;
            initial[cellKey] = { measurements };
          }
        }
      });

      if (Object.keys(initial).length > 0) {
        setCellValues(prev => ({ ...prev, ...initial }));
      }
     
    }, [isBending, inspectionDataMap, workItems, bendingProducts, template.columns, gapColumnId]);

    const updateCell = (key: string, update: Partial<CellValue>) => {
      setCellValues(prev => ({
        ...prev,
        [key]: { ...prev[key], ...update },
      }));
    };

    // 행별 판정 계산
    const getRowJudgment = (rowIdx: number): '적' | '부' | null => {
      let hasAnyValue = false;
      let hasFail = false;

      for (const col of template.columns) {
        const sectionItem = columnItemMap.get(col.id);
        if (!sectionItem) continue;

        const key = `${rowIdx}-${col.id}`;
        const cell = cellValues[key];
        if (!cell) continue;

        const mType = sectionItem.measurement_type;

        if (mType === 'checkbox' || col.column_type === 'check') {
          if (cell.status === 'bad') hasFail = true;
          if (cell.status) hasAnyValue = true;
        } else if (mType === 'numeric') {
          const measurements = cell.measurements || ['', '', ''];
          for (const m of measurements) {
            if (m) {
              hasAnyValue = true;
              const val = parseFloat(m);
              if (!isNaN(val) && !isWithinTolerance(val, sectionItem, effectiveWorkItems[rowIdx])) hasFail = true;
            }
          }
        } else if (mType === 'single_value') {
          if (cell.value) {
            hasAnyValue = true;
            const val = parseFloat(cell.value);
            if (!isNaN(val) && !isWithinTolerance(val, sectionItem, effectiveWorkItems[rowIdx])) hasFail = true;
          }
        } else if (mType === 'substitute') {
          hasAnyValue = true;
        } else if (cell.value || cell.text) {
          hasAnyValue = true;
        }
      }

      if (!hasAnyValue) return null;
      return hasFail ? '부' : '적';
    };

    // ref로 데이터 수집 노출 - 정규화된 document_data 레코드 형식
    useImperativeHandle(ref, () => ({
      getInspectionData: () => {
        const records: Array<{
          section_id: number | null;
          column_id: number | null;
          row_index: number;
          field_key: string;
          field_value: string | null;
        }> = [];

        // ===== 1. 기본 필드 (bf_xxx) =====
        if (template.basic_fields?.length > 0) {
          for (const field of template.basic_fields) {
            const val = resolveFieldValue(field);
            if (val && val !== '-' && val !== '(입력)') {
              records.push({
                section_id: null, column_id: null, row_index: 0,
                field_key: `bf_${field.id}`,
                field_value: val,
              });
            }
          }
        }

        // ===== 2. 행별 검사 데이터 =====
        // Bending 모드: 구성품별 데이터 (개소 단위, field_key에 구성품/포인트 인코딩)
        if (isBending && bendingProducts.length > 0) {
          bendingProducts.forEach((product, productIdx) => {
            for (const col of template.columns) {
              const label = col.label.trim();
              const isGapCol = col.id === gapColumnId;

              // text 컬럼 (분류/제품명, 타입) → bendingInfo에서 동적 생성이므로 저장 불필요
              if (col.column_type === 'text') continue;

              // 판정 컬럼 → 자동 계산 결과 저장
              if (isJudgmentColumn(label)) {
                const judgment = getBendingProductJudgment(productIdx);
                if (judgment) {
                  records.push({
                    section_id: null, column_id: col.id, row_index: 0,
                    field_key: `b${productIdx}_judgment`,
                    field_value: judgment === '적' ? 'OK' : 'NG',
                  });
                }
                continue;
              }

              // 간격 컬럼 (per-point 데이터)
              if (isGapCol) {
                product.gapPoints.forEach((_gp, pointIdx) => {
                  const cellKey = `b-${productIdx}-p${pointIdx}-${col.id}`;
                  const cell = cellValues[cellKey];
                  if (cell?.measurements?.[0]) {
                    records.push({
                      section_id: null, column_id: col.id, row_index: 0,
                      field_key: `b${productIdx}_p${pointIdx}_n1`,
                      field_value: cell.measurements[0],
                    });
                  }
                });
                continue;
              }

              // 비간격 merged 컬럼
              const cellKey = `b-${productIdx}-${col.id}`;
              const cell = cellValues[cellKey];

              // check 컬럼 (절곡상태)
              if (col.column_type === 'check') {
                records.push({
                  section_id: null, column_id: col.id, row_index: 0,
                  field_key: `b${productIdx}_ok`,
                  field_value: cell?.status === 'good' ? 'OK' : '',
                });
                records.push({
                  section_id: null, column_id: col.id, row_index: 0,
                  field_key: `b${productIdx}_ng`,
                  field_value: cell?.status === 'bad' ? 'NG' : '',
                });
                continue;
              }

              // complex 컬럼 (길이/너비 측정)
              if (col.column_type === 'complex' && col.sub_labels) {
                let inputIdx = 0;
                for (const sl of col.sub_labels) {
                  const slLower = sl.toLowerCase();
                  if (slLower.includes('도면') || slLower.includes('기준')) continue;
                  if (slLower.includes('point') || slLower.includes('포인트')) continue;
                  const n = inputIdx + 1;
                  const val = cell?.measurements?.[inputIdx] || null;
                  if (val) {
                    records.push({
                      section_id: null, column_id: col.id, row_index: 0,
                      field_key: `b${productIdx}_n${n}`,
                      field_value: val,
                    });
                  }
                  inputIdx++;
                }
                continue;
              }

              // fallback
              if (cell?.value) {
                records.push({
                  section_id: null, column_id: col.id, row_index: 0,
                  field_key: `b${productIdx}_value`,
                  field_value: cell.value,
                });
              }
            }
          });
        }

        // 개소(WorkItem)별 데이터: 비-Bending 또는 Bending이지만 구성품이 없는 경우 (AS-IS)
        if (!isBending || bendingProducts.length === 0) effectiveWorkItems.forEach((wi, rowIdx) => {
          for (const col of template.columns) {
            // 일련번호 컬럼 → 저장 (mng show에서 표시용)
            if (isSerialColumn(col.label)) {
              records.push({
                section_id: null, column_id: col.id, row_index: rowIdx,
                field_key: 'value', field_value: String(rowIdx + 1),
              });
              continue;
            }

            // 판정 컬럼 → 자동 계산 결과 저장
            if (isJudgmentColumn(col.label)) {
              const judgment = getRowJudgment(rowIdx);
              if (judgment) {
                records.push({
                  section_id: null, column_id: col.id, row_index: rowIdx,
                  field_key: 'value', field_value: judgment === '적' ? 'OK' : 'NG',
                });
              }
              continue;
            }

            const sectionItem = columnItemMap.get(col.id);
            if (!sectionItem) continue;

            const sectionId = itemSectionMap.get(sectionItem.id) ?? null;
            const key = `${rowIdx}-${col.id}`;
            const cell = cellValues[key];

            const mType = sectionItem.measurement_type || '';

            if (col.column_type === 'complex' && col.sub_labels) {
              // 복합 컬럼: sub_label 유형별 처리
              let inputIdx = 0;
              for (const subLabel of col.sub_labels) {
                const sl = subLabel.toLowerCase();

                if (sl.includes('도면') || sl.includes('기준')) {
                  // 기준치 → formatStandard 결과 저장
                  const stdVal = formatStandard(sectionItem, wi);
                  records.push({
                    section_id: sectionId, column_id: col.id, row_index: rowIdx,
                    field_key: 'standard', field_value: stdVal || null,
                  });
                } else if (sl.includes('ok') || sl.includes('ng')) {
                  // OK·NG → cell.status 저장
                  const n = inputIdx + 1;
                  if (mType === 'checkbox') {
                    records.push({
                      section_id: sectionId, column_id: col.id, row_index: rowIdx,
                      field_key: `n${n}_ok`,
                      field_value: cell?.status === 'good' ? 'OK' : '',
                    });
                    records.push({
                      section_id: sectionId, column_id: col.id, row_index: rowIdx,
                      field_key: `n${n}_ng`,
                      field_value: cell?.status === 'bad' ? 'NG' : '',
                    });
                  }
                  inputIdx++;
                } else {
                  // 측정값
                  const n = inputIdx + 1;
                  const val = cell?.measurements?.[inputIdx] || null;
                  if (mType === 'checkbox') {
                    records.push({
                      section_id: sectionId, column_id: col.id, row_index: rowIdx,
                      field_key: `n${n}_ok`,
                      field_value: val?.toLowerCase() === 'ok' ? 'OK' : '',
                    });
                    records.push({
                      section_id: sectionId, column_id: col.id, row_index: rowIdx,
                      field_key: `n${n}_ng`,
                      field_value: val?.toLowerCase() === 'ng' ? 'NG' : '',
                    });
                  } else {
                    records.push({
                      section_id: sectionId, column_id: col.id, row_index: rowIdx,
                      field_key: `n${n}`,
                      field_value: val,
                    });
                  }
                  inputIdx++;
                }
              }
            } else if (cell?.value !== undefined) {
              records.push({
                section_id: sectionId, column_id: col.id, row_index: rowIdx,
                field_key: 'value', field_value: cell.value || null,
              });
            } else if (cell?.text !== undefined) {
              records.push({
                section_id: sectionId, column_id: col.id, row_index: rowIdx,
                field_key: 'value', field_value: cell.text || null,
              });
            }
          }
        });

        // ===== 3. 종합판정 =====
        records.push({
          section_id: null, column_id: null, row_index: 0,
          field_key: 'overall_result', field_value: overallResult,
        });

        // ===== 4. 부적합 내용 (비고) =====
        if (inadequateContent) {
          records.push({
            section_id: null, column_id: null, row_index: 0,
            field_key: 'remark', field_value: inadequateContent,
          });
        }

        return { template_id: template.id, records };
      },
    }));

    // Bending 제품별 판정
    const getBendingProductJudgment = (productIdx: number): '적' | '부' | null => {
      const checkCol = template.columns.find(c => c.column_type === 'check');
      if (!checkCol) return null;
      const cell = cellValues[`b-${productIdx}-${checkCol.id}`];
      if (cell?.status === 'bad') return '부';
      if (cell?.status === 'good') return '적';
      return null;
    };

    // 종합판정
    const judgments = isBending && bendingProducts.length > 0
      ? bendingProducts.map((_, idx) => getBendingProductJudgment(idx))
      : effectiveWorkItems.map((_, idx) => getRowJudgment(idx));
    const overallResult = calculateOverallResult(judgments);

    // 컬럼별 colspan 계산 (mng _colSpan 동기화)
    const getColSpan = (col: (typeof template.columns)[0]) => {
      if (col.column_type === 'complex' && col.sub_labels && col.sub_labels.length > 0) {
        return col.sub_labels.length;
      }
      const label = (col.label || '').trim();
      if (label.includes('검사항목') || label.includes('항목') ||
          label.includes('검사기준') || label.includes('기준')) return 2;
      return 1;
    };

    // check 컬럼의 체크박스 라벨 추출 (sub_labels 기반, 없으면 양호/불량)
    const getCheckLabels = (col: (typeof template.columns)[0]): [string, string] => {
      if (col.sub_labels && col.sub_labels.length >= 2) {
        const a = (col.sub_labels[0] || '').trim();
        const b = (col.sub_labels[1] || '').trim();
        if (a && b && !/^n?\d+$/i.test(a)) return [a, b];
      }
      return ['양호', '불량'];
    };

    // 기본필드 값 해석 (field_key 또는 label 기반 매핑)
    const resolveFieldValue = (field: (typeof template.basic_fields)[0]) => {
      if (!field) return '';
      // field_key가 있으면 field_key 기준, 없으면 label 기준 매칭
      const key = field.field_key || '';
      const label = (field.label || '').trim();

      const LABEL_TO_KEY: Record<string, string> = {
        '품명': 'product_name',
        '제품명': 'product_name',
        '규격': 'specification',
        '수주 LOT NO': 'lot_no',
        'LOT NO': 'lot_no',
        '로트크기': 'lot_size',
        '발주처': 'client',
        '현장명': 'site_name',
        '검사일자': 'inspection_date',
        '검사자': 'inspector',
      };

      const resolvedKey = key || LABEL_TO_KEY[label] || '';

      switch (resolvedKey) {
        case 'product_name': return order.items?.[0]?.productName || '-';
        case 'specification': return field.default_value || '-';
        case 'lot_no': return order.lotNo || '-';
        case 'lot_size': return `${effectiveWorkItems.length || order.items?.length || 0} 개소`;
        case 'client': return order.client || '-';
        case 'site_name': return order.projectName || '-';
        case 'inspection_date': return fullDate;
        case 'inspector': return primaryAssignee;
        default: return field.default_value || '-';
      }
    };

    // --- complex 컬럼 하위 셀 렌더링 ---
    const renderComplexCells = (
      col: (typeof template.columns)[0],
      cellKey: string,
      cell: CellValue | undefined,
      workItem: WorkItemData,
    ) => {
      if (!col.sub_labels) return null;
      const sectionItem = columnItemMap.get(col.id);
      let inputIdx = 0;

      return col.sub_labels.map((subLabel, subIdx) => {
        const sl = subLabel.toLowerCase();

        // 도면치수/기준치 → 기준값 readonly 표시
        if (sl.includes('도면') || sl.includes('기준')) {
          return (
            <td key={`${col.id}-s${subIdx}`} className="border border-gray-400 px-2 py-1.5 text-center text-gray-500">
              {sectionItem ? formatStandard(sectionItem, workItem) : '-'}
            </td>
          );
        }

        // OK/NG → 체크박스 토글
        if (sl.includes('ok') || sl.includes('ng')) {
          return (
            <td key={`${col.id}-s${subIdx}`} className="border border-gray-400 p-1">
              <div className="flex flex-col items-center gap-0.5">
                <label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
                  <InspectionCheckbox
                    checked={cell?.status === 'good'}
                    onClick={() => updateCell(cellKey, { status: cell?.status === 'good' ? null : 'good' })}
                    readOnly={readOnly}
                  />
                  OK
                </label>
                <label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
                  <InspectionCheckbox
                    checked={cell?.status === 'bad'}
                    onClick={() => updateCell(cellKey, { status: cell?.status === 'bad' ? null : 'bad' })}
                    readOnly={readOnly}
                  />
                  NG
                </label>
              </div>
            </td>
          );
        }

        // POINT → 포인트 번호 표시 (mng mCell 동기화)
        if (sl.includes('point') || sl.includes('포인트')) {
          return (
            <td key={`${col.id}-s${subIdx}`} className="border border-gray-400 px-1 py-1.5 text-center">
              <span className="text-gray-400 text-[10px] italic">({subIdx + 1})</span>
            </td>
          );
        }

        // 측정값 → 입력 필드
        const mIdx = inputIdx++;
        return (
          <td key={`${col.id}-s${subIdx}`} className="border border-gray-400 p-0.5">
            <input
              type="text"
              className={INPUT_CLASS}
              value={cell?.measurements?.[mIdx] || ''}
              onChange={e => {
                const m: [string, string, string] = [
                  ...(cell?.measurements || ['', '', '']),
                ] as [string, string, string];
                m[mIdx] = e.target.value;
                updateCell(cellKey, { measurements: m });
              }}
              readOnly={readOnly}
              placeholder="측정값"
            />
          </td>
        );
      });
    };

    // 다단계 헤더 행 빌드 (group_name "/" 구분자 지원, mng renderHeaders 동기화)
    const buildHeaderRows = () => {
      const cols = template.columns;
      if (cols.length === 0) return [];

      const thCls = 'border border-gray-400 px-2 py-1.5 bg-gray-100 text-center';
      const thSubCls = 'border border-gray-400 px-1 py-1 bg-gray-100 text-center text-[10px]';

      // 1) group_name "/" split → depth 배열
      const colGroups = cols.map(col => {
        const gn = (col.group_name || '').trim();
        return gn ? gn.split('/').map(s => s.trim()) : [];
      });
      // 단일 레벨 group_name은 그룹 행 생성 안 함 (하위 호환)
      const maxDepth = Math.max(0, ...colGroups.map(g => g.length > 1 ? g.length : 0));
      const needSubRow = cols.some(c =>
        c.column_type === 'complex' && c.sub_labels && c.sub_labels.length > 0
      );
      const totalHeaderRows = maxDepth + 1 + (needSubRow ? 1 : 0);

      const rows: React.ReactElement[] = [];

      // 2) 그룹 행들 (maxDepth > 0일 때만)
      if (maxDepth > 0) {
        for (let depth = 0; depth < maxDepth; depth++) {
          const cells: React.ReactElement[] = [];
          let ci = 0;

          while (ci < cols.length) {
            const levels = colGroups[ci];
            const col = cols[ci];

            // 그룹 없는/단일 레벨: depth=0에서 전체 rowspan
            if (levels.length <= 1) {
              if (depth === 0) {
                const cs = getColSpan(col);
                cells.push(
                  <th key={`g0-${ci}`} className={thCls}
                      colSpan={cs > 1 ? cs : undefined}
                      rowSpan={totalHeaderRows}
                      style={col.width ? { width: col.width } : undefined}>
                    {col.label}
                  </th>
                );
              }
              ci++; continue;
            }

            if (levels.length <= depth) { ci++; continue; }

            // 같은 prefix 중복 방지
            const prefix = levels.slice(0, depth + 1).join('/');
            let isFirst = true;
            for (let k = 0; k < ci; k++) {
              if (colGroups[k].length > depth &&
                  colGroups[k].slice(0, depth + 1).join('/') === prefix) {
                isFirst = false; break;
              }
            }
            if (!isFirst) { ci++; continue; }

            // colspan 합산
            let span = getColSpan(col);
            let cj = ci + 1;
            while (cj < cols.length) {
              if (colGroups[cj].length > depth &&
                  colGroups[cj].slice(0, depth + 1).join('/') === prefix) {
                span += getColSpan(cols[cj]); cj++;
              } else break;
            }

            // 하위 그룹 존재 여부
            let hasDeeper = false;
            for (let k = ci; k < cj; k++) {
              if (colGroups[k].length > depth + 1) { hasDeeper = true; break; }
            }

            const groupLabel = levels[depth];
            const rs = (!hasDeeper && depth < maxDepth - 1) ? maxDepth - depth : undefined;
            cells.push(
              <th key={`g-${ci}-${depth}`} className={thCls} colSpan={span} rowSpan={rs}>
                {groupLabel}
              </th>
            );
            ci = cj;
          }

          rows.push(<tr key={`grp-${depth}`}>{cells}</tr>);
        }
      }

      // 3) 컬럼 라벨 행
      const labelCells: React.ReactElement[] = [];
      cols.forEach((col, idx) => {
        const levels = colGroups[idx];
        if (maxDepth > 0 && levels.length <= 1) return;

        const cs = getColSpan(col);
        const isComplex = col.column_type === 'complex' && col.sub_labels && col.sub_labels.length > 0;

        if (needSubRow && isComplex) {
          labelCells.push(
            <th key={col.id} className={thCls} colSpan={cs}>{col.label}</th>
          );
        } else if (needSubRow) {
          labelCells.push(
            <th key={col.id} className={thCls}
                colSpan={cs > 1 ? cs : undefined} rowSpan={2}
                style={col.width ? { width: col.width } : undefined}>
              {col.label}
            </th>
          );
        } else {
          labelCells.push(
            <th key={col.id} className={thCls}
                colSpan={cs > 1 ? cs : undefined}
                style={col.width ? { width: col.width } : undefined}>
              {col.label}
            </th>
          );
        }
      });
      rows.push(<tr key="labels">{labelCells}</tr>);

      // 4) sub_labels 행
      if (needSubRow) {
        const subCells: React.ReactElement[] = [];
        cols.forEach(col => {
          if (col.column_type === 'complex' && col.sub_labels && col.sub_labels.length > 0) {
            col.sub_labels.forEach((sl, si) => {
              const slLower = (sl || '').toLowerCase();
              const isBasis = slLower.includes('도면') || slLower.includes('기준');
              subCells.push(
                <th key={`${col.id}-sh-${si}`} className={thSubCls}
                    style={isBasis ? { background: '#f3f4f6' } : undefined}>
                  {sl}
                </th>
              );
            });
          }
        });
        rows.push(<tr key="sublabels">{subCells}</tr>);
      }

      return rows;
    };

    // --- Bending DATA 테이블 body 렌더링 (제품별 다중 POINT 행 + rowSpan 병합) ---
    const renderBendingBody = () => {
      return bendingExpandedRows.map((row) => {
        const { productIdx, product, pointIdx, gapPoint, isFirstRow, totalPoints } = row;

        return (
          <tr key={`b-${productIdx}-${pointIdx}`}>
            {template.columns.map(col => {
              const isGapCol = col.id === gapColumnId;

              // 간격 외 컬럼: 첫 행만 렌더 (rowSpan으로 병합)
              if (!isGapCol && !isFirstRow) return null;

              const rs = isFirstRow && !isGapCol ? totalPoints : undefined;
              const cellKey = isGapCol
                ? `b-${productIdx}-p${pointIdx}-${col.id}`
                : `b-${productIdx}-${col.id}`;
              const cell = cellValues[cellKey];
              const label = col.label.trim();

              // 1. 분류/제품명 (text)
              if (col.column_type === 'text' && (label.includes('분류') || label.includes('제품명'))) {
                return (
                  <td key={col.id} rowSpan={rs}
                      className="border border-gray-400 px-2 py-1.5 text-center align-middle text-xs"
                      style={col.width ? { width: col.width } : undefined}>
                    <div>{product.category}</div>
                    <div className="font-medium">{product.productName}</div>
                  </td>
                );
              }

              // 2. 타입 (text)
              if (col.column_type === 'text' && label === '타입') {
                return (
                  <td key={col.id} rowSpan={rs}
                      className="border border-gray-400 px-2 py-1.5 text-center align-middle text-xs whitespace-pre-line"
                      style={col.width ? { width: col.width } : undefined}>
                    {product.productType}
                  </td>
                );
              }

              // 3. check (절곡상태 - 양호/불량)
              if (col.column_type === 'check') {
                const [goodLabel, badLabel] = getCheckLabels(col);
                return (
                  <td key={col.id} rowSpan={rs} className="border border-gray-400 p-1"
                      colSpan={getColSpan(col) > 1 ? getColSpan(col) : undefined}>
                    <div className="flex flex-col items-center gap-0.5">
                      <label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
                        <InspectionCheckbox
                          checked={cell?.status === 'good'}
                          onClick={() => updateCell(cellKey, { status: cell?.status === 'good' ? null : 'good' })}
                          readOnly={readOnly}
                        />
                        {goodLabel}
                      </label>
                      <label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
                        <InspectionCheckbox
                          checked={cell?.status === 'bad'}
                          onClick={() => updateCell(cellKey, { status: cell?.status === 'bad' ? null : 'bad' })}
                          readOnly={readOnly}
                        />
                        {badLabel}
                      </label>
                    </div>
                  </td>
                );
              }

              // 4. complex 간격 (per-POINT) → POINT + 도면치수 + 측정값
              if (isGapCol && col.column_type === 'complex' && col.sub_labels) {
                return col.sub_labels.map((sl, si) => {
                  const slLower = sl.toLowerCase();
                  if (slLower.includes('point') || slLower.includes('포인트')) {
                    return (
                      <td key={`${col.id}-s${si}`}
                          className="border border-gray-400 px-1 py-1.5 text-center text-xs">
                        {gapPoint.point}
                      </td>
                    );
                  }
                  if (slLower.includes('도면') || slLower.includes('기준')) {
                    return (
                      <td key={`${col.id}-s${si}`}
                          className="border border-gray-400 px-2 py-1.5 text-center text-blue-600 font-bold text-xs">
                        {gapPoint.designValue}
                      </td>
                    );
                  }
                  return (
                    <td key={`${col.id}-s${si}`} className="border border-gray-400 p-0.5">
                      <input type="text" className={INPUT_CLASS}
                        value={cell?.measurements?.[0] || ''}
                        onChange={e => updateCell(cellKey, { measurements: [e.target.value, '', ''] })}
                        readOnly={readOnly} placeholder="측정값"
                      />
                    </td>
                  );
                });
              }

              // 5. complex 길이/너비 (merged) → 도면치수 + 측정값
              if (!isGapCol && col.column_type === 'complex' && col.sub_labels) {
                const isLen = label.includes('길이');
                const designVal = isLen ? product.lengthDesign : product.widthDesign;
                return col.sub_labels.map((sl, si) => {
                  const slLower = sl.toLowerCase();
                  if (slLower.includes('도면') || slLower.includes('기준')) {
                    return (
                      <td key={`${col.id}-s${si}`} rowSpan={rs}
                          className="border border-gray-400 px-2 py-1.5 text-center text-blue-600 font-bold text-xs">
                        {designVal}
                      </td>
                    );
                  }
                  return (
                    <td key={`${col.id}-s${si}`} rowSpan={rs} className="border border-gray-400 p-0.5">
                      <input type="text" className={INPUT_CLASS}
                        value={cell?.measurements?.[si] || ''}
                        onChange={e => {
                          const m: [string, string, string] = [...(cell?.measurements || ['', '', ''])] as [string, string, string];
                          m[si] = e.target.value;
                          updateCell(cellKey, { measurements: m });
                        }}
                        readOnly={readOnly} placeholder="측정값"
                      />
                    </td>
                  );
                });
              }

              // 6. 판정 (적/부)
              if (isJudgmentColumn(label)) {
                return <JudgmentCell key={col.id} judgment={getBendingProductJudgment(productIdx)} rowSpan={rs} />;
              }

              // fallback
              return (
                <td key={col.id} rowSpan={rs}
                    className="border border-gray-400 px-2 py-1.5 text-center text-xs">-</td>
              );
            })}
          </tr>
        );
      });
    };

    return (
      <div className="p-6 bg-white">
        {/* ===== 헤더: KD + 회사명(좌) | 제목(중앙) | 결재라인(우) ===== */}
        <div className="flex justify-between items-start mb-4">
          <div className="text-center" style={{ width: 80 }}>
            <div className="text-2xl font-bold">KD</div>
            {template.company_name && (
              <div className="text-xs">{template.company_name}</div>
            )}
          </div>
          <div className="flex-1 text-center">
            <h1 className="text-xl font-bold tracking-widest">
              {template.title || template.name || '중간검사 성적서'}
            </h1>
          </div>
          <div>
            {template.approval_lines?.length > 0 ? (
              <table className="border-collapse text-xs">
                <tbody>
                  <tr>
                    {template.approval_lines.map(line => (
                      <td key={`n-${line.id}`} className="border border-gray-400 px-3 py-1 bg-gray-100 text-center font-medium">
                        {line.name || '-'}
                      </td>
                    ))}
                  </tr>
                  <tr>
                    {template.approval_lines.map(line => (
                      <td key={`d-${line.id}`} className="border border-gray-400 px-3 py-1 text-center">
                        <div className="text-gray-400 text-xs">{line.dept || ''}</div>
                        <div className="h-6" />
                      </td>
                    ))}
                  </tr>
                </tbody>
              </table>
            ) : (
              <span className="text-xs text-gray-400">결재라인 미설정</span>
            )}
          </div>
        </div>

        {/* ===== 기본 필드: 2열 배치 (15:35:15:35) ===== */}
        {template.basic_fields?.length > 0 && (
          <table className="w-full border-collapse text-xs mb-4" style={{ tableLayout: 'fixed' }}>
            <tbody>
              {Array.from({ length: Math.ceil(template.basic_fields.length / 2) }, (_, rowIdx) => {
                const f1 = template.basic_fields[rowIdx * 2];
                const f2 = template.basic_fields[rowIdx * 2 + 1];
                return (
                  <tr key={rowIdx}>
                    <td className="border border-gray-400 px-2 py-1.5 bg-gray-100 font-medium text-center" style={{ width: '15%' }}>
                      {f1.label}
                    </td>
                    <td className="border border-gray-400 px-2 py-1.5" style={{ width: '35%' }}>
                      {resolveFieldValue(f1)}
                    </td>
                    {f2 ? (
                      <>
                        <td className="border border-gray-400 px-2 py-1.5 bg-gray-100 font-medium text-center" style={{ width: '15%' }}>
                          {f2.label}
                        </td>
                        <td className="border border-gray-400 px-2 py-1.5" style={{ width: '35%' }}>
                          {resolveFieldValue(f2)}
                        </td>
                      </>
                    ) : (
                      <td className="border border-gray-400 px-2 py-1.5" colSpan={2} />
                    )}
                  </tr>
                );
              })}
            </tbody>
          </table>
        )}

        {/* ===== 이미지 섹션: items 없는 섹션 ===== */}
        {imageSections.map(section => (
          <SectionImage key={section.id} section={section} />
        ))}

        {/* ===== DATA 테이블: columns 기반 헤더 + work items 행 ===== */}
        {template.columns.length > 0 && (isBending ? bendingProducts.length > 0 : effectiveWorkItems.length > 0) && (
          <>
            {dataSections.length > 0 && (
              <p className="text-sm font-bold mb-1 mt-3">■ {dataSections[0].title || dataSections[0].name}</p>
            )}
            <div className="overflow-x-auto">
              <table className="w-full border-collapse text-xs">
                <thead>
                  {buildHeaderRows()}
                </thead>
                <tbody>
                  {isBending ? renderBendingBody() : effectiveWorkItems.map((wi, rowIdx) => (
                    <tr key={wi.id}>
                      {template.columns.map(col => {
                        const cellKey = `${rowIdx}-${col.id}`;
                        const cell = cellValues[cellKey];

                        // 일련번호/NO
                        if (isSerialColumn(col.label)) {
                          return (
                            <td key={col.id} className="border border-gray-400 px-2 py-1.5 text-center">
                              {rowIdx + 1}
                            </td>
                          );
                        }

                        // 판정 (자동 계산)
                        if (isJudgmentColumn(col.label)) {
                          return <JudgmentCell key={col.id} judgment={getRowJudgment(rowIdx)} />;
                        }

                        // check → 커스텀 라벨 (mng _getCheckLabels 동기화)
                        if (col.column_type === 'check') {
                          const [goodLabel, badLabel] = getCheckLabels(col);
                          return (
                            <td key={col.id} className="border border-gray-400 p-1"
                                colSpan={getColSpan(col) > 1 ? getColSpan(col) : undefined}>
                              <div className="flex flex-col items-center gap-0.5">
                                <label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
                                  <InspectionCheckbox
                                    checked={cell?.status === 'good'}
                                    onClick={() => updateCell(cellKey, { status: cell?.status === 'good' ? null : 'good' })}
                                    readOnly={readOnly}
                                  />
                                  {goodLabel}
                                </label>
                                <label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
                                  <InspectionCheckbox
                                    checked={cell?.status === 'bad'}
                                    onClick={() => updateCell(cellKey, { status: cell?.status === 'bad' ? null : 'bad' })}
                                    readOnly={readOnly}
                                  />
                                  {badLabel}
                                </label>
                              </div>
                            </td>
                          );
                        }

                        // complex → sub_labels 개수만큼 셀 생성
                        if (col.column_type === 'complex' && col.sub_labels && col.sub_labels.length > 0) {
                          return renderComplexCells(col, cellKey, cell, wi);
                        }

                        // select (판정 외) → 텍스트 입력
                        if (col.column_type === 'select') {
                          return (
                            <td key={col.id} className="border border-gray-400 p-0.5"
                                colSpan={getColSpan(col) > 1 ? getColSpan(col) : undefined}>
                              <input
                                type="text"
                                className={INPUT_CLASS}
                                value={cell?.value || ''}
                                onChange={e => updateCell(cellKey, { value: e.target.value })}
                                readOnly={readOnly}
                                placeholder="-"
                              />
                            </td>
                          );
                        }

                        // text/기타 → 텍스트 입력
                        return (
                          <td key={col.id} className="border border-gray-400 p-0.5"
                              colSpan={getColSpan(col) > 1 ? getColSpan(col) : undefined}>
                            <input
                              type="text"
                              className={INPUT_CLASS}
                              value={cell?.value || cell?.text || ''}
                              onChange={e => updateCell(cellKey, { value: e.target.value })}
                              readOnly={readOnly}
                              placeholder="-"
                            />
                          </td>
                        );
                      })}
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </>
        )}

        {/* ===== 푸터: 비고(좌) + 종합판정(우) 높이 동일 배치 ===== */}
        <div className="mt-4 flex items-stretch gap-4">
          <div className="flex-1">
            <table className="w-full h-full border-collapse text-xs">
              <tbody>
                <tr>
                  <td className="border border-gray-400 px-3 py-2 bg-gray-100 font-medium align-top" style={{ width: 100 }}>
                    {template.footer_remark_label || '비고'}
                  </td>
                  <td className="border border-gray-400 px-3 py-2 align-top">
                    <textarea
                      value={inadequateContent}
                      onChange={e => !readOnly && setInadequateContent(e.target.value)}
                      disabled={readOnly}
                      className="w-full border-0 bg-transparent focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-xs resize-none"
                      rows={2}
                    />
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
          <div>
            <table className="h-full border-collapse text-xs">
              <tbody>
                <tr>
                  <td className="border border-gray-400 px-4 py-2 bg-gray-100 font-medium">
                    {template.footer_judgement_label || '종합판정'}
                  </td>
                </tr>
                <tr>
                  <td className="border border-gray-400 px-4 py-3 text-center">
                    {overallResult ? (
                      <span className={`font-bold text-sm ${
                        overallResult === '합격' ? 'text-blue-600' : 'text-red-600'
                      }`}>
                        {overallResult}
                      </span>
                    ) : (
                      <span className="text-gray-300 text-sm">&nbsp;</span>
                    )}
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
      </div>
    );
  }
);
