if (D2RMM.getVersion == null || D2RMM.getVersion() < 1.8) {
    D2RMM.error('Requires D2RMM version 1.8 or higher.');
    return;
}

const PC_INVENTORY_ORIGINAL = 'global\\ui\\layouts\\playerinventoryoriginallayouthd.json';
const PC_INVENTORY_EXPANSION = 'global\\ui\\layouts\\playerinventoryexpansionlayouthd.json';
const CONTROLLER_INVENTORY_ORIGINAL = 'global\\ui\\layouts\\controller\\playerinventoryoriginallayouthd.json';
const CONTROLLER_INVENTORY_EXPANSION = 'global\\ui\\layouts\\controller\\playerinventoryexpansionlayouthd.json';
const PC_BANK_EXPANSION = 'global\\ui\\layouts\\bankexpansionlayouthd.json';
const CONTROLLER_BANK_EXPANSION = 'global\\ui\\layouts\\controller\\bankexpansionlayouthd.json';

const CONTROLLER_FAKE_STASH_ACTION_ORIGINAL = 'FakeStashControllerOriginalAction';
const CONTROLLER_FAKE_STASH_ACTION_EXPANSION = 'FakeStashControllerExpansionAction';
const CONTROLLER_FAKE_STASH_ACTION_ORIGINAL_FILE = 'global\\ui\\layouts\\fakestashcontrolleroriginalactionhd.json';
const CONTROLLER_FAKE_STASH_ACTION_EXPANSION_FILE = 'global\\ui\\layouts\\fakestashcontrollerexpansionactionhd.json';

const FAKE_STASH_BUTTON_NAME = 'fake_stash_button';
const GOLD_DROP_BUTTON_NAME = 'gold_drop_button';
const FAKE_STASH_ASSET = 'PANEL/utility_buttons/fake_stash_button';
const GOLD_DROP_ASSET = 'PANEL/utility_buttons/gold_drop_button';

const ASSET_FILES = [
    'hd/global/ui/panel/utility_buttons/fake_stash_button.sprite',
    'hd/global/ui/panel/utility_buttons/fake_stash_button.lowend.sprite',
    'hd/global/ui/panel/utility_buttons/gold_drop_button.sprite',
    'hd/global/ui/panel/utility_buttons/gold_drop_button.lowend.sprite'
];

function findNodeByName(node, name)
{
    if (!node || typeof node !== 'object') {
        return null;
    }
    if (node.name === name) {
        return node;
    }
    if (!Array.isArray(node.children)) {
        return null;
    }

    for (let i = 0; i < node.children.length; ++i) {
        const found = findNodeByName(node.children[i], name);
        if (found) {
            return found;
        }
    }
    return null;
}

function removeDirectChildrenByName(parent, names)
{
    if (!parent || !Array.isArray(parent.children)) {
        return;
    }
    parent.children = parent.children.filter((child) => {
        return !child || !names.includes(child.name);
    });
}

function makeFakeStashButton(position, isController, controllerActionPanel)
{
    const fields = {
        rect: { x: position.x, y: position.y, width: 100, height: 100 },
        filename: FAKE_STASH_ASSET,
        hoveredFrame: 1,
        disabledFrame: 2,
        pressedFrame: 3,
        tooltipString: '보관함',
        onClickMessage: isController
            ? ('PanelManager:OpenPanel:' + controllerActionPanel)
            : 'PanelManager:OpenPanel:BankExpansionLayout'
    };

    if (isController) {
        fields.navigation = {
            left: { name: 'gold_button' },
            up: { name: 'grid' },
            right: { panelBoundary: true },
            down: { panelBoundary: true }
        };
        fields.buttonLegendAction = 'select';
        fields.buttonLegendString = '보관함';
    }

    return {
        type: 'ButtonWidget',
        name: FAKE_STASH_BUTTON_NAME,
        fields: fields
    };
}

