import { useBool } from "@common/hooks/useBool";
import { useQuery } from "@common/hooks/useAsyncState";
import { useInputState } from "@common/hooks/useInputState";
import { useUpdate } from "@common/hooks/useUpdate";
import { fuzzyMatch } from "@common/utils";
import clsx from "clsx";
import { useEffect, useMemo, useRef } from "preact/hooks";
import styles from "../../assets/settings-modal.module.css";
import { useAppState } from "../../contexts/state";
import LLM from "../../utils/llm";

export const ConnectionSettings = () => {
    const { connection, model, imageModel, dispatch } = useAppState();
    const [url, setUrl] = useInputState(connection?.url ?? "");
    const [apiKey, setApiKey] = useInputState(connection?.apiKey ?? "");
    const [selectedModel, setSelectedModel] = useInputState(model?.id ?? "");
    const [selectedImageModel, setSelectedImageModel] = useInputState(imageModel?.id ?? "");
    const [update, triggerFetch] = useUpdate();
    const showPassword = useBool(false);

    const urlRef = useRef(url);
    const apiKeyRef = useRef(apiKey);
    urlRef.current = url;
    apiKeyRef.current = apiKey;

    const connectionToFetch = useMemo<LLM.Connection | null>(() => {
        const currentUrl = urlRef.current;
        const currentApiKey = apiKeyRef.current;
        if (!currentUrl || !currentApiKey) return null;
        return { url: currentUrl, apiKey: currentApiKey };
    }, [update]);

    const fetchModels = useMemo(() => async (conn: LLM.Connection | null) => {
        if (!conn) return [];
        const r = await LLM.getModels(conn);
        return r.data;
    }, []);

    const modelsData = useQuery(fetchModels, connectionToFetch);
    const textModelsData = useMemo(() => modelsData?.filter(LLM.isTextModel), [modelsData]);
    const imageModelsData = useMemo(() => modelsData?.filter(LLM.isImageModel), [modelsData]);
    const isLoadingModels = connectionToFetch != null && textModelsData == undefined;

    const [modelFilter, setModelFilter] = useInputState("");

    const groupedModels = useMemo(() => {
        const sorted = (textModelsData ?? []).sort((a, b) => {
            const aWeight = Number(a.supported_parameters.includes('tools')) * 2 + Number(a.supported_parameters.includes('reasoning'));
            const bWeight = Number(b.supported_parameters.includes('tools')) * 2 + Number(b.supported_parameters.includes('reasoning'));
            if (aWeight !== bWeight) return bWeight - aWeight;
            if (a.context_length !== b.context_length) return b.context_length - a.context_length;
            return a.id.localeCompare(b.id);
        });
        const groups = Map.groupBy(sorted, m => m.context_length);
        return Array.from(groups.entries())
            .sort((a, b) => b[0] - a[0])
            .map(([context, models]) => ({ context, models }));
    }, [textModelsData]);

    const filteredGroupedModels = useMemo(() => {
        if (!modelFilter) return groupedModels;
        return groupedModels
            .map(({ context, models }) => ({
                context,
                models: models.filter(m => m.id === selectedModel || fuzzyMatch(m.id, modelFilter)),
            }))
            .filter(({ models }) => models.length > 0);
    }, [groupedModels, modelFilter, selectedModel]);

    const filteredImageModels = useMemo(() => {
        const sorted = [...(imageModelsData ?? [])].sort((a, b) => a.id.localeCompare(b.id));
        if (!modelFilter) return sorted;
        return sorted.filter(m => m.id === selectedImageModel || fuzzyMatch(m.id, modelFilter));
    }, [imageModelsData, modelFilter, selectedImageModel]);

    const handleBlur = () => {
        if (url && apiKey) {
            dispatch({ type: "SET_CONNECTION", connection: { url, apiKey } });
            triggerFetch();
        }
    };

    const handleKeyDown = (e: KeyboardEvent) => {
        if (e.key === "Enter" && url && apiKey) {
            dispatch({ type: "SET_CONNECTION", connection: { url, apiKey } });
            triggerFetch();
        }
    };

    const handleModelChange = (e: Event) => {
        setSelectedModel(e);
        const target = e.target as HTMLSelectElement;
        const selectedModelInfo = textModelsData?.find(m => m.id === target.value) ?? null;
        dispatch({ type: "SET_MODEL", model: selectedModelInfo });
    };

    const handleImageModelChange = (e: Event) => {
        setSelectedImageModel(e);
        const target = e.target as HTMLSelectElement;
        const selectedModelInfo = imageModelsData?.find(m => m.id === target.value) ?? null;
        dispatch({ type: "SET_IMAGE_MODEL", model: selectedModelInfo });
    };

    const connectionToTest = url && apiKey ? { url, apiKey } : null;

    return (
        <div class={styles.form} autocomplete="off">
            <div class={styles.formGroup}>
                <label class={styles.label}>API URL</label>
                <input
                    type="text"
                    value={url}
                    onInput={setUrl}
                    onBlur={handleBlur}
                    onKeyDown={handleKeyDown}
                    placeholder="http://localhost:1234"
                    class={styles.input}
                    autocomplete="off"
                    name="api-url-random"
                />
            </div>
            <div class={styles.formGroup}>
                <label class={styles.label}>API Key</label>
                <div class={styles.inputRow}>
                    <input
                        type={showPassword.value ? "text" : "password"}
                        value={apiKey}
                        onInput={setApiKey}
                        onBlur={handleBlur}
                        onKeyDown={handleKeyDown}
                        placeholder="your-api-key"
                        class={styles.input}
                        autocomplete="new-password"
                        name="api-key-random"
                    />
                    <button
                        type="button"
                        onClick={showPassword.toggle}
                        class={styles.iconButton}
                        title={showPassword.value ? "Hide API key" : "Show API key"}
                    >
                        {showPassword.value ? "🙈" : "👁️"}
                    </button>
                </div>
            </div>
            <div class={clsx(styles.formGroup, styles.formGroupFill)}>
                <label class={styles.label}>Model</label>
                {connectionToTest ? (
                    isLoadingModels ? (
                        <p>Loading models...</p>
                    ) : groupedModels.length > 0 ? (
                        <>
                            <input
                                type="text"
                                value={modelFilter}
                                onInput={setModelFilter}
                                placeholder="Filter models..."
                                class={styles.input}
                            />
                            <select
                                value={selectedModel}
                                onChange={handleModelChange}
                                class={clsx(styles.select, styles.selectMultiline)}
                                size={3}
                            >
                                {filteredGroupedModels.map(({ context, models }) => (
                                    <optgroup key={context} label={`${context} context`}>
                                        {models.map(m => (
                                            <option key={m.id} value={m.id}>
                                                {m.supported_parameters.includes('tools') ? '🔨' : ''}{m.supported_parameters.includes('reasoning') ? '🧠' : ''}{m.id} {m.top_provider.max_completion_tokens ? `(len: ${m.top_provider.max_completion_tokens})` : ''}
                                            </option>
                                        ))}
                                    </optgroup>
                                ))}
                            </select>
                        </>
                    ) : (
                        <p>No models available</p>
                    )
                ) : (
                    <p>Enter connection details to load models</p>
                )}
            </div>
            <div class={clsx(styles.formGroup, styles.formGroupFill)}>
                <label class={styles.label}>Image Model</label>
                {connectionToTest ? (
                    imageModelsData == undefined ? (
                        <p>Loading models...</p>
                    ) : imageModelsData.length > 0 ? (
                        <select
                            value={selectedImageModel}
                            onChange={handleImageModelChange}
                            class={clsx(styles.select, styles.selectMultiline)}
                            size={3}
                        >
                            <option value="">— none —</option>
                            {filteredImageModels.map(m => (
                                <option key={m.id} value={m.id}>{m.id}</option>
                            ))}
                        </select>
                    ) : (
                        <p>No image models available</p>
                    )
                ) : (
                    <p>Enter connection details to load models</p>
                )}
            </div>
        </div>
    );
};
