import { useCallback } from 'react';

import type { Identifier } from '../../types/identifiers';

interface TagRecord {
  id?: Identifier;
  label: string;
  [key: string]: unknown;
}

interface ApiClient {
  post: <T = { data: unknown }>(path: string, payload: unknown) => Promise<{ data: T } | T>;
}

interface TagManager {
  buildPayload: (input: { label: string }) => Record<string, unknown>;
}

interface UseDocumentTaggingArgs {
  apiClient: ApiClient;
  tags: TagRecord[];
  tagManager: TagManager;
  refreshTags: () => Promise<void> | void;
  resolveTargetDocumentIds: (ids?: Identifier[] | null) => Identifier[];
  notifyApiError: (error: unknown, message: string) => void;
  setStatusMessage: (message: string, variant?: string) => void;
  updateDocumentCaches?: (id: Identifier, updater: (doc: TagRecord | null) => TagRecord | null) => void;
}

interface BulkTagOperationArgs {
  labels: string[];
  action: 'add' | 'remove';
  documentIds?: Identifier[];
}

interface BulkTagOperationResult {
  ok: boolean;
  reason?: 'no-labels' | 'no-selection' | 'tag-missing' | 'no-tags' | 'request-failed';
  label?: string;
  tagCount?: number;
  docsCount?: number;
}

const useDocumentTagging = ({
  apiClient,
  tags,
  tagManager,
  refreshTags,
  resolveTargetDocumentIds,
  notifyApiError,
  setStatusMessage,
  updateDocumentCaches,
}: UseDocumentTaggingArgs) => {
  const bulkTagOperation = useCallback(
    async ({ labels, action, documentIds }: BulkTagOperationArgs): Promise<BulkTagOperationResult> => {
      const normalized = labels.map((label) => label.trim()).filter((label) => label.length > 0);
      if (!normalized.length) {
        return { ok: false, reason: 'no-labels' };
      }
      const targetDocumentIds = resolveTargetDocumentIds(documentIds);
      if (!targetDocumentIds.length) {
        return { ok: false, reason: 'no-selection' };
      }

      let tagIds: Identifier[] = [];

      if (action === 'remove') {
        const missing = normalized.find(
          (label) => !tags.some((tag) => tag.label.toLowerCase() === label.toLowerCase()),
        );
        if (missing) {
          return { ok: false, reason: 'tag-missing', label: missing };
        }

        tagIds = normalized.map((label) => {
          const tag = tags.find((item) => item.label.toLowerCase() === label.toLowerCase());
          return tag?.id;
        }).filter(Boolean);
      }

      try {
        if (action === 'add') {
          const createdIds: Identifier[] = [];
          const createdTags: TagRecord[] = [];
          for (const label of normalized) {
            let tag = tags.find((item) => item.label.toLowerCase() === label.toLowerCase()) || null;
            if (!tag) {
              const payload = tagManager.buildPayload({ label });
              const response = await apiClient.post<{ id?: Identifier; label: string }>('/tags', payload);
              tag = 'data' in response ? response.data : response;
              await refreshTags();
            }
            createdIds.push(tag.id);
            createdTags.push(tag);
          }
          tagIds = Array.from(new Set(createdIds));

          if (updateDocumentCaches) {
            const tagById = new Map<Identifier, TagRecord>();
            tags.forEach((tag) => {
              if (tag?.id != null) {
                tagById.set(tag.id, tag);
              }
            });
            createdTags.forEach((tag) => {
              if (tag?.id != null) {
                tagById.set(tag.id, tag);
              }
            });
            targetDocumentIds.forEach((docId) => {
              tagIds.forEach((tagId) => {
                const cachedTag = tagById.get(tagId);
                if (!cachedTag) {
                  return;
                }
                updateDocumentCaches(docId, (doc) => {
                  if (!doc) {
                    return doc;
                  }
                  const currentTags = Array.isArray((doc as any).tags) ? (doc as any).tags : [];
                  if (currentTags.some((entry: any) => entry?.id === tagId)) {
                    return doc;
                  }
                  return {
                    ...(doc as any),
                    tags: [...currentTags, { ...cachedTag }],
                  };
                });
              });
            });
          }
        }

        tagIds = Array.from(new Set(tagIds));

        if (!tagIds.length) {
          return { ok: false, reason: 'no-tags' };
        }

        await apiClient.post('/documents/bulk/tags', {
          document_ids: targetDocumentIds,
          tag_ids: tagIds,
          action,
        });

        if (updateDocumentCaches) {
          targetDocumentIds.forEach((docId) => {
            tagIds.forEach((tagId) => {
              updateDocumentCaches(docId, (doc) => {
                if (!doc || !Array.isArray((doc as any).tags)) {
                  return doc;
                }
                const currentTags = (doc as any).tags;
                if (action === 'remove') {
                  const filtered = currentTags.filter((entry: any) => entry?.id !== tagId);
                  return filtered.length === currentTags.length ? doc : { ...(doc as any), tags: filtered };
                }
                return doc;
              });
            });
          });
        }

        return {
          ok: true,
          tagCount: tagIds.length,
          docsCount: targetDocumentIds.length,
        };
      } catch (error) {
        const message =
          error.response?.data?.error ||
          (action === 'add' ? 'Failed to assign tags.' : 'Failed to remove tags.');
        notifyApiError(error, message);
        return { ok: false, reason: 'request-failed' };
      }
    },
    [
      resolveTargetDocumentIds,
      tags,
      refreshTags,
      notifyApiError,
      tagManager,
      apiClient,
      updateDocumentCaches,
    ],
  );

  const handleBulkTagAddFromDetail = useCallback(
    async ({ label, input, documentIds }: { label?: string; input?: HTMLInputElement | null; documentIds?: Identifier[] }) => {
      const trimmed = label?.trim?.() || '';
      if (!trimmed) {
        setStatusMessage('Enter a tag label.', 'error');
        return;
      }
      const targetIds = resolveTargetDocumentIds(documentIds);
      if (!targetIds.length) {
        setStatusMessage('Select documents before assigning tags.', 'error');
        return;
      }
      const result = await bulkTagOperation({
        labels: [trimmed],
        action: 'add',
        documentIds: targetIds,
      });
      if (result?.ok) {
        const { tagCount, docsCount } = result;
        setStatusMessage(
          `Assigned ${tagCount} tag${tagCount === 1 ? '' : 's'} to ${docsCount} document${docsCount === 1 ? '' : 's'
          }.`,
          'success',
        );
        if (input) {
          input.value = '';
        }
      }
    },
    [bulkTagOperation, resolveTargetDocumentIds, setStatusMessage],
  );

  const handleBulkTagRemoveFromDetail = useCallback(
    async ({ label, input, documentIds }: { label?: string; input?: HTMLInputElement | null; documentIds?: Identifier[] }) => {
      const trimmed = label?.trim?.() || '';
      if (!trimmed) {
        setStatusMessage('Enter a tag label to remove.', 'error');
        return;
      }
      const targetIds = resolveTargetDocumentIds(documentIds);
      if (!targetIds.length) {
        setStatusMessage('Select documents before removing tags.', 'error');
        return;
      }
      const result = await bulkTagOperation({
        labels: [trimmed],
        action: 'remove',
        documentIds: targetIds,
      });
      if (result?.ok) {
        const { docsCount } = result;
        setStatusMessage(
          `Removed tags from ${docsCount} document${docsCount === 1 ? '' : 's'}.`,
          'success',
        );
        if (input) {
          input.value = '';
        }
      } else if (result?.reason === 'tag-missing') {
        setStatusMessage(`Tag “${result.label}” not found.`, 'error');
      }
    },
    [bulkTagOperation, resolveTargetDocumentIds, setStatusMessage],
  );

  const handleBulkSelectionReanalyze = useCallback(
    async (documentIdsOverride: Identifier[] | null = null) => {
      const targetIds = resolveTargetDocumentIds(documentIdsOverride);
      if (!targetIds.length) {
        setStatusMessage('Select documents before requesting re-analysis.', 'error');
        return;
      }

      try {
        const response = await apiClient.post<{ queued?: number }>(
          '/documents/bulk/reanalyze',
          {
            document_ids: targetIds,
            force: true,
          },
        );
        const payload = 'data' in response ? response.data : response;
        const queued = Number.isFinite(payload?.queued)
          ? Number(payload.queued)
          : targetIds.length;
        setStatusMessage(
          `Queued re-analysis for ${queued} document${queued === 1 ? '' : 's'}.`,
          'success',
        );
      } catch (error) {
        const message =
          error.response?.data?.error || 'Failed to queue document re-analysis.';
        notifyApiError(error, message);
      }
    },
    [resolveTargetDocumentIds, notifyApiError, setStatusMessage, apiClient],
  );

  return {
    bulkTagOperation,
    handleBulkTagAddFromDetail,
    handleBulkTagRemoveFromDetail,
    handleBulkSelectionReanalyze,
  };
};

export default useDocumentTagging;
