// ============================================================
// 생산관리 > 반제품소요량 + 원자재소요량 — wace productionplanning.xml 1:1.
//
// 매퍼 매핑:
//   getMbomListWithPartName       → getMbomOptions()        (4017)
//   getMbomSemiProductItems       → getSemiRequirement()    (5252)
//   getMbomRawMaterialItems       → getRawRequirement()     (5273) 1차 — 구매품
//   getMbomRawSourceItems         → getRawRequirement()     (5298) 2차 — 원소재
//
// 서비스 로직(ProductionPlanningService.java:2030~2270) 의 LinkedHashMap 합산을
// 자바스크립트 Map 으로 1:1 이식.
// ============================================================

import { getPool } from "../database/db";

// ─── 입력 / 출력 ────────────────────────────────────────────

export interface MbomRequirementInputItem {
  mbomObjid: string;
  qty: number | string;
}

export interface SemiRequirementRow {
  PART_NO: string;
  PART_NAME: string;
  CATEGORY_NAME: string;
  UNIT: string;
  MATERIAL: string;
  SPEC: string;
  REQUIRED_QTY: number;
}

export interface RawRequirementRow {
  PART_NO: string;
  PART_NAME: string;
  CATEGORY_NAME: string; // '구매품' | '원소재'
  UNIT: string;
  MATERIAL: string;
  SPEC: string;
  REQUIRED_QTY: number | string;
  RAW_MATERIAL: string;
  RAW_MATERIAL_SIZE: string;
  MATERIAL_PART_NO: string;
  MATERIAL_REQUIRED_QTY: number | string;
}

function toInt(v: any): number {
  if (v == null) return 0;
  if (typeof v === "number") return Math.trunc(v);
  const n = Number(String(v));
  return Number.isFinite(n) ? Math.trunc(n) : 0;
}
function toNum(v: any): number {
  if (v == null) return 0;
  if (typeof v === "number") return v;
  const n = Number(String(v));
  return Number.isFinite(n) ? n : 0;
}

// ─── M-BOM 옵션 조회 (셀렉트박스용 + 품명 매핑) ────────────────

export async function getMbomOptions(): Promise<Array<{ objid: string; mbom_no: string; part_name: string }>> {
  const pool = getPool();
  const r = await pool.query(`
    SELECT OBJID::VARCHAR AS objid,
           COALESCE(MBOM_NO, '')  AS mbom_no,
           COALESCE(PART_NAME, '') AS part_name
      FROM MBOM_HEADER
     WHERE STATUS = 'Y'
     ORDER BY REGDATE DESC, MBOM_NO
  `);
  return r.rows;
}

// ─── 메뉴 3: 반제품 소요량 ──────────────────────────────────
//
// 매퍼: getMbomSemiProductItems (5252~5270)
// 조건: MBOM_DETAIL × PART_MNG, PART_TYPE IN ('0001812', '0001813'), 1레벨 제외.
// 자바 서비스: 동일 PART_NO 합산 (입력수량 × 항목수량).

export async function getSemiRequirement(items: MbomRequirementInputItem[]): Promise<SemiRequirementRow[]> {
  if (!Array.isArray(items) || items.length === 0) return [];
  const pool = getPool();

  // PART_NO 기준 LinkedHashMap (자바 LinkedHashMap 동일 보장)
  const partMap = new Map<string, SemiRequirementRow>();

  for (const it of items) {
    const mbomObjid = String(it?.mbomObjid ?? "").trim();
    const inputQty = toInt(it?.qty);
    if (!mbomObjid || inputQty <= 0) continue;

    const r = await pool.query(
      `
      SELECT
        MD.PART_NO,
        MD.PART_NAME,
        COALESCE(MD.QTY, 1)::INTEGER AS ITEM_QTY,
        P.PART_TYPE,
        COALESCE((SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = P.PART_TYPE LIMIT 1), '') AS CATEGORY_NAME,
        COALESCE(P.UNIT, '')     AS UNIT,
        COALESCE(P.MATERIAL, '') AS MATERIAL,
        COALESCE(P.SPEC, '')     AS SPEC
      FROM MBOM_DETAIL MD
      INNER JOIN PART_MNG P ON P.OBJID = MD.PART_OBJID
      WHERE MD.MBOM_HEADER_OBJID = $1
        AND MD.STATUS = 'ACTIVE'
        AND (MD.PARENT_OBJID IS NOT NULL AND MD.PARENT_OBJID != '')
        AND P.PART_TYPE IN ('0001812', '0001813')
      ORDER BY P.PART_TYPE, MD.PART_NO
      `,
      [mbomObjid],
    );

    for (const row of r.rows) {
      const partNo = String(row.part_no ?? "").trim();
      if (!partNo) continue;
      const itemQty = toInt(row.item_qty);
      const required = inputQty * itemQty;
      const existing = partMap.get(partNo);
      if (existing) {
        existing.REQUIRED_QTY += required;
      } else {
        partMap.set(partNo, {
          PART_NO: partNo,
          PART_NAME: row.part_name ?? "",
          CATEGORY_NAME: row.category_name ?? "",
          UNIT: row.unit ?? "",
          MATERIAL: row.material ?? "",
          SPEC: row.spec ?? "",
          REQUIRED_QTY: required,
        });
      }
    }
  }

  return Array.from(partMap.values());
}

