/**
 * 邀請制網絡：樹建構、上下線查詢、管理員移動（防環）。
 * 資料來源：TenantUserRecord.invite_code / parent_uid。
 */
import type {
  InviteMemberBrief,
  InviteTreeNode,
  MemberStatus,
  MyInviteNetwork,
  Role,
} from "../domain/types";
import type { TenantUserRecord } from "./tenantUsers";
import { loadTenantUsers, upsertTenantUser } from "./tenantUsers";

function normalizeStatus(s: unknown): MemberStatus {
  return s === "suspended" ? "suspended" : "active";
}

/** 計算每人直屬／全部子孫人數 */
function countDownlines(users: TenantUserRecord[]): Map<string, { direct: number; total: number }> {
  const children = new Map<string, string[]>();
  for (const u of users) {
    const p = u.parent_uid?.trim() || "";
    if (!p) continue;
    const list = children.get(p) || [];
    list.push(u.uid);
    children.set(p, list);
  }

  const memo = new Map<string, number>();
  function totalOf(uid: string, stack: Set<string>): number {
    if (memo.has(uid)) return memo.get(uid)!;
    if (stack.has(uid)) return 0; // 防壞資料環
    stack.add(uid);
    const kids = children.get(uid) || [];
    let n = kids.length;
    for (const k of kids) n += totalOf(k, stack);
    stack.delete(uid);
    memo.set(uid, n);
    return n;
  }

  const out = new Map<string, { direct: number; total: number }>();
  for (const u of users) {
    out.set(u.uid, {
      direct: (children.get(u.uid) || []).length,
      total: totalOf(u.uid, new Set()),
    });
  }
  return out;
}

function depthOf(
  uid: string,
  byUid: Map<string, TenantUserRecord>,
  cache: Map<string, number>,
  stack: Set<string>,
): number {
  if (cache.has(uid)) return cache.get(uid)!;
  if (stack.has(uid)) return 0;
  const u = byUid.get(uid);
  if (!u?.parent_uid) {
    cache.set(uid, 0);
    return 0;
  }
  stack.add(uid);
  const d = 1 + depthOf(u.parent_uid, byUid, cache, stack);
  stack.delete(uid);
  cache.set(uid, d);
  return d;
}

export function toInviteBrief(
  u: TenantUserRecord,
  counts: Map<string, { direct: number; total: number }>,
  depth: number,
): InviteMemberBrief {
  const c = counts.get(u.uid) || { direct: 0, total: 0 };
  return {
    uid: u.uid,
    email: u.email,
    display_name: u.display_name,
    invite_code: (u.invite_code || "").trim() || "—",
    parent_uid: u.parent_uid?.trim() || null,
    roles: u.roles as Role[],
    status: normalizeStatus(u.status),
    downline_count: c.direct,
    total_downline_count: c.total,
    depth,
    joined_at: u.created_at,
    avatar_url: u.avatar_url?.trim() || null,
  };
}

export function buildInviteBriefs(users: TenantUserRecord[]): Map<string, InviteMemberBrief> {
  const counts = countDownlines(users);
  const byUid = new Map(users.map((u) => [u.uid, u]));
  const depthCache = new Map<string, number>();
  const map = new Map<string, InviteMemberBrief>();
  for (const u of users) {
    const depth = depthOf(u.uid, byUid, depthCache, new Set());
    map.set(u.uid, toInviteBrief(u, counts, depth));
  }
  return map;
}

/**
 * 穩定錯誤碼（i18n key）。UI 用 t(err.message) 顯示雙語。
 * 與 play.err.* 同一風格。
 */
export const INVITE_ERR = {
  notFound: "invite.err.notFound",
  needAdmin: "invite.err.needAdmin",
  memberNotFound: "invite.err.memberNotFound",
  selfParent: "invite.err.selfParent",
  parentNotFound: "invite.err.parentNotFound",
  cycle: "invite.err.cycle",
  /** 已有邀請人，不可再補填 */
  alreadyBound: "invite.err.alreadyBound",
  /** 邀請碼空白／格式 */
  codeRequired: "invite.err.codeRequired",
  /** 邀請碼不存在 */
  codeNotFound: "invite.err.codeNotFound",
  /** 不能填自己的碼 */
  codeSelf: "invite.err.codeSelf",
} as const;

/**
 * 會員自行補填邀請碼（僅當尚無邀請人）。
 * 成功後 parent_uid 指向持有該碼的會員。
 */
