import { Request, Response } from "express";
import { getPool } from "../database/db";
import { logger } from "../utils/logger";
import { AuthenticatedRequest } from "../types/auth";
import {
  syncScreenGroupsToMenu,
  syncMenuToScreenGroups,
  getSyncStatus,
  syncAllCompanies,
} from "../services/menuScreenSyncService";

// pool 인스턴스 가져오기
const pool = getPool();

// ============================================================
// 화면 그룹 (screen_groups) CRUD
// ============================================================

// 화면 그룹 목록 조회
export const getScreenGroups = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const companyCode = req.user?.companyCode || "*";
    const { page = 1, size = 20, searchTerm, excludePop } = req.query;
    const offset = (parseInt(page as string) - 1) * parseInt(size as string);

    let whereClause = "WHERE 1=1";
    const params: any[] = [];
    let paramIndex = 1;

    // 회사 코드 필터링 (멀티테넌시)
    if (companyCode !== "*") {
      whereClause += ` AND company_code = $${paramIndex}`;
      params.push(companyCode);
      paramIndex++;
    }

    // POP 그룹 제외 (PC 화면관리용)
    if (excludePop === "true") {
      whereClause += ` AND (hierarchy_path IS NULL OR (hierarchy_path NOT LIKE 'POP/%' AND hierarchy_path != 'POP'))`;
    }

    // 검색어 필터링
    if (searchTerm) {
      whereClause += ` AND (group_name ILIKE $${paramIndex} OR group_code ILIKE $${paramIndex} OR description ILIKE $${paramIndex})`;
      params.push(`%${searchTerm}%`);
      paramIndex++;
    }

    // 전체 개수 조회
    const countQuery = `SELECT COUNT(*) as total FROM screen_groups ${whereClause}`;
    const countResult = await pool.query(countQuery, params);
    const total = parseInt(countResult.rows[0].total);

    // 데이터 조회 (screens 배열 포함) - 삭제된 화면(is_active = 'D') 제외
    const dataQuery = `
      SELECT 
        sg.*,
        (SELECT COUNT(*) FROM screen_group_screens sgs 
         LEFT JOIN screen_definitions sd ON sgs.screen_id = sd.screen_id
         WHERE sgs.group_id = sg.id AND sd.is_active != 'D') as screen_count,
        (SELECT json_agg(
          json_build_object(
            'id', sgs.id,
            'screen_id', sgs.screen_id,
            'screen_name', sd.screen_name,
            'screen_role', sgs.screen_role,
            'display_order', sgs.display_order,
            'is_default', sgs.is_default,
            'table_name', sd.table_name
          ) ORDER BY sgs.display_order
        ) FROM screen_group_screens sgs 
        LEFT JOIN screen_definitions sd ON sgs.screen_id = sd.screen_id
        WHERE sgs.group_id = sg.id
          AND sd.is_active != 'D'
        ) as screens
      FROM screen_groups sg
      ${whereClause}
      ORDER BY sg.display_order ASC, sg.created_date DESC
      LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
    `;
    params.push(parseInt(size as string), offset);
    
    const result = await pool.query(dataQuery, params);

    logger.info("화면 그룹 목록 조회", { companyCode, total, count: result.rows.length });

    res.json({
      success: true,
      data: result.rows,
      total,
      page: parseInt(page as string),
      size: parseInt(size as string),
      totalPages: Math.ceil(total / parseInt(size as string)),
    });
  } catch (error: any) {
    logger.error("화면 그룹 목록 조회 실패:", error);
    res.status(500).json({ success: false, message: "화면 그룹 목록 조회에 실패했습니다.", error: error.message });
  }
};

// 화면 그룹 상세 조회
export const getScreenGroup = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const companyCode = req.user?.companyCode || "*";

    let query = `
      SELECT sg.*,
        (SELECT json_agg(
          json_build_object(
            'id', sgs.id,
            'screen_id', sgs.screen_id,
            'screen_name', sd.screen_name,
            'screen_role', sgs.screen_role,
            'display_order', sgs.display_order,
            'is_default', sgs.is_default,
            'table_name', sd.table_name
          ) ORDER BY sgs.display_order
        ) FROM screen_group_screens sgs 
        LEFT JOIN screen_definitions sd ON sgs.screen_id = sd.screen_id
        WHERE sgs.group_id = sg.id
          AND sd.is_active != 'D'
        ) as screens
      FROM screen_groups sg
      WHERE sg.id = $1
    `;
    const params: any[] = [id];

    // 멀티테넌시 필터링
    if (companyCode !== "*") {
      query += ` AND sg.company_code = $2`;
      params.push(companyCode);
    }

    const result = await pool.query(query, params);

    if (result.rows.length === 0) {
      return res.status(404).json({ success: false, message: "화면 그룹을 찾을 수 없습니다." });
    }

    res.json({ success: true, data: result.rows[0] });
  } catch (error: any) {
    logger.error("화면 그룹 상세 조회 실패:", error);
    res.status(500).json({ success: false, message: "화면 그룹 조회에 실패했습니다.", error: error.message });
  }
};

// 화면 그룹 생성
export const createScreenGroup = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userCompanyCode = req.user?.companyCode || "*";
    const userId = req.user?.userId || "";
    const { group_name, group_code, main_table_name, description, icon, display_order, is_active, parent_group_id, target_company_code } = req.body;

    if (!group_name || !group_code) {
      return res.status(400).json({ success: false, message: "그룹명과 그룹코드는 필수입니다." });
    }

    // 회사 코드 결정: 최고 관리자가 특정 회사를 선택한 경우 해당 회사로, 아니면 사용자 회사
    let finalCompanyCode = userCompanyCode;
    if (userCompanyCode === "*" && target_company_code) {
      // 최고 관리자가 특정 회사를 선택한 경우
      finalCompanyCode = target_company_code;
    }

    // 부모 그룹이 있으면 group_level과 hierarchy_path 계산
    let groupLevel = 0;
    let parentHierarchyPath = "";
    
    if (parent_group_id) {
      const parentQuery = `SELECT id, group_level, hierarchy_path FROM screen_groups WHERE id = $1`;
      const parentResult = await pool.query(parentQuery, [parent_group_id]);
      if (parentResult.rows.length > 0) {
        groupLevel = (parentResult.rows[0].group_level || 0) + 1;
        parentHierarchyPath = parentResult.rows[0].hierarchy_path || `/${parent_group_id}/`;
      }
    }

    const query = `
      INSERT INTO screen_groups (group_name, group_code, main_table_name, description, icon, display_order, is_active, company_code, writer, parent_group_id, group_level)
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
      RETURNING *
    `;
    const params = [
      group_name,
      group_code,
      main_table_name || null,
      description || null,
      icon || null,
      display_order || 0,
      is_active || 'Y',
      finalCompanyCode,
      userId,
      parent_group_id || null,
      groupLevel
    ];

    const result = await pool.query(query, params);
    const newGroupId = result.rows[0].id;

    // hierarchy_path 업데이트
    const hierarchyPath = parent_group_id 
      ? `${parentHierarchyPath}${newGroupId}/`.replace('//', '/')
      : `/${newGroupId}/`;
    await pool.query(`UPDATE screen_groups SET hierarchy_path = $1 WHERE id = $2`, [hierarchyPath, newGroupId]);

    // 업데이트된 데이터 반환
    const updatedResult = await pool.query(`SELECT * FROM screen_groups WHERE id = $1`, [newGroupId]);

    logger.info("화면 그룹 생성", { userCompanyCode, finalCompanyCode, groupId: newGroupId, groupName: group_name, parentGroupId: parent_group_id });

    res.json({ success: true, data: updatedResult.rows[0], message: "화면 그룹이 생성되었습니다." });
  } catch (error: any) {
    logger.error("화면 그룹 생성 실패:", error);
    if (error.code === '23505') {
      return res.status(400).json({ success: false, message: "이미 존재하는 그룹 코드입니다." });
    }
    res.status(500).json({ success: false, message: "화면 그룹 생성에 실패했습니다.", error: error.message });
  }
};

// 화면 그룹 수정
export const updateScreenGroup = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const userCompanyCode = req.user?.companyCode || "*";
    const { group_name, group_code, main_table_name, description, icon, display_order, is_active, parent_group_id, target_company_code } = req.body;

    // 회사 코드 결정: 최고 관리자가 특정 회사를 선택한 경우 해당 회사로, 아니면 현재 그룹의 회사 유지
    let finalCompanyCode = target_company_code || null;

    // 부모 그룹이 변경되면 group_level과 hierarchy_path 재계산
    let groupLevel = 0;
    let hierarchyPath = `/${id}/`;
    
    if (parent_group_id !== undefined && parent_group_id !== null) {
      // 자기 자신을 부모로 지정하는 것 방지
      if (Number(parent_group_id) === Number(id)) {
        return res.status(400).json({ success: false, message: "자기 자신을 상위 그룹으로 지정할 수 없습니다." });
      }
      
      const parentQuery = `SELECT id, group_level, hierarchy_path FROM screen_groups WHERE id = $1`;
      const parentResult = await pool.query(parentQuery, [parent_group_id]);
      if (parentResult.rows.length > 0) {
        // 순환 참조 방지: 부모의 hierarchy_path에 현재 그룹 ID가 포함되어 있으면 오류
        if (parentResult.rows[0].hierarchy_path && parentResult.rows[0].hierarchy_path.includes(`/${id}/`)) {
          return res.status(400).json({ success: false, message: "하위 그룹을 상위 그룹으로 지정할 수 없습니다." });
        }
        groupLevel = (parentResult.rows[0].group_level || 0) + 1;
        const parentPath = parentResult.rows[0].hierarchy_path || `/${parent_group_id}/`;
        hierarchyPath = `${parentPath}${id}/`.replace('//', '/');
      }
    }

    // 쿼리 구성: 회사 코드 변경 포함 여부
    let query: string;
    let params: any[];
    
    if (userCompanyCode === "*" && finalCompanyCode) {
      // 최고 관리자가 회사를 변경하는 경우
      query = `
        UPDATE screen_groups 
        SET group_name = $1, group_code = $2, main_table_name = $3, description = $4, 
            icon = $5, display_order = $6, is_active = $7, updated_date = NOW(),
            parent_group_id = $8, group_level = $9, hierarchy_path = $10, company_code = $11
        WHERE id = $12
      `;
      params = [
        group_name, group_code, main_table_name, description, icon, display_order, is_active,
        parent_group_id || null, groupLevel, hierarchyPath, finalCompanyCode, id
      ];
    } else {
      // 회사 코드 변경 없음
      query = `
        UPDATE screen_groups 
        SET group_name = $1, group_code = $2, main_table_name = $3, description = $4, 
            icon = $5, display_order = $6, is_active = $7, updated_date = NOW(),
            parent_group_id = $8, group_level = $9, hierarchy_path = $10
        WHERE id = $11
      `;
      params = [
        group_name, group_code, main_table_name, description, icon, display_order, is_active,
        parent_group_id || null, groupLevel, hierarchyPath, id
      ];
    }

    // 멀티테넌시 필터링 (최고 관리자가 아닌 경우)
    if (userCompanyCode !== "*") {
      const paramIndex = params.length + 1;
      query += ` AND company_code = $${paramIndex}`;
      params.push(userCompanyCode);
    }

    query += " RETURNING *";

    const result = await pool.query(query, params);

    if (result.rows.length === 0) {
      return res.status(404).json({ success: false, message: "화면 그룹을 찾을 수 없거나 권한이 없습니다." });
    }

    logger.info("화면 그룹 수정", { userCompanyCode, groupId: id, parentGroupId: parent_group_id, targetCompanyCode: finalCompanyCode });

    res.json({ success: true, data: result.rows[0], message: "화면 그룹이 수정되었습니다." });
  } catch (error: any) {
    logger.error("화면 그룹 수정 실패:", error);
    res.status(500).json({ success: false, message: "화면 그룹 수정에 실패했습니다.", error: error.message });
  }
};

