/**
 * Content Plan Plugin - Context for Global State Management
 */

import {
    createContext,
    useContext,
    useState,
    useEffect,
    useCallback,
    useRef,
} from '@wordpress/element';
import type React from 'react';
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';
import {
    ContentPlanContextState,
    ContentPlan,
    ContentPlanItem,
    Post,
    Settings,
    FieldConfigs,
    Strings,
    Data,
    User,
    UsageData,
    PresetResponse,
    ApiResponse,
    PostCreationFormData,
    ContentPlanGenerationFormData,
    Task,
    Log,
} from '../types';

// Create the context
const ContentPlanContext = createContext<ContentPlanContextState | undefined>(
    undefined
);

// Custom hook to use the context
export const useContentPlan = (): ContentPlanContextState => {
    const context = useContext(ContentPlanContext);
    if (!context) {
        throw new Error(
            'useContentPlan must be used within a ContentPlanProvider'
        );
    }
    return context;
};

// Context Provider Component
interface ContentPlanProviderProps {
    children: React.ReactNode;
}

export const ContentPlanProvider: React.FC<ContentPlanProviderProps> = ({
    children,
}) => {
    const [loading, setLoading] = useState<boolean>(false);
    const [saving, setSaving] = useState<boolean>(false);
    const [error, setError] = useState<string | null>(null);
    const [contentPlan, setContentPlan] = useState<ContentPlan>({
        contentPlanItems: [],
    });
    const [posts, setPosts] = useState<Post[]>([]);
    const [settings, setSettings] = useState<Settings>({});
    const [fieldConfigs, setFieldConfigs] = useState<FieldConfigs>({});
    const [strings, setStrings] = useState<Strings>({});
    const [data, setData] = useState<Data>({});
    const [user, setUser] = useState<User>({ name: '', role: '' });
    const [usage, setUsage] = useState<UsageData | null>(null);
    const [usageError, setUsageError] = useState<string | null>(null);
    const [backendConfigured, setBackendConfigured] = useState<boolean>(false);
    const [sidebarOpen, setSidebarOpen] = useState<boolean>(false);
    const [sidebarType, setSidebarType] = useState<string | null>(null); // 'post-creation', 'settings', etc.
    const [generationModalOpen, setGenerationModalOpen] =
        useState<boolean>(false);
    const [tasks, setTasks] = useState<Task[]>([]);
    const [tasksLoading, setTasksLoading] = useState<boolean>(false);
    const [tasksError, setTasksError] = useState<string | null>(null);
    const [logs, setLogs] = useState<Log[]>([]);
    const [logsLoading, setLogsLoading] = useState<boolean>(false);
    const [logsError, setLogsError] = useState<string | null>(null);
    const [currentTaskId, setCurrentTaskId] = useState<string | null>(null);
    const [shouldPollTasks, setShouldPollTasks] = useState<boolean>(false);
    const [taskFilterId, setTaskFilterId] = useState<number | null>(null); // Task ID to filter content plan items by
    const [isNewWebsite, setIsNewWebsite] = useState<boolean>(false);
    const [opted, setOpted] = useState<boolean | undefined>(undefined);
    const [optinUrl, setOptinUrl] = useState<string | undefined>(undefined);
    const [timezone, setTimezone] = useState<{
        string: string;
        offset: number;
    } | null>(null);
    // Track if preset has been loaded (prevents tasks from being fetched before preset)
    const presetLoadedRef = useRef<boolean>(false);
    // Store previous tasks to detect status changes
    const previousTasksRef = useRef<Task[]>([]);
    // Track when failed tasks without results were first seen (for timeout)
    const failedTasksStartTimeRef = useRef<Map<string, number>>(new Map());
    // Track polling interval to prevent multiple intervals
    const pollingIntervalRef = useRef<NodeJS.Timeout | null>(null);
    // Track if fetchTasks is currently in progress to prevent concurrent calls
    const fetchTasksInProgressRef = useRef<boolean>(false);
    // Track which completed task IDs we've already refreshed usage for
    const refreshedUsageForTasksRef = useRef<Set<number>>(new Set());

    // Actions for content plan data
    const updateContentPlan = (newData: Partial<ContentPlan>): void => {
        setContentPlan(prevData => ({ ...prevData, ...newData }));
    };

    const addContentPlanItem = (item: ContentPlanItem): void => {
        setContentPlan(prevData => ({
            ...prevData,
            contentPlanItems: [...prevData.contentPlanItems, item],
        }));
    };

    const updateContentPlanItemLocal = (
        itemId: number,
        updatedItem: Partial<ContentPlanItem>
    ): void => {
        setContentPlan(prevData => ({
            ...prevData,
            contentPlanItems: prevData.contentPlanItems.map(item =>
                item.id === itemId ? { ...item, ...updatedItem } : item
            ),
        }));
    };

    const deleteContentPlanItemLocal = (itemId: number): void => {
        setContentPlan(prevData => ({
            ...prevData,
            contentPlanItems: prevData.contentPlanItems.filter(
                item => item.id !== itemId
            ),
        }));
    };

    const updateSettings = (newSettings: Partial<Settings>): void => {
        setSettings(prevSettings => {
            const updated = { ...prevSettings, ...newSettings };
            return updated;
        });
    };

    const updateFieldConfigs = (newConfigs: Partial<FieldConfigs>): void => {
        setFieldConfigs(prevConfigs => {
            const updated = { ...prevConfigs };
            Object.keys(newConfigs).forEach(key => {
                if (newConfigs[key]) {
                    updated[key] = newConfigs[key]!;
                }
            });
            return updated;
        });
    };

    const updateStrings = (newStrings: Partial<Strings>): void => {
        setStrings(prevStrings => {
            const updated = { ...prevStrings };
            Object.keys(newStrings).forEach(key => {
                if (newStrings[key]) {
                    updated[key] = newStrings[key]!;
                }
            });
            return updated;
        });
    };

    const clearError = (): void => {
        setError(null);
    };

    const setLoadingState = (isLoading: boolean): void => {
        setLoading(isLoading);
    };

    const setSavingState = (isSaving: boolean): void => {
        setSaving(isSaving);
    };

    const setErrorState = (errorMessage: string | null): void => {
        setError(errorMessage);
    };

    // Sidebar control functions
    const openSidebar = (type: string): void => {
        setSidebarType(type);
        setSidebarOpen(true);
    };

    const closeSidebar = (): void => {
        setSidebarOpen(false);
        setSidebarType(null);
    };

    // Generation modal control functions
    const openGenerationModal = (): void => {
        setGenerationModalOpen(true);
    };

    const closeGenerationModal = (): void => {
        setGenerationModalOpen(false);
    };

    const setCurrentTaskIdState = (taskId: string | null): void => {
        setCurrentTaskId(taskId);
    };

    // Task filter functions
    const setTaskFilter = (taskId: number | null): void => {
        setTaskFilterId(taskId);
    };

    const clearTaskFilter = (): void => {
        setTaskFilterId(null);
    };

    // Helper function to get strings with fallback
    const getString = (
        category: string,
        key: string,
        fallback: string = ''
    ): string => {
        return strings[category]?.[key] || fallback;
    };

    // Helper function to get the latest date from content plan items
    const getLatestContentPlanDate = (): string | null => {
        if (
            !contentPlan.contentPlanItems ||
            contentPlan.contentPlanItems.length === 0
        ) {
            return null;
        }

        const dates = contentPlan.contentPlanItems
            .map(item => item.scheduled_on)
            .filter(date => date !== null && date !== undefined) as string[];

        if (dates.length === 0) {
            return null;
        }

        // Compare dates as strings (YYYY-MM-DD format sorts correctly)
        // This avoids timezone conversion issues
        const latestDate = dates.reduce((latest, current) => {
            return current > latest ? current : latest;
        });

        // Return just the date part (YYYY-MM-DD) without time
        const dateOnly = latestDate.split(' ')[0];
        return dateOnly;
    };

    // Fetch preset data (settings, field configs, strings)
    const fetchPresetData = useCallback(async (): Promise<void> => {
        setLoading(true);
        setError(null);

        try {
            // Get locale from localized script data
            const locale = (window as any).cpepai_data?.locale || '';
            const presetPath = locale
                ? `/cpepai/v1/preset?locale=${encodeURIComponent(locale)}`
                : '/cpepai/v1/preset';
            const preset: PresetResponse = await apiFetch({
                path: presetPath,
                method: 'GET',
            });

            if (preset.settings) {
                // Merge with existing settings to preserve optimistic updates
                // Preserve onboarding_completed if it was set optimistically
                setSettings(prevSettings => {
                    const merged = {
                        ...prevSettings,
                        ...preset.settings,
                    };
                    // Preserve onboarding_completed from previous settings if it was true
                    // (optimistic update should not be overwritten by stale server data)
                    if (
                        prevSettings.onboarding_completed === true &&
                        preset.settings &&
                        (!preset.settings.onboarding_completed ||
                            preset.settings.onboarding_completed !== true)
                    ) {
                        merged.onboarding_completed = true;
                    }
                    return merged;
                });
            }
            if (preset.field_configs) {
                setFieldConfigs(preset.field_configs);
            }
            if (preset.strings) {
                setStrings(preset.strings);
            }
            if (preset.backend_configured !== undefined) {
                setBackendConfigured(preset.backend_configured);
            }
            if (preset.usage) {
                setUsage(preset.usage);
                setUsageError(null); // Clear error if usage is successfully fetched
            } else {
                // Set usage error if backend is configured but fetch failed
                // Otherwise, clear error (backend not configured is not an error)
                if (preset.backend_configured && preset.usage_error) {
                    setUsageError(preset.usage_error);
                } else {
                    setUsageError(null);
                }
            }
            if (preset.is_new_website !== undefined) {
                setIsNewWebsite(preset.is_new_website);
            }
            if (preset.opted !== undefined) {
                setOpted(preset.opted);
            }
            if (preset.optin_url !== undefined) {
                setOptinUrl(preset.optin_url);
            }
            if (preset.timezone) {
                setTimezone(preset.timezone);
            }

            // Mark preset as loaded
            presetLoadedRef.current = true;
        } catch (err: any) {
            setError(err.message);
            // Only set usage error if the entire preset fetch failed
            // (which would prevent usage from being fetched)
            setUsageError(err.message);
            console.error('Error fetching preset data:', err);
            // Still mark as loaded even on error to allow app to continue
            presetLoadedRef.current = true;
        } finally {
            setLoading(false);
        }
    }, []);

    // Refresh usage data only (lightweight function for frequent updates)
    const refreshUsage = useCallback(async (): Promise<void> => {
        try {
            // Get locale from localized script data
            const locale = (window as any).cpepai_data?.locale || '';
            // Add cache-busting parameter and locale to ensure fresh data
            const cacheBuster = `_=${Date.now()}`;
            const localeParam = locale
                ? `&locale=${encodeURIComponent(locale)}`
                : '';
            const preset: PresetResponse = await apiFetch({
                path: `/cpepai/v1/preset?${cacheBuster}${localeParam}`,
                method: 'GET',
            });

            if (preset.backend_configured !== undefined) {
                setBackendConfigured(preset.backend_configured);
            }
            if (preset.usage) {
                setUsage(preset.usage);
                setUsageError(null); // Clear error if usage is successfully fetched
            } else {
                // Set usage error if backend is configured but fetch failed
                // Otherwise, clear error (backend not configured is not an error)
                if (preset.backend_configured && preset.usage_error) {
                    setUsageError(preset.usage_error);
                } else {
                    setUsageError(null);
                }
            }
            if (preset.opted !== undefined) {
                setOpted(preset.opted);
            }
            if (preset.optin_url !== undefined) {
                setOptinUrl(preset.optin_url);
            }
        } catch (err: any) {
            // Set usage error to indicate fetch failed
            setUsageError(err.message || 'Failed to fetch usage data');
            console.error('Error refreshing usage data:', err);
        }
    }, []);

    // Save settings
    const saveSettings = useCallback(
        async (settingsData: Settings): Promise<any> => {
            setSaving(true);
            setError(null);

            try {
                const result = await apiFetch({
                    path: '/cpepai/v1/settings',
                    method: 'POST',
                    data: { settings: settingsData },
                });

                if ((result as any).settings) {
                    // Merge with existing settings to preserve optimistic updates
                    // Preserve onboarding_completed if it was set optimistically
                    setSettings(prevSettings => {
                        const merged = {
                            ...prevSettings,
                            ...(result as any).settings,
                        };
                        // Preserve onboarding_completed from previous settings if it was true
                        // (optimistic update should not be overwritten by stale server data)
                        if (
                            prevSettings.onboarding_completed === true &&
                            (!(result as any).settings.onboarding_completed ||
                                (result as any).settings
                                    .onboarding_completed !== true)
                        ) {
                            merged.onboarding_completed = true;
                        }
                        return merged;
                    });
                }
                return result;
            } catch (err: any) {
                setError(err.message);
                console.error('Error saving settings:', err);
                throw err;
            } finally {
                setSaving(false);
            }
        },
        []
    );

    // Refresh all data
    const refreshData = useCallback((): void => {
        fetchPresetData();
    }, [fetchPresetData]);

    // API Functions for Content Plan Items
    const fetchContentPlanItems = useCallback(
        async (planId: number = 1): Promise<void> => {
            setLoading(true);
            setError(null);

            try {
                const items: ContentPlanItem[] = await apiFetch({
                    path: `/cpepai/v1/items`,
                    method: 'GET',
                });

                // Ensure all items have a status field and post field
                const itemsWithStatus = (items || []).map(item => ({
                    ...item,
                    status: item.status || 'planned',
                    post: item.post || null,
                }));

                setContentPlan(prevData => ({
                    ...prevData,
                    contentPlanItems: itemsWithStatus,
                }));
            } catch (err: any) {
                console.error('Error fetching content plan items:', err);
                setError(err.message);
            } finally {
                setLoading(false);
            }
        },
        []
    );

    // Recalculate scheduled dates for future items
    const recalculateSchedule = useCallback(async (): Promise<void> => {
        setSaving(true);
        setError(null);

        try {
            const result = await apiFetch({
                path: '/cpepai/v1/items/recalculate-schedule',
                method: 'POST',
            });

            // Refresh content plan items to show updated dates
            await fetchContentPlanItems(1);
        } catch (err: any) {
            setError(err.message);
            console.error('Error recalculating schedule:', err);
            throw err;
        } finally {
            setSaving(false);
        }
    }, [fetchContentPlanItems]);

    const createContentPlanItem = useCallback(
        async (
            itemData: Partial<ContentPlanItem>,
            planId: number = 1
        ): Promise<void> => {
            setSaving(true);
            setError(null);

            // Create a temporary ID for optimistic update
            const tempId = -Date.now(); // Negative to avoid conflicts with real IDs

            // Create optimistic item
            const optimisticItem: ContentPlanItem = {
                id: tempId,
                title: itemData.title || '',
                structure: itemData.structure || '[]',
                keywords: itemData.keywords || '',
                status: itemData.status || 'planned',
                scheduled_on: itemData.scheduled_on || null,
                post: itemData.post || null,
                created_at: new Date().toISOString(),
                updated_at: new Date().toISOString(),
            };

            // Store previous state for rollback
            const previousItems = contentPlan.contentPlanItems;

            // Add optimistic item to state
            setContentPlan(prev => ({
                ...prev,
                contentPlanItems: [...prev.contentPlanItems, optimisticItem],
            }));

            try {
                const result: ApiResponse = await apiFetch({
                    path: `/cpepai/v1/items`,
                    method: 'POST',
                    data: itemData,
                });

                if (!result.success) {
                    throw new Error(
                        result.message || 'Failed to create content plan item'
                    );
                }

                // Update the temporary item with the real one from the server
                if (result.data) {
                    setContentPlan(prev => ({
                        ...prev,
                        contentPlanItems: prev.contentPlanItems.map(item =>
                            item.id === tempId
                                ? (result.data as ContentPlanItem)
                                : item
                        ),
                    }));
                }
            } catch (err: any) {
                // Rollback on error
                setContentPlan(prev => ({
                    ...prev,
                    contentPlanItems: previousItems,
                }));
                setError(err.message);
                console.error('Error creating content plan item:', err);
                throw err;
            } finally {
                setSaving(false);
            }
        },
        [contentPlan.contentPlanItems]
    );

    const updateContentPlanItemAPI = useCallback(
        async (
            id: number,
            itemData: Partial<ContentPlanItem>,
            planId: number = 1
        ): Promise<void> => {
            setSaving(true);
            setError(null);

            // Store the previous state for rollback if needed
            const previousItems = contentPlan.contentPlanItems;

            // Find existing item
            const existingItem = previousItems.find(item => item.id === id);
            if (!existingItem) {
                throw new Error('Item not found');
            }

            // Optimistically update the local state
            const updatedItem: ContentPlanItem = {
                ...existingItem,
                ...itemData,
                id,
                title: itemData.title || existingItem.title,
                structure: itemData.structure || existingItem.structure,
                keywords: itemData.keywords || existingItem.keywords,
                status: itemData.status || existingItem.status,
                scheduled_on:
                    itemData.scheduled_on !== undefined
                        ? itemData.scheduled_on
                        : existingItem.scheduled_on,
                post:
                    itemData.post !== undefined
                        ? itemData.post
                        : existingItem.post,
                created_at: existingItem.created_at,
                updated_at: new Date().toISOString(),
            };

            setContentPlan(prev => ({
                ...prev,
                contentPlanItems: prev.contentPlanItems.map(item =>
                    item.id === id ? updatedItem : item
                ),
            }));

            try {
                const result: ApiResponse = await apiFetch({
                    path: `/cpepai/v1/items/${id}`,
                    method: 'PUT',
                    data: itemData,
                });

                if (!result.success) {
                    throw new Error(
                        result.message || 'Failed to update content plan item'
                    );
                }
            } catch (err: any) {
                // Rollback to previous state on error
                setContentPlan(prev => ({
                    ...prev,
                    contentPlanItems: previousItems,
                }));
                setError(err.message);
                console.error('Error updating content plan item:', err);
                throw err;
            } finally {
                setSaving(false);
            }
        },
        [contentPlan.contentPlanItems]
    );

    const deleteContentPlanItemAPI = useCallback(
        async (id: number, planId: number = 1): Promise<void> => {
            setSaving(true);
            setError(null);

            // Store previous state for rollback if needed
            const previousItems = contentPlan.contentPlanItems;
            const itemToDelete = previousItems.find(item => item.id === id);

            // Optimistically remove the item from local state
            deleteContentPlanItemLocal(id);

            try {
                const result: ApiResponse = await apiFetch({
                    path: `/cpepai/v1/items/${id}`,
                    method: 'DELETE',
                });

                if (!result.success) {
                    throw new Error(
                        result.message || 'Failed to delete content plan item'
                    );
                }
            } catch (err: any) {
                // Rollback to previous state on error
                setContentPlan(prev => ({
                    ...prev,
                    contentPlanItems: previousItems,
                }));
                setError(err.message);
                console.error('Error deleting content plan item:', err);
                throw err;
            } finally {
                setSaving(false);
            }
        },
        [contentPlan.contentPlanItems, deleteContentPlanItemLocal]
    );

    // Bulk delete multiple content plan items
    const bulkDeleteContentPlanItems = useCallback(
        async (ids: number[], planId: number = 1): Promise<void> => {
            setSaving(true);
            setError(null);

            // Store previous state for rollback if needed
            const previousItems = contentPlan.contentPlanItems;

            // Optimistically remove the items from local state
            setContentPlan(prev => ({
                ...prev,
                contentPlanItems: prev.contentPlanItems.filter(
                    item => !ids.includes(item.id)
                ),
            }));

            try {
                const result: ApiResponse = await apiFetch({
                    path: `/cpepai/v1/items/bulk-delete`,
                    method: 'POST',
                    data: { ids },
                });

                if (!result.success) {
                    throw new Error(
                        result.message || 'Failed to delete content plan items'
                    );
                }
            } catch (err: any) {
                // Rollback to previous state on error
                setContentPlan(prev => ({
                    ...prev,
                    contentPlanItems: previousItems,
                }));
                setError(err.message);
                console.error('Error bulk deleting content plan items:', err);
                throw err;
            } finally {
                setSaving(false);
            }
        },
        [contentPlan.contentPlanItems]
    );

    // Create multiple content plan items with date scheduling
    const createMultipleContentPlanItems = useCallback(
        async (
            items: ContentPlanItem[],
            publishInterval: number,
            planId: number = 1,
            latestScheduledDate?: string | null
        ): Promise<void> => {
            setSaving(true);
            setError(null);

            // Calculate dates for approved items based on the latest scheduled date from backend
            let startDate: Date;

            if (
                latestScheduledDate &&
                typeof latestScheduledDate === 'string' &&
                latestScheduledDate.length > 0
            ) {
                // Parse the date from backend (might be "YYYY-MM-DD" or "YYYY-MM-DD HH:MM:SS")
                const dateOnly = latestScheduledDate.split(' ')[0];
                const [year, month, day] = dateOnly.split('-').map(Number);
                startDate = new Date(year, month - 1, day);
            } else {
                // Fallback to today if no date provided
                const today = new Date();
                startDate = new Date(
                    today.getFullYear(),
                    today.getMonth(),
                    today.getDate()
                );
            }

            // Calculate scheduled dates for each approved item
            const itemsWithScheduledDates = items.map((item, index) => {
                const year = startDate.getFullYear();
                const month = startDate.getMonth();
                const day = startDate.getDate();

                const scheduledDate = new Date(
                    year,
                    month,
                    day + (index + 1) * publishInterval
                );
                // Format date manually to avoid timezone conversion issues with toISOString()
                // Include time as 10:00:00 AM for publishing
                const scheduledDateStr = `${scheduledDate.getFullYear()}-${String(
                    scheduledDate.getMonth() + 1
                ).padStart(2, '0')}-${String(scheduledDate.getDate()).padStart(
                    2,
                    '0'
                )} 10:00:00`;

                return {
                    ...item,
                    scheduled_on: scheduledDateStr,
                    status: 'planned' as const,
                };
            });

            // Store previous state for rollback if needed
            const previousItems = contentPlan.contentPlanItems;

            // Store the temporary IDs to track which items to replace
            const tempIds = itemsWithScheduledDates.map(item => item.id);

            // Optimistically add items to local state
            setContentPlan(prev => ({
                ...prev,
                contentPlanItems: [
                    ...prev.contentPlanItems,
                    ...itemsWithScheduledDates,
                ],
            }));

            try {
                const result: ApiResponse = await apiFetch({
                    path: `/cpepai/v1/items/bulk`,
                    method: 'POST',
                    data: {
                        items: itemsWithScheduledDates.map(item => ({
                            title: item.title,
                            structure: item.structure,
                            keywords: item.keywords,
                            scheduled_on: item.scheduled_on,
                            status: item.status,
                        })),
                    },
                });

                if (!result.success) {
                    throw new Error(
                        result.message || 'Failed to create content plan items'
                    );
                }

                // Replace optimistic items with actual created items from the server
                if (result.data && Array.isArray(result.data)) {
                    setContentPlan(prev => ({
                        ...prev,
                        contentPlanItems: [
                            // Keep items that are not temporary (not in tempIds)
                            ...prev.contentPlanItems.filter(
                                item => !tempIds.includes(item.id)
                            ),
                            // Add the real items from the server
                            ...result.data,
                        ],
                    }));
                }

                // Refresh usage data after creating multiple items
                refreshUsage().catch(err => {
                    console.error(
                        'Error refreshing usage after creating multiple items:',
                        err
                    );
                });
            } catch (err: any) {
                // Rollback to previous state on error
                setContentPlan(prev => ({
                    ...prev,
                    contentPlanItems: previousItems,
                }));
                setError(err.message);
                console.error(
                    'Error creating multiple content plan items:',
                    err
                );
                throw err;
            } finally {
                setSaving(false);
            }
        },
        [contentPlan.contentPlanItems, refreshUsage]
    );

    // Create Post API Function
    const createPost = useCallback(
        async (postData: PostCreationFormData): Promise<void> => {
            setSaving(true);
            setError(null);

            try {
                const result: ApiResponse = await apiFetch({
                    path: '/cpepai/v1/posts',
                    method: 'POST',
                    data: {
                        title: postData.title.trim(),
                        content: postData.content.trim(),
                        excerpt: postData.excerpt.trim(),
                        status: postData.status,
                        category: postData.category.trim(),
                        tags: postData.tags
                            .split(',')
                            .map(tag => tag.trim())
                            .filter(tag => tag),
                        meta_description: postData.meta_description.trim(),
                        keywords: postData.keywords.trim(),
                        scheduled_on: postData.scheduled_on,
                    },
                });

                if (!result.success) {
                    throw new Error(result.message || 'Failed to create post');
                }
            } catch (err: any) {
                setError(err.message);
                console.error('Error creating post:', err);
                throw err;
            } finally {
                setSaving(false);
            }
        },
        []
    );

    // Generate Content Plan API Function
    const generateContentPlan = useCallback(
        async (
            formData: ContentPlanGenerationFormData
        ): Promise<{
            items: ContentPlanItem[];
            devInfo?: any;
            latestScheduledDate?: string;
            taskId?: string;
        }> => {
            setSaving(true);
            setError(null);

            try {
                let result: ApiResponse;
                try {
                    result = await apiFetch({
                        path: '/cpepai/v1/generate-plan',
                        method: 'POST',
                        data: formData,
                    });
                } catch (fetchError: any) {
                    // WordPress apiFetch might throw errors with different structures
                    console.error('Context: apiFetch threw error:', fetchError);
                    console.error(
                        'Context: fetchError type:',
                        typeof fetchError
                    );
                    console.error(
                        'Context: fetchError.data:',
                        fetchError?.data
                    );
                    console.error(
                        'Context: fetchError.response:',
                        fetchError?.response
                    );
                    console.error(
                        'Context: fetchError.message:',
                        fetchError?.message
                    );

                    // Extract error from various possible locations in the thrown error
                    let errorMsg = 'Failed to generate content plan';
                    if (fetchError && typeof fetchError === 'object') {
                        // WordPress apiFetch error structure: error.data contains the response body
                        // Check multiple possible locations
                        const errorData =
                            fetchError.data ||
                            fetchError.response?.data ||
                            fetchError.response ||
                            fetchError;

                        console.error(
                            'Context: Extracted errorData:',
                            errorData
                        );

                        // Check for error field (highest priority)
                        if (errorData.error) {
                            errorMsg = errorData.error;
                            console.error(
                                'Context: Found error in errorData.error:',
                                errorMsg
                            );
                        }
                        // Check for message field
                        else if (errorData.message) {
                            errorMsg = errorData.message;
                            console.error(
                                'Context: Found error in errorData.message:',
                                errorMsg
                            );
                        }
                        // Check for validation_errors
                        else if (
                            errorData.validation_errors &&
                            typeof errorData.validation_errors === 'object'
                        ) {
                            const validationErrors =
                                errorData.validation_errors;
                            const firstError =
                                Object.values(validationErrors)[0];
                            errorMsg =
                                typeof firstError === 'string'
                                    ? firstError
                                    : errorMsg;
                            console.error(
                                'Context: Found error in validation_errors:',
                                errorMsg
                            );
                        }
                        // Check fetchError.message as fallback
                        else if (fetchError.message) {
                            errorMsg = fetchError.message;
                            console.error(
                                'Context: Found error in fetchError.message:',
                                errorMsg
                            );
                        }
                    } else if (typeof fetchError === 'string') {
                        errorMsg = fetchError;
                        console.error(
                            'Context: fetchError is string:',
                            errorMsg
                        );
                    }

                    console.error(
                        'Context: Final error message to throw:',
                        errorMsg
                    );
                    throw new Error(errorMsg);
                }

                // IMMEDIATE check: If the response itself looks like an error object, throw immediately
                const resultAny = result as any;
                if (resultAny && typeof resultAny === 'object') {
                    // Check if this IS an error response (has error field and status_code)
                    if (resultAny.error && resultAny.status_code >= 400) {
                        console.error(
                            'Context: IMMEDIATE ERROR DETECTION - Response is error object:',
                            resultAny
                        );
                        let errorMsg =
                            resultAny.error ||
                            (resultAny.validation_errors &&
                            typeof resultAny.validation_errors === 'object'
                                ? Object.values(resultAny.validation_errors)[0]
                                : null) ||
                            'Failed to generate content plan';
                        console.error(
                            'Context: Throwing immediate error:',
                            errorMsg
                        );
                        throw new Error(
                            typeof errorMsg === 'string'
                                ? errorMsg
                                : 'Failed to generate content plan'
                        );
                    }
                }

                // Check for error responses - API might return error object even with 200 status
                // Check for error field, status_code indicating error (4xx, 5xx), or success === false
                // (resultAny already declared above)
                const hasError =
                    resultAny.error ||
                    (resultAny.status_code && resultAny.status_code >= 400) ||
                    resultAny.success === false;

                if (hasError) {
                    console.error(
                        'Context: Error detected in response:',
                        result
                    );
                    // Extract error message from various possible locations
                    let errorMsg =
                        resultAny.error ||
                        resultAny.message ||
                        (resultAny.validation_errors &&
                        typeof resultAny.validation_errors === 'object'
                            ? (Object.values(
                                  resultAny.validation_errors
                              )[0] as string)
                            : null) ||
                        'Failed to generate content plan';
                    console.error(
                        'Context: Extracted error message:',
                        errorMsg
                    );
                    console.error('Context: Throwing error:', errorMsg);
                    throw new Error(errorMsg);
                }

                // Extract taskId from response (could be at top level or in data)
                const taskId = result.taskId || result.data?.taskId;
                const responseData = result.data || {};

                // Convert the API response data to ContentPlanItem format and return them
                if (responseData.content_plan) {
                    const newItems: ContentPlanItem[] =
                        responseData.content_plan.map(
                            (item: any, index: number) => {
                                // Normalize structure data to match the format expected by manual creation
                                let normalizedStructure =
                                    item['Structure of the post'];

                                // If structure is an array, convert to the expected format
                                if (Array.isArray(normalizedStructure)) {
                                    normalizedStructure =
                                        normalizedStructure.map(
                                            (structureItem: any) => {
                                                // If structureItem is not a string, return it as-is or handle it appropriately
                                                if (
                                                    typeof structureItem !==
                                                    'string'
                                                ) {
                                                    console.warn(
                                                        'Structure item is not a string:',
                                                        structureItem
                                                    );
                                                    // If it's already an object with type and subtitle, return it
                                                    if (
                                                        structureItem &&
                                                        typeof structureItem ===
                                                            'object' &&
                                                        structureItem.type
                                                    ) {
                                                        return structureItem;
                                                    }
                                                    // Otherwise, try to convert it to a string
                                                    structureItem =
                                                        String(structureItem);
                                                }

                                                // Handle different conclusion naming conventions
                                                if (
                                                    structureItem
                                                        .toLowerCase()
                                                        .includes(
                                                            'conclusion'
                                                        ) ||
                                                    structureItem
                                                        .toLowerCase()
                                                        .includes('summary')
                                                ) {
                                                    return 'conclusion_summary';
                                                }
                                                // Handle excerpt/tldr naming
                                                if (
                                                    structureItem
                                                        .toLowerCase()
                                                        .includes('excerpt') ||
                                                    structureItem
                                                        .toLowerCase()
                                                        .includes('tldr')
                                                ) {
                                                    return 'excerpt_tldr';
                                                }
                                                // For other items, check if they match predefined types
                                                const predefinedTypes = [
                                                    'excerpt_tldr',
                                                    'paragraph',
                                                    'key_takeaways',
                                                    'faq',
                                                    'conclusion_summary',
                                                ];
                                                if (
                                                    predefinedTypes.includes(
                                                        structureItem
                                                    )
                                                ) {
                                                    return structureItem;
                                                }
                                                // For custom content, treat as paragraph with subtitle
                                                return {
                                                    type: 'paragraph',
                                                    subtitle: structureItem,
                                                };
                                            }
                                        );
                                }

                                return {
                                    id: -(Date.now() + index + 1), // Negative temporary IDs to avoid conflicts
                                    title: item['post title'],
                                    structure:
                                        JSON.stringify(normalizedStructure),
                                    keywords: item['Keywords.'].join(', '),
                                    status: 'planned' as const,
                                    scheduled_on: item['Scheduled on'],
                                    post: null,
                                    created_at: new Date().toISOString(),
                                    updated_at: new Date().toISOString(),
                                };
                            }
                        );

                    // Return items, dev info, and latest scheduled date from backend
                    return {
                        items: newItems,
                        devInfo: responseData.dev_info || null,
                        latestScheduledDate:
                            responseData.latest_scheduled_date || null,
                        taskId: taskId || undefined,
                    };
                }

                // If no content_plan in response, check if taskId is present (async task)
                if (taskId) {
                    return {
                        items: [],
                        latestScheduledDate: undefined,
                        taskId: taskId,
                    };
                }

                // Defensive check: if we reach here with no content_plan and no taskId,
                // but there are error indicators, throw an error
                // (resultAny already declared above)
                if (
                    resultAny.error ||
                    (resultAny.status_code && resultAny.status_code >= 400)
                ) {
                    console.error(
                        'Context: Error detected after initial check:',
                        resultAny
                    );
                    let errorMsg =
                        resultAny.error ||
                        resultAny.message ||
                        (resultAny.validation_errors &&
                        typeof resultAny.validation_errors === 'object'
                            ? (Object.values(
                                  resultAny.validation_errors
                              )[0] as string)
                            : null) ||
                        'Failed to generate content plan';
                    throw new Error(errorMsg);
                }

                // Refresh usage data after generating content plan
                refreshUsage().catch(err => {
                    console.error(
                        'Error refreshing usage after content plan generation:',
                        err
                    );
                });

                return { items: [], latestScheduledDate: undefined };
            } catch (err: any) {
                console.error('Context: Error generating content plan:', err);
                // Don't set error in context - modal handles error display
                throw err;
            } finally {
                setSaving(false);
            }
        },
        [refreshUsage]
    );

    // Fetch Posts API Function
    const [postsLoading, setPostsLoading] = useState<boolean>(false);
    const [postsError, setPostsError] = useState<string | null>(null);
    const [lastFetchTime, setLastFetchTime] = useState<number>(0);
    const FETCH_COOLDOWN = 1000; // 1 second cooldown between fetches

    const fetchPosts = useCallback(
        async (
            fields: string[] = ['id', 'title'],
            postTypes: string[] = ['post', 'page'],
            args: any = {}
        ): Promise<void> => {
            const now = Date.now();
            if (postsLoading || now - lastFetchTime < FETCH_COOLDOWN) {
                return;
            }

            setPostsLoading(true);
            setPostsError(null);
            setLastFetchTime(now);

            try {
                const queryParams = new URLSearchParams();

                // Add fields parameter
                if (fields.length > 0) {
                    fields.forEach(field =>
                        queryParams.append('fields[]', field)
                    );
                }

                // Add post_types parameter
                if (postTypes.length > 0) {
                    postTypes.forEach(type =>
                        queryParams.append('post_types[]', type)
                    );
                }

                // Add other parameters
                if (args.search) queryParams.append('search', args.search);
                if (args.limit && args.limit > 0)
                    queryParams.append('limit', args.limit.toString());
                if (args.orderby) queryParams.append('orderby', args.orderby);
                if (args.order) queryParams.append('order', args.order);

                const queryString = queryParams.toString();
                const path = `/cpepai/v1/posts${
                    queryString ? `?${queryString}` : ''
                }`;

                const posts: Post[] = await apiFetch({
                    path,
                    method: 'GET',
                });

                setPosts(posts || []);
            } catch (err: any) {
                console.error('Error fetching posts:', err);
                setPostsError(err.message);
            } finally {
                setPostsLoading(false);
            }
        },
        [postsLoading, lastFetchTime]
    );

    // Fetch Tasks API Function
    const fetchTasks = useCallback(
        async (args?: {
            status?: string;
            type?: string;
            limit?: number;
        }): Promise<void> => {
            // Prevent concurrent calls - if a fetch is already in progress, skip this call
            if (fetchTasksInProgressRef.current) {
                return;
            }

            // Wait for preset to load before fetching tasks
            if (!presetLoadedRef.current) {
                // Wait up to 5 seconds for preset to load
                let waitCount = 0;
                while (!presetLoadedRef.current && waitCount < 50) {
                    await new Promise(resolve => setTimeout(resolve, 100));
                    waitCount++;
                }
            }

            // Mark fetch as in progress
            fetchTasksInProgressRef.current = true;

            setTasksLoading(true);
            setTasksError(null);

            try {
                const queryParams = new URLSearchParams();

                if (args?.status) {
                    queryParams.append('status', args.status);
                }
                if (args?.type) {
                    queryParams.append('type', args.type);
                }
                if (args?.limit) {
                    queryParams.append('limit', args.limit.toString());
                }

                const queryString = queryParams.toString();
                const path = `/cpepai/v1/tasks${queryString ? `?${queryString}` : ''}`;

                const result: ApiResponse<Task[]> = await apiFetch({
                    path,
                    method: 'GET',
                });

                if (result.success && result.data) {
                    // Check if any task changed from processing/queued to completed
                    const previousTasks = previousTasksRef.current;
                    const newTasks = result.data;

                    // Find tasks that changed from processing/queued to completed
                    const completedTasks = newTasks.filter(newTask => {
                        if (newTask.status !== 'completed') {
                            return false;
                        }

                        // Find the previous version of this task
                        const previousTask = previousTasks.find(
                            prevTask => prevTask.id === newTask.id
                        );

                        // Check if it was previously processing or queued
                        return (
                            previousTask &&
                            (previousTask.status === 'processing' ||
                                previousTask.status === 'queued')
                        );
                    });

                    // Also find newly completed tasks that consume tokens
                    // (plan generation or post generation tasks that are completed)
                    const tokenConsumingTaskTypes = [
                        'content_plan_generation',
                        'post_generation',
                    ];
                    const tokenConsumingCompletedTasks = newTasks.filter(
                        newTask => {
                            if (newTask.status !== 'completed') {
                                return false;
                            }

                            // Skip if we've already refreshed usage for this task
                            if (
                                refreshedUsageForTasksRef.current.has(
                                    newTask.id
                                )
                            ) {
                                return false;
                            }

                            // Check if this is a token-consuming task type
                            const taskType = (newTask.type || '').toLowerCase();
                            const isTokenConsuming =
                                tokenConsumingTaskTypes.some(type =>
                                    taskType.includes(type)
                                );

                            if (!isTokenConsuming) {
                                return false;
                            }

                            // Check if we've seen this task before
                            const previousTask = previousTasks.find(
                                prevTask => prevTask.id === newTask.id
                            );

                            // Refresh usage if:
                            // 1. We haven't seen this task before (newly completed)
                            // 2. It just transitioned from processing/queued to completed
                            const justCompleted =
                                previousTask &&
                                (previousTask.status === 'processing' ||
                                    previousTask.status === 'queued');
                            const newlySeen = !previousTask;

                            return justCompleted || newlySeen;
                        }
                    );

                    // If any task completed, refetch content plan items and refresh usage
                    if (completedTasks.length > 0) {
                        // Refetch content plan items to get the newly generated items
                        fetchContentPlanItems(1);
                    }

                    // Refresh usage for token-consuming tasks
                    // Add a small delay to ensure database update has completed
                    if (tokenConsumingCompletedTasks.length > 0) {
                        // Mark these tasks as having refreshed usage
                        tokenConsumingCompletedTasks.forEach(task => {
                            refreshedUsageForTasksRef.current.add(task.id);
                        });

                        // Refresh usage with retry mechanism
                        // First attempt after 500ms, retry after 1.5s if needed
                        const refreshUsageWithRetry = async (
                            attempt: number = 1
                        ) => {
                            try {
                                await refreshUsage();
                            } catch (err) {
                                console.error(
                                    `Error refreshing usage after task completion (attempt ${attempt}):`,
                                    err
                                );
                                // Retry once after a longer delay
                                if (attempt === 1) {
                                    setTimeout(() => {
                                        refreshUsageWithRetry(2);
                                    }, 1500);
                                }
                            }
                        };

                        // Wait a bit to ensure database update has completed
                        setTimeout(() => {
                            refreshUsageWithRetry();
                        }, 500); // 500ms delay to ensure DB update completes
                    }

                    // Note: Polling for failed tasks without results is handled by the polling useEffect
                    // No need for immediate retry here as the polling interval (2 seconds) will handle it

                    // Update tasks state
                    setTasks(newTasks);
                    // Store current tasks as previous for next comparison
                    previousTasksRef.current = newTasks;

                    // Update should_poll flag from backend response
                    if (typeof result.should_poll === 'boolean') {
                        setShouldPollTasks(result.should_poll);
                    }

                    // Note: Usage data is refreshed:
                    // - On app load (via fetchPresetData)
                    // - After content plan generation
                    // - After post generation
                    // - When content plan items are created
                    // - When tasks complete (to reflect token consumption)
                } else {
                    throw new Error(result.message || 'Failed to fetch tasks');
                }
            } catch (err: any) {
                console.error('Error fetching tasks:', err);
                setTasksError(err.message);
            } finally {
                setTasksLoading(false);
                // Reset the in-progress flag so subsequent calls can proceed
                fetchTasksInProgressRef.current = false;
            }
        },
        [fetchContentPlanItems, refreshUsage]
    );

    // Mark Task as Watched API Function
    const markTaskAsWatched = useCallback(
        async (taskId: number): Promise<void> => {
            try {
                const result: ApiResponse = await apiFetch({
                    path: `/cpepai/v1/tasks/${taskId}/watch`,
                    method: 'POST',
                });

                if (!result.success) {
                    throw new Error(
                        result.message || 'Failed to mark task as watched'
                    );
                }

                // Update the task in local state
                setTasks(prevTasks =>
                    prevTasks.map(task =>
                        task.id === taskId ? { ...task, watched: true } : task
                    )
                );
            } catch (err: any) {
                console.error('Error marking task as watched:', err);
                setTasksError(err.message);
                throw err;
            }
        },
        []
    );

    // Fetch Logs API Function
    const fetchLogs = useCallback(
        async (args?: {
            level?: string;
            limit?: number;
            offset?: number;
        }): Promise<void> => {
            setLogsLoading(true);
            setLogsError(null);

            try {
                const queryParams = new URLSearchParams();

                if (args?.level) {
                    queryParams.append('level', args.level);
                }
                if (args?.limit) {
                    queryParams.append('limit', args.limit.toString());
                }
                if (args?.offset) {
                    queryParams.append('offset', args.offset.toString());
                }

                const queryString = queryParams.toString();
                const path = `/cpepai/v1/logs${queryString ? `?${queryString}` : ''}`;

                const result: ApiResponse<Log[]> = await apiFetch({
                    path,
                    method: 'GET',
                });

                if (result.success && result.data) {
                    setLogs(result.data);
                } else {
                    throw new Error(result.message || 'Failed to fetch logs');
                }
            } catch (err: any) {
                console.error('Error fetching logs:', err);
                setLogsError(err.message);
            } finally {
                setLogsLoading(false);
            }
        },
        []
    );

    // Add Failed Task Function - Creates a failed task locally and adds it to the top of the tasks list
    const addFailedTask = useCallback(
        (type: string, errorMessage: string, itemId?: number): void => {
            const now = new Date().toISOString();
            const taskId = `failed-${Date.now()}`;

            // Generate a unique negative ID for local failed tasks
            const localId = -Date.now();

            const failedTask: Task = {
                id: localId,
                task_id: taskId,
                type: type,
                status: 'failed',
                result: {
                    error: errorMessage,
                },
                created_at: now,
                updated_at: now,
            };

            // Add the failed task at the top of the tasks array
            setTasks(prevTasks => [failedTask, ...prevTasks]);

            // Set the current task ID to highlight the failed task
            setCurrentTaskId(taskId);
        },
        []
    );

    // Polling for tasks with processing status or failed tasks without result
    // Use shouldPollTasks flag from backend, but also check local tasks as fallback
    useEffect(() => {
        // Check if we should poll based on backend flag or local tasks
        const hasProcessingTasks =
            shouldPollTasks ||
            tasks.some(
                task => task.status === 'processing' || task.status === 'queued'
            );

        // Also poll for failed tasks that don't have a result yet (webhook might still be updating)
        // A task needs polling if: it's failed AND (no result OR result is empty object OR result has no error field)
        const failedTasksWithoutResult = tasks.filter(task => {
            if (task.status !== 'failed') return false;
            // No result at all
            if (!task.result) return true;
            // Result is empty object
            if (
                typeof task.result === 'object' &&
                Object.keys(task.result).length === 0
            )
                return true;
            // Result exists but doesn't have error field (webhook might still be updating)
            if (typeof task.result === 'object' && !task.result.error)
                return true;
            return false;
        });

        // Track when we first see failed tasks without results
        const now = Date.now();
        failedTasksWithoutResult.forEach(task => {
            if (!failedTasksStartTimeRef.current.has(task.task_id)) {
                failedTasksStartTimeRef.current.set(task.task_id, now);
            }
        });

        // Remove tracking for tasks that now have results with error messages
        tasks.forEach(task => {
            if (
                task.status === 'failed' &&
                task.result &&
                typeof task.result === 'object' &&
                task.result.error &&
                failedTasksStartTimeRef.current.has(task.task_id)
            ) {
                failedTasksStartTimeRef.current.delete(task.task_id);
            }
        });

        // Stop polling for failed tasks after 30 seconds (webhook should arrive much sooner)
        const hasFailedTasksWithoutResult = failedTasksWithoutResult.some(
            task => {
                const startTime = failedTasksStartTimeRef.current.get(
                    task.task_id
                );
                return startTime && now - startTime < 30000; // 30 seconds timeout
            }
        );

        // Clear any existing interval first to prevent multiple intervals
        if (pollingIntervalRef.current) {
            clearInterval(pollingIntervalRef.current);
            pollingIntervalRef.current = null;
        }

        if (!hasProcessingTasks && !hasFailedTasksWithoutResult) {
            return;
        }

        // Set up polling interval - poll every 2 seconds for faster updates
        pollingIntervalRef.current = setInterval(() => {
            fetchTasks({ limit: 20 });
        }, 2000);

        return () => {
            if (pollingIntervalRef.current) {
                clearInterval(pollingIntervalRef.current);
                pollingIntervalRef.current = null;
            }
        };
    }, [tasks, shouldPollTasks, fetchTasks]);

    // Load data on mount only (not on every render or when functions change)
    // Preset must be loaded first, then tasks and content plan items
    // eslint-disable-next-line react-hooks/exhaustive-deps
    useEffect(() => {
        const loadInitialData = async () => {
            // First, fetch preset data and wait for it to complete
            await fetchPresetData();

            // Then fetch tasks and content plan items (can be parallel)
            // Note: fetchTasks will check presetLoadedRef internally, but we've already set it above
            fetchContentPlanItems(1); // Load content plan items on mount
            await fetchTasks({ limit: 20 }); // Load tasks on mount (await to ensure it completes)

            // Set mock data for now
            setData({
                message: 'Content Plan Plugin Active',
                version: '1.0.0',
            });

            setUser({
                name: 'Admin User',
                role: 'Administrator',
            });
        };

        loadInitialData();
    }, []); // Empty array ensures this only runs on mount

    const value: ContentPlanContextState = {
        // State
        loading,
        saving,
        error,
        contentPlan,
        posts,
        postsLoading,
        postsError,
        tasks,
        tasksLoading,
        tasksError,
        logs,
        logsLoading,
        logsError,
        settings,
        fieldConfigs,
        strings,
        data,
        user,
        usage,
        usageError,
        backendConfigured,
        sidebarOpen,
        sidebarType,
        generationModalOpen,
        currentTaskId,
        taskFilterId,
        isNewWebsite,
        opted,
        optinUrl,
        timezone,

        // Actions
        updateContentPlan,
        addContentPlanItem,
        updateContentPlanItem: updateContentPlanItemLocal,
        deleteContentPlanItem: deleteContentPlanItemLocal,
        updateSettings,
        updateFieldConfigs,
        updateStrings,
        clearError,
        setLoadingState,
        setSavingState,
        setErrorState,
        getString,
        getLatestContentPlanDate, // Helper function to get latest date
        saveSettings,
        refreshData,
        refreshUsage,
        openSidebar,
        closeSidebar,
        openGenerationModal,
        closeGenerationModal,
        setCurrentTaskId: setCurrentTaskIdState,
        setTaskFilter,
        clearTaskFilter,

        // API Functions
        fetchContentPlanItems,
        createContentPlanItem,
        updateContentPlanItemAPI,
        deleteContentPlanItemAPI,
        bulkDeleteContentPlanItems,
        createMultipleContentPlanItems,
        createPost,
        fetchPosts,
        generateContentPlan,
        fetchTasks,
        markTaskAsWatched,
        addFailedTask,
        fetchLogs,
        recalculateSchedule,
    };

    return (
        <ContentPlanContext.Provider value={value}>
            {children}
        </ContentPlanContext.Provider>
    );
};

export default ContentPlanContext;
