// Brass and Sigil -- bounty engine V1 (command-driven)
//
// Per docs/economy-system.md §4-5:
//   Each player has a per-day pool of available bounties drawn randomly
//   from BOUNTY_POOL. They can accept up to TIER.dailyBountySlots active
//   at once. Completing the objective marks the bounty READY -- they
//   then turn it in to claim the spur reward. Cancellation applies the
//   same cooldown as completion (no RNG shopping).
//
// V1 turn-in is a command. When the bnstoolkit mod ships, the Adventurer's
// Guild NPC will be the turn-in surface and this command becomes a
// fallback/debug path.
//
// Player commands:
//   /bns bounty list       -- today's available bounties
//   /bns bounty active     -- your accepted bounties
//   /bns bounty accept <id>
//   /bns bounty cancel <id>
//   /bns bounty turnin <id>
//   /bns bounty completed  -- your completed bounties + cooldowns
//
// Bounty state per player (in player.persistentData):
//   bnsBountyPool        compound { id -> 1 }  -- today's available 5
//   bnsBountyPoolRefresh long  -- epoch ms of last roll
//   bnsBountyActive      compound { id -> { progress, target, reward, source, ready? } }
//   bnsBountyCompleted   compound { id -> cooldown_until_ms }

const BOUNTY_POOL_SIZE = 5;
const BOUNTY_REFRESH_MS = 24 * 60 * 60 * 1000; // 24h
const BOUNTY_COOLDOWN_MS = 24 * 60 * 60 * 1000; // 24h cooldown after complete or cancel

// ─── Bounty catalogue ───────────────────────────────────────────────
// Each bounty: { id, name, target, count, type, reward, source }
//   target = entity id ('minecraft:zombie') OR resource for non-kill bounties
//   type   = 'kill' (V1 only supports kill bounties)
//   reward = spurs paid on turn-in
//   source = guild name (for flavour in messages)
const BOUNTY_POOL = [
    // ── Easy: novice-tier mobs ──
    { id: 'b_zombie_10',   name: 'Slay 10 Zombies',        target: 'minecraft:zombie',        count: 10, type: 'kill', reward: 15,  source: "Adventurer's Guild" },
    { id: 'b_skeleton_10', name: 'Slay 10 Skeletons',      target: 'minecraft:skeleton',      count: 10, type: 'kill', reward: 15,  source: "Adventurer's Guild" },
    { id: 'b_spider_8',    name: 'Slay 8 Spiders',         target: 'minecraft:spider',        count: 8,  type: 'kill', reward: 12,  source: "Adventurer's Guild" },
    { id: 'b_creeper_5',   name: 'Slay 5 Creepers',        target: 'minecraft:creeper',       count: 5,  type: 'kill', reward: 20,  source: "Adventurer's Guild" },
    { id: 'b_drowned_8',   name: 'Slay 8 Drowned',         target: 'minecraft:drowned',       count: 8,  type: 'kill', reward: 18,  source: "Adventurer's Guild" },
    { id: 'b_husk_8',      name: 'Slay 8 Husks',           target: 'minecraft:husk',          count: 8,  type: 'kill', reward: 18,  source: "Adventurer's Guild" },

    // ── Medium: nether basic ──
    { id: 'b_blaze_5',     name: 'Slay 5 Blazes',          target: 'minecraft:blaze',         count: 5,  type: 'kill', reward: 50,  source: "Adventurer's Guild" },
    { id: 'b_piglin_8',    name: 'Slay 8 Piglins',         target: 'minecraft:piglin',        count: 8,  type: 'kill', reward: 40,  source: "Adventurer's Guild" },
    { id: 'b_magma_6',     name: 'Slay 6 Magma Cubes',     target: 'minecraft:magma_cube',    count: 6,  type: 'kill', reward: 30,  source: "Adventurer's Guild" },
    { id: 'b_ghast_3',     name: 'Slay 3 Ghasts',          target: 'minecraft:ghast',         count: 3,  type: 'kill', reward: 60,  source: "Adventurer's Guild" },

    // ── Medium: modded ─
    { id: 'b_plunderer_5', name: 'Hunt 5 Plunderers',      target: 'supplementaries:plunderer', count: 5, type: 'kill', reward: 75,  source: "Adventurer's Guild" },

    // ── Hard: nether elites + end ──
    { id: 'b_witch_3',     name: 'Slay 3 Witches',         target: 'minecraft:witch',         count: 3,  type: 'kill', reward: 80,  source: "Adventurer's Guild" },
    { id: 'b_enderman_5',  name: 'Slay 5 Endermen',        target: 'minecraft:enderman',      count: 5,  type: 'kill', reward: 90,  source: "Adventurer's Guild" },
    { id: 'b_wither_skel_3', name: 'Slay 3 Wither Skeletons', target: 'minecraft:wither_skeleton', count: 3, type: 'kill', reward: 150, source: "Adventurer's Guild" },
    { id: 'b_shulker_3',   name: 'Slay 3 Shulkers',        target: 'minecraft:shulker',       count: 3,  type: 'kill', reward: 200, source: "Adventurer's Guild" },

    // ── Boss tier (rare picks) ──
    { id: 'b_warden_1',    name: 'Slay the Warden',        target: 'minecraft:warden',        count: 1,  type: 'kill', reward: 800, source: "Adventurer's Guild" },
    { id: 'b_voidworm_1',  name: 'Destroy a Void Worm',    target: 'alexsmobs:void_worm',     count: 1,  type: 'kill', reward: 1200, source: "Adventurer's Guild" },
    { id: 'b_warped_mosco_1', name: 'Slay a Warped Mosco', target: 'alexsmobs:warped_mosco',  count: 1,  type: 'kill', reward: 700, source: "Adventurer's Guild" },
    { id: 'b_farseer_1',   name: 'Slay the Farseer',       target: 'alexsmobs:farseer',       count: 1,  type: 'kill', reward: 900, source: "Adventurer's Guild" },
    { id: 'b_skreecher_1', name: 'Slay a Skreecher',       target: 'alexsmobs:skreecher',     count: 1,  type: 'kill', reward: 400, source: "Adventurer's Guild" },
];

