// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT
/****************************************************************************
 * Planar Vagabond's Guide to the Multiverse (planarvagabond.com)
 * Referee Tools
 *
 * Copyright 2023 Eric Woodward
 * Source released under MIT License
 * https://www.itsericwoodward.com/licenses/mit
 ****************************************************************************/

// TODO: Add quick buttons to roll specific dice and add them to the box, along with [X] to clear
// adding a dice looks for a matching die in the pattern and increments how many there are, or appends (if no match)
// so clicking "d6" 3x gives you "3d6" in box

// utilities
const getRandomValue = (max = 1, min = 1) =>
        Math.round(Math.random() * (max - min)) + min,
    rollDice = (sides = 1, count = 1) => {
        const vals = [...Array(count).keys()].map(() => getRandomValue(sides)),
            total = vals.reduce((v, a) => a + v, 0);
        return [total, ...vals];
    },
    getRandomValueFromArray = (array = []) => {
        if (!array.length) return '';
        const roll = getRandomValue(array.length);
        return `${array[roll - 1]} (${roll})`;
    },
    elementBuilder = (tag) => {
        const el = document.createElement(tag);
        return {
            // need to use `function` rather than `=>` to keep `this` intact
            addEventListener: function (type, fn) {
                el.addEventListener(type, fn);
                return this;
            },
            appendChild: function (child) {
                el.appendChild(child);
                return this;
            },
            appendToId: function (targetId) {
                const target = document.getElementById(targetId);
                if (target) target.append(el);
                return this;
            },
            getElement: function () {
                return el;
            },
            prependToId: function (targetId) {
                const target = document.getElementById(targetId);
                if (target) target.prepend(el);
                return this;
            },
            set: function (attr, val) {
                el[attr] = val;
                return this;
            },
            setInnerHTML: function (val) {
                el.innerHTML = val;
                return this;
            },
        };
    };

// Die Roller script
const addRollerForm = () => {
    const rollerForm = document.getElementById('js-rollerForm'),
        rollerInput = document.getElementById('js-rollerInput'),
        rollerOutput = document.getElementById('js-rollerOutput'),
        isExpressionRE =
            /(?:(?:^|[-+_*/])(?:\s*-?\d*d?\d+(\.\d+)?(?:[+-]\s*\d*d?\d+)?\s*))+$/i;
    if (!rollerForm) return;

    // double-click [x] to clear list
    rollerForm.addEventListener('reset', (e) => {
        if (rollerInput.value === '') rollerOutput.replaceChildren();
    });

    // do the roll(s) and show the (cleaned-up) result(s)
    rollerForm.addEventListener('submit', (e) => {
        e.preventDefault();
        if (!window.dice) return;

        const newEl = document.createElement('li'),
            // support multiple sets of dice
            rolls = rollerInput.value.split(/,\s*/);

        newEl.innerText = rolls
            .map((roll) => {
                const result = dice.roll(roll),
                    stringifiedResult = dice.stringify(result);
                return `${stringifiedResult.replaceAll(
                    '!!!mods listing not yet complete!!!',
                    ''
                )}${
                    // only show total if there's multiple dice thrown in a set
                    stringifiedResult.includes(',') || isExpressionRE.test(roll)
                        ? ` = ${+result}`
                        : ''
                }`;
            })
            .join(', ');
        rollerOutput.prepend(newEl);
    });

    // clear on escape key
    rollerForm.addEventListener('keydown', (event) => {
        if (event.key === 'Escape') rollerForm.reset();
    });
};

// Dungeon room populator
const RoomTypes = {
        EMPTY: 2,
        MONSTER: 4,
        SPECIAL: 5,
        TRAP: 6,
    },
    getRoomType = (room = 1) => {
        if (room <= RoomTypes.EMPTY) return 'Empty';
        if (room <= RoomTypes.MONSTER) return 'Monster';
        if (room <= RoomTypes.SPECIAL) return 'Special';
        return 'Trap';
    },
    checkForTreasure = (room = 1, treasure = 1) => {
        if (room <= RoomTypes.EMPTY) return treasure <= 1;
        if (room <= RoomTypes.MONSTER) return treasure <= 3;
        if (room <= RoomTypes.SPECIAL) return false;
        return treasure <= 2;
    },
    addRoomForm = () => {
        const roomForm = document.getElementById('js-roomForm'),
            roomOutput = document.getElementById('js-roomOutput');

        if (!roomForm) return;

        roomForm?.addEventListener('submit', (e) => {
            e.preventDefault();

            const newEl = document.createElement('li'),
                roomVal = getRandomValue(6),
                treasureVal = getRandomValue(6),
                roomType = getRoomType(roomVal),
                hasTreasure = checkForTreasure(roomVal, treasureVal);

            newEl.innerText = `${roomType}${
                hasTreasure ? ' (Treasure)' : ''
            } {${roomVal},${treasureVal}}`;
            roomOutput?.prepend(newEl);
        });
    };

// Complication Randomizer scripts
const shuffleContainer = (parentId) => {
        const container = document.getElementById(parentId),
            children = container.children,
            length = children.length,
            shuffled = [...children];

        for (let i = 0; i < length * length; i++)
            shuffled.sort(() => 0.5 - Math.random());
        for (let i = 0; i < length; i++) container.appendChild(shuffled[i]);
    },
    setContainerContents = (parentId, content = '') => {
        const container = document.getElementById(parentId);
        if (container?.innerHTML) container.innerHTML = content;
    },
    addComplicationForm = () => {
        const complicationForm = document.getElementById('js-complicationForm'),
            formControls = document.createElement('div');

        if (!complicationForm) return;

        formControls.innerHTML = [
            '<button id="js-btnSetDungeon">Dungeon</button>',
            '<button id="js-btnSetWilderness">Wilderness</button>',
            '<input type="submit" value="Randomize!" />',
        ].join('\n');

        complicationForm?.appendChild(formControls);

        complicationForm?.addEventListener('submit', (e) => {
            e.preventDefault();
            shuffleContainer('js-complicationList');
        });

        const setDungeonButton = document.getElementById('js-btnSetDungeon'),
            setWildernessButton = document.getElementById(
                'js-btnSetWilderness'
            );

        setDungeonButton?.addEventListener('click', (e) => {
            e.preventDefault();
            setContainerContents(
                'js-complicationList',
                [
                    '<li>Encounter</li>',
                    '<li>Signs / Portents</li>',
                    '<li>Locality</li>',
                    '<li>Exhaustion *</li>',
                    '<li>Light Source *</li>',
                    '<li>No Complications</li>',
                ].join('\n')
            );
        });

        setWildernessButton?.addEventListener('click', (e) => {
            e.preventDefault();
            (distanceText = `<strong>${
                distanceTotal * 10
            } yards</strong><br /><em>[${distanceRolls}]</em>`),
                setContainerContents(
                    'js-complicationList',
                    [
                        '<li>Encounter</li>',
                        '<li>Encounter *</li>',
                        '<li>Signs / Portents</li>',
                        '<li>Locality / Weather (2d4)</li>',
                        '<li>Lose Way (1d3 hrs)</li>',
                        '<li>No Complications</li>',
                    ].join('\n')
                );
        });
    },
    addAstralComplicationForm = () => {
        const complicationForm = document.getElementById(
            'js-astralComplicationForm'
        );
        if (!complicationForm) return;

        complicationForm?.addEventListener('submit', (e) => {
            e.preventDefault();
            shuffleContainer('js-astralComplicationList');
        });
    };

const reactionButtonVal = [
        'Attack!',
        'Hateful',
        'Leery',
        'Rude',
        'Aloof',
        'Uncertain',
        'Confused',
        'Indifferent',
        'Cordial',
        'Amiable',
        'Friendly!',
    ],
    rollDistance = (type = 'W', hasDoubles) => {
        // roll 2d6 for starting dungeon distance
        if (type !== 'W') return rollDice(6, 2);

        // roll 4d6 (or 1d4, if any doubles above) for starting wilderness distance
        return hasDoubles ? rollDice(4) : rollDice(6, 4);
    },
    rollEncounter = (sides = 1, type = 'W') => {
        type = `${type}`.toUpperCase();
        // roll 3d8 for monsters, note doubles (first 2 / last 2 / first & last) / triples
        const [monsterTotal, ...monsterRolls] = rollDice(sides, 3),
            hasStealthParty =
                monsterRolls.length > 1 && monsterRolls[0] === monsterRolls[1],
            hasStealthMonster =
                monsterRolls.length > 2 && monsterRolls[1] === monsterRolls[2],
            hasDoubleSurprise = hasStealthParty && hasStealthMonster,
            hasDoubles = hasStealthParty || hasStealthMonster,
            // roll 2d6 for reaction, note string & numeric value
            [reactionTotal, ...reactionRolls] = rollDice(6, 2),
            // roll for distance, see above
            [distanceTotal, ...distanceRolls] = rollDistance(type, hasDoubles),
            // Derived string and scores
            reactionText = `<strong>${
                reactionButtonVal[reactionTotal - 2]
            } (${reactionTotal})</strong><br /><em>[${reactionRolls}]</em>`,
            distanceText = [
                `<strong>${distanceTotal * 10}`,
                type === 'W' ? ' yards' : "'",
                '</strong>',
                `<br /><em>[${distanceRolls}]</em>`,
            ].join(''),
            encounterOutput = document.getElementById('js-encounterOutput'),
            outputTable = document.createElement('table');

        // additional text denoting surprise results.
        let surpriseText = '';
        if (hasDoubleSurprise) surpriseText = 'Double Surprise';
        else if (hasStealthMonster) surpriseText = 'Monster has stealth';
        else if (hasStealthParty) surpriseText = 'Party has stealth';
        if (surpriseText) surpriseText = `<br />${surpriseText}!`;

        // combine monster roll total + surprise + roll data
        const monsterRollText = `<strong>${monsterTotal}${surpriseText}</strong><br /><em>[${monsterRolls}]</em>`;
        outputTable.innerHTML = `
            <thead>
                <th colspan="2">
                    ${type === 'W' ? 'Wilderness' : 'Dungeon'} Encounter<br />
                    ${dayjs().format('YYYY-MM-DD HH:mm:ss')}
                </th>
            </thead>
            <tbody>
                <tr>
                    <th>Monster</th><td>${monsterRollText}</td>
                </tr>
                <tr>
                    <th>Distance</th><td>${distanceText}</td>
                </tr>
                <tr>
                    <th>Reaction</th><td>${reactionText}</td>
                </tr>
            </tbody>
        `;
        outputTable.classList.add('encounterResultTable');

        encounterOutput.prepend(outputTable);
    },
    addEncounterRoller = () => {
        const complicationForm = document.getElementById('js-encounterForm'),
            formControls = document.createElement('div');
        if (!complicationForm) return;

        formControls.innerHTML = `
            <div class="encounterButtonsWrapper">
                <div class="encounterButtonsDungeonWrapper">
                    <em>Dungeon</em><br />
                    <button id="js-btnRollDungeonEncounter3d4">3d4</button>
                    <button id="js-btnRollDungeonEncounter3d6">3d6</button>
                    <button id="js-btnRollDungeonEncounter3d8">3d8</button>
                </div>
                <div class="encounterButtonsWildernessWrapper">
                    <em>Wilderness</em><br />
                    <button id="js-btnRollWildernessEncounter3d4">3d4</button>
                    <button id="js-btnRollWildernessEncounter3d6">3d6</button>
                    <button id="js-btnRollWildernessEncounter3d8">3d8</button>
                </div>
            </div>
        `;

        complicationForm?.appendChild(formControls);

        const rollDungeonEncounter3d4Button = document.getElementById(
                'js-btnRollDungeonEncounter3d4'
            ),
            rollDungeonEncounter3d6Button = document.getElementById(
                'js-btnRollDungeonEncounter3d6'
            ),
            rollDungeonEncounter3d8Button = document.getElementById(
                'js-btnRollDungeonEncounter3d8'
            ),
            rollWildernessEncounter3d4Button = document.getElementById(
                'js-btnRollWildernessEncounter3d4'
            ),
            rollWildernessEncounter3d6Button = document.getElementById(
                'js-btnRollWildernessEncounter3d6'
            ),
            rollWildernessEncounter3d8Button = document.getElementById(
                'js-btnRollWildernessEncounter3d8'
            );

        if (rollDungeonEncounter3d4Button)
            rollDungeonEncounter3d4Button.addEventListener('click', (e) => {
                e.preventDefault();
                rollEncounter(4, 'D');
            });
        if (rollDungeonEncounter3d6Button)
            rollDungeonEncounter3d6Button.addEventListener('click', (e) => {
                e.preventDefault();
                rollEncounter(6, 'D');
            });
        if (rollDungeonEncounter3d8Button)
            rollDungeonEncounter3d8Button.addEventListener('click', (e) => {
                e.preventDefault();
                rollEncounter(8, 'D');
            });
        if (rollWildernessEncounter3d4Button)
            rollWildernessEncounter3d4Button.addEventListener('click', (e) => {
                e.preventDefault();
                rollEncounter(4, 'W');
            });
        if (rollWildernessEncounter3d6Button)
            rollWildernessEncounter3d6Button.addEventListener('click', (e) => {
                e.preventDefault();
                rollEncounter(6, 'W');
            });
        if (rollWildernessEncounter3d8Button)
            rollWildernessEncounter3d8Button.addEventListener('click', (e) => {
                e.preventDefault();
                rollEncounter(8, 'W');
            });
    };

