<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';

import { trpc } from '../utils/trpc';

type VoteListResponse = Awaited<ReturnType<typeof trpc.vote.getVoteList.query>>;
type VoteDetail = Awaited<ReturnType<typeof trpc.vote.getVoteDetail.query>>;
type PollSummary = VoteListResponse['polls'][number];

const polls = ref<PollSummary[]>([]);
const voteReward = ref(0);
const currentVoteId = ref<number | null>(null);
const currentVote = ref<VoteDetail | null>(null);
const loading = ref(false);
const detailLoading = ref(false);
const isVoteAdmin = ref(false);
const showNewVote = ref(false);
const message = ref('');
const messageKind = ref<'success' | 'error'>('success');
const mySinglePick = ref(0);
const myMultiPick = ref<number[]>([]);
const myComment = ref('');
const newVoteTitle = ref('');
const newVoteOptionsText = ref('');
const newVoteMultipleOptions = ref(1);

const getErrorMessage = (error: unknown): string => {
    if (error instanceof Error) {
        return error.message;
    }
    return typeof error === 'string' ? error : '요청을 처리하지 못했습니다.';
};

const showMessage = (text: string, kind: 'success' | 'error') => {
    message.value = text;
    messageKind.value = kind;
};

const isEnded = (poll: { endAt: string | null; closedAt: string | null }): boolean => {
    if (poll.closedAt) {
        return true;
    }
    return poll.endAt ? new Date(poll.endAt).getTime() < Date.now() : false;
};

const canVote = computed(
    () => Boolean(currentVote.value) && !currentVote.value?.myVote && !isEnded(currentVote.value!.voteInfo)
);

const voteTotal = computed(() => (currentVote.value?.votes ?? []).reduce((total, vote) => total + vote.count, 0));

const voteDistribution = computed(() => {
    const result = Array.from({ length: currentVote.value?.voteInfo.options.length ?? 0 }, () => 0);
    for (const vote of currentVote.value?.votes ?? []) {
        for (const selection of vote.selection) {
            if (selection >= 0 && selection < result.length) {
                result[selection] += vote.count;
            }
        }
    }
    return result;
});

const newVoteOptions = computed(() => newVoteOptionsText.value.split('\n').filter((option) => option.length > 0));

const percentage = (count: number, total: number): string => ((count / Math.max(1, total)) * 100).toFixed(1);

const formatStartDate = (value: string): string => value.slice(0, 10);

const formatCommentDate = (value: string): string => {
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) {
        return value;
    }
    const pad = (part: number) => String(part).padStart(2, '0');
    return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
};

const voteColor = (index: number): string =>
    ['#ff0000', '#ffa500', '#ffff00', '#008000', '#0000ff', '#000080', '#800080'][index % 7]!;

const voteColorText = (index: number): string => ([1, 2].includes(index % 7) ? '#000' : '#fff');

const loadVoteDetail = async (voteId: number) => {
    detailLoading.value = true;
    try {
        const detail = await trpc.vote.getVoteDetail.query({ voteId });
        currentVote.value = detail;
        currentVoteId.value = voteId;
        mySinglePick.value = detail.myVote?.[0] ?? 0;
        myMultiPick.value = detail.myVote ? [...detail.myVote] : [];
        myComment.value = '';
    } catch (error) {
        showMessage(getErrorMessage(error), 'error');
    } finally {
        detailLoading.value = false;
    }
};

const reloadVote = async () => {
    if (loading.value) {
        return;
    }
    loading.value = true;
    message.value = '';
    try {
        const result = await trpc.vote.getVoteList.query();
        polls.value = result.polls;
        voteReward.value = result.voteReward;
        const nextVoteId =
            (currentVoteId.value && result.polls.some((poll) => poll.id === currentVoteId.value)
                ? currentVoteId.value
                : result.polls[0]?.id) ?? null;
        if (nextVoteId) {
            await loadVoteDetail(nextVoteId);
        } else {
            currentVoteId.value = null;
            currentVote.value = null;
        }
    } catch (error) {
        showMessage(getErrorMessage(error), 'error');
    } finally {
        loading.value = false;
    }
};

const selectVote = (voteId: number) => {
    if (voteId !== currentVoteId.value) {
        void loadVoteDetail(voteId);
    }
};

