import { FC, useEffect, useMemo, useState } from 'react';
import { LocalizeText, localizeWithFallback, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common';
import { useWired } from '../../../../hooks';
import { OctaneInput } from '../../../../layout';
import { WiredExtraBaseView } from './WiredExtraBaseView';
import { Checkbox } from '../../../../common/Checkbox';
import { Radio } from '../../../../common/Radio';
import { BaseButton } from '../../../../common/BaseButton';
import { NativeTextTree } from '../../../../common/native-text/NativeTextTree';

const MODE_LINEAR = 1;
const MODE_EXPONENTIAL = 2;
const MODE_MANUAL = 3;

const SUB_CURRENT_LEVEL = 0;
const SUB_CURRENT_XP = 1;
const SUB_LEVEL_PROGRESS = 2;
const SUB_LEVEL_PROGRESS_PERCENT = 3;
const SUB_TOTAL_XP_REQUIRED = 4;
const SUB_XP_REMAINING = 5;
const SUB_IS_AT_MAX = 6;
const SUB_MAX_LEVEL = 7;

const DEFAULT_STEP_SIZE = 100;
// Official defaults for a new box: 50 levels, 20 % growth, every sub-variable.
const DEFAULT_MAX_LEVEL = 50;
const DEFAULT_FIRST_LEVEL_XP = 100;
const DEFAULT_INCREASE_FACTOR = 20;
const DEFAULT_INTERPOLATION_TEXT = '';
const DEFAULT_SUBVARIABLES = [0, 1, 2, 3, 4, 5, 6, 7];
/** A box saved before sub-variables were stored had these two. */
const LEGACY_SUBVARIABLES = [SUB_CURRENT_LEVEL, SUB_CURRENT_XP];
const OFFICIAL_MODE_MANUAL = 0;
const OFFICIAL_MODE_LINEAR = 1;
const OFFICIAL_MODE_EXPONENTIAL = 2;
const DEFAULT_PLACEHOLDER = '5=100            (Level 5 = 100 XP)\n10=500\n20=4000\n...';

interface IVariableLevelUpEditorData {
    mode?: number;
    stepSize?: number;
    maxLevel?: number;
    firstLevelXp?: number;
    increaseFactor?: number;
    interpolationText?: string;
    subvariables?: number[] | null;
}

interface ILevelEntry {
    level: number;
    requiredXp: number;
}

const localizeOrFallback = (key: string, fallback: string, params?: string[], values?: string[]) => {
    const localized = params?.length ? LocalizeText(key, params, values ?? []) : LocalizeText(key);

    return !localized || localized === key ? fallback : localized;
};

const normalizeMode = (value: number) => {
    switch (value) {
        case MODE_EXPONENTIAL:
        case MODE_MANUAL:
            return value;
        default:
            return MODE_LINEAR;
    }
};

const normalizeNonNegativeInt = (value: number, fallback: number) => {
    if (!Number.isFinite(value)) return fallback;

    return Math.max(0, Math.trunc(value));
};

const normalizePositiveInt = (value: number, fallback: number) => {
    if (!Number.isFinite(value)) return fallback;

    return Math.max(1, Math.trunc(value));
};

const normalizeInterpolationText = (value: string) => (value ?? '').replace(/\r/g, '');

const normalizeSubvariables = (value?: number[] | null) => {
    if (value === null) return [...LEGACY_SUBVARIABLES];
    if (!Array.isArray(value)) return [...LEGACY_SUBVARIABLES];

    return [...new Set(value.filter((subvariable) => Number.isInteger(subvariable) && subvariable >= SUB_CURRENT_LEVEL && subvariable <= SUB_MAX_LEVEL))];
};

const parseEditorData = (value: string): IVariableLevelUpEditorData => {
    if (!value?.trim()) return {};

    if (!value.trim().startsWith('{')) {
        return {
            mode: MODE_MANUAL,
            interpolationText: normalizeInterpolationText(value)
        };
    }

    try {
        return (JSON.parse(value) as IVariableLevelUpEditorData) || {};
    } catch {
        return {};
    }
};

/**
 * Official layout: [sub-variable mask, mode (0 manual, 1 linear, 2 exponential), the mode's figures:
 * linear step and max level, or exponential first level XP, growth % and max level]; the manual
 * "level=xp" lines as text. A box from before that layout sends its JSON text and no ints.
 */
const readEditorData = (intData: number[], stringData: string): IVariableLevelUpEditorData => {
    if (!intData?.length || stringData?.trim().startsWith('{')) return parseEditorData(stringData);

    const mask = intData[0] ?? 0;
    const subvariables = DEFAULT_SUBVARIABLES.filter((subvariable) => (mask & (1 << subvariable)) !== 0);

    switch (intData[1]) {
        case OFFICIAL_MODE_MANUAL:
            return { mode: MODE_MANUAL, interpolationText: stringData ?? '', subvariables };
        case OFFICIAL_MODE_EXPONENTIAL:
            return { mode: MODE_EXPONENTIAL, firstLevelXp: intData[2], increaseFactor: intData[3], maxLevel: intData[4], subvariables };
        default:
            return { mode: MODE_LINEAR, stepSize: intData[2], maxLevel: intData[3], subvariables };
    }
};

const parseIntInput = (value: string, fallback: number) => {
    const parsedValue = parseInt((value ?? '').trim(), 10);

    return Number.isFinite(parsedValue) ? parsedValue : fallback;
};

const parseManualAnchors = (value: string) => {
    const anchors = new Map<number, number>();
    const lines = normalizeInterpolationText(value).split('\n');

    for (const rawLine of lines) {
        const trimmedLine = rawLine.trim();

        if (!trimmedLine.length) continue;

        const separator = trimmedLine.includes('=') ? '=' : trimmedLine.includes(',') ? ',' : '';

        if (!separator.length) continue;

        const [rawLevel, rawXp] = trimmedLine.split(separator, 2).map((part) => part.trim());
        const level = parseInt(rawLevel, 10);
        const xp = parseInt(rawXp, 10);

        if (!Number.isFinite(level) || !Number.isFinite(xp) || level <= 0) continue;

        anchors.set(level, Math.max(0, xp));
    }

    if (!anchors.has(1)) anchors.set(1, 0);

    return [...anchors.entries()].sort((left, right) => left[0] - right[0]);
};

const buildLinearEntries = (stepSize: number, maxLevel: number) => {
    const entries: ILevelEntry[] = [];

    for (let level = 1; level <= maxLevel; level++) {
        entries.push({
            level,
            requiredXp: Math.max(0, (level - 1) * stepSize)
        });
    }

    return entries;
};

const buildExponentialEntries = (firstLevelXp: number, increaseFactor: number, maxLevel: number) => {
    const entries: ILevelEntry[] = [{ level: 1, requiredXp: 0 }];
    let nextIncrement = Math.max(0, firstLevelXp);
    let threshold = 0;

    for (let level = 2; level <= maxLevel; level++) {
        threshold += nextIncrement;

        entries.push({
            level,
            requiredXp: Math.max(0, Math.round(threshold))
        });

        nextIncrement = Math.max(0, Math.round(nextIncrement * ((100 + Math.max(0, increaseFactor)) / 100)));
    }

    return entries;
};

const buildManualEntries = (value: string) => {
    const anchors = parseManualAnchors(value);

    if (!anchors.length) return [{ level: 1, requiredXp: 0 }];

    const entries = new Map<number, number>();

    for (let index = 0; index < anchors.length; index++) {
        const [currentLevel, currentXp] = anchors[index];

        entries.set(currentLevel, currentXp);

        if (index >= anchors.length - 1) continue;

        const [nextLevel, nextXp] = anchors[index + 1];

        if (nextLevel <= currentLevel) continue;

        const deltaLevel = nextLevel - currentLevel;
        const deltaXp = nextXp - currentXp;

        for (let level = currentLevel + 1; level < nextLevel; level++) {
            const progress = (level - currentLevel) / deltaLevel;
            entries.set(level, Math.max(0, Math.round(currentXp + deltaXp * progress)));
        }
    }

    return [...entries.entries()].sort((left, right) => left[0] - right[0]).map(([level, requiredXp]) => ({ level, requiredXp }));
};

const buildPreviewEntries = (mode: number, stepSize: number, maxLevel: number, firstLevelXp: number, increaseFactor: number, interpolationText: string) => {
    switch (mode) {
        case MODE_EXPONENTIAL:
            return buildExponentialEntries(firstLevelXp, increaseFactor, maxLevel);
        case MODE_MANUAL:
            return buildManualEntries(interpolationText);
        default:
            return buildLinearEntries(stepSize, maxLevel);
    }
};

export const WiredExtraVariableLevelUpSystemView: FC<{}> = () => {
    const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
    const [mode, setMode] = useState(MODE_LINEAR);
    const [stepSizeInput, setStepSizeInput] = useState(DEFAULT_STEP_SIZE.toString());
    const [maxLevelInput, setMaxLevelInput] = useState(DEFAULT_MAX_LEVEL.toString());
    const [firstLevelXpInput, setFirstLevelXpInput] = useState(DEFAULT_FIRST_LEVEL_XP.toString());
    const [increaseFactorInput, setIncreaseFactorInput] = useState(DEFAULT_INCREASE_FACTOR.toString());
    const [interpolationText, setInterpolationText] = useState(DEFAULT_INTERPOLATION_TEXT);
    const [selectedSubvariables, setSelectedSubvariables] = useState<number[]>(DEFAULT_SUBVARIABLES);
    const [isModeSectionOpen, setIsModeSectionOpen] = useState(true);
    const [isPreviewSectionOpen, setIsPreviewSectionOpen] = useState(true);
    const [isSubvariablesSectionOpen, setIsSubvariablesSectionOpen] = useState(true);

    useEffect(() => {
        if (!trigger) {
            setMode(MODE_LINEAR);
            setStepSizeInput(DEFAULT_STEP_SIZE.toString());
            setMaxLevelInput(DEFAULT_MAX_LEVEL.toString());
            setFirstLevelXpInput(DEFAULT_FIRST_LEVEL_XP.toString());
            setIncreaseFactorInput(DEFAULT_INCREASE_FACTOR.toString());
            setInterpolationText(DEFAULT_INTERPOLATION_TEXT);
            setSelectedSubvariables([...DEFAULT_SUBVARIABLES]);
            return;
        }

        const editorData = readEditorData(trigger.intData, trigger.stringData);

        setMode(normalizeMode(editorData.mode ?? MODE_LINEAR));
        setStepSizeInput(normalizeNonNegativeInt(editorData.stepSize ?? DEFAULT_STEP_SIZE, DEFAULT_STEP_SIZE).toString());
        setMaxLevelInput(normalizePositiveInt(editorData.maxLevel ?? DEFAULT_MAX_LEVEL, DEFAULT_MAX_LEVEL).toString());
        setFirstLevelXpInput(normalizeNonNegativeInt(editorData.firstLevelXp ?? DEFAULT_FIRST_LEVEL_XP, DEFAULT_FIRST_LEVEL_XP).toString());
        setIncreaseFactorInput(normalizeNonNegativeInt(editorData.increaseFactor ?? DEFAULT_INCREASE_FACTOR, DEFAULT_INCREASE_FACTOR).toString());
        setInterpolationText(normalizeInterpolationText(editorData.interpolationText ?? DEFAULT_INTERPOLATION_TEXT));
        setSelectedSubvariables(normalizeSubvariables(editorData.subvariables));
    }, [trigger]);

    const normalizedStepSize = useMemo(() => normalizeNonNegativeInt(parseIntInput(stepSizeInput, DEFAULT_STEP_SIZE), DEFAULT_STEP_SIZE), [stepSizeInput]);
    const normalizedMaxLevel = useMemo(() => normalizePositiveInt(parseIntInput(maxLevelInput, DEFAULT_MAX_LEVEL), DEFAULT_MAX_LEVEL), [maxLevelInput]);
    const normalizedFirstLevelXp = useMemo(
        () => normalizeNonNegativeInt(parseIntInput(firstLevelXpInput, DEFAULT_FIRST_LEVEL_XP), DEFAULT_FIRST_LEVEL_XP),
        [firstLevelXpInput]
    );
    const normalizedIncreaseFactor = useMemo(
        () => normalizeNonNegativeInt(parseIntInput(increaseFactorInput, DEFAULT_INCREASE_FACTOR), DEFAULT_INCREASE_FACTOR),
        [increaseFactorInput]
    );
    const normalizedInterpolation = useMemo(() => normalizeInterpolationText(interpolationText), [interpolationText]);

    const previewEntries = useMemo(
        () => buildPreviewEntries(mode, normalizedStepSize, normalizedMaxLevel, normalizedFirstLevelXp, normalizedIncreaseFactor, normalizedInterpolation),
        [mode, normalizedFirstLevelXp, normalizedIncreaseFactor, normalizedInterpolation, normalizedMaxLevel, normalizedStepSize]
    );

    const interpolationPlaceholder = useMemo(() => {
        const localizedText = LocalizeText('wiredfurni.params.levelup.interpolation_placeholder');

        if (!localizedText || localizedText === 'wiredfurni.params.levelup.interpolation_placeholder') return DEFAULT_PLACEHOLDER;

        return localizedText.includes('5,100') ? localizedText.replace(/,/g, '=') : localizedText;
    }, []);

    const save = () => {
        const mask = selectedSubvariables.reduce((value, subvariable) => value | (1 << subvariable), 0);

        switch (mode) {
            case MODE_MANUAL:
                setIntParams([mask, OFFICIAL_MODE_MANUAL]);
                setStringParam(normalizedInterpolation);
                return;
            case MODE_EXPONENTIAL:
                setIntParams([mask, OFFICIAL_MODE_EXPONENTIAL, normalizedFirstLevelXp, normalizedIncreaseFactor, normalizedMaxLevel]);
                setStringParam('');
                return;
            default:
                setIntParams([mask, OFFICIAL_MODE_LINEAR, normalizedStepSize, normalizedMaxLevel]);
                setStringParam('');
        }
    };

    const toggleSubvariable = (subvariable: number) => {
        setSelectedSubvariables((previousValue) => {
            if (previousValue.includes(subvariable)) {
                return previousValue.filter((value) => value !== subvariable);
            }

            return [...previousValue, subvariable].sort((left, right) => left - right);
        });
    };

    const modeOptions = [
        { value: MODE_LINEAR, label: localizeOrFallback('wiredfurni.params.levelup.mode.1', 'Lineare') },
        { value: MODE_EXPONENTIAL, label: localizeOrFallback('wiredfurni.params.levelup.mode.2', 'Esponenziale') },
        {
            value: MODE_MANUAL,
            label: localizeWithFallback('wiredfurni.params.levelup.mode.0', localizeOrFallback('wiredfurni.params.levelup.mode.3', 'Manuale'))
        }
    ];

    const subvariableOptions = [
        { key: SUB_CURRENT_LEVEL, suffix: 'current_level' },
        { key: SUB_CURRENT_XP, suffix: 'current_xp' },
        { key: SUB_LEVEL_PROGRESS, suffix: 'progress' },
        { key: SUB_LEVEL_PROGRESS_PERCENT, suffix: 'progress_percentage' },
        { key: SUB_TOTAL_XP_REQUIRED, suffix: 'xp_required' },
        { key: SUB_XP_REMAINING, suffix: 'xp_remaining' },
        { key: SUB_IS_AT_MAX, suffix: 'is_maxed' },
        { key: SUB_MAX_LEVEL, suffix: 'max_level' }
    ];

    return (
        <NativeTextTree>
            <WiredExtraBaseView hasSpecialInput={true} requiresFurni={WiredFurniType.STUFF_SELECTION_OPTION_NONE} save={save} cardStyle={{ width: 260 }}>
                <div className="octane-wired__levelup">
                    <div className="octane-wired__levelup-section">
                        <BaseButton type="button" className="octane-wired__levelup-section-header" onClick={() => setIsModeSectionOpen((value) => !value)}>
                            <Text bold>{LocalizeText('wiredfurni.params.levelup.mode')}</Text>
                            <span className={`octane-wired__levelup-chevron ${isModeSectionOpen ? 'is-open' : ''}`}>^</span>
                        </BaseButton>

                        {isModeSectionOpen && (
                            <div className="octane-wired__levelup-section-body">
                                <div className={`octane-wired__levelup-mode-block ${mode === MODE_LINEAR ? 'is-active' : 'is-inactive'}`}>
                                    <label className="octane-wired__levelup-mode-label">
                                        <Radio checked={mode === MODE_LINEAR} name="wiredVariableLevelUpMode" onCheckedChange={() => setMode(MODE_LINEAR)} />
                                        <Text>{localizeOrFallback('wiredfurni.params.levelup.mode.1', 'Lineare')}</Text>
                                    </label>
                                    <div className="octane-wired__levelup-fields">
                                        <div className="octane-wired__levelup-field-row">
                                            <Text>{LocalizeText('wiredfurni.params.levelup.step_size')}</Text>
                                            <OctaneInput
                                                className="octane-wired__levelup-number"
                                                disabled={mode !== MODE_LINEAR}
                                                type="number"
                                                value={stepSizeInput}
                                                onChange={(event) => setStepSizeInput(event.target.value)}
                                            />
                                        </div>
                                        <div className="octane-wired__levelup-field-row">
                                            <Text>{LocalizeText('wiredfurni.params.levelup.max_level')}</Text>
                                            <OctaneInput
                                                className="octane-wired__levelup-number"
                                                disabled={mode !== MODE_LINEAR}
                                                type="number"
                                                value={maxLevelInput}
                                                onChange={(event) => setMaxLevelInput(event.target.value)}
                                            />
                                        </div>
                                    </div>
                                </div>

                                <div className={`octane-wired__levelup-mode-block ${mode === MODE_EXPONENTIAL ? 'is-active' : 'is-inactive'}`}>
                                    <label className="octane-wired__levelup-mode-label">
                                        <Radio checked={mode === MODE_EXPONENTIAL} name="wiredVariableLevelUpMode" onCheckedChange={() => setMode(MODE_EXPONENTIAL)} />
                                        <Text>{localizeOrFallback('wiredfurni.params.levelup.mode.2', 'Esponenziale')}</Text>
                                    </label>
                                    <div className="octane-wired__levelup-fields">
                                        <div className="octane-wired__levelup-field-row">
                                            <Text>{LocalizeText('wiredfurni.params.levelup.first_level_xp')}</Text>
                                            <OctaneInput
                                                className="octane-wired__levelup-number"
                                                disabled={mode !== MODE_EXPONENTIAL}
                                                type="number"
                                                value={firstLevelXpInput}
                                                onChange={(event) => setFirstLevelXpInput(event.target.value)}
                                            />
                                        </div>
                                        <div className="octane-wired__levelup-field-row">
                                            <Text>{LocalizeText('wiredfurni.params.levelup.increase_factor')}</Text>
                                            <OctaneInput
                                                className="octane-wired__levelup-number"
                                                disabled={mode !== MODE_EXPONENTIAL}
                                                type="number"
                                                value={increaseFactorInput}
                                                onChange={(event) => setIncreaseFactorInput(event.target.value)}
                                            />
                                        </div>
                                        <div className="octane-wired__levelup-field-row">
                                            <Text>{LocalizeText('wiredfurni.params.levelup.max_level')}</Text>
                                            <OctaneInput
                                                className="octane-wired__levelup-number"
                                                disabled={mode !== MODE_EXPONENTIAL}
                                                type="number"
                                                value={maxLevelInput}
                                                onChange={(event) => setMaxLevelInput(event.target.value)}
                                            />
                                        </div>
                                    </div>
                                </div>

                                <div className={`octane-wired__levelup-mode-block ${mode === MODE_MANUAL ? 'is-active' : 'is-inactive'}`}>
                                    <label className="octane-wired__levelup-mode-label">
                                        <Radio checked={mode === MODE_MANUAL} name="wiredVariableLevelUpMode" onCheckedChange={() => setMode(MODE_MANUAL)} />
                                        <Text>
                                            {localizeWithFallback(
                                                'wiredfurni.params.levelup.mode.0',
                                                localizeOrFallback('wiredfurni.params.levelup.mode.3', 'Inserimento manuale')
                                            )}
                                        </Text>
                                    </label>
                                    <textarea
                                        className="form-control form-control-sm octane-wired__levelup-textarea"
                                        disabled={mode !== MODE_MANUAL}
                                        placeholder={interpolationPlaceholder}
                                        value={interpolationText}
                                        onChange={(event) => setInterpolationText(event.target.value)}
                                    />
                                </div>
                            </div>
                        )}
                    </div>

                    <div className="octane-wired__divider" />

                    <div className="octane-wired__levelup-section">
                        <BaseButton type="button" className="octane-wired__levelup-section-header" onClick={() => setIsPreviewSectionOpen((value) => !value)}>
                            <Text bold>{LocalizeText('wiredfurni.params.levelup.preview')}</Text>
                            <span className={`octane-wired__levelup-chevron ${isPreviewSectionOpen ? 'is-open' : ''}`}>^</span>
                        </BaseButton>

                        {isPreviewSectionOpen && (
                            <div className="octane-wired__levelup-preview">
                                {previewEntries.map((entry) => (
                                    <div key={entry.level} className="octane-wired__levelup-preview-entry">
                                        {localizeOrFallback(
                                            'wiredfurni.params.levelup.preview.entry',
                                            `Livello: ${entry.level} - XP: ${entry.requiredXp}`,
                                            ['lvl', 'xp'],
                                            [entry.level.toString(), entry.requiredXp.toString()]
                                        )}
                                    </div>
                                ))}
                            </div>
                        )}
                    </div>

                    <div className="octane-wired__divider" />

                    <div className="octane-wired__levelup-section">
                        <BaseButton type="button" className="octane-wired__levelup-section-header" onClick={() => setIsSubvariablesSectionOpen((value) => !value)}>
                            <Text bold>{LocalizeText('wiredfurni.params.create_subvariables')}</Text>
                            <span className={`octane-wired__levelup-chevron ${isSubvariablesSectionOpen ? 'is-open' : ''}`}>^</span>
                        </BaseButton>

                        {isSubvariablesSectionOpen && (
                            <div className="octane-wired__levelup-subvariables">
                                {subvariableOptions.map((subvariable) => (
                                    <div key={subvariable.key} className="octane-wired__levelup-subvariable-row">
                                        <label className="octane-wired__levelup-subvariable-label">
                                            <Checkbox checked={selectedSubvariables.includes(subvariable.key)} onCheckedChange={() => toggleSubvariable(subvariable.key)} />
                                            <Text>{LocalizeText(`wiredfurni.params.levelup.subvariable.${subvariable.key}`)}</Text>
                                        </label>
                                        <input className="octane-wired__levelup-subvariable-token" readOnly tabIndex={-1} type="text" value={subvariable.suffix} />
                                    </div>
                                ))}
                            </div>
                        )}
                    </div>
                </div>
            </WiredExtraBaseView>
        </NativeTextTree>
    );
};
