package com.zb.mom.ww.mxgateway.client;

import galaxy_repository.v1.GalaxyRepositoryOuterClass.GalaxyObject;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * One node in a lazy-loaded Galaxy browse tree. Holds the underlying
 * {@link GalaxyObject} and exposes {@link #expand()} to fetch its direct
 * children on demand. Expansion is one-shot: a second call is a no-op.
 * Pagination of large sibling sets is handled internally by the client.
 */
public final class LazyBrowseNode {
    private final GalaxyRepositoryClient client;
    private final GalaxyObject object;
    private final boolean hasChildrenHint;
    private final BrowseChildrenOptions options;

    // expandLock gates the start of a new expand AND the publish of the in-flight
    // future. Readers (getChildren / isExpanded) use a separate read-write lock so
    // they never block on the gRPC call.
    private final Object expandLock = new Object();
    private CompletableFuture<Void> inFlight;

    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private List<LazyBrowseNode> children = Collections.emptyList();
    private boolean isExpanded;

    LazyBrowseNode(
            GalaxyRepositoryClient client,
            GalaxyObject object,
            boolean hasChildrenHint,
            BrowseChildrenOptions options) {
        this.client = client;
        this.object = object;
        this.hasChildrenHint = hasChildrenHint;
        this.options = options;
    }

    /** @return the underlying Galaxy object proto for this node. */
    public GalaxyObject getObject() {
        return object;
    }

    /** @return {@code true} when the server reports this node has at least one matching descendant. */
    public boolean hasChildrenHint() {
        return hasChildrenHint;
    }

    /** @return a snapshot of direct children loaded by {@link #expand()}; empty until then. */
    public List<LazyBrowseNode> getChildren() {
        readWriteLock.readLock().lock();
        try {
            return List.copyOf(children);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

    /** @return {@code true} after the first {@link #expand()} call completes. */
    public boolean isExpanded() {
        readWriteLock.readLock().lock();
        try {
            return isExpanded;
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

    /**
     * Fetches direct children from the gateway and populates {@link #getChildren()}.
     * Idempotent: subsequent calls are no-ops and do not issue a second RPC.
     *
     * <p>Concurrent callers coalesce onto a single in-flight RPC: the first caller
     * (the "leader") issues the gRPC call, while any other thread that calls
     * {@code expand()} during that window blocks on the leader's future and sees
     * the same result (or the same exception). On failure the in-flight slot is
     * cleared so a subsequent call can retry.
     *
     * <p>Readers ({@link #getChildren()} / {@link #isExpanded()}) take a separate
     * read lock and are never blocked for the duration of the RPC.
     *
     * @throws MxGatewayException on transport or protocol failure
     */
    public void expand() {
        if (isExpanded()) {
            return;
        }

        CompletableFuture<Void> future;
        boolean iAmTheLeader;
        synchronized (expandLock) {
            if (isExpanded()) {
                return;
            }
            if (inFlight != null) {
                future = inFlight;
                iAmTheLeader = false;
            } else {
                future = new CompletableFuture<>();
                inFlight = future;
                iAmTheLeader = true;
            }
        }

        if (iAmTheLeader) {
            try {
                List<LazyBrowseNode> loaded =
                        client.browseChildrenInner(object.getGobjectId(), options);
                readWriteLock.writeLock().lock();
                try {
                    this.children = loaded;
                    this.isExpanded = true;
                } finally {
                    readWriteLock.writeLock().unlock();
                }
                synchronized (expandLock) {
                    inFlight = null;
                }
                future.complete(null);
            } catch (RuntimeException ex) {
                synchronized (expandLock) {
                    inFlight = null;
                }
                future.completeExceptionally(ex);
                throw ex;
            }
        } else {
            try {
                future.get();
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                throw new MxGatewayException("Interrupted waiting for browse-children expand.", ie);
            } catch (ExecutionException ee) {
                Throwable cause = ee.getCause();
                if (cause instanceof MxGatewayException me) {
                    throw me;
                }
                if (cause instanceof RuntimeException re) {
                    throw re;
                }
                throw new MxGatewayException("BrowseChildren expand failed.", cause);
            }
        }
    }
}
