<template>
    <BaseLayout hide-search>
        <main class="content">
            <div class="container-fluid p-0">
                <div class="card">
                    <div class="card-header">Decoded</div>
                    <div class="card-body">
                        <ul>
                            <li v-for="(value, index) in decoded" :key="index">{{ value }}</li>
                        </ul>
                    </div>
                </div>
                <div class="card">
                    <div class="card-header">Deserialized</div>
                    <div class="card-body">
                        <p>kind: {{ deserialized.kind }}</p>
                        <ul>
                            <li v-for="(value, name) in fields" :key="name">{{ name }}: {{ value }}</li>
                        </ul>
                    </div>
                </div>
                <div class="card">
                    <div class="card-header">Expanded</div>
                    <div class="card-body">
                        <router-link v-if="expandedRoute" :to="expandedRoute">{{ expandedRoute }}</router-link>
                        <span v-else-if="error" class="text-danger">{{ error }}</span>
                        <span v-else>No page exists for kind '{{ deserialized.kind }}'</span>
                    </div>
                </div>
                <div class="card">
                    <div class="card-header">Examples</div>
                    <table class="table table-striped mb-0">
                        <thead>
                        <tr>
                            <th>Kind</th>
                            <th>Fields</th>
                            <th>Serialized</th>
                            <th>Bits</th>
                            <th>Token</th>
                        </tr>
                        </thead>
                        <tbody>
                        <tr v-for="example in examples" :key="example.token">
                            <td>{{ example.kind }}</td>
                            <td>
                                <span v-for="(value, name) in example.fields" :key="name" class="me-2">
                                    {{ name }}: {{ value }}
                                </span>
                            </td>
                            <td>[{{ example.ints.join(', ') }}]</td>
                            <td><code>{{ example.bits }}</code></td>
                            <td>
                                <router-link :to="'/' + example.token">{{ example.token }}</router-link>
                            </td>
                        </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </main>
    </BaseLayout>
</template>

<script>
import {mapActions} from 'vuex';
import BaseLayout from '@/components/BaseLayout.vue';
import {decodeShortId, deserializeShortId, encodeShortId, serializeShortId} from '@/short-id';
import {expandedRoute as buildExpandedRoute, NEEDS_IDMAP} from '@/router';

const EXAMPLES = [
    {kind: 'item', owner_identity_id: 7, item_local_id: 42},
    {kind: 'group', group_id: 11},
    {kind: 'group_item', owner_group_id: 5, item_local_id: 42},
    {kind: 'file', file_id: 123},
    {kind: 'storage_location', owner_identity_id: 3, storage_location_id: 1000},
    {kind: 'category', category_id: 5},
    {kind: 'workflow', owner_identity_id: 2, workflow_id: 9},
];

// Debug-only mirror of short-id.js's bit-packing (2-bit kind tag + 4-bit chunks, see docs/handles-and-shortids.md), recomputed from `ints` for display only.
const KIND_TAG_BITS = 2;
const CHUNK_BITS = 4;
const DIRECT_KIND_COUNT = 2 ** KIND_TAG_BITS - 1;

function chunkedIntBits(value, chunkBits) {
    value = BigInt(value);
    const mask = (1n << BigInt(chunkBits)) - 1n;
    const chunks = [];
    let remaining = value;
    do {
        chunks.unshift(remaining & mask);
        remaining >>= BigInt(chunkBits);
    } while (remaining > 0n);
    return chunks.map((chunk, i) => {
        const continuation = i === chunks.length - 1 ? '0' : '1';
        return continuation + chunk.toString(2).padStart(chunkBits, '0');
    }).join('');
}

function shortIdBitSegments(ints) {
    const [kindIndex, ...fieldValues] = ints;
    const segments = [];
    if (kindIndex < DIRECT_KIND_COUNT) {
        segments.push(kindIndex.toString(2).padStart(KIND_TAG_BITS, '0'));
    } else {
        segments.push('1'.repeat(KIND_TAG_BITS));
        segments.push(chunkedIntBits(kindIndex - DIRECT_KIND_COUNT, CHUNK_BITS));
    }
    fieldValues.forEach(value => segments.push(chunkedIntBits(value, CHUNK_BITS)));

    const totalBits = segments.join('').length;
    const paddingBits = (6 - (totalBits % 6)) % 6;
    if (paddingBits > 0) {
        segments.push('0'.repeat(paddingBits));
    }
    return segments.join(' ');
}

export default {
    name: 'ShortId',
    components: {
        BaseLayout
    },
    props: {
        short_id: {
            type: String,
            required: true
        }
    },
    data() {
        return {
            error: null
        }
    },
    computed: {
        decoded() {
            return decodeShortId(this.short_id);
        },
        deserialized() {
            return deserializeShortId(this.decoded);
        },
        fields() {
            const {kind, ...fields} = this.deserialized;
            return fields;
        },
        expandedRoute() {
            return buildExpandedRoute(this.deserialized);
        },
        examples() {
            return EXAMPLES.map(named => {
                const {kind, ...fields} = named;
                const ints = serializeShortId(named);
                const token = encodeShortId(ints);
                return {kind, fields, ints, token, bits: shortIdBitSegments(ints)};
            });
        }
    },
    watch: {
        // expandedRoute re-evaluates once fetchIdMap resolves (see buildExpandedRoute in router.js); finishes the redirect the router's synchronous guard couldn't.
        expandedRoute(url) {
            if (url) {
                this.$router.replace(url);
            }
        }
    },
    methods: {
        ...mapActions(['fetchIdMap'])
    },
    mounted() {
        if (NEEDS_IDMAP.has(this.deserialized.kind) && !this.$store.state.idmapLoaded) {
            this.fetchIdMap().catch(e => {
                this.error = e.message;
            });
        }
    }
}
</script>
