// [DEF:api_module:Module]
// @TIER:      STANDARD
// @SEMANTICS: api, client, fetch, rest
// @PURPOSE: Handles all communication with the backend API.
// @LAYER: Infra-API

import { addToast } from './toasts.js';
import { PUBLIC_WS_URL } from '$env/static/public';

const API_BASE_URL = '/api';

// [DEF:buildApiError:Function]
// @PURPOSE: Creates a normalized Error object for failed API responses.
// @PRE: response is a failed fetch Response object.
// @POST: Returned error contains message and status fields.
async function buildApiError(response) {
    const errorData = await response.json().catch(() => ({}));
    const detail = errorData?.detail;
    const message = detail
        ? (
            typeof detail === 'string'
                ? detail
                : (typeof detail?.message === 'string' ? detail.message : JSON.stringify(detail))
        )
        : `API request failed with status ${response.status}`;
    const error = new Error(message);
    /** @type {any} */ (error).status = response.status;
    /** @type {any} */ (error).detail = detail;
    if (detail && typeof detail === 'object' && detail.error_code) {
        /** @type {any} */ (error).error_code = String(detail.error_code);
    }
    return error;
}
// [/DEF:buildApiError:Function]

// [DEF:notifyApiError:Function]
// @PURPOSE: Shows toast for API errors with explicit handling of critical statuses.
// @PRE: error is an Error instance.
// @POST: User gets visible toast feedback for request failure.
function notifyApiError(error) {
    if (error?.status === 401) {
        addToast(`401 Unauthorized: ${error.message}`, 'error');
        return;
    }
    if (error?.status >= 500) {
        addToast(`Server error (${error.status}): ${error.message}`, 'error');
        return;
    }
    addToast(error.message, 'error');
}
// [/DEF:notifyApiError:Function]

// [DEF:shouldSuppressApiErrorToast:Function]
// @PURPOSE: Avoid noisy toasts for expected non-critical API failures.
// @PRE: endpoint can be empty; error can be null.
// @POST: Returns true only for explicitly allowed suppressed scenarios.
function shouldSuppressApiErrorToast(endpoint, error) {
    const isGitStatusEndpoint =
        typeof endpoint === 'string' &&
        endpoint.startsWith('/git/repositories/') &&
        endpoint.endsWith('/status');
    const isNoRepoError =
        (error?.status === 400 || error?.status === 404) &&
        /Repository for dashboard .* not found/i.test(String(error?.message || ''));

    const isGitPullEndpoint =
        typeof endpoint === 'string' &&
        endpoint.startsWith('/git/repositories/') &&
        endpoint.endsWith('/pull');
    const isUnfinishedMergeError =
        error?.status === 409 &&
        (
            String(error?.error_code || '') === 'GIT_UNFINISHED_MERGE' ||
            String(error?.detail?.error_code || '') === 'GIT_UNFINISHED_MERGE'
        );

    return (isGitStatusEndpoint && isNoRepoError) || (isGitPullEndpoint && isUnfinishedMergeError);
}
// [/DEF:shouldSuppressApiErrorToast:Function]

// [DEF:getWsUrl:Function]
// @PURPOSE: Returns the WebSocket URL for a specific task, with fallback logic.
// @PRE: taskId is provided.
// @POST: Returns valid WebSocket URL string.
// @PARAM:   taskId (string) - The ID of the task.
// @RETURN:  string - The WebSocket URL.
export const getWsUrl = (taskId) => {
    let baseUrl = PUBLIC_WS_URL;
    if (!baseUrl) {
        const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
        // Use the current host and port to allow Vite proxy to handle the connection
        baseUrl = `${protocol}//${window.location.host}`;
    }
    return `${baseUrl}/ws/logs/${taskId}`;
};
// [/DEF:getWsUrl:Function]

// [DEF:getAuthHeaders:Function]
// @PURPOSE: Returns headers with Authorization if token exists.
function getAuthHeaders() {
    const headers = {
        'Content-Type': 'application/json',
    };
    if (typeof window !== 'undefined') {
        const token = localStorage.getItem('auth_token');
        if (token) {
            headers['Authorization'] = `Bearer ${token}`;
        }
    }
    return headers;
}
// [/DEF:getAuthHeaders:Function]

