import {
    ChangeEventHandler, Dispatch,
    KeyboardEventHandler,
    MouseEventHandler,
    ReactNode, SetStateAction,
    useCallback,
    useEffect,
    useRef,
    useState
} from "react";
import {FixedSizeList as List} from "react-window";
import Select, {createFilter, MenuListProps} from "react-select";
import * as R from 'ramda';

import {useQuery} from "@tanstack/react-query";

// TODO: Fix this for wrapping items, esp on phones
const height = 35;

function MenuList(props: MenuListProps) {
    const {
        options,
        children,
        maxHeight,
        getValue
    } = props as Omit<MenuListProps, 'children'> & {
        children: ReactNode[]
    };

    const [value] = getValue();
    const initialOffset = options.indexOf(value) * height;

    return (<List
        width={'100%'}
        height={maxHeight}
        itemCount={children.length}
        itemSize={height}
        initialScrollOffset={initialOffset}
    >
        {({
              index,
              style
          }) => <div style={style}>{children[index]}</div>}
    </List>);
}

interface Option {
    label: string,
    value: string,
    data: {
        backlinks: unknown[]
        aliases?: string[]
    }
}

export function LargeSelect() {
    const {
        data: options,
        isLoading,
    } = useQuery({
        queryKey: ['obsidian-metadata'],
        initialData: [],
        queryFn: async () => {
            const response = await fetch("http://100.115.154.44:9002/metadata")
            const fullData: any[] = await response.json();

            return R.sortBy(v => -(v.data.backlinks?.length ?? 0), fullData.map(md => ({
                value: md.relativePath,
                label: md.fileName,
                data: md,
            }) as Option));
        },
    });

    const [selected, setSelected] = useState<Option | null>(null);

    const onChange = useCallback((value: Option) => {
        setSelected(value);
        navigator.clipboard.writeText(`[[${value.label}]]`)
    }, []);

    return (
        <div className={"m-5 text-lg"}>
            <Select
                onChange={onChange as any}
                components={{MenuList}}
                isLoading={isLoading}
                options={options}
                filterOption={createFilter({ignoreAccents: false})}
            />

            {selected && <div className={"mt-2"}>
                <div className="py-1">
                    <Alias original={selected.label}/>
                </div>

                {selected.data.aliases?.map(alias => (
                    <div className="py-1">
                        <Alias original={selected.label} alias={alias}/>
                    </div>
                ))}

                <div className="py-1">
                    <CustomAlias selected={selected}/>
                </div>
            </div>}
        </div>
    )
}

function CustomAlias({selected}: {
    selected: Option
}) {
    const [alias, setAlias] = useState('');

    // Reset when selection changes
    useEffect(() => {
        setAlias('');
    }, [selected.value]);

    const onClick: MouseEventHandler = useCallback((e) => {
        if ((e.target as HTMLElement).tagName == 'SPAN' && alias.length > 0) {
            navigator.clipboard.writeText(`[[${selected!.label}|${alias}]]`);
        }
    }, [selected.value, selected.label, alias]);

    return (
        <span onClick={onClick} className={"rounded-md p-1 hover:bg-slate-100"}>
            <span className={"text-slate-300 p-0.5"}>[[</span>
            <span>{selected.label}</span>
            <span className={"text-slate-300 p-0.5"}>|</span>
            <span>
                <CustomAliasField content={alias} setContent={setAlias} selected={selected}/>
            </span>
            <span className={"text-slate-300 p-0.5"}>]]</span>
        </span>
    )
}

function CustomAliasField({
                              selected,
                              content,
                              setContent
                          }: {
    selected: Option,
    content: string,
    setContent: Dispatch<SetStateAction<string>>,
}) {
    const [width, setWidth] = useState<any>(0);
    const span = useRef<HTMLSpanElement>(null);

    // Resize on change of content
    useEffect(() => {
        setWidth(span.current!.offsetWidth);
        // setWidth(content.length + 'ch');
    }, [content]);

    const changeHandler: ChangeEventHandler<HTMLInputElement> = useCallback(evt => {
        setContent(evt.target.value);
    }, []);

    const onCustomElementKeyDown: KeyboardEventHandler<HTMLInputElement> = useCallback((e) => {
        if (e.key == 'Enter') {
            navigator.clipboard.writeText(`[[${selected!.label}|${content}]]`);
            (e.target as HTMLInputElement).blur()
        }
    }, [selected.value, selected.label, content]);

    return (
        <span>
            <span style={{
                position: 'absolute',
                opacity: 0,
                zIndex: -100,
                whiteSpace: 'pre',
            }} ref={span}>{content}</span>
            <input
                className={"border-none p-0 px-1"}
                type="text" style={{width: `calc(${width}px + 0.25rem)`}} autoFocus onChange={changeHandler}
                onKeyDown={onCustomElementKeyDown}/>
        </span>
    );
}

function Alias({
                   original,
                   alias
               }: {
    original: string,
    alias: string
}) {
    const onClick = useCallback(() => {
        if (alias) {
            navigator.clipboard.writeText(`[[${original}|${alias}]]`)
        } else {
            navigator.clipboard.writeText(`[[${original}]]`)
        }
    }, [original, alias]);

    return (
        <span className={"rounded-md p-1 hover:bg-slate-100"} onClick={onClick}>
            <span className={"text-slate-300 p-0.5"}>[[</span>
            <span>{original}</span>
            {alias && <><span className={"text-slate-300 p-0.5"}>|</span>
                <span>{alias}</span></>}
            <span className={"text-slate-300 p-0.5"}>]]</span>
        </span>
    );
}
