import { Injectable } from '@nestjs/common';
import { AdminIdentity } from '../auth/admin/admin-identity';
import { SuperAdminService } from '../admin/super-admin.service';
import { CONSOLE_MOVED_MENU_URLS } from './console-moved-menus';
import { HIDDEN_MENU_URLS } from './hidden-menus';
import { MenuRepository } from './menu.repository';

@Injectable()
export class MenuService {
  constructor(
    private menuRepository: MenuRepository,
    private superAdmin: SuperAdminService,
  ) {}

  /**
   * 전체 메뉴 트리 조회 (권한 설정용)
   */
  async getAllMenuList() {
    const menu = await this.menuRepository.getAllMenuList();
    return this.buildMenuTree(menu);
  }

  /**
   * 관리자의 권한에 맞는 메뉴 트리 반환 (사이드바용)
   *
   * 최고관리자는 **권한 그룹 배분과 무관하게** 전체 트리를 받는다 — 사이드바 질의는 메뉴↔권한 매핑
   * inner join 이라 매핑 없는 메뉴는 누구에게도 보이지 않는다. 권한 그룹 표와 매핑 표는 건드리지 않는다.
   *
   * 콘솔로 옮긴 메뉴는 **두 갈래 모두에서** 빠진다 (hardening M6a DD6). 갈래를 늘리지 않고 목록 선택
   * 뒤 한 자리에서 뺀다 — 갈래마다 제외를 쓰면 한쪽을 고칠 때 다른 쪽이 조용히 어긋나고, 그때 그
   * 메뉴는 사이드바와 콘솔 양쪽에 뜬다. 한 갈래만 빼면 나중에 매핑이 하나 들어가는 날 같은 일이 난다.
   *
   * 숨긴 기능 메뉴(HIDDEN_MENU_URLS)도 같은 자리에서 뺀다. 다만 콘솔 이전과 달리 권한 설정용 전체 트리
   * (getAllMenuList)에는 남는다 — 권한 배분 대상에서는 지우지 않는다.
   */
  async getMyMenuTree(admin: AdminIdentity) {
    const menus = (await this.superAdmin.isSuperAdmin(admin))
      ? await this.menuRepository.getAllMenuList()
      : await this.menuRepository.getMenusByAdminId(admin.admId!);
    return this.buildMenuTree(
      this.excludeHiddenMenus(this.excludeConsoleMoved(menus)),
    );
  }

  /**
   * 콘솔로 옮긴 메뉴를 사이드바 목록에서 뺀다 (DD6·DD7).
   *
   * 트리를 세운 **뒤**가 아니라 평탄 목록에서 뺀다 — 트리에서 빼려면 부모·자식 두 경우를 나눠야 하고,
   * 그 분기가 「부모째 지웠다」와 「자식만 빠졌다」를 구별하지 못하는 자리를 만든다.
   *
   * **잎이라고 주장하지 않고 자손까지 함께 뺀다**(로컬 리뷰 LOW 2). 부모만 빼면 `buildMenuTree` 가
   * 자식의 부모 조회에 실패하고, 자식은 `mnuParentId` 가 있어 루트로도 올라가지 않는다 — 서브트리가
   * 흔적 없이 사라진다. 옮긴 메뉴의 자손은 부모가 콘솔로 갔으므로 사이드바에서 함께 빠지는 것이
   * 맞고, 그 사실을 가정이 아니라 코드로 만든다. 던지지 않는 이유는 `tn_menu` 에 자식 행 하나가
   * 들어오는 날 사이드바가 통째로 500 이 되면 안 되기 때문이다.
   */
  private excludeConsoleMoved(menus: any[]) {
    //* `mnuId` 가 없는 행은 집합에 넣지 않는다 — `undefined` 가 들어가면 `mnuParentId` 가 없는
    //*   루트 행들이 전부 자손으로 읽힌다.
    const removed = new Set<unknown>(
      menus
        .filter(
          (item) =>
            CONSOLE_MOVED_MENU_URLS.includes(item?.mnuUrl) &&
            item?.mnuId != null,
        )
        .map((item) => item.mnuId),
    );
    //* 고정점까지 돈다 — 깊이를 가정하지 않는다. 메뉴 행은 수십 건이라 이 비용은 보이지 않는다.
    for (let grew = removed.size > 0; grew; ) {
      grew = false;
      for (const item of menus) {
        if (
          item?.mnuId != null &&
          item?.mnuParentId != null &&
          !removed.has(item.mnuId) &&
          removed.has(item.mnuParentId)
        ) {
          removed.add(item.mnuId);
          grew = true;
        }
      }
    }
    return menus.filter((item) => !removed.has(item?.mnuId));
  }

  /**
   * 숨긴 기능 메뉴를 사이드바 목록에서 뺀다. 콘솔 이전과 같이 자손까지 함께 빼고, 더해서 **자식을 전부
   * 잃은 부모도 뺀다** — 「서비스 관리」(13)는 숨긴 두 메뉴만 자식으로 가져서 이 규칙이 없으면 빈 그룹으로
   * 남는다. 이 규칙은 숨김 제외에만 건다. 콘솔 이전은 기존 동작 그대로다.
   */
  private excludeHiddenMenus(menus: any[]) {
    const removed = new Set<unknown>(
      menus
        .filter(
          (item) =>
            HIDDEN_MENU_URLS.includes(item?.mnuUrl) && item?.mnuId != null,
        )
        .map((item) => item.mnuId),
    );
    for (let grew = removed.size > 0; grew; ) {
      grew = false;
      for (const item of menus) {
        if (item?.mnuId == null || removed.has(item.mnuId)) continue;
        const parentRemoved =
          item.mnuParentId != null && removed.has(item.mnuParentId);
        const children = menus.filter((c) => c?.mnuParentId === item.mnuId);
        const emptied =
          children.length > 0 && children.every((c) => removed.has(c.mnuId));
        if (parentRemoved || emptied) {
          removed.add(item.mnuId);
          grew = true;
        }
      }
    }
    return menus.filter((item) => !removed.has(item?.mnuId));
  }

  /**
   * 플랫 메뉴 목록을 트리 구조로 변환
   */
  private buildMenuTree(menus: any[]) {
    const menuMap = new Map();
    const treeList: any[] = [];

    // 1단계: 모든 메뉴를 Map에 등록
    menus.forEach((item) => {
      if (!item.mnuId) return;
      menuMap.set(item.mnuId, { ...item, selected: false, children: [] });
    });

    // 2단계: 부모-자식 관계 구성
    menus.forEach((item) => {
      if (!item.mnuId) return;

      if (item.mnuParentId) {
        const parent = menuMap.get(item.mnuParentId);
        if (parent) {
          parent.children.push(menuMap.get(item.mnuId));
        }
      } else {
        treeList.push(menuMap.get(item.mnuId));
      }
    });

    return treeList;
  }
}