const changeMultiPick = (index: number, checked: boolean) => {
    const limit = currentVote.value?.voteInfo.multipleOptions ?? 0;
    if (checked && limit > 0 && myMultiPick.value.length > limit) {
        myMultiPick.value = myMultiPick.value.filter((value) => value !== index);
        showMessage(`${limit}개까지만 선택할 수 있습니다.`, 'error');
    }
};

const submitVote = async () => {
    if (!currentVote.value) {
        return;
    }
    const selection = currentVote.value.voteInfo.multipleOptions === 1 ? [mySinglePick.value] : [...myMultiPick.value];
    if (selection.length === 0) {
        showMessage('선택한 항목이 없습니다.', 'error');
        return;
    }
    try {
        const result = await trpc.vote.submitVote.mutate({
            voteId: currentVote.value.voteInfo.id,
            selection,
        });
        showMessage(result.wonLottery ? '특별한 설문 보상이 제공되었습니다!' : '설문을 마쳤습니다.', 'success');
        await loadVoteDetail(currentVote.value.voteInfo.id);
    } catch (error) {
        showMessage(getErrorMessage(error), 'error');
    }
};

const submitComment = async () => {
    if (!currentVote.value || myComment.value.length === 0) {
        return;
    }
    try {
        await trpc.vote.addComment.mutate({
            voteId: currentVote.value.voteInfo.id,
            text: myComment.value,
        });
        myComment.value = '';
        showMessage('댓글을 달았습니다.', 'success');
        await loadVoteDetail(currentVote.value.voteInfo.id);
    } catch (error) {
        showMessage(getErrorMessage(error), 'error');
    }
};

const submitNewVote = async () => {
    try {
        await trpc.vote.createPoll.mutate({
            title: newVoteTitle.value,
            body: '',
            options: newVoteOptions.value,
            multipleOptions: newVoteMultipleOptions.value,
            revealMode: 'after_vote',
            closePrevious: true,
        });
        showMessage('설문 조사가 생성되었습니다.', 'success');
        newVoteTitle.value = '';
        newVoteOptionsText.value = '';
        newVoteMultipleOptions.value = 1;
        showNewVote.value = false;
        await reloadVote();
    } catch (error) {
        showMessage(getErrorMessage(error), 'error');
    }
};

onMounted(() => {
    void reloadVote();
    void trpc.vote.getAdminStatus
        .query()
        .then((result) => {
            isVoteAdmin.value = result.ok === true;
        })
        .catch(() => {
            isVoteAdmin.value = false;
        });
});
</script>