// [DEF:fetchApi:Function]
// @PURPOSE: Generic GET request wrapper.
// @PRE: endpoint string is provided.
// @POST: Returns Promise resolving to JSON data or throws on error.
// @PARAM: endpoint (string) - API endpoint.
// @RETURN: Promise<any> - JSON response.
async function fetchApi(endpoint) {
    try {
        console.log(`[api.fetchApi][Action] Fetching from context={{'endpoint': '${endpoint}'}}`);
        const response = await fetch(`${API_BASE_URL}${endpoint}`, {
            headers: getAuthHeaders()
        });
        console.log(`[api.fetchApi][Action] Received response context={{'status': ${response.status}, 'ok': ${response.ok}}}`);
        if (!response.ok) {
            throw await buildApiError(response);
        }
        if (response.status === 204) return null;
        return await response.json();
    } catch (error) {
        console.error(`[api.fetchApi][Coherence:Failed] Error fetching from ${endpoint}:`, error);
        notifyApiError(error);
        throw error;
    }
}
// [/DEF:fetchApi:Function]

// [DEF:fetchApiBlob:Function]
// @PURPOSE: Generic GET wrapper for binary payloads.
// @PRE: endpoint string is provided.
// @POST: Returns Blob or throws on error.
async function fetchApiBlob(endpoint, options = {}) {
    const notifyError = options.notifyError !== false;
    try {
        const response = await fetch(`${API_BASE_URL}${endpoint}`, {
            headers: getAuthHeaders()
        });
        if (response.status === 202) {
            const payload = await response.json().catch(() => ({ message: "Resource is being prepared" }));
            const error = new Error(payload?.message || "Resource is being prepared");
            /** @type {any} */ (error).status = 202;
            throw error;
        }
        if (!response.ok) {
            throw await buildApiError(response);
        }
        return await response.blob();
    } catch (error) {
        console.error(`[api.fetchApiBlob][Coherence:Failed] Error fetching blob from ${endpoint}:`, error);
        if (notifyError) {
            notifyApiError(error);
        }
        throw error;
    }
}
// [/DEF:fetchApiBlob:Function]

// [DEF:postApi:Function]
// @PURPOSE: Generic POST request wrapper.
// @PRE: endpoint and body are provided.
// @POST: Returns Promise resolving to JSON data or throws on error.
// @PARAM: endpoint (string) - API endpoint.
// @PARAM: body (object) - Request payload.
// @RETURN: Promise<any> - JSON response.
async function postApi(endpoint, body) {
    try {
        console.log(`[api.postApi][Action] Posting to context={{'endpoint': '${endpoint}'}}`);
        const response = await fetch(`${API_BASE_URL}${endpoint}`, {
            method: 'POST',
            headers: getAuthHeaders(),
            body: JSON.stringify(body),
        });
        console.log(`[api.postApi][Action] Received response context={{'status': ${response.status}, 'ok': ${response.ok}}}`);
        if (!response.ok) {
            throw await buildApiError(response);
        }
        if (response.status === 204) return null;
        return await response.json();
    } catch (error) {
        console.error(`[api.postApi][Coherence:Failed] Error posting to ${endpoint}:`, error);
        notifyApiError(error);
        throw error;
    }
}
// [/DEF:postApi:Function]

// [DEF:requestApi:Function]
// @PURPOSE: Generic request wrapper.
// @PRE: endpoint and method are provided.
// @POST: Returns Promise resolving to JSON data or throws on error.
async function requestApi(endpoint, method = 'GET', body = null) {
    try {
        console.log(`[api.requestApi][Action] ${method} to context={{'endpoint': '${endpoint}'}}`);
        const options = {
            method,
            headers: getAuthHeaders(),
        };
        if (body) {
            options.body = JSON.stringify(body);
        }
        const response = await fetch(`${API_BASE_URL}${endpoint}`, options);
        console.log(`[api.requestApi][Action] Received response context={{'status': ${response.status}, 'ok': ${response.ok}}}`);
        if (!response.ok) {
            const error = await buildApiError(response);
            console.error(`[api.requestApi][Action] Request failed context={{'status': ${response.status}, 'message': '${error.message}'}}`);
            throw error;
        }
        if (response.status === 204) {
            console.log('[api.requestApi][Action] 204 No Content received');
            return null;
        }
        return await response.json();
    } catch (error) {
        console.error(`[api.requestApi][Coherence:Failed] Error ${method} to ${endpoint}:`, error);
        if (!shouldSuppressApiErrorToast(endpoint, error)) {
            notifyApiError(error);
        }
        throw error;
    }
}
// [/DEF:requestApi:Function]

