<script lang="ts">
import config from '#shared/character-config.json';
function raceOptionToText(option: RaceOption): string
{
    const text = [];
    if(option.training) text.push(`+${option.training} point${option.training > 1 ? 's' : ''} de statistique${option.training > 1 ? 's' : ''}.`);
    if(option.spec) text.push(`+${option.spec} spécialisation${option.spec > 1 ? 's' : ''}.`);
    if(option.shaping) text.push(`+${option.shaping} transformation${option.shaping > 1 ? 's' : ''} par jour.`);
    if(option.modifier) text.push(`+${option.modifier} au modifieur de votre choix.`);
    if(option.health) text.push(`+${option.health} PV max.`);
    if(option.mana) text.push(`+${option.mana} mana max.`);
    return text.join('\n');
}
function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]): TrainingOption[]
{
    const characterData = config as CharacterConfig;
    return progression.map(e => characterData.training[stat][e[0]][e[1]]);
}

const mainStatTexts: Record<MainStat, string> = {
    "strength": "Force",
    "dexterity": "Dextérité",
    "constitution": "Constitution",
    "intelligence": "Intelligence",
    "curiosity": "Curiosité",
    "charisma": "Charisme",
    "psyche": "Psyché",
}

function abilitySpecialFeatures(type: "points" | "max", curiosity: DoubleIndex<TrainingLevel>[], value: number): number
{
    if(type === 'points')
    {
        if(curiosity.find(e => e[0] == 7 && e[1] === 0))
            return Math.max(6, value);
        if(curiosity.find(e => e[0] == 7 && e[1] === 2))
            return value + 1;
    }
    return value;
}
</script>
<script setup lang="ts">
import { Icon } from '@iconify/vue/dist/iconify.js';
import PreviewA from '~/components/prose/PreviewA.vue';
import { clamp } from '~/shared/general.util';
import type { Ability, Character, CharacterConfig, DoubleIndex, Level, MainStat, RaceOption, TrainingLevel, TrainingOption } from '~/types/character';

definePageMeta({
    guestsGoesTo: '/user/login',
});
let id = useRouter().currentRoute.value.params.id;
const { add } = useToast();
const characterConfig = config as CharacterConfig;
const data = ref<Character>({
    id: -1,
    name: '',
    progress: {
        training: {
            strength: [[1, 0], [2, 0], [3, 0], [4, 0]],
            dexterity: [[1, 0], [2, 0], [3, 0], [4, 0]],
            constitution: [[1, 0], [2, 0], [3, 0], [4, 0]],
            intelligence: [[1, 0], [2, 0], [3, 0], [4, 0]],
            curiosity: [[1, 0], [2, 0], [3, 0], [4, 0]],
            charisma: [[1, 0], [2, 0], [3, 0], [4, 0]],
            psyche: [[1, 0], [2, 0], [3, 0], [4, 0]],
        },
        level: 1,
        race: {
            index: undefined,
            progress: [[1, 0]],
        },
        abilities: {},
        modifiers: {},
        notes: "",
    },
});

const peopleOpen = ref(false), trainingOpen = ref(false), notesOpen = ref(false), abilityOpen = ref(false), trainingTab = ref(0);
const raceOptions = computed(() => data.value.progress.race.index !== undefined ? characterConfig.peoples[data.value.progress.race.index!].options : undefined);
const selectedRaceOptions = computed(() => raceOptions !== undefined ? data.value.progress.race.progress!.map(e => raceOptions.value![e[0]][e[1]]) : undefined);
const trainingPoints = computed(() => raceOptions.value ? data.value.progress.race.progress?.reduce((p, v) => p + (raceOptions.value![v[0]][v[1]].training ?? 0), 0) : 0);
const training = computed(() => Object.entries(characterConfig.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, data.value.progress.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][]);
const maxTraining = computed(() => Object.entries(data.value.progress.training).reduce((p, v) => { p[v[0]] = v[1].reduce((_p, _v) => Math.max(_p, _v[0]) , 0); return p; }, {} as Record<MainStat, number>));
const trainingSpent = computed(() => Object.values(maxTraining.value).reduce((p, v) => p + v, 0));
const modifiers = computed(() => Object.entries(maxTraining.value).reduce((p, v) => { p[v[0]] = Math.floor(v[1] / 3) + (data.value.progress.modifiers ? (data.value.progress.modifiers[v[0] as MainStat] ?? 0) : 0); return p; }, {} as Record<MainStat, number>))
const modifierPoints = computed(() => (selectedRaceOptions.value ? selectedRaceOptions.value.reduce((p, v) => p + (v?.modifier ?? 0), 0) : 0) + training.value.reduce((p, v) => p + v[1].reduce((_p, _v) => _p + (_v?.modifier ?? 0), 0), 0));
const modifierSpent = computed(() => Object.values(data.value.progress.modifiers ?? {}).reduce((p, v) => p + v, 0));
const abilityPoints = computed(() => training.value.flatMap(e => e[1].filter(_e => _e.ability !== undefined)).reduce((p, v) => p + v.ability!, 0));
const abilityMax = computed(() => Object.entries(characterConfig.ability).reduce((p, v) => { p[v[0]] = abilitySpecialFeatures("max", data.value.progress.training.curiosity, Math.floor(maxTraining.value[v[1].max[0]] / 3) + Math.floor(maxTraining.value[v[1].max[1]] / 3)); return p; }, {} as Record<Ability, number>));
const abilitySpent = computed(() => Object.values(data.value.progress.abilities ?? {}).reduce((p, v) => p + v[0], 0));

