/**
 * 절곡 작업일지 유틸리티 함수
 *
 * PHP viewBendingWork_slat.php의 calWeight, 재질매핑, 길이버킷팅 등을 TypeScript로 구현
 */

import type {
  WeightResult,
  MaterialMapping,
  LengthQuantity,
  GuideRailPartRow,
  BottomBarPartRow,
  ShutterBoxPartRow,
  SmokeBarrierPartRow,
  BendingInfoExtended,
  ShutterBoxData,
  ProductionSummary,
} from './types';

// ============================================================
// 상수
// ============================================================

const SUS_DENSITY = 7.93;  // g/cm3
const EGI_DENSITY = 7.85;  // g/cm3

// 가이드레일
const WALL_PART_WIDTH = 412;  // mm - 벽면형 파트 폭
const SIDE_PART_WIDTH = 462;  // mm - 측면형 파트 폭
const WALL_BASE_HEIGHT = 80;   // 벽면형 하부BASE 높이 (legacy: wall_basesize 135*80)
const SIDE_BASE_HEIGHT = 130;  // 측면형 하부BASE 높이 (legacy: side_basesize 135*130)
const BASE_WIDTH = 135;  // 하부BASE 폭

// 하단마감재
const BOTTOM_BAR_WIDTH = 184;  // mm
const EXTRA_FINISH_WIDTH = 238;  // mm

// 연기차단재
const SMOKE_BARRIER_WIDTH = 26;  // mm

// 셔터박스
const BOX_FINISH_MATERIAL = 'EGI 1.55T';
const BOX_COVER_LENGTH = 1219;  // 상부덮개 고정 길이

// 길이 버킷
const _GUIDE_RAIL_LENGTH_BUCKETS = [2438, 3000, 3500, 4000, 4300];
const _SHUTTER_BOX_LENGTH_BUCKETS = [1219, 2438, 3000, 3500, 4000, 4150];
const _SMOKE_W50_LENGTH_BUCKETS = [2438, 3000, 3500, 4000, 4300];

// ============================================================
// 하부BASE 치수 파싱 헬퍼
// ============================================================

/** baseDimension 문자열("135*130")에서 width/height 파싱. 없으면 기본값 사용 */
function parseBaseDimension(baseDimension: string | undefined, fallbackHeight: number): { width: number; height: number } {
  if (baseDimension) {
    const parts = baseDimension.split('*').map(Number);
    if (parts.length === 2 && parts[0] > 0 && parts[1] > 0) {
      return { width: parts[0], height: parts[1] };
    }
  }
  return { width: BASE_WIDTH, height: fallbackHeight };
}

// ============================================================
// 핵심 함수: calWeight (PHP Lines 27-55)
// ============================================================

/**
 * 무게 계산
 * volume_cm3 = (thickness * width * height) / 1000
 * weight_kg = (volume_cm3 * density) / 1000
 */
export function calcWeight(material: string, width: number, height: number): WeightResult {
  const thicknessMatch = material.match(/(\d+(\.\d+)?)/);
  const thickness = thicknessMatch ? parseFloat(thicknessMatch[1]) : 0;
  const isSUS = material.toUpperCase().includes('SUS');
  const density = isSUS ? SUS_DENSITY : EGI_DENSITY;
  const volume_cm3 = (thickness * width * height) / 1000;
  const weight_kg = (volume_cm3 * density) / 1000;
  return {
    weight: Math.round(weight_kg * 100) / 100,
    type: isSUS ? 'SUS' : 'EGI',
  };
}

// ============================================================
// 제품코드별 재질 매핑 (PHP Lines 330-366)
// ============================================================

