import { useCallback, useEffect, useMemo, useState } from 'preact/hooks';
import { useInputState } from '@common/hooks/useInputState';
import { useInputCallback } from '@common/hooks/useInputCallback';
import { useAsyncEffect } from '@common/hooks/useAsyncEffect';
import { usePrevious } from '@common/hooks/usePrevious';

import { Connection, HORDE_ANON_KEY, type IConnection, type IHordeModel } from '../../tools/connection';
import { Huggingface } from '../../tools/huggingface';
import { INSTRUCTS } from '../../contexts/state';

import styles from './header.module.css';

interface IProps {
    connection: IConnection;
    setConnection: (c: IConnection) => void;
}

export const ConnectionEditor = ({ connection, setConnection }: IProps) => {
    // kobold
    const [connectionUrl, setConnectionUrl] = useInputState('');
    // horde
    const [apiKey, setApiKey] = useInputState(HORDE_ANON_KEY);
    const [modelName, setModelName] = useInputState('');
    const prevModelName = usePrevious(modelName);

    const [instruct, setInstruct] = useInputState('');
    const [modelTemplate, setModelTemplate] = useInputState('');
    const [hordeModels, setHordeModels] = useState<IHordeModel[]>([]);
    const [contextLength, setContextLength] = useState<number>(0);

    const isOnline = useMemo(() => contextLength > 0, [contextLength]);

    useEffect(() => {
        setInstruct(connection.instruct);
        connection.url && setConnectionUrl(connection.url);
        connection.model && setModelName(connection.model);
        setApiKey(connection.apiKey || HORDE_ANON_KEY);

        if (connection.type === 'kobold') {
            Connection.getContextLength(connection).then(setContextLength);
        } else if (connection.type === 'horde') {
            Connection.getHordeModels()
                .then(m => setHordeModels(Array.from(
                    m.values())
                    .sort((a, b) =>
                        b.maxContext - a.maxContext || a.name.localeCompare(b.name)
                    )
                ));
        }
    }, [connection]);

    useAsyncEffect(async () => {
        if (!modelName) return;

        const template = await Huggingface.findModelTemplate(modelName);
        if (!template) return;

        setModelTemplate(template);
        if (prevModelName) {
            setInstruct(template);
        }
    }, [modelName]);

    const setBackendType = useInputCallback((type) => {
        switch (type) {
            case 'kobold':
            case 'horde':
                setConnection({
                    type,
                    instruct,
                    url: connectionUrl,
                    apiKey,
                    model: modelName,
                });
                break;
        }
    }, [setConnection, connectionUrl, apiKey, modelName, instruct]);

    const handleSetInstruct = useInputCallback((instruct: string) => {
        setConnection({ ...connection, instruct });
    }, [setConnection, connection]);

    const handleBlurUrl = useCallback(() => {
        const regex = /^(?:http(s?):\/\/)?(.*?)\/?$/i;
        const url = connectionUrl.replace(regex, 'http$1://$2');

        setConnection({
            type: 'kobold',
            instruct,
            url,
            apiKey,
            model: modelName,
        });
    }, [connectionUrl, instruct, setConnection]);

    const handleBlurHorde = useCallback(() => {
        setConnection({
            type: 'horde',
            instruct,
            url: connectionUrl,
            apiKey,
            model: modelName,
        });
    }, [apiKey, modelName, instruct, setConnection]);

    return (
        <div class={styles.connectionEditor}>
            <select value={connection.type} onChange={setBackendType}>
                <option value='kobold'>Kobold CPP</option>
                <option value='horde'>Horde</option>
            </select>
            <select value={instruct} onChange={handleSetInstruct} title='Instruct template'>
                {modelName && modelTemplate && <optgroup label='Native model template'>
                    <option value={modelTemplate} title='Native for model'>{modelName}</option>
                </optgroup>}
                <optgroup label='Manual templates'>
                    {Object.entries(INSTRUCTS).map(([label, value]) => (
                        <option value={value} key={value}>
                            {label}
                        </option>
                    ))}
                </optgroup>
                {instruct !== modelTemplate
                    && !Object.values(INSTRUCTS).includes(instruct)
                    && <optgroup label='Custom'>
                        <option value={connection.instruct}>Custom</option>
                    </optgroup>
                }
            </select>
            {connection.type === 'kobold' && <input
                value={connectionUrl}
                onInput={setConnectionUrl}
                onBlur={handleBlurUrl}
                class={isOnline ? styles.valid : styles.invalid}
            />}
            {connection.type === 'horde' && <>
                <input
                    placeholder='Horde API key'
                    title='Horde API key'
                    value={apiKey}
                    onInput={setApiKey}
                    onBlur={handleBlurHorde}
                />

                <select
                    value={modelName}
                    onChange={setModelName}
                    onBlur={handleBlurHorde}
                    title='Horde model'
                >
                    {hordeModels.map((m) => (
                        <option value={m.name} key={m.name}>
                            {m.name} ({m.maxLength}/{m.maxContext})
                        </option>
                    ))}
                </select>
            </>}
        </div>
    );
};