function patchInventoryLayout(filename, position, isController, controllerActionPanel)
{
    const layout = D2RMM.readJson(filename);
    if (!layout) {
        throw new Error('Could not read inventory layout: ' + filename);
    }
    if (!Array.isArray(layout.children)) {
        layout.children = [];
    }

    removeDirectChildrenByName(layout, [FAKE_STASH_BUTTON_NAME]);
    layout.children.push(makeFakeStashButton(position, isController, controllerActionPanel));

    // Controller navigation bridge: only modify an already-existing navigation object.
    // This preserves all native gold-button behavior and changes only its right destination.
    if (isController) {
        const goldButton = findNodeByName(layout, 'gold_button');
        if (goldButton && goldButton.fields && goldButton.fields.navigation) {
            goldButton.fields.navigation.right = { name: FAKE_STASH_BUTTON_NAME };
        }
    }

    D2RMM.writeJson(filename, layout);
}

function installControllerFakeStashActionPanel(filename, panelName, inventoryPanelName)
{
    D2RMM.writeJson(filename, {
        type: 'PausePanel',
        name: panelName,
        fields: {
            priority: 9001,
            fitToParent: true
        },
        children: [
            {
                type: 'TimerWidget',
                name: 'CloseCurrentInventory',
                fields: {
                    time: 0.000,
                    message: 'PanelManager:ClosePanel:' + inventoryPanelName
                }
            },
            {
                type: 'TimerWidget',
                name: 'OpenFakeStash',
                fields: {
                    time: 0.010,
                    message: 'PanelManager:OpenPanel:BankExpansionLayout'
                }
            },
            {
                type: 'TimerWidget',
                name: 'ReopenInventoryOnRight',
                fields: {
                    time: 0.020,
                    message: 'PanelManager:OpenPanel:' + inventoryPanelName
                }
            },
            {
                type: 'TimerWidget',
                name: 'CloseFakeStashRelay',
                fields: {
                    time: 0.030,
                    message: 'PanelManager:ClosePanel:' + panelName
                }
            }
        ]
    });
}

function installControllerFakeStashActionPanels()
{
    installControllerFakeStashActionPanel(
        CONTROLLER_FAKE_STASH_ACTION_ORIGINAL_FILE,
        CONTROLLER_FAKE_STASH_ACTION_ORIGINAL,
        'PlayerInventoryOriginalLayout'
    );
    installControllerFakeStashActionPanel(
        CONTROLLER_FAKE_STASH_ACTION_EXPANSION_FILE,
        CONTROLLER_FAKE_STASH_ACTION_EXPANSION,
        'PlayerInventoryExpansionLayout'
    );
}

function makeGoldDropButton(position)
{
    return {
        type: 'ButtonWidget',
        name: GOLD_DROP_BUTTON_NAME,
        fields: {
            rect: { x: position.x, y: position.y, width: 48, height: 49 },
            filename: GOLD_DROP_ASSET,
            hoveredFrame: 1,
            disabledFrame: 2,
            pressedFrame: 3,
            tooltipString: '금화 떨구기',
            onClickMessage: 'PlayerInventoryPanelMessage:DropGold'
        }
    };
}

function patchGoldDropButton(bankPanel, position)
{
    const basicContainer = findNodeByName(bankPanel, 'basicstash_container');
    if (!basicContainer || !Array.isArray(basicContainer.children)) {
        throw new Error('Could not find basicstash_container in stash layout.');
    }

    // Remove only this standalone button and the legacy HUD2nd drop-button pair.
    // The vanilla gold_withdraw / BankPanelMessage:Withdraw node is intentionally untouched.
    removeDirectChildrenByName(basicContainer, [GOLD_DROP_BUTTON_NAME, 'iv_gold_button', 'gold_box2']);
    basicContainer.children.push(makeGoldDropButton(position));
}

