import React from "react";
import { useOpenK9Client, } from "./client";
import { loadQueryString, saveQueryString, loadLocalStorage, saveLocalStorage, } from "./queryString";
import { containsAtLeastOne } from "../embeddable/Main";
function toEnabledSet(values) {
    if (Array.isArray(values))
        return new Set(values);
    const s = new Set();
    Object.entries(values || {}).forEach(([k, v]) => {
        if (v)
            s.add(k);
    });
    return s;
}
export function useSelections({ useKeycloak = true, persistInQueryString = false, localStorageKey = "selections", defaultString = "", queryStringValues, queryStringMap, }) {
    const defaultSearch = {
        text: defaultString,
        selection: [],
        textOnChange: defaultString,
        filters: [],
        range: [0, 10],
        commitId: 0,
    };
    const enabled = toEnabledSet(queryStringValues);
    const buildMap = {
        keyObj: queryStringMap?.keyObj,
        ...(enabled.has("text") ? { text: queryStringMap?.text ?? "text" } : {}),
        ...(enabled.has("textOnChange")
            ? { textOnChange: queryStringMap?.textOnChange ?? "textOnChange" }
            : {}),
        ...(enabled.has("selection")
            ? { selection: queryStringMap?.selection ?? "selection" }
            : {}),
        ...(enabled.has("filters")
            ? { filters: queryStringMap?.filters ?? "filters" }
            : {}),
        ...(enabled.has("search")
            ? { search: queryStringMap?.search ?? "search" }
            : {}),
    };
    const [state, dispatch] = React.useReducer(reducer, defaultSearch, (def) => persistInQueryString
        ? loadQueryString(def, buildMap)
        : loadLocalStorage(def, localStorageKey, buildMap));
    const [canSave, setCanSave] = React.useState(false);
    const client = useOpenK9Client();
    React.useEffect(() => {
        if (useKeycloak && client.authInit) {
            client.authInit.then(() => setCanSave(true));
        }
        else if (!useKeycloak) {
            setCanSave(true);
        }
    }, []);
    React.useEffect(() => {
        if (canSave) {
            if (persistInQueryString) {
                saveQueryString(state, buildMap);
            }
            else {
                saveLocalStorage(state, localStorageKey, buildMap);
            }
        }
    }, [canSave, state, persistInQueryString, localStorageKey]);
    return [state, dispatch];
}
function reducer(state, action) {
    switch (action.type) {
        case "set-text": {
            const size = action.pageSize && action.pageSize > 0
                ? action.pageSize
                : state.range[1] ?? 10;
            return {
                ...state,
                text: action.text,
                textOnChange: action.textOnchange,
                selection: shiftSelection(state.textOnChange, action.textOnchange, state.selection),
                range: [0, size],
                commitId: state.commitId + 1,
            };
        }
        case "set-text-btn": {
            return {
                ...state,
                textOnChange: action.textOnchange,
                selection: shiftSelection(state.textOnChange, action.textOnchange, state.selection),
            };
        }
        case "reset-search": {
            return {
                ...state,
                text: "",
                textOnChange: "",
                selection: [],
                range: [0, state.range[1]],
                commitId: state.commitId + 1,
            };
        }
        case "set-selection": {
            const { text, selection } = (() => {
                if (action.replaceText ||
                    action.selection.token?.tokenType === "AUTOCORRECT") {
                    const tokenText = action.selection.token
                        ? getTokenText(action.selection.token)
                        : state.textOnChange?.slice(action.selection.start, action.selection.end);
                    const text = state.textOnChange?.slice(0, action.selection.start) +
                        tokenText +
                        state.textOnChange?.slice(action.selection.end);
                    const selection = action.selection.token?.tokenType === "AUTOCORRECT"
                        ? null
                        : {
                            text: tokenText,
                            textOnChange: tokenText,
                            start: action.selection.start,
                            end: action.selection.start + tokenText.length,
                            token: action.selection.token,
                            isAuto: action.selection.isAuto,
                        };
                    return { text, selection };
                }
                else {
                    return { text: state.text, selection: action.selection };
                }
            })();
            return {
                ...state,
                text,
                textOnChange: text,
                selection: shiftSelection(state.textOnChange || "", state.textOnChange || "", selection
                    ? state.selection.filter((s) => !isOverlapping(s, selection))
                    : state.selection).concat(selection ? [selection] : []),
                range: [0, state.range[1]],
                commitId: state.commitId + 1,
            };
        }
        case "set-filters": {
            return {
                ...state,
                filters: [...state.filters, action.filter],
                range: [0, state.range[1]],
                commitId: state.commitId + 1,
            };
        }
        case "set-all-filters": {
            return {
                ...state,
                filters: action.filter,
                range: [0, state.range[1]],
                commitId: state.commitId + 1,
            };
        }
        case "reset-filters": {
            return {
                ...state,
                filters: [],
                range: [0, state.range[1]],
                commitId: state.commitId + 1,
            };
        }
        case "remove-filter": {
            const filters = state.filters.filter((token) => {
                const searchToken = action.filter;
                if (searchToken && searchToken.values && token && token.values)
                    return !(token.suggestionCategoryId === searchToken.suggestionCategoryId &&
                        token.isFilter === searchToken.isFilter &&
                        token.keywordKey === searchToken.keywordKey &&
                        token.tokenType === searchToken.tokenType &&
                        containsAtLeastOne(searchToken.values, token.values));
                return true;
            });
            return {
                ...state,
                filters,
                range: [0, state.range[1]],
                commitId: state.commitId + 1,
            };
        }
        case "set-range": {
            return { ...state, range: action.range };
        }
        case "set-page-size": {
            const size = action.pageSize && action.pageSize > 0
                ? action.pageSize
                : state.range[1];
            return { ...state, range: [0, size], commitId: state.commitId + 1 };
        }
        default:
            return state;
    }
}
function shiftSelection(prevText, nextText, prevSelection) {
    if (prevText === nextText)
        return prevSelection;
    const commonPrefixLength = findCommonPrefixLength(prevText, nextText);
    const commonSuffixLength = findCommonSuffixLength(prevText, nextText, commonPrefixLength);
    const changePrevEnd = prevText.length - commonSuffixLength;
    const changeNextEnd = nextText.length - commonSuffixLength;
    const changeDelta = changeNextEnd - changePrevEnd;
    const prefixAttributes = prevSelection.filter((a) => a.start <= commonPrefixLength && a.end <= commonPrefixLength);
    const suffixAttributes = prevSelection
        .filter((a) => a.start >= changePrevEnd)
        .map((a) => ({
        ...a,
        start: a.start + changeDelta,
        end: a.end + changeDelta,
    }));
    return prefixAttributes.concat(suffixAttributes);
}
function findCommonPrefixLength(a, b) {
    const length = Math.min(a.length, b.length);
    let i = 0;
    for (; i < length && a.charCodeAt(i) === b.charCodeAt(i); ++i) { }
    return i;
}
function findCommonSuffixLength(a, b, startFromIndex) {
    const length = Math.min(a.length, b.length) - startFromIndex;
    if (length <= 0)
        return 0;
    let i = 0;
    for (; i < length &&
        a.charCodeAt(a.length - i - 1) === b.charCodeAt(b.length - i - 1); ++i) { }
    return i;
}
function getTokenText(token) {
    switch (token.tokenType) {
        case "DATASOURCE":
            return token.value;
        case "DOCTYPE":
            return token.value;
        case "ENTITY":
            return token.entityName;
        case "TEXT":
            return token.value;
        case "AUTOCORRECT":
            return token.value;
        case "AUTOCOMPLETE":
            return token.value;
        case "FILTER":
            return token.value;
    }
}
export function getAutoSelections(selection, entries) {
    return entries
        .filter(({ text }) => text.length > 0)
        .map(getAutoSelection)
        .filter(isNotNull)
        .filter((entry, index, autoSelections) => {
        return selection
            .concat(autoSelections.slice(0, index))
            .every((s) => !isOverlapping(entry, s));
    });
}
function getAutoSelection(entry) {
    const [first, second] = [...entry.tokens].sort((a, b) => a.score - b.score);
    if (first) {
        if (first.tokenType === "AUTOCOMPLETE" || first.tokenType === "AUTOCORRECT")
            return null;
        if (!second || first.score >= second.score * 2) {
            return {
                text: entry.text,
                start: entry.start,
                end: entry.end,
                token: first,
                isAuto: true,
            };
        }
    }
    return null;
}
function isNotNull(value) {
    return value !== null;
}
export function isOverlapping(a, b) {
    return isInRange(a, b.start) || isInRange(a, b.end);
}
function isInRange({ start, end }, position) {
    return position >= start && position <= end;
}