// 화면 그룹 삭제
export const deleteScreenGroup = async (req: AuthenticatedRequest, res: Response) => {
  const client = await pool.connect();
  try {
    const { id } = req.params;
    const deleteNumberingRules = req.query.deleteNumberingRules === "true";
    const companyCode = req.user?.companyCode || "*";

    await client.query('BEGIN');

    // 0. 삭제할 그룹의 company_code 확인
    const targetGroupResult = await client.query(
      `SELECT company_code FROM screen_groups WHERE id = $1`,
      [id]
    );
    if (targetGroupResult.rows.length === 0) {
      await client.query('ROLLBACK');
      return res.status(404).json({ success: false, message: "화면 그룹을 찾을 수 없습니다." });
    }
    const targetCompanyCode = targetGroupResult.rows[0].company_code;

    // 권한 체크: 최고관리자가 아닌 경우 자신의 회사 그룹만 삭제 가능
    if (companyCode !== "*" && targetCompanyCode !== companyCode) {
      await client.query('ROLLBACK');
      return res.status(403).json({ success: false, message: "권한이 없습니다." });
    }

    // 1. 삭제할 그룹과 하위 그룹 ID 수집 (같은 회사만 - CASCADE 삭제 대상)
    const childGroupsResult = await client.query(`
      WITH RECURSIVE child_groups AS (
        SELECT id, company_code FROM screen_groups WHERE id = $1 AND company_code = $2
        UNION ALL
        SELECT sg.id, sg.company_code FROM screen_groups sg
        JOIN child_groups cg ON sg.parent_group_id = cg.id AND sg.company_code = cg.company_code
      )
      SELECT id FROM child_groups
    `, [id, targetCompanyCode]);
    const groupIdsToDelete = childGroupsResult.rows.map((r: any) => r.id);

    logger.info("화면 그룹 삭제 대상", { 
      companyCode, 
      targetCompanyCode,
      groupId: id, 
      childGroupIds: groupIdsToDelete 
    });

    // 2. 삭제될 그룹에 연결된 메뉴 정리
    if (groupIdsToDelete.length > 0) {
      // 2-1. 삭제할 메뉴 objid 수집
      const menusToDelete = await client.query(`
        SELECT objid FROM menu_info 
        WHERE screen_group_id = ANY($1::int[])
        AND company_code = $2
      `, [groupIdsToDelete, targetCompanyCode]);
      const menuObjids = menusToDelete.rows.map((r: any) => r.objid);

      if (menuObjids.length > 0) {
        // 2-2. screen_menu_assignments에서 해당 메뉴 관련 데이터 삭제
        await client.query(`
          DELETE FROM screen_menu_assignments 
          WHERE menu_objid = ANY($1::bigint[])
          AND company_code = $2
        `, [menuObjids, targetCompanyCode]);

        // 2-3. menu_info에서 해당 메뉴 삭제
        await client.query(`
          DELETE FROM menu_info 
          WHERE screen_group_id = ANY($1::int[])
          AND company_code = $2
        `, [groupIdsToDelete, targetCompanyCode]);

        logger.info("그룹 삭제 시 연결된 메뉴 삭제", { 
          groupIds: groupIdsToDelete, 
          deletedMenuCount: menuObjids.length,
          companyCode: targetCompanyCode 
        });
      }

      // 2-4. 해당 회사의 채번 규칙 삭제 (최상위 그룹 삭제 + 사용자가 명시적으로 요청한 경우에만)
      if (deleteNumberingRules) {
        const isRootGroup = await client.query(
          `SELECT 1 FROM screen_groups WHERE id = $1 AND parent_group_id IS NULL`,
          [id]
        );
        
        if (isRootGroup.rows.length > 0) {
          await client.query(
            `DELETE FROM numbering_rule_parts 
             WHERE rule_id IN (SELECT rule_id FROM numbering_rules WHERE company_code = $1)`,
            [targetCompanyCode]
          );
          const deletedRules = await client.query(
            `DELETE FROM numbering_rules WHERE company_code = $1 RETURNING rule_id`,
            [targetCompanyCode]
          );
          if (deletedRules.rowCount && deletedRules.rowCount > 0) {
            logger.warn("최상위 그룹 삭제 시 채번 규칙 삭제 (사용자 명시 요청)", { 
              companyCode: targetCompanyCode, 
              deletedCount: deletedRules.rowCount 
            });
          }
        }
      }
    }

    // 3. screen_groups 삭제 (해당 그룹만 - 하위 그룹은 프론트엔드에서 순차 삭제)
    const result = await client.query(
      `DELETE FROM screen_groups WHERE id = $1 AND company_code = $2 RETURNING id`,
      [id, targetCompanyCode]
    );

    if (result.rows.length === 0) {
      await client.query('ROLLBACK');
      return res.status(404).json({ success: false, message: "화면 그룹을 찾을 수 없거나 권한이 없습니다." });
    }

    await client.query('COMMIT');

    logger.info("화면 그룹 삭제 완료", { companyCode, targetCompanyCode, groupId: id, cleanedRefs: groupIdsToDelete.length });

    res.json({ success: true, message: "화면 그룹이 삭제되었습니다." });
  } catch (error: any) {
    await client.query('ROLLBACK');
    logger.error("화면 그룹 삭제 실패:", error);
    res.status(500).json({ success: false, message: "화면 그룹 삭제에 실패했습니다.", error: error.message });
  } finally {
    client.release();
  }
};


// ============================================================
// 화면-그룹 연결 (screen_group_screens) CRUD
// ============================================================

// 그룹에 화면 추가
export const addScreenToGroup = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userCompanyCode = req.user?.companyCode || "*";
    const userId = req.user?.userId || "";
    const { group_id, screen_id, screen_role, display_order, is_default, target_company_code } = req.body;

    if (!group_id || !screen_id) {
      return res.status(400).json({ success: false, message: "그룹 ID와 화면 ID는 필수입니다." });
    }

    // 최고 관리자가 다른 회사로 복제할 때 target_company_code 사용
    const effectiveCompanyCode = (userCompanyCode === "*" && target_company_code) 
      ? target_company_code 
      : userCompanyCode;

    const query = `
      INSERT INTO screen_group_screens (group_id, screen_id, screen_role, display_order, is_default, company_code, writer)
      VALUES ($1, $2, $3, $4, $5, $6, $7)
      RETURNING *
    `;
    const params = [
      group_id,
      screen_id,
      screen_role || 'main',
      display_order || 0,
      is_default || 'N',
      effectiveCompanyCode,
      userId
    ];

    const result = await pool.query(query, params);

    logger.info("화면-그룹 연결 추가", { companyCode: effectiveCompanyCode, groupId: group_id, screenId: screen_id });

    res.json({ success: true, data: result.rows[0], message: "화면이 그룹에 추가되었습니다." });
  } catch (error: any) {
    logger.error("화면-그룹 연결 추가 실패:", error);
    if (error.code === '23505') {
      return res.status(400).json({ success: false, message: "이미 그룹에 추가된 화면입니다." });
    }
    res.status(500).json({ success: false, message: "화면 추가에 실패했습니다.", error: error.message });
  }
};

// 그룹에서 화면 제거
export const removeScreenFromGroup = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const companyCode = req.user?.companyCode || "*";

    let query = `DELETE FROM screen_group_screens WHERE id = $1`;
    const params: any[] = [id];

    if (companyCode !== "*") {
      query += ` AND company_code = $2`;
      params.push(companyCode);
    }

    query += " RETURNING id";

    const result = await pool.query(query, params);

    if (result.rows.length === 0) {
      return res.status(404).json({ success: false, message: "연결을 찾을 수 없거나 권한이 없습니다." });
    }

    logger.info("화면-그룹 연결 제거", { companyCode, id });

    res.json({ success: true, message: "화면이 그룹에서 제거되었습니다." });
  } catch (error: any) {
    logger.error("화면-그룹 연결 제거 실패:", error);
    res.status(500).json({ success: false, message: "화면 제거에 실패했습니다.", error: error.message });
  }
};

// 그룹 내 화면 순서/역할 수정
export const updateScreenInGroup = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const companyCode = req.user?.companyCode || "*";
    const { screen_role, display_order, is_default } = req.body;

    let query = `
      UPDATE screen_group_screens 
      SET screen_role = $1, display_order = $2, is_default = $3, updated_date = NOW()
      WHERE id = $4
    `;
    const params: any[] = [screen_role, display_order, is_default, id];

    if (companyCode !== "*") {
      query += ` AND company_code = $5`;
      params.push(companyCode);
    }

    query += " RETURNING *";

    const result = await pool.query(query, params);

    if (result.rows.length === 0) {
      return res.status(404).json({ success: false, message: "연결을 찾을 수 없거나 권한이 없습니다." });
    }

    res.json({ success: true, data: result.rows[0], message: "화면 정보가 수정되었습니다." });
  } catch (error: any) {
    logger.error("화면-그룹 연결 수정 실패:", error);
    res.status(500).json({ success: false, message: "화면 정보 수정에 실패했습니다.", error: error.message });
  }
};


// ============================================================
// 화면 필드 조인 설정 (screen_field_joins) CRUD
// ============================================================

// 화면 필드 조인 목록 조회
export const getFieldJoins = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const companyCode = req.user?.companyCode || "*";
    const { screen_id } = req.query;

    let query = `
      SELECT sfj.*,
        tl1.table_label as save_table_label,
        tl2.table_label as join_table_label
      FROM screen_field_joins sfj
      LEFT JOIN table_labels tl1 ON sfj.save_table = tl1.table_name
      LEFT JOIN table_labels tl2 ON sfj.join_table = tl2.table_name
      WHERE 1=1
    `;
    const params: any[] = [];
    let paramIndex = 1;

    if (companyCode !== "*") {
      query += ` AND sfj.company_code = $${paramIndex}`;
      params.push(companyCode);
      paramIndex++;
    }

    if (screen_id) {
      query += ` AND sfj.screen_id = $${paramIndex}`;
      params.push(screen_id);
      paramIndex++;
    }

    query += " ORDER BY sfj.id ASC";

    const result = await pool.query(query, params);

    res.json({ success: true, data: result.rows });
  } catch (error: any) {
    logger.error("필드 조인 목록 조회 실패:", error);
    res.status(500).json({ success: false, message: "필드 조인 목록 조회에 실패했습니다.", error: error.message });
  }
};

// 화면 필드 조인 생성
export const createFieldJoin = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const companyCode = req.user?.companyCode || "*";
    const userId = req.user?.userId || "";
    const { 
      screen_id, layout_id, component_id, field_name,
      save_table, save_column, join_table, join_column, display_column,
      join_type, filter_condition, sort_column, sort_direction, is_active
    } = req.body;

    if (!screen_id || !save_table || !save_column || !join_table || !join_column || !display_column) {
      return res.status(400).json({ success: false, message: "필수 필드가 누락되었습니다." });
    }

    const query = `
      INSERT INTO screen_field_joins (
        screen_id, layout_id, component_id, field_name,
        save_table, save_column, join_table, join_column, display_column,
        join_type, filter_condition, sort_column, sort_direction, is_active, company_code, writer
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
      RETURNING *
    `;
    const params = [
      screen_id, layout_id || null, component_id || null, field_name || null,
      save_table, save_column, join_table, join_column, display_column,
      join_type || 'LEFT', filter_condition || null, sort_column || null, sort_direction || 'ASC',
      is_active || 'Y', companyCode === "*" ? "*" : companyCode, userId
    ];

    const result = await pool.query(query, params);

    logger.info("필드 조인 생성", { companyCode, screenId: screen_id, id: result.rows[0].id });

    res.json({ success: true, data: result.rows[0], message: "필드 조인이 생성되었습니다." });
  } catch (error: any) {
    logger.error("필드 조인 생성 실패:", error);
    res.status(500).json({ success: false, message: "필드 조인 생성에 실패했습니다.", error: error.message });
  }
};

// 화면 필드 조인 수정
export const updateFieldJoin = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const companyCode = req.user?.companyCode || "*";
    const { 
      layout_id, component_id, field_name,
      save_table, save_column, join_table, join_column, display_column,
      join_type, filter_condition, sort_column, sort_direction, is_active
    } = req.body;

    let query = `
      UPDATE screen_field_joins SET
        layout_id = $1, component_id = $2, field_name = $3,
        save_table = $4, save_column = $5, join_table = $6, join_column = $7, display_column = $8,
        join_type = $9, filter_condition = $10, sort_column = $11, sort_direction = $12,
        is_active = $13, updated_date = NOW()
      WHERE id = $14
    `;
    const params: any[] = [
      layout_id, component_id, field_name,
      save_table, save_column, join_table, join_column, display_column,
      join_type, filter_condition, sort_column, sort_direction, is_active, id
    ];

    if (companyCode !== "*") {
      query += ` AND company_code = $15`;
      params.push(companyCode);
    }

    query += " RETURNING *";

    const result = await pool.query(query, params);

    if (result.rows.length === 0) {
      return res.status(404).json({ success: false, message: "필드 조인을 찾을 수 없거나 권한이 없습니다." });
    }

    res.json({ success: true, data: result.rows[0], message: "필드 조인이 수정되었습니다." });
  } catch (error: any) {
    logger.error("필드 조인 수정 실패:", error);
    res.status(500).json({ success: false, message: "필드 조인 수정에 실패했습니다.", error: error.message });
  }
};

// 화면 필드 조인 삭제
export const deleteFieldJoin = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const companyCode = req.user?.companyCode || "*";

    let query = `DELETE FROM screen_field_joins WHERE id = $1`;
    const params: any[] = [id];

    if (companyCode !== "*") {
      query += ` AND company_code = $2`;
      params.push(companyCode);
    }

    query += " RETURNING id";

    const result = await pool.query(query, params);

    if (result.rows.length === 0) {
      return res.status(404).json({ success: false, message: "필드 조인을 찾을 수 없거나 권한이 없습니다." });
    }

    res.json({ success: true, message: "필드 조인이 삭제되었습니다." });
  } catch (error: any) {
    logger.error("필드 조인 삭제 실패:", error);
    res.status(500).json({ success: false, message: "필드 조인 삭제에 실패했습니다.", error: error.message });
  }
};


// ============================================================
// 데이터 흐름 (screen_data_flows) CRUD
// ============================================================

// 데이터 흐름 목록 조회
export const getDataFlows = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const companyCode = req.user?.companyCode || "*";
    const { group_id, source_screen_id } = req.query;

    let query = `
      SELECT sdf.*,
        sd1.screen_name as source_screen_name,
        sd2.screen_name as target_screen_name,
        sg.group_name
      FROM screen_data_flows sdf
      LEFT JOIN screen_definitions sd1 ON sdf.source_screen_id = sd1.screen_id
      LEFT JOIN screen_definitions sd2 ON sdf.target_screen_id = sd2.screen_id
      LEFT JOIN screen_groups sg ON sdf.group_id = sg.id
      WHERE 1=1
    `;
    const params: any[] = [];
    let paramIndex = 1;

    if (companyCode !== "*") {
      query += ` AND sdf.company_code = $${paramIndex}`;
      params.push(companyCode);
      paramIndex++;
    }

    if (group_id) {
      query += ` AND sdf.group_id = $${paramIndex}`;
      params.push(group_id);
      paramIndex++;
    }

    // 특정 화면에서 시작하는 데이터 흐름만 조회
    if (source_screen_id) {
      query += ` AND sdf.source_screen_id = $${paramIndex}`;
      params.push(source_screen_id);
      paramIndex++;
    }

    query += " ORDER BY sdf.id ASC";

    const result = await pool.query(query, params);

    res.json({ success: true, data: result.rows });
  } catch (error: any) {
    logger.error("데이터 흐름 목록 조회 실패:", error);
    res.status(500).json({ success: false, message: "데이터 흐름 목록 조회에 실패했습니다.", error: error.message });
  }
};