function patchPcFakeStashCompanion(bankPanel)
{
    if (!Array.isArray(bankPanel.children)) {
        bankPanel.children = [];
    }

    // Moved verbatim in behavior from the item-dictionary fake-stash feature.
    // Directly toggling BankExpansionLayout skips the normal NPC path that displays SidePanel_L.
    const obsoleteNames = [
        'StashOpenPanelBorders',
        'StashOpenPlayerInventory',
        'StashLeftDockingBar'
    ];

    bankPanel.children = bankPanel.children.filter((child) => {
        if (!child) return false;
        const name = String(child.name || '');
        return !obsoleteNames.includes(name) && !name.endsWith('Stash_LeftDockingBar') &&
            !name.endsWith('Stash_OpenPanelBorders') && !name.endsWith('Stash_OpenPlayerInventory');
    });

    bankPanel.children.unshift({
        type: 'ImageWidget',
        name: 'StashLeftDockingBar',
        fields: {
            rect: { x: -236, y: -205 },
            filename: 'PANEL\\Docking_Bar\\SidePanel_L'
        }
    });
}

function removeStandaloneGoldDropButton(bankPanel)
{
    const basicContainer = findNodeByName(bankPanel, 'basicstash_container');
    if (!basicContainer || !Array.isArray(basicContainer.children)) {
        throw new Error('Could not find basicstash_container in stash layout.');
    }

    // Disabled means remove only this standalone mod's button.
    // Vanilla gold_withdraw remains untouched.
    removeDirectChildrenByName(basicContainer, [GOLD_DROP_BUTTON_NAME]);
}

function patchPcBank(position, enableGoldDropButton)
{
    const bankPanel = D2RMM.readJson(PC_BANK_EXPANSION);
    if (!bankPanel) {
        throw new Error('Could not read stash layout: ' + PC_BANK_EXPANSION);
    }

    patchPcFakeStashCompanion(bankPanel);
    if (enableGoldDropButton) {
        patchGoldDropButton(bankPanel, position);
    } else {
        removeStandaloneGoldDropButton(bankPanel);
    }
    D2RMM.writeJson(PC_BANK_EXPANSION, bankPanel);
}

function patchControllerBank(position, enableGoldDropButton)
{
    const bankPanel = D2RMM.readJson(CONTROLLER_BANK_EXPANSION);
    if (!bankPanel) {
        throw new Error('Could not read stash layout: ' + CONTROLLER_BANK_EXPANSION);
    }

    if (enableGoldDropButton) {
        patchGoldDropButton(bankPanel, position);
    } else {
        removeStandaloneGoldDropButton(bankPanel);
    }
    D2RMM.writeJson(CONTROLLER_BANK_EXPANSION, bankPanel);
}

function parseXYPosition(value, fallbackX, fallbackY)
{
    const text = String(value == null ? '' : value).trim();
    const parts = text.split(',');
    if (parts.length !== 2) {
        return { x: fallbackX, y: fallbackY };
    }

    const x = Number.parseFloat(parts[0].trim());
    const y = Number.parseFloat(parts[1].trim());
    if (!Number.isFinite(x) || !Number.isFinite(y)) {
        return { x: fallbackX, y: fallbackY };
    }
    return { x: x, y: y };
}

// Fake-stash positions remain fixed at the verified stable values.
const fakeStashPcPosition = { x: 260, y: 1326 };
const fakeStashControllerPosition = { x: 380, y: 1310 };

// Gold-drop coordinates are user-adjustable while the button is enabled.
const DEFAULT_GOLD_DROP_PC = { x: 556, y: 1286 };
const DEFAULT_GOLD_DROP_CONTROLLER = { x: 619, y: 1226 };
const enableGoldDropButton = config.enableGoldDropButton !== false;
const goldDropPcPosition = parseXYPosition(
    config.goldDropPositionPC,
    DEFAULT_GOLD_DROP_PC.x,
    DEFAULT_GOLD_DROP_PC.y
);
const goldDropControllerPosition = parseXYPosition(
    config.goldDropPositionController,
    DEFAULT_GOLD_DROP_CONTROLLER.x,
    DEFAULT_GOLD_DROP_CONTROLLER.y
);

for (let i = 0; i < ASSET_FILES.length; ++i) {
    D2RMM.copyFile(ASSET_FILES[i], ASSET_FILES[i], true);
}

installControllerFakeStashActionPanels();

