// #region GitServiceClient [C:3] [TYPE Service] [SEMANTICS git, service, api, repository, branch]
// @BRIEF API client for Git operations — repository management, branches, commits, merges, deployments.
// @LAYER Service
// @RELATION DEPENDS_ON -> [ApiModule]

import { requestApi } from '../lib/api';

const API_BASE = '/git';

// #region buildDashboardRepoEndpoint [C:2] [TYPE Function]
// @BRIEF Build /git/repositories/{ref}{suffix} endpoint with optional env_id query param.
// @PRE  dashboardRef is non-empty string.
// @POST Returns absolute API endpoint path with env_id appended when envId is provided.
// @DATA_CONTRACT Input(dashboardRef, suffix, envId?) -> Output(string)
function buildDashboardRepoEndpoint(dashboardRef: string | number, suffix: string, envId: string | number | null = null): string {
    const encodedRef = encodeURIComponent(String(dashboardRef));
    const endpoint = `${API_BASE}/repositories/${encodedRef}${suffix}`;
    if (!envId) return endpoint;
    const sep = endpoint.includes('?') ? '&' : '?';
    return `${endpoint}${sep}env_id=${encodeURIComponent(String(envId))}`;
}
// #endregion buildDashboardRepoEndpoint

// #region gitRequest [C:2] [TYPE Function]
// @BRIEF Wrapper around requestApi that auto-suppresses API-level toasts —
//        Git error display is handled by useGitManager.js handlers (persistent toasts + modal banner).
// @PRE  Works like requestApi(endpoint, method, body, extraOptions).
// @POST Api-level toast suppressed; error propagates to caller for handler-level display.
async function gitRequest<T = unknown>(endpoint: string, method = 'GET', body: unknown = null, extraOptions: Record<string, unknown> = {}): Promise<T> {
    const requestOptions = { ...extraOptions, suppressToast: true };
    return requestApi<T>(endpoint, method, body, requestOptions);
}
// #endregion gitRequest

interface GitServerConfig {
    id?: string;
    name?: string;
    provider?: string;
    url?: string;
    [key: string]: unknown;
}

interface RemoteRepoCreatePayload {
    name: string;
    private?: boolean;
    description?: string;
    auto_init?: boolean;
    default_branch?: string;
    [key: string]: unknown;
}

interface PromotePayload {
    from_branch: string;
    to_branch: string;
    mode: string;
    title?: string;
    description?: string;
    reason?: string;
    [key: string]: unknown;
}

