<template>
    <Page v-if="$config.profile.editorEnabled">
        <MustLogin v-if="!$user()" />
        <div v-else>
            <AdPlaceholder :phkey="['header', null]" />
            <div class="mb-3 d-flex justify-content-between flex-column flex-md-row">
                <h2 class="text-nowrap">
                    <Avatar :user="$user()" />
                    @{{ $user()?.username }}
                </h2>
                <div>
                    <nuxt-link :to="/* @ts-ignore */ `/@${$user().username}`" class="btn btn-outline-primary btn-sm">
                        <Icon v="id-card" />
                        <T>profile.show</T>
                    </nuxt-link>
                </div>
            </div>

            <form :class="[saving ? 'saving' : '']" @submit.prevent="save">
                <TabsNav
                    :tabs="['opinions', 'names', $config.pronouns.enabled ? 'pronouns' : undefined, 'description', 'flags', 'links', 'birthday', 'timezone', 'words', 'circle', 'calendar', 'sensitive', $isGranted() ? 'admin' : undefined]"
                    pills
                    showheaders
                    navclass="mb-3 border-bottom-0"
                >
                    <template #admin-header>
                        <Icon v="user-cog" />
                        Team section
                    </template>
                    <template #admin>
                        <p class="small text-muted mb-0">
                            This will be shown on the “Team” page.
                            If you leave it empty, you won't show up there (for this language version).
                            You can use a different display name in different language versions.
                            Please only add yourself here, if you're actually working on <strong>this language version</strong>.
                        </p>

                        <div class="form-group">
                            <label for="teamName">Team page display name:</label>
                            <input v-model="teamName" class="form-control" name="teamName" maxlength="64">
                            <PropagateCheckbox v-if="otherProfiles > 0" field="teamName" :before="beforeChanges.teamName" :after="teamName" @change="propagateChanged" />
                        </div>

                        <hr>

                        <p class="small text-muted mb-0">
                            If you feel that you've contributed to this language version enough to get credited in the footer
                            (not saying how much that is, that's on you to decide 😉),
                            then add your name and areas here (in the local language!).
                            Please only add yourself here, if you're actually working on <strong>this language version</strong>.
                            The team as a whole will be credited in the footer either way.
                        </p>

                        <div class="form-group">
                            <label for="footerName">Footer display name:</label>
                            <input v-model="footerName" class="form-control" name="footerName" maxlength="64">
                            <PropagateCheckbox v-if="otherProfiles > 0" field="footerName" :before="beforeChanges.footerName" :after="footerName" @change="propagateChanged" />
                        </div>

                        <div class="form-group">
                            <label for="footerAreas">Areas responsible for / contributing to:</label>
                            <ListInput v-model="footerAreas" />
                        </div>

                        <template v-if="$te('contact.team.credentials')">
                            <hr>

                            <p class="small text-muted mb-0">
                                This will be displayed on the team page in the "Credentials" section.
                                You might want to put here your full legal name here, but it's not required
                                (you can leave this field empty).
                            </p>

                            <div class="form-group">
                                <label for="credentials">Credentials:</label>
                                <ListInput v-model="credentials" />
                            </div>

                            <div class="form-group">
                                <label for="credentials">Credentials level:</label>
                                <select v-model="credentialsLevel" class="form-select">
                                    <option :value="null"></option>
                                    <option :value="1">
                                        Higher education, but irrelevant field
                                    </option>
                                    <option :value="2">
                                        Bachelor (not completed yet)
                                    </option>
                                    <option :value="3">
                                        Bachelor
                                    </option>
                                    <option :value="4">
                                        Master (not completed yet)
                                    </option>
                                    <option :value="5">
                                        Master
                                    </option>
                                    <option :value="6">
                                        PhD (not completed yet)
                                    </option>
                                    <option :value="7">
                                        PhD
                                    </option>
                                </select>
                            </div>

                            <div class="form-group">
                                <label for="credentials">Name for credentials:</label>
                                <input v-model="credentialsName" class="form-control" placeholder="(not required)">
                            </div>
                        </template>
                    </template>

                    <template #opinions-header>
                        <Icon v="comment-smile" />
                        <T>profile.opinions.header</T>
                    </template>
                    <template #opinions>
                        <LegendOpinionListInput v-model="defaultOpinions" readonly class="mb-0" />
                        <LegendOpinionListInput v-model="opinions" :maxitems="10" />
                    </template>

                    <template #names-header>
                        <Icon v="signature" />
                        <T>profile.names</T>
                    </template>
                    <template #names>
                        <p v-if="$te('profile.namesInfo')" class="small text-muted">
                            <T>profile.namesInfo</T>
                        </p>
                        <OpinionListInput
                            v-model="names"
                            :prototype="{ value: '', opinion: 'meh', pronunciation: null }"
                            :custom-opinions="opinions"
                            :maxitems="128"
                            :maxlength="$config.profile.longNames ? 255 : 32"
                        >
                            <template #additional="s">
                                <PronunciationInput v-model="s.val.pronunciation" />
                            </template>
                        </OpinionListInput>
                        <InlineMarkdownInstructions v-model="markdown" />
                        <PropagateCheckbox v-if="otherProfiles > 0" field="names" :before="beforeChanges.names" :after="names" @change="propagateChanged" />
                    </template>

                    <template #pronouns-header>
                        <Icon v="tags" />
                        <T>profile.pronouns</T>
                    </template>
                    <template #pronouns>
                        <div v-if="$t('profile.pronounsInfo')" class="alert alert-info">
                            <p class="small mb-0">
                                <Icon v="info-circle" />
                                <T>profile.pronounsInfo</T>
                            </p>
                        </div>
                        <OpinionListInput v-model="pronouns" :validation="validatePronoun" :custom-opinions="opinions" :maxitems="128" :maxlength="192" />
                    </template>

                    <template #description-header>
                        <Icon v="comment-edit" />
                        <T>profile.description</T>
                    </template>
                    <template #description>
                        <textarea v-model="description" class="form-control form-control-sm" maxlength="1024" rows="8"></textarea>
                        <InlineMarkdownInstructions v-model="markdown" />
                    </template>

                    <template #flags-header>
                        <Icon v="flag" />
                        <T>profile.flags</T>
                    </template>
                    <template #flags>
                        <p class="small text-muted mb-0">
                            <T>profile.flagsInfo</T>
                        </p>
                        <ButtonList v-slot="s" v-model="flags" :options="allFlagsOptions">
                            <Flag
                                :name="s.desc.split('|')[0]"
                                :alt="s.desc.split('|')[1]"
                                :img="`/flags/${s.v}.png`"
                                :asterisk="allFlags[s.v].asterisk"
                            />
                        </ButtonList>
                        <PropagateCheckbox v-if="otherProfiles > 0" field="flags" :before="beforeChanges.flags" :after="flags" @change="propagateChanged" />

                        <details class="form-group border rounded" :open="customFlags.length > 0">
                            <summary class="px-3 py-2">
                                <T>profile.flagsCustom</T>
                            </summary>
                            <div class="border-top">
                                <CustomFlagsWidget v-model="customFlags" sizes="flag" :maxitems="128" />
                            </div>
                        </details>
                        <PropagateCheckbox v-if="otherProfiles > 0" field="customFlags" :before="beforeChanges.customFlags" :after="customFlags" @change="propagateChanged" />
                        <Answer question="flags" small />
                    </template>

                    <template #links-header>
                        <Icon v="link" />
                        <T>profile.links</T>
                    </template>
                    <template #links>
                        <ListInput v-model="links" :maxitems="128">
                            <template #default="s">
                                <input
                                    v-model="s.val"
                                    type="url"
                                    class="form-control"
                                    required
                                    @keyup="s.update(s.val)"
                                    @paste="$nextTick(() => s.update(s.val))"
                                    @change="s.update(s.val)"
                                >
                            </template>
                            <template #validation="s">
                                <p v-if="s.val && !isValidLink(s.val)" class="small text-danger">
                                    <Icon v="exclamation-triangle" />
                                    <span class="ml-1">{{ $t('crud.validation.invalidLink') }}</span>
                                </p>
                            </template>
                        </ListInput>
                        <PropagateCheckbox v-if="otherProfiles > 0" field="links" :before="beforeChanges.links" :after="links" @change="propagateChanged" />
                        <p class="small text-muted mb-0">
                            <Icon v="ad" />
                            <T>profile.linksRecommended</T>
                            <a v-for="provider in recommendedLinkProviders()" :href="provider.homepage" target="_blank" rel="noopener">
                                <Icon :v="provider.icon" :set="provider.iconSet || 'l'" />
                                {{ provider.name }}
                            </a>
                            <T>profile.linksRecommendedAfter</T>
                            😉
                        </p>
                        <p v-if="$te('profile.linksWarning')" class="small text-muted mt-2 mb-0">
                            <Icon v="exclamation-triangle" />
                            <T>profile.linksWarning</T>
                        </p>
                    </template>

                    <template #birthday-header>
                        <Icon v="birthday-cake" />
                        <T>profile.birthday</T>
                    </template>
                    <template #birthday>
                        <p class="small text-muted">
                            <T>profile.birthdayInfo</T>
                        </p>
                        <div class="input-group mb-3">
                            <ClientOnly>
                                <datepicker
                                    v-model="birthday"
                                    inline
                                    :disabled-dates="disabledDates"
                                    :open-date="disabledDates.from"
                                    :initial-view="birthday !== null ? 'day' : 'year'"
                                />
                            </ClientOnly>
                        </div>
                        <PropagateCheckbox v-if="otherProfiles > 0" field="birthday" :before="beforeChanges.birthday" :after="birthday" @change="propagateChanged" />
                        <button v-if="birthday !== null" type="button" class="btn btn-outline-danger btn-sm" @click="birthday = null">
                            <Icon v="times" />
                            <T>crud.remove</T>
                        </button>
                    </template>

                    <template #timezone-header>
                        <Icon v="clock" />
                        <T>profile.timezone.header</T>
                    </template>
                    <template #timezone>
                        <p class="small text-muted">
                            <T>profile.timezone.info</T>
                        </p>
                        <TimezoneSelect v-model="timezone" />
                        <PropagateCheckbox v-if="otherProfiles > 0" field="timezone" :before="beforeChanges.timezone" :after="timezone" @change="propagateChanged" />
                    </template>

                    <template #words-header>
                        <Icon v="scroll-old" />
                        <T>profile.words</T>
                    </template>
                    <template #words>
                        <template v-for="i in [0, 1, 2, 3]">
                            <h4 class="h5">
                                <T>profile.column</T> {{ i + 1 }}
                            </h4>
                            <input v-model="words[i].header" class="form-control form-control-sm mb-2" :placeholder="$t('profile.wordsColumnHeader')" maxlength="36">
                            <OpinionListInput v-model="words[i].values" group="words" :custom-opinions="opinions" :maxitems="128" />
                        </template>
                        <button type="button" class="btn btn-outline-warning btn-sm" @click.prevent="resetWords">
                            <T>profile.editor.defaults</T>
                        </button>
                        <InlineMarkdownInstructions v-model="markdown" />
                    </template>

                    <template #circle-header>
                        <Icon v="heart-circle" />
                        <T>profile.circles.header</T>
                    </template>
                    <template #circle>
                        <p class="small text-muted">
                            <T>profile.circles.info</T>
                        </p>

                        <CircleListInput v-model="circle" :maxitems="16" />

                        <CircleMentions />
                    </template>

                    <template #sensitive-header>
                        <Icon v="engine-warning" />
                        <T>profile.sensitive.header</T>
                    </template>
                    <template #sensitive>
                        <p class="small text-muted">
                            <T>profile.sensitive.info</T>
                        </p>

                        <ListInput v-model="sensitive" :maxlength="64" :maxitems="16" />
                    </template>

                    <template #calendar-header>
                        <Icon v="calendar" />
                        <T>profile.calendar.header</T>
                    </template>
                    <template #calendar>
                        <p class="small text-muted">
                            <T>profile.calendar.info</T>
                        </p>

                        <section class="my-5">
                            <p class="h5">
                                <T>profile.calendar.customEvents.header</T><T>quotation.colon</T>
                            </p>
                            <PersonalEventListInput v-model="customEvents" :maxitems="100" />
                        </section>

                        <section class="my-5">
                            <p class="h5">
                                <T>profile.calendar.publicEvents.header</T><T>quotation.colon</T>
                            </p>
                            <!-- @vue-ignore -->
                            <PersonalCalendar
                                :year="year"
                                :events="events"
                                class="my-3"
                                remove-button
                                :maxitems="100"
                                @delete="(d) => events = events.filter(e => e !== d)"
                            />
                            <PropagateCheckbox v-if="otherProfiles > 0" field="events" :before="beforeChanges.events" :after="events" @change="propagateChanged" />
                            <!-- @vue-ignore -->
                            <CalendarEventsList :year="year" add-button @add="(e) => events.push(e)" />
                        </section>
                    </template>
                </TabsNav>

                <section>
                    <button class="btn btn-primary w-100" type="submit">
                        <Icon v="save" />
                        <T>profile.editor.save</T>
                    </button>
                </section>

                <AdPlaceholder :phkey="['content-1', 'content-mobile-1']" />
            </form>
        </div>
    </Page>
</template>

<script lang="ts">
import { head, buildList, isValidLink } from '../src/helpers.ts';
import { pronouns, pronounLibrary } from '../src/data.ts';
import { buildPronoun } from '../src/buildPronoun.ts';
import { buildFlags } from '../src/flags.ts';
import link from '../plugins/link.ts';
import { birthdateRange, formatDate, parseDate } from '../src/birthdate.ts';
import opinions from '../src/opinions.ts';
import { calendar } from '../src/calendar/calendar.ts';
import type { Config } from '../locale/config.ts';
import type { Translator } from '../src/translator.ts';
import type { Pronoun } from '../src/classes.ts';
import type { Flag } from '../src/flags.ts';
import type { Opinion } from '../src/opinions.ts';
import type { OpinionFormValue, Profile, SaveProfilePayload, WordCategory } from '../src/profile.ts';

interface ProfileFormValue extends Omit<Profile, 'birthday' | 'linksMetadata' | 'verifiedLinks' | 'opinions' |
        'card' | 'cardDark' | 'lastUpdate'> {
    birthday: Date | null;
    opinions: OpinionFormValue[];
}

const defaultWords = (config: Config): WordCategory[] => {
    if (!config.profile.enabled || !config.profile.editorEnabled) {
        return [];
    }
    return config.profile.defaultWords.map(({ header, values }) => {
        return {
            header,
            values: values.map((v) => {
                return { value: v.replace(/"/g, '\''), opinion: 'meh' };
            }),
        };
    });
};

function coerceWords(words: WordCategory[]): WordCategory[] {
    for (let i = 0; i < 4; i++) {
        words[i] = words[i] || {
            header: null,
            values: [],
        };
    }
    return words;
}

function fixArrayObject<T>(arrayObject: Record<string, T> | T[]): T[] {
    return Array.isArray(arrayObject) ? arrayObject : Object.values(arrayObject);
}

const opinionsToForm = (
    opinions: Record<string, Opinion>,
    translator: Translator,
): OpinionFormValue[] => buildList(function*() {
    for (const [key, options] of Object.entries(opinions)) {
        yield {
            key,
            icon: options.icon,
            description: options.description || translator.get(`profile.opinion.${key}`),
            colour: options.colour || '',
            style: options.style || '',
        };
    }
});

const buildProfile = (
    profiles: Record<string, Profile>,
    config: Config,
    translator: Translator,
): ProfileFormValue => {
    // card in this locale exists
    for (const locale in profiles) {
        if (!profiles.hasOwnProperty(locale)) {
            continue;
        }
        if (locale === config.locale) {
            const profile = profiles[locale];
            return {
                names: profile.names,
                pronouns: profile.pronouns,
                description: profile.description,
                birthday: parseDate(profile.birthday),
                timezone: profile.timezone,
                links: profile.links,
                flags: profile.flags,
                customFlags: fixArrayObject(profile.customFlags),
                words: coerceWords(profile.words),
                teamName: profile.teamName,
                footerName: profile.footerName,
                footerAreas: profile.footerAreas,
                credentials: profile.credentials,
                credentialsLevel: profile.credentialsLevel,
                credentialsName: profile.credentialsName,
                opinions: opinionsToForm(profile.opinions || {}, translator),
                circle: profile.circle,
                sensitive: profile.sensitive,
                markdown: profile.markdown,
                events: profile.events,
                customEvents: profile.customEvents,
            };
        }
    }

    // card in this locale doesn't exist yet, but we can copy some non-language-specific fields from another card
    for (const locale in profiles) {
        if (!profiles.hasOwnProperty(locale)) {
            continue;
        }
        const profile = profiles[locale];
        return {
            names: profile.names,
            pronouns: [],
            description: '',
            birthday: parseDate(profile.birthday),
            timezone: profile.timezone,
            links: profile.links,
            flags: profile.flags.filter((f) => !f.startsWith('-')),
            customFlags: fixArrayObject(profile.customFlags),
            words: [...defaultWords(config)],
            teamName: profile.teamName,
            footerName: profile.footerName,
            footerAreas: [],
            credentials: [],
            credentialsLevel: null,
            credentialsName: null,
            opinions: opinionsToForm(profile.opinions || {}, translator),
            circle: profile.circle,
            sensitive: [],
            markdown: profile.markdown,
            events: [],
            customEvents: [],
        };
    }

    // no cards in other languages available, start with a fresh one
    return {
        names: [],
        pronouns: [],
        description: '',
        birthday: null,
        timezone: null,
        links: [],
        flags: [],
        customFlags: [],
        words: [...defaultWords(config)],
        teamName: '',
        footerName: '',
        footerAreas: [],
        credentials: [],
        credentialsLevel: null,
        credentialsName: null,
        opinions: [],
        circle: [],
        sensitive: [],
        markdown: false,
        events: [],
        customEvents: [],
    };
};

type AsyncData = ProfileFormValue & { beforeChanges: ProfileFormValue, otherProfiles: number } | Record<string, never>;

export default link.extend({
    components: {
        datepicker: () => {
            if (!process.client) {
                return null;
            }
            return import('vuejs-datepicker');
        },
    },
    async asyncData({ app, store }): Promise<AsyncData> {
        if (!store.state.user) {
            return {};
        }

        const profiles = (await app.$axios.$get(`/profile/get/${encodeURIComponent(store.state.user.username)}?version=2&props=flags,pronouns,names,age,timezone,links,customFlags,team,opinions,circle&lprops[${app.$config.locale}]=all`, { headers: {
            authorization: `Bearer ${store.state.token}`,
        } })).profiles;

        const profile = buildProfile(profiles, app.$config, app.$translator);

        return {
            ...profile,
            beforeChanges: JSON.parse(JSON.stringify(profile)),
            otherProfiles: Object.keys(profiles).filter((locale) => locale !== app.$config.locale).length,
        };
    },
    data() {
        const bdayRange = birthdateRange(this.$config);
        return {
            saving: false,
            disabledDates: {
                to: bdayRange.min,
                from: bdayRange.max,
            },
            propagate: [] as string[],
            defaultOpinions: opinionsToForm(opinions, this.$translator),
            isValidLink,
            year: calendar.getCurrentYear(),
        };
    },
    head() {
        return head({
            title: this.$t('profile.editor.header'),
        }, this.$translator);
    },
    computed: {
        mainPronoun(): Pronoun | null {
            if (!this.$config.profile.editorEnabled || !this.$config.profile.flags?.defaultPronoun) {
                return null;
            }
            let mainPronoun = buildPronoun(pronouns, this.$config.profile.flags?.defaultPronoun, this.$config, this.$translator);
            let mainOpinion = -1;
            for (const { value: pronoun, opinion } of this.pronouns) {
                const opinionValue = opinions[opinion]?.value || 0;
                if (opinionValue > mainOpinion) {
                    const p = this.normaliseAndBuildPronoun(pronoun);
                    if (p) {
                        mainPronoun = p;
                        mainOpinion = opinionValue;
                    }
                }
            }

            return mainPronoun;
        },
        allFlags(): Record<string, Flag> {
            return buildFlags(this.$config.locale);
        },
        allFlagsOptions(): Record<string, string> {
            const entries = Object.entries(this.allFlags).map(([flagName, flag]) => {
                return [flagName, `${this.$translateForPronoun(flag.display, this.mainPronoun)}|${flag.display}`];
            });
            entries.sort((a, b) => a[1].localeCompare(b[1]));
            return Object.fromEntries(entries);
        },
    },
    mounted() {
        if (process.client && !this.$user()) {
            window.sessionStorage.setItem('after-login', window.location.pathname);
            this.$router.push(`/${this.$config.user.route}`);
        }
    },
    methods: {
        async save(): Promise<void> {
            this.saving = true;
            try {
                await this.$post('/profile/save', {
                    username: this.$user()!.username,

                    opinions: this.opinions,
                    names: this.names,
                    pronouns: this.pronouns,
                    description: this.description,
                    birthday: formatDate(this.birthday),
                    timezone: this.timezone,
                    links: [...this.links],
                    flags: [...this.flags],
                    customFlags: [...fixArrayObject(this.customFlags)],
                    words: this.words,
                    circle: this.circle,
                    sensitive: this.sensitive,
                    markdown: this.markdown,
                    events: this.events,
                    customEvents: this.customEvents,

                    teamName: this.teamName,
                    footerName: this.footerName,
                    footerAreas: this.footerAreas,
                    credentials: this.credentials,
                    credentialsLevel: this.credentialsLevel,
                    credentialsName: this.credentialsName,

                    propagate: this.propagate,
                } satisfies SaveProfilePayload);
                this.$router.push(`/@${this.$user()!.username}`);
            } finally {
                this.saving = false;
            }
        },
        normalisePronoun(pronoun: string): string | null {
            try {
                return decodeURIComponent(pronoun
                    .toLowerCase()
                    .trim()
                    .replace(new RegExp(`^${this.$base}`), '')
                    .replace(new RegExp(`^${this.$base.replace(/^https?:\/\//, '')}`), '')
                    .replace(new RegExp('^/'), ''));
            } catch {
                return null;
            }
        },
        normaliseAndBuildPronoun(pronoun: string): Pronoun | null {
            return buildPronoun(pronouns, this.normalisePronoun(pronoun), this.$config, this.$translator);
        },
        validatePronoun(pronoun: string): string | null {
            const normalisedPronoun = this.normalisePronoun(pronoun);
            if (!normalisedPronoun) {
                return 'profile.pronounsNotFound';
            }
            return this.validateAnyPronoun(normalisedPronoun) ||
                    this.$config.pronouns.null && this.$config.pronouns.null.routes && this.$config.pronouns.null.routes.includes(normalisedPronoun) ||
                    this.$config.pronouns.mirror && this.$config.pronouns.mirror.route === normalisedPronoun ||
                    buildPronoun(pronouns, normalisedPronoun, this.$config, this.$translator)
                ? null
                : 'profile.pronounsNotFound';
        },
        validateAnyPronoun(pronoun: string): boolean {
            const prefix = `${this.$config.pronouns.any}:`;
            return pronoun === this.$config.pronouns.any ||
                    pronoun.startsWith(prefix) && !!pronounLibrary.byKey()[pronoun.substring(prefix.length)];
        },
        async resetWords(): Promise<void> {
            await this.$confirm();

            this.words = [...defaultWords(this.$config)];
        },
        propagateChanged(field: string, checked: boolean): void {
            this.propagate = this.propagate.filter((f) => f !== field);
            if (checked) {
                this.propagate.push(field);
            }
        },
    },
});
</script>

<style lang="scss" scoped>
    .avatar {
        width: 100%;
        max-width: 5rem;
        max-height: 5rem;
    }
    .saving {
        opacity: .5;
    }
    section.form-group {
        margin-bottom: 5rem;
    }
</style>