patchInventoryLayout(PC_INVENTORY_ORIGINAL, fakeStashPcPosition, false, null);
patchInventoryLayout(PC_INVENTORY_EXPANSION, fakeStashPcPosition, false, null);
patchInventoryLayout(CONTROLLER_INVENTORY_ORIGINAL, fakeStashControllerPosition, true, CONTROLLER_FAKE_STASH_ACTION_ORIGINAL);
patchInventoryLayout(CONTROLLER_INVENTORY_EXPANSION, fakeStashControllerPosition, true, CONTROLLER_FAKE_STASH_ACTION_EXPANSION);
patchPcBank(goldDropPcPosition, enableGoldDropButton);
patchControllerBank(goldDropControllerPosition, enableGoldDropButton);

/* ========================================================================
 * Standalone stash interaction-radius marker
 * Fixed radius = 48. Beacon appearance is selected by C/S/O options.
 * SpecialNavi beacon textures by Porory / HeoHeo are bundled with this mod.
 * Stable utility-button code above is intentionally untouched.
 * ====================================================================== */
const STASH_RADIUS_FIXED_RADIUS = 48.0;
const STASH_RADIUS_BANK_PATH = 'hd\\objects\\chests\\bank.json';
const STASH_RADIUS_PREFAB_FILE = 'hd\\env\\stash_radius\\pf_stash_radius.json';
const STASH_RADIUS_PREFAB_LOGICAL = 'data/hd/env/stash_radius/pf_stash_radius.json';
const STASH_RADIUS_TEXTURE_LOGICAL = 'data/hd/env/stash_radius/texture/stash_radius_selected.texture';
const STASH_RADIUS_ALPHA_TARGET = 'hd/env/stash_radius/texture/stash_radius_selected$a.texture';
const STASH_RADIUS_RGB_TARGET = 'hd/env/stash_radius/texture/stash_radius_selected$rgb.texture';
const STASH_RADIUS_PLACEMENT_NAME = 'stash_interaction_radius';
const STASH_RADIUS_PLACEMENT_ID = 1601100001;

const STASH_RADIUS_VALID_C = [
    'c00','c01','c02','c03','c04','c05','c06','c07','c08','c09',
    'c10','c11','c12','c13','c14','c15','c16','c17','c18','c19'
];
const STASH_RADIUS_VALID_S = ['s00','s01','s02','s03','s04','s05','s06','s07'];
const STASH_RADIUS_VALID_O = ['o25','o50','o75','o100'];

function pickStashRadiusOption(value, allowed, fallback)
{
    const v = String(value == null ? '' : value).trim().toLowerCase();
    return allowed.includes(v) ? v : fallback;
}

function getStashRadiusTextureSources(c, s, o)
{
    const stem = 'beacon_' + c + '_' + s + '_' + o;
    return {
        alpha: 'assets/porory_beacon/texture/' + stem + '$a.texture',
        rgb: 'assets/porory_beacon/texture/' + stem + '$rgb.texture'
    };
}

function makeStashRadiusQuarter(name, id, x, z, radius, orientation)
{
    return {
        type: 'Entity',
        name: name,
        id: id,
        components: [
            {
                type: 'TransformDefinitionComponent',
                name: name + '_tf',
                position: { x: x, y: 0.0, z: z },
                orientation: orientation,
                scale: { x: radius, y: 1.0, z: radius },
                inheritOnlyPosition: false
            },
            {
                type: 'DecalDefinitionComponent',
                name: name + '_decal',
                diffuseMap: STASH_RADIUS_TEXTURE_LOGICAL,
                normalMap: '',
                ormMap: '',
                edgeFade: 0.0,
                heightFade: 0.0,
                weight: 1.0,
                applyToAlbedo: true,
                applyToNormal: false,
                applyToOcclusion: false,
                applyToRoughness: false,
                applyToMetalness: false,
                thresholdValue: 0.0,
                thresholdHardness: 1.0,
                emissiveIntensity: 1.0,
                emissiveFocus: 1.0,
                lightMask: 16
            }
        ]
    };
}

