import { randomUUID } from '@/tools';

// ---------------------------------------------------------------------------
// 角色 / 用户 / 日志（接口与后端 DTO 映射）
// ---------------------------------------------------------------------------

export interface RoleModel {
  id: string;
  name: string;
  menus: string[];
}
/**
 * 将接口返回的角色数据转为前端 `RoleModel`。
 *
 * @param data - 原始接口对象（含 `pid`、`rolename`、`menuIds` 等）
 * @returns 规范化后的角色模型
 */
export function fromRoleData(data: Record<string, any>): RoleModel {
  return {
    id: data.pid,
    name: data.rolename,
    menus: Array.isArray(data.menuIds) ? data.menuIds : [],
  };
}

export interface UserModel {
  id: string;
  name: string;
  account: string;
  roles: RoleModel[];
  /**
   * 需要重置密码
   */
  resetPassword?: boolean;
}
/**
 * 将接口返回的用户数据转为前端 `UserModel`。
 *
 * @param data - 原始接口对象（含 `pid`、`username`、`userid`、`roles`、`isFirst` 等）
 * @returns 规范化后的用户模型
 */
export function fromUserData(data: Record<string, any>): UserModel {
  return {
    id: data.pid,
    name: data.username,
    account: data.userid,
    roles: Array.isArray(data.roles) ? data.roles.map(fromRoleData) : [],
    resetPassword: data.isFirst === 0,
  };
}

export interface LoggerModel {
  uid: string;
  id?: string | number;
  title: string;
  date: string;
  operator: string;
  __raw__?: any;
}
/**
 * 将接口返回的日志条目转为前端 `LoggerModel`。
 *
 * @param data - 原始接口对象
 * @returns 规范化后的日志模型
 */
export function fromLoggerData(data: Record<string, any>): LoggerModel {
  return {
    uid:  data.pid ?? randomUUID(),
    id: data.operId,
    title: data.title,
    date: data.operTime,
    operator: data.operName,
    __raw__: data,
  };
}

// ---------------------------------------------------------------------------
// 菜单树（类型、展平、按 id 查找 / 过滤）
// ---------------------------------------------------------------------------

export interface MenuModel {
  id: string;
  label: string;
  path: string;
  meta: {
    title?: string;
    icon?: string;
  };
  children?: MenuModel[];
}
/**
 * 将接口返回的菜单树转为前端 `MenuModel[]`，并生成面包屑式 `label`、按 `meta.order` 排序。
 *
 * @param menus - 接口菜单数组；根调用时可不传 `parent`
 * @param parent - 递归用父节点，用于拼接 `label`
 * @returns 根调用返回菜单树；子调用无数据时返回 `undefined` 以便省略空 `children`
 */
export function fromMenusData(menus?: Record<string, any>[], parent?: MenuModel): MenuModel[] | undefined {
  if (!Array.isArray(menus) || menus.length === 0) return parent ? void 0 : [];
  return menus
    .sort((a, b) => (a.meta?.order ?? 0) - (b.meta?.order ?? 0))
    .map((item) => {
      item.meta ??= {};
      const menu = { ...item, label: [parent?.label, item.meta?.title].filter(Boolean).join(' / ') } as MenuModel;
      menu.children = fromMenusData(item.children, menu);
      return menu;
    });
}

/**
 * 展平后的叶子菜单项：`parents` 为从根到直接父级的祖先链（不含自身）。
 */
export type MenuLeafFlat = Omit<MenuModel, 'children'> & {
  parents: MenuModel[];
};
/**
 * 从菜单树中取出所有叶子节点（无 `children` 或 `children` 为空），按深度优先顺序展平，并附带祖先链。
 *
 * @param menus - 根级菜单数组
 * @returns 叶子项列表，每项带 `parents` 祖先路径
 */
export function flattenMenuLeaves(menus: MenuModel[]): MenuLeafFlat[] {
  const walk = (nodes: MenuModel[], ancestors: MenuModel[]): MenuLeafFlat[] =>
    nodes.flatMap((node) => {
      // 非叶子：继续向下 DFS，当前节点入栈作为子树的祖先
      if (node.children?.length) {
        return walk(node.children, [...ancestors, node]);
      }
      const { children: _c, ...leaf } = node;
      return [{ ...leaf, parents: [...ancestors] }];
    });
  return walk(menus, []);
}