export function applyInviteCode(uid: string, rawCode: string): MyInviteNetwork {
  const code = (rawCode || "").trim().toUpperCase();
  if (!code) throw new Error(INVITE_ERR.codeRequired);

  const users = loadTenantUsers();
  const me = users.find((u) => u.uid === uid);
  if (!me) throw new Error(INVITE_ERR.notFound);

  if ((me.parent_uid || "").trim()) {
    throw new Error(INVITE_ERR.alreadyBound);
  }

  const inviter = users.find(
    (u) => (u.invite_code || "").trim().toUpperCase() === code,
  );
  if (!inviter) throw new Error(INVITE_ERR.codeNotFound);
  if (inviter.uid === uid) throw new Error(INVITE_ERR.codeSelf);

  // 理論上 me 無 parent，不會成環；仍擋「邀請人是自己的子孫」
  if (isDescendantOf(uid, inviter.uid, users)) {
    throw new Error(INVITE_ERR.cycle);
  }

  upsertTenantUser({
    ...me,
    parent_uid: inviter.uid,
  });
  return getMyInviteNetwork(uid);
}

/** 一般會員：自己的邀請碼 + 上線 + 直屬下線 */
export function getMyInviteNetwork(uid: string): MyInviteNetwork {
  const users = loadTenantUsers();
  const briefs = buildInviteBriefs(users);
  const me = briefs.get(uid);
  if (!me) throw new Error(INVITE_ERR.notFound);

  const upline = me.parent_uid ? briefs.get(me.parent_uid) || null : null;
  const downlines = users
    .filter((u) => (u.parent_uid || "") === uid)
    .map((u) => briefs.get(u.uid)!)
    .filter(Boolean)
    .sort((a, b) => b.joined_at - a.joined_at);

  return {
    me,
    upline,
    downlines,
    total_downline_count: me.total_downline_count,
  };
}

/** 管理員：森林（多根） */
export function buildInviteForest(users?: TenantUserRecord[]): InviteTreeNode[] {
  const list = users ?? loadTenantUsers();
  const briefs = buildInviteBriefs(list);
  const byParent = new Map<string | null, InviteMemberBrief[]>();

  for (const u of list) {
    const brief = briefs.get(u.uid)!;
    const p = brief.parent_uid;
    // 上線不存在時當根，避免孤兒懸空
    const parentKey =
      p && briefs.has(p) ? p : null;
    const arr = byParent.get(parentKey) || [];
    arr.push(brief);
    byParent.set(parentKey, arr);
  }

  function build(uid: string | null): InviteTreeNode[] {
    const kids = byParent.get(uid) || [];
    kids.sort((a, b) => a.display_name.localeCompare(b.display_name, "zh-Hant"));
    return kids.map((k) => ({
      ...k,
      children: build(k.uid),
    }));
  }

  return build(null);
}

/** 是否為 ancestor 的子孫（含自己） */
export function isDescendantOf(
  ancestorUid: string,
  maybeDescendantUid: string,
  users: TenantUserRecord[],
): boolean {
  if (ancestorUid === maybeDescendantUid) return true;
  const byUid = new Map(users.map((u) => [u.uid, u]));
  let cur = byUid.get(maybeDescendantUid);
  const seen = new Set<string>();
  while (cur?.parent_uid) {
    if (seen.has(cur.uid)) break;
    seen.add(cur.uid);
    if (cur.parent_uid === ancestorUid) return true;
    cur = byUid.get(cur.parent_uid);
  }
  return false;
}

/**
 * 管理員移動節點：將 uid 掛到 newParentUid 下（null = 升為根）。
 * 不可掛到自己或自己的子孫底下。
 */
export function adminMoveInviteMember(
  actorIsAdmin: boolean,
  uid: string,
  newParentUid: string | null,
): InviteMemberBrief {
  if (!actorIsAdmin) throw new Error(INVITE_ERR.needAdmin);
  const users = loadTenantUsers();
  const target = users.find((u) => u.uid === uid);
  if (!target) throw new Error(INVITE_ERR.memberNotFound);

  const parent = newParentUid?.trim() || null;
  if (parent === uid) throw new Error(INVITE_ERR.selfParent);
  if (parent) {
    const p = users.find((u) => u.uid === parent);
    if (!p) throw new Error(INVITE_ERR.parentNotFound);
    if (isDescendantOf(uid, parent, users)) {
      throw new Error(INVITE_ERR.cycle);
    }
  }

  const current = target.parent_uid?.trim() || null;
  if (current === parent) {
    const briefs = buildInviteBriefs(users);
    return briefs.get(uid)!;
  }

  const next = upsertTenantUser({
    ...target,
    parent_uid: parent,
  });
  const briefs = buildInviteBriefs(loadTenantUsers());
  return briefs.get(next.uid)!;
}

/** 扁平候選上線列表（移動選單用） */
export function listInviteParentCandidates(
  excludeSubtreeRootUid?: string,
): InviteMemberBrief[] {
  const users = loadTenantUsers();
  const briefs = buildInviteBriefs(users);
  return users
    .filter((u) => {
      if (!excludeSubtreeRootUid) return true;
      return !isDescendantOf(excludeSubtreeRootUid, u.uid, users);
    })
    .map((u) => briefs.get(u.uid)!)
    .filter(Boolean)
    .sort((a, b) => a.display_name.localeCompare(b.display_name, "zh-Hant"));
}