function writeStashRadiusPrefab(radius)
{
    const half = radius / 2.0;
    D2RMM.writeJson(STASH_RADIUS_PREFAB_FILE, {
        dependencies: {
            particles: [], models: [], skeletons: [], animations: [],
            textures: [{ path: STASH_RADIUS_TEXTURE_LOGICAL }],
            physics: [], json: [], variantdata: [], objecteffects: [], other: []
        },
        type: 'Prefab',
        name: 'pf_stash_radius',
        entities: [
            makeStashRadiusQuarter('stash_radius_q1', 1601100101,  half, -half, radius, { x: 0.0, y:  0.0,     z: 0.0, w: 1.0 }),
            makeStashRadiusQuarter('stash_radius_q2', 1601100102,  half,  half, radius, { x: 0.0, y: -0.70711, z: 0.0, w: 0.70711 }),
            makeStashRadiusQuarter('stash_radius_q3', 1601100103, -half, -half, radius, { x: 0.0, y:  0.70711, z: 0.0, w: 0.70711 }),
            makeStashRadiusQuarter('stash_radius_q4', 1601100104, -half,  half, radius, { x: 0.0, y:  1.0,     z: 0.0, w: 0.0 })
        ]
    });
}

function patchStashRadius(radius, c, s, o)
{
    const bank = D2RMM.readJson(STASH_RADIUS_BANK_PATH);
    if (!bank || typeof bank !== 'object') {
        throw new Error('Could not read stash object: ' + STASH_RADIUS_BANK_PATH);
    }

    if (!bank.dependencies || typeof bank.dependencies !== 'object') bank.dependencies = {};
    if (!Array.isArray(bank.dependencies.json)) bank.dependencies.json = [];
    if (!Array.isArray(bank.entities)) bank.entities = [];

    bank.dependencies.json = bank.dependencies.json.filter((entry) => {
        return !(entry && String(entry.path || '').toLowerCase() === STASH_RADIUS_PREFAB_LOGICAL.toLowerCase());
    });
    bank.entities = bank.entities.filter((entity) => {
        if (!entity) return false;
        if (entity.name === STASH_RADIUS_PLACEMENT_NAME || entity.id === STASH_RADIUS_PLACEMENT_ID) return false;
        if (!Array.isArray(entity.components)) return true;
        return !entity.components.some((component) => {
            return component && String(component.prefab || '').toLowerCase() === STASH_RADIUS_PREFAB_LOGICAL.toLowerCase();
        });
    });

    const source = getStashRadiusTextureSources(c, s, o);
    D2RMM.copyFile(source.alpha, STASH_RADIUS_ALPHA_TARGET, true);
    D2RMM.copyFile(source.rgb, STASH_RADIUS_RGB_TARGET, true);
    writeStashRadiusPrefab(radius);

    bank.dependencies.json.push({ path: STASH_RADIUS_PREFAB_LOGICAL });
    bank.entities.push({
        type: 'Entity',
        name: STASH_RADIUS_PLACEMENT_NAME,
        id: STASH_RADIUS_PLACEMENT_ID,
        components: [
            {
                type: 'TransformDefinitionComponent',
                name: 'stash_radius_placement_tf',
                position: { x: 0.0, y: 0.0, z: 0.0 },
                orientation: { x: 0.0, y: 0.0, z: 0.0, w: 1.0 },
                scale: { x: 1.0, y: 1.0, z: 1.0 },
                inheritOnlyPosition: false
            },
            {
                type: 'PrefabPlacementDefinitionComponent',
                name: 'stash_radius_placement_prefab',
                prefab: STASH_RADIUS_PREFAB_LOGICAL
            }
        ]
    });

    D2RMM.writeJson(STASH_RADIUS_BANK_PATH, bank);
}

const stashRadiusC = pickStashRadiusOption(config.stashRadiusC, STASH_RADIUS_VALID_C, 'c01');
const stashRadiusS = pickStashRadiusOption(config.stashRadiusS, STASH_RADIUS_VALID_S, 's00');
const stashRadiusO = pickStashRadiusOption(config.stashRadiusO, STASH_RADIUS_VALID_O, 'o25');
patchStashRadius(STASH_RADIUS_FIXED_RADIUS, stashRadiusC, stashRadiusS, stashRadiusO);