export function getMaterialMapping(productCode: string, finishMaterial: string): MaterialMapping {
  // Group 1: KQTS01, KSS01, KSS02 - SUS 전용 제품
  if (['KQTS01', 'KSS01', 'KSS02'].includes(productCode)) {
    return {
      guideRailFinish: 'SUS 1.2T',
      bodyMaterial: 'EGI 1.55T',
      guideRailExtraFinish: '',
      bottomBarFinish: 'SUS 1.5T',
      bottomBarExtraFinish: '없음',
    };
  }
  // Group 2: KTE01 - 마감유형에 따라 분기
  if (productCode === 'KTE01') {
    const isSUS = finishMaterial === 'SUS마감';
    return {
      guideRailFinish: 'EGI 1.55T',
      bodyMaterial: 'EGI 1.55T',
      guideRailExtraFinish: isSUS ? 'SUS 1.2T' : '',
      bottomBarFinish: 'EGI 1.55T',
      bottomBarExtraFinish: isSUS ? 'SUS 1.2T' : '없음',
    };
  }
  // 기타 제품코드 (KSE01, KWE01 등) - EGI 기본, 마감유형에 따라 SUS 추가
  const isSUS = finishMaterial?.includes('SUS');
  return {
    guideRailFinish: 'EGI 1.55T',
    bodyMaterial: 'EGI 1.55T',
    guideRailExtraFinish: isSUS ? 'SUS 1.2T' : '',
    bottomBarFinish: 'EGI 1.55T',
    bottomBarExtraFinish: isSUS ? 'SUS 1.2T' : '없음',
  };
}

// ============================================================
// 이미지 URL 빌더
// ============================================================

const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://api.sam.kr';

/**
 * 절곡 이미지 키 생성 (bending_images 맵 조회용)
 */
export function getBendingImageKey(
  category: 'guiderail' | 'bottombar' | 'smokebarrier' | 'box',
  productCode: string,
  type?: 'wall' | 'side' | 'both' | 'bottom' | 'rear',
): string {
  switch (category) {
    case 'guiderail': {
      const isLargeProfile = ['KQTS01', 'KTE01'].includes(productCode);
      const size = isLargeProfile
        ? (type === 'wall' ? '130x75' : '130x125')
        : (type === 'wall' ? '120x70' : '120x120');
      return `guiderail_${productCode}_${type}_${size}`;
    }
    case 'bottombar':
      return `bottombar_${productCode}`;
    case 'smokebarrier':
      return 'smokeban';
    case 'box':
      return `box_${type || 'both'}`;
    default:
      return '';
  }
}

export function getBendingImageUrl(
  category: 'guiderail' | 'bottombar' | 'smokebarrier' | 'box',
  productCode: string,
  type?: 'wall' | 'side' | 'both' | 'bottom' | 'rear',
  bendingImages?: Record<string, string>,
): string {
  const key = getBendingImageKey(category, productCode, type);
  if (bendingImages?.[key]) return bendingImages[key];
  // fallback: API 서버 직접 (레거시)
  switch (category) {
    case 'guiderail': {
      const isLargeProfile = ['KQTS01', 'KTE01'].includes(productCode);
      const size = isLargeProfile
        ? (type === 'wall' ? '130x75' : '130x125')
        : (type === 'wall' ? '120x70' : '120x120');
      return `${API_BASE}/images/bending/guiderail/guiderail_${productCode}_${type}_${size}.jpg`;
    }
    case 'bottombar':
      return `${API_BASE}/images/bending/bottombar/bottombar_${productCode}.jpg`;
    case 'smokebarrier':
      return `${API_BASE}/images/bending/part/smokeban.jpg`;
    case 'box':
      return `${API_BASE}/images/bending/box/box_${type || 'both'}.jpg`;
    default:
      return '';
  }
}

// ============================================================
// getSLengthCode (PHP Lines 56-100)
// ============================================================

export function getSLengthCode(length: number, category: string): string | null {
  if (category === '연기차단재50') {
    return length === 3000 ? '53' : length === 4000 ? '54' : null;
  }
  if (category === '연기차단재80') {
    return length === 3000 ? '83' : length === 4000 ? '84' : null;
  }
  const map: Record<number, string> = {
    1219: '12', 2438: '24', 3000: '30', 3500: '35',
    4000: '40', 4150: '41', 4200: '42', 4300: '43',
  };
  return map[length] || null;
}

// ============================================================
// 가이드레일 파트 행 생성
// ============================================================

/**
 * 벽면형 가이드레일 파트 행 생성
 * 파트: ①②마감재, ③본체, ④C형, ⑤D형, (⑥별도마감), 하부BASE
 */