// Index pool by id for O(1) lookup
const BOUNTY_INDEX = {};
for (const b of BOUNTY_POOL) BOUNTY_INDEX[b.id] = b;

// ─── Player tier → slot cap ─────────────────────────────────────────
const TIER_BOUNTY_SLOTS = [0, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 10];

function playerBountySlotCap(player) {
    const data = player.persistentData;
    const tier = data.contains('bnsTier') ? data.getInt('bnsTier') : 1;
    const safe = (tier >= 1 && tier <= 13) ? tier : 1;
    return TIER_BOUNTY_SLOTS[safe];
}

// ─── Compound storage helpers ───────────────────────────────────────
function getActiveMap(player) {
    const d = player.persistentData;
    if (!d.contains('bnsBountyActive')) d.put('bnsBountyActive', {});
    return d.getCompound('bnsBountyActive');
}
function getCompletedMap(player) {
    const d = player.persistentData;
    if (!d.contains('bnsBountyCompleted')) d.put('bnsBountyCompleted', {});
    return d.getCompound('bnsBountyCompleted');
}
function getPoolList(player) {
    const d = player.persistentData;
    if (!d.contains('bnsBountyPool')) d.put('bnsBountyPool', {});
    return d.getCompound('bnsBountyPool');
}

// ─── Daily pool refresh ─────────────────────────────────────────────
function nowMs() { return Date.now(); }

function rollDailyPool(player) {
    const eligible = BOUNTY_POOL.slice(); // shallow copy
    // Filter out bounties still on cooldown
    const completed = getCompletedMap(player);
    const now = nowMs();
    const available = eligible.filter(b => {
        if (!completed.contains(b.id)) return true;
        return completed.getLong(b.id) <= now;
    });
    // Shuffle and pick top BOUNTY_POOL_SIZE
    for (let i = available.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        const t = available[i]; available[i] = available[j]; available[j] = t;
    }
    const picked = available.slice(0, BOUNTY_POOL_SIZE);
    const pool = getPoolList(player);
    // Clear old pool entries
    for (const k of pool.getAllKeys()) pool.remove(k);
    for (const b of picked) pool.putInt(b.id, 1);
    player.persistentData.putLong('bnsBountyPoolRefresh', now);
    console.info(`[bns/bounty] rolled pool for ${player.username}: ${picked.map(b=>b.id).join(', ')}`);
}

function ensurePoolFresh(player) {
    const d = player.persistentData;
    const lastRoll = d.contains('bnsBountyPoolRefresh') ? d.getLong('bnsBountyPoolRefresh') : 0;
    if (nowMs() - lastRoll > BOUNTY_REFRESH_MS) {
        rollDailyPool(player);
    }
}

// ─── Active-bounty management ───────────────────────────────────────
function activeBountyCount(player) {
    return getActiveMap(player).getAllKeys().size();
}

function addActiveBounty(player, bountyDef) {
    const map = getActiveMap(player);
    const entry = {};
    entry['progress'] = 0;
    entry['target']   = bountyDef.count;
    entry['reward']   = bountyDef.reward;
    entry['source']   = bountyDef.source;
    entry['name']     = bountyDef.name;
    entry['ready']    = false;
    entry['targetId'] = bountyDef.target;
    entry['type']     = bountyDef.type;
    map.put(bountyDef.id, entry);
}

function removeActiveBounty(player, bountyId) {
    getActiveMap(player).remove(bountyId);
}

function markCooldown(player, bountyId) {
    getCompletedMap(player).putLong(bountyId, nowMs() + BOUNTY_COOLDOWN_MS);
}

// ─── Progress hook: increment matching active bounties on kill ──────
EntityEvents.death(event => {
    const src = event.source.player;
    if (!src) return;
    const player = src;
    const killedId = event.entity.type;

    const active = getActiveMap(player);
    let updated = false;
    for (const id of active.getAllKeys()) {
        const entry = active.getCompound(id);
        if (entry.getString('type') !== 'kill') continue;
        if (entry.getString('targetId') !== killedId) continue;
        if (entry.getBoolean('ready')) continue;
        const newProgress = entry.getInt('progress') + 1;
        entry.putInt('progress', newProgress);
        if (newProgress >= entry.getInt('target')) {
            entry.putBoolean('ready', true);
            player.tell(Text.gold(`Bounty READY: ${entry.getString('name')}. Turn in with /bns bounty turnin ${id}`));
        } else {
            // Light progress notification every few kills to not spam
            if (newProgress === 1 || newProgress === entry.getInt('target') - 1 || newProgress % 5 === 0) {
                player.tell(Text.gray(`Bounty progress: ${entry.getString('name')} — ${newProgress}/${entry.getInt('target')}`));
            }
        }
        updated = true;
    }
});

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

    const bountyCmd = Commands.literal('bns')
        .then(Commands.literal('bounty')

            // /bns bounty list
            .then(Commands.literal('list').executes(ctx => {
                const player = ctx.source.player;
                if (!player) { ctx.source.sendSystemMessage(Text.red('Run from a player context.')); return 0; }
                ensurePoolFresh(player);
                const pool = getPoolList(player);
                const cap = playerBountySlotCap(player);
                const active = activeBountyCount(player);
                player.tell(Text.aqua(`--- Today's bounties (active: ${active}/${cap}) ---`));
                if (pool.getAllKeys().size() === 0) {
                    player.tell(Text.gray('  (No bounties currently available — check back in 24h)'));
                    return 1;
                }
                for (const id of pool.getAllKeys()) {
                    const b = BOUNTY_INDEX[id];
                    if (!b) continue;
                    const accepted = getActiveMap(player).contains(id);
                    const tag = accepted ? '§a[ACCEPTED]§7' : '§7';
                    player.tell(Text.gray(`  ${tag} ${id}  "${b.name}"  reward: ${b.reward} spurs`));
                }
                player.tell(Text.gray('Accept: /bns bounty accept <id>'));
                return 1;
            }))

            // /bns bounty active
            .then(Commands.literal('active').executes(ctx => {
                const player = ctx.source.player;
                if (!player) { ctx.source.sendSystemMessage(Text.red('Run from a player context.')); return 0; }
                const active = getActiveMap(player);
                const cap = playerBountySlotCap(player);
                player.tell(Text.aqua(`--- Active bounties (${active.getAllKeys().size()}/${cap}) ---`));
                if (active.getAllKeys().size() === 0) {
                    player.tell(Text.gray('  (No active bounties — accept some with /bns bounty list)'));
                    return 1;
                }
                for (const id of active.getAllKeys()) {
                    const e = active.getCompound(id);
                    const flag = e.getBoolean('ready') ? '§e[READY]§7' : '§7';
                    player.tell(Text.gray(
                        `  ${flag} ${id}  "${e.getString('name')}"  ${e.getInt('progress')}/${e.getInt('target')}  reward: ${e.getInt('reward')} spurs`
                    ));
                }
                player.tell(Text.gray('Turn in: /bns bounty turnin <id>   Cancel: /bns bounty cancel <id>'));
                return 1;
            }))

            // /bns bounty accept <id>
            .then(Commands.literal('accept').then(Commands.argument('id', Arguments.STRING.create(event))
                .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');
                    ensurePoolFresh(player);
                    const pool = getPoolList(player);
                    if (!pool.contains(id)) {
                        player.tell(Text.red(`Bounty ${id} isn't in your pool today. Use /bns bounty list.`));
                        return 0;
                    }
                    if (getActiveMap(player).contains(id)) {
                        player.tell(Text.red(`You've already accepted ${id}.`));
                        return 0;
                    }
                    const cap = playerBountySlotCap(player);
                    if (activeBountyCount(player) >= cap) {
                        player.tell(Text.red(`You're at your bounty cap (${cap}). Turn in or cancel one first.`));
                        return 0;
                    }
                    const b = BOUNTY_INDEX[id];
                    addActiveBounty(player, b);
                    player.tell(Text.gold(`Accepted: ${b.name}. Reward: ${b.reward} spurs on turn-in.`));
                    console.info(`[bns/bounty] ${player.username} accepted ${id}`);
                    return 1;
                })))

            // /bns bounty cancel <id>
            .then(Commands.literal('cancel').then(Commands.argument('id', Arguments.STRING.create(event))
                .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 active = getActiveMap(player);
                    if (!active.contains(id)) {
                        player.tell(Text.red(`You haven't accepted ${id}.`));
                        return 0;
                    }
                    removeActiveBounty(player, id);
                    markCooldown(player, id);
                    player.tell(Text.gold(`Cancelled ${id}. Cooldown applied (24h) — quest will return to your pool tomorrow.`));
                    console.info(`[bns/bounty] ${player.username} cancelled ${id}`);
                    return 1;
                })))

            // /bns bounty turnin <id>
            .then(Commands.literal('turnin').then(Commands.argument('id', Arguments.STRING.create(event))
                .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 active = getActiveMap(player);
                    if (!active.contains(id)) {
                        player.tell(Text.red(`You haven't accepted ${id}.`));
                        return 0;
                    }
                    const entry = active.getCompound(id);
                    if (!entry.getBoolean('ready')) {
                        player.tell(Text.red(
                            `${entry.getString('name')} isn't complete yet (${entry.getInt('progress')}/${entry.getInt('target')}).`
                        ));
                        return 0;
                    }
                    const reward = entry.getInt('reward');
                    removeActiveBounty(player, id);
                    markCooldown(player, id);
                    giveCoins(player, reward);  // shared helper from plots.js
                    player.tell(Text.gold(`Turn-in: ${entry.getString('name')} — paid ${reward} spurs.`));
                    console.info(`[bns/bounty] ${player.username} turned in ${id} for ${reward} spurs`);
                    return 1;
                })))

            // /bns bounty completed
            .then(Commands.literal('completed').executes(ctx => {
                const player = ctx.source.player;
                if (!player) { ctx.source.sendSystemMessage(Text.red('Run from a player context.')); return 0; }
                const completed = getCompletedMap(player);
                const now = nowMs();
                player.tell(Text.aqua(`--- Completed / cooldown bounties ---`));
                if (completed.getAllKeys().size() === 0) {
                    player.tell(Text.gray('  (None yet)'));
                    return 1;
                }
                for (const id of completed.getAllKeys()) {
                    const until = completed.getLong(id);
                    const b = BOUNTY_INDEX[id];
                    const name = b ? b.name : id;
                    if (until <= now) {
                        player.tell(Text.gray(`  ${id}  "${name}"  cooldown expired`));
                    } else {
                        const hrs = Math.floor((until - now) / 3600000);
                        const min = Math.floor(((until - now) % 3600000) / 60000);
                        player.tell(Text.gray(`  ${id}  "${name}"  cooldown ${hrs}h ${min}m`));
                    }
                }
                return 1;
            }))
        );

    event.register(bountyCmd);
});

console.info('[bns/bounty_engine] loaded — ' + BOUNTY_POOL.length + ' bounties in catalogue');
