import { RecipeCard } from '../components/RecipeCard.js';
import { state, getCurrentPageState } from '../state/index.js';
import { showToast } from '../utils/uiHelpers.js';
import { captureScrollPosition, restoreScrollPosition } from '../utils/infiniteScroll.js';

const RECIPE_ENDPOINTS = {
    list: '/api/lm/recipes',
    detail: '/api/lm/recipe',
    scan: '/api/lm/recipes/scan',
    update: '/api/lm/recipe',
    roots: '/api/lm/recipes/roots',
    folders: '/api/lm/recipes/folders',
    folderTree: '/api/lm/recipes/folder-tree',
    unifiedFolderTree: '/api/lm/recipes/unified-folder-tree',
    move: '/api/lm/recipe/move',
    moveBulk: '/api/lm/recipes/move-bulk',
    bulkDelete: '/api/lm/recipes/bulk-delete',
    repairBulk: '/api/lm/recipes/repair-bulk',
    rematchBulk: '/api/lm/recipes/rematch-bulk',
    rematchSingle: '/api/lm/recipe/{recipe_id}/rematch',
};

const RECIPE_SIDEBAR_CONFIG = {
    config: {
        displayName: 'Recipe',
        supportsMove: true,
    },
    endpoints: RECIPE_ENDPOINTS,
};

export function extractRecipeId(filePath) {
    if (!filePath) return null;
    const basename = filePath.split('/').pop().split('\\').pop();
    const dotIndex = basename.lastIndexOf('.');
    return dotIndex > 0 ? basename.substring(0, dotIndex) : basename;
}

export async function fetchRecipeDetails(recipeId) {
    if (!recipeId) {
        throw new Error('Unable to determine recipe ID');
    }

    const encodedRecipeId = encodeURIComponent(recipeId);
    const response = await fetch(`${RECIPE_ENDPOINTS.detail}/${encodedRecipeId}`);
    if (!response.ok) {
        throw new Error(`Failed to load recipe: ${response.statusText}`);
    }

    return response.json();
}

export async function sendRecipeWorkflow(recipeId) {
    if (!recipeId) {
        throw new Error('Unable to determine recipe ID');
    }

    const encodedRecipeId = encodeURIComponent(recipeId);
    const response = await fetch(`${RECIPE_ENDPOINTS.detail}/${encodedRecipeId}/send-workflow`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
    });

    const result = await response.json();

    if (!response.ok) {
        return { success: false, error: result.error || response.statusText };
    }

    return result;
}

/**
 * Fetch recipes with pagination for virtual scrolling
 * @param {number} page - Page number to fetch
 * @param {number} pageSize - Number of items per page
 * @returns {Promise<Object>} Object containing items, total count, and pagination info
 */