export function buildWallGuideRailRows(
  lengthData: LengthQuantity[],
  baseDimension: string,
  mapping: MaterialMapping,
  productCode?: string,
): GuideRailPartRow[] {
  const rows: GuideRailPartRow[] = [];
  const codePrefix = productCode?.replace(/\d+$/, '') || 'KSS';
  const isSteel = codePrefix === 'KTE';
  const isSUS = ['KSS', 'KQTS', 'KTE'].includes(codePrefix);
  const finishPrefix = isSUS ? 'RS' : 'RE';
  const bodyPrefix = isSteel ? 'RT' : 'RM';

  for (const ld of lengthData) {
    if (ld.quantity <= 0) continue;

    // ①②마감재
    const finishW = calcWeight(mapping.guideRailFinish, WALL_PART_WIDTH, ld.length);
    rows.push({
      partName: '①②마감재', lotPrefix: finishPrefix, material: mapping.guideRailFinish,
      length: ld.length, quantity: ld.quantity, weight: Math.round(finishW.weight * ld.quantity * 100) / 100,
    });

    // ③본체
    const bodyW = calcWeight(mapping.bodyMaterial, WALL_PART_WIDTH, ld.length);
    rows.push({
      partName: '③본체', lotPrefix: bodyPrefix, material: mapping.bodyMaterial,
      length: ld.length, quantity: ld.quantity, weight: Math.round(bodyW.weight * ld.quantity * 100) / 100,
    });

    // ④C형
    rows.push({
      partName: '④C형', lotPrefix: 'RC', material: mapping.bodyMaterial,
      length: ld.length, quantity: ld.quantity, weight: Math.round(bodyW.weight * ld.quantity * 100) / 100,
    });

    // ⑤D형
    rows.push({
      partName: '⑤D형', lotPrefix: 'RD', material: mapping.bodyMaterial,
      length: ld.length, quantity: ld.quantity, weight: Math.round(bodyW.weight * ld.quantity * 100) / 100,
    });

    // ⑥별도마감 (SUS마감 시만)
    if (mapping.guideRailExtraFinish) {
      const extraW = calcWeight(mapping.guideRailExtraFinish, WALL_PART_WIDTH, ld.length);
      rows.push({
        partName: '⑥별도마감', lotPrefix: 'YY', material: mapping.guideRailExtraFinish,
        length: ld.length, quantity: ld.quantity, weight: Math.round(extraW.weight * ld.quantity * 100) / 100,
      });
    }
  }

  // 하부BASE (길이 데이터와 무관하게 1행)
  // baseDimension: "135*130" (KQTS01/KTE01) 또는 "135*80" (기타)
  const totalQty = lengthData.reduce((sum, ld) => sum + ld.quantity, 0);
  if (totalQty > 0) {
    const { width: bw, height: bh } = parseBaseDimension(baseDimension, WALL_BASE_HEIGHT);
    const baseW = calcWeight('EGI 1.55T', bw, bh);
    rows.push({
      partName: '하부BASE', lotPrefix: 'XX', material: 'EGI 1.55T',
      length: 0, quantity: totalQty, weight: Math.round(baseW.weight * totalQty * 100) / 100,
    });
  }

  return rows;
}

/**
 * 측면형 가이드레일 파트 행 생성
 */