// 데이터 흐름 생성
export const createDataFlow = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const companyCode = req.user?.companyCode || "*";
    const userId = req.user?.userId || "";
    const { 
      group_id, source_screen_id, source_action, target_screen_id, target_action,
      data_mapping, flow_type, flow_label, condition_expression, is_active
    } = req.body;

    if (!source_screen_id || !target_screen_id) {
      return res.status(400).json({ success: false, message: "소스 화면과 타겟 화면은 필수입니다." });
    }

    const query = `
      INSERT INTO screen_data_flows (
        group_id, source_screen_id, source_action, target_screen_id, target_action,
        data_mapping, flow_type, flow_label, condition_expression, is_active, company_code, writer
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
      RETURNING *
    `;
    const params = [
      group_id || null, source_screen_id, source_action || null, target_screen_id, target_action || null,
      data_mapping ? JSON.stringify(data_mapping) : null, flow_type || 'unidirectional',
      flow_label || null, condition_expression || null, is_active || 'Y',
      companyCode === "*" ? "*" : companyCode, userId
    ];

    const result = await pool.query(query, params);

    logger.info("데이터 흐름 생성", { companyCode, id: result.rows[0].id });

    res.json({ success: true, data: result.rows[0], message: "데이터 흐름이 생성되었습니다." });
  } catch (error: any) {
    logger.error("데이터 흐름 생성 실패:", error);
    res.status(500).json({ success: false, message: "데이터 흐름 생성에 실패했습니다.", error: error.message });
  }
};

// 데이터 흐름 수정
export const updateDataFlow = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const companyCode = req.user?.companyCode || "*";
    const { 
      group_id, source_screen_id, source_action, target_screen_id, target_action,
      data_mapping, flow_type, flow_label, condition_expression, is_active
    } = req.body;

    let query = `
      UPDATE screen_data_flows SET
        group_id = $1, source_screen_id = $2, source_action = $3, 
        target_screen_id = $4, target_action = $5, data_mapping = $6,
        flow_type = $7, flow_label = $8, condition_expression = $9,
        is_active = $10, updated_date = NOW()
      WHERE id = $11
    `;
    const params: any[] = [
      group_id, source_screen_id, source_action, target_screen_id, target_action,
      data_mapping ? JSON.stringify(data_mapping) : null, flow_type, flow_label, condition_expression, is_active, id
    ];

    if (companyCode !== "*") {
      query += ` AND company_code = $12`;
      params.push(companyCode);
    }

    query += " RETURNING *";

    const result = await pool.query(query, params);

    if (result.rows.length === 0) {
      return res.status(404).json({ success: false, message: "데이터 흐름을 찾을 수 없거나 권한이 없습니다." });
    }

    res.json({ success: true, data: result.rows[0], message: "데이터 흐름이 수정되었습니다." });
  } catch (error: any) {
    logger.error("데이터 흐름 수정 실패:", error);
    res.status(500).json({ success: false, message: "데이터 흐름 수정에 실패했습니다.", error: error.message });
  }
};

// 데이터 흐름 삭제
export const deleteDataFlow = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const companyCode = req.user?.companyCode || "*";

    let query = `DELETE FROM screen_data_flows WHERE id = $1`;
    const params: any[] = [id];

    if (companyCode !== "*") {
      query += ` AND company_code = $2`;
      params.push(companyCode);
    }

    query += " RETURNING id";

    const result = await pool.query(query, params);

    if (result.rows.length === 0) {
      return res.status(404).json({ success: false, message: "데이터 흐름을 찾을 수 없거나 권한이 없습니다." });
    }

    res.json({ success: true, message: "데이터 흐름이 삭제되었습니다." });
  } catch (error: any) {
    logger.error("데이터 흐름 삭제 실패:", error);
    res.status(500).json({ success: false, message: "데이터 흐름 삭제에 실패했습니다.", error: error.message });
  }
};


// ============================================================
// 화면-테이블 관계 (screen_table_relations) CRUD
// ============================================================

// 화면-테이블 관계 목록 조회
export const getTableRelations = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const companyCode = req.user?.companyCode || "*";
    const { screen_id, group_id } = req.query;

    let query = `
      SELECT str.*,
        sd.screen_name,
        sg.group_name,
        tl.table_label
      FROM screen_table_relations str
      LEFT JOIN screen_definitions sd ON str.screen_id = sd.screen_id
      LEFT JOIN screen_groups sg ON str.group_id = sg.id
      LEFT JOIN table_labels tl ON str.table_name = tl.table_name
      WHERE 1=1
    `;
    const params: any[] = [];
    let paramIndex = 1;

    if (companyCode !== "*") {
      query += ` AND str.company_code = $${paramIndex}`;
      params.push(companyCode);
      paramIndex++;
    }

    if (screen_id) {
      query += ` AND str.screen_id = $${paramIndex}`;
      params.push(screen_id);
      paramIndex++;
    }

    if (group_id) {
      query += ` AND str.group_id = $${paramIndex}`;
      params.push(group_id);
      paramIndex++;
    }

    query += " ORDER BY str.id ASC";

    const result = await pool.query(query, params);

    res.json({ success: true, data: result.rows });
  } catch (error: any) {
    logger.error("화면-테이블 관계 목록 조회 실패:", error);
    res.status(500).json({ success: false, message: "화면-테이블 관계 목록 조회에 실패했습니다.", error: error.message });
  }
};

// 화면-테이블 관계 생성
export const createTableRelation = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const companyCode = req.user?.companyCode || "*";
    const userId = req.user?.userId || "";
    const { group_id, screen_id, table_name, relation_type, crud_operations, description, is_active } = req.body;

    if (!screen_id || !table_name) {
      return res.status(400).json({ success: false, message: "화면 ID와 테이블명은 필수입니다." });
    }

    const query = `
      INSERT INTO screen_table_relations (group_id, screen_id, table_name, relation_type, crud_operations, description, is_active, company_code, writer)
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
      RETURNING *
    `;
    const params = [
      group_id || null, screen_id, table_name, relation_type || 'main',
      crud_operations || 'CRUD', description || null, is_active || 'Y',
      companyCode === "*" ? "*" : companyCode, userId
    ];

    const result = await pool.query(query, params);

    logger.info("화면-테이블 관계 생성", { companyCode, screenId: screen_id, tableName: table_name });

    res.json({ success: true, data: result.rows[0], message: "화면-테이블 관계가 생성되었습니다." });
  } catch (error: any) {
    logger.error("화면-테이블 관계 생성 실패:", error);
    res.status(500).json({ success: false, message: "화면-테이블 관계 생성에 실패했습니다.", error: error.message });
  }
};

// 화면-테이블 관계 수정
export const updateTableRelation = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const companyCode = req.user?.companyCode || "*";
    const { group_id, table_name, relation_type, crud_operations, description, is_active } = req.body;

    let query = `
      UPDATE screen_table_relations SET
        group_id = $1, table_name = $2, relation_type = $3, crud_operations = $4,
        description = $5, is_active = $6, updated_date = NOW()
      WHERE id = $7
    `;
    const params: any[] = [group_id, table_name, relation_type, crud_operations, description, is_active, id];

    if (companyCode !== "*") {
      query += ` AND company_code = $8`;
      params.push(companyCode);
    }

    query += " RETURNING *";

    const result = await pool.query(query, params);

    if (result.rows.length === 0) {
      return res.status(404).json({ success: false, message: "화면-테이블 관계를 찾을 수 없거나 권한이 없습니다." });
    }

    res.json({ success: true, data: result.rows[0], message: "화면-테이블 관계가 수정되었습니다." });
  } catch (error: any) {
    logger.error("화면-테이블 관계 수정 실패:", error);
    res.status(500).json({ success: false, message: "화면-테이블 관계 수정에 실패했습니다.", error: error.message });
  }
};

// 화면-테이블 관계 삭제
export const deleteTableRelation = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const companyCode = req.user?.companyCode || "*";

    let query = `DELETE FROM screen_table_relations WHERE id = $1`;
    const params: any[] = [id];

    if (companyCode !== "*") {
      query += ` AND company_code = $2`;
      params.push(companyCode);
    }

    query += " RETURNING id";

    const result = await pool.query(query, params);

    if (result.rows.length === 0) {
      return res.status(404).json({ success: false, message: "화면-테이블 관계를 찾을 수 없거나 권한이 없습니다." });
    }

    res.json({ success: true, message: "화면-테이블 관계가 삭제되었습니다." });
  } catch (error: any) {
    logger.error("화면-테이블 관계 삭제 실패:", error);
    res.status(500).json({ success: false, message: "화면-테이블 관계 삭제에 실패했습니다.", error: error.message });
  }
};

// ============================================================
// 화면 레이아웃 요약 정보 (미리보기용)
// ============================================================

// 화면 레이아웃 요약 조회 (위젯 타입별 개수, 라벨 목록)
export const getScreenLayoutSummary = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { screenId } = req.params;

    // 화면의 컴포넌트 정보 조회
    const query = `
      SELECT 
        properties->>'widgetType' as widget_type,
        properties->>'label' as label,
        properties->>'fieldName' as field_name,
        properties->>'tableName' as table_name
      FROM screen_layouts
      WHERE screen_id = $1
        AND component_type = 'component'
      ORDER BY display_order ASC
    `;
    
    const result = await pool.query(query, [screenId]);
    
    // 위젯 타입별 집계
    const widgetCounts: Record<string, number> = {};
    const labels: string[] = [];
    const fields: Array<{ label: string; widgetType: string; fieldName?: string }> = [];
    
    result.rows.forEach((row: any) => {
      const widgetType = row.widget_type || 'text';
      widgetCounts[widgetType] = (widgetCounts[widgetType] || 0) + 1;
      
      if (row.label && row.label !== '기본 버튼') {
        labels.push(row.label);
        fields.push({
          label: row.label,
          widgetType: widgetType,
          fieldName: row.field_name,
        });
      }
    });

    // 화면 타입 추론 (가장 많은 컴포넌트 기준)
    let screenType = 'form'; // 기본값
    if (widgetCounts['table'] > 0) {
      screenType = 'grid';
    } else if (widgetCounts['custom'] > 2) {
      screenType = 'dashboard';
    } else if (Object.keys(widgetCounts).length <= 2 && widgetCounts['button'] > 0) {
      screenType = 'action';
    }

    logger.info("화면 레이아웃 요약 조회", { screenId, widgetCounts, fieldCount: fields.length });

    res.json({
      success: true,
      data: {
        screenId: parseInt(screenId),
        screenType,
        widgetCounts,
        totalComponents: result.rows.length,
        fields: fields.slice(0, 10), // 최대 10개
        labels: labels.slice(0, 8), // 최대 8개
      },
    });
  } catch (error: any) {
    logger.error("화면 레이아웃 요약 조회 실패:", error);
    res.status(500).json({ success: false, message: "화면 레이아웃 요약 조회에 실패했습니다.", error: error.message });
  }
};