// [DEF:api:Data]
// @PURPOSE: API client object with specific methods.
export const api = {
    fetchApi,
    postApi,
    requestApi,
    getPlugins: () => fetchApi('/plugins'),
    getTasks: (options = {}) => {
        const params = new URLSearchParams();
        if (options.limit != null) params.append('limit', String(options.limit));
        if (options.offset != null) params.append('offset', String(options.offset));
        if (options.status) params.append('status', options.status);
        if (options.task_type) params.append('task_type', options.task_type);
        if (options.completed_only != null) params.append('completed_only', String(Boolean(options.completed_only)));
        if (Array.isArray(options.plugin_id)) {
            options.plugin_id.forEach((pluginId) => params.append('plugin_id', pluginId));
        }
        const query = params.toString();
        return fetchApi(`/tasks${query ? `?${query}` : ''}`);
    },
    getTask: (taskId) => fetchApi(`/tasks/${taskId}`),
    getTaskLogs: (taskId, options = {}) => {
        const params = new URLSearchParams();
        if (options.level) params.append('level', options.level);
        if (options.source) params.append('source', options.source);
        if (options.search) params.append('search', options.search);
        if (options.offset != null) params.append('offset', String(options.offset));
        if (options.limit != null) params.append('limit', String(options.limit));
        const query = params.toString();
        return fetchApi(`/tasks/${taskId}/logs${query ? `?${query}` : ''}`);
    },
    createTask: (pluginId, params) => postApi('/tasks', { plugin_id: pluginId, params }),

    // Profile
    getProfilePreferences: () => fetchApi('/profile/preferences'),
    updateProfilePreferences: (payload) => requestApi('/profile/preferences', 'PATCH', payload),
    lookupSupersetAccounts: (environmentId, options = {}) => {
        const normalizedEnvironmentId = String(environmentId || '').trim();
        if (!normalizedEnvironmentId) {
            throw new Error('environmentId is required for Superset account lookup');
        }
        const params = new URLSearchParams({ environment_id: normalizedEnvironmentId });
        if (options.search) params.append('search', options.search);
        if (options.page_index != null) params.append('page_index', String(options.page_index));
        if (options.page_size != null) params.append('page_size', String(options.page_size));
        if (options.sort_column) params.append('sort_column', options.sort_column);
        if (options.sort_order) params.append('sort_order', options.sort_order);
        return fetchApi(`/profile/superset-accounts?${params.toString()}`);
    },

    // Settings
    getSettings: () => fetchApi('/settings'),
    updateGlobalSettings: (settings) => requestApi('/settings/global', 'PATCH', settings),
    getEnvironments: () => fetchApi('/settings/environments'),
    addEnvironment: (env) => postApi('/settings/environments', env),
    updateEnvironment: (id, env) => requestApi(`/settings/environments/${id}`, 'PUT', env),
    deleteEnvironment: (id) => requestApi(`/settings/environments/${id}`, 'DELETE'),
    testEnvironmentConnection: (id) => postApi(`/settings/environments/${id}/test`, {}),
    updateEnvironmentSchedule: (id, schedule) => requestApi(`/environments/${id}/schedule`, 'PUT', schedule),
    getStorageSettings: () => fetchApi('/settings/storage'),
    updateStorageSettings: (storage) => requestApi('/settings/storage', 'PUT', storage),
    getEnvironmentsList: () => fetchApi('/environments'),
    getLlmStatus: () => fetchApi('/llm/status'),
    getEnvironmentDatabases: (id) => fetchApi(`/environments/${id}/databases`),
    getStorageFileBlob: (path) =>
        fetchApiBlob(`/storage/file?path=${encodeURIComponent(path)}`),

    // Dashboards
    getDashboards: (envId, options = {}) => {
        const params = new URLSearchParams({ env_id: envId });
        if (options.search) params.append('search', options.search);
        if (options.page) params.append('page', options.page);
        if (options.page_size) params.append('page_size', options.page_size);
        if (options.page_context) params.append('page_context', options.page_context);
        if (options.apply_profile_default != null) {
            params.append('apply_profile_default', String(Boolean(options.apply_profile_default)));
        }
        if (options.override_show_all != null) {
            params.append('override_show_all', String(Boolean(options.override_show_all)));
        }
        if (options.filters?.title) {
            for (const value of options.filters.title) params.append('filter_title', value);
        }
        if (options.filters?.git_status) {
            for (const value of options.filters.git_status) params.append('filter_git_status', value);
        }
        if (options.filters?.llm_status) {
            for (const value of options.filters.llm_status) params.append('filter_llm_status', value);
        }
        if (options.filters?.changed_on) {
            for (const value of options.filters.changed_on) params.append('filter_changed_on', value);
        }
        if (options.filters?.actor) {
            for (const value of options.filters.actor) params.append('filter_actor', value);
        }
        return fetchApi(`/dashboards?${params.toString()}`);
    },
    getDashboardDetail: (envId, dashboardRef) => fetchApi(`/dashboards/${encodeURIComponent(String(dashboardRef))}?env_id=${envId}`),
    getDashboardTaskHistory: (envId, dashboardRef, options = {}) => {
        const params = new URLSearchParams();
        if (envId) params.append('env_id', envId);
        if (options.limit) params.append('limit', options.limit);
        return fetchApi(`/dashboards/${encodeURIComponent(String(dashboardRef))}/tasks?${params.toString()}`);
    },
    getDashboardThumbnail: (envId, dashboardRef, options = {}) => {
        const params = new URLSearchParams();
        params.append('env_id', envId);
        if (options.force != null) params.append('force', String(Boolean(options.force)));
        return fetchApiBlob(`/dashboards/${encodeURIComponent(String(dashboardRef))}/thumbnail?${params.toString()}`, { notifyError: false });
    },
    getDatabaseMappings: (sourceEnvId, targetEnvId) => fetchApi(`/dashboards/db-mappings?source_env_id=${sourceEnvId}&target_env_id=${targetEnvId}`),
    calculateMigrationDryRun: (payload) => postApi('/migration/dry-run', payload),

    // Datasets
    getDatasets: (envId, options = {}) => {
        const params = new URLSearchParams({ env_id: envId });
        if (options.search) params.append('search', options.search);
        if (options.page) params.append('page', options.page);
        if (options.page_size) params.append('page_size', options.page_size);
        return fetchApi(`/datasets?${params.toString()}`);
    },
    getDatasetIds: (envId, options = {}) => {
        const params = new URLSearchParams({ env_id: envId });
        if (options.search) params.append('search', options.search);
        return fetchApi(`/datasets/ids?${params.toString()}`);
    },
    getDatasetDetail: (envId, datasetId) => fetchApi(`/datasets/${datasetId}?env_id=${envId}`),

    // Settings
    getConsolidatedSettings: () => fetchApi('/settings/consolidated'),
    updateConsolidatedSettings: (settings) => requestApi('/settings/consolidated', 'PATCH', settings),

    // Automation Policies
    getValidationPolicies: () => fetchApi('/settings/automation/policies'),
    createValidationPolicy: (policy) => postApi('/settings/automation/policies', policy),
    updateValidationPolicy: (id, policy) => requestApi(`/settings/automation/policies/${id}`, 'PATCH', policy),
    deleteValidationPolicy: (id) => requestApi(`/settings/automation/policies/${id}`, 'DELETE'),

    // Health
    getHealthSummary: (environmentId) => {
        const params = new URLSearchParams();
        if (environmentId) params.append('environment_id', environmentId);
        const query = params.toString();
        return fetchApi(`/health/summary${query ? `?${query}` : ''}`);
    },
};
// [/DEF:api:Data]