// #region gitService [C:3] [TYPE Object]
// @BRIEF Exported Git API client with methods for all repository and config operations.
// @RELATION CALLS -> [ApiModule.requestApi]
export const gitService = {

    // #region getConfigs [C:2] [TYPE Function]
    // @BRIEF Fetch all Git server configurations.
    // @PRE  User is authenticated (handled by ApiModule auth headers).
    // @POST Returns list of GitServerConfig objects.
    // @DATA_CONTRACT None -> Array<GitServerConfig>
    async getConfigs<T = unknown>(): Promise<T> {
        console.log('[getConfigs][Action] Fetching Git configs');
        return gitRequest<T>(`${API_BASE}/config`);
    },
    // #endregion getConfigs

    // #region createConfig [C:2] [TYPE Function]
    // @BRIEF Create a new Git server configuration.
    // @PRE  config object is valid with required fields.
    // @POST New config is persisted and returned.
    // @DATA_CONTRACT Input(GitServerConfig) -> Output(GitServerConfig)
    async createConfig<T = unknown>(config: GitServerConfig): Promise<T> {
        console.log('[createConfig][Action] Creating Git config');
        return gitRequest<T>(`${API_BASE}/config`, 'POST', config);
    },
    // #endregion createConfig

    // #region deleteConfig [C:2] [TYPE Function]
    // @BRIEF Delete an existing Git server configuration.
    // @PRE  configId exists in the backend.
    // @POST Config is removed from the backend.
    async deleteConfig<T = unknown>(configId: string): Promise<T> {
        console.log(`[deleteConfig][Action] Deleting Git config ${configId}`);
        return gitRequest<T>(`${API_BASE}/config/${configId}`, 'DELETE');
    },
    // #endregion deleteConfig

    // #region updateConfig [C:2] [TYPE Function]
    // @BRIEF Update an existing Git server configuration.
    // @PRE  configId exists and configData contains valid fields.
    // @POST Config is updated and returned.
    async updateConfig<T = unknown>(configId: string, config: GitServerConfig): Promise<T> {
        console.log(`[updateConfig][Action] Updating Git config ${configId}`);
        return gitRequest<T>(`${API_BASE}/config/${configId}`, 'PUT', config);
    },
    // #endregion updateConfig

    // #region testConnection [C:2] [TYPE Function]
    // @BRIEF Test connection to a Git server with provided credentials.
    // @PRE  config contains valid URL and PAT.
    // @POST Returns connection status (success/failure).
    // @DATA_CONTRACT Input(GitServerConfig) -> Output({status: string})
    async testConnection<T = unknown>(config: GitServerConfig): Promise<T> {
        console.log('[testConnection][Action] Testing Git connection');
        return gitRequest<T>(`${API_BASE}/config/test`, 'POST', config);
    },
    // #endregion testConnection

    // #region listGiteaRepositories [C:2] [TYPE Function]
    // @BRIEF List repositories on Gitea for a saved Git configuration.
    // @PRE  configId references a GITEA provider config.
    // @POST Returns array of Gitea repository metadata.
    async listGiteaRepositories<T = unknown>(configId: string): Promise<T> {
        console.log(`[listGiteaRepositories][Action] Listing Gitea repositories for config ${configId}`);
        return gitRequest<T>(`${API_BASE}/config/${configId}/gitea/repos`);
    },
    // #endregion listGiteaRepositories

    // #region createGiteaRepository [C:2] [TYPE Function]
    // @BRIEF Create a repository on Gitea for a saved Git configuration.
    // @PRE  configId references a GITEA config; payload has non-empty name.
    // @POST Repository created on Gitea; returns created repo metadata.
    async createGiteaRepository<T = unknown>(configId: string, payload: RemoteRepoCreatePayload): Promise<T> {
        console.log(`[createGiteaRepository][Action] Creating Gitea repository ${payload?.name} for config ${configId}`);
        return gitRequest<T>(`${API_BASE}/config/${configId}/gitea/repos`, 'POST', payload);
    },
    // #endregion createGiteaRepository

    // #region createRemoteRepository [C:2] [TYPE Function]
    // @BRIEF Create repository on remote provider selected by Git config (idempotent: returns existing on 409).
    // @PRE  configId exists and points to supported provider; payload has non-empty name.
    // @POST Remote repository created (or existing returned); normalized payload returned.
    // @DATA_CONTRACT Input(configId, RemoteRepoCreatePayload) -> Output(RemoteRepoSchema)
    async createRemoteRepository<T = unknown>(configId: string, payload: RemoteRepoCreatePayload): Promise<T> {
        console.log(`[createRemoteRepository][Action] Creating remote repository ${payload?.name} for config ${configId}`);
        return gitRequest<T>(`${API_BASE}/config/${configId}/repositories`, 'POST', payload);
    },
    // #endregion createRemoteRepository

    // #region deleteGiteaRepository [C:2] [TYPE Function]
    // @BRIEF Delete a repository on Gitea for a saved Git configuration.
    // @PRE  configId references a GITEA config; owner and repoName are non-empty.
    // @POST Repository deleted on Gitea server.
    async deleteGiteaRepository<T = unknown>(configId: string, owner: string, repoName: string): Promise<T> {
        console.log(`[deleteGiteaRepository][Action] Deleting Gitea repository ${owner}/${repoName} for config ${configId}`);
        return gitRequest<T>(`${API_BASE}/config/${configId}/gitea/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repoName)}`, 'DELETE');
    },
    // #endregion deleteGiteaRepository

    // #region initRepository [C:3] [TYPE Function]
    // @BRIEF Initialize or clone a Git repository for a dashboard.
    // @PRE  dashboardRef is non-empty; configId and remoteUrl are valid; envId required when ref is slug.
    // @POST Repository initialized on the backend; GitRepository record created.
    // @DATA_CONTRACT Input(dashboardRef, configId, remoteUrl, envId?) -> Output({status: string, message: string})
    // @RELATION CALLS -> [ApiModule.requestApi]
    async initRepository<T = unknown>(dashboardRef: string | number, configId: string, remoteUrl: string, envId: string | number | null = null): Promise<T> {
        console.log(`[initRepository][Action] Initializing repo for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/init', envId), 'POST', {
            config_id: configId,
            remote_url: remoteUrl
        });
    },
    // #endregion initRepository

    // #region getRepositoryBinding [C:2] [TYPE Function]
    // @BRIEF Fetch repository binding metadata (config/provider) for a dashboard.
    // @PRE  Repository should be initialized for the dashboard.
    // @POST Returns provider, config_id, remote_url, local_path.
    async getRepositoryBinding<T = unknown>(dashboardRef: string | number, envId: string | number | null = null): Promise<T> {
        console.log(`[getRepositoryBinding][Action] Fetching repository binding for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '', envId));
    },
    // #endregion getRepositoryBinding

    // #region getBranches [C:2] [TYPE Function]
    // @BRIEF List all branches for a dashboard's repository.
    // @PRE  Repository must be initialized; envId required when dashboardRef is a slug.
    // @POST Returns array of BranchSchema objects.
    async getBranches<T = unknown>(dashboardRef: string | number, envId: string | number | null = null): Promise<T> {
        console.log(`[getBranches][Action] Fetching branches for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/branches', envId));
    },
    // #endregion getBranches

    // #region createBranch [C:2] [TYPE Function]
    // @BRIEF Create a new branch in the dashboard's repository.
    // @PRE  Source branch exists; name is non-empty; envId required when ref is slug.
    // @POST New branch is created on the backend.
    async createBranch<T = unknown>(dashboardRef: string | number, name: string, fromBranch: string, envId: string | number | null = null): Promise<T> {
        console.log(`[createBranch][Action] Creating branch ${name} for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/branches', envId), 'POST', {
            name,
            from_branch: fromBranch
        });
    },
    // #endregion createBranch

    // #region checkoutBranch [C:2] [TYPE Function]
    // @BRIEF Switch the repository to a different branch.
    // @PRE  Target branch exists; envId required when ref is slug.
    // @POST Repository HEAD is moved to the target branch.
    async checkoutBranch<T = unknown>(dashboardRef: string | number, name: string, envId: string | number | null = null): Promise<T> {
        console.log(`[checkoutBranch][Action] Checking out branch ${name} for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/checkout', envId), 'POST', { name });
    },
    // #endregion checkoutBranch

    // #region commit [C:2] [TYPE Function]
    // @BRIEF Stage and commit changes to the repository.
    // @PRE  message is non-empty; envId required when ref is slug.
    // @POST Changes are committed to the current branch.
    async commit<T = unknown>(dashboardRef: string | number, message: string, files: string[], envId: string | number | null = null): Promise<T> {
        console.log(`[commit][Action] Committing changes for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/commit', envId), 'POST', { message, files });
    },
    // #endregion commit

    // #region push [C:2] [TYPE Function]
    // @BRIEF Push local commits to the remote repository.
    // @PRE  Remote 'origin' is configured and accessible; envId required when ref is slug.
    // @POST Remote is updated with local commits.
    async push<T = unknown>(dashboardRef: string | number, envId: string | number | null = null): Promise<T> {
        console.log(`[push][Action] Pushing changes for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/push', envId), 'POST');
    },
    // #endregion push

    // #region deleteRepository [C:2] [TYPE Function]
    // @BRIEF Delete local repository binding and workspace for a dashboard.
    // @PRE  dashboardRef resolves on backend; envId required when ref is slug.
    // @POST Repository record and local folder are removed.
    async deleteRepository<T = unknown>(dashboardRef: string | number, envId: string | number | null = null): Promise<T> {
        console.log(`[deleteRepository][Action] Deleting repository for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '', envId), 'DELETE');
    },
    // #endregion deleteRepository

    // #region pull [C:2] [TYPE Function]
    // @BRIEF Pull changes from the remote repository.
    // @PRE  Remote 'origin' is configured and accessible; envId required when ref is slug.
    // @POST Local repository is updated with remote changes.
    async pull<T = unknown>(dashboardRef: string | number, envId: string | number | null = null): Promise<T> {
        console.log(`[pull][Action] Pulling changes for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/pull', envId), 'POST');
    },
    // #endregion pull

    // #region getMergeStatus [C:2] [TYPE Function]
    // @BRIEF Fetch unfinished-merge status for a repository.
    // @PRE  Repository must exist; envId required when ref is slug.
    // @POST Returns merge status payload with has_unfinished_merge flag.
    async getMergeStatus<T = unknown>(dashboardRef: string | number, envId: string | number | null = null): Promise<T> {
        console.log(`[getMergeStatus][Action] Fetching merge status for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/merge/status', envId));
    },
    // #endregion getMergeStatus

    // #region getMergeConflicts [C:2] [TYPE Function]
    // @BRIEF List merge conflicts for a repository with unresolved merge.
    // @PRE  Unfinished merge is in progress; envId required when ref is slug.
    // @POST Returns array of conflict file entries with mine/theirs previews.
    async getMergeConflicts<T = unknown>(dashboardRef: string | number, envId: string | number | null = null): Promise<T> {
        console.log(`[getMergeConflicts][Action] Fetching merge conflicts for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/merge/conflicts', envId));
    },
    // #endregion getMergeConflicts

    // #region resolveMergeConflicts [C:2] [TYPE Function]
    // @BRIEF Apply conflict resolution strategies and stage resolved files.
    // @PRE  resolutions array contains file_path and resolution entries; envId required when ref is slug.
    // @POST Conflicts are resolved and staged.
    async resolveMergeConflicts<T = unknown>(dashboardRef: string | number, resolutions: { file_path: string; resolution: string }[], envId: string | number | null = null): Promise<T> {
        console.log(`[resolveMergeConflicts][Action] Resolving ${Array.isArray(resolutions) ? resolutions.length : 0} conflicts for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/merge/resolve', envId), 'POST', {
            resolutions: Array.isArray(resolutions) ? resolutions : []
        });
    },
    // #endregion resolveMergeConflicts

    // #region abortMerge [C:2] [TYPE Function]
    // @BRIEF Abort current unfinished merge.
    // @PRE  Repository exists; envId required when ref is slug.
    // @POST Merge state is aborted or reported as absent.
    async abortMerge<T = unknown>(dashboardRef: string | number, envId: string | number | null = null): Promise<T> {
        console.log(`[abortMerge][Action] Aborting merge for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/merge/abort', envId), 'POST');
    },
    // #endregion abortMerge

    // #region continueMerge [C:2] [TYPE Function]
    // @BRIEF Finalize unfinished merge by creating a merge commit.
    // @PRE  All conflicts are resolved; envId required when ref is slug.
    // @POST Merge commit is created.
    async continueMerge<T = unknown>(dashboardRef: string | number, message = '', envId: string | number | null = null): Promise<T> {
        console.log(`[continueMerge][Action] Continuing merge for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/merge/continue', envId), 'POST', {
            message: String(message || '').trim() || null
        });
    },
    // #endregion continueMerge

    // #region getEnvironments [C:2] [TYPE Function]
    // @BRIEF Fetch available deployment environments from Git config.
    // @POST Returns list of environment objects.
    async getEnvironments<T = unknown>(): Promise<T> {
        console.log('[getEnvironments][Action] Fetching environments');
        return gitRequest<T>(`${API_BASE}/environments`);
    },
    // #endregion getEnvironments

    // #region deploy [C:2] [TYPE Function]
    // @BRIEF Deploy a dashboard to a target environment.
    // @PRE  Environment must be active and accessible; envId required when ref is slug.
    // @POST Dashboard is imported into target Superset instance.
    async deploy<T = unknown>(dashboardRef: string | number, environmentId: string, envId: string | number | null = null): Promise<T> {
        console.log(`[deploy][Action] Deploying dashboard ${dashboardRef} to environment ${environmentId}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/deploy', envId), 'POST', {
            environment_id: environmentId
        });
    },
    // #endregion deploy

    // #region getHistory [C:2] [TYPE Function]
    // @BRIEF Fetch commit history for a dashboard repository.
    // @POST Returns list of commits with author, date, message.
    async getHistory<T = unknown>(dashboardRef: string | number, limit = 50, envId: string | number | null = null): Promise<T> {
        console.log(`[getHistory][Action] Fetching history for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, `/history?limit=${limit}`, envId));
    },
    // #endregion getHistory

    // #region sync [C:2] [TYPE Function]
    // @BRIEF Synchronize local dashboard state with Git repository.
    // @POST Dashboard state is synced to Git workspace.
    async sync<T = unknown>(dashboardRef: string | number, sourceEnvId: string | number | null = null, envId: string | number | null = null): Promise<T> {
        console.log(`[sync][Action] Syncing dashboard ${dashboardRef}`);
        const params = new URLSearchParams();
        if (sourceEnvId) params.append('source_env_id', String(sourceEnvId));
        if (envId) params.append('env_id', String(envId));
        const query = params.toString();
        const endpoint = `${API_BASE}/repositories/${encodeURIComponent(String(dashboardRef))}/sync${query ? `?${query}` : ''}`;
        return gitRequest<T>(endpoint, 'POST');
    },
    // #endregion sync

    // #region getStatus [C:2] [TYPE Function]
    // @BRIEF Fetch current Git status for a dashboard repository (dirty files, branch info).
    // @PRE  Repository path exists on disk; envId required when ref is slug.
    // @POST Returns status payload with is_dirty, staged_files, modified_files, current_branch.
    async getStatus<T = unknown>(dashboardRef: string | number, envId: string | number | null = null): Promise<T> {
        console.log(`[getStatus][Action] Fetching status for dashboard ${dashboardRef}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/status', envId));
    },
    // #endregion getStatus

    // #region getStatusesBatch [C:2] [TYPE Function]
    // @BRIEF Fetch Git statuses for multiple dashboards in one batch request.
    // @PRE  dashboardIds is a non-empty array of numeric IDs.
    // @POST Returns a map of dashboard_id -> status payload (with NO_REPO for uninitialized).
    async getStatusesBatch<T = unknown>(dashboardIds: (string | number)[]): Promise<T> {
        console.log(`[getStatusesBatch][Action] Fetching statuses for ${dashboardIds.length} dashboards`);
        return gitRequest<T>(`${API_BASE}/repositories/status/batch`, 'POST', {
            dashboard_ids: dashboardIds
        });
    },
    // #endregion getStatusesBatch

    // #region getDiff [C:2] [TYPE Function]
    // @BRIEF Fetch Git diff for a dashboard repository (staged or unstaged).
    // @PRE  Repository path exists; envId required when ref is slug.
    // @POST Returns unified diff string.
    async getDiff<T = unknown>(dashboardRef: string | number, filePath: string | null = null, staged = false, envId: string | number | null = null): Promise<T> {
        console.log(`[getDiff][Action] Fetching diff for dashboard ${dashboardRef} (file: ${filePath}, staged: ${staged})`);
        let endpoint = `${API_BASE}/repositories/${encodeURIComponent(String(dashboardRef))}/diff`;
        const params = new URLSearchParams();
        if (filePath) params.append('file_path', filePath);
        if (staged) params.append('staged', 'true');
        if (envId) params.append('env_id', String(envId));
        if (params.toString()) endpoint += `?${params.toString()}`;
        return gitRequest<T>(endpoint);
    },
    // #endregion getDiff

    // #region promote [C:2] [TYPE Function]
    // @BRIEF Promote changes between branches via MR or direct merge.
    // @PRE  Dashboard repository initialized; from_branch and to_branch differ; envId required when ref is slug.
    // @POST Returns MR URL (MR mode) or merge status (direct mode).
    async promote<T = unknown>(dashboardRef: string | number, payload: PromotePayload, envId: string | number | null = null): Promise<T> {
        console.log(`[promote][Action] Promoting ${payload?.from_branch} -> ${payload?.to_branch} for dashboard ${dashboardRef} mode=${payload?.mode}`);
        return gitRequest<T>(buildDashboardRepoEndpoint(dashboardRef, '/promote', envId), 'POST', payload);
    }
    // #endregion promote
};
// #endregion gitService
// #endregion GitServiceClient