// 여러 화면의 레이아웃 요약 일괄 조회 (미니어처 렌더링용 좌표 포함)
export const getMultipleScreenLayoutSummary = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { screenIds } = req.body;

    if (!screenIds || !Array.isArray(screenIds) || screenIds.length === 0) {
      return res.status(400).json({ success: false, message: "screenIds 배열이 필요합니다." });
    }

    // 여러 화면의 컴포넌트 정보 (좌표 포함) 한번에 조회
    // componentType이 더 정확한 위젯 종류 (table-list, button-primary 등)
    // 다양한 컴포넌트 타입에서 사용 컬럼 추출
    const query = `
      SELECT 
        screen_id,
        component_type,
        position_x,
        position_y,
        width,
        height,
        properties->>'componentType' as component_kind,
        properties->>'widgetType' as widget_type,
        properties->>'label' as label,
        COALESCE(
          properties->'componentConfig'->>'bindField',
          properties->>'bindField',
          properties->'componentConfig'->>'field',
          properties->>'field',
          properties->>'columnName'
        ) as bind_field,
        -- componentConfig 전체 (JavaScript에서 다양한 패턴 파싱용)
        properties->'componentConfig' as component_config
      FROM screen_layouts
      WHERE screen_id = ANY($1)
        AND component_type = 'component'
      ORDER BY screen_id, display_order ASC
    `;
    
    const result = await pool.query(query, [screenIds]);
    
    // 화면별로 그룹핑
    const summaryMap: Record<number, any> = {};
    
    screenIds.forEach((id: number) => {
      summaryMap[id] = {
        screenId: id,
        screenType: 'form',
        widgetCounts: {},
        totalComponents: 0,
        // 미니어처 렌더링용 레이아웃 데이터
        layoutItems: [],
        canvasWidth: 0,
        canvasHeight: 0,
      };
    });

    result.rows.forEach((row: any) => {
      const screenId = row.screen_id;
      // componentKind가 더 정확한 타입 (table-list, button-primary, table-search-widget 등)
      const componentKind = row.component_kind || row.widget_type || 'text';
      const widgetType = row.widget_type || 'text';
      const componentConfig = row.component_config || {};
      
      // 다양한 컴포넌트 타입에서 usedColumns, joinColumns 추출
      let usedColumns: string[] = [];
      let joinColumns: string[] = [];
      
      // 1. 기본 columns 배열에서 추출 (table-list 등)
      if (Array.isArray(componentConfig.columns)) {
        componentConfig.columns.forEach((col: any) => {
          const colName = col.columnName || col.field || col.name;
          if (colName && !usedColumns.includes(colName)) {
            usedColumns.push(colName);
          }
          if (col.isEntityJoin === true && colName && !joinColumns.includes(colName)) {
            joinColumns.push(colName);
          }
        });
      }
      
      // 2. split-panel-layout의 leftPanel.columns, rightPanel.columns 추출
      if (componentKind === 'split-panel-layout') {
        if (componentConfig.leftPanel?.columns && Array.isArray(componentConfig.leftPanel.columns)) {
          componentConfig.leftPanel.columns.forEach((col: any) => {
            const colName = col.name || col.columnName || col.field;
            if (colName && !usedColumns.includes(colName)) {
              usedColumns.push(colName);
            }
          });
        }
        if (componentConfig.rightPanel?.columns && Array.isArray(componentConfig.rightPanel.columns)) {
          componentConfig.rightPanel.columns.forEach((col: any) => {
            const colName = col.name || col.columnName || col.field;
            if (colName) {
              // customer_mng.customer_name 같은 경우 조인 컬럼으로 처리
              if (colName.includes('.')) {
                if (!joinColumns.includes(colName)) {
                  joinColumns.push(colName);
                }
              } else {
                if (!usedColumns.includes(colName)) {
                  usedColumns.push(colName);
                }
              }
            }
          });
        }
      }
      
      // 3. selected-items-detail-input의 additionalFields, displayColumns 추출
      if (componentKind === 'selected-items-detail-input') {
        if (componentConfig.additionalFields && Array.isArray(componentConfig.additionalFields)) {
          componentConfig.additionalFields.forEach((field: any) => {
            const fieldName = field.name || field.field;
            if (fieldName && !usedColumns.includes(fieldName)) {
              usedColumns.push(fieldName);
            }
          });
        }
        // displayColumns는 연관 테이블에서 가져오는 표시용 컬럼이므로
        // 메인 테이블의 joinColumns가 아님 (parentDataMapping에서 별도 추출됨)
        // 단, 참조용으로 usedColumns에는 추가 가능
        if (componentConfig.displayColumns && Array.isArray(componentConfig.displayColumns)) {
          componentConfig.displayColumns.forEach((col: any) => {
            const colName = col.name || col.columnName || col.field;
            // displayColumns는 연관 테이블 컬럼이므로 메인 테이블 usedColumns에 추가하지 않음
            // 조인 컬럼은 parentDataMapping.targetField에서 추출됨
          });
        }
      }
      
      // 4. bindField가 있으면 usedColumns에 추가 (인풋 필드, 텍스트 필드 등)
      if (row.bind_field && !usedColumns.includes(row.bind_field)) {
        usedColumns.push(row.bind_field);
      }
      
      // 5. componentConfig.field 또는 componentConfig.valueField도 추가
      const configField = componentConfig.field || componentConfig.valueField;
      if (configField && typeof configField === 'string' && !usedColumns.includes(configField)) {
        usedColumns.push(configField);
      }
      
      if (summaryMap[screenId]) {
        summaryMap[screenId].widgetCounts[componentKind] = 
          (summaryMap[screenId].widgetCounts[componentKind] || 0) + 1;
        summaryMap[screenId].totalComponents++;
        
        // 레이아웃 아이템 추가 (미니어처 렌더링용)
        summaryMap[screenId].layoutItems.push({
          x: row.position_x || 0,
          y: row.position_y || 0,
          width: row.width || 100,
          height: row.height || 30,
          componentKind: componentKind, // 정확한 컴포넌트 종류
          widgetType: widgetType,
          label: row.label,
          bindField: row.bind_field || null, // 바인딩된 컬럼명
          usedColumns: usedColumns, // 이 컴포넌트에서 사용하는 컬럼 목록
          joinColumns: joinColumns, // 이 컴포넌트에서 조인 컬럼 목록
        });
        
        // 캔버스 크기 계산 (최대 좌표 기준)
        const rightEdge = (row.position_x || 0) + (row.width || 100);
        const bottomEdge = (row.position_y || 0) + (row.height || 30);
        if (rightEdge > summaryMap[screenId].canvasWidth) {
          summaryMap[screenId].canvasWidth = rightEdge;
        }
        if (bottomEdge > summaryMap[screenId].canvasHeight) {
          summaryMap[screenId].canvasHeight = bottomEdge;
        }
      }
    });

    // 화면 타입 추론 (componentKind 기준)
    Object.values(summaryMap).forEach((summary: any) => {
      if (summary.widgetCounts['table-list'] > 0) {
        summary.screenType = 'grid';
      } else if (summary.widgetCounts['table-search-widget'] > 1) {
        summary.screenType = 'dashboard';
      } else if (summary.totalComponents <= 5 && summary.widgetCounts['button-primary'] > 0) {
        summary.screenType = 'action';
      }
    });

    logger.info("여러 화면 레이아웃 요약 조회", { screenIds, count: Object.keys(summaryMap).length });

    res.json({
      success: true,
      data: summaryMap,
    });
  } catch (error: any) {
    logger.error("여러 화면 레이아웃 요약 조회 실패:", error);
    res.status(500).json({ success: false, message: "여러 화면 레이아웃 요약 조회에 실패했습니다.", error: error.message });
  }
};

// ============================================================
// 화면 서브 테이블 관계 조회 (조인/참조 테이블)
// ============================================================