// [/DEF:api_module:Module]

// Export individual functions for easier use in components
export { requestApi };
export const getPlugins = api.getPlugins;
export const getTasks = api.getTasks;
export const getTask = api.getTask;
export const createTask = api.createTask;
export const getProfilePreferences = api.getProfilePreferences;
export const updateProfilePreferences = api.updateProfilePreferences;
export const lookupSupersetAccounts = api.lookupSupersetAccounts;
export const getSettings = api.getSettings;
export const updateGlobalSettings = api.updateGlobalSettings;
export const getEnvironments = api.getEnvironments;
export const addEnvironment = api.addEnvironment;
export const updateEnvironment = api.updateEnvironment;
export const deleteEnvironment = api.deleteEnvironment;
export const testEnvironmentConnection = api.testEnvironmentConnection;
export const updateEnvironmentSchedule = api.updateEnvironmentSchedule;
export const getEnvironmentsList = api.getEnvironmentsList;
export const getStorageSettings = api.getStorageSettings;
export const updateStorageSettings = api.updateStorageSettings;
export const getDashboards = api.getDashboards;
export const getDatasets = api.getDatasets;
export const getConsolidatedSettings = api.getConsolidatedSettings;
export const updateConsolidatedSettings = api.updateConsolidatedSettings;
export const getValidationPolicies = api.getValidationPolicies;
export const createValidationPolicy = api.createValidationPolicy;
export const updateValidationPolicy = api.updateValidationPolicy;
export const deleteValidationPolicy = api.deleteValidationPolicy;
export const getHealthSummary = api.getHealthSummary;