<template>
    <main id="container" class="pageVote bg0">
        <header class="back_bar bg0">
            <RouterLink class="legacy-button legacy-button--navigation back_btn" to="/">창 닫기</RouterLink>
            <button
                class="legacy-button legacy-button--navigation reload_btn"
                type="button"
                :disabled="loading"
                @click="reloadVote"
            >
                갱신
            </button>
            <h2 class="title"></h2>
            <div>&nbsp;</div>
            <div></div>
        </header>

        <div
            v-if="message"
            class="vote-notice"
            :class="messageKind"
            :role="messageKind === 'error' ? 'alert' : 'status'"
        >
            {{ message }}
        </div>
        <div id="vote-title" class="bg2">설문 조사({{ voteReward }}금과 추첨으로 유니크템 증정!)</div>

        <div v-if="detailLoading && !currentVote" class="loading">불러오는 중...</div>
        <table v-if="currentVote" id="vote-result">
            <colgroup>
                <col class="vote-idx" />
                <col class="vote-count" />
                <col class="vote-percent" />
                <col class="vote-option" />
            </colgroup>
            <thead>
                <tr>
                    <th colspan="3" class="text-end bg1">설문 제목</th>
                    <th id="vote-detail-title">
                        {{ currentVote.voteInfo.title }}
                        <template v-if="currentVote.voteInfo.multipleOptions !== 1">
                            ({{
                                currentVote.voteInfo.multipleOptions === 0
                                    ? currentVote.voteInfo.options.length
                                    : currentVote.voteInfo.multipleOptions
                            }}개 선택 가능 )
                        </template>
                    </th>
                </tr>
                <tr>
                    <th colspan="3" class="text-end bg1">게시자</th>
                    <th id="vote-detail-opener">{{ currentVote.voteInfo.openerName || '[SYSTEM]' }}</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="(option, index) in currentVote.voteInfo.options" :key="index">
                    <td v-if="canVote" class="text-center">
                        <input
                            v-if="currentVote.voteInfo.multipleOptions === 1"
                            :id="`v-vote-${index}`"
                            v-model="mySinglePick"
                            class="form-check-input"
                            type="radio"
                            :value="index"
                        />
                        <input
                            v-else
                            :id="`v-vote-${index}`"
                            v-model="myMultiPick"
                            class="form-check-input"
                            type="checkbox"
                            :value="index"
                            @change="changeMultiPick(index, ($event.target as HTMLInputElement).checked)"
                        />
                    </td>
                    <td
                        v-else
                        class="text-end f_tnum"
                        :style="{ backgroundColor: voteColor(index), color: voteColorText(index) }"
                    >
                        {{ index + 1 }}.
                    </td>
                    <td class="text-end f_tnum vote-count">
                        <label :for="`v-vote-${index}`">{{ voteDistribution[index] }}명</label>
                    </td>
                    <td class="text-end f_tnum vote-percent">
                        <label :for="`v-vote-${index}`">
                            ({{ percentage(voteDistribution[index] ?? 0, voteTotal) }}%)
                        </label>
                    </td>
                    <td>
                        <label :for="`v-vote-${index}`">{{ option }}</label>
                    </td>
                </tr>
            </tbody>
            <tfoot>
                <tr>
                    <template v-if="canVote">
                        <td class="text-center">투표</td>
                        <td colspan="2">
                            <button class="legacy-button legacy-button--secondary vote-submit" @click="submitVote">
                                투표
                            </button>
                        </td>
                    </template>
                    <td v-else colspan="3" class="text-center">결산</td>
                    <td>
                        투표율: {{ voteTotal }} / {{ currentVote.userCnt }} ({{
                            percentage(voteTotal, currentVote.userCnt)
                        }}%)
                    </td>
                </tr>
            </tfoot>
        </table>

        <form v-if="currentVote" @submit.prevent="submitComment">
            <table id="vote-comment">
                <colgroup>
                    <col class="comment-idx" />
                    <col class="comment-name" />
                    <col class="comment-text" />
                    <col class="comment-date" />
                </colgroup>
                <thead>
                    <tr class="bg1 text-center">
                        <th>#</th>
                        <th><span>국가명</span><span>장수명</span></th>
                        <th>댓글</th>
                        <th>일시</th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="(comment, index) in currentVote.comments" :key="comment.id">
                        <td class="comment-idx f_tnum">{{ index + 1 }}.</td>
                        <td class="comment-name">
                            <span>{{ comment.nationName }}</span
                            ><span>{{ comment.generalName }}</span>
                        </td>
                        <td>{{ comment.text }}</td>
                        <td class="comment-date f_tnum">{{ formatCommentDate(comment.createdAt) }}</td>
                    </tr>
                </tbody>
                <tfoot>
                    <tr>
                        <td></td>
                        <td>
                            <button class="legacy-button legacy-button--secondary comment-submit" type="submit">
                                댓글 달기
                            </button>
                        </td>
                        <td colspan="2">
                            <input v-model="myComment" class="form-control" maxlength="200" aria-label="댓글" />
                        </td>
                    </tr>
                </tfoot>
            </table>
        </form>

        <div id="vote-old-title" class="bg2">이전 설문 조사</div>
        <div id="vote-old-list">
            <div v-for="poll in polls" :key="poll.id" class="vote-old-item">
                <a href="#" @click.prevent="selectVote(poll.id)">{{ poll.title }}</a>
                ({{ formatStartDate(poll.startAt) }})
            </div>
        </div>

        <div v-if="isVoteAdmin" id="vote-new-panel">
            <div><a href="#" @click.prevent="showNewVote = !showNewVote">새 설문 조사 열기</a></div>
            <template v-if="showNewVote">
                <div class="admin-row">
                    <div>설문 제목</div>
                    <div><input v-model="newVoteTitle" class="form-control" type="text" /></div>
                </div>
                <div class="admin-row">
                    <div>설문 대상(엔터로 구분) ({{ newVoteOptions.length }}건)</div>
                    <div>
                        <textarea
                            v-model="newVoteOptionsText"
                            class="form-control"
                            :rows="newVoteOptions.length + 1"
                        ></textarea>
                    </div>
                </div>
                <div class="admin-row">
                    <div>동시 응답 수(0=모두)</div>
                    <div>
                        <input
                            v-model.number="newVoteMultipleOptions"
                            class="form-control"
                            type="number"
                            min="0"
                            :max="newVoteOptions.length"
                        />
                    </div>
                </div>
                <div class="admin-submit">
                    <button class="legacy-button legacy-button--secondary" type="button" @click="submitNewVote">
                        제출
                    </button>
                </div>
            </template>
        </div>

        <footer class="bottom_bar bg0">
            <RouterLink class="legacy-button legacy-button--navigation back_btn" to="/">창 닫기</RouterLink>
        </footer>
    </main>