// 여러 화면의 서브 테이블 정보 조회 (메인 테이블 → 서브 테이블 관계)
export const getScreenSubTables = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { screenIds } = req.body;

    if (!screenIds || !Array.isArray(screenIds) || screenIds.length === 0) {
      return res.status(400).json({ success: false, message: "screenIds 배열이 필요합니다." });
    }

    // 화면별 서브 테이블 그룹화
    const screenSubTables: Record<number, {
      screenId: number;
      screenName: string;
      mainTable: string;
      subTables: Array<{
        tableName: string;
        componentType: string;
        relationType: string; // 'join' | 'lookup' | 'source' | 'reference'
        fieldMappings?: Array<{ sourceField: string; targetField: string; sourceDisplayName?: string; targetDisplayName?: string }>;
      }>;
      saveTables?: Array<{
        tableName: string;
        saveType: 'save' | 'edit' | 'delete' | 'transferData';
        componentType: string;
        isMainTable: boolean;
      }>;
    }> = {};

    // 1. 기존 방식: componentConfig에서 tableName, sourceTable, fieldMappings 추출
    const componentQuery = `
      SELECT DISTINCT
        sd.screen_id,
        sd.screen_name,
        sd.table_name as main_table,
        COALESCE(
          sl.properties->'componentConfig'->>'tableName',
          sl.properties->'componentConfig'->>'sourceTable'
        ) as sub_table,
        sl.properties->>'componentType' as component_type,
        sl.properties->'componentConfig'->>'targetTable' as target_table,
        sl.properties->'componentConfig'->'fieldMappings' as field_mappings,
        sl.properties->'componentConfig'->'columns' as columns_config
      FROM screen_definitions sd
      JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
      WHERE sd.screen_id = ANY($1)
      AND (
        sl.properties->'componentConfig'->>'tableName' IS NOT NULL
        OR sl.properties->'componentConfig'->>'sourceTable' IS NOT NULL
      )
      ORDER BY sd.screen_id
    `;

    const componentResult = await pool.query(componentQuery, [screenIds]);

    // fieldMappings의 한글 컬럼명을 조회하기 위한 테이블-컬럼 쌍 수집
    const columnLabelLookups: Array<{ table: string; column: string }> = [];
    componentResult.rows.forEach((row: any) => {
      if (row.field_mappings && Array.isArray(row.field_mappings)) {
        row.field_mappings.forEach((fm: any) => {
          const mainTable = row.main_table;
          const subTable = row.sub_table;
          if (fm.sourceField && subTable) {
            columnLabelLookups.push({ table: subTable, column: fm.sourceField });
          }
          if (fm.targetField && mainTable) {
            columnLabelLookups.push({ table: mainTable, column: fm.targetField });
          }
        });
      }
    });

    // 한글 컬럼명 조회
    const columnLabelMap = new Map<string, string>(); // "table.column" -> "한글명"
    if (columnLabelLookups.length > 0) {
      const uniqueLookups = [...new Set(columnLabelLookups.map(l => `${l.table}|${l.column}`))];
      const conditions = uniqueLookups.map((lookup, i) => {
        const [table, column] = lookup.split('|');
        return `(table_name = $${i * 2 + 1} AND column_name = $${i * 2 + 2})`;
      });
      const params = uniqueLookups.flatMap(lookup => lookup.split('|'));
      
      if (conditions.length > 0) {
        const labelQuery = `
          SELECT table_name, column_name, column_label
          FROM table_type_columns
          WHERE (${conditions.join(' OR ')}) AND company_code = '*'
        `;
        const labelResult = await pool.query(labelQuery, params);
        labelResult.rows.forEach((row: any) => {
          const key = `${row.table_name}.${row.column_name}`;
          columnLabelMap.set(key, row.column_label || row.column_name);
        });
      }
    }

    componentResult.rows.forEach((row: any) => {
      const screenId = row.screen_id;
      const mainTable = row.main_table;
      const subTable = row.sub_table;

      // 메인 테이블과 동일한 경우 제외
      if (!subTable || subTable === mainTable) {
        return;
      }

      if (!screenSubTables[screenId]) {
        screenSubTables[screenId] = {
          screenId,
          screenName: row.screen_name,
          mainTable: mainTable || '',
          subTables: [],
        };
      }

      // 중복 체크
      const exists = screenSubTables[screenId].subTables.some(
        (st) => st.tableName === subTable
      );

      if (!exists) {
        // 관계 타입 추론
        let relationType = 'lookup';
        const componentType = row.component_type || '';
        if (componentType.includes('autocomplete') || componentType.includes('entity-search')) {
          relationType = 'lookup';
        } else if (componentType.includes('modal-repeater') || componentType.includes('selected-items')) {
          relationType = 'source';
        } else if (componentType.includes('table')) {
          relationType = 'join';
        }

        // fieldMappings 파싱 (JSON 배열 또는 null)
        let fieldMappings: Array<{ sourceField: string; targetField: string; sourceDisplayName?: string; targetDisplayName?: string }> | undefined;
        
        if (row.field_mappings && Array.isArray(row.field_mappings)) {
          // 1. 직접 fieldMappings가 있는 경우
          fieldMappings = row.field_mappings.map((fm: any) => {
            const sourceField = fm.sourceField || fm.source_field || '';
            const targetField = fm.targetField || fm.target_field || '';
            
            // 한글 컬럼명 조회 (sourceField는 서브테이블 컬럼, targetField는 메인테이블 컬럼)
            const sourceKey = `${subTable}.${sourceField}`;
            const targetKey = `${mainTable}.${targetField}`;
            
            return {
              sourceField,
              targetField,
              // sourceField(서브테이블 컬럼)의 한글명
              sourceDisplayName: columnLabelMap.get(sourceKey) || sourceField,
              // targetField(메인테이블 컬럼)의 한글명
              targetDisplayName: columnLabelMap.get(targetKey) || targetField,
            };
          }).filter((fm: any) => fm.sourceField || fm.targetField);
        } else if (row.columns_config && Array.isArray(row.columns_config)) {
          // 2. columns_config.mapping에서 추출 (item_info 같은 경우)
          // mapping.type === 'source'인 경우: sourceField(서브테이블) → field(메인테이블)
          fieldMappings = [];
          row.columns_config.forEach((col: any) => {
            if (col.mapping && col.mapping.type === 'source' && col.mapping.sourceField) {
              fieldMappings!.push({
                sourceField: col.field || '',                    // 메인 테이블 컬럼
                targetField: col.mapping.sourceField || '',      // 서브 테이블 컬럼
                sourceDisplayName: col.label || col.field || '', // 한글 라벨
                targetDisplayName: col.mapping.sourceField || '', // 서브 테이블은 영문만
              });
            }
          });
          if (fieldMappings.length === 0) {
            fieldMappings = undefined;
          }
        }

        screenSubTables[screenId].subTables.push({
          tableName: subTable,
          componentType: componentType,
          relationType: relationType,
          fieldMappings: fieldMappings,
        });
      }
    });

    // 2. 추가 방식: 화면에서 사용하는 컬럼 중 table_type_columns.reference_table이 설정된 경우
    // 화면의 usedColumns/joinColumns에서 reference_table 조회
    const referenceQuery = `
      WITH screen_used_columns AS (
        -- 화면별 사용 컬럼 추출 (componentConfig.columns에서)
        SELECT 
          sd.screen_id,
          sd.screen_name,
          sd.table_name::text as main_table,
          jsonb_array_elements(
            COALESCE(
              sl.properties->'componentConfig'->'columns',
              '[]'::jsonb
            )
          )->>'columnName' as column_name
        FROM screen_definitions sd
        JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
        WHERE sd.screen_id = ANY($1)
        AND sl.properties->'componentConfig'->'columns' IS NOT NULL
        AND jsonb_array_length(sl.properties->'componentConfig'->'columns') > 0
        
        UNION
        
        -- bindField도 포함
        SELECT 
          sd.screen_id,
          sd.screen_name,
          sd.table_name::text as main_table,
          COALESCE(
            sl.properties->'componentConfig'->>'bindField',
            sl.properties->>'bindField',
            sl.properties->'componentConfig'->>'field',
            sl.properties->>'field'
          ) as column_name
        FROM screen_definitions sd
        JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
        WHERE sd.screen_id = ANY($1)
        AND (
          sl.properties->'componentConfig'->>'bindField' IS NOT NULL
          OR sl.properties->>'bindField' IS NOT NULL
          OR sl.properties->'componentConfig'->>'field' IS NOT NULL
          OR sl.properties->>'field' IS NOT NULL
        )
        
        UNION
        
        -- valueField 추출 (entity-search-input, autocomplete-search-input 등에서 사용)
        SELECT 
          sd.screen_id,
          sd.screen_name,
          sd.table_name::text as main_table,
          sl.properties->'componentConfig'->>'valueField' as column_name
        FROM screen_definitions sd
        JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
        WHERE sd.screen_id = ANY($1)
        AND sl.properties->'componentConfig'->>'valueField' IS NOT NULL
        
        UNION
        
        -- parentFieldId 추출 (캐스케이딩 관계에서 사용)
        SELECT 
          sd.screen_id,
          sd.screen_name,
          sd.table_name::text as main_table,
          sl.properties->'componentConfig'->>'parentFieldId' as column_name
        FROM screen_definitions sd
        JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
        WHERE sd.screen_id = ANY($1)
        AND sl.properties->'componentConfig'->>'parentFieldId' IS NOT NULL
        
        UNION
        
        -- cascadingParentField 추출 (캐스케이딩 부모 필드)
        SELECT 
          sd.screen_id,
          sd.screen_name,
          sd.table_name::text as main_table,
          sl.properties->'componentConfig'->>'cascadingParentField' as column_name
        FROM screen_definitions sd
        JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
        WHERE sd.screen_id = ANY($1)
        AND sl.properties->'componentConfig'->>'cascadingParentField' IS NOT NULL
        
        UNION
        
        -- controlField 추출 (conditional-container에서 사용)
        SELECT 
          sd.screen_id,
          sd.screen_name,
          sd.table_name::text as main_table,
          sl.properties->'componentConfig'->>'controlField' as column_name
        FROM screen_definitions sd
        JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
        WHERE sd.screen_id = ANY($1)
        AND sl.properties->'componentConfig'->>'controlField' IS NOT NULL
      )
      SELECT DISTINCT
        suc.screen_id,
        suc.screen_name,
        suc.main_table,
        suc.column_name,
        cl.column_label as source_display_name,
        cl.reference_table,
        cl.reference_column,
        ref_cl.column_label as target_display_name
      FROM screen_used_columns suc
      JOIN table_type_columns cl ON cl.table_name = suc.main_table AND cl.column_name = suc.column_name AND cl.company_code = '*'
      LEFT JOIN table_type_columns ref_cl ON ref_cl.table_name = cl.reference_table AND ref_cl.column_name = cl.reference_column AND ref_cl.company_code = '*'
      WHERE cl.reference_table IS NOT NULL 
        AND cl.reference_table != ''
        AND cl.reference_table != suc.main_table
        AND cl.input_type = 'entity'
      ORDER BY suc.screen_id
    `;

    const referenceResult = await pool.query(referenceQuery, [screenIds]);

    logger.info("table_type_columns reference_table 조회 결과", { 
      screenIds, 
      referenceCount: referenceResult.rows.length,
      references: referenceResult.rows.map((r: any) => ({
        screenId: r.screen_id,
        column: r.column_name,
        refTable: r.reference_table
      }))
    });

    referenceResult.rows.forEach((row: any) => {
      const screenId = row.screen_id;
      const mainTable = row.main_table;
      const referenceTable = row.reference_table;

      if (!referenceTable || referenceTable === mainTable) {
        return;
      }

      if (!screenSubTables[screenId]) {
        screenSubTables[screenId] = {
          screenId,
          screenName: row.screen_name,
          mainTable: mainTable || '',
          subTables: [],
        };
      }

      // 중복 체크
      const exists = screenSubTables[screenId].subTables.some(
        (st) => st.tableName === referenceTable
      );

      if (!exists) {
        screenSubTables[screenId].subTables.push({
          tableName: referenceTable,
          componentType: 'column_reference',
          relationType: 'reference',
          fieldMappings: [{
            sourceField: row.column_name,
            targetField: row.reference_column || 'id',
            sourceDisplayName: row.source_display_name || row.column_name,
            targetDisplayName: row.target_display_name || row.reference_column || 'id',
          }],
        });
      } else {
        // 이미 존재하면 fieldMappings에 추가
        const existingSubTable = screenSubTables[screenId].subTables.find(
          (st) => st.tableName === referenceTable
        );
        if (existingSubTable && existingSubTable.fieldMappings) {
          const mappingExists = existingSubTable.fieldMappings.some(
            (fm) => fm.sourceField === row.column_name
          );
          if (!mappingExists) {
            existingSubTable.fieldMappings.push({
              sourceField: row.column_name,
              targetField: row.reference_column || 'id',
              sourceDisplayName: row.source_display_name || row.column_name,
              targetDisplayName: row.target_display_name || row.reference_column || 'id',
            });
          }
        }
      }
    });

    // 3. parentDataMapping 파싱 (selected-items-detail-input 등에서 사용)
    const parentMappingQuery = `
      SELECT 
        sd.screen_id,
        sd.screen_name,
        sd.table_name as main_table,
        sl.properties->>'componentType' as component_type,
        sl.properties->'componentConfig'->'parentDataMapping' as parent_data_mapping
      FROM screen_definitions sd
      JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
      WHERE sd.screen_id = ANY($1)
      AND sl.properties->'componentConfig'->'parentDataMapping' IS NOT NULL
    `;

    const parentMappingResult = await pool.query(parentMappingQuery, [screenIds]);

    parentMappingResult.rows.forEach((row: any) => {
      const screenId = row.screen_id;
      const mainTable = row.main_table;
      const componentType = row.component_type || 'parentDataMapping';
      const parentDataMapping = row.parent_data_mapping;

      if (!Array.isArray(parentDataMapping)) return;

      if (!screenSubTables[screenId]) {
        screenSubTables[screenId] = {
          screenId,
          screenName: row.screen_name,
          mainTable: mainTable || '',
          subTables: [],
        };
      }

      parentDataMapping.forEach((mapping: any) => {
        const sourceTable = mapping.sourceTable;
        if (!sourceTable || sourceTable === mainTable) return;

        // 중복 체크
        const existingSubTable = screenSubTables[screenId].subTables.find(
          (st) => st.tableName === sourceTable
        );

        const newMapping = {
          sourceTable: sourceTable, // 연관 테이블 정보 추가
          sourceField: mapping.sourceField || '',
          targetField: mapping.targetField || '',
          sourceDisplayName: mapping.sourceField || '',
          targetDisplayName: mapping.targetField || '',
        };

        if (existingSubTable) {
          // 이미 존재하면 fieldMappings에 추가
          if (!existingSubTable.fieldMappings) {
            existingSubTable.fieldMappings = [];
          }
          const mappingExists = existingSubTable.fieldMappings.some(
            (fm: any) => fm.sourceField === newMapping.sourceField && fm.targetField === newMapping.targetField
          );
          if (!mappingExists) {
            existingSubTable.fieldMappings.push(newMapping);
          }
        } else {
          screenSubTables[screenId].subTables.push({
            tableName: sourceTable,
            componentType: componentType,
            relationType: 'parentMapping',
            fieldMappings: [newMapping],
          });
        }
      });
    });

    logger.info("parentDataMapping 파싱 완료", { 
      screenIds, 
      parentMappingCount: parentMappingResult.rows.length 
    });

    // 4. rightPanel.relation 파싱 (split-panel-layout 등에서 사용)
    // screen_layouts (v1)와 screen_layouts_v2 모두 조회
    const rightPanelQuery = `
      -- V1: screen_layouts에서 조회
      SELECT 
        sd.screen_id,
        sd.screen_name,
        sd.table_name as main_table,
        sl.properties->>'componentType' as component_type,
        sl.properties->'componentConfig'->'rightPanel'->'relation' as right_panel_relation,
        sl.properties->'componentConfig'->'rightPanel'->>'tableName' as right_panel_table,
        sl.properties->'componentConfig'->'rightPanel'->'columns' as right_panel_columns
      FROM screen_definitions sd
      JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
      WHERE sd.screen_id = ANY($1)
      AND sl.properties->'componentConfig'->'rightPanel'->'relation' IS NOT NULL

      UNION ALL

      -- V2: screen_layouts_v2에서 조회 (v2-split-panel-layout 컴포넌트)
      SELECT 
        sd.screen_id,
        sd.screen_name,
        sd.table_name as main_table,
        comp->'overrides'->>'type' as component_type,
        comp->'overrides'->'rightPanel'->'relation' as right_panel_relation,
        comp->'overrides'->'rightPanel'->>'tableName' as right_panel_table,
        comp->'overrides'->'rightPanel'->'columns' as right_panel_columns
      FROM screen_definitions sd
      JOIN screen_layouts_v2 slv2 ON sd.screen_id = slv2.screen_id,
      jsonb_array_elements(slv2.layout_data->'components') as comp
      WHERE sd.screen_id = ANY($1)
      AND comp->'overrides'->'rightPanel'->'relation' IS NOT NULL
    `;

    const rightPanelResult = await pool.query(rightPanelQuery, [screenIds]);

    // rightPanel.columns에서 참조되는 외부 테이블 수집 (예: customer_mng.customer_name → customer_mng)
    const rightPanelJoinedTables: Map<string, Set<string>> = new Map(); // screenId_tableName → Set<참조테이블>
    
    rightPanelResult.rows.forEach((row: any) => {
      const screenId = row.screen_id;
      const rightPanelTable = row.right_panel_table;
      const rightPanelColumns = row.right_panel_columns;
      
      if (rightPanelColumns && Array.isArray(rightPanelColumns)) {
        rightPanelColumns.forEach((col: any) => {
          const colName = col.name || col.columnName || col.field;
          if (colName && colName.includes('.')) {
            const refTable = colName.split('.')[0];
            const key = `${screenId}_${rightPanelTable}`;
            if (!rightPanelJoinedTables.has(key)) {
              rightPanelJoinedTables.set(key, new Set());
            }
            rightPanelJoinedTables.get(key)!.add(refTable);
          }
        });
      }
    });

    rightPanelResult.rows.forEach((row: any) => {
      const screenId = row.screen_id;
      const mainTable = row.main_table;
      const componentType = row.component_type || 'split-panel-layout';
      const relation = row.right_panel_relation;
      const rightPanelTable = row.right_panel_table;

      // relation 객체에서 테이블 및 필드 매핑 추출
      const subTable = rightPanelTable || relation?.targetTable || relation?.tableName;
      if (!subTable || subTable === mainTable) return;
      
      // rightPanel.columns에서 참조하는 외부 테이블 목록
      const key = `${screenId}_${subTable}`;
      const joinedTables = rightPanelJoinedTables.get(key) ? Array.from(rightPanelJoinedTables.get(key)!) : [];

      if (!screenSubTables[screenId]) {
        screenSubTables[screenId] = {
          screenId,
          screenName: row.screen_name,
          mainTable: mainTable || '',
          subTables: [],
        };
      }

      // 중복 체크
      const existingSubTable = screenSubTables[screenId].subTables.find(
        (st) => st.tableName === subTable
      );

      // relation에서 필드 매핑 추출
      const fieldMappings: Array<{ sourceField: string; targetField: string; sourceDisplayName?: string; targetDisplayName?: string }> = [];
      
      if (relation?.sourceField && relation?.targetField) {
        fieldMappings.push({
          sourceField: relation.sourceField,
          targetField: relation.targetField,
          sourceDisplayName: relation.sourceField,
          targetDisplayName: relation.targetField,
        });
      }
      
      // fieldMappings 배열이 있는 경우
      if (relation?.fieldMappings && Array.isArray(relation.fieldMappings)) {
        relation.fieldMappings.forEach((fm: any) => {
          fieldMappings.push({
            sourceField: fm.sourceField || fm.source_field || '',
            targetField: fm.targetField || fm.target_field || '',
            sourceDisplayName: fm.sourceField || fm.source_field || '',
            targetDisplayName: fm.targetField || fm.target_field || '',
          });
        });
      }

      if (existingSubTable) {
        // 이미 존재하면 fieldMappings에 추가
        if (!existingSubTable.fieldMappings) {
          existingSubTable.fieldMappings = [];
        }
        fieldMappings.forEach((newMapping) => {
          const mappingExists = existingSubTable.fieldMappings!.some(
            (fm) => fm.sourceField === newMapping.sourceField && fm.targetField === newMapping.targetField
          );
          if (!mappingExists) {
            existingSubTable.fieldMappings!.push(newMapping);
          }
        });
        // 추가 정보도 업데이트
        if (relation?.type) {
          (existingSubTable as any).originalRelationType = relation.type;
        }
        if (relation?.foreignKey) {
          (existingSubTable as any).foreignKey = relation.foreignKey;
        }
        if (relation?.leftColumn) {
          (existingSubTable as any).leftColumn = relation.leftColumn;
        }
      } else {
        screenSubTables[screenId].subTables.push({
          tableName: subTable,
          componentType: componentType,
          relationType: 'rightPanelRelation',
          // 관계 유형 추론을 위한 추가 정보
          originalRelationType: relation?.type || 'join',  // 원본 relation.type ("join" | "detail")
          foreignKey: relation?.foreignKey,                 // 디테일 테이블의 FK 컬럼
          leftColumn: relation?.leftColumn,                 // 마스터 테이블의 선택 기준 컬럼
          joinedTables: joinedTables.length > 0 ? joinedTables : undefined,  // rightPanel.columns에서 참조하는 외부 테이블들
          fieldMappings: fieldMappings.length > 0 ? fieldMappings : undefined,
        } as any);
      }
    });

    logger.info("rightPanel.relation 파싱 완료", { 
      screenIds, 
      rightPanelCount: rightPanelResult.rows.length 
    });
    
    // 5. joinedTables에 대한 FK 컬럼을 table_type_columns에서 조회
    // rightPanelRelation에서 joinedTables가 있는 경우, 해당 테이블과 조인하는 FK 컬럼 찾기
    const joinedTableFKLookups: Array<{ subTableName: string; refTable: string }> = [];
    Object.values(screenSubTables).forEach((screenData: any) => {
      screenData.subTables.forEach((subTable: any) => {
        if (subTable.joinedTables && Array.isArray(subTable.joinedTables)) {
          subTable.joinedTables.forEach((refTable: string) => {
            joinedTableFKLookups.push({ subTableName: subTable.tableName, refTable });
          });
        }
      });
    });
    
    // table_type_columns에서 FK 컬럼 조회 (reference_table로 조인하는 컬럼 찾기)
    const joinColumnsByTable: { [key: string]: string[] } = {}; // tableName → [FK 컬럼들]
    if (joinedTableFKLookups.length > 0) {
      const uniqueLookups = joinedTableFKLookups.filter((item, index, self) =>
        index === self.findIndex((t) => t.subTableName === item.subTableName && t.refTable === item.refTable)
      );
      
      // 각 subTable에 대해 reference_table이 일치하는 컬럼 조회
      const subTableNames = [...new Set(uniqueLookups.map(l => l.subTableName))];
      const refTableNames = [...new Set(uniqueLookups.map(l => l.refTable))];
      
      const fkQuery = `
        SELECT 
          cl.table_name, 
          cl.column_name, 
          cl.column_label, 
          cl.reference_table, 
          cl.reference_column,
          tl.table_label as reference_table_label
        FROM table_type_columns cl
        LEFT JOIN table_labels tl ON cl.reference_table = tl.table_name
        WHERE cl.table_name = ANY($1)
        AND cl.reference_table = ANY($2)
        AND cl.company_code = '*'
      `;
      
      const fkResult = await pool.query(fkQuery, [subTableNames, refTableNames]);
      
      // 참조 정보 포함 객체 배열로 저장 (한글명 포함)
      const joinColumnRefsByTable: Record<string, Array<{ column: string; columnLabel: string; refTable: string; refTableLabel: string; refColumn: string }>> = {};
      
      fkResult.rows.forEach((row: any) => {
        if (!joinColumnRefsByTable[row.table_name]) {
          joinColumnRefsByTable[row.table_name] = [];
        }
        // 중복 체크
        const exists = joinColumnRefsByTable[row.table_name].some(
          (ref) => ref.column === row.column_name && ref.refTable === row.reference_table
        );
        if (!exists) {
          joinColumnRefsByTable[row.table_name].push({
            column: row.column_name,
            columnLabel: row.column_label || row.column_name, // 컬럼 한글명 (없으면 영문명)
            refTable: row.reference_table,
            refTableLabel: row.reference_table_label || row.reference_table, // 참조 테이블 한글명 (없으면 영문명)
            refColumn: row.reference_column || 'id',
          });
        }
      });
      
      // subTables에 joinColumns (문자열 배열) 및 joinColumnRefs (참조 정보 배열) 추가
      Object.values(screenSubTables).forEach((screenData: any) => {
        screenData.subTables.forEach((subTable: any) => {
          const refs = joinColumnRefsByTable[subTable.tableName];
          if (refs) {
            (subTable as any).joinColumns = refs.map(r => r.column);
            (subTable as any).joinColumnRefs = refs;
          }
        });
      });
      
      logger.info("rightPanel joinedTables FK 조회 완료", { 
        lookupCount: uniqueLookups.length,
        resultCount: fkResult.rows.length,
        joinColumnsByTable
      });
    }

    // 5. 모든 fieldMappings의 한글명을 table_type_columns에서 가져와서 적용
    // 모든 테이블/컬럼 조합을 수집
    const columnLookups: Array<{ tableName: string; columnName: string }> = [];
    Object.values(screenSubTables).forEach((screenData: any) => {
      screenData.subTables.forEach((subTable: any) => {
        if (subTable.fieldMappings) {
          subTable.fieldMappings.forEach((mapping: any) => {
            // sourceTable + sourceField (연관 테이블의 컬럼)
            if (mapping.sourceTable && mapping.sourceField) {
              columnLookups.push({ tableName: mapping.sourceTable, columnName: mapping.sourceField });
            }
            // mainTable + targetField (메인 테이블의 컬럼)
            if (screenData.mainTable && mapping.targetField) {
              columnLookups.push({ tableName: screenData.mainTable, columnName: mapping.targetField });
            }
          });
        }
      });
    });

    // 중복 제거
    const uniqueColumnLookups = columnLookups.filter((item, index, self) =>
      index === self.findIndex((t) => t.tableName === item.tableName && t.columnName === item.columnName)
    );

    // table_type_columns에서 한글명 조회
    const columnLabelsMap: { [key: string]: string } = {};
    if (uniqueColumnLookups.length > 0) {
      const columnLabelsQuery = `
        SELECT 
          table_name,
          column_name,
          column_label
        FROM table_type_columns
        WHERE (table_name, column_name) IN (
          ${uniqueColumnLookups.map((_, i) => `($${i * 2 + 1}, $${i * 2 + 2})`).join(', ')}
        )
        AND company_code = '*'
      `;
      const columnLabelsParams = uniqueColumnLookups.flatMap(item => [item.tableName, item.columnName]);
      
      try {
        const columnLabelsResult = await pool.query(columnLabelsQuery, columnLabelsParams);
        columnLabelsResult.rows.forEach((row: any) => {
          const key = `${row.table_name}.${row.column_name}`;
          columnLabelsMap[key] = row.column_label;
        });
        logger.info("table_type_columns 한글명 조회 완료", { count: columnLabelsResult.rows.length });
      } catch (error: any) {
        logger.warn("table_type_columns 한글명 조회 실패 (무시하고 계속 진행):", error.message);
      }
    }

    // 각 fieldMappings에 한글명 적용
    Object.values(screenSubTables).forEach((screenData: any) => {
      screenData.subTables.forEach((subTable: any) => {
        if (subTable.fieldMappings) {
          subTable.fieldMappings.forEach((mapping: any) => {
            // sourceDisplayName: 연관 테이블의 컬럼 한글명
            if (mapping.sourceTable && mapping.sourceField) {
              const sourceKey = `${mapping.sourceTable}.${mapping.sourceField}`;
              if (columnLabelsMap[sourceKey]) {
                mapping.sourceDisplayName = columnLabelsMap[sourceKey];
              }
            }
            // targetDisplayName: 메인 테이블의 컬럼 한글명
            if (screenData.mainTable && mapping.targetField) {
              const targetKey = `${screenData.mainTable}.${mapping.targetField}`;
              if (columnLabelsMap[targetKey]) {
                mapping.targetDisplayName = columnLabelsMap[targetKey];
              }
            }
          });
        }
      });
    });

    // 6. v2-repeater 컴포넌트에서 selectedTable/foreignKey 추출
    const v2RepeaterQuery = `
      SELECT DISTINCT
        sd.screen_id,
        sd.screen_name,
        sd.table_name as main_table,
        comp->'overrides'->>'type' as component_type,
        comp->'overrides'->>'selectedTable' as sub_table,
        comp->'overrides'->>'foreignKey' as foreign_key,
        comp->'overrides'->>'parentTable' as parent_table
      FROM screen_definitions sd
      JOIN screen_layouts_v2 slv2 ON sd.screen_id = slv2.screen_id,
      jsonb_array_elements(slv2.layout_data->'components') as comp
      WHERE sd.screen_id = ANY($1)
      AND comp->'overrides'->>'type' = 'v2-repeater'
      AND comp->'overrides'->>'selectedTable' IS NOT NULL
    `;
    const v2RepeaterResult = await pool.query(v2RepeaterQuery, [screenIds]);
    v2RepeaterResult.rows.forEach((row: any) => {
      const screenId = row.screen_id;
      const mainTable = row.main_table;
      const subTable = row.sub_table;
      const foreignKey = row.foreign_key;
      if (!subTable || subTable === mainTable) return;
      if (!screenSubTables[screenId]) {
        screenSubTables[screenId] = {
          screenId,
          screenName: row.screen_name,
          mainTable: mainTable || '',
          subTables: [],
        };
      }
      const exists = screenSubTables[screenId].subTables.some(
        (st) => st.tableName === subTable
      );
      if (!exists) {
        screenSubTables[screenId].subTables.push({
          tableName: subTable,
          componentType: 'v2-repeater',
          relationType: 'rightPanelRelation',
          fieldMappings: foreignKey ? [{
            sourceField: 'id',
            targetField: foreignKey,
            sourceDisplayName: 'ID',
            targetDisplayName: foreignKey,
          }] : undefined,
        });
      }
    });
    logger.info("v2-repeater 서브 테이블 추출 완료", {
      screenIds,
      v2RepeaterCount: v2RepeaterResult.rows.length,
    });

    // 7. rightPanel.components 내부의 componentConfig.detailTable 추출 (v2-bom-tree 등)
    const v2DetailTableQuery = `
      SELECT DISTINCT
        sd.screen_id,
        sd.screen_name,
        sd.table_name as main_table,
        inner_comp->>'type' as component_type,
        inner_comp->'componentConfig'->>'detailTable' as sub_table,
        inner_comp->'componentConfig'->>'foreignKey' as foreign_key
      FROM screen_definitions sd
      JOIN screen_layouts_v2 slv2 ON sd.screen_id = slv2.screen_id,
      jsonb_array_elements(slv2.layout_data->'components') as comp,
      jsonb_array_elements(
        COALESCE(
          comp->'overrides'->'rightPanel'->'components',
          comp->'overrides'->'leftPanel'->'components',
          '[]'::jsonb
        )
      ) as inner_comp
      WHERE sd.screen_id = ANY($1)
      AND inner_comp->'componentConfig'->>'detailTable' IS NOT NULL
    `;
    const v2DetailTableResult = await pool.query(v2DetailTableQuery, [screenIds]);
    v2DetailTableResult.rows.forEach((row: any) => {
      const screenId = row.screen_id;
      const mainTable = row.main_table;
      const subTable = row.sub_table;
      const foreignKey = row.foreign_key;
      if (!subTable || subTable === mainTable) return;
      if (!screenSubTables[screenId]) {
        screenSubTables[screenId] = {
          screenId,
          screenName: row.screen_name,
          mainTable: mainTable || '',
          subTables: [],
        };
      }
      const exists = screenSubTables[screenId].subTables.some(
        (st) => st.tableName === subTable
      );
      if (!exists) {
        screenSubTables[screenId].subTables.push({
          tableName: subTable,
          componentType: row.component_type || 'v2-bom-tree',
          relationType: 'rightPanelRelation',
          fieldMappings: foreignKey ? [{
            sourceField: 'id',
            targetField: foreignKey,
            sourceDisplayName: 'ID',
            targetDisplayName: foreignKey,
          }] : undefined,
        });
      }
    });
    logger.info("v2-bom-tree/detailTable 서브 테이블 추출 완료", {
      screenIds,
      v2DetailTableCount: v2DetailTableResult.rows.length,
    });

    // ============================================================
    // 저장 테이블 정보 추출
    // ============================================================
    // 제외 조건:
    // 1. table-list + 체크박스 활성화 + openModalWithData 버튼이 있는 화면
    //    → 선택 후 다음 화면으로 넘기는 패턴 (실제 DB 저장 아님)
    const saveTableQuery = `
      SELECT DISTINCT
        sd.screen_id,
        sd.screen_name,
        sd.table_name as main_table,
        sl.properties->'componentConfig'->'action'->>'type' as action_type,
        sl.properties->>'componentType' as component_type,
        sl.properties->'componentConfig'->>'targetTable' as target_table,
        sl.properties->'componentConfig'->'action'->'dataTransfer'->>'targetTable' as transfer_target_table
      FROM screen_definitions sd
      JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
      WHERE sd.screen_id = ANY($1)
        AND sl.properties->'componentConfig'->'action'->>'type' = 'save'
        AND sl.properties->'componentConfig'->'action'->>'targetScreenId' IS NULL
        -- 제외: table-list + 체크박스가 있는 화면
        AND NOT EXISTS (
          SELECT 1 FROM screen_layouts sl_list
          WHERE sl_list.screen_id = sd.screen_id
            AND sl_list.properties->>'componentType' = 'table-list'
            AND (sl_list.properties->'componentConfig'->'checkbox'->>'enabled')::boolean = true
        )
        -- 제외: openModalWithData 버튼이 있는 화면 (선택 → 다음 화면 패턴)
        AND NOT EXISTS (
          SELECT 1 FROM screen_layouts sl_modal
          WHERE sl_modal.screen_id = sd.screen_id
            AND sl_modal.properties->'componentConfig'->'action'->>'type' = 'openModalWithData'
        )
      ORDER BY sd.screen_id
    `;

    const saveTableResult = await pool.query(saveTableQuery, [screenIds]);

    saveTableResult.rows.forEach((row: any) => {
      const screenId = row.screen_id;
      const mainTable = row.main_table;
      const actionType = row.action_type as 'save' | 'edit' | 'delete' | 'transferData';
      const componentType = row.component_type || 'component';
      const targetTable = row.target_table || row.transfer_target_table || mainTable;

      // 화면 정보가 없으면 초기화
      if (!screenSubTables[screenId]) {
        screenSubTables[screenId] = {
          screenId,
          screenName: row.screen_name,
          mainTable: mainTable || '',
          subTables: [],
          saveTables: [],
        };
      }

      // saveTables 배열 초기화
      if (!screenSubTables[screenId].saveTables) {
        screenSubTables[screenId].saveTables = [];
      }

      // 중복 체크
      const existingSaveTable = screenSubTables[screenId].saveTables!.find(
        (st) => st.tableName === targetTable && st.saveType === actionType
      );

      if (!existingSaveTable && targetTable) {
        screenSubTables[screenId].saveTables!.push({
          tableName: targetTable,
          saveType: actionType,
          componentType,
          isMainTable: targetTable === mainTable,
        });
      }
    });

    logger.info("화면 서브 테이블 정보 조회 완료", { 
      screenIds, 
      resultCount: Object.keys(screenSubTables).length,
      details: Object.values(screenSubTables).map(s => ({
        screenId: s.screenId,
        mainTable: s.mainTable,
        subTables: s.subTables.map(st => st.tableName),
        saveTables: s.saveTables?.map(st => st.tableName) || []
      }))
    });

    // ============================================================
    // 6. 전역 메인 테이블 목록 수집 (우선순위 적용용)
    // ============================================================
    // 메인 테이블 조건:
    // 1. screen_definitions.table_name (컴포넌트 직접 연결)
    // 2. v2-split-panel-layout의 rightPanel.tableName (WHERE 조건 대상)
    // 
    // 이 목록에 있으면 서브 테이블로 분류되지 않음 (우선순위: 메인 > 서브)
    const globalMainTablesQuery = `
      -- 1. 모든 화면의 메인 테이블 (screen_definitions.table_name)
      SELECT DISTINCT table_name as main_table
      FROM screen_definitions
      WHERE screen_id = ANY($1)
      AND table_name IS NOT NULL

      UNION

      -- 2. v2-split-panel-layout의 rightPanel.tableName (WHERE 조건 대상)
      -- 현재 그룹의 화면들에서 마스터-디테일로 연결된 테이블
      SELECT DISTINCT comp->'overrides'->'rightPanel'->>'tableName' as main_table
      FROM screen_definitions sd
      JOIN screen_layouts_v2 slv2 ON sd.screen_id = slv2.screen_id,
      jsonb_array_elements(slv2.layout_data->'components') as comp
      WHERE sd.screen_id = ANY($1)
      AND comp->'overrides'->'rightPanel'->>'tableName' IS NOT NULL

      UNION

      -- 3. v1 screen_layouts의 rightPanel.tableName (WHERE 조건 대상)
      SELECT DISTINCT sl.properties->'componentConfig'->'rightPanel'->>'tableName' as main_table
      FROM screen_definitions sd
      JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
      WHERE sd.screen_id = ANY($1)
      AND sl.properties->'componentConfig'->'rightPanel'->>'tableName' IS NOT NULL
    `;

    const globalMainTablesResult = await pool.query(globalMainTablesQuery, [screenIds]);
    const globalMainTables = globalMainTablesResult.rows
      .map((r: any) => r.main_table)
      .filter((t: string) => t != null && t !== '');

    logger.info("전역 메인 테이블 목록 수집 완료", { 
      count: globalMainTables.length,
      tables: globalMainTables
    });

    res.json({
      success: true,
      data: screenSubTables,
      globalMainTables: globalMainTables,  // 메인 테이블로 분류되어야 하는 테이블 목록
    });
  } catch (error: any) {
    logger.error("화면 서브 테이블 정보 조회 실패:", error);
    res.status(500).json({ success: false, message: "화면 서브 테이블 정보 조회에 실패했습니다.", error: error.message });
  }
};