// ─── 메뉴 4: 원자재 소요량 ──────────────────────────────────
//
// 매퍼: getMbomRawMaterialItems (5273~5295) + getMbomRawSourceItems (5298~5318)
// 자바 서비스: 구매품/원소재 두 LinkedHashMap. 원소재는 소수점 합산 후 올림.

export async function getRawRequirement(items: MbomRequirementInputItem[]): Promise<RawRequirementRow[]> {
  if (!Array.isArray(items) || items.length === 0) return [];
  const pool = getPool();

  // 운영판 1:1 — 두 갈래 LinkedHashMap (구매품 / 원소재)
  const purchaseMap = new Map<string, RawRequirementRow>();
  // 원소재는 소수점 합산을 위해 임시 number 보관 후 마지막에 올림 처리
  const rawSourceMap = new Map<string, RawRequirementRow & { __rawSum: number }>();

  for (const it of items) {
    const mbomObjid = String(it?.mbomObjid ?? "").trim();
    const inputQty = toInt(it?.qty);
    if (!mbomObjid || inputQty <= 0) continue;

    // 1) 구매품 (PART_TYPE = '0000063')
    const r1 = await pool.query(
      `
      SELECT
        MD.PART_NO,
        MD.PART_NAME,
        COALESCE(MD.QTY, 1)::INTEGER AS ITEM_QTY,
        COALESCE((SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = P.PART_TYPE LIMIT 1), '') AS CATEGORY_NAME,
        COALESCE(P.UNIT, '') AS UNIT
      FROM MBOM_DETAIL MD
      INNER JOIN PART_MNG P ON P.OBJID = MD.PART_OBJID
      WHERE MD.MBOM_HEADER_OBJID = $1
        AND MD.STATUS = 'ACTIVE'
        AND (MD.PARENT_OBJID IS NOT NULL AND MD.PARENT_OBJID != '')
        AND P.PART_TYPE = '0000063'
      ORDER BY MD.PART_NO
      `,
      [mbomObjid],
    );

    for (const row of r1.rows) {
      const partNo = String(row.part_no ?? "").trim();
      if (!partNo) continue;
      const itemQty = toInt(row.item_qty);
      const required = inputQty * itemQty;
      const existing = purchaseMap.get(partNo);
      if (existing) {
        existing.REQUIRED_QTY = toInt(existing.REQUIRED_QTY) + required;
      } else {
        purchaseMap.set(partNo, {
          PART_NO: partNo,
          PART_NAME: row.part_name ?? "",
          CATEGORY_NAME: row.category_name ?? "",
          UNIT: row.unit ?? "",
          MATERIAL: "",
          SPEC: "",
          REQUIRED_QTY: required,
          RAW_MATERIAL: "",
          RAW_MATERIAL_SIZE: "",
          MATERIAL_PART_NO: "",
          MATERIAL_REQUIRED_QTY: "",
        });
      }
    }

    // 2) 원소재 (RAW_MATERIAL_PART_NO 가 있는 항목)
    const r2 = await pool.query(
      `
      SELECT
        MD.RAW_MATERIAL_PART_NO AS PART_NO,
        MD.RAW_MATERIAL         AS PART_NAME,
        COALESCE(MD.REQUIRED_QTY, 0)::NUMERIC AS ITEM_QTY,
        MD.RAW_MATERIAL,
        MD.RAW_MATERIAL_SIZE
      FROM MBOM_DETAIL MD
      WHERE MD.MBOM_HEADER_OBJID = $1
        AND MD.STATUS = 'ACTIVE'
        AND MD.RAW_MATERIAL_PART_NO IS NOT NULL
        AND MD.RAW_MATERIAL_PART_NO != ''
      ORDER BY MD.RAW_MATERIAL_PART_NO
      `,
      [mbomObjid],
    );

    for (const row of r2.rows) {
      const materialPartNo = String(row.part_no ?? "").trim();
      if (!materialPartNo) continue;
      const itemQty = toNum(row.item_qty);
      const required = inputQty * itemQty;
      const existing = rawSourceMap.get(materialPartNo);
      if (existing) {
        existing.__rawSum += required;
      } else {
        rawSourceMap.set(materialPartNo, {
          PART_NO: "",
          PART_NAME: "",
          CATEGORY_NAME: "원소재",
          UNIT: "",
          MATERIAL: row.raw_material ?? "",
          SPEC: row.raw_material_size ?? "",
          REQUIRED_QTY: "",
          RAW_MATERIAL: row.raw_material ?? "",
          RAW_MATERIAL_SIZE: row.raw_material_size ?? "",
          MATERIAL_PART_NO: materialPartNo,
          MATERIAL_REQUIRED_QTY: "",
          __rawSum: required,
        });
      }
    }
  }

  // 구매품 먼저, 원소재(올림) 뒤
  const result: RawRequirementRow[] = [];
  for (const v of purchaseMap.values()) result.push(v);
  for (const v of rawSourceMap.values()) {
    const ceilQty = Math.ceil(v.__rawSum);
    const { __rawSum, ...rest } = v;
    result.push({ ...rest, MATERIAL_REQUIRED_QTY: ceilQty });
  }
  return result;
}