</template>

<style scoped>
.pageVote {
    margin: 0 auto;
    color: #fff;
    font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic';
    font-size: 14px;
    line-height: 1.5;
}

.bg0 {
    background-image: var(--sammo-texture-walnut);
}

.bg1 {
    background-image: var(--sammo-texture-green);
}

.bg2 {
    background-image: var(--sammo-texture-blue);
}

.back_bar {
    width: 100%;
    height: 32px;
    display: grid;
    grid-template-columns: 90px 90px 1fr 90px 90px;
}

.back_bar .title {
    margin: 0;
}

.back_btn,
.reload_btn {
    height: 32px;
    min-height: 32px;
    margin-right: 2px;
    font-weight: 600;
}

.back_bar .back_btn,
.back_bar .reload_btn {
    width: 88px;
}

#vote-title {
    font-size: 1.8em;
    line-height: 1.5;
    text-align: center;
}

#vote-old-title {
    font-size: 1.5em;
    line-height: 1.5;
    text-align: center;
}

#vote-result,
#vote-comment {
    width: 100%;
    border-collapse: collapse;
}

#vote-result th,
#vote-result td,
#vote-comment th,
#vote-comment td {
    padding-right: 1ch;
    padding-left: 1ch;
}

#vote-result label {
    display: block;
}

#vote-result .vote-idx {
    width: 5ch;
}

#vote-result .vote-count {
    width: 55px;
    padding-right: 0;
}

#vote-result .vote-percent {
    width: 70px;
    padding-left: 0;
}

.vote-submit {
    width: 100%;
}

#vote-comment .comment-idx {
    width: 5ch;
    text-align: end;
}

#vote-comment .comment-name {
    width: 110px;
    text-align: center;
}

#vote-comment .comment-name span,
#vote-comment thead th:nth-child(2) span {
    display: inline-block;
    width: 50%;
}

#vote-comment tbody tr {
    border-top: 1px solid gray;
}

.comment-submit {
    width: 50%;
    margin-left: 50%;
}

.form-control {
    width: 100%;
    min-height: 35.5px;
    box-sizing: border-box;
    padding: 5.25px 10.5px;
    border: 1px solid #6c757d;
    border-radius: 5.25px;
    color: #fff;
    background: #212529;
    font: inherit;
}

.form-check-input {
    width: 1em;
    height: 1em;
    margin: 0;
    accent-color: #0d6efd;
}

.text-end {
    text-align: end;
}

.text-center {
    text-align: center;
}

.f_tnum {
    font-variant-numeric: tabular-nums;
}

#vote-old-list,
#vote-new-panel {
    padding: 0 7px;
}

.vote-old-item a,
#vote-new-panel a {
    color: #6ea8fe;
}

.admin-row {
    display: grid;
    grid-template-columns: 25% 75%;
}

.admin-row > div {
    padding: 2px 0;
}

.admin-submit {
    width: 20%;
    margin-left: 80%;
    display: grid;
}

.bottom_bar {
    height: 55.5px;
    padding-top: 20px;
    box-sizing: border-box;
}

.bottom_bar .back_btn {
    display: inline-block;
    width: auto;
}

.vote-notice {
    padding: 5px 10px;
    border: 1px solid #477a47;
    color: #d8f5d8;
}

.vote-notice.error {
    border-color: #9b4848;
    color: #ffd0d0;
}

.loading {
    padding: 12px;
    text-align: center;
}

@media (min-width: 501px) {
    .pageVote {
        width: 1000px;
    }

    #vote-comment .comment-name {
        width: 260px;
    }

    #vote-comment .comment-date {
        width: 98px;
        padding-right: 0.5ch;
        padding-left: 0.5ch;
        text-align: center;
    }
}

@media (max-width: 500px) {
    .pageVote {
        width: 500px;
    }

    #vote-comment .comment-name {
        width: 130px;
    }

    #vote-comment .comment-date {
        width: 50px;
        padding-right: 0.5ch;
        padding-left: 0.5ch;
        text-align: center;
    }

    .admin-row {
        grid-template-columns: 100%;
    }
}
</style>