// ============================================================
// 메뉴-화면그룹 동기화 API
// ============================================================

/**
 * 화면관리 → 메뉴 동기화
 * screen_groups를 menu_info로 동기화
 */
export const syncScreenGroupsToMenuController = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userCompanyCode = req.user?.companyCode || "*";
    const userId = req.user?.userId || "";
    const { targetCompanyCode } = req.body;

    // 최고 관리자가 특정 회사를 지정한 경우 해당 회사로
    let companyCode = userCompanyCode;
    if (userCompanyCode === "*" && targetCompanyCode) {
      companyCode = targetCompanyCode;
    }

    // 최고 관리자(*)는 회사를 지정해야 함
    if (companyCode === "*") {
      return res.status(400).json({
        success: false,
        message: "동기화할 회사를 선택해주세요.",
      });
    }

    logger.info("화면관리 → 메뉴 동기화 요청", { companyCode, userId });

    const result = await syncScreenGroupsToMenu(companyCode, userId);

    if (!result.success) {
      return res.status(500).json({
        success: false,
        message: "동기화 중 오류가 발생했습니다.",
        errors: result.errors,
      });
    }

    res.json({
      success: true,
      message: `동기화 완료: 생성 ${result.created}개, 연결 ${result.linked}개, 스킵 ${result.skipped}개`,
      data: result,
    });
  } catch (error: any) {
    logger.error("화면관리 → 메뉴 동기화 실패:", error);
    res.status(500).json({
      success: false,
      message: "동기화에 실패했습니다.",
      error: error.message,
    });
  }
};