export async function fetchRecipesPage(page = 1, pageSize = 100) {
    const pageState = getCurrentPageState();

    try {
        const params = new URLSearchParams({
            page: page,
            page_size: pageSize || pageState.pageSize || 20,
            sort_by: pageState.sortBy
        });

        if (pageState.showFavoritesOnly) {
            params.append('favorite', 'true');
        }

        if (pageState.activeFolder !== null && pageState.activeFolder !== undefined) {
            params.append('folder', pageState.activeFolder);
            params.append('recursive', pageState.searchOptions?.recursive !== false);
        } else if (pageState.searchOptions?.recursive !== undefined) {
            params.append('recursive', pageState.searchOptions.recursive);
        }

        // If we have a specific recipe ID to load
        if (pageState.customFilter?.active && pageState.customFilter?.recipeId) {
            // Special case: load specific recipe
            const response = await fetch(
                `${RECIPE_ENDPOINTS.detail}/${encodeURIComponent(pageState.customFilter.recipeId)}`
            );

            if (!response.ok) {
                throw new Error(`Failed to load recipe: ${response.statusText}`);
            }

            const recipe = await response.json();

            // Return in expected format
            return {
                items: [recipe],
                totalItems: 1,
                totalPages: 1,
                currentPage: 1,
                hasMore: false
            };
        }

        // Add custom filter for Lora if present
        if (pageState.customFilter?.active && pageState.customFilter?.loraHash) {
            params.append('lora_hash', pageState.customFilter.loraHash);
            params.append('bypass_filters', 'true');
        } else if (pageState.customFilter?.active && pageState.customFilter?.checkpointHash) {
            params.append('checkpoint_hash', pageState.customFilter.checkpointHash);
            params.append('bypass_filters', 'true');
        } else {
            // Normal filtering logic

            // Add search filter if present
            if (pageState.filters?.search) {
                params.append('search', pageState.filters.search);

                // Add search option parameters
                if (pageState.searchOptions) {
                    params.append('search_title', pageState.searchOptions.title.toString());
                    params.append('search_tags', pageState.searchOptions.tags.toString());
                    params.append('search_lora_name', pageState.searchOptions.loraName.toString());
                    params.append('search_lora_model', pageState.searchOptions.loraModel.toString());
                    params.append('search_prompt', (pageState.searchOptions.prompt || false).toString());
                    params.append('fuzzy', 'true');
                }
            }

            // Add base model filters
            if (pageState.filters?.baseModel && pageState.filters.baseModel.length) {
                // Check for empty wildcard marker - if present, no models should match
                const EMPTY_WILDCARD_MARKER = '__EMPTY_WILDCARD_RESULT__';
                if (pageState.filters.baseModel.length === 1 && 
                    pageState.filters.baseModel[0] === EMPTY_WILDCARD_MARKER) {
                    // Wildcard resolved to no matches - return empty results
                    return {
                        items: [],
                        totalItems: 0,
                        totalPages: 0,
                        currentPage: page,
                        hasMore: false
                    };
                }
                params.append('base_models', pageState.filters.baseModel.join(','));
            }

            // Add tag filters
            if (pageState.filters?.tags && Object.keys(pageState.filters.tags).length) {
                Object.entries(pageState.filters.tags).forEach(([tag, state]) => {
                    if (state === 'include') {
                        params.append('tag_include', tag);
                    } else if (state === 'exclude') {
                        params.append('tag_exclude', tag);
                    }
                });
            }

            // Add LoRA availability filter (no statuses selected = no filtering)
            if (pageState.filters?.loraAvailability && pageState.filters.loraAvailability.length > 0) {
                params.append('lora_availability', pageState.filters.loraAvailability.join(','));
            }
        }

        // Fetch recipes
        const response = await fetch(`${RECIPE_ENDPOINTS.list}?${params.toString()}`);

        if (!response.ok) {
            throw new Error(`Failed to load recipes: ${response.statusText}`);
        }

        const data = await response.json();

        return {
            items: data.items,
            totalItems: data.total,
            totalPages: data.total_pages,
            currentPage: page,
            hasMore: page < data.total_pages
        };
    } catch (error) {
        console.error('Error fetching recipes:', error);
        showToast('toast.recipes.fetchFailed', { message: error.message }, 'error');
        throw error;
    }
}

/**
 * Reset and reload models using virtual scrolling
 * @param {Object} options - Operation options
 * @returns {Promise<Object>} The fetch result
 */
export async function resetAndReloadWithVirtualScroll(options = {}) {
    const {
        modelType = 'lora',
        updateFolders = false,
        fetchPageFunction,
        preserveScroll = false
    } = options;

    const pageState = getCurrentPageState();
    const scrollSnapshot = preserveScroll ? captureScrollPosition() : null;

    try {
        pageState.isLoading = true;

        // Reset page counter
        pageState.currentPage = 1;

        const pageSize = state.virtualScroller?.pageSize || pageState.pageSize || 100;
        const result = await fetchPageFunction(1, pageSize);

        // Update the virtual scroller
        state.virtualScroller.refreshWithData(
            result.items,
            result.totalItems,
            result.hasMore
        );

        // Update state
        pageState.hasMore = result.hasMore;
        pageState.currentPage = 2; // Next page will be 2

        if (scrollSnapshot) {
            await restoreScrollPosition(scrollSnapshot);
        } else if (state.virtualScroller?.scrollContainer) {
            state.virtualScroller.scrollContainer.scrollTop = 0;
        }

        return result;
    } catch (error) {
        console.error(`Error reloading ${modelType}s:`, error);
        showToast('toast.recipes.reloadFailed', { modelType: modelType, message: error.message }, 'error');
        throw error;
    } finally {
        pageState.isLoading = false;
    }
}

/**
 * Load more models using virtual scrolling
 * @param {Object} options - Operation options
 * @returns {Promise<Object>} The fetch result
 */
export async function loadMoreWithVirtualScroll(options = {}) {
    const {
        modelType = 'lora',
        resetPage = false,
        updateFolders = false,
        fetchPageFunction,
        preserveScroll = false
    } = options;

    const pageState = getCurrentPageState();
    const scrollSnapshot = preserveScroll ? captureScrollPosition() : null;

    try {
        // Start loading state
        pageState.isLoading = true;

        // Reset to first page if requested
        if (resetPage) {
            pageState.currentPage = 1;
        }

        const pageSize = state.virtualScroller?.pageSize || pageState.pageSize || 100;
        const result = await fetchPageFunction(pageState.currentPage, pageSize);

        // Update virtual scroller with the new data
        state.virtualScroller.refreshWithData(
            result.items,
            result.totalItems,
            result.hasMore
        );

        // Update state
        pageState.hasMore = result.hasMore;
        pageState.currentPage = 2; // Next page to load would be 2

        if (scrollSnapshot) {
            await restoreScrollPosition(scrollSnapshot);
        }

        return result;
    } catch (error) {
        console.error(`Error loading ${modelType}s:`, error);
        showToast('toast.recipes.loadFailed', { modelType: modelType, message: error.message }, 'error');
        throw error;
    } finally {
        pageState.isLoading = false;
    }
}

/**
 * Reset and reload recipes using virtual scrolling
 * @param {boolean} updateFolders - Whether to update folder tags
 * @returns {Promise<Object>} The fetch result
 */
export async function resetAndReload(updateFolders = false, options = {}) {
    return resetAndReloadWithVirtualScroll({
        modelType: 'recipe',
        updateFolders,
        fetchPageFunction: fetchRecipesPage,
        preserveScroll: options.preserveScroll === true
    });
}

/**
 * Refreshes the recipe list by triggering a backend scan, then reloading.
 * @param {boolean} fullRebuild - If true, fully rebuild the cache; if false, incremental scan
 */
export async function syncChanges() {
    return refreshRecipes(false);
}

export async function refreshRecipes(fullRebuild = true) {
    const actionLabel = fullRebuild ? 'Rebuilding recipe cache' : 'Refreshing recipes';
    const actionToast = fullRebuild ? 'Full rebuild' : 'Refresh';

    try {
        state.loadingManager.show(`${actionLabel}...`, 0);

        const url = new URL(RECIPE_ENDPOINTS.scan, window.location.origin);
        url.searchParams.append('full_rebuild', fullRebuild);

        const response = await fetch(url);

        if (!response.ok) {
            throw new Error(`Failed to refresh recipe cache: ${response.status} ${response.statusText}`);
        }

        const data = await response.json();
        if (data.status === 'cancelled') {
            showToast('toast.api.operationCancelled', {}, 'info');
            return;
        }

        await resetAndReload(false);

        showToast('toast.api.refreshComplete', { action: actionToast }, 'success');
    } catch (error) {
        console.error('Error refreshing recipes:', error);
        showToast('toast.api.refreshFailed', { action: fullRebuild ? 'rebuild' : 'refresh', type: 'recipe' }, 'error');
    } finally {
        state.loadingManager.hide();
        state.loadingManager.restoreProgressBar();
    }
}

/**
 * Load more recipes with pagination - updated to work with VirtualScroller
 * @param {boolean} resetPage - Whether to reset to the first page
 * @returns {Promise<void>}
 */