export function buildSideGuideRailRows(
  lengthData: LengthQuantity[],
  baseDimension: string,
  mapping: MaterialMapping,
  productCode?: string,
): GuideRailPartRow[] {
  const rows: GuideRailPartRow[] = [];
  const codePrefix = productCode?.replace(/\d+$/, '') || 'KSS';
  const isSteel = codePrefix === 'KTE';
  const isSUS = ['KSS', 'KQTS', 'KTE'].includes(codePrefix);
  const finishPrefix = isSUS ? 'SS' : 'SE';
  const bodyPrefix = isSteel ? 'ST' : 'SM';

  for (const ld of lengthData) {
    if (ld.quantity <= 0) continue;

    const finishW = calcWeight(mapping.guideRailFinish, SIDE_PART_WIDTH, ld.length);
    rows.push({
      partName: '①②마감재', lotPrefix: finishPrefix, material: mapping.guideRailFinish,
      length: ld.length, quantity: ld.quantity, weight: Math.round(finishW.weight * ld.quantity * 100) / 100,
    });

    const bodyW = calcWeight(mapping.bodyMaterial, SIDE_PART_WIDTH, ld.length);
    rows.push({
      partName: '③본체', lotPrefix: bodyPrefix, material: mapping.bodyMaterial,
      length: ld.length, quantity: ld.quantity, weight: Math.round(bodyW.weight * ld.quantity * 100) / 100,
    });

    rows.push({
      partName: '④C형', lotPrefix: 'SC', material: mapping.bodyMaterial,
      length: ld.length, quantity: ld.quantity, weight: Math.round(bodyW.weight * ld.quantity * 100) / 100,
    });

    rows.push({
      partName: '⑤D형', lotPrefix: 'SD', material: mapping.bodyMaterial,
      length: ld.length, quantity: ld.quantity, weight: Math.round(bodyW.weight * ld.quantity * 100) / 100,
    });
  }

  // 하부BASE
  // baseDimension: "135*130" (측면형은 항상 135*130)
  const totalQty = lengthData.reduce((sum, ld) => sum + ld.quantity, 0);
  if (totalQty > 0) {
    const { width: bw, height: bh } = parseBaseDimension(baseDimension, SIDE_BASE_HEIGHT);
    const baseW = calcWeight('EGI 1.55T', bw, bh);
    rows.push({
      partName: '하부BASE', lotPrefix: 'XX', material: 'EGI 1.55T',
      length: 0, quantity: totalQty, weight: Math.round(baseW.weight * totalQty * 100) / 100,
    });
  }

  return rows;
}

// ============================================================
// 하단마감재 파트 행 생성
// ============================================================

export function buildBottomBarRows(
  bottomBar: BendingInfoExtended['bottomBar'],
  mapping: MaterialMapping,
  productCode?: string,
): BottomBarPartRow[] {
  const rows: BottomBarPartRow[] = [];
  if (!bottomBar) return rows;
  const codePrefix = productCode?.replace(/\d+$/, '') || 'KSS';
  const isSteel = codePrefix === 'KTE';
  const lotPrefix = isSteel ? 'TS' : (mapping.bottomBarFinish.includes('SUS') ? 'BS' : 'BE');

  // ①하단마감재 - 3000mm
  if (bottomBar.length3000Qty > 0) {
    const w = calcWeight(mapping.bottomBarFinish, BOTTOM_BAR_WIDTH, 3000);
    rows.push({
      partName: '①하단마감재', lotPrefix, material: mapping.bottomBarFinish,
      length: 3000, quantity: bottomBar.length3000Qty,
      weight: Math.round(w.weight * bottomBar.length3000Qty * 100) / 100,
    });
  }

  // ①하단마감재 - 4000mm
  if (bottomBar.length4000Qty > 0) {
    const w = calcWeight(mapping.bottomBarFinish, BOTTOM_BAR_WIDTH, 4000);
    rows.push({
      partName: '①하단마감재', lotPrefix, material: mapping.bottomBarFinish,
      length: 4000, quantity: bottomBar.length4000Qty,
      weight: Math.round(w.weight * bottomBar.length4000Qty * 100) / 100,
    });
  }

  // ④별도마감재 (extraFinish !== '없음' 일 때만)
  if (mapping.bottomBarExtraFinish !== '없음' && mapping.bottomBarExtraFinish) {
    const extraLotPrefix = 'YY';

    if (bottomBar.length3000Qty > 0) {
      const w = calcWeight(mapping.bottomBarExtraFinish, EXTRA_FINISH_WIDTH, 3000);
      rows.push({
        partName: '④별도마감재', lotPrefix: extraLotPrefix, material: mapping.bottomBarExtraFinish,
        length: 3000, quantity: bottomBar.length3000Qty,
        weight: Math.round(w.weight * bottomBar.length3000Qty * 100) / 100,
      });
    }
    if (bottomBar.length4000Qty > 0) {
      const w = calcWeight(mapping.bottomBarExtraFinish, EXTRA_FINISH_WIDTH, 4000);
      rows.push({
        partName: '④별도마감재', lotPrefix: extraLotPrefix, material: mapping.bottomBarExtraFinish,
        length: 4000, quantity: bottomBar.length4000Qty,
        weight: Math.round(w.weight * bottomBar.length4000Qty * 100) / 100,
      });
    }
  }

  return rows;
}