/**
 * 메뉴 → 화면관리 동기화
 * menu_info를 screen_groups로 동기화
 */
export const syncMenuToScreenGroupsController = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userCompanyCode = req.user?.companyCode || "*";
    const userId = req.user?.userId || "";
    const { targetCompanyCode } = req.body;

    // 최고 관리자가 특정 회사를 지정한 경우 해당 회사로
    let companyCode = userCompanyCode;
    if (userCompanyCode === "*" && targetCompanyCode) {
      companyCode = targetCompanyCode;
    }

    // 최고 관리자(*)는 회사를 지정해야 함
    if (companyCode === "*") {
      return res.status(400).json({
        success: false,
        message: "동기화할 회사를 선택해주세요.",
      });
    }

    logger.info("메뉴 → 화면관리 동기화 요청", { companyCode, userId });

    const result = await syncMenuToScreenGroups(companyCode, userId);

    if (!result.success) {
      return res.status(500).json({
        success: false,
        message: "동기화 중 오류가 발생했습니다.",
        errors: result.errors,
      });
    }

    res.json({
      success: true,
      message: `동기화 완료: 생성 ${result.created}개, 연결 ${result.linked}개, 스킵 ${result.skipped}개`,
      data: result,
    });
  } catch (error: any) {
    logger.error("메뉴 → 화면관리 동기화 실패:", error);
    res.status(500).json({
      success: false,
      message: "동기화에 실패했습니다.",
      error: error.message,
    });
  }
};

/**
 * 동기화 상태 조회
 */
export const getSyncStatusController = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userCompanyCode = req.user?.companyCode || "*";
    const { targetCompanyCode } = req.query;

    // 최고 관리자가 특정 회사를 지정한 경우 해당 회사로
    let companyCode = userCompanyCode;
    if (userCompanyCode === "*" && targetCompanyCode) {
      companyCode = targetCompanyCode as string;
    }

    // 최고 관리자(*)는 회사를 지정해야 함
    if (companyCode === "*") {
      return res.status(400).json({
        success: false,
        message: "조회할 회사를 선택해주세요.",
      });
    }

    const status = await getSyncStatus(companyCode);

    res.json({
      success: true,
      data: status,
    });
  } catch (error: any) {
    logger.error("동기화 상태 조회 실패:", error);
    res.status(500).json({
      success: false,
      message: "동기화 상태 조회에 실패했습니다.",
      error: error.message,
    });
  }
};

/**
 * 전체 회사 동기화
 * 모든 회사에 대해 양방향 동기화 수행 (최고 관리자만)
 */
export const syncAllCompaniesController = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userCompanyCode = req.user?.companyCode || "*";
    const userId = req.user?.userId || "";

    // 최고 관리자만 전체 동기화 가능
    if (userCompanyCode !== "*") {
      return res.status(403).json({
        success: false,
        message: "전체 동기화는 최고 관리자만 수행할 수 있습니다.",
      });
    }

    logger.info("전체 회사 동기화 요청", { userId });

    const result = await syncAllCompanies(userId);

    if (!result.success) {
      return res.status(500).json({
        success: false,
        message: "전체 동기화 중 오류가 발생했습니다.",
      });
    }

    // 결과 요약
    const totalCreated = result.results.reduce((sum, r) => sum + r.created, 0);
    const totalLinked = result.results.reduce((sum, r) => sum + r.linked, 0);

    res.json({
      success: true,
      message: `전체 동기화 완료: ${result.totalCompanies}개 회사 중 ${result.successCount}개 성공`,
      data: {
        totalCompanies: result.totalCompanies,
        successCount: result.successCount,
        failedCount: result.failedCount,
        totalCreated,
        totalLinked,
        details: result.results,
      },
    });
  } catch (error: any) {
    logger.error("전체 회사 동기화 실패:", error);
    res.status(500).json({
      success: false,
      message: "전체 동기화에 실패했습니다.",
      error: error.message,
    });
  }
};

/**
 * [PoC] screen_groups 기반 메뉴 트리 조회
 * 
 * 기존 menu_info 대신 screen_groups를 사이드바 메뉴로 사용하기 위한 테스트 API
 * - screen_groups를 트리 구조로 반환
 * - 각 그룹에 연결된 기본 화면의 URL 포함
 * - menu_objid를 통해 권한 체크 가능
 * 
 * DB 변경 없이 로직만 추가
 */
export const getMenuTreeFromScreenGroups = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userCompanyCode = req.user?.companyCode || "*";
    const { targetCompanyCode } = req.query;
    
    // 조회할 회사 코드 결정
    const companyCode = userCompanyCode === "*" && targetCompanyCode 
      ? String(targetCompanyCode) 
      : userCompanyCode;
    
    logger.info("[PoC] screen_groups 기반 메뉴 트리 조회", { 
      userCompanyCode, 
      targetCompanyCode: companyCode 
    });
    
    // 1. screen_groups 조회 (계층 구조 포함)
    const groupsQuery = `
      SELECT 
        sg.id,
        sg.group_name,
        sg.group_code,
        sg.parent_group_id,
        sg.group_level,
        sg.display_order,
        sg.icon,
        sg.is_active,
        sg.menu_objid,
        sg.company_code,
        -- 기본 화면 정보 (URL 생성용)
        (
          SELECT json_build_object(
            'screen_id', sd.screen_id,
            'screen_name', sd.screen_name,
            'screen_code', sd.screen_code
          )
          FROM screen_group_screens sgs
          JOIN screen_definitions sd ON sgs.screen_id = sd.screen_id
          WHERE sgs.group_id = sg.id AND sgs.company_code = sg.company_code
          ORDER BY 
            CASE WHEN sgs.is_default = 'Y' THEN 0 ELSE 1 END,
            sgs.display_order ASC
          LIMIT 1
        ) as default_screen,
        -- 하위 화면 개수
        (
          SELECT COUNT(*) 
          FROM screen_group_screens sgs 
          WHERE sgs.group_id = sg.id AND sgs.company_code = sg.company_code
        ) as screen_count
      FROM screen_groups sg
      WHERE sg.company_code = $1
        AND (sg.is_active = 'Y' OR sg.is_active IS NULL)
      ORDER BY sg.group_level ASC, sg.display_order ASC, sg.group_name ASC
    `;
    
    const groupsResult = await pool.query(groupsQuery, [companyCode]);
    
    // 2. 트리 구조로 변환
    const groups = groupsResult.rows;
    const groupMap = new Map<number, any>();
    const rootGroups: any[] = [];
    
    // 먼저 모든 그룹을 Map에 저장
    for (const group of groups) {
      const menuItem = {
        id: group.id,
        objid: group.menu_objid || group.id, // 권한 체크용 (menu_objid 우선)
        name: group.group_name,
        name_kor: group.group_name,
        icon: group.icon,
        url: group.default_screen 
          ? `/screens/${group.default_screen.screen_id}` 
          : null,
        screen_id: group.default_screen?.screen_id || null,
        screen_code: group.default_screen?.screen_code || null,
        screen_count: parseInt(group.screen_count) || 0,
        parent_id: group.parent_group_id,
        level: group.group_level || 0,
        display_order: group.display_order || 0,
        is_active: group.is_active === 'Y',
        menu_objid: group.menu_objid, // 기존 권한 시스템 연결용
        children: [],
        // menu_info 호환 필드
        menu_name_kor: group.group_name,
        menu_url: group.default_screen 
          ? `/screens/${group.default_screen.screen_id}` 
          : null,
        parent_obj_id: null, // 나중에 설정
        seq: group.display_order || 0,
        status: group.is_active === 'Y' ? 'active' : 'inactive',
      };
      
      groupMap.set(group.id, menuItem);
    }
    
    // 부모-자식 관계 설정
    for (const group of groups) {
      const menuItem = groupMap.get(group.id);
      
      if (group.parent_group_id && groupMap.has(group.parent_group_id)) {
        const parent = groupMap.get(group.parent_group_id);
        parent.children.push(menuItem);
        menuItem.parent_obj_id = parent.objid;
      } else {
        // 최상위 그룹
        rootGroups.push(menuItem);
        menuItem.parent_obj_id = "0";
      }
    }
    
    // 3. 통계 정보
    const stats = {
      totalGroups: groups.length,
      groupsWithScreens: groups.filter(g => g.default_screen).length,
      groupsWithMenuObjid: groups.filter(g => g.menu_objid).length,
      rootGroups: rootGroups.length,
    };
    
    logger.info("[PoC] screen_groups 메뉴 트리 생성 완료", stats);
    
    res.json({
      success: true,
      message: "[PoC] screen_groups 기반 메뉴 트리",
      data: rootGroups,
      stats,
      // 플랫 리스트도 제공 (기존 menu_info 형식 호환)
      flatList: Array.from(groupMap.values()).map(item => ({
        objid: String(item.objid),
        OBJID: String(item.objid),
        menu_name_kor: item.name,
        MENU_NAME_KOR: item.name,
        menu_url: item.url,
        MENU_URL: item.url,
        parent_obj_id: String(item.parent_obj_id || "0"),
        PARENT_OBJ_ID: String(item.parent_obj_id || "0"),
        seq: item.seq,
        SEQ: item.seq,
        status: item.status,
        STATUS: item.status,
        menu_type: 1, // 사용자 메뉴
        MENU_TYPE: 1,
        screen_group_id: item.id,
        menu_objid: item.menu_objid,
      })),
    });
    
  } catch (error: any) {
    logger.error("[PoC] screen_groups 메뉴 트리 조회 실패:", error);
    res.status(500).json({
      success: false,
      message: "메뉴 트리 조회에 실패했습니다.",
      error: error.message,
    });
  }
};

