import { TreeLabel, ThreadsStatsRetrieveStatsFields, useLabelsDestroy, useLabelsList, useThreadsStatsRetrieve, ThreadsStatsRetrieve200, useLabelsPartialUpdate } from "@/features/api/gen";
import { getThreadsStatsQueryKey, useMailboxContext } from "@/features/providers/mailbox";
import useArchive from "@/features/message/use-archive";
import useDeleteLabel from "@/features/message/use-delete-label";
import useAddLabel from "@/features/message/use-add-label";
import { DropdownMenu, Icon, IconSize, IconType } from "@gouvfr-lasuite/ui-kit";
import { Button, useModals } from "@gouvfr-lasuite/cunningham-react";
import clsx from "clsx";
import { Link, useLocation, useNavigate } from "@tanstack/react-router";
import { useUrlSearchParams } from "@/hooks/use-url-search-params";
import { useEffect, useEffectEvent, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useQueryClient } from "@tanstack/react-query";
import { useLayoutDragContext } from "@/features/layouts/components/layout-context";
import { MAILBOX_FOLDERS } from "../../../mailbox-list";
import { addToast, ToasterItem } from "@/features/ui/components/toaster";
import { toast } from "react-toastify";
import { useFold } from "@/features/providers/fold";
import { SubLabelCreation } from "../label-form-modal";
import { handle } from "@/features/utils/errors";
import ViewHelper from "@/features/utils/view-helper";

export type LabelTransferData = {
  type: 'label';
  label: Pick<TreeLabel, 'id' | 'display_name' | 'name'>;
}

type LabelItemProps = TreeLabel & {
  level?: number;
  onEdit: (label: TreeLabel | SubLabelCreation) => void;
  canManage: boolean;
  defaultFoldState?: false | undefined;
}

