export type CollectFeedbackLabels = {
    rate: string;
    average: string;
    commentPlaceholder: string;
    commentSubmit: string;
    noComments: string;
    missingActor: string;
};

export type CollectFeedbackUrls = {
    feedback: string;
    rate: string;
    comment: string;
    viewed: string;
};

export type CollectFeedbackItem = {
    kind: string;
    fileId?: string;
    publicId?: string;
};

type FeedbackPayload = {
    average: number | null;
    count: number;
    myRating: number | null;
    comments: Array<{ body: string; actor: string; createdAt: string | null }>;
};

const replaceFileKey = (template: string, fileKey: string): string =>
    template.replace('__FILE__', encodeURIComponent(fileKey));

export const createCollectFeedback = (options: {
    suffix?: 'Lightbox' | 'Hero';
    urls: CollectFeedbackUrls;
    csrfToken?: string;
    labels: CollectFeedbackLabels;
    getCurrentItem: () => CollectFeedbackItem | null;
}): { refresh: () => void } => {
    const suffix = options.suffix ?? 'Lightbox';
    const panel = document.getElementById(`collect${suffix}Feedback`);
    const starsRoot = document.getElementById(`collect${suffix}Stars`);
    const avgEl = document.getElementById(`collect${suffix}Avg`);
    const commentsEl = document.getElementById(`collect${suffix}Comments`);
    const commentForm = document.getElementById(`collect${suffix}CommentForm`) as HTMLFormElement | null;
    const commentInput = document.getElementById(`collect${suffix}CommentInput`) as HTMLTextAreaElement | null;

    if (!panel || !starsRoot || !avgEl || !commentsEl || !commentForm || !commentInput) {
        return { refresh: () => undefined };
    }

    if (options.labels.commentPlaceholder) {
        commentInput.placeholder = options.labels.commentPlaceholder;
    }

    const starButtons = Array.from(starsRoot.querySelectorAll<HTMLButtonElement>('[data-rating]'));
    let busy = false;

    const getFileKey = (item: CollectFeedbackItem | null): string | null => {
        if (!item || item.kind !== 'image') {
            return null;
        }

        return item.fileId || item.publicId || null;
    };

    const renderStars = (myRating: number | null) => {
        starButtons.forEach((btn) => {
            const value = Number(btn.dataset.rating || 0);
            btn.classList.toggle('is-active', myRating !== null && value <= myRating);
            btn.disabled = busy;
        });
    };

    const renderComments = (comments: FeedbackPayload['comments']) => {
        commentsEl.innerHTML = '';
        if (!comments.length) {
            const empty = document.createElement('li');
            empty.className = 'collect-lightbox__comments-empty';
            empty.textContent = options.labels.noComments;
            commentsEl.appendChild(empty);
            return;
        }

        comments.forEach((comment) => {
            const item = document.createElement('li');
            item.className = 'collect-lightbox__comment';
            item.innerHTML = `
                <div class="collect-lightbox__comment-meta">
                    <strong></strong>
                    <span></span>
                </div>
                <p></p>
            `;
            const strong = item.querySelector('strong');
            const span = item.querySelector('span');
            const p = item.querySelector('p');
            if (strong) strong.textContent = comment.actor;
            if (span) span.textContent = comment.createdAt || '';
            if (p) p.textContent = comment.body;
            commentsEl.appendChild(item);
        });
    };

    const renderFeedback = (feedback: FeedbackPayload) => {
        if (feedback.count > 0 && feedback.average !== null) {
            avgEl.textContent = options.labels.average
                .replace('%average%', String(feedback.average))
                .replace('%count%', String(feedback.count));
        } else {
            avgEl.textContent = options.labels.rate;
        }
        renderStars(feedback.myRating);
        renderComments(feedback.comments);
    };

    const markViewed = async (fileKey: string) => {
        try {
            const body = new FormData();
            if (options.csrfToken) {
                body.append('_token', options.csrfToken);
            }
            await fetch(replaceFileKey(options.urls.viewed, fileKey), {
                method: 'POST',
                body,
                credentials: 'same-origin',
            });
        } catch {
            /* ignore */
        }
    };

    const loadFeedback = async () => {
        const item = options.getCurrentItem();
        const fileKey = getFileKey(item);
        if (!fileKey) {
            panel.hidden = true;
            return;
        }

        panel.hidden = false;
        avgEl.textContent = '…';
        renderStars(null);
        commentsEl.innerHTML = '';

        try {
            const res = await fetch(replaceFileKey(options.urls.feedback, fileKey), {
                credentials: 'same-origin',
            });
            const data = await res.json();
            if (data.success && data.feedback) {
                renderFeedback(data.feedback as FeedbackPayload);
            } else {
                avgEl.textContent = options.labels.rate;
            }
        } catch {
            avgEl.textContent = options.labels.rate;
        }

        void markViewed(fileKey);
    };

    const submitRating = async (rating: number) => {
        const fileKey = getFileKey(options.getCurrentItem());
        if (!fileKey || busy) return;

        busy = true;
        renderStars(rating);
        const body = new FormData();
        body.append('rating', String(rating));
        if (options.csrfToken) {
            body.append('_token', options.csrfToken);
        }

        try {
            const res = await fetch(replaceFileKey(options.urls.rate, fileKey), {
                method: 'POST',
                body,
                credentials: 'same-origin',
            });
            const data = await res.json();
            if (data.error === 'missing_actor') {
                window.alert(options.labels.missingActor);
                return;
            }
            if (data.success && data.feedback) {
                renderFeedback(data.feedback as FeedbackPayload);
            }
        } finally {
            busy = false;
            renderStars(rating);
        }
    };

    starButtons.forEach((btn) => {
        btn.addEventListener('click', () => {
            const rating = Number(btn.dataset.rating || 0);
            if (rating >= 1 && rating <= 5) {
                void submitRating(rating);
            }
        });
    });

    commentForm.addEventListener('submit', (event) => {
        event.preventDefault();
        void (async () => {
            const fileKey = getFileKey(options.getCurrentItem());
            const comment = commentInput.value.trim();
            if (!fileKey || !comment || busy) return;

            busy = true;
            const body = new FormData();
            body.append('comment', comment);
            if (options.csrfToken) {
                body.append('_token', options.csrfToken);
            }

            try {
                const res = await fetch(replaceFileKey(options.urls.comment, fileKey), {
                    method: 'POST',
                    body,
                    credentials: 'same-origin',
                });
                const data = await res.json();
                if (data.error === 'missing_actor') {
                    window.alert(options.labels.missingActor);
                    return;
                }
                if (data.success && data.feedback) {
                    commentInput.value = '';
                    renderFeedback(data.feedback as FeedbackPayload);
                }
            } finally {
                busy = false;
            }
        })();
    });

    return { refresh: () => void loadFeedback() };
};