// ============================================================
// POP 전용 화면 그룹 API
// hierarchy_path LIKE 'POP/%' 필터로 POP 카테고리만 조회
// ============================================================

// POP 화면 그룹 목록 조회 (카테고리 트리용)
export const getPopScreenGroups = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const companyCode = req.user?.companyCode || "*";
    const { searchTerm } = req.query;

    let whereClause = "WHERE (hierarchy_path LIKE 'POP/%' OR hierarchy_path = 'POP')";
    const params: any[] = [];
    let paramIndex = 1;

    // 회사 코드 필터링 (멀티테넌시) - 일반 회사는 자기 회사 데이터만
    if (companyCode !== "*") {
      whereClause += ` AND company_code = $${paramIndex}`;
      params.push(companyCode);
      paramIndex++;
    }

    // 검색어 필터링
    if (searchTerm) {
      whereClause += ` AND (group_name ILIKE $${paramIndex} OR group_code ILIKE $${paramIndex} OR description ILIKE $${paramIndex})`;
      params.push(`%${searchTerm}%`);
      paramIndex++;
    }

    // POP 그룹 조회 (POP 레이아웃이 있는 화면만 카운트/포함)
    const dataQuery = `
      SELECT 
        sg.*,
        (SELECT COUNT(*) FROM screen_group_screens sgs
         INNER JOIN screen_layouts_pop slp ON sgs.screen_id = slp.screen_id AND sgs.company_code = slp.company_code
         WHERE sgs.group_id = sg.id AND sgs.company_code = sg.company_code) as screen_count,
        (SELECT json_agg(
          json_build_object(
            'id', sgs.id,
            'screen_id', sgs.screen_id,
            'screen_name', sd.screen_name,
            'screen_role', sgs.screen_role,
            'display_order', sgs.display_order,
            'is_default', sgs.is_default,
            'table_name', sd.table_name
          ) ORDER BY sgs.display_order
        ) FROM screen_group_screens sgs 
        LEFT JOIN screen_definitions sd ON sgs.screen_id = sd.screen_id
        INNER JOIN screen_layouts_pop slp ON sgs.screen_id = slp.screen_id AND sgs.company_code = slp.company_code
        WHERE sgs.group_id = sg.id AND sgs.company_code = sg.company_code
        ) as screens
      FROM screen_groups sg
      ${whereClause}
      ORDER BY sg.display_order ASC, sg.hierarchy_path ASC
    `;
    
    const result = await pool.query(dataQuery, params);

    logger.info("POP 화면 그룹 목록 조회", { companyCode, count: result.rows.length });

    res.json({
      success: true,
      data: result.rows,
    });
  } catch (error: any) {
    logger.error("POP 화면 그룹 목록 조회 실패:", error);
    res.status(500).json({ success: false, message: "POP 화면 그룹 목록 조회에 실패했습니다.", error: error.message });
  }
};

// POP 화면 그룹 생성 (hierarchy_path 자동 설정)
export const createPopScreenGroup = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const userCompanyCode = req.user?.companyCode || "*";
    const userId = req.user?.userId || "";
    const { group_name, group_code, description, icon, display_order, parent_group_id, target_company_code } = req.body;

    if (!group_name || !group_code) {
      return res.status(400).json({ success: false, message: "그룹명과 그룹코드는 필수입니다." });
    }

    // 회사 코드 결정
    const effectiveCompanyCode = target_company_code || userCompanyCode;
    if (userCompanyCode !== "*" && effectiveCompanyCode !== userCompanyCode) {
      return res.status(403).json({ success: false, message: "다른 회사의 그룹을 생성할 권한이 없습니다." });
    }

    // hierarchy_path 계산 - POP 하위로 설정
    let hierarchyPath = "POP";
    if (parent_group_id) {
      // 부모 그룹의 hierarchy_path 조회
      const parentResult = await pool.query(
        `SELECT hierarchy_path FROM screen_groups WHERE id = $1`,
        [parent_group_id]
      );
      if (parentResult.rows.length > 0) {
        hierarchyPath = `${parentResult.rows[0].hierarchy_path}/${group_code}`;
      }
    } else {
      // 최상위 POP 카테고리
      hierarchyPath = `POP/${group_code}`;
    }

    // 중복 체크
    const duplicateCheck = await pool.query(
      `SELECT id FROM screen_groups WHERE group_code = $1 AND company_code = $2`,
      [group_code, effectiveCompanyCode]
    );
    if (duplicateCheck.rows.length > 0) {
      return res.status(400).json({ success: false, message: "동일한 그룹코드가 이미 존재합니다." });
    }

    // 그룹 생성 (writer 컬럼 사용, is_active는 'Y' - 기존 스키마에 맞춤)
    const insertQuery = `
      INSERT INTO screen_groups (
        group_name, group_code, description, icon, display_order, 
        parent_group_id, hierarchy_path, company_code, writer, is_active
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'Y')
      RETURNING *
    `;
    const insertParams = [
      group_name,
      group_code,
      description || null,
      icon || null,
      display_order || 0,
      parent_group_id || null,
      hierarchyPath,
      effectiveCompanyCode,
      userId,
    ];

    const result = await pool.query(insertQuery, insertParams);
    
    logger.info("POP 화면 그룹 생성", { groupId: result.rows[0].id, groupCode: group_code, companyCode: effectiveCompanyCode });

    res.json({ success: true, data: result.rows[0], message: "POP 화면 그룹이 생성되었습니다." });
  } catch (error: any) {
    logger.error("POP 화면 그룹 생성 실패:", error);
    res.status(500).json({ success: false, message: "POP 화면 그룹 생성에 실패했습니다.", error: error.message });
  }
};

// POP 화면 그룹 수정
export const updatePopScreenGroup = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const companyCode = req.user?.companyCode || "*";
    const { group_name, description, icon, display_order, is_active } = req.body;

    // 기존 그룹 확인
    let checkQuery = `SELECT * FROM screen_groups WHERE id = $1`;
    const checkParams: any[] = [id];
    if (companyCode !== "*") {
      checkQuery += ` AND company_code = $2`;
      checkParams.push(companyCode);
    }

    const existing = await pool.query(checkQuery, checkParams);
    if (existing.rows.length === 0) {
      return res.status(404).json({ success: false, message: "그룹을 찾을 수 없습니다." });
    }

    // POP 그룹인지 확인
    if (!existing.rows[0].hierarchy_path?.startsWith("POP")) {
      return res.status(400).json({ success: false, message: "POP 그룹만 수정할 수 있습니다." });
    }

    // 업데이트
    const updateQuery = `
      UPDATE screen_groups
      SET group_name = COALESCE($1, group_name),
          description = COALESCE($2, description),
          icon = COALESCE($3, icon),
          display_order = COALESCE($4, display_order),
          is_active = COALESCE($5, is_active),
          updated_date = NOW()
      WHERE id = $6
      RETURNING *
    `;
    const updateParams = [group_name, description, icon, display_order, is_active, id];
    const result = await pool.query(updateQuery, updateParams);

    logger.info("POP 화면 그룹 수정", { groupId: id, companyCode });

    res.json({ success: true, data: result.rows[0], message: "POP 화면 그룹이 수정되었습니다." });
  } catch (error: any) {
    logger.error("POP 화면 그룹 수정 실패:", error);
    res.status(500).json({ success: false, message: "POP 화면 그룹 수정에 실패했습니다.", error: error.message });
  }
};

// POP 화면 그룹 삭제
export const deletePopScreenGroup = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { id } = req.params;
    const companyCode = req.user?.companyCode || "*";

    // 기존 그룹 확인
    let checkQuery = `SELECT * FROM screen_groups WHERE id = $1`;
    const checkParams: any[] = [id];
    if (companyCode !== "*") {
      checkQuery += ` AND company_code = $2`;
      checkParams.push(companyCode);
    }

    const existing = await pool.query(checkQuery, checkParams);
    if (existing.rows.length === 0) {
      // 그룹이 다른 회사 소속인지 확인하여 구체적 메시지 제공
      const anyGroup = await pool.query(`SELECT company_code FROM screen_groups WHERE id = $1`, [id]);
      if (anyGroup.rows.length > 0) {
        return res.status(403).json({ 
          success: false, 
          message: `이 그룹은 ${anyGroup.rows[0].company_code === "*" ? "최고관리자" : anyGroup.rows[0].company_code} 소속이라 삭제할 수 없습니다.` 
        });
      }
      return res.status(404).json({ success: false, message: "그룹을 찾을 수 없습니다." });
    }

    // POP 그룹인지 확인
    if (!existing.rows[0].hierarchy_path?.startsWith("POP")) {
      return res.status(400).json({ success: false, message: "POP 그룹만 삭제할 수 있습니다." });
    }

    // 하위 그룹 확인
    const childCheck = await pool.query(
      `SELECT COUNT(*) as count FROM screen_groups WHERE parent_group_id = $1`,
      [id]
    );
    if (parseInt(childCheck.rows[0].count) > 0) {
      return res.status(400).json({ 
        success: false, 
        message: `하위 그룹이 ${childCheck.rows[0].count}개 있어 삭제할 수 없습니다. 하위 그룹을 먼저 삭제해주세요.` 
      });
    }

    // 연결된 화면 확인
    const screenCheck = await pool.query(
      `SELECT COUNT(*) as count FROM screen_group_screens WHERE group_id = $1`,
      [id]
    );
    if (parseInt(screenCheck.rows[0].count) > 0) {
      return res.status(400).json({ 
        success: false, 
        message: `그룹에 연결된 화면이 ${screenCheck.rows[0].count}개 있어 삭제할 수 없습니다. 화면을 먼저 제거해주세요.` 
      });
    }

    // 삭제
    await pool.query(`DELETE FROM screen_groups WHERE id = $1`, [id]);

    logger.info("POP 화면 그룹 삭제", { groupId: id, companyCode });

    res.json({ success: true, message: "POP 화면 그룹이 삭제되었습니다." });
  } catch (error: any) {
    logger.error("POP 화면 그룹 삭제 실패:", error);
    res.status(500).json({ success: false, message: "POP 화면 그룹 삭제에 실패했습니다.", error: error.message });
  }
};

// POP 루트 그룹 확보 (최고관리자 전용 - 일반 회사는 복사 기능으로 배포)
export const ensurePopRootGroup = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const companyCode = req.user?.companyCode || "*";
    
    // 최고관리자만 자동 생성
    if (companyCode !== "*") {
      const existing = await pool.query(
        `SELECT * FROM screen_groups WHERE hierarchy_path = 'POP' AND company_code = $1`,
        [companyCode]
      );
      if (existing.rows.length > 0) {
        return res.json({ success: true, data: existing.rows[0] });
      }
      return res.json({ success: true, data: null, message: "POP 루트 그룹이 없습니다." });
    }

    // 최고관리자(*): 루트 그룹 확인 후 없으면 생성
    const checkQuery = `
      SELECT * FROM screen_groups 
      WHERE hierarchy_path = 'POP' AND company_code = '*'
    `;
    const existing = await pool.query(checkQuery, []);

    if (existing.rows.length > 0) {
      return res.json({ success: true, data: existing.rows[0] });
    }

    const insertQuery = `
      INSERT INTO screen_groups (
        group_name, group_code, hierarchy_path, company_code, 
        description, display_order, is_active, writer
      ) VALUES ('POP 화면', 'POP', 'POP', '*', 'POP 화면 관리 루트', 0, 'Y', $1)
      RETURNING *
    `;
    const result = await pool.query(insertQuery, [req.user?.userId || ""]);

    logger.info("POP 루트 그룹 생성", { groupId: result.rows[0].id });

    res.json({ success: true, data: result.rows[0], message: "POP 루트 그룹이 생성되었습니다." });
  } catch (error: any) {
    logger.error("POP 루트 그룹 확보 실패:", error);
    res.status(500).json({ success: false, message: "POP 루트 그룹 확보에 실패했습니다.", error: error.message });
  }
};