DslOperationsFacade.java

package com.taxonomy.versioning.service;

import com.taxonomy.architecture.model.ArchitectureCommitIndex;
import com.taxonomy.architecture.model.ArchitectureDslDocument;
import com.taxonomy.architecture.repository.ArchitectureDslDocumentRepository;
import com.taxonomy.architecture.service.CommitIndexService;
import com.taxonomy.dsl.diff.ModelDiff;
import com.taxonomy.dsl.export.DslMaterializeService;
import com.taxonomy.dsl.export.TaxDslExportService;
import com.taxonomy.dsl.model.CanonicalArchitectureModel;
import com.taxonomy.dsl.storage.DslBranch;
import com.taxonomy.dsl.storage.DslCommit;
import com.taxonomy.dsl.storage.DslGitRepository;
import com.taxonomy.dsl.storage.DslGitRepositoryFactory;
import com.taxonomy.dto.ElementHistoryAggregation;
import com.taxonomy.dto.ViewContext;
import com.taxonomy.dto.VersionedSearchResult;
import com.taxonomy.workspace.service.RepositoryStateGuard;
import com.taxonomy.workspace.service.WorkspaceContext;
import com.taxonomy.workspace.service.WorkspaceResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;

/**
 * High-level facade that aggregates the DSL operation services.
 *
 * <p>Provides coarse-grained operations for Git versioning, DSL export,
 * materialization, commit indexing, and conflict detection, so that
 * the {@code DslApiController} does not need direct access to
 * repositories or low-level services.
 */
@Service
public class DslOperationsFacade {

    private static final Logger log = LoggerFactory.getLogger(DslOperationsFacade.class);

    private final TaxDslExportService exportService;
    private final DslMaterializeService materializeService;
    private final ArchitectureDslDocumentRepository documentRepository;
    private final DslGitRepositoryFactory repositoryFactory;
    private final CommitIndexService commitIndexService;
    private final ConflictDetectionService conflictDetectionService;
    private final RepositoryStateGuard stateGuard;
    private final RepositoryStateService repositoryStateService;
    private final WorkspaceResolver workspaceResolver;

    public DslOperationsFacade(TaxDslExportService exportService,
                               DslMaterializeService materializeService,
                               ArchitectureDslDocumentRepository documentRepository,
                               DslGitRepositoryFactory repositoryFactory,
                               CommitIndexService commitIndexService,
                               ConflictDetectionService conflictDetectionService,
                               RepositoryStateGuard stateGuard,
                               RepositoryStateService repositoryStateService,
                               WorkspaceResolver workspaceResolver) {
        this.exportService = exportService;
        this.materializeService = materializeService;
        this.documentRepository = documentRepository;
        this.repositoryFactory = repositoryFactory;
        this.commitIndexService = commitIndexService;
        this.conflictDetectionService = conflictDetectionService;
        this.stateGuard = stateGuard;
        this.repositoryStateService = repositoryStateService;
        this.workspaceResolver = workspaceResolver;
    }

    private DslGitRepository resolveRepository() {
        return repositoryFactory.resolveRepository(resolveContext());
    }

    /**
     * Resolve the current workspace context from the authenticated user.
     * Falls back to {@link WorkspaceContext#SHARED} if resolution fails.
     *
     * <p>Eagerly provisions the workspace state via
     * {@link RepositoryStateService} before resolving the context.
     * Without this, the very first call in a request returns {@code SHARED}
     * (no workspace yet), but a later call (after {@code getViewContext}
     * triggers lazy workspace creation) returns a workspace-scoped context
     * — causing two operations in the same flow to target different repos.
     */
    private WorkspaceContext resolveContext() {
        try {
            // Ensure workspace state is provisioned before resolving context,
            // so that resolveCurrentContext() finds the workspace on the
            // very first call (mirrors what getViewContext does internally).
            String username = workspaceResolver.resolveCurrentUsername();
            repositoryStateService.ensureWorkspaceState(username);
            return workspaceResolver.resolveCurrentContext();
        } catch (Exception e) {
            return WorkspaceContext.SHARED;
        }
    }

