// Brass & Sigil — Plot system V1
//
// SPAWN COMMERCIAL PLOTS (not bases). Players' homes go out in the world
// with normal FTB Chunks claims. A "plot" is a small commercial chunk-
// allocation at spawn for shops. 1 plot per player; release-and-rebuy to
// change size.
//
// Player commands  (/bns plot ...):
//   list                     show all plots + availability + price
//   info <id>                detailed info for one plot
//   buy <id>                 purchase if eligible (no current plot, has coin)
//   release                  release current plot (50% refund)
// Admin commands  (OP, level 2):
//   reload                   re-load plot definitions (currently in-script)
//
// Storage:
//   Plot definitions:   hardcoded `PLOT_DEFS` below; admin edits and reloads.
//                       Path-to-disk loading deferred to V1.1 (KubeJS file
//                       I/O is sandboxed).
//   Ownership state:    server.persistentData.bnsPlots  -- compound NBT
//                       keyed by plot id, value = player UUID string. Single
//                       source of truth.
//
// Integrations (best-effort; verify in playtest):
//   - Numismatics coin handling: inventory-based. Counts coins by denomination
//     value, takes greedily by largest first. Assumed values:
//        spur=1, bevel=8, cog=64, crown=512, sun=4096
//     If Numismatics uses different ratios, adjust COIN_VALUES below.
//   - FTB Chunks claim transfer: runs `/ftbchunks claim <x> <z>` as the player
//     for each chunk in the plot, prefixed by `execute in <dim> as <player>`.
//     Release runs `/ftbchunks unclaim`. May need adjusting if FTBC command
//     syntax differs in this version.
//
// Performance:
//   - All event-handler-style work is bounded by player action (command).
//     No periodic ticks. No event hot-path scans.
//   - Plot lookup is O(1) via Map.
//   - Coin inventory scan is O(36) per purchase -- once per player action.

// ─── Plot definitions ─────────────────────────────────────────────────
// V1 hardcoded. Replace with admin-defined plots once the spawn marketplace
// is built. Chunks are [chunkX, chunkZ] pairs in the overworld.
const PLOT_DEFS = new Map([
    ['p001', {
        id: 'p001', name: 'Market Stall 1', size: 'small',
        chunks: [[10, 10]],                     // 1x1
        price: 100,                             // in spurs
        dimension: 'minecraft:overworld',
    }],
    ['p002', {
        id: 'p002', name: 'Corner Lot',  size: 'medium',
        chunks: [[12, 10], [12, 11]],            // 1x2
        price: 250,
        dimension: 'minecraft:overworld',
    }],
    ['p003', {
        id: 'p003', name: 'Anchor Plot', size: 'large',
        chunks: [[14, 10], [14, 11], [15, 10], [15, 11]],  // 2x2
        price: 600,
        dimension: 'minecraft:overworld',
    }],
]);

const REFUND_FRACTION = 0.5;     // released plots refund 50% of purchase price

// ─── Numismatics coin handling ────────────────────────────────────────
// Authoritative values from in-game tooltips (verified by user 2026-05-23).
// Chain is NON-uniform (x8, x2, x4, x8, x8) -- don't assume otherwise.
const COIN_VALUES = {
    'numismatics:spur':     1,
    'numismatics:bevel':    8,
    'numismatics:sprocket': 16,
    'numismatics:cog':      64,
    'numismatics:crown':    512,
    'numismatics:sun':      4096,
};
const DENOMS_DESC = ['numismatics:sun', 'numismatics:crown', 'numismatics:cog',
                     'numismatics:sprocket', 'numismatics:bevel', 'numismatics:spur'];

function countCoins(player) {
    let total = 0;
    const inv = player.inventory;
    for (let i = 0; i < inv.containerSize; i++) {
        const stack = inv.getItem(i);
        const v = COIN_VALUES[stack.id];
        if (v) total += v * stack.count;
    }
    return total;
}

function takeCoins(player, amount) {
    let remaining = amount;
    const inv = player.inventory;
    // Pass 1: take from largest denomination downward (minimises change).
    for (const id of DENOMS_DESC) {
        const value = COIN_VALUES[id];
        if (remaining < value) continue;
        for (let i = 0; i < inv.containerSize && remaining >= value; i++) {
            const stack = inv.getItem(i);
            if (stack.id !== id) continue;
            const take = Math.min(stack.count, Math.floor(remaining / value));
            stack.shrink(take);
            remaining -= take * value;
        }
    }
    return amount - remaining;     // returns how many spurs actually taken
}

