import { MutableRefObject, useCallback, useState } from 'react';
import type { TagId, TenantId } from '../../types/identifiers';

type ApiClient = {
  get: (path: string) => Promise<{ data: unknown }>
  post: (path: string, body: unknown) => Promise<{ data: unknown }>
  patch: (path: string, body: unknown) => Promise<{ data: unknown }>
  delete: (path: string) => Promise<{ data: unknown }>
};

interface TagManagerInterface {
  buildPayload: (input: { label?: string; color?: string | null }) => { label: string; color: string | null };
}

interface TagEntry {
  id?: TagId;
  label?: string;
  color?: string | null;
  [key: string]: unknown;
}

interface UseTagsOptions {
  apiClient: ApiClient;
  notifyApiError: (error: unknown, fallback: string) => void;
  setStatusMessage: (message: string, variant?: string) => void;
  tagManager: TagManagerInterface;
  tenantIdRef: MutableRefObject<TenantId | null>;
  setActiveTagFilters: (updater: (prev: Array<TagId>) => Array<TagId>) => void;
  mapDocumentCaches?: (mapper: (doc: any) => any) => void;
}

const useTags = ({
  apiClient,
  notifyApiError,
  setStatusMessage,
  tagManager,
  tenantIdRef,
  setActiveTagFilters,
  mapDocumentCaches,
}: UseTagsOptions) => {
  const [tags, setTags] = useState<TagEntry[]>([]);

  const refreshTags = useCallback(async () => {
    const requestTenantId = tenantIdRef.current;
    try {
      const { data } = await apiClient.get('/tags');
      if (tenantIdRef.current !== requestTenantId) {
        return;
      }
      setTags(data || []);
    } catch (error) {
      if (tenantIdRef.current !== requestTenantId) {
        return;
      }
      notifyApiError(error, 'Unable to load tags.');
    }
  }, [apiClient, notifyApiError, tenantIdRef]);

  const handleTagUpdate = useCallback(
    async (tagId: TagId, changes: { label?: string; color?: string | null }) => {
      if (tagId == null) {
        throw new Error('Missing tag identifier.');
      }

      const payload: Record<string, unknown> = {};
      if (changes?.label != null) {
        payload.label = changes.label;
      }
      if (Object.prototype.hasOwnProperty.call(changes, 'color')) {
        payload.color = changes.color;
      }

      if (Object.keys(payload).length === 0) {
        return false;
      }

      try {
        await apiClient.patch(`/tags/${tagId}`, payload);
        await refreshTags();
        setStatusMessage('Tag updated.', 'success');
        return true;
      } catch (error) {
        const message = error.response?.data?.error || 'Failed to update tag.';
        notifyApiError(error, message);
        throw new Error(message);
      }
    },
    [apiClient, notifyApiError, refreshTags, setStatusMessage],
  );

  const handleTagCreate = useCallback(
    async ({ label, color }: { label?: string; color?: string | null } = {}) => {
      const payload = tagManager.buildPayload({ label, color });
      try {
        await apiClient.post('/tags', payload);
        await refreshTags();
        setStatusMessage('Tag created.', 'success');
      } catch (error) {
        const message = error.response?.data?.error || 'Failed to create tag.';
        notifyApiError(error, message);
        throw new Error(message);
      }
    },
    [apiClient, notifyApiError, refreshTags, setStatusMessage, tagManager],
  );

  const handleTagDelete = useCallback(
    async (tagId: TagId) => {
      if (tagId == null) {
        throw new Error('Missing tag identifier.');
      }

      try {
        await apiClient.delete(`/tags/${tagId}`);
        setActiveTagFilters((prev) => prev.filter((id) => id !== tagId));

        const stripTagFromDoc = (doc: any) => {
          if (!doc || !Array.isArray(doc.tags)) {
            return doc;
          }
          const nextTags = doc.tags.filter((tag) => tag.id !== tagId);
          if (nextTags.length === doc.tags.length) {
            return doc;
          }
          return { ...doc, tags: nextTags };
        };

        mapDocumentCaches?.(stripTagFromDoc);

        await refreshTags();
        setStatusMessage('Tag deleted.', 'success');
        return true;
      } catch (error) {
        const message = error.response?.data?.error || 'Failed to delete tag.';
        notifyApiError(error, message);
        throw new Error(message);
      }
    },
    [apiClient, mapDocumentCaches, notifyApiError, refreshTags, setActiveTagFilters, setStatusMessage],
  );

  return {
    tags,
    refreshTags,
    handleTagUpdate,
    handleTagCreate,
    handleTagDelete,
    setTags,
  };
};

export default useTags;
