import { query, queryOne, transaction } from "../database/db";
import { logger } from "../utils/logger";
import {
  Language,
  LangKey,
  LangText,
  LangCategory,
  CreateLanguageRequest,
  UpdateLanguageRequest,
  CreateLangKeyRequest,
  UpdateLangKeyRequest,
  SaveLangTextsRequest,
  GetLangKeysParams,
  GetUserTextParams,
  BatchTranslationRequest,
  GenerateKeyRequest,
  CreateOverrideKeyRequest,
  ApiResponse,
} from "../types/multilang";

export class MultiLangService {
  constructor() {}

  // =====================================================
  // 카테고리 관련 메서드
  // =====================================================

  /**
   * 카테고리 목록 조회 (트리 구조)
   */
  async getCategories(): Promise<LangCategory[]> {
    try {
      logger.info("카테고리 목록 조회 시작");

      const categories = await query<{
        category_id: number;
        category_code: string;
        category_name: string;
        parent_id: number | null;
        level: number;
        key_prefix: string;
        description: string | null;
        sort_order: number;
        is_active: string;
      }>(
        `SELECT category_id, category_code, category_name, parent_id, 
                level, key_prefix, description, sort_order, is_active
         FROM multi_lang_category
         WHERE is_active = 'Y'
         ORDER BY level ASC, sort_order ASC, category_name ASC`
      );

      // 트리 구조로 변환
      const categoryMap = new Map<number, LangCategory>();
      const rootCategories: LangCategory[] = [];

      // 모든 카테고리를 맵에 저장
      categories.forEach((cat) => {
        const category: LangCategory = {
          categoryId: cat.category_id,
          categoryCode: cat.category_code,
          categoryName: cat.category_name,
          parentId: cat.parent_id,
          level: cat.level,
          keyPrefix: cat.key_prefix,
          description: cat.description || undefined,
          sortOrder: cat.sort_order,
          isActive: cat.is_active,
          children: [],
        };
        categoryMap.set(cat.category_id, category);
      });

      // 부모-자식 관계 설정
      categoryMap.forEach((category) => {
        if (category.parentId && categoryMap.has(category.parentId)) {
          const parent = categoryMap.get(category.parentId)!;
          parent.children = parent.children || [];
          parent.children.push(category);
        } else if (!category.parentId) {
          rootCategories.push(category);
        }
      });

      logger.info(`카테고리 목록 조회 완료: ${categories.length}개`);
      return rootCategories;
    } catch (error) {
      logger.error("카테고리 목록 조회 중 오류 발생:", error);
      throw new Error(
        `카테고리 목록 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 카테고리 단일 조회
   */
  async getCategoryById(categoryId: number): Promise<LangCategory | null> {
    try {
      const category = await queryOne<{
        category_id: number;
        category_code: string;
        category_name: string;
        parent_id: number | null;
        level: number;
        key_prefix: string;
        description: string | null;
        sort_order: number;
        is_active: string;
      }>(
        `SELECT category_id, category_code, category_name, parent_id,
                level, key_prefix, description, sort_order, is_active
         FROM multi_lang_category
         WHERE category_id = $1`,
        [categoryId]
      );

      if (!category) {
        return null;
      }

      return {
        categoryId: category.category_id,
        categoryCode: category.category_code,
        categoryName: category.category_name,
        parentId: category.parent_id,
        level: category.level,
        keyPrefix: category.key_prefix,
        description: category.description || undefined,
        sortOrder: category.sort_order,
        isActive: category.is_active,
      };
    } catch (error) {
      logger.error("카테고리 조회 중 오류 발생:", error);
      throw new Error(
        `카테고리 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 카테고리 경로 조회 (부모 포함)
   */
  async getCategoryPath(categoryId: number): Promise<LangCategory[]> {
    try {
      const categories = await query<{
        category_id: number;
        category_code: string;
        category_name: string;
        parent_id: number | null;
        level: number;
        key_prefix: string;
        description: string | null;
        sort_order: number;
        is_active: string;
      }>(
        `WITH RECURSIVE category_path AS (
           SELECT category_id, category_code, category_name, parent_id,
                  level, key_prefix, description, sort_order, is_active
           FROM multi_lang_category
           WHERE category_id = $1
           UNION ALL
           SELECT c.category_id, c.category_code, c.category_name, c.parent_id,
                  c.level, c.key_prefix, c.description, c.sort_order, c.is_active
           FROM multi_lang_category c
           INNER JOIN category_path cp ON c.category_id = cp.parent_id
         )
         SELECT * FROM category_path ORDER BY level ASC`,
        [categoryId]
      );

      return categories.map((cat) => ({
        categoryId: cat.category_id,
        categoryCode: cat.category_code,
        categoryName: cat.category_name,
        parentId: cat.parent_id,
        level: cat.level,
        keyPrefix: cat.key_prefix,
        description: cat.description || undefined,
        sortOrder: cat.sort_order,
        isActive: cat.is_active,
      }));
    } catch (error) {
      logger.error("카테고리 경로 조회 중 오류 발생:", error);
      throw new Error(
        `카테고리 경로 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 키 자동 생성
   */
  async generateKey(params: GenerateKeyRequest): Promise<number> {
    try {
      logger.info("키 자동 생성 시작", { params });

      // 카테고리 경로 조회
      const categoryPath = await this.getCategoryPath(params.categoryId);
      if (categoryPath.length === 0) {
        throw new Error("존재하지 않는 카테고리입니다");
      }

      // lang_key 자동 생성 (prefix.meaning 형식)
      const prefixes = categoryPath.map((c) => c.keyPrefix);
      const langKey = [...prefixes, params.keyMeaning].join(".");

      // 중복 체크
      const existingKey = await queryOne<{ key_id: number }>(
        `SELECT key_id FROM multi_lang_key_master 
         WHERE company_code = $1 AND lang_key = $2`,
        [params.companyCode, langKey]
      );

      if (existingKey) {
        throw new Error(`이미 존재하는 키입니다: ${langKey}`);
      }

      // 트랜잭션으로 키와 텍스트 생성
      let keyId: number = 0;

      await transaction(async (client) => {
        // 키 생성
        const keyResult = await client.query(
          `INSERT INTO multi_lang_key_master 
           (company_code, lang_key, category_id, key_meaning, usage_note, description, is_active, created_by, updated_by)
           VALUES ($1, $2, $3, $4, $5, $6, 'Y', $7, $7)
           RETURNING key_id`,
          [
            params.companyCode,
            langKey,
            params.categoryId,
            params.keyMeaning,
            params.usageNote || null,
            params.usageNote || null,
            params.createdBy || "system",
          ]
        );

        keyId = keyResult.rows[0].key_id;

        // 텍스트 생성
        for (const text of params.texts) {
          await client.query(
            `INSERT INTO multi_lang_text 
             (key_id, lang_code, lang_text, is_active, created_by, updated_by)
             VALUES ($1, $2, $3, 'Y', $4, $4)`,
            [keyId, text.langCode, text.langText, params.createdBy || "system"]
          );
        }
      });

      logger.info("키 자동 생성 완료", { keyId, langKey });
      return keyId;
    } catch (error) {
      logger.error("키 자동 생성 중 오류 발생:", error);
      throw new Error(
        `키 자동 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 회사별 오버라이드 키 생성
   */
  async createOverrideKey(params: CreateOverrideKeyRequest): Promise<number> {
    try {
      logger.info("오버라이드 키 생성 시작", { params });

      // 원본 키 조회
      const baseKey = await queryOne<{
        key_id: number;
        company_code: string;
        lang_key: string;
        category_id: number | null;
        key_meaning: string | null;
      }>(
        `SELECT key_id, company_code, lang_key, category_id, key_meaning
         FROM multi_lang_key_master WHERE key_id = $1`,
        [params.baseKeyId]
      );

      if (!baseKey) {
        throw new Error("원본 키를 찾을 수 없습니다");
      }

      // 공통 키(*)만 오버라이드 가능
      if (baseKey.company_code !== "*") {
        throw new Error("공통 키(*)만 오버라이드 할 수 있습니다");
      }

      // 이미 오버라이드 키가 있는지 확인
      const existingOverride = await queryOne<{ key_id: number }>(
        `SELECT key_id FROM multi_lang_key_master 
         WHERE company_code = $1 AND lang_key = $2`,
        [params.companyCode, baseKey.lang_key]
      );

      if (existingOverride) {
        throw new Error("이미 해당 회사의 오버라이드 키가 존재합니다");
      }

      let keyId: number = 0;

      await transaction(async (client) => {
        // 오버라이드 키 생성
        const keyResult = await client.query(
          `INSERT INTO multi_lang_key_master 
           (company_code, lang_key, category_id, key_meaning, base_key_id, is_active, created_by, updated_by)
           VALUES ($1, $2, $3, $4, $5, 'Y', $6, $6)
           RETURNING key_id`,
          [
            params.companyCode,
            baseKey.lang_key,
            baseKey.category_id,
            baseKey.key_meaning,
            params.baseKeyId,
            params.createdBy || "system",
          ]
        );

        keyId = keyResult.rows[0].key_id;

        // 텍스트 생성
        for (const text of params.texts) {
          await client.query(
            `INSERT INTO multi_lang_text 
             (key_id, lang_code, lang_text, is_active, created_by, updated_by)
             VALUES ($1, $2, $3, 'Y', $4, $4)`,
            [keyId, text.langCode, text.langText, params.createdBy || "system"]
          );
        }
      });

      logger.info("오버라이드 키 생성 완료", { keyId, langKey: baseKey.lang_key });
      return keyId;
    } catch (error) {
      logger.error("오버라이드 키 생성 중 오류 발생:", error);
      throw new Error(
        `오버라이드 키 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 회사의 오버라이드 키 목록 조회
   */
  async getOverrideKeys(companyCode: string): Promise<LangKey[]> {
    try {
      logger.info("오버라이드 키 목록 조회 시작", { companyCode });

      const keys = await query<{
        key_id: number;
        company_code: string;
        lang_key: string;
        category_id: number | null;
        key_meaning: string | null;
        usage_note: string | null;
        base_key_id: number | null;
        is_active: string;
        created_date: Date | null;
      }>(
        `SELECT key_id, company_code, lang_key, category_id, key_meaning,
                usage_note, base_key_id, is_active, created_date
         FROM multi_lang_key_master
         WHERE company_code = $1 AND base_key_id IS NOT NULL
         ORDER BY lang_key ASC`,
        [companyCode]
      );

      return keys.map((k) => ({
        keyId: k.key_id,
        companyCode: k.company_code,
        langKey: k.lang_key,
        categoryId: k.category_id ?? undefined,
        keyMeaning: k.key_meaning ?? undefined,
        usageNote: k.usage_note ?? undefined,
        baseKeyId: k.base_key_id ?? undefined,
        isActive: k.is_active,
        createdDate: k.created_date ?? undefined,
      }));
    } catch (error) {
      logger.error("오버라이드 키 목록 조회 중 오류 발생:", error);
      throw new Error(
        `오버라이드 키 목록 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 키 존재 여부 및 미리보기 확인
   */
  async previewGeneratedKey(categoryId: number, keyMeaning: string, companyCode: string): Promise<{
    langKey: string;
    exists: boolean;
    isOverride: boolean;
    baseKeyId?: number;
  }> {
    try {
      // 카테고리 경로 조회
      const categoryPath = await this.getCategoryPath(categoryId);
      if (categoryPath.length === 0) {
        throw new Error("존재하지 않는 카테고리입니다");
      }

      // lang_key 생성
      const prefixes = categoryPath.map((c) => c.keyPrefix);
      const langKey = [...prefixes, keyMeaning].join(".");

      // 공통 키 확인
      const commonKey = await queryOne<{ key_id: number }>(
        `SELECT key_id FROM multi_lang_key_master 
         WHERE company_code = '*' AND lang_key = $1`,
        [langKey]
      );

      // 회사별 키 확인
      const companyKey = await queryOne<{ key_id: number }>(
        `SELECT key_id FROM multi_lang_key_master 
         WHERE company_code = $1 AND lang_key = $2`,
        [companyCode, langKey]
      );

      return {
        langKey,
        exists: !!companyKey,
        isOverride: !!commonKey && !companyKey,
        baseKeyId: commonKey?.key_id,
      };
    } catch (error) {
      logger.error("키 미리보기 중 오류 발생:", error);
      throw new Error(
        `키 미리보기 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 언어 목록 조회
   */
  async getLanguages(): Promise<Language[]> {
    try {
      logger.info("언어 목록 조회 시작");

      const languages = await query<{
        lang_code: string;
        lang_name: string;
        lang_native: string | null;
        is_active: string | null;
        sort_order: number | null;
        created_date: Date | null;
        created_by: string | null;
        updated_date: Date | null;
        updated_by: string | null;
      }>(
        `SELECT lang_code, lang_name, lang_native, is_active, sort_order,
                created_date, created_by, updated_date, updated_by
         FROM language_master
         ORDER BY sort_order ASC, lang_code ASC`
      );

      const mappedLanguages: Language[] = languages.map((lang) => ({
        langCode: lang.lang_code,
        langName: lang.lang_name,
        langNative: lang.lang_native || "",
        isActive: lang.is_active || "N",
        sortOrder: lang.sort_order ?? undefined,
        createdDate: lang.created_date || undefined,
        createdBy: lang.created_by || undefined,
        updatedDate: lang.updated_date || undefined,
        updatedBy: lang.updated_by || undefined,
      }));

      logger.info(`언어 목록 조회 완료: ${mappedLanguages.length}개`);
      return mappedLanguages;
    } catch (error) {
      logger.error("언어 목록 조회 중 오류 발생:", error);
      throw new Error(
        `언어 목록 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 언어 생성
   */
  async createLanguage(languageData: CreateLanguageRequest): Promise<Language> {
    try {
      logger.info("언어 생성 시작", { languageData });

      // 중복 체크
      const existingLanguage = await queryOne<{ lang_code: string }>(
        `SELECT lang_code FROM language_master WHERE lang_code = $1`,
        [languageData.langCode]
      );

      if (existingLanguage) {
        throw new Error(
          `이미 존재하는 언어 코드입니다: ${languageData.langCode}`
        );
      }

      // 언어 생성
      const createdLanguage = await queryOne<{
        lang_code: string;
        lang_name: string;
        lang_native: string | null;
        is_active: string | null;
        sort_order: number | null;
        created_date: Date | null;
        created_by: string | null;
        updated_date: Date | null;
        updated_by: string | null;
      }>(
        `INSERT INTO language_master 
         (lang_code, lang_name, lang_native, is_active, sort_order, created_by, updated_by)
         VALUES ($1, $2, $3, $4, $5, $6, $7)
         RETURNING *`,
        [
          languageData.langCode,
          languageData.langName,
          languageData.langNative,
          languageData.isActive || "Y",
          languageData.sortOrder || 0,
          languageData.createdBy || "system",
          languageData.updatedBy || "system",
        ]
      );

      logger.info("언어 생성 완료", { langCode: createdLanguage!.lang_code });

      return {
        langCode: createdLanguage!.lang_code,
        langName: createdLanguage!.lang_name,
        langNative: createdLanguage!.lang_native || "",
        isActive: createdLanguage!.is_active || "N",
        sortOrder: createdLanguage!.sort_order ?? undefined,
        createdDate: createdLanguage!.created_date || undefined,
        createdBy: createdLanguage!.created_by || undefined,
        updatedDate: createdLanguage!.updated_date || undefined,
        updatedBy: createdLanguage!.updated_by || undefined,
      };
    } catch (error) {
      logger.error("언어 생성 중 오류 발생:", error);
      throw new Error(
        `언어 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 언어 수정
   */
  async updateLanguage(
    langCode: string,
    languageData: UpdateLanguageRequest
  ): Promise<Language> {
    try {
      logger.info("언어 수정 시작", { langCode, languageData });

      // 기존 언어 확인
      const existingLanguage = await queryOne<{ lang_code: string }>(
        `SELECT lang_code FROM language_master WHERE lang_code = $1`,
        [langCode]
      );

      if (!existingLanguage) {
        throw new Error(`언어를 찾을 수 없습니다: ${langCode}`);
      }

      // 동적 UPDATE 쿼리 생성
      const updates: string[] = [];
      const values: any[] = [];
      let paramIndex = 1;

      if (languageData.langName) {
        updates.push(`lang_name = $${paramIndex++}`);
        values.push(languageData.langName);
      }
      if (languageData.langNative) {
        updates.push(`lang_native = $${paramIndex++}`);
        values.push(languageData.langNative);
      }
      if (languageData.isActive) {
        updates.push(`is_active = $${paramIndex++}`);
        values.push(languageData.isActive);
      }
      if (languageData.sortOrder !== undefined) {
        updates.push(`sort_order = $${paramIndex++}`);
        values.push(languageData.sortOrder);
      }

      updates.push(`updated_by = $${paramIndex++}`);
      values.push(languageData.updatedBy || "system");

      values.push(langCode); // WHERE 조건용

      // 언어 수정
      const updatedLanguage = await queryOne<{
        lang_code: string;
        lang_name: string;
        lang_native: string | null;
        is_active: string | null;
        sort_order: number | null;
        created_date: Date | null;
        created_by: string | null;
        updated_date: Date | null;
        updated_by: string | null;
      }>(
        `UPDATE language_master SET ${updates.join(", ")}
         WHERE lang_code = $${paramIndex}
         RETURNING *`,
        values
      );

      logger.info("언어 수정 완료", { langCode });

      return {
        langCode: updatedLanguage!.lang_code,
        langName: updatedLanguage!.lang_name,
        langNative: updatedLanguage!.lang_native || "",
        isActive: updatedLanguage!.is_active || "N",
        sortOrder: updatedLanguage!.sort_order ?? undefined,
        createdDate: updatedLanguage!.created_date || undefined,
        createdBy: updatedLanguage!.created_by || undefined,
        updatedDate: updatedLanguage!.updated_date || undefined,
        updatedBy: updatedLanguage!.updated_by || undefined,
      };
    } catch (error) {
      logger.error("언어 수정 중 오류 발생:", error);
      throw new Error(
        `언어 수정 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 언어 상태 토글
   */
  async toggleLanguage(langCode: string): Promise<string> {
    try {
      logger.info("언어 상태 토글 시작", { langCode });

      // 현재 언어 조회
      const currentLanguage = await queryOne<{ is_active: string | null }>(
        `SELECT is_active FROM language_master WHERE lang_code = $1`,
        [langCode]
      );

      if (!currentLanguage) {
        throw new Error(`언어를 찾을 수 없습니다: ${langCode}`);
      }

      const newStatus = currentLanguage.is_active === "Y" ? "N" : "Y";

      // 상태 업데이트
      await query(
        `UPDATE language_master 
         SET is_active = $1, updated_by = $2
         WHERE lang_code = $3`,
        [newStatus, "system", langCode]
      );

      const result = newStatus === "Y" ? "활성화" : "비활성화";
      logger.info("언어 상태 토글 완료", { langCode, result });

      return result;
    } catch (error) {
      logger.error("언어 상태 토글 중 오류 발생:", error);
      throw new Error(
        `언어 상태 토글 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 다국어 키 목록 조회
   */
  async getLangKeys(params: GetLangKeysParams): Promise<LangKey[]> {
    try {
      logger.info("다국어 키 목록 조회 시작", { params });

      const whereConditions: string[] = [];
      const values: any[] = [];
      let paramIndex = 1;

      // 회사 코드 필터
      if (params.companyCode) {
        whereConditions.push(`company_code = $${paramIndex++}`);
        values.push(params.companyCode);
      }

      // 메뉴 코드 필터
      if (params.menuCode) {
        whereConditions.push(`usage_note = $${paramIndex++}`);
        values.push(params.menuCode);
      }

      // 카테고리 필터 (하위 카테고리 포함)
      if (params.categoryId) {
        whereConditions.push(`category_id IN (
          WITH RECURSIVE category_tree AS (
            SELECT category_id FROM multi_lang_category WHERE category_id = $${paramIndex}
            UNION ALL
            SELECT c.category_id FROM multi_lang_category c
            INNER JOIN category_tree ct ON c.parent_id = ct.category_id
          )
          SELECT category_id FROM category_tree
        )`);
        values.push(params.categoryId);
        paramIndex++;
      }

      // 검색 조건 (OR)
      if (params.searchText) {
        whereConditions.push(
          `(lang_key ILIKE $${paramIndex} OR description ILIKE $${paramIndex} OR usage_note ILIKE $${paramIndex})`
        );
        values.push(`%${params.searchText}%`);
        paramIndex++;
      }

      const whereClause =
        whereConditions.length > 0
          ? `WHERE ${whereConditions.join(" AND ")}`
          : "";

      const langKeys = await query<{
        key_id: number;
        company_code: string;
        usage_note: string | null;
        lang_key: string;
        description: string | null;
        is_active: string | null;
        category_id: number | null;
        created_date: Date | null;
        created_by: string | null;
        updated_date: Date | null;
        updated_by: string | null;
      }>(
        `SELECT key_id, company_code, usage_note, lang_key, description, is_active, category_id,
                created_date, created_by, updated_date, updated_by
         FROM multi_lang_key_master
         ${whereClause}
         ORDER BY company_code ASC, usage_note ASC, lang_key ASC`,
        values
      );

      const mappedKeys: LangKey[] = langKeys.map((key) => ({
        keyId: key.key_id,
        companyCode: key.company_code,
        menuName: key.usage_note || undefined,
        langKey: key.lang_key,
        description: key.description || undefined,
        isActive: key.is_active || "Y",
        categoryId: key.category_id || undefined,
        createdDate: key.created_date || undefined,
        createdBy: key.created_by || undefined,
        updatedDate: key.updated_date || undefined,
        updatedBy: key.updated_by || undefined,
      }));

      logger.info(`다국어 키 목록 조회 완료: ${mappedKeys.length}개`);
      return mappedKeys;
    } catch (error) {
      logger.error("다국어 키 목록 조회 중 오류 발생:", error);
      throw new Error(
        `다국어 키 목록 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 특정 키의 다국어 텍스트 조회
   */
  async getLangTexts(keyId: number): Promise<LangText[]> {
    try {
      logger.info("다국어 텍스트 조회 시작", { keyId });

      const langTexts = await query<{
        text_id: number;
        key_id: number;
        lang_code: string;
        lang_text: string;
        is_active: string | null;
        created_date: Date | null;
        created_by: string | null;
        updated_date: Date | null;
        updated_by: string | null;
      }>(
        `SELECT text_id, key_id, lang_code, lang_text, is_active,
                created_date, created_by, updated_date, updated_by
         FROM multi_lang_text
         WHERE key_id = $1 AND is_active = $2
         ORDER BY lang_code ASC`,
        [keyId, "Y"]
      );

      const mappedTexts: LangText[] = langTexts.map((text) => ({
        textId: text.text_id,
        keyId: text.key_id,
        langCode: text.lang_code,
        langText: text.lang_text,
        isActive: text.is_active || "Y",
        createdDate: text.created_date || undefined,
        createdBy: text.created_by || undefined,
        updatedDate: text.updated_date || undefined,
        updatedBy: text.updated_by || undefined,
      }));

      logger.info(`다국어 텍스트 조회 완료: ${mappedTexts.length}개`);
      return mappedTexts;
    } catch (error) {
      logger.error("다국어 텍스트 조회 중 오류 발생:", error);
      throw new Error(
        `다국어 텍스트 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 다국어 키 생성
   */
  async createLangKey(keyData: CreateLangKeyRequest): Promise<number> {
    try {
      logger.info("다국어 키 생성 시작", { keyData });

      // 중복 체크
      const existingKey = await queryOne<{ key_id: number }>(
        `SELECT key_id FROM multi_lang_key_master 
         WHERE company_code = $1 AND lang_key = $2`,
        [keyData.companyCode, keyData.langKey]
      );

      if (existingKey) {
        throw new Error(
          `동일한 회사에 이미 존재하는 언어키입니다: ${keyData.langKey}`
        );
      }

      // 다국어 키 생성
      const createdKey = await queryOne<{ key_id: number }>(
        `INSERT INTO multi_lang_key_master 
         (company_code, usage_note, lang_key, description, is_active, created_by, updated_by)
         VALUES ($1, $2, $3, $4, $5, $6, $7)
         RETURNING key_id`,
        [
          keyData.companyCode,
          keyData.menuName || null,
          keyData.langKey,
          keyData.description || null,
          keyData.isActive || "Y",
          keyData.createdBy || "system",
          keyData.updatedBy || "system",
        ]
      );

      logger.info("다국어 키 생성 완료", {
        keyId: createdKey!.key_id,
        langKey: keyData.langKey,
      });

      return createdKey!.key_id;
    } catch (error) {
      logger.error("다국어 키 생성 중 오류 발생:", error);
      throw new Error(
        `다국어 키 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 다국어 키 수정
   */
  async updateLangKey(
    keyId: number,
    keyData: UpdateLangKeyRequest
  ): Promise<void> {
    try {
      logger.info("다국어 키 수정 시작", { keyId, keyData });

      // 기존 키 확인
      const existingKey = await queryOne<{ key_id: number }>(
        `SELECT key_id FROM multi_lang_key_master WHERE key_id = $1`,
        [keyId]
      );

      if (!existingKey) {
        throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
      }

      // 중복 체크 (자신을 제외하고)
      if (keyData.companyCode && keyData.langKey) {
        const duplicateKey = await queryOne<{ key_id: number }>(
          `SELECT key_id FROM multi_lang_key_master 
           WHERE company_code = $1 AND lang_key = $2 AND key_id != $3`,
          [keyData.companyCode, keyData.langKey, keyId]
        );

        if (duplicateKey) {
          throw new Error(
            `동일한 회사에 이미 존재하는 언어키입니다: ${keyData.langKey}`
          );
        }
      }

      // 동적 UPDATE 쿼리 생성
      const updates: string[] = [];
      const values: any[] = [];
      let paramIndex = 1;

      if (keyData.companyCode) {
        updates.push(`company_code = $${paramIndex++}`);
        values.push(keyData.companyCode);
      }
      if (keyData.menuName !== undefined) {
        updates.push(`usage_note = $${paramIndex++}`);
        values.push(keyData.menuName);
      }
      if (keyData.langKey) {
        updates.push(`lang_key = $${paramIndex++}`);
        values.push(keyData.langKey);
      }
      if (keyData.description !== undefined) {
        updates.push(`description = $${paramIndex++}`);
        values.push(keyData.description);
      }

      updates.push(`updated_by = $${paramIndex++}`);
      values.push(keyData.updatedBy || "system");

      values.push(keyId); // WHERE 조건용

      // 다국어 키 수정
      await query(
        `UPDATE multi_lang_key_master SET ${updates.join(", ")}
         WHERE key_id = $${paramIndex}`,
        values
      );

      logger.info("다국어 키 수정 완료", { keyId });
    } catch (error) {
      logger.error("다국어 키 수정 중 오류 발생:", error);
      throw new Error(
        `다국어 키 수정 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 다국어 키 삭제
   */
  async deleteLangKey(keyId: number): Promise<void> {
    try {
      logger.info("다국어 키 삭제 시작", { keyId });

      // 기존 키 확인
      const existingKey = await queryOne<{ key_id: number }>(
        `SELECT key_id FROM multi_lang_key_master WHERE key_id = $1`,
        [keyId]
      );

      if (!existingKey) {
        throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
      }

      // 트랜잭션으로 키와 연관된 텍스트 모두 삭제
      await transaction(async (client) => {
        // 관련된 다국어 텍스트 삭제
        await client.query(`DELETE FROM multi_lang_text WHERE key_id = $1`, [
          keyId,
        ]);

        // 다국어 키 삭제
        await client.query(
          `DELETE FROM multi_lang_key_master WHERE key_id = $1`,
          [keyId]
        );
      });

      logger.info("다국어 키 삭제 완료", { keyId });
    } catch (error) {
      logger.error("다국어 키 삭제 중 오류 발생:", error);
      throw new Error(
        `다국어 키 삭제 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 다국어 키 상태 토글
   */
  async toggleLangKey(keyId: number): Promise<string> {
    try {
      logger.info("다국어 키 상태 토글 시작", { keyId });

      // 현재 키 조회
      const currentKey = await queryOne<{ is_active: string | null }>(
        `SELECT is_active FROM multi_lang_key_master WHERE key_id = $1`,
        [keyId]
      );

      if (!currentKey) {
        throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
      }

      const newStatus = currentKey.is_active === "Y" ? "N" : "Y";

      // 상태 업데이트
      await query(
        `UPDATE multi_lang_key_master 
         SET is_active = $1, updated_by = $2
         WHERE key_id = $3`,
        [newStatus, "system", keyId]
      );

      const result = newStatus === "Y" ? "활성화" : "비활성화";
      logger.info("다국어 키 상태 토글 완료", { keyId, result });

      return result;
    } catch (error) {
      logger.error("다국어 키 상태 토글 중 오류 발생:", error);
      throw new Error(
        `다국어 키 상태 토글 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 다국어 텍스트 저장/수정
   */
  async saveLangTexts(
    keyId: number,
    textData: SaveLangTextsRequest
  ): Promise<void> {
    try {
      logger.info("다국어 텍스트 저장 시작", {
        keyId,
        textCount: textData.texts.length,
      });

      // 기존 키 확인
      const existingKey = await queryOne<{ key_id: number }>(
        `SELECT key_id FROM multi_lang_key_master WHERE key_id = $1`,
        [keyId]
      );

      if (!existingKey) {
        throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
      }

      // 트랜잭션으로 기존 텍스트 삭제 후 새로 생성
      await transaction(async (client) => {
        // 기존 텍스트 삭제
        await client.query(`DELETE FROM multi_lang_text WHERE key_id = $1`, [
          keyId,
        ]);

        // 새로운 텍스트 삽입
        if (textData.texts.length > 0) {
          for (const text of textData.texts) {
            await client.query(
              `INSERT INTO multi_lang_text 
               (key_id, lang_code, lang_text, is_active, created_by, updated_by)
               VALUES ($1, $2, $3, $4, $5, $6)`,
              [
                keyId,
                text.langCode,
                text.langText,
                text.isActive || "Y",
                text.createdBy || "system",
                text.updatedBy || "system",
              ]
            );
          }
        }
      });

      logger.info("다국어 텍스트 저장 완료", {
        keyId,
        savedCount: textData.texts.length,
      });
    } catch (error) {
      logger.error("다국어 텍스트 저장 중 오류 발생:", error);
      throw new Error(
        `다국어 텍스트 저장 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 사용자별 다국어 텍스트 조회
   */
  async getUserText(params: GetUserTextParams): Promise<string> {
    try {
      logger.info("사용자별 다국어 텍스트 조회 시작", { params });

      const result = await queryOne<{ lang_text: string }>(
        `SELECT mlt.lang_text
         FROM multi_lang_text mlt
         INNER JOIN multi_lang_key_master mlkm ON mlt.key_id = mlkm.key_id
         WHERE mlt.lang_code = $1
           AND mlt.is_active = $2
           AND mlkm.company_code = $3
           AND mlkm.usage_note = $4
           AND mlkm.lang_key = $5
           AND mlkm.is_active = $6`,
        [
          params.userLang,
          "Y",
          params.companyCode,
          params.menuCode,
          params.langKey,
          "Y",
        ]
      );

      if (!result) {
        logger.warn("사용자별 다국어 텍스트를 찾을 수 없음", { params });
        return params.langKey; // 기본값으로 키 반환
      }

      logger.info("사용자별 다국어 텍스트 조회 완료", {
        params,
        langText: result.lang_text,
      });

      return result.lang_text;
    } catch (error) {
      logger.error("사용자별 다국어 텍스트 조회 중 오류 발생:", error);
      throw new Error(
        `사용자별 다국어 텍스트 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 특정 키의 다국어 텍스트 조회
   */
  async getLangText(
    companyCode: string,
    langKey: string,
    langCode: string
  ): Promise<string> {
    try {
      logger.info("특정 키의 다국어 텍스트 조회 시작", {
        companyCode,
        langKey,
        langCode,
      });

      const result = await queryOne<{ lang_text: string }>(
        `SELECT mlt.lang_text
         FROM multi_lang_text mlt
         INNER JOIN multi_lang_key_master mlkm ON mlt.key_id = mlkm.key_id
         WHERE mlt.lang_code = $1
           AND mlt.is_active = $2
           AND mlkm.company_code = $3
           AND mlkm.lang_key = $4
           AND mlkm.is_active = $5`,
        [langCode, "Y", companyCode, langKey, "Y"]
      );

      if (!result) {
        logger.warn("특정 키의 다국어 텍스트를 찾을 수 없음", {
          companyCode,
          langKey,
          langCode,
        });
        return langKey; // 기본값으로 키 반환
      }

      logger.info("특정 키의 다국어 텍스트 조회 완료", {
        companyCode,
        langKey,
        langCode,
        langText: result.lang_text,
      });

      return result.lang_text;
    } catch (error) {
      logger.error("특정 키의 다국어 텍스트 조회 중 오류 발생:", error);
      throw new Error(
        `특정 키의 다국어 텍스트 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 배치 번역 조회 (회사별 우선순위 적용)
   * 우선순위: 회사별 키 > 공통 키(*)
   * 폴백: 요청 언어 번역이 없으면 KR 번역 사용
   */
  async getBatchTranslations(
    params: BatchTranslationRequest
  ): Promise<Record<string, string>> {
    try {
      logger.info("배치 번역 조회 시작", {
        companyCode: params.companyCode,
        menuCode: params.menuCode,
        userLang: params.userLang,
        keyCount: params.langKeys.length,
      });

      if (params.langKeys.length === 0) {
        return {};
      }

      // 모든 키에 대한 번역 조회
      const placeholders = params.langKeys
        .map((_, i) => `$${i + 4}`)
        .join(", ");

      // 회사별 우선순위를 적용하기 위해 정렬 수정
      // 회사별 키가 먼저 오도록 DESC 정렬 (company_code가 '*'보다 특정 회사 코드가 알파벳 순으로 앞)
      // 또는 CASE WHEN을 사용하여 명시적으로 우선순위 설정
      const translations = await query<{
        lang_text: string;
        lang_key: string;
        company_code: string;
        priority: number;
      }>(
        `SELECT mlt.lang_text, mlkm.lang_key, mlkm.company_code,
                CASE WHEN mlkm.company_code = $3 THEN 1 ELSE 2 END as priority
         FROM multi_lang_text mlt
         INNER JOIN multi_lang_key_master mlkm ON mlt.key_id = mlkm.key_id
         WHERE mlt.lang_code = $1
           AND mlt.is_active = $2
           AND mlkm.lang_key IN (${placeholders})
           AND mlkm.company_code IN ($3, '*')
           AND mlkm.is_active = $2
         ORDER BY mlkm.lang_key ASC, priority ASC`,
        [params.userLang, "Y", params.companyCode, ...params.langKeys]
      );

      const result: Record<string, string> = {};
      const processedKeys = new Set<string>();

      // 우선순위 기반으로 번역 적용
      // priority가 낮은 것(회사별)이 먼저 오므로, 먼저 처리된 키는 덮어쓰지 않음
      translations.forEach((translation) => {
        const langKey = translation.lang_key;
        if (params.langKeys.includes(langKey) && !processedKeys.has(langKey)) {
          result[langKey] = translation.lang_text;
          processedKeys.add(langKey);
        }
      });

      // 번역이 없는 키들에 대해 KR 폴백 조회 (요청 언어가 KR이 아닌 경우)
      const missingKeys = params.langKeys.filter((key) => !processedKeys.has(key));
      
      if (missingKeys.length > 0 && params.userLang !== "KR") {
        logger.info("KR 폴백 번역 조회 시작", { missingCount: missingKeys.length });
        
        const fallbackPlaceholders = missingKeys.map((_, i) => `$${i + 3}`).join(", ");
        const fallbackTranslations = await query<{
          lang_text: string;
          lang_key: string;
          company_code: string;
          priority: number;
        }>(
          `SELECT mlt.lang_text, mlkm.lang_key, mlkm.company_code,
                  CASE WHEN mlkm.company_code = $2 THEN 1 ELSE 2 END as priority
           FROM multi_lang_text mlt
           INNER JOIN multi_lang_key_master mlkm ON mlt.key_id = mlkm.key_id
           WHERE mlt.lang_code = 'KR'
             AND mlt.is_active = $1
             AND mlkm.lang_key IN (${fallbackPlaceholders})
             AND mlkm.company_code IN ($2, '*')
             AND mlkm.is_active = $1
           ORDER BY mlkm.lang_key ASC, priority ASC`,
          ["Y", params.companyCode, ...missingKeys]
        );

        // KR 폴백 적용
        const fallbackProcessed = new Set<string>();
        fallbackTranslations.forEach((translation) => {
          const langKey = translation.lang_key;
          if (!result[langKey] && !fallbackProcessed.has(langKey)) {
            result[langKey] = translation.lang_text;
            fallbackProcessed.add(langKey);
          }
        });
        
        logger.info("KR 폴백 번역 조회 완료", {
          missingCount: missingKeys.length,
          foundFallback: fallbackTranslations.length,
        });
      }

      // 여전히 없는 키는 키 자체를 반환 (최후의 폴백)
      params.langKeys.forEach((key) => {
        if (!result[key]) {
          result[key] = key;
        }
      });

      logger.info("배치 번역 조회 완료", {
        totalKeys: params.langKeys.length,
        foundTranslations: translations.length,
        companyOverrides: translations.filter(t => t.company_code !== '*').length,
        resultKeys: Object.keys(result).length,
      });

      return result;
    } catch (error) {
      logger.error("배치 번역 조회 중 오류 발생:", error);
      throw new Error(
        `배치 번역 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 언어 삭제
   */
  async deleteLanguage(langCode: string): Promise<void> {
    try {
      logger.info("언어 삭제 시작", { langCode });

      // 기존 언어 확인
      const existingLanguage = await queryOne<{ lang_code: string }>(
        `SELECT lang_code FROM language_master WHERE lang_code = $1`,
        [langCode]
      );

      if (!existingLanguage) {
        throw new Error(`언어를 찾을 수 없습니다: ${langCode}`);
      }

      // 트랜잭션으로 언어와 관련 텍스트 삭제
      await transaction(async (client) => {
        // 해당 언어의 다국어 텍스트 삭제
        const deleteResult = await client.query(
          `DELETE FROM multi_lang_text WHERE lang_code = $1`,
          [langCode]
        );

        logger.info(`삭제된 다국어 텍스트 수: ${deleteResult.rowCount}`, {
          langCode,
        });

        // 언어 마스터 삭제
        await client.query(`DELETE FROM language_master WHERE lang_code = $1`, [
          langCode,
        ]);
      });

      logger.info("언어 삭제 완료", { langCode });
    } catch (error) {
      logger.error("언어 삭제 중 오류 발생:", error);
      throw new Error(
        `언어 삭제 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  // =====================================================
  // 회사/메뉴 기반 카테고리 자동 생성 메서드
  // =====================================================

  /**
   * 화면(screen) 루트 카테고리 확인 또는 생성
   */
  async ensureScreenRootCategory(): Promise<number> {
    try {
      // 기존 screen 카테고리 확인
      const existing = await queryOne<{ category_id: number }>(
        `SELECT category_id FROM multi_lang_category 
         WHERE category_code = 'screen' AND parent_id IS NULL`,
        []
      );

      if (existing) {
        return existing.category_id;
      }

      // 없으면 생성
      const result = await queryOne<{ category_id: number }>(
        `INSERT INTO multi_lang_category 
         (category_code, category_name, parent_id, level, key_prefix, description, sort_order, is_active, created_date)
         VALUES ('screen', '화면', NULL, 1, 'screen', '화면 디자이너에서 자동 생성된 다국어 키', 100, 'Y', NOW())
         RETURNING category_id`,
        []
      );

      logger.info("화면 루트 카테고리 생성", { categoryId: result?.category_id });
      return result!.category_id;
    } catch (error) {
      logger.error("화면 루트 카테고리 생성 실패:", error);
      throw error;
    }
  }

  /**
   * 회사 카테고리 확인 또는 생성
   */
  async ensureCompanyCategory(companyCode: string, companyName: string): Promise<number> {
    try {
      const screenRootId = await this.ensureScreenRootCategory();

      // 기존 회사 카테고리 확인
      const existing = await queryOne<{ category_id: number }>(
        `SELECT category_id FROM multi_lang_category 
         WHERE category_code = $1 AND parent_id = $2`,
        [companyCode, screenRootId]
      );

      if (existing) {
        return existing.category_id;
      }

      // 회사 카테고리 생성
      const displayName = companyCode === "*" ? "공통" : companyName;
      const keyPrefix = companyCode === "*" ? "common" : companyCode.toLowerCase();

      const result = await queryOne<{ category_id: number }>(
        `INSERT INTO multi_lang_category 
         (category_code, category_name, parent_id, level, key_prefix, description, sort_order, is_active, created_date)
         VALUES ($1, $2, $3, 2, $4, $5, $6, 'Y', NOW())
         RETURNING category_id`,
        [
          companyCode,
          displayName,
          screenRootId,
          keyPrefix,
          `${displayName} 회사의 화면 다국어`,
          companyCode === "*" ? 0 : 10,
        ]
      );

      logger.info("회사 카테고리 생성", { companyCode, categoryId: result?.category_id });
      return result!.category_id;
    } catch (error) {
      logger.error("회사 카테고리 생성 실패:", error);
      throw error;
    }
  }

  /**
   * 메뉴 카테고리 확인 또는 생성 (메뉴 경로 전체)
   */
  async ensureMenuCategory(
    companyCode: string,
    companyName: string,
    menuPath: string[] // ["영업관리", "수주관리"]
  ): Promise<number> {
    try {
      if (menuPath.length === 0) {
        return await this.ensureCompanyCategory(companyCode, companyName);
      }

      let parentId = await this.ensureCompanyCategory(companyCode, companyName);
      let currentLevel = 3;

      for (const menuName of menuPath) {
        // 현재 메뉴 카테고리 확인
        const existing = await queryOne<{ category_id: number }>(
          `SELECT category_id FROM multi_lang_category 
           WHERE category_name = $1 AND parent_id = $2`,
          [menuName, parentId]
        );

        if (existing) {
          parentId = existing.category_id;
        } else {
          // 메뉴 카테고리 생성
          const menuCode = `${companyCode}_${menuName}`.replace(/\s+/g, "_");
          const keyPrefix = menuName.toLowerCase().replace(/\s+/g, "_");

          const result = await queryOne<{ category_id: number }>(
            `INSERT INTO multi_lang_category 
             (category_code, category_name, parent_id, level, key_prefix, description, sort_order, is_active, created_date)
             VALUES ($1, $2, $3, $4, $5, $6, 0, 'Y', NOW())
             RETURNING category_id`,
            [menuCode, menuName, parentId, currentLevel, keyPrefix, `${menuName} 메뉴의 다국어`]
          );

          logger.info("메뉴 카테고리 생성", { menuName, categoryId: result?.category_id });
          parentId = result!.category_id;
        }

        currentLevel++;
      }

      return parentId;
    } catch (error) {
      logger.error("메뉴 카테고리 생성 실패:", error);
      throw error;
    }
  }

  /**
   * 메뉴 경로 조회 (menu_info에서 부모 메뉴까지)
   */
  async getMenuPath(menuObjId: string): Promise<string[]> {
    try {
      const menus = await query<{ menu_name_kor: string; level: number }>(
        `WITH RECURSIVE menu_path AS (
           SELECT objid, parent_obj_id, menu_name_kor, 1 as level
           FROM menu_info
           WHERE objid = $1
           UNION ALL
           SELECT m.objid, m.parent_obj_id, m.menu_name_kor, mp.level + 1
           FROM menu_info m
           INNER JOIN menu_path mp ON m.objid = mp.parent_obj_id
           WHERE m.parent_obj_id IS NOT NULL AND m.parent_obj_id != 0
         )
         SELECT menu_name_kor, level FROM menu_path 
         WHERE menu_name_kor IS NOT NULL
         ORDER BY level DESC`,
        [menuObjId]
      );

      return menus.map((m) => m.menu_name_kor);
    } catch (error) {
      logger.error("메뉴 경로 조회 실패:", error);
      return [];
    }
  }

  /**
   * 화면 그룹 경로 조회 (screen_groups에서 계층 구조 조회)
   * @param screenId 화면 ID
   * @returns 그룹 경로 배열 (최상위 → 현재 그룹 순서)
   */
  async getScreenGroupPath(screenId: number): Promise<string[]> {
    try {
      // 화면이 속한 그룹 조회
      const screenGroup = await queryOne<{ group_id: number }>(
        `SELECT group_id FROM screen_group_screens WHERE screen_id = $1 LIMIT 1`,
        [screenId]
      );

      if (!screenGroup) {
        logger.info("화면이 그룹에 속하지 않음", { screenId });
        return [];
      }

      // 그룹의 계층 구조 경로 조회 (최상위 → 현재 그룹 순서)
      const groups = await query<{ group_name: string; group_level: number }>(
        `WITH RECURSIVE group_path AS (
           SELECT id, parent_group_id, group_name, group_level, 1 as depth
           FROM screen_groups
           WHERE id = $1
           UNION ALL
           SELECT g.id, g.parent_group_id, g.group_name, g.group_level, gp.depth + 1
           FROM screen_groups g
           INNER JOIN group_path gp ON g.id = gp.parent_group_id
           WHERE g.parent_group_id IS NOT NULL
         )
         SELECT group_name, group_level FROM group_path
         ORDER BY depth DESC`,
        [screenGroup.group_id]
      );

      return groups.map((g) => g.group_name);
    } catch (error) {
      logger.error("화면 그룹 경로 조회 실패:", error);
      return [];
    }
  }

  /**
   * 화면 그룹 기반 카테고리 확인 또는 생성
   * @param companyCode 회사 코드
   * @param companyName 회사 이름
   * @param groupPath 그룹 경로 (상위 → 하위 순서)
   * @returns 최종 카테고리 ID
   */
  async ensureScreenGroupCategory(
    companyCode: string,
    companyName: string,
    groupPath: string[]
  ): Promise<number> {
    try {
      if (groupPath.length === 0) {
        // 그룹이 없으면 회사 카테고리만 반환
        return await this.ensureCompanyCategory(companyCode, companyName);
      }

      let parentId = await this.ensureCompanyCategory(companyCode, companyName);
      let currentLevel = 3; // SCREEN(1) > Company(2) > Group(3)

      for (const groupName of groupPath) {
        // 현재 그룹 카테고리 확인
        const existing = await queryOne<{ category_id: number }>(
          `SELECT category_id FROM multi_lang_category 
           WHERE category_name = $1 AND parent_id = $2`,
          [groupName, parentId]
        );

        if (existing) {
          parentId = existing.category_id;
        } else {
          // 그룹 카테고리 생성
          const groupCode = `${companyCode}_GROUP_${groupName}`.replace(/\s+/g, "_");
          const keyPrefix = groupName.toLowerCase().replace(/\s+/g, "_");

          const result = await queryOne<{ category_id: number }>(
            `INSERT INTO multi_lang_category 
             (category_code, category_name, parent_id, level, key_prefix, description, sort_order, is_active, created_date)
             VALUES ($1, $2, $3, $4, $5, $6, 0, 'Y', NOW())
             RETURNING category_id`,
            [groupCode, groupName, parentId, currentLevel, keyPrefix, `${groupName} 화면 그룹의 다국어`]
          );

          logger.info("화면 그룹 카테고리 생성", { groupName, categoryId: result?.category_id });
          parentId = result!.category_id;
        }

        currentLevel++;
      }

      return parentId;
    } catch (error) {
      logger.error("화면 그룹 카테고리 생성 실패:", error);
      throw error;
    }
  }

  /**
   * 화면 라벨 다국어 키 자동 생성
   * 화면 그룹 기반으로 카테고리 생성 (기존 메뉴 기반에서 변경)
   */
  async generateScreenLabelKeys(params: {
    screenId: number;
    companyCode: string;
    companyName: string;
    menuObjId?: string; // 하위 호환성 유지 (미사용, 화면 그룹 기반으로 변경)
    labels: Array<{ componentId: string; label: string; type?: string }>;
  }): Promise<Array<{ componentId: string; keyId: number; langKey: string }>> {
    try {
      logger.info("화면 라벨 다국어 키 자동 생성 시작", {
        screenId: params.screenId,
        companyCode: params.companyCode,
        labelCount: params.labels.length,
      });

      // 화면 그룹 경로 조회 (화면이 속한 그룹의 계층 구조)
      const groupPath = await this.getScreenGroupPath(params.screenId);
      logger.info("화면 그룹 경로 조회 완료", { screenId: params.screenId, groupPath });

      // 화면 그룹 기반 카테고리 확보
      const categoryId = await this.ensureScreenGroupCategory(
        params.companyCode,
        params.companyName,
        groupPath
      );

      // 카테고리 경로 조회 (키 생성용)
      const categoryPath = await this.getCategoryPath(categoryId);
      const keyPrefixParts = categoryPath.map((c) => c.keyPrefix);

      const results: Array<{ componentId: string; keyId: number; langKey: string }> = [];

      for (const labelInfo of params.labels) {
        // 라벨을 키 형태로 변환 (한글 → 스네이크케이스)
        const keyMeaning = this.labelToKeyMeaning(labelInfo.label);
        const langKey = [...keyPrefixParts, keyMeaning].join(".");

        // 기존 키 확인
        const existingKey = await queryOne<{ key_id: number }>(
          `SELECT key_id FROM multi_lang_key_master 
           WHERE lang_key = $1 AND company_code = $2`,
          [langKey, params.companyCode]
        );

        let keyId: number;

        if (existingKey) {
          keyId = existingKey.key_id;
          logger.info("기존 키 사용", { langKey, keyId });
        } else {
          // 새 키 생성
          const keyResult = await queryOne<{ key_id: number }>(
            `INSERT INTO multi_lang_key_master 
             (company_code, lang_key, description, is_active, category_id, key_meaning, created_date, created_by)
             VALUES ($1, $2, $3, 'Y', $4, $5, NOW(), 'system')
             RETURNING key_id`,
            [
              params.companyCode,
              langKey,
              `화면 ${params.screenId}의 ${labelInfo.type || "라벨"}: ${labelInfo.label}`,
              categoryId,
              keyMeaning,
            ]
          );

          keyId = keyResult!.key_id;

          // 한국어 텍스트 저장 (원문)
          await query(
            `INSERT INTO multi_lang_text (key_id, lang_code, lang_text, is_active, created_date, created_by)
             VALUES ($1, 'KR', $2, 'Y', NOW(), 'system')
             ON CONFLICT (key_id, lang_code) DO UPDATE SET lang_text = $2, updated_date = NOW()`,
            [keyId, labelInfo.label]
          );

          logger.info("새 키 생성", { langKey, keyId });
        }

        results.push({
          componentId: labelInfo.componentId,
          keyId,
          langKey,
        });
      }

      logger.info("화면 라벨 다국어 키 생성 완료", {
        screenId: params.screenId,
        generatedCount: results.length,
      });

      return results;
    } catch (error) {
      logger.error("화면 라벨 다국어 키 생성 실패:", error);
      throw new Error(
        `화면 라벨 다국어 키 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  /**
   * 라벨을 키 의미로 변환 (한글 → 스네이크케이스 또는 영문 유지)
   */
  private labelToKeyMeaning(label: string): string {
    // 이미 영문 스네이크케이스면 그대로 사용
    if (/^[a-z][a-z0-9_]*$/.test(label)) {
      return label;
    }

    // 영문 일반이면 스네이크케이스로 변환
    if (/^[A-Za-z][A-Za-z0-9 ]*$/.test(label)) {
      return label.toLowerCase().replace(/\s+/g, "_");
    }

    // 한글이면 간단한 변환 (특수문자 제거, 공백을 _로)
    return label
      .replace(/[^\w가-힣\s]/g, "")
      .replace(/\s+/g, "_")
      .toLowerCase();
  }
}
