/**
 * VideoSearchComponent
 * 
 * This component provides a user interface for searching and embedding Dailymotion videos
 * in both Gutenberg and Classic WordPress editors. It includes search functionality,
 * video browsing, and player configuration options.
 *
 * @module VideoSearchComponent
 * @since 2.0.0
 */

// WordPress packages
// Note: @wordpress/edit-post is not available in WP 6.4, so we conditionally import it
import { Fragment, useEffect, useState, useCallback } from "@wordpress/element"
import { __ } from "@wordpress/i18n"
import React from "react"
import { fetchApi } from "../../Libs/ApiCall"

// Components
import Header from "./HeaderComponent"
import SearchForm from "./SearchFormComponent"
import Tabs from "./TabsComponent"
import PerPostPlayer from "../PerPostPlayerComponent/PerPostPlayerComponent"
import Feedback from "../FeedbackComponent/FeedbackComponent"

// Interfaces
import VideoCardInterface from "Interfaces/VideoCardInterface"
import RequestParamsInterface from "Interfaces/RequestParamsInterface"
import ChannelInterface from "Interfaces/ChannelInterface"
import PlayerIdInterface from "Interfaces/PlayerIdInterface"
import PlayerIdsResponse from "Interfaces/PlayerIdsResponse"

/**
 * Video Search Component
 * 
 * Main component for searching and embedding Dailymotion videos in WordPress editors.
 * This component adapts its UI based on whether it's being used in the Gutenberg editor
 * (where it appears as a sidebar) or the Classic editor (where it appears inline).
 *
 * @since 2.0.0
 * @return {JSX.Element} The rendered component
 */
