import { useCallback } from 'react';
import type { DocumentId } from '../../types/identifiers';
import type { Document } from '../../types/documents';
import {
    addDocumentTags,
    createTag,
    deleteDocumentTag,
} from '../../lib/api/apiClient';
import useNotifyApiError from '../../hooks/useNotifyApiError';
import { useStatusToast } from '../../lib/context/StatusToastContext';
import type {
    TagsState,
    DocumentsState,
    Tag,
} from '../types/workspaceTypes';

interface DocumentTagExtras {
    option?: Tag | null;
    input?: { value?: string } | null;
}

interface UseDocumentTagMutationsArgs {
    tagsState: TagsState;
    documentsState: Pick<DocumentsState, 'updateDocumentCaches'>;
}

export const useDocumentTagMutations = ({
    tagsState,
    documentsState,
}: UseDocumentTagMutationsArgs) => {
    const { showToast } = useStatusToast();
    const notifyApiError = useNotifyApiError();

    const attachTagToDocument = useCallback(
        async ({
            documentId,
            tag,
        }: {
            documentId?: DocumentId;
            tag?: Tag | null;
        }) => {
            if (!documentId || !tag?.id) {
                return false;
            }

            const cachedTag: Tag = {
                id: tag.id,
                label: tag.label,
                color: Object.prototype.hasOwnProperty.call(tag, 'color') ? tag.color ?? null : null,
            };

            try {
                await addDocumentTags(documentId, [cachedTag.id]);
                documentsState.updateDocumentCaches(documentId, (doc) => {
                    if (!doc) {
                        return doc;
                    }
                    const currentTags = Array.isArray(doc.tags) ? doc.tags : [];
                    if (currentTags.some((entry) => entry?.id === cachedTag.id)) {
                        return doc;
                    }
                    return { ...doc, tags: [...currentTags, cachedTag] };
                });
                showToast('Tag assigned.', 'success');
                return true;
            } catch (error) {
                const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to assign tag.';
                notifyApiError(error, message);
                return false;
            }
        },
        [notifyApiError, showToast, documentsState],
    );

    const handleDocumentTagAdd = useCallback(
        async (document: Document, label: string, extras?: DocumentTagExtras | null) => {
            const normalizedLabel = tagsState.tagManager.normalizeLabel(label);
            const optionCandidate = extras?.option ?? null;
            const input = extras?.input ?? null;

            let tag: Tag | null = null;
            if (optionCandidate && optionCandidate.id) {
                tag = tagsState.tags.find((item) => item.id === optionCandidate.id) || optionCandidate;
            }
            if (!tag) {
                tag = tagsState.tags.find((item) => item.label.toLowerCase() === normalizedLabel.toLowerCase()) || null;
            }
            try {
                if (!tag) {
                    const payload = tagsState.tagManager.buildPayload({ label: normalizedLabel }) as { label: string; color?: string | null };
                    const data = await createTag(payload);
                    tag = data as Tag;
                    await tagsState.refreshTags();
                }
                await attachTagToDocument({
                    documentId: document.id as DocumentId,
                    tag,
                });
                if (input && typeof input === 'object' && 'value' in input) {
                    (input as { value?: string }).value = '';
                }
            } catch (error) {
                notifyApiError(error, 'Failed to assign tag.');
            }
        },
        [tagsState, attachTagToDocument, notifyApiError],
    );

    const handleDocumentTagAttach = useCallback(
        async (documentId: DocumentId, tagId: DocumentId) => {
            if (!documentId || !tagId) {
                return false;
            }

            const resolveTagForCache = (): Tag | null => {
                const lookupTag = tagsState.tagLookupById.get(tagId);
                if (!lookupTag || lookupTag.id == null) {
                    return null;
                }
                const labelText = `${lookupTag.label ?? ''} `.trim();
                if (!labelText) {
                    return null;
                }
                return {
                    id: lookupTag.id,
                    label: labelText,
                    color: Object.prototype.hasOwnProperty.call(lookupTag, 'color') ? (lookupTag as Tag).color ?? null : null,
                };
            };

            const resolvedTag = resolveTagForCache();
            return attachTagToDocument({
                documentId,
                tag: resolvedTag,
            });
        },
        [
            attachTagToDocument,
            tagsState,
        ],
    );

    const handleDocumentTagDetach = useCallback(
        async (documentId?: DocumentId, tagId?: DocumentId) => {
            if (!documentId || !tagId) {
                return false;
            }

            try {
                await deleteDocumentTag(documentId, tagId);
                // Inlined applyTagRemovalToCaches logic
                documentsState.updateDocumentCaches(documentId, (doc) => {
                    if (!doc || !Array.isArray(doc.tags)) {
                        return doc;
                    }
                    const nextTags = doc.tags.filter((tagEntry) => tagEntry.id !== tagId);
                    if (nextTags.length === doc.tags.length) {
                        return doc;
                    }
                    return { ...doc, tags: nextTags };
                });
                showToast('Tag removed.', 'success');
                return true;
            } catch (error) {
                const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to remove tag.';
                notifyApiError(error, message);
                return false;
            }
        },
        [documentsState, notifyApiError, showToast],
    );

    return {
        handleDocumentTagAdd,
        handleDocumentTagAttach,
        handleDocumentTagDetach,
    };
};