if(id !== 'new')
{
    const character = await useRequestFetch()(`/api/character/${id}`);

    if(!character)
    {
        throw new Error('Donnée du personnage introuvables');
    }

    data.value = { name: character.name, progress: character.progress } as Character;
}

function selectRaceOption(level: Level, choice: number)
{
    const character = data.value;
    if(level > character.progress.level)
        return;

    if(character.progress.race.progress === undefined)
        character.progress.race.progress = [[1, 0]];

    if(level == 1)
        return;

    for(let i = 1; i < level; i++) //Check previous levels as a requirement
    {
        if(!character.progress.race.progress.some(e => e[0] == i))
            return;
    }

    if(character.progress.race.progress.some(e => e[0] === level))
    {
        character.progress.race.progress.splice(character.progress.race.progress.findIndex(e => e[0] === level), 1, [level, choice]);
    }
    else
    {
        character.progress.race.progress.push([level, choice]);
    }

    data.value = character;
}
function switchTrainingOption(stat: MainStat, level: TrainingLevel, choice: number)
{
    const character = data.value;

    if(level == 0)
        return;

    for(let i = 1; i < level; i++) //Check previous levels as a requirement
    {
        if(!character.progress.training[stat].some(e => e[0] == i))
            return;
    }

    if(character.progress.training[stat].some(e => e[0] === level))
    {
        if(character.progress.training[stat].some(e => e[0] === level && e[1] === choice))
        {
            for(let i = 15; i >= level; i --) //Invalidate higher levels
            {
                const index = character.progress.training[stat].findIndex(e => e[0] == i);
                if(index !== -1)
                    character.progress.training[stat].splice(index, 1);
            }
        }
        else
            character.progress.training[stat].splice(character.progress.training[stat].findIndex(e => e[0] === level), 1, [level, choice]);
    }
    else if(trainingPoints.value && trainingPoints.value > 0)
    {
        character.progress.training[stat].push([level, choice]);
    }

    data.value = character;
}
function updateLevel()
{
    const character = data.value;

    if(character.progress.race.progress) //Invalidate higher levels
    {
        for(let level = 20; level > character.progress.level; level--)
        {
            const index = character.progress.race.progress.findIndex(e => e[0] == level);
            if(index !== -1)
                character.progress.race.progress.splice(index, 1);
        }
    }

    data.value = character;
}
async function save(leave: boolean)
{
    if(data.value.name === '' || data.value.progress.race.index === undefined || data.value.progress.race.index === -1)
    {                
        add({ title: 'Données manquantes', content: "Merci de saisir un nom et une race avant de pouvoir enregistrer votre personnage", type: 'error', duration: 25000, timer: true });
        return;
    }
    if(id === 'new')
    {
        id = await useRequestFetch()(`/api/character`, {
            method: 'post',
            body: data.value,
            onResponseError: (e) => {
                add({ title: 'Erreur d\enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
            }
        });
        add({ content: 'Personnage créé', type: 'success', duration: 25000, timer: true });
        useRouter().replace({ name: 'character-id-edit', params: { id: id } })
        if(leave) useRouter().push({ name: 'character-id', params: { id: id } });
    }
    else
    {
        await useRequestFetch()(`/api/character/${id}`, {
            method: 'post',
            body: data.value,
            onResponseError: (e) => {
                add({ title: 'Erreur d\enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
            }
        });
        add({ content: 'Personnage enregistré', type: 'success', duration: 25000, timer: true });
        if(leave) useRouter().push({ name: 'character-id', params: { id: id } });
    }
}

useShortcuts({
    "Meta_S": () =>save(false),
})
</script>

<template>
    <Head>
        <Title>d[any] - Edition de {{ data.name || 'nouveau personnage' }}</Title>
    </Head>
    <div  class="flex flex-col gap-8 align-center">
        <div class="flex flex-row gap-4 align-center justify-between">
            <div></div>
            <div class="flex flex-row gap-4 align-center justify-center">
                <Tooltip side="left" message="Developpement en cours"><Avatar src="" icon="radix-icons:person" size="large" /></Tooltip>
                <Label class="flex items-start justify-between flex-col gap-2">
                    <span class="pb-1 mx-2 md:p-0">Nom du personnage</span>
                    <input class="caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50
                    bg-light-20 dark:bg-dark-20 outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40
                    border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20"
                        type="text" v-model="data.name">
                </Label>
                <Label class="flex items-start justify-between flex-col gap-2">
                    <span class="pb-1 mx-2 md:p-0">Niveau</span>
                    <NumberFieldRoot :min="1" :max="20" v-model="data.progress.level" @update:model-value="updateLevel" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 
                data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
                        <NumberFieldInput class="tabular-nums w-20 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
                    </NumberFieldRoot>
                </Label>
            </div>
            <div  class="self-center">
                <Tooltip side="right" message="Ctrl+S"><Button @click="() => save(true)">Enregistrer</Button></Tooltip>
            </div>
        </div>
        <div class="flex flex-1 flex-col min-w-[800px] w-[75vw] max-w-[1200px]">
            <Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="peopleOpen" @update:model-value="() => { trainingOpen = false; abilityOpen = false; notesOpen = false; }">
                <template #label>
                    <span class="font-bold text-xl">Peuple</span>
                </template>
                <template #default>
                    <div class="m-2 overflow-auto">
                        <Select label="Peuple de votre personnage" :v-model="data.progress.race.index" :default-value="data.progress.race.index?.toString() ?? ''" @update:model-value="(index) => { data.progress.race.index = parseInt(index ?? '-1'); data.progress.race.progress = [[1, 0]]}">
                            <SelectItem v-for="(people, index) of characterConfig.peoples" :label="people.name" :value="index.toString()" :key="index" />
                        </Select>
                        <template v-if="data.progress.race.index !== undefined">
                            <div class="w-full border-b border-light-30 dark:border-dark-30 pb-4">
                                <span class="text-sm text-light-70 dark:text-dark-70">{{ characterConfig.peoples[data.progress.race.index].description }}</span>
                            </div>
                            <div class="flex flex-col gap-4 max-h-[50vh] pe-4 relative">
                                <span class="sticky top-0 py-1 bg-light-0 dark:bg-dark-0 z-10 text-xl">Niveaux restants: {{ data.progress.level - (data.progress.race.progress?.length ?? 0) }}</span>
                                <div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.peoples[data.progress.race.index].options" :class="{ 'opacity-30': index > data.progress.level }">
                                    <div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-64" v-for="(option, i) of level" @click="selectRaceOption(index as Level, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= data.progress.level, '!border-accent-blue bg-accent-blue bg-opacity-20': data.progress.race.progress?.some(e => e[0] == index && e[1] === i) ?? false }"><MarkdownRenderer :content="raceOptionToText(option)" /></div>
                                </div>
                            </div>
                        </template>
                    </div>
                </template>
            </Collapsible>
            <Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="trainingOpen" :disabled="data.progress.race.index === undefined" @update:model-value="() => { peopleOpen = false; abilityOpen = false; notesOpen = false; }">
                <template #label>
                    <span class="font-bold text-xl">Entrainement</span>
                </template>
                <template #default>
                    <div class="flex flex-col gap-4 max-h-[50vh] pe-4 relative overflow-y-auto overflow-x-hidden">
                        <div class="sticky top-0 py-2 bg-light-0 dark:bg-dark-0 z-10 flex justify-between">
                            <Icon icon="radix-icons:caret-left" class="w-6 h-6 border border-light-30 dark:border-dark-30 cursor-pointer" @click="() => trainingTab = clamp(trainingTab - 1, 0, 6)" />
                            <span class="text-xl" :class="{ 'text-light-red dark:text-dark-red': (trainingPoints ?? 0) < trainingSpent }">Points d'entrainement restants: {{ (trainingPoints ?? 0) - trainingSpent }}</span>
                            <Icon icon="radix-icons:caret-right" class="w-6 h-6 border border-light-30 dark:border-dark-30 cursor-pointer" @click="() => trainingTab = clamp(trainingTab + 1, 0, 6)" />
                        </div>
                        <div class="flex gap-4 relative" :style="`left: calc(calc(-100% - 1em) * ${trainingTab}); transition: left .5s ease;`">
                            <div class="flex w-full flex-shrink-0 flex-col gap-2 relative" v-for="(text, stat) of mainStatTexts">
                                <div class="sticky top-1 flex justify-between z-20 mx-16">
                                    <div class="py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold border border-light-30 dark:border-dark-30 flex">{{ text }}
                                        <div class="flex gap-2" v-if="maxTraining[stat] >= 0">: Niveau {{ maxTraining[stat] }} (+{{ modifiers[stat] }} 
                                            <NumberFieldRoot :default-value="data.progress.modifiers[stat] ?? 0" v-model="data.progress.modifiers[stat]" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 
                                data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
                                                <NumberFieldInput class="tabular-nums w-8 text-base font-normal bg-transparent px-2 outline-none caret-light-50 dark:caret-dark-50" />
                                            </NumberFieldRoot>
                                        )</div></div>
                                    <div class="py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 flex gap-2 justify-center items-center" :class="{ 'text-light-red dark:text-dark-red': (modifierPoints ?? 0) < modifierSpent }">Modifieur bonus: {{ modifierPoints - modifierSpent }}</div>
                                </div>
                                <div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training[stat]" :class="{ 'opacity-30': index > maxTraining[stat] + 1 }">
                                    <div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption(stat, index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining[stat] + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training[stat]?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
                                </div>
                            </div>
                        </div>
                    </div>
                </template>
            </Collapsible>
            <Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="abilityOpen" :disabled="data.progress.race.index === undefined" @update:model-value="() => { trainingOpen = false;peopleOpen = false; notesOpen = false; }">
                <template #label>
                    <span class="font-bold text-xl">Compétences</span>
                </template>
                <template #default>
                    <div class="flex flex-col gap-2 max-h-[50vh] px-4 relative overflow-y-auto">
                        <div class="sticky top-0 py-2 bg-light-0 dark:bg-dark-0 z-10 flex justify-between">
                            <span class="text-xl -mx-2" :class="{ 'text-light-red dark:text-dark-red': (abilityPoints ?? 0) < abilitySpent }">Points d'entrainement restants: {{ (abilityPoints ?? 0) - abilitySpent }}</span>
                        </div>
                        <div class="grid gap-4 grid-cols-6">
                            <div v-for="(ability, index) of characterConfig.ability" class="flex flex-col items-center border border-light-30 dark:border-dark-30 p-2">
                                <div class="flex items-center justify-center gap-4">
                                    <NumberFieldRoot :min="0" :max="abilityMax[index]" :default-value="data.progress.abilities[index] ? data.progress.abilities[index][0] : 0" @update:model-value="(value) => { data.progress.abilities[index] = [value, data.progress.abilities[index] ? data.progress.abilities[index][1] : 0]; }" class="border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 
                                data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
                                        <NumberFieldInput class="tabular-nums w-8 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
                                    </NumberFieldRoot>
                                    <span class="font-bold col-span-4">/{{ abilityMax[index] }}</span>
                                </div>
                                <span class="text-xl font-bold flex-2">{{ ability.name }}</span>
                                <span class="text-sm text-light-70 dark:text-dark-70 flex-1">({{ mainStatTexts[ability.max[0]] }} + {{ mainStatTexts[ability.max[1]] }})</span>
                            </div>
                        </div>
                    </div>
                </template>
            </Collapsible>
            <Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="notesOpen" @update:model-value="() => { trainingOpen = false; peopleOpen = false; abilityOpen = false; }">
                <template #label>
                    <span class="font-bold text-xl">Notes libres</span>
                </template>
                <template #default>
                    <Editor class="min-h-[400px] border border-light-30 dark:border-dark-30" v-model="data.progress.notes" />
                </template>
            </Collapsible>
        </div>
    </div>
</template>