import { toast } from "react-toastify";

export type TextToken = {
    type: 'text';
    content: string;
    isWhitespace: boolean;
    isLineBreak?: boolean;
};

export type BlankToken = {
    type: 'blank';
    id: number;
};

type Token = TextToken | BlankToken;

export type BlankState = {
    id: number;
    position: number;
};


export const getTextSegments = (text: string): Token[] => {
    const tokens: Token[] = [];
    let lastIndex = 0;
    const regex = /{{(\d+)}}/g;
    let match;

    const addTextTokens = (text: string) => {
        // Split by newlines first
        const lines = text.replaceAll("\\n",'\n').split(/(\n)/);

        lines.forEach((line, i) => {
            if (line === '\n') {
                tokens.push({
                    type: 'text',
                    content: '<br>',
                    isWhitespace: false,
                    isLineBreak: true
                });
                return;
            }

            const normalizedText = line.replace(/\s+/g, ' ');
            if (normalizedText) {
                const parts = normalizedText.split(/(\s)/);
                parts.forEach(part => {
                    if (part) {
                        tokens.push({
                            type: 'text',
                            content: part,
                            isWhitespace: /^\s+$/.test(part)
                        });
                    }
                });
            }
        });
    };

    while ((match = regex.exec(text)) !== null) {
        if (match.index > lastIndex) {
            addTextTokens(text.slice(lastIndex, match.index));
        }
        tokens.push({
            type: 'blank',
            id: parseInt(match[1])
        });
        lastIndex = regex.lastIndex;
    }

    if (lastIndex < text.length) {
        addTextTokens(text.slice(lastIndex));
    }

    return tokens;
}

export const reconstructTextFromTokens = (tokens: Token[]): string => {
    return tokens.map(token => {
        if (token.type === 'blank') {
            return `{{${token.id}}}`;
        }
        if (token.type === 'text' && token.isLineBreak) {
            return '\n';
        }
        return token.content;
    }).join('');
}


export type BlanksState = {
    text: string;
    blanks: BlankState[];
    selectedBlankId: number | null;
    draggedItemId: string | null;
    textMode: boolean;
    setEditing: React.Dispatch<React.SetStateAction<boolean>>;
};

export type BlanksAction =
    | { type: "SET_TEXT"; payload: string }
    | { type: "SET_BLANKS"; payload: BlankState[] }
    | { type: "ADD_BLANK" }
    | { type: "REMOVE_BLANK"; payload: number }
    | { type: "SELECT_BLANK"; payload: number | null }
    | { type: "SET_DRAGGED_ITEM"; payload: string | null }
    | { type: "MOVE_BLANK"; payload: { blankId: number; newPosition: number } }
    | { type: "TOGGLE_EDIT_MODE" }
    | { type: "RESET", payload: { text: string } };


export const blanksReducer = (state: BlanksState, action: BlanksAction): BlanksState => {
    switch (action.type) {
        case "SET_TEXT": {
            return {
                ...state,
                text: action.payload,
            };
        }
        case "SET_BLANKS": {
            return {
                ...state,
                blanks: action.payload,
            };
        }
        case "ADD_BLANK":
            state.setEditing(true);
            const newBlankId = Math.max(...state.blanks.map(b => b.id), 0) + 1;
            const newBlanks = [
                ...state.blanks,
                { id: newBlankId, position: state.blanks.length }
            ];
            const newText = state.text + ` {{${newBlankId}}}`;

            return {
                ...state,
                blanks: newBlanks,
                text: newText
            };

        case "REMOVE_BLANK": {
            if (state.blanks.length === 1) {
                toast.error("There needs to be at least 1 blank!");
                break;
            } 
            state.setEditing(true);
            const blanksToKeep = state.blanks.filter(b => b.id !== action.payload);
            const updatedBlanks = blanksToKeep.map((blank, index) => ({
                ...blank,
                position: index
            }));

            const tokens = getTextSegments(state.text).filter(
                token => !(token.type === 'blank' && token.id === action.payload)
            );

            const newText = reconstructTextFromTokens(tokens);

            return {
                ...state,
                blanks: updatedBlanks,
                text: newText,
                selectedBlankId: state.selectedBlankId === action.payload ? null : state.selectedBlankId
            };
        }

        case "MOVE_BLANK": {
            state.setEditing(true);
            const { blankId, newPosition } = action.payload;
            const tokens = getTextSegments(state.text);

            // Find the current position of the blank
            const currentPosition = tokens.findIndex(
                token => token.type === 'blank' && token.id === blankId
            );

            if (currentPosition === -1) return state;

            // Remove the blank and its surrounding whitespace
            const blankToken = tokens[currentPosition];
            tokens.splice(currentPosition, 1);

            // When inserting at new position, ensure there's whitespace around the blank
            let insertPosition = newPosition;
            const prevToken = tokens[insertPosition - 1];
            const nextToken = tokens[insertPosition];

            // Insert space before if needed
            if (!prevToken || (prevToken.type === 'text' && !prevToken.isWhitespace)) {
                tokens.splice(insertPosition, 0, {
                    type: 'text',
                    content: ' ',
                    isWhitespace: true
                });
                insertPosition++;
            }

            // Insert the blank
            tokens.splice(insertPosition, 0, blankToken);
            insertPosition++;

            // Insert space after if needed
            if (!nextToken || (nextToken.type === 'text' && !nextToken.isWhitespace)) {
                tokens.splice(insertPosition, 0, {
                    type: 'text',
                    content: ' ',
                    isWhitespace: true
                });
            }

            // Reconstruct the text
            const newText = reconstructTextFromTokens(tokens);

            // Update blank positions
            const updatedBlanks = tokens.reduce((acc, token, idx) => {
                if (token.type === 'blank') {
                    acc.push({ id: token.id, position: idx });
                }
                return acc;
            }, [] as BlankState[]);

            return {
                ...state,
                text: newText,
                blanks: updatedBlanks
            };
        }
        case "SELECT_BLANK":
            return { ...state, selectedBlankId: action.payload };
        case "SET_DRAGGED_ITEM":
            state.setEditing(true);
            return { ...state, draggedItemId: action.payload };
        case "TOGGLE_EDIT_MODE":
            return { ...state, textMode: !state.textMode };

        case "RESET":
            return {
                text: action.payload.text || "",
                blanks: [],
                selectedBlankId: null,
                draggedItemId: null,
                textMode: false,
                setEditing: state.setEditing
            };
    }
    return state;
};