function giveCoins(player, amount) {
    // Give back as largest denomination possible to minimise inventory clutter.
    let remaining = amount;
    for (const id of DENOMS_DESC) {
        const value = COIN_VALUES[id];
        if (remaining < value) continue;
        const count = Math.floor(remaining / value);
        if (count > 0) {
            player.give(Item.of(id, count));
            remaining -= count * value;
        }
    }
}

// ─── Ownership storage (server.persistentData.bnsPlots compound) ──────
function getOwnerMap() {
    // Lazily ensure the compound exists. The compound is a plot_id -> uuid map.
    const root = Utils.server.persistentData;
    if (!root.contains('bnsPlots')) {
        root.put('bnsPlots', {});
    }
    return root.getCompound('bnsPlots');
}

function ownerOf(plotId) {
    const map = getOwnerMap();
    return map.contains(plotId) ? map.getString(plotId) : null;
}

function plotOwnedBy(playerUuid) {
    const map = getOwnerMap();
    for (const key of map.getAllKeys()) {
        if (map.getString(key) === playerUuid) return key;
    }
    return null;
}

function recordPurchase(plotId, playerUuid) {
    getOwnerMap().putString(plotId, playerUuid);
    console.info(`[bns/plots] purchase recorded: ${plotId} -> ${playerUuid}`);
}

function clearOwner(plotId) {
    getOwnerMap().remove(plotId);
    console.info(`[bns/plots] ownership cleared: ${plotId}`);
}

// ─── FTB Chunks integration (best-effort) ────────────────────────────
// FTB Chunks doesn't expose a KubeJS API for "claim chunk X for player Y",
// but its commands (/ftbchunks claim, /ftbchunks unclaim) work as
// console-as-player. Issue them with `execute as <player>` so they claim
// to that player's team.
function claimChunksForPlayer(plot, player) {
    for (const [cx, cz] of plot.chunks) {
        // /ftbchunks claim claims the chunk the executor stands in by default,
        // but accepts X Z args in most versions. Try with explicit coords first;
        // if that fails the syntax may need a `position` subcommand variant.
        const cmd = `execute as ${player.username} in ${plot.dimension} run ftbchunks claim ${cx} ${cz}`;
        Utils.server.runCommandSilent(cmd);
        console.info(`[bns/plots] claim: ${cmd}`);
    }
}

function unclaimChunksForPlayer(plot, player) {
    for (const [cx, cz] of plot.chunks) {
        const cmd = `execute as ${player.username} in ${plot.dimension} run ftbchunks unclaim ${cx} ${cz}`;
        Utils.server.runCommandSilent(cmd);
        console.info(`[bns/plots] unclaim: ${cmd}`);
    }
}

// ─── Helpers ──────────────────────────────────────────────────────────
function formatPlotLine(plot) {
    const owner = ownerOf(plot.id);
    const status = owner ? `OWNED (${owner.substring(0,8)}...)` : 'AVAILABLE';
    const cn = plot.chunks.length;
    return `${plot.id}  "${plot.name}"  ${plot.size}  ${cn} chunk${cn>1?'s':''}  ${plot.price} spurs  [${status}]`;
}

function sendMulti(source, lines) {
    for (const line of lines) source.sendSystemMessage(Text.of(line));
}