const VideoSearchComponent = () => {
    const [loading, setLoading] = useState(false)
    const [activeTab, setActiveTab] = useState('video')
    const [params, setParams] = useState({
        page: 1,
        search: '',
        sort: 'recent',
        owners: '',
        global: false
    })
    const [videos, setVideos] = useState<VideoCardInterface[]>([])
    const [hasMore, setHasMore] = useState(false)
    const [isGutenberg, setIsGutenberg] = useState(false)
    const searchVideoUrl = '/dm-pro/v2/search-video'
    const [channels, setChannels] = useState<ChannelInterface[]>([])
    const [playerId, setPlayerId] = useState<PlayerIdInterface[]>([])
    const [showFeedback, setShowFeedback] = useState(false);
    const [feedbackType, setFeedbackType] = useState('');
    const [feedbackMessage, setFeedbackMessage] = useState('');
    // Conditionally load @wordpress/edit-post components (not available in WP 6.4)
    const [editPostComponents, setEditPostComponents] = useState<{PluginSidebar?: any, PluginSidebarMoreMenuItem?: any} | null>(null)


    /**
     * Dailymotion Icon SVG
     * 
     * Renders the Dailymotion logo as an SVG icon for use in the Gutenberg sidebar.
     *
     * @since 2.0.0
     * @return {JSX.Element} The SVG icon element
     */
    const dailymotionIcon = () => {
        return <svg
            id="Dailymotion"
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 96 96"
        >
            <style>{".st0{fill:currentColor;}.st1{fill:none;}"}</style>
            <g>
                <path
                    id="Medium_00000036224591124208101180000008332054064288928391_"
                    className="st0"
                    d="M44,10H16c-1.1,0-2,0.9-2,2v12
		            c0,0.5,0.2,1,0.6,1.4l12,12C27,37.8,27.5,38,28,38h16c5.5,0,10,4.5,10,10s-4.5,10-10,10H22c-1.1,0-2,0.9-2,2v12
		            c0,0.5,0.2,1,0.6,1.4l12,12C33,85.8,33.5,86,34,86h10c21,0,38-17,38-38S65,10,44,10z M18,16.8l8,8v6.3l-8-8V16.8z M58,48
		            c0-7.7-6.3-14-14-14H30v-8h14c12.1,0,22,9.9,22,22s-9.9,22-22,22h-9.2l-8-8H44C51.7,62,58,55.7,58,48z M24,64.8l8,8v6.3l-8-8V64.8z
		            M44,82h-8v-8h8c14.3,0,26-11.7,26-26S58.3,22,44,22H28.8l-8-8H44c18.7,0,34,15.3,34,34S62.7,82,44,82z"
                />
                <rect x="0" className="st1" width="96" height="96" />
            </g>
        </svg>
    }

    /**
     * Fetch video or playlist data from Dailymotion API
     *
     * This method fetches videos or playlists from the Dailymotion API based on the current
     * search parameters and active tab. It constructs the appropriate query based on whether
     * we're searching for videos or playlists, and whether we're searching globally or within
     * specific channels.
     * 
     * The results are merged into the existing collection of videos when paginating.
     *
     * @since 2.0.0
     * @return {Promise<void>} A promise that resolves when the data has been fetched and state updated
     */
    const fetchData = async (): Promise<void> => {
        try {
            const query: RequestParamsInterface = {
                data: {
                    fields: 'id,title,thumbnail_480_url,description,duration,created_time,tags,status,private,private_id,owner.screenname,owner.avatar_60_url',
                    limit: 10,
                    flags: 'no_live,exportable,verified',
                    page: params.page,
                    sort: params.sort,
                },
                url: '/videos',
                global: params.global,
                owners: params.owners,
            }

            // will overwrite default value if user start searching playlist
            if (activeTab !== 'video') {
                query.data.fields = 'id,name,thumbnail_480_url,description,created_time,private,owner.screenname,owner.avatar_60_url,videos_total'
                delete query.data.flags;
                query.url = '/playlists'
            }

            if (params.search !== "") {
                query.data.search = params.search
            }

            const getVideos: any = await fetchApi(searchVideoUrl, 'POST', query)

            if (!getVideos || !Array.isArray(getVideos.list)) {
                setHasMore(false)
                setVideos([])
                feedbackController('feedback-error', __('Failed to load results. Please try again.', 'textdomain'))
                return
            }

            setHasMore(Boolean(getVideos.has_more))
            setVideos(prevVideos => [...prevVideos, ...getVideos.list])
        } catch (error) {
            setHasMore(false)
            setVideos([])
            feedbackController('feedback-error', __('Failed to load results. Please try again.', 'textdomain'))
            console.error('Error fetching search results:', error)
        }
    }

    /**
     * Fetch available channels from the API
     * 
     * This method fetches the list of available Dailymotion channels from the plugin's API.
     * These channels are used in the search form to allow filtering videos by channel.
     * If the API call fails, an empty array is set as the channels state.
     *
     * @since 2.0.0
     * @return {Promise<void>} A promise that resolves when the channels have been fetched
     */
    const fetchChannel = async () => {
        try {
            const getChannels = await fetchApi('/dm-pro/v2/get-channels', 'GET') as ChannelInterface[] | null

            if (Array.isArray(getChannels)) {
                setChannels(getChannels);
            } else {
                setChannels([])
            }
        } catch (error) {
            setChannels([])
            console.error('Error fetching channels:', error)
        }
    }

    /**
     * Fetch player IDs from the API
     * 
     * This method fetches the available player IDs from the plugin's API.
     * These player IDs are used to configure how videos are displayed in the frontend.
     * If the API call fails, an empty array is set as the playerId state.
     *
     * @since 2.0.0
     * @return {Promise<void>} A promise that resolves when the player IDs have been fetched
     */
    const fetchPlayerId = async () => {
        try {
            const response = await fetchApi('/dm-pro/v2/get-player-ids', 'GET') as PlayerIdsResponse

            if (response && Array.isArray(response.ids)) {
                setPlayerId(response.ids);
            } else {
                setPlayerId([])
            }
        } catch (error) {
            setPlayerId([])
            console.error('Error fetching player IDs:', error)
        }
    }

    /**
     * Handle search form submission
     * 
     * This method is called when the search form is submitted. It resets the current
     * video list and search parameters, then updates the params state with the new
     * search criteria, which triggers a new API call via the useEffect hook.
     *
     * @since 2.0.0
     * @param {any} formData The form data containing search parameters
     * @return {void}
     */
    const handleSearchSubmit = (formData: any) => {
        setHasMore(false)
        setVideos([])
        setParams({
            page: 1,
            search: formData.search,
            sort: formData.sort,
            owners: formData.channel,
            global: formData.global,
        })
    }

    /**
     * Handle "Show more" button click
     * 
     * This method is called when the user clicks the "Show more" button to load
     * additional videos. It increments the page parameter in the params state,
     * which triggers a new API call via the useEffect hook to fetch the next page
     * of results.
     *
     * @since 2.0.0
     * @return {void}
     */
    const handleShowMore = useCallback(() => {
        setParams((prevState) => ({
            ...prevState,
            page: prevState.page + 1,
        }))
    }, [])

    /**
     * Check if the current editor is Gutenberg
     * 
     * This method determines whether the component is being used in the Gutenberg editor
     * by checking for the 'block-editor-page' class on the document body. This affects
     * how the component is rendered (as a sidebar in Gutenberg, or inline in Classic editor).
     *
     * @since 2.0.0
     * @return {boolean} True if the current editor is Gutenberg, false otherwise
     */
    const checkEditorMode = (): boolean => {
        if (document.body.classList.contains('block-editor-page')) {
            return true
        }

        return false
    }

    /**
     * Hide the feedback message
     * 
     * This method hides the currently displayed feedback message by setting
     * the showFeedback state to false. It's called automatically after the
     * timeout in feedbackController, and can also be called manually.
     *
     * @since 2.0.0
     * @return {void}
     */
    const hideFeedback = useCallback(() => {
        setShowFeedback(false)
    }, [])

    /**
     * Display feedback messages to the user
     * 
     * This method shows feedback messages (success, error, etc.) to the user
     * by updating the feedback state variables. The feedback is automatically
     * hidden after 3 seconds.
     *
     * @since 2.0.0
     * @param {string} type The type of feedback (success, error, warning, etc.)
     * @param {string} message The message to display to the user
     * @return {void}
     */
    const feedbackController = useCallback((type: string, message: string) => {
        setFeedbackType(type)
        setFeedbackMessage(message)
        setShowFeedback(true)

        setTimeout(() => {
            hideFeedback()
        }, 3000)
    }, [hideFeedback])

    useEffect(() => {
        void fetchData()
    }, [params]);

    useEffect(() => {
        // Detect if Gutenberg is active
        setIsGutenberg(checkEditorMode())
        void fetchChannel()
        void fetchPlayerId()
    }, [])

    useEffect(() => {
        if (!isGutenberg) {
            // @ts-ignore
            return
        }

        let canceled = false

        import('@wordpress/edit-post')
            .then((module) => {
                if (canceled) {
                    return
                }
                setEditPostComponents({
                    PluginSidebar: module.PluginSidebar,
                    PluginSidebarMoreMenuItem: module.PluginSidebarMoreMenuItem,
                })
            })
            .catch(() => {
                if (!canceled) {
                    setEditPostComponents(null)
                }
            })

        return () => {
            canceled = true
        }
    }, [isGutenberg])

    /**
     * Handle tab change between video and playlist
     * 
     * This method is called when the user switches between the video and playlist tabs.
     * It updates the activeTab state, resets the videos list and search parameters,
     * and triggers a new search with default parameters for the selected tab.
     *
     * @since 2.0.0
     * @param {string} tabId The ID of the selected tab ('video' or 'playlist')
     * @return {void}
     */
    const handleTabChange = (tabId: string) => {
        setActiveTab(tabId);
        setVideos([]);
        setHasMore(false);
        setParams({
            page: 1,
            search: '',
            sort: 'recent',
            global: false,
            owners: ""
        });
    }

    if (isGutenberg && editPostComponents?.PluginSidebar && editPostComponents?.PluginSidebarMoreMenuItem) {
        const { PluginSidebar, PluginSidebarMoreMenuItem } = editPostComponents;
        return (
            <Fragment>
                <PluginSidebarMoreMenuItem
                    target="dm-pro-video-search"
                    icon={dailymotionIcon()}
                >
                    {__('Dailymotion', 'textdomain')}
                </PluginSidebarMoreMenuItem>
                <PluginSidebar
                    name="dm-pro-video-search"
                    title={__('Video Search', 'textdomain')}
                    icon={dailymotionIcon()}
                    className="dm-pro-video-search  dm-pro-tokens"
                >

                    <Header editorMode="gutenberg" channels={channels}></Header>
                    <SearchForm onSearching={handleSearchSubmit} channels={channels} feedbackController={feedbackController}></SearchForm>

                    <div id="searchResultWrapper" className="search-result-wrapper ps ps--active-y">
                        <Tabs
                            activeTab={activeTab}
                            onTabChange={handleTabChange}
                            videoResult={videos}
                            feedbackController={feedbackController}
                            hasMore={hasMore}
                            onShowMore={handleShowMore}
                        />
                    </div>

                    {playerId.length > 0 && (
                        <PerPostPlayer playerId={playerId}></PerPostPlayer>
                    )}

                    <Feedback
                        type={feedbackType}
                        message={feedbackMessage}
                        isShowing={showFeedback}
                        onClose={hideFeedback}
                        allowHtml={true}
                    />
                </PluginSidebar>
            </Fragment>
        )
    } else {
        return (
            <div className="dm-pro-video-search  dm-pro-tokens">
                <Header editorMode="classic" channels={channels}></Header>
                <SearchForm onSearching={handleSearchSubmit} channels={channels} feedbackController={feedbackController}></SearchForm>

                <div id="searchResultWrapper" className="search-result-wrapper ps ps--active-y">
                    <Tabs
                        activeTab={activeTab}
                        onTabChange={handleTabChange}
                        videoResult={videos}
                        feedbackController={feedbackController}
                        hasMore={hasMore}
                        onShowMore={handleShowMore}
                    />
                </div>
                {playerId.length > 0 && (
                    <PerPostPlayer playerId={playerId}></PerPostPlayer>
                )}

                <Feedback
                    type={feedbackType}
                    message={feedbackMessage}
                    isShowing={showFeedback}
                    onClose={hideFeedback}
                    allowHtml={true}
                />
            </div>
        )
    }

}

export default VideoSearchComponent
