export type DocumentVisibilityScope = "private" | "shared" | "public" | null | undefined;

export type DocumentVisibilityRecord = {
  id: string;
  user_id?: string | null;
  parent_id?: string | null;
  access_scope?: DocumentVisibilityScope;
  deleted_at?: string | null;
};

export type DocumentVisibilityShareRecord = {
  document_id: string;
  include_descendants?: boolean | null;
};

type BuildVisibleDocumentIdsInput = {
  userId: string;
  docs: readonly DocumentVisibilityRecord[];
  directShares: readonly DocumentVisibilityShareRecord[];
  directGroupShares: readonly DocumentVisibilityShareRecord[];
  includeDeletedDocuments?: boolean;
};

function normalizeDocumentId(value: string | null | undefined): string | null {
  const trimmed = String(value ?? "").trim();
  return trimmed.length > 0 ? trimmed : null;
}

function buildInheritedShareMap(
  shares: readonly DocumentVisibilityShareRecord[],
): Map<string, { includeDescendants: boolean }> {
  const result = new Map<string, { includeDescendants: boolean }>();
  for (const share of shares) {
    const documentId = normalizeDocumentId(share.document_id);
    if (!documentId) continue;
    const existing = result.get(documentId);
    if (!existing) {
      result.set(documentId, {
        includeDescendants: Boolean(share.include_descendants),
      });
      continue;
    }
    existing.includeDescendants = existing.includeDescendants || Boolean(share.include_descendants);
  }
  return result;
}

function canAccessByInheritedShare(input: {
  docId: string;
  parentById: Map<string, string | null>;
  directShareMap: Map<string, { includeDescendants: boolean }>;
  cache: Map<string, boolean>;
}): boolean {
  const cached = input.cache.get(input.docId);
  if (typeof cached === "boolean") return cached;
  if (input.directShareMap.has(input.docId)) {
    input.cache.set(input.docId, true);
    return true;
  }
  let parentId = input.parentById.get(input.docId) ?? null;
  for (let depth = 0; depth < 60 && parentId; depth += 1) {
    const parentShare = input.directShareMap.get(parentId);
    if (parentShare && parentShare.includeDescendants) {
      input.cache.set(input.docId, true);
      return true;
    }
    parentId = input.parentById.get(parentId) ?? null;
  }
  input.cache.set(input.docId, false);
  return false;
}

export function buildVisibleDocumentIds(input: BuildVisibleDocumentIdsInput): Set<string> {
  const candidateDocs = input.includeDeletedDocuments
    ? [...input.docs]
    : input.docs.filter((doc) => doc.deleted_at == null);

  const parentById = new Map<string, string | null>();
  for (const doc of candidateDocs) {
    parentById.set(doc.id, normalizeDocumentId(doc.parent_id) ?? null);
  }

  const directShares = buildInheritedShareMap(input.directShares);
  const directGroupShares = buildInheritedShareMap(input.directGroupShares);
  const shareAccessCache = new Map<string, boolean>();
  const groupAccessCache = new Map<string, boolean>();
  const visible = new Set<string>();

  for (const doc of candidateDocs) {
    if (String(doc.user_id ?? "") === input.userId) {
      visible.add(doc.id);
      continue;
    }
    if (doc.access_scope === "public") {
      visible.add(doc.id);
      continue;
    }
    if (
      canAccessByInheritedShare({
        docId: doc.id,
        parentById,
        directShareMap: directShares,
        cache: shareAccessCache,
      }) ||
      canAccessByInheritedShare({
        docId: doc.id,
        parentById,
        directShareMap: directGroupShares,
        cache: groupAccessCache,
      })
    ) {
      visible.add(doc.id);
    }
  }

  return visible;
}