// ─── Command tree ─────────────────────────────────────────────────────
ServerEvents.commandRegistry(event => {
    const { commands: Commands, arguments: Arguments } = event;

    const plotIdArg = () => Commands.argument('id', Arguments.STRING.create(event));

    const tree = Commands.literal('bns')
        .then(Commands.literal('plot')

            // /bns plot list
            .then(Commands.literal('list').executes(ctx => {
                const lines = ['§7--- Spawn plots ---'];
                for (const plot of PLOT_DEFS.values()) {
                    lines.push(formatPlotLine(plot));
                }
                lines.push('§7Run §f/bns plot info <id>§7 for details or §f/bns plot buy <id>§7 to purchase.');
                sendMulti(ctx.source, lines);
                return 1;
            }))

            // /bns plot info <id>
            .then(Commands.literal('info').then(plotIdArg().executes(ctx => {
                const id = Arguments.STRING.getResult(ctx, 'id');
                const plot = PLOT_DEFS.get(id);
                if (!plot) {
                    ctx.source.sendSystemMessage(Text.red(`No such plot: ${id}`));
                    return 0;
                }
                const owner = ownerOf(id);
                const chunkStr = plot.chunks.map(([x,z]) => `(${x},${z})`).join(', ');
                sendMulti(ctx.source, [
                    `§6Plot ${id}: §f${plot.name}`,
                    `§7  Size:      §f${plot.size}  (${plot.chunks.length} chunk${plot.chunks.length>1?'s':''})`,
                    `§7  Chunks:    §f${chunkStr}`,
                    `§7  Dimension: §f${plot.dimension}`,
                    `§7  Price:     §f${plot.price} spurs`,
                    `§7  Owner:     §f${owner || 'AVAILABLE'}`,
                ]);
                return 1;
            })))

            // /bns plot buy <id>
            .then(Commands.literal('buy').then(plotIdArg().executes(ctx => {
                const player = ctx.source.player;
                if (!player) {
                    ctx.source.sendSystemMessage(Text.red('Run from a player context.'));
                    return 0;
                }
                const id = Arguments.STRING.getResult(ctx, 'id');
                const plot = PLOT_DEFS.get(id);
                if (!plot) {
                    player.tell(Text.red(`No such plot: ${id}`));
                    return 0;
                }
                if (ownerOf(id)) {
                    player.tell(Text.red(`Plot ${id} is already owned.`));
                    return 0;
                }
                const existing = plotOwnedBy(player.uuid.toString());
                if (existing) {
                    player.tell(Text.red(
                        `You already own plot ${existing}. Release it first with /bns plot release.`
                    ));
                    return 0;
                }
                const have = countCoins(player);
                if (have < plot.price) {
                    player.tell(Text.red(
                        `You need ${plot.price} spurs (have ${have}). Earn coins at the bank exchange.`
                    ));
                    return 0;
                }
                const took = takeCoins(player, plot.price);
                if (took < plot.price) {
                    // Shouldn't happen if countCoins agreed, but be safe.
                    giveCoins(player, took);
                    player.tell(Text.red(`Coin removal failed -- refunded. Try again.`));
                    return 0;
                }
                recordPurchase(id, player.uuid.toString());
                claimChunksForPlayer(plot, player);
                player.tell(Text.gold(
                    `Plot ${id} (${plot.name}) is now yours. Paid ${plot.price} spurs.`
                ));
                console.info(`[bns/plots] ${player.username} bought ${id} for ${plot.price} spurs`);
                return 1;
            })))

            // /bns plot release
            .then(Commands.literal('release').executes(ctx => {
                const player = ctx.source.player;
                if (!player) {
                    ctx.source.sendSystemMessage(Text.red('Run from a player context.'));
                    return 0;
                }
                const id = plotOwnedBy(player.uuid.toString());
                if (!id) {
                    player.tell(Text.red(`You don't own a plot.`));
                    return 0;
                }
                const plot = PLOT_DEFS.get(id);
                const refund = Math.floor(plot.price * REFUND_FRACTION);
                unclaimChunksForPlayer(plot, player);
                clearOwner(id);
                giveCoins(player, refund);
                player.tell(Text.gold(
                    `Released plot ${id}. Refunded ${refund} spurs (${Math.round(REFUND_FRACTION*100)}% of ${plot.price}).`
                ));
                console.info(`[bns/plots] ${player.username} released ${id}, refunded ${refund} spurs`);
                return 1;
            }))

            // /bns plot reload  (OP)
            .then(Commands.literal('reload').requires(src => src.hasPermission(2))
                .executes(ctx => {
                    // V1: PLOT_DEFS is hardcoded; reload is a no-op until V1.1
                    // (JSON-from-disk loading). Logged so admins know it ran.
                    ctx.source.sendSystemMessage(Text.gold(
                        `Plot defs are hardcoded in plots.js (V1). Edit the file + restart to change.`
                    ));
                    console.info(`[bns/plots] reload called (no-op in V1)`);
                    return 1;
                }))
        );

    event.register(tree);
    console.info('[bns/plots] commands registered: list, info, buy, release, reload');
});