export async function loadMoreRecipes(resetPage = false) {
    const pageState = getCurrentPageState();

    // Use virtual scroller if available
    if (state.virtualScroller) {
        return loadMoreWithVirtualScroll({
            modelType: 'recipe',
            resetPage,
            updateFolders: false,
            fetchPageFunction: fetchRecipesPage
        });
    }
}

/**
 * Create a recipe card instance from recipe data
 * @param {Object} recipe - Recipe data
 * @returns {HTMLElement} Recipe card DOM element
 */
export function createRecipeCard(recipe) {
    const recipeCard = new RecipeCard(recipe, (recipe) => {
        if (window.recipeManager) {
            window.recipeManager.showRecipeDetails(recipe);
        }
    });
    return recipeCard.element;
}

/**
 * Update recipe metadata on the server
 * @param {string} filePath - The file path of the recipe (e.g. D:/Workspace/ComfyUI/models/loras/recipes/86b4c335-ecfc-4791-89d2-3746e55a7614.webp)
 * @param {Object} updates - The metadata updates to apply
 * @returns {Promise<Object>} The updated recipe data
 */
export async function updateRecipeMetadata(filePath, updates, options = {}) {
    try {
        state.loadingManager.showSimpleLoading('Saving metadata...');
        const listFilePath = options.listFilePath || filePath;

        // Extract recipeId from filePath (basename without extension)
        const recipeId = extractRecipeId(filePath);
        if (!recipeId) {
            throw new Error('Unable to determine recipe ID');
        }

        const response = await fetch(`${RECIPE_ENDPOINTS.update}/${encodeURIComponent(recipeId)}/update`, {
            method: 'PUT',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(updates)
        });

        const data = await response.json();

        if (!data.success) {
            showToast('toast.recipes.updateFailed', { error: data.error }, 'error');
            throw new Error(data.error || 'Failed to update recipe');
        }

        state.virtualScroller.updateSingleItem(listFilePath, updates);

        return data;
    } catch (error) {
        console.error('Error updating recipe:', error);
        showToast('toast.recipes.updateError', { message: error.message }, 'error');
        throw error;
    } finally {
        state.loadingManager.hide();
    }
}

export class RecipeSidebarApiClient {
    constructor() {
        this.apiConfig = RECIPE_SIDEBAR_CONFIG;
    }

    async fetchUnifiedFolderTree() {
        const response = await fetch(this.apiConfig.endpoints.unifiedFolderTree);
        if (!response.ok) {
            throw new Error('Failed to fetch recipe folder tree');
        }
        return response.json();
    }

    async fetchModelRoots() {
        const response = await fetch(this.apiConfig.endpoints.roots);
        if (!response.ok) {
            throw new Error('Failed to fetch recipe roots');
        }
        return response.json();
    }

    async fetchModelFolders() {
        const response = await fetch(this.apiConfig.endpoints.folders);
        if (!response.ok) {
            throw new Error('Failed to fetch recipe folders');
        }
        return response.json();
    }