/**
 * 在菜单树中按 id 列表查找节点，返回**扁平数组**。
 *
 * - 结果顺序与入参 `ids` 一致。
 * - 树中不存在的 id 会被跳过，不会占位。
 * - 树内同一 `id` 出现多次时，取 DFS **先遇到**的节点引用。
 *
 * @param menus - 完整菜单树
 * @param ids - 待查找的菜单 id 列表
 * @returns 命中的 `MenuModel` 数组（保持 `ids` 顺序）
 */
export function findMenusByIds(menus: MenuModel[], ids: string[]): MenuModel[] {
  if (ids.length === 0) return [];
  // DFS 建 id → 节点表；同一 id 多次出现时保留先遇到的引用
  const byId = new Map<string, MenuModel>();
  const walk = (nodes: MenuModel[]) => {
    for (const node of nodes) {
      if (!byId.has(node.id)) byId.set(node.id, node);
      if (node.children?.length) walk(node.children);
    }
  };
  walk(menus);
  return ids.map((id) => byId.get(id)).filter((n): n is MenuModel => n != null);
}

/**
 * 按 id 集合过滤菜单树，**保持树形结构**（浅拷贝节点，不修改入参树）。
 *
 * 流程分三步：
 * 1. **标记**：DFS 收集应保留的节点 id（可选把祖先 id 一并加入）。
 * 2. **裁剪**：只克隆 `keep` 集合中的节点及其保留子分支。
 * 3. **可选收缩**：`options.collapseSingleChild` 为 true 时去掉「仅有一个子节点」的中间层。
 *
 * @param menus - 完整菜单树
 * @param ids - 权限或配置中给出的菜单 id（可仅为叶子 id）
 * @param options - 可选行为配置
 * @param options.includeAncestors - `true`（默认）时，任一 id 命中会把从根到该节点的父级链一并保留，得到完整子树；`false` 时只保留 id 直接命中的节点（父级不在 `ids` 中则不会出现，可能多根或零散）。
 * @param options.collapseSingleChild - `true` 时，过滤后若节点只剩一个子节点则用子顶替父，可连续收缩至分叉或叶子；`false`（默认）保留原层级。
 * @returns 新的菜单树根数组；`ids` 为空时返回 `[]`
 */
export function filterMenuTreeByIds(
  menus: MenuModel[],
  ids?: string[],
  options?: { includeAncestors?: boolean; collapseSingleChild?: boolean },
): MenuModel[] {
  const idSet = new Set(ids?.filter(Boolean) ?? []);
  if (idSet.size === 0) return [];

  const includeAncestors = options?.includeAncestors ?? true;
  const collapseSingleChild = options?.collapseSingleChild ?? false;
  /** 裁剪阶段应保留的节点 id */
  const keep = new Set<string>();

  /** 阶段一：根据命中 id 填充 `keep` */
  const mark = (nodes: MenuModel[], ancestors: MenuModel[]) => {
    for (const node of nodes) {
      if (idSet.has(node.id)) {
        keep.add(node.id);
        // 侧栏等场景：子节点被授权时，父级占位也要保留
        if (includeAncestors) {
          for (const a of ancestors) keep.add(a.id);
        }
      }
      if (node.children?.length) mark(node.children, [...ancestors, node]);
    }
  };
  mark(menus, []);

  /** 阶段二：仅克隆 keep 内的子树 */
  const prune = (nodes: MenuModel[]): MenuModel[] => {
    const out: MenuModel[] = [];
    for (const node of nodes) {
      if (!keep.has(node.id)) continue;
      const nextChildren = node.children?.length ? prune(node.children) : undefined;
      out.push({
        ...node,
        children: nextChildren?.length ? nextChildren : undefined,
      });
    }
    return out;
  };

  const pruned = prune(menus);
  if (!collapseSingleChild) return pruned;

  /**
   * 阶段三：子树内若存在「单子父链」，自下而上递归折叠后，再用 while 吃掉残余单层包裹。
   */
  const collapse = (node: MenuModel): MenuModel => {
    const nextChildren = node.children?.length ? node.children.map(collapse) : undefined;
    let n: MenuModel = {
      ...node,
      children: nextChildren?.length ? nextChildren : undefined,
    };
    while (n.children?.length === 1) {
      n = n.children[0];
    }
    return n;
  };

  return pruned.map(collapse);
}