// ============================================================
// 셔터박스 구성요소 행 생성 (방향별)
// ============================================================

function parseBoxSize(size: string): { width: number; height: number } {
  const parts = size.split('*').map(Number);
  return { width: parts[0] || 500, height: parts[1] || 380 };
}

export function buildShutterBoxRows(box: ShutterBoxData): ShutterBoxPartRow[] {
  const rows: ShutterBoxPartRow[] = [];
  const { width: boxWidth, height: boxHeight } = parseBoxSize(box.size);
  const isStandard = box.size === '500*380';

  // 방향별 파트 정의
  let parts: { name: string; prefix: string; dim: number }[];

  if (isStandard) {
    parts = [
      { name: '①전면부', prefix: 'CF', dim: boxHeight + 122 },
      { name: '②린텔부', prefix: 'CL', dim: boxWidth - 330 },
      { name: '③⑤점검구', prefix: 'CP', dim: boxWidth - 200 },
      { name: '④후면코너부', prefix: 'CB', dim: 170 },
    ];
  } else if (box.direction === '양면') {
    parts = [
      { name: '①전면부', prefix: 'XX', dim: boxHeight + 122 },
      { name: '②린텔부', prefix: 'CL', dim: boxWidth - 330 },
      { name: '③점검구', prefix: 'XX', dim: boxWidth - 200 },
      { name: '④후면코너부', prefix: 'CB', dim: 170 },
      { name: '⑤점검구', prefix: 'XX', dim: boxHeight - 100 },
    ];
  } else if (box.direction === '밑면') {
    parts = [
      { name: '①전면부', prefix: 'XX', dim: boxHeight + 122 },
      { name: '②린텔부', prefix: 'CL', dim: boxWidth - 330 },
      { name: '③점검구', prefix: 'XX', dim: boxWidth - 200 },
      { name: '④후면부', prefix: 'CB', dim: boxHeight + 85 * 2 },
    ];
  } else if (box.direction === '후면') {
    parts = [
      { name: '①전면부', prefix: 'XX', dim: boxHeight + 122 },
      { name: '②린텔부', prefix: 'CL', dim: boxWidth + 85 * 2 },
      { name: '③점검구', prefix: 'XX', dim: boxHeight - 200 },
      { name: '④후면코너부', prefix: 'CB', dim: boxHeight + 85 * 2 },
    ];
  } else {
    parts = [];
  }

  // 구성요소 기준 정렬: 파트 → 길이 순서
  for (const p of parts) {
    for (const ld of box.lengthData) {
      if (ld.quantity <= 0) continue;
      const w = calcWeight(BOX_FINISH_MATERIAL, p.dim, ld.length);
      rows.push({
        partName: p.name, lotPrefix: p.prefix, material: BOX_FINISH_MATERIAL,
        dimension: `${ld.length}`, quantity: ld.quantity,
        weight: Math.round(w.weight * ld.quantity * 100) / 100,
      });
    }
  }

  // 상부덮개
  if (box.coverQty > 0) {
    const coverWidth = boxWidth - 111;
    const w = calcWeight(BOX_FINISH_MATERIAL, coverWidth, BOX_COVER_LENGTH);
    rows.push({
      partName: isStandard ? '⑤상부덮개' : (box.direction === '양면' ? '⑥상부덮개' : '⑤상부덮개'),
      lotPrefix: 'XX', material: BOX_FINISH_MATERIAL,
      dimension: `1219 * ${coverWidth}`, quantity: box.coverQty,
      weight: Math.round(w.weight * box.coverQty * 100) / 100,
    });
  }

  // 마구리 (레거시: 무게는 원본 box 크기로 계산, 표시 치수만 +5)
  if (box.finCoverQty > 0) {
    const w = calcWeight(BOX_FINISH_MATERIAL, boxWidth, boxHeight);
    rows.push({
      partName: isStandard ? '⑥측면부(마구리)' : (box.direction === '양면' ? '⑦측면부(마구리)' : '⑥측면부(마구리)'),
      lotPrefix: 'XX', material: BOX_FINISH_MATERIAL,
      dimension: `${boxWidth + 5} * ${boxHeight + 5}`, quantity: box.finCoverQty,
      weight: Math.round(w.weight * box.finCoverQty * 100) / 100,
    });
  }

  return rows;
}