// Magic Item Concept Roller
// Based on http://recedingrules.blogspot.com/2010/03/spell-like-effect-spur.html

const modeOpts = ['Wielder', 'Touch', 'Distance', 'Area Effect'],
    intentOpts = ['Attack', 'Defense', 'Utility', 'Transport', 2, 3],
    rangeOpts = [10, 20, 30, 40, 50, 60, 70, 'Sight'],
    durationOpts = [
        'Instant',
        '1 round',
        '1d6 rounds',
        '1 turn',
        '1d6 turns',
        '1 hour',
        '1d6 hours',
        '1 day',
        '1d6 days',
        'Permanent',
    ],
    effectOpts = [
        'Alter',
        'Animate',
        'Charm / Compel',
        'Conjure',
        'Delude',
        'Dispel / Disappear',
        'Distort',
        'Divine',
        'Evoke',
        'Shield',
        'Summon',
        'Transmute',
    ],
    mediumOpts = [
        'Animal',
        'Vegetable',
        'Mineral',
        'Metal',
        'Fire',
        'Earth',
        'Water',
        'Air',
        'Law',
        'Chaos',
        'Shadow',
        'Light',
        'Sound',
        'Dead',
        'Infernal',
        'Time',
        'Space / Dimension',
        'Human',
        'Demihuman',
        'Humanoid',
        'Monster',
        'Terrain',
    ],
    getIntent = () => {
        const roll = getRandomValue(intentOpts.length),
            idx = roll - 1;
        if (typeof intentOpts[idx] === 'number') {
            const [total, ...rolls] = rollDice(4, intentOpts[idx]);
            return `${rolls
                .map((r) => `${intentOpts[r - 1]} (${r})`)
                .join(' / ')}`;
        }

        return `${intentOpts[idx]} (${roll})`;
    },
    getRange = () => {
        const roll = getRandomValue(rangeOpts.length),
            idx = roll - 1;

        if (typeof rangeOpts[idx] === 'number') {
            const val = rangeOpts[idx];
            return `${val}' / ${val * 2}' / ${val * 3}' (${roll})`;
        }

        return `${rangeOpts[idx]} (${roll})`;
    },
    getResultList = () => {
        return `
            Magic Item Results
            <em>(${dayjs().format('YYYY-MM-DD HH:mm:ss')})</em>
            <ul>
                <li>
                    Mode:
                    <strong>${getRandomValueFromArray(modeOpts)}</strong>
                </li>
                <li>
                    Intent: 
                    <strong>${getIntent()}</strong>
                </li>
                <li>
                    Range: 
                    <strong>${getRange()}</strong>
                </li>
                <li>
                    Duration: 
                    <strong>${getRandomValueFromArray(durationOpts)}</strong>
                </li>
                <li>
                    Effect: 
                    <strong>${getRandomValueFromArray(effectOpts)}</strong>
                </li>
                <li>
                    Medium: 
                    <strong>${getRandomValueFromArray(mediumOpts)}</strong>
                </li>
            </ul>                
        `;
    },
    addMagicItemConceptRoller = () => {
        const magicItemConceptForm = document.getElementById(
            'js-magicItemConcept'
        );
        if (!magicItemConceptForm) return;

        magicItemConceptForm?.appendChild(
            elementBuilder('input')
                .set('type', 'submit')
                .set('value', 'Roll!')
                .getElement()
        );

        magicItemConceptForm?.addEventListener('submit', (e) => {
            e.preventDefault();
            elementBuilder('li')
                .setInnerHTML(getResultList())
                .prependToId('js-magicItemConcept-output');
        });
    };

export default (() => {
    addRollerForm();
    addRoomForm();
    addComplicationForm();
    addAstralComplicationForm();
    addEncounterRoller();
    addMagicItemConceptRoller();
})();

// @license-end