    async moveBulkModels(filePaths, targetPath) {
        if (!this.apiConfig.config.supportsMove) {
            showToast('toast.api.bulkMoveNotSupported', { type: this.apiConfig.config.displayName }, 'warning');
            return [];
        }

        const recipeIds = filePaths
            .map((path) => extractRecipeId(path))
            .filter((id) => !!id);

        if (recipeIds.length === 0) {
            showToast('toast.models.noModelsSelected', {}, 'warning');
            return [];
        }

        const response = await fetch(this.apiConfig.endpoints.moveBulk, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                recipe_ids: recipeIds,
                target_path: targetPath,
            }),
        });

        const result = await response.json();

        if (!response.ok || !result.success) {
            throw new Error(result.error || `Failed to move ${this.apiConfig.config.displayName}s`);
        }

        if (result.failure_count > 0) {
            showToast(
                'toast.api.bulkMovePartial',
                {
                    successCount: result.success_count,
                    type: this.apiConfig.config.displayName,
                    failureCount: result.failure_count,
                },
                'warning'
            );

            const failedFiles = (result.results || [])
                .filter((item) => !item.success)
                .map((item) => item.message || 'Unknown error');

            if (failedFiles.length > 0) {
                const failureMessage =
                    failedFiles.length <= 3
                        ? failedFiles.join('\n')
                        : `${failedFiles.slice(0, 3).join('\n')}\n(and ${failedFiles.length - 3} more)`;
                showToast('toast.api.bulkMoveFailures', { failures: failureMessage }, 'warning', 6000);
            }
        } else {
            showToast(
                'toast.api.bulkMoveSuccess',
                {
                    successCount: result.success_count,
                    type: this.apiConfig.config.displayName,
                },
                'success'
            );
        }

        return result.results || [];
    }

    async moveSingleModel(filePath, targetPath) {
        if (!this.apiConfig.config.supportsMove) {
            showToast('toast.api.moveNotSupported', { type: this.apiConfig.config.displayName }, 'warning');
            return null;
        }

        const recipeId = extractRecipeId(filePath);
        if (!recipeId) {
            showToast('toast.api.moveFailed', { message: 'Recipe ID missing' }, 'error');
            return null;
        }

        const response = await fetch(this.apiConfig.endpoints.move, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                recipe_id: recipeId,
                target_path: targetPath,
            }),
        });

        const result = await response.json();

        if (!response.ok || !result.success) {
            throw new Error(result.error || `Failed to move ${this.apiConfig.config.displayName}`);
        }

        if (result.message) {
            showToast('toast.api.moveInfo', { message: result.message }, 'info');
        } else {
            showToast('toast.api.moveSuccess', { type: this.apiConfig.config.displayName }, 'success');
        }

        return {
            original_file_path: result.original_file_path || filePath,
            new_file_path: result.new_file_path || filePath,
            folder: result.folder || '',
            message: result.message,
        };
    }

    async repairBulkModels(filePaths) {
        if (!filePaths || filePaths.length === 0) {
            throw new Error('No file paths provided');
        }

        const recipeIds = filePaths
            .map((path) => extractRecipeId(path))
            .filter((id) => !!id);

        if (recipeIds.length === 0) {
            throw new Error('No recipe IDs could be derived from file paths');
        }

        const response = await fetch(this.apiConfig.endpoints.repairBulk, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                recipe_ids: recipeIds,
            }),
        });

        const result = await response.json();

        if (!response.ok || !result.success) {
            throw new Error(result.error || 'Failed to repair recipes');
        }

        return result;
    }

    async rematchBulkModels(filePaths) {
        if (!filePaths || filePaths.length === 0) {
            throw new Error('No file paths provided');
        }

        const recipeIds = filePaths
            .map((path) => extractRecipeId(path))
            .filter((id) => !!id);

        if (recipeIds.length === 0) {
            throw new Error('No recipe IDs could be derived from file paths');
        }

        const response = await fetch(this.apiConfig.endpoints.rematchBulk, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                recipe_ids: recipeIds,
            }),
        });

        const result = await response.json();

        if (!response.ok || !result.success) {
            throw new Error(result.error || 'Failed to rematch recipes');
        }

        return result;
    }

    async bulkDeleteModels(filePaths) {
        if (!filePaths || filePaths.length === 0) {
            throw new Error('No file paths provided');
        }

        const recipeIds = filePaths
            .map((path) => extractRecipeId(path))
            .filter((id) => !!id);

        if (recipeIds.length === 0) {
            throw new Error('No recipe IDs could be derived from file paths');
        }

        try {
            state.loadingManager?.showSimpleLoading('Deleting recipes...');

            const response = await fetch(this.apiConfig.endpoints.bulkDelete, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    recipe_ids: recipeIds,
                }),
            });

            const result = await response.json();

            if (!response.ok || !result.success) {
                throw new Error(result.error || 'Failed to delete recipes');
            }

            return {
                success: true,
                deleted_count: result.total_deleted,
                failed_count: result.total_failed || 0,
                errors: result.failed || [],
                // Undo batch fields — batch_id on merge success, batch_ids
                // array on merge failure
                batch_id: result.batch_id || null,
                batch_ids: result.batch_ids || null,
            };
        } finally {
            state.loadingManager?.hide();
        }
    }
}