    // ── Export ───────────────────────────────────────────────────────

    /**
     * Export all architecture elements as DSL text.
     */
    public String exportAll(String namespace) {
        return exportService.exportAll(namespace);
    }

    /**
     * Build the canonical architecture model.
     */
    public CanonicalArchitectureModel buildCanonicalModel() {
        return exportService.buildCanonicalModel();
    }

    // ── Materialization ─────────────────────────────────────────────

    /**
     * Materialize DSL text into the database.
     */
    public DslMaterializeService.MaterializeResult materialize(String dslText, String path,
                                                                String branch, String commitId) {
        return materializeService.materialize(dslText, path, branch, commitId);
    }

    /**
     * Incrementally materialize the delta between two DSL versions.
     */
    public DslMaterializeService.MaterializeResult materializeIncremental(Long beforeDocId, Long afterDocId) {
        return materializeService.materializeIncremental(beforeDocId, afterDocId);
    }

    /**
     * Look up a document by ID to resolve the branch for incremental materialization.
     */
    public Optional<ArchitectureDslDocument> findDocumentById(Long id) {
        return documentRepository.findById(id);
    }

    // ── Git operations ──────────────────────────────────────────────

    /**
     * Commit DSL text to a branch.
     */
    public String commitDsl(String branch, String dslText, String author, String message) throws IOException {
        return resolveRepository().commitDsl(branch, dslText, author, message);
    }

    /**
     * Whether the Git repository is database-backed.
     */
    public boolean isDatabaseBacked() {
        return resolveRepository().isDatabaseBacked();
    }

    /**
     * Get DSL commit history for a branch.
     */
    public List<DslCommit> getDslHistory(String branch) throws IOException {
        return resolveRepository().getDslHistory(branch);
    }

    /**
     * Find the document ID for a given commit ID (from materialized documents).
     */
    public Optional<Long> findDocumentIdByCommitId(String commitId) {
        return documentRepository.findByCommitId(commitId)
                .map(ArchitectureDslDocument::getId);
    }

    /**
     * Compute a diff between two DSL versions (by Git SHA or document ID).
     */
    public ModelDiff diffBetween(String beforeId, String afterId) throws Exception {
        if (looksLikeGitSha(beforeId) && looksLikeGitSha(afterId)) {
            return resolveRepository().diffBetween(beforeId, afterId);
        } else {
            return materializeService.diffDocuments(Long.valueOf(beforeId), Long.valueOf(afterId));
        }
    }

    /**
     * Compute a JGit text diff between two commits.
     */
    public String textDiff(String beforeId, String afterId) throws Exception {
        return resolveRepository().textDiff(beforeId, afterId);
    }

    /**
     * List all branches in the Git repository.
     */
    public List<DslBranch> listBranches() throws IOException {
        return resolveRepository().listBranches();
    }

    /**
     * Create a new branch by forking from an existing branch.
     */
    public String createBranch(String name, String fromBranch) throws IOException {
        return resolveRepository().createBranch(name, fromBranch);
    }

    /**
     * Cherry-pick a commit onto a target branch.
     */
    public String cherryPick(String commitId, String targetBranch) throws IOException {
        return resolveRepository().cherryPick(commitId, targetBranch);
    }

    /**
     * Merge one branch into another.
     */
    public String merge(String fromBranch, String intoBranch) throws IOException {
        return resolveRepository().merge(fromBranch, intoBranch);
    }

    /**
     * Revert a specific commit on a branch.
     */
    public String revert(String commitId, String branch) throws IOException {
        return resolveRepository().revert(commitId, branch);
    }

    /**
     * Undo the last commit on a branch.
     */
    public String undoLast(String branch) throws IOException {
        return resolveRepository().undoLast(branch);
    }

    /**
     * Restore DSL content from a specific commit.
     */
    public String restore(String commitId, String branch) throws Exception {
        return resolveRepository().restore(commitId, branch);
    }

    /**
     * Delete a branch.
     */
    public boolean deleteBranch(String name) throws IOException {
        return resolveRepository().deleteBranch(name);
    }