export const LabelItem = ({ level = 0, onEdit, canManage, defaultFoldState, ...label }: LabelItemProps) => {
  const { selectedMailbox, invalidateThreadsStats } = useMailboxContext();
  const modals = useModals();
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
  const [isDragOver, setIsDragOver] = useState(false);
  const queryParams = useMemo(() => {
    const params = new URLSearchParams({ label_slug: label.slug });
    return params.toString();
  }, [label.slug]);
  const { data: stats } = useThreadsStatsRetrieve({
    mailbox_id: selectedMailbox?.id,
    stats_fields: ThreadsStatsRetrieveStatsFields.all_unread,
    label_slug: label.slug
  }, {
    query: {
      queryKey: getThreadsStatsQueryKey(selectedMailbox!.id, queryParams),
    }
  });
  const unreadCount = (stats?.data as ThreadsStatsRetrieve200)?.all_unread ?? 0;
  const { closeLeftPanel, setDragAction, getIsShiftHeld } = useLayoutDragContext();
  const navigate = useNavigate();
  const pathname = useLocation({ select: (l) => l.pathname });
  const searchParams = useUrlSearchParams();
  const { t } = useTranslation();
  const isActive = searchParams.get('label_slug') === label.slug;
  const hasActiveChild = Boolean(searchParams.get('label_slug')?.startsWith(`${label.slug}-`));
  const isFoldedByDefault = label.children.length === 0 ? null : (defaultFoldState ?? !hasActiveChild);
  const foldKey = useMemo(() => `label-item-${label.display_name}${label.children.length > 0 ? `-with-children` : ''}`, [label.display_name, label.children.length]);
  const { isFolded, toggle, setFoldState } = useFold(foldKey, isFoldedByDefault);
  const foldTimeoutRef = useRef<NodeJS.Timeout | null>(null);
  const shouldAutoArchive = !ViewHelper.isArchivedView() && !ViewHelper.isSpamView() && !ViewHelper.isTrashedView() && !ViewHelper.isDraftsView();

  // Suppress the default archive toast: we render our own combined
  // "Label assigned + N archived" toast below.
  const { markAsArchived, markAsUnarchived } = useArchive({ showToast: false });

  const unfoldIfNeeded = useEffectEvent(() => {
    if (isFolded) {
      setFoldState(false);
    }
  });

  const goToDefaultFolder = () => {
    const defaultFolder = MAILBOX_FOLDERS()[0];
    navigate({ to: pathname, search: defaultFolder.filter });
  }
  const moveLabelMutation = useLabelsPartialUpdate();

  const deleteMutation = useLabelsDestroy({
    mutation: {
      onSuccess: () => {
        if (searchParams.get('label_slug') === label.slug ||
          searchParams.get('label_slug')?.startsWith(`${label.slug}-`)) {
          const newSearchParams = new URLSearchParams(searchParams.toString());
          newSearchParams.delete('label_slug');
          if (newSearchParams.toString()) {
            navigate({ to: pathname, search: Object.fromEntries(newSearchParams) });
          } else {
            goToDefaultFolder();
          }
        }
      },
    },
  });
  const queryClient = useQueryClient();
  const labelsQuery = useLabelsList({ mailbox_id: selectedMailbox!.id }, { query: { enabled: false } })
  const hasChildren = label.children && label.children.length > 0;
  const handleClick = (e: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>) => {
    e.preventDefault();
    toggle();
  }

  const { deleteLabel } = useDeleteLabel();
  const { addLabel } = useAddLabel();
  // ThreadLabel shape (no `children`) for the local cache patcher in `addLabel`.
  const threadLabel = useMemo(() => ({
    id: label.id,
    name: label.name,
    slug: label.slug,
    color: label.color,
    display_name: label.display_name,
    description: label.description,
    is_auto: label.is_auto,
  }), [label]);

  const handleDragStart = (e: React.DragEvent<HTMLAnchorElement>) => {
    e.dataTransfer.setData('application/json', JSON.stringify({
      type: 'label',
      label: {
        id: label.id,
        display_name: label.display_name,
        name: label.name
      }
    } as LabelTransferData));
  }

  const handleDragOver = (e: React.DragEvent<HTMLAnchorElement>) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragOver(true);

    if (e.dataTransfer.types.includes('text/thread-drag')) {
      // Safari doesn't expose `e.shiftKey` on drag events, fall back to the
      // globally-tracked ref.
      const shiftHeld = e.shiftKey || getIsShiftHeld();
      // Auto-archive also requires at least one editable thread in the
      // dragged selection — archive is a shared-state mutation. The
      // source advertises this via `text/thread-editable`. Labelling
      // alone only needs mailbox-level `manage_labels` (enforced by
      // `canManage` on the drop handlers below).
      const hasEditable = e.dataTransfer.types.includes('text/thread-editable');
      const action = !shiftHeld && shouldAutoArchive && hasEditable
        ? t('Assign this label and archive')
        : t('Assign this label');
      setDragAction(action);
    }

    if (!foldTimeoutRef.current) {
      foldTimeoutRef.current = setTimeout(() => {
        if (isFolded === true) toggle();
      }, 750);
    }
  };

  const handleDragLeave = () => {
    setIsDragOver(false);
    setDragAction(null);
    if (foldTimeoutRef.current) {
      clearTimeout(foldTimeoutRef.current);
      foldTimeoutRef.current = null;
    }
  };

  const handleDropThread = (transferData: { threadIds?: string[], labels: string[], hasEditable?: boolean }, shiftKeyHeld: boolean = false) => {
    const canBeAssigned = !transferData.labels.includes(label.id);
    if (!canBeAssigned) return;

    if (!transferData.threadIds || transferData.threadIds.length === 0) return;

    const threadIds = transferData.threadIds;
    // Archive is gated on per-thread edit rights; without any editable
    // thread we fall back to assign-only.
    const doArchive = !shiftKeyHeld && shouldAutoArchive && transferData.hasEditable === true;
    const toastId = `label-assign-${label.id}-${Date.now()}`;

    addLabel({
      label: threadLabel,
      threadIds,
      onSuccess: () => {
        invalidateThreadsStats();
        if (doArchive) {
          markAsArchived({
            threadIds,
            onSuccess: (_, updatedCount) => {
              // Label assignment is fully successful under the current
              // permission model (all dragged threads belong to the
              // label's mailbox), so partial/none status is driven by
              // the archive mutation alone.
              const archivedCount = updatedCount ?? threadIds.length;
              const submittedCount = threadIds.length;
              const isNone = archivedCount === 0;
              const isPartial = archivedCount > 0 && archivedCount < submittedCount;
              const toastType = isNone ? 'error' : isPartial ? 'warning' : 'info';

              const undo = () => {
                deleteLabel({
                  labelId: label.id,
                  labelSlug: label.slug,
                  threadIds,
                });
                if (archivedCount > 0) {
                  markAsUnarchived({
                    threadIds,
                    onSuccess: () => toast.dismiss(toastId),
                  });
                } else {
                  toast.dismiss(toastId);
                }
              };

              const mainMessage = isNone
                ? t('Label "{{label}}" assigned, but no threads could be archived.', { label: label.name })
                : isPartial
                  ? t('Label "{{label}}" assigned. {{count}} of {{total}} threads archived.', {
                      count: archivedCount,
                      total: submittedCount,
                      label: label.name,
                    })
                  : t('Label "{{label}}" assigned and {{count}} threads archived.', {
                      count: archivedCount,
                      label: label.name,
                      defaultValue_one: 'Label "{{label}}" assigned and thread archived.',
                      defaultValue_other: 'Label "{{label}}" assigned and {{count}} threads archived.',
                    });

              addToast(
                <ToasterItem type={toastType} actions={[{ label: t('Undo'), onClick: undo }]}>
                  <Icon name="label" type={IconType.OUTLINED} />
                  <div>
                    <p>{mainMessage}</p>
                    {(isPartial || isNone) && (
                      <p>{t('You may not have sufficient permissions for all selected threads.')}</p>
                    )}
                  </div>
                </ToasterItem>,
                { toastId }
              );
            },
          });
        } else {
          const undo = () => {
            deleteLabel({
              labelId: label.id,
              labelSlug: label.slug,
              threadIds,
            });
            toast.dismiss(toastId);
          };

          addToast(
            <ToasterItem type="info" actions={[{ label: t('Undo'), onClick: undo }]}>
              <Icon name="label" type={IconType.OUTLINED} />
              <span>{t('Label "{{label}}" assigned to {{count}} threads.', {
                count: threadIds.length,
                label: label.name,
                defaultValue_one: 'Label "{{label}}" assigned to this thread.',
                defaultValue_other: 'Label "{{label}}" assigned to {{count}} threads.',
              })}</span>
            </ToasterItem>,
            { toastId }
          );
        }
      },
    });
  }

  const handleDropLabel = (transferData: LabelTransferData) => {
    // If label is dropped on itself do nothing.
    if (transferData.label.id === label.id) return;
    // If label is dropped on a child do nothing.
    if (label.name.startsWith(`${transferData.label.name}/`)) return;
    // If label is dropped on a direct parent do nothing.
    if (transferData.label.name === `${label.name}/${transferData.label.display_name}`) return;

    moveLabelMutation.mutate({
      id: transferData.label.id,
      data: {
        name: [label.name, transferData.label.display_name].join('/'),
      }
    }, {
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: labelsQuery.queryKey });
      },
    });
  }

  const handleDrop = (e: React.DragEvent<HTMLAnchorElement>) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragOver(false);
    setDragAction(null);
    const rawData = e.dataTransfer.getData('application/json');
    if (!rawData) return;

    try {
      const data = JSON.parse(rawData);

      if (data.type === 'thread') handleDropThread(data, e.shiftKey || getIsShiftHeld());
      else if (data.type === 'label') handleDropLabel(data);
    } catch (error) {
      handle(new Error('Error parsing drag data.'), { extra: { error } });
    }
  };

  const handleDelete = async () => {
    const decision = await modals.deleteConfirmationModal({
      title: <span className="c__modal__text--centered">{t('Delete label "{{label}}"', { label: label.display_name })}</span>,
      children: t('Are you sure you want to delete this label? This action is irreversible!'),
    });

    if (decision === 'delete') {
      deleteMutation.mutate({ id: label.id }, {
        onSuccess: () => {
          queryClient.invalidateQueries({ queryKey: labelsQuery.queryKey });
        },
      })
    }
  }

  const getPaddingLeftItem = (level: number) => {
    const offset = 1;

    return `${offset * level}rem`;
  }

  useEffect(() => {
    if (hasActiveChild) {
      unfoldIfNeeded();
    }
  }, [hasActiveChild]);

  return (
    <>
      <Link
        to="."
        search={{ label_slug: label.slug }}
        onClick={closeLeftPanel}
        className={clsx("label-item", isActive && "label-item--active", isDragOver && "label-item--drag-over")}
        style={level > 0 ? { paddingLeft: getPaddingLeftItem(level) } : {}}
        data-focus-within={isDropdownOpen}
        title={label.display_name}
        onDragStart={handleDragStart}
        onDragOver={canManage ? handleDragOver : undefined}
        onDragLeave={canManage ? handleDragLeave : undefined}
        onDrop={canManage ? handleDrop : undefined}
      >
        <div className="label-item__column">
          {hasChildren && (
            <Button
              onClick={handleClick}
              variant="tertiary"
              size="nano"
              color="brand"
              className='label-item__toggle'
              aria-expanded={isFolded}
              title={isFolded ? t('Collapse') : t('Expand')}
              icon={<Icon name={isFolded ? "chevron_right" : "expand_more"} size={IconSize.MEDIUM} />}
              aria-label={isFolded ? t('Expand') : t('Collapse')}
            />
          )}
          <div className="label-item__name">
            <span className="label-item__icon" aria-hidden="true" style={{ backgroundColor: label.color }} />
            <span className="label-name label-name--truncated">{label.display_name}</span>
          </div>
        </div>
        <div className="label-item__column">
          {canManage && (
            <div className="label-item__dropdown-actions">
              <DropdownMenu
                isOpen={isDropdownOpen}
                onOpenChange={setIsDropdownOpen}
                options={[
                  {
                    label: t('Edit'),
                    icon: <Icon name="edit" />,
                    callback: () => onEdit(label),
                  },
                  {
                    label: t('Add a sub-label'),
                    icon: <Icon name="add" />,
                    callback: () => onEdit({ name: `${label.name}/`, color: label.color }),
                    showSeparator: true,
                  },
                  {
                    label: t('Delete'),
                    icon: <Icon name="delete" />,
                    callback: handleDelete,
                    variant: 'danger'
                  },
                ]}
              >
                <Button
                  onClick={() => setIsDropdownOpen(true)}
                  icon={<Icon name="more_horiz" />}
                  variant="tertiary"
                  aria-label={t('More options')}
                  size="nano"
                />
              </DropdownMenu>
            </div>
          )}
          {unreadCount > 0 && <span className="mailbox__item-counter">{unreadCount}</span>}
        </div>
      </Link>

      {hasChildren && isFolded === false && (
        <div className="label-children">
          {label.children.map((child) => (
            <LabelItem key={child.id} {...child} level={level + 1} onEdit={onEdit} canManage={canManage} defaultFoldState={defaultFoldState} />
          ))}
        </div>
      )}
    </>
  );
}