// ============================================================
// 연기차단재 파트 행 생성
// ============================================================

export function buildSmokeBarrierRows(
  smokeBarrier: BendingInfoExtended['smokeBarrier'],
): SmokeBarrierPartRow[] {
  const rows: SmokeBarrierPartRow[] = [];
  if (!smokeBarrier) return rows;

  // 레일용 W50
  for (const ld of (smokeBarrier.w50 ?? [])) {
    if (ld.quantity <= 0) continue;
    const w = calcWeight('EGI 0.8T', SMOKE_BARRIER_WIDTH, ld.length);
    const code = getSLengthCode(ld.length, '연기차단재50');
    rows.push({
      partName: '레일용 [W50]', material: 'EGI 0.8T',
      length: ld.length, quantity: ld.quantity,
      weight: Math.round(w.weight * ld.quantity * 100) / 100,
      lotCode: code ? `GI-${code}` : 'GI',
    });
  }

  // 케이스용 W80
  if ((smokeBarrier.w80Qty ?? 0) > 0) {
    const w = calcWeight('EGI 0.8T', SMOKE_BARRIER_WIDTH, 3000);
    const code = getSLengthCode(3000, '연기차단재80');
    rows.push({
      partName: '케이스용 [W80]', material: 'EGI 0.8T',
      length: 3000, quantity: smokeBarrier.w80Qty,
      weight: Math.round(w.weight * smokeBarrier.w80Qty * 100) / 100,
      lotCode: code ? `GI-${code}` : 'GI',
    });
  }

  return rows;
}

// ============================================================
// 생산량 합계 계산
// ============================================================

export function calculateProductionSummary(
  bendingInfo: BendingInfoExtended,
  mapping: MaterialMapping,
): ProductionSummary {
  let susTotal = 0;
  let egiTotal = 0;

  function addWeight(material: string, width: number, height: number, qty: number) {
    if (qty <= 0) return;
    const { weight, type } = calcWeight(material, width, height);
    const total = weight * qty;
    if (type === 'SUS') susTotal += total;
    else egiTotal += total;
  }

  // 가이드레일 - 벽면형
  if (bendingInfo.guideRail?.wall) {
    for (const ld of bendingInfo.guideRail.wall.lengthData) {
      if (ld.quantity <= 0) continue;
      addWeight(mapping.guideRailFinish, WALL_PART_WIDTH, ld.length, ld.quantity);
      addWeight(mapping.bodyMaterial, WALL_PART_WIDTH, ld.length, ld.quantity * 3); // 본체+C형+D형
      if (mapping.guideRailExtraFinish) {
        addWeight(mapping.guideRailExtraFinish, WALL_PART_WIDTH, ld.length, ld.quantity);
      }
    }
    const totalWallQty = bendingInfo.guideRail.wall.lengthData.reduce((s, l) => s + l.quantity, 0);
    const wallBase = parseBaseDimension(bendingInfo.guideRail.wall.baseDimension, WALL_BASE_HEIGHT);
    addWeight('EGI 1.55T', wallBase.width, wallBase.height, totalWallQty);
  }

  // 가이드레일 - 측면형
  if (bendingInfo.guideRail?.side) {
    for (const ld of bendingInfo.guideRail.side.lengthData) {
      if (ld.quantity <= 0) continue;
      addWeight(mapping.guideRailFinish, SIDE_PART_WIDTH, ld.length, ld.quantity);
      addWeight(mapping.bodyMaterial, SIDE_PART_WIDTH, ld.length, ld.quantity * 3);
    }
    const totalSideQty = bendingInfo.guideRail.side.lengthData.reduce((s, l) => s + l.quantity, 0);
    const sideBase = parseBaseDimension(bendingInfo.guideRail.side.baseDimension, SIDE_BASE_HEIGHT);
    addWeight('EGI 1.55T', sideBase.width, sideBase.height, totalSideQty);
  }

  // 하단마감재
  if (bendingInfo.bottomBar?.length3000Qty > 0) {
    addWeight(mapping.bottomBarFinish, BOTTOM_BAR_WIDTH, 3000, bendingInfo.bottomBar.length3000Qty);
    if (mapping.bottomBarExtraFinish !== '없음' && mapping.bottomBarExtraFinish) {
      addWeight(mapping.bottomBarExtraFinish, EXTRA_FINISH_WIDTH, 3000, bendingInfo.bottomBar.length3000Qty);
    }
  }
  if (bendingInfo.bottomBar?.length4000Qty > 0) {
    addWeight(mapping.bottomBarFinish, BOTTOM_BAR_WIDTH, 4000, bendingInfo.bottomBar.length4000Qty);
    if (mapping.bottomBarExtraFinish !== '없음' && mapping.bottomBarExtraFinish) {
      addWeight(mapping.bottomBarExtraFinish, EXTRA_FINISH_WIDTH, 4000, bendingInfo.bottomBar.length4000Qty);
    }
  }

  // 셔터박스
  for (const box of (bendingInfo.shutterBox ?? [])) {
    const boxRows = buildShutterBoxRows(box);
    for (const row of boxRows) {
      if (row.weight > 0) egiTotal += row.weight; // 셔터박스는 항상 EGI
    }
  }

  // 연기차단재
  const smokeRows = buildSmokeBarrierRows(bendingInfo.smokeBarrier);
  for (const row of smokeRows) {
    if (row.weight > 0) egiTotal += row.weight; // 연기차단재는 항상 EGI
  }

  susTotal = Math.round(susTotal * 100) / 100;
  egiTotal = Math.round(egiTotal * 100) / 100;

  return {
    susTotal,
    egiTotal,
    grandTotal: Math.round((susTotal + egiTotal) * 100) / 100,
  };
}

// ============================================================
// 숫자 포맷 헬퍼
// ============================================================

export function fmt(v?: number): string {
  return v != null && v > 0 ? v.toLocaleString() : '-';
}

export function fmtWeight(v: number): string {
  return v > 0 ? v.toFixed(2) : '-';
}

/** 길이(mm) → 길이코드 변환 (PrefixResolver.lengthToCode 프론트 버전) */
export function lengthToCode(lengthMm: number): string {
  return getSLengthCode(lengthMm, '') || String(lengthMm);
}

/**
 * lotNoMap에서 LOT NO 조회
 *
 * bending_info의 길이와 실제 자재투입 길이가 다를 수 있으므로,
 * 정확한 매칭 실패 시 prefix만으로 fallback 매칭합니다.
 *
 * @param lotNoMap - item_code → lot_no 매핑 (e.g. 'BD-SS-35' → 'INIT-260221-BDSS35')
 * @param prefix - 세부품목 prefix (e.g. 'SS', 'SM', 'BS')
 * @param length - 길이(mm), optional
 */
export function lookupLotNo(
  lotNoMap: Record<string, string> | undefined,
  prefix: string,
  length?: number,
): string {
  if (!lotNoMap) return '-';

  // 1. 정확한 매칭 (prefix + lengthCode)
  if (length) {
    const code = lengthToCode(length);
    const exact = lotNoMap[`BD-${prefix}-${code}`];
    if (exact) return exact;
  }

  // 2. Fallback: prefix만으로 매칭 (첫 번째 일치 항목)
  const prefixKey = `BD-${prefix}-`;
  const fallbackKey = Object.keys(lotNoMap).find(k => k.startsWith(prefixKey));
  if (fallbackKey) return lotNoMap[fallbackKey];

  // 3. STOCK 원자재 LOT fallback (와일드카드 또는 _rawMaterialLot)
  const wildcard = lotNoMap[`BD-${prefix}-*`];
  if (wildcard) return wildcard;
  return lotNoMap['_rawMaterialLot'] || '-';
}