    /**
     * Read DSL text from the HEAD of a branch.
     */
    public String getDslAtHead(String branch) throws IOException {
        return resolveRepository().getDslAtHead(branch);
    }

    /**
     * Read DSL text from a specific commit.
     */
    public String getDslAtCommit(String commitId) throws Exception {
        return resolveRepository().getDslAtCommit(commitId);
    }

    /**
     * Get the HEAD commit SHA for a branch.
     */
    public String getHeadCommit(String branch) throws IOException {
        return resolveRepository().getHeadCommit(branch);
    }

    // ── Conflict detection ──────────────────────────────────────────

    /**
     * Preview a merge (dry run).
     */
    public ConflictDetectionService.MergePreview previewMerge(String from, String into) {
        return conflictDetectionService.previewMerge(from, into, resolveContext());
    }

    /**
     * Preview a cherry-pick (dry run).
     */
    public ConflictDetectionService.CherryPickPreview previewCherryPick(String commitId, String targetBranch) {
        return conflictDetectionService.previewCherryPick(commitId, targetBranch, resolveContext());
    }

    /**
     * Get merge conflict details.
     */
    public ConflictDetectionService.ConflictDetails getMergeConflictDetails(String from, String into) {
        return conflictDetectionService.getMergeConflictDetails(from, into, resolveContext());
    }

    /**
     * Get cherry-pick conflict details.
     */
    public ConflictDetectionService.ConflictDetails getCherryPickConflictDetails(String commitId, String targetBranch) {
        return conflictDetectionService.getCherryPickConflictDetails(commitId, targetBranch, resolveContext());
    }

    // ── View context & state guard ──────────────────────────────────

    /**
     * Get the view context for the current user on a branch.
     */
    public ViewContext getViewContext(String branch) {
        return repositoryStateService.getViewContext(
                workspaceResolver.resolveCurrentUsername(), branch, resolveContext());
    }

    /**
     * Check whether a write operation is safe.
     */
    public RepositoryStateGuard.OperationCheck checkWriteOperation(String branch, String operationType) {
        return stateGuard.checkWriteOperation(
                workspaceResolver.resolveCurrentUsername(), branch, operationType);
    }

    /**
     * Resolve the current username.
     */
    public String resolveCurrentUsername() {
        return workspaceResolver.resolveCurrentUsername();
    }

    /**
     * Resolve the workspace branch for a user. Falls back to "draft" if no
     * workspace branch is configured.
     *
     * @param username the username to resolve the branch for
     * @return the user's workspace branch, or "draft" as default
     */
    public String resolveWorkspaceBranch(String username) {
        return repositoryStateService.resolveWorkspaceBranch(username);
    }

    // ── Document listing ────────────────────────────────────────────

    /**
     * List all stored DSL documents.
     */
    public List<ArchitectureDslDocument> listDocuments() {
        return documentRepository.findAll();
    }

    // ── History search ──────────────────────────────────────────────

    /**
     * Index commits on a branch for history search.
     */
    public int indexBranch(String branch) {
        return commitIndexService.indexBranch(branch, resolveContext());
    }

    /**
     * Search architecture commit history.
     */
    public List<ArchitectureCommitIndex> searchHistory(String query, int maxResults) {
        return commitIndexService.search(query, maxResults);
    }

    /**
     * Find commits that affected a specific element.
     */
    public List<ArchitectureCommitIndex> findByElement(String elementId) {
        return commitIndexService.findByElement(elementId);
    }

    /**
     * Find commits that affected a specific relation.
     */
    public List<ArchitectureCommitIndex> findByRelation(String key) {
        return commitIndexService.findByRelation(key);
    }

    /**
     * Get aggregated history for an element.
     */
    public ElementHistoryAggregation aggregateElementHistory(String elementId) {
        return commitIndexService.aggregateElementHistory(elementId);
    }

    // ── Internal helpers ────────────────────────────────────────────

    private static boolean looksLikeGitSha(String s) {
        return s != null && s.length() == 40 && s.matches("[0-9a-f]+");
    }
}