import { useMemo, useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { useContentCatalogPayload } from '@/business/content-catalog/hooks';
import {
  buildDashboardSignOfWeekPayload,
  buildSignLanguageCategories,
  filterSignLanguageItems,
  getSignLanguageProgressPercent,
  normalizeSignLanguageItems,
  selectSignLanguageSignOfWeek,
} from '@/business/sign-language/selectors';
import { canPersistPersonalScopeResults } from '@/business/scope/selectors';
import type {
  SignLanguageDraft,
  SignLanguageStepDraft,
  SignLanguagePage,
  SignLanguageVideoModalState,
} from '@/business/sign-language/types';
import { useLearnedSignsProgress } from '@/business/user-progress/hooks';
import { updateManagedContentCatalog } from '@/shared/api/contentCatalog';
import { useScopeContext } from '@/shared/app/scope-context';
import { usePermissions } from '@/shared/app/usePermissions';
import { CONTENT_CATALOG_TYPES } from '@/shared/constants/contentCatalog';
import { CONTENT_CATALOG_QUERY_KEYS } from '@/shared/constants/contentCatalog';
import type {
  SignLanguageCategoryFilter,
  SignLanguageViewMode,
} from '@/shared/constants/signLanguage';
import { getOptionalErrorMessage } from '@/shared/errors/errorMessages';
import type {
  SignItem,
  SignLanguagePageContent,
} from '@/shared/types/app';
import type { DashboardSignOfWeek } from '@/shared/types/dashboard';

const EMPTY_SIGN_LANGUAGE_PAGE_CONTENT: SignLanguagePageContent = {
  rememberTitle: '',
  rememberDescription: '',
};
const EMPTY_LEARNED_SIGN_IDS = new Set<string>();
const DEFAULT_SIGN_STEP: SignLanguageStepDraft = {
  step: 1,
  instruction: '',
  duration: 3,
};
const DEFAULT_SIGN_DRAFT: SignLanguageDraft = {
  id: null,
  word: '',
  category: 'basic-needs',
  description: '',
  image: '',
  tip: '',
  videoUrl: '',
  gifUrl: '',
  youtubeSearchUrl: '',
  videoSteps: [DEFAULT_SIGN_STEP],
};

function createSignId(): string {
  return `sign-${crypto.randomUUID()}`;
}

function draftFromSign(sign: SignItem): SignLanguageDraft {
  return {
    id: sign.id,
    word: sign.word,
    category: sign.category,
    description: sign.description,
    image: sign.image,
    tip: sign.tip,
    videoUrl: sign.videoUrl,
    gifUrl: sign.gifUrl,
    youtubeSearchUrl: sign.youtubeSearchUrl ?? '',
    videoSteps: sign.videoSteps.map((step, index) => ({
      step: step.step || index + 1,
      instruction: step.instruction,
      duration: step.duration,
    })),
  };
}

function signFromDraft(draft: SignLanguageDraft): SignItem {
  return {
    id: draft.id ?? createSignId(),
    word: draft.word.trim(),
    category: draft.category,
    description: draft.description.trim(),
    image: draft.image.trim(),
    tip: draft.tip.trim(),
    videoUrl: draft.videoUrl.trim(),
    gifUrl: draft.gifUrl.trim(),
    youtubeSearchUrl: draft.youtubeSearchUrl.trim() || undefined,
    videoSteps: draft.videoSteps
      .map((step, index) => ({
        step: index + 1,
        instruction: step.instruction.trim(),
        duration: Number.isFinite(step.duration) && step.duration > 0 ? step.duration : 3,
      }))
      .filter((step) => step.instruction.length > 0),
  };
}

function validateSignDraft(draft: SignLanguageDraft): string | null {
  if (!draft.word.trim()) return 'Title is required.';
  if (!draft.description.trim()) return 'Description is required.';
  if (!draft.image.trim()) return 'Preview image URL or upload is required.';
  if (!draft.tip.trim()) return 'Teaching tip is required.';
  if (!draft.videoUrl.trim()) return 'YouTube video URL is required.';
  if (draft.videoSteps.every((step) => !step.instruction.trim())) {
    return 'At least one guide step is required.';
  }
  return null;
}

export function useSignLanguagePage(): SignLanguagePage {
  const { effectiveTenant, ownTenant, selectedTenant } = useScopeContext();
  const permissions = usePermissions();
  const queryClient = useQueryClient();
  const canPersistProgress = canPersistPersonalScopeResults(ownTenant, selectedTenant);
  const canManageSigns =
    effectiveTenant?.level === 'organization' && permissions.has('MANAGE_CONTENT_CATALOG');
  const signsQuery = useContentCatalogPayload<readonly SignItem[]>(
    CONTENT_CATALOG_TYPES.signLanguageItems,
    [],
  );
  const signOfWeekQuery = useContentCatalogPayload<DashboardSignOfWeek | null>(
    CONTENT_CATALOG_TYPES.dashboardSignOfWeek,
    null,
  );
  const pageContentQuery = useContentCatalogPayload<SignLanguagePageContent>(
    CONTENT_CATALOG_TYPES.signLanguagePageContent,
    EMPTY_SIGN_LANGUAGE_PAGE_CONTENT,
  );
  const progress = useLearnedSignsProgress({ enabled: canPersistProgress });
  const [searchQuery, setSearchQuery] = useState('');
  const [categoryFilter, setCategoryFilter] = useState<SignLanguageCategoryFilter>('all');
  const [selectedSignId, setSelectedSignId] = useState<string | null>(null);
  const [signDraft, setSignDraft] = useState<SignLanguageDraft | null>(null);
  const [signDraftMode, setSignDraftMode] = useState<'create' | 'edit' | null>(null);
  const [signDraftError, setSignDraftError] = useState<string | null>(null);
  const [signSaveMessage, setSignSaveMessage] = useState<string | null>(null);
  const [pendingDeleteSign, setPendingDeleteSign] = useState<SignItem | null>(null);
  const signs = useMemo(
    () => normalizeSignLanguageItems(signsQuery.payload),
    [signsQuery.payload],
  );
  const signOfWeek = useMemo(
    () => selectSignLanguageSignOfWeek(signs, signOfWeekQuery.payload),
    [signOfWeekQuery.payload, signs],
  );
  const saveSigns = useMutation({
    mutationFn: async (nextSigns: readonly SignItem[]) =>
      updateManagedContentCatalog<readonly SignItem[]>(
        CONTENT_CATALOG_TYPES.signLanguageItems,
        { payload: nextSigns },
      ),
    onSuccess: async () => {
      await queryClient.invalidateQueries({
        queryKey: [...CONTENT_CATALOG_QUERY_KEYS.content, CONTENT_CATALOG_TYPES.signLanguageItems],
      });
      setSignSaveMessage('Sign cards saved.');
    },
  });
  const saveSignOfWeek = useMutation({
    mutationFn: async (sign: SignItem) =>
      updateManagedContentCatalog<DashboardSignOfWeek>(
        CONTENT_CATALOG_TYPES.dashboardSignOfWeek,
        { payload: buildDashboardSignOfWeekPayload(sign) },
      ),
    onSuccess: async () => {
      await queryClient.invalidateQueries({
        queryKey: [...CONTENT_CATALOG_QUERY_KEYS.content, CONTENT_CATALOG_TYPES.dashboardSignOfWeek],
      });
      setSignSaveMessage('Sign of the week saved.');
    },
  });
  const filters = useMemo(
    () => ({
      searchQuery,
      categoryFilter,
    }),
    [categoryFilter, searchQuery],
  );
  const categories = useMemo(
    () => buildSignLanguageCategories(signs),
    [signs],
  );
  const filteredSigns = useMemo(
    () => filterSignLanguageItems(signs, filters),
    [filters, signs],
  );
  const selectedSign = useMemo(
    () => signs.find((sign) => sign.id === selectedSignId) ?? null,
    [selectedSignId, signs],
  );
  const learnedSignIds = canPersistProgress ? progress.learnedSignIds : EMPTY_LEARNED_SIGN_IDS;
  const progressPercent = useMemo(
    () => getSignLanguageProgressPercent(signs, learnedSignIds),
    [learnedSignIds, signs],
  );

  async function toggleLearned(id: string) {
    if (!canPersistProgress) {
      return;
    }

    const sign = signs.find((item) => item.id === id);

    if (!sign) {
      return;
    }

    await progress.toggleLearnedSign(id, sign.word);
  }

  function startCreateSign() {
    if (!canManageSigns) return;
    setSignDraft(DEFAULT_SIGN_DRAFT);
    setSignDraftMode('create');
    setSignDraftError(null);
    setSignSaveMessage(null);
  }

  function startEditSign(sign: SignItem) {
    if (!canManageSigns) return;
    setSignDraft(draftFromSign(sign));
    setSignDraftMode('edit');
    setSignDraftError(null);
    setSignSaveMessage(null);
  }

  function updateSignDraft(updates: Partial<SignLanguageDraft>) {
    setSignDraft((current) => current ? { ...current, ...updates } : current);
    setSignDraftError(null);
    setSignSaveMessage(null);
  }

  function updateSignDraftStep(index: number, updates: Partial<SignLanguageStepDraft>) {
    setSignDraft((current) => current ? {
      ...current,
      videoSteps: current.videoSteps.map((step, stepIndex) =>
        stepIndex === index ? { ...step, ...updates } : step,
      ),
    } : current);
    setSignDraftError(null);
    setSignSaveMessage(null);
  }

  function addSignDraftStep() {
    setSignDraft((current) => current ? {
      ...current,
      videoSteps: [
        ...current.videoSteps,
        { ...DEFAULT_SIGN_STEP, step: current.videoSteps.length + 1 },
      ],
    } : current);
  }

  function removeSignDraftStep(index: number) {
    setSignDraft((current) => current ? {
      ...current,
      videoSteps: current.videoSteps
        .filter((_, stepIndex) => stepIndex !== index)
        .map((step, stepIndex) => ({ ...step, step: stepIndex + 1 })),
    } : current);
  }

  function cancelSignDraft() {
    setSignDraft(null);
    setSignDraftMode(null);
    setSignDraftError(null);
  }

  function saveSignDraft() {
    if (!canManageSigns || !signDraft || !signDraftMode) return;

    const validationError = validateSignDraft(signDraft);
    if (validationError) {
      setSignDraftError(validationError);
      return;
    }

    const nextSign = signFromDraft(signDraft);
    const nextSigns = signDraftMode === 'create'
      ? [...signs, nextSign]
      : signs.map((sign) => sign.id === nextSign.id ? nextSign : sign);

    void saveSigns.mutateAsync(nextSigns).then(() => {
      setSignDraft(null);
      setSignDraftMode(null);
      setSignDraftError(null);
    }).catch(() => undefined);
  }

  function requestDeleteSign(sign: SignItem) {
    if (!canManageSigns) return;
    setPendingDeleteSign(sign);
  }

  function cancelDeleteSign() {
    setPendingDeleteSign(null);
  }

  function confirmDeleteSign() {
    if (!canManageSigns || !pendingDeleteSign) return;

    const id = pendingDeleteSign.id;
    const nextSigns = signs.filter((sign) => sign.id !== id);
    void saveSigns.mutateAsync(nextSigns).then(() => {
      setPendingDeleteSign(null);
      if (signDraft?.id === id) cancelSignDraft();
      if (selectedSignId === id) setSelectedSignId(null);
    }).catch(() => undefined);
  }

  function selectSignOfWeek(signId: string) {
    if (!canManageSigns) return;

    const sign = signs.find((item) => item.id === signId);
    if (!sign) return;

    void saveSignOfWeek.mutateAsync(sign).catch(() => undefined);
  }

  return {
    signs,
    filteredSigns,
    categories,
    filters,
    learnedSignIds,
    learnedCount: learnedSignIds.size,
    progressPercent,
    canPersistProgress,
    canManageSigns,
    selectedSign,
    signOfWeekId: signOfWeek?.id ?? null,
    signDraft,
    signDraftMode,
    signDraftError,
    signManagementError: saveSigns.error || saveSignOfWeek.error,
    signSaveMessage,
    isSavingSigns: saveSigns.isPending || saveSignOfWeek.isPending,
    pendingDeleteSign,
    pageContent: pageContentQuery.payload,
    isLoading: signsQuery.isLoading || signOfWeekQuery.isLoading || pageContentQuery.isLoading || (canPersistProgress && progress.isLoading),
    isSaving: (canPersistProgress && progress.isSaving) || saveSigns.isPending || saveSignOfWeek.isPending,
    signsError: signsQuery.error || signOfWeekQuery.error,
    pageContentError: pageContentQuery.error,
    progressErrorMessage: getOptionalErrorMessage(progress.error),
    setSearchQuery,
    clearSearch: () => setSearchQuery(''),
    setCategoryFilter,
    selectSign: setSelectedSignId,
    closeSign: () => setSelectedSignId(null),
    toggleLearned,
    startCreateSign,
    startEditSign,
    updateSignDraft,
    updateSignDraftStep,
    addSignDraftStep,
    removeSignDraftStep,
    cancelSignDraft,
    saveSignDraft,
    requestDeleteSign,
    cancelDeleteSign,
    confirmDeleteSign,
    selectSignOfWeek,
  };
}

export function useSignLanguageVideoModalState(): SignLanguageVideoModalState {
  const [showSteps, setShowSteps] = useState(false);
  const [viewMode, setViewMode] = useState<SignLanguageViewMode>('gif');
  const [gifLoaded, setGifLoaded] = useState(false);
  const [gifError, setGifError] = useState(false);

  return {
    showSteps,
    viewMode,
    gifLoaded,
    gifError,
    showStepGuide: () => setShowSteps(true),
    hideStepGuide: () => setShowSteps(false),
    toggleStepGuide: () => setShowSteps((current) => !current),
    setViewMode,
    markGifLoaded: () => setGifLoaded(true),
    markGifFailed: () => {
      setGifError(true);
      setGifLoaded(true);
    },
  };
}
