import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { getFolderTree } from '../lib/apiClient';
import {
  TrashIcon,
  AnalyzeIcon,
  IconX,
  FolderOutlineIcon,
  TagIcon,
  CorrespondentIcon,
} from '../ui/icons';
import SelectionAssignmentMenu, { SelectionAssignmentMenuItem } from './SelectionAssignmentMenu';
import SelectionSummary from './SelectionSummary';
import { useAppState } from '../app/appState';
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
import { DEFAULT_FOLDER_NAME } from '../constants/workspace';
import type { DocumentId } from '../types/identifiers';

type NullableDocumentId = DocumentId | null;

type SelectedIdList = NullableDocumentId[] | null;

type FolderTreeNode = {
  id?: DocumentId;
  name?: string;
  label?: string;
  value?: DocumentId;
  children?: FolderTreeNode[];
};

interface TagOption {
  id?: DocumentId;
  label?: string;
  name?: string;
  color?: string | null;
}

interface CorrespondentOption {
  id?: DocumentId;
  name?: string;
  label?: string;
}

import type { Document } from '../types/documents';

interface BulkTagMutationArgs {
  label: string;
  input: unknown;
  documentIds: DocumentId[];
}

interface BulkCorrespondentAddArgs {
  name: string;
  input: unknown;
  documentIds: DocumentId[];
}

interface BulkCorrespondentRemoveArgs {
  assignments: Array<{ correspondent_id: DocumentId }>;
  documentIds: DocumentId[];
}

export interface SelectionFloatingActionsProps {
  selectionCount?: number;
  selectedDocumentIds?: SelectedIdList;
  selectedFolderIds?: SelectedIdList;
  documentLookup?: Map<DocumentId, Document> | null;
  tags?: TagOption[] | null;
  tagLookupById?: Map<DocumentId, TagOption> | null;
  correspondents?: CorrespondentOption[] | null;
  folderOptions?: SelectionAssignmentMenuItem[] | null;
  onBulkTagAdd?: (args: BulkTagMutationArgs) => Promise<void> | void;
  onBulkTagRemove?: (args: BulkTagMutationArgs) => Promise<void> | void;
  onBulkCorrespondentAdd?: (args: BulkCorrespondentAddArgs) => Promise<void> | void;
  onBulkCorrespondentRemove?: (args: BulkCorrespondentRemoveArgs) => Promise<void> | void;
  onBulkReanalyze?: (documentIds: DocumentId[]) => Promise<void> | void;
  onDeleteSelection?: () => void;
  onClearSelection?: () => void;
  onMoveDocumentsToFolder?: (documentIds: DocumentId[], folderId: DocumentId) => Promise<void> | void;
}

const normalizeDocumentList = (selectedIds?: SelectedIdList): DocumentId[] =>
  Array.isArray(selectedIds)
    ? selectedIds.filter((value): value is DocumentId => value !== null && value !== undefined)
    : [];

const isRecord = (value: unknown): value is Record<string, unknown> => value != null && Object(value) === value;

const splitLabelSegments = (input: unknown): string[] => {
  const text = `${input ?? ''}`.trim();
  return text ? text.split('/') : [];
};

const buildFolderTreeOptions = (tree?: FolderTreeNode[] | null): SelectionAssignmentMenuItem[] => {
  const entries: SelectionAssignmentMenuItem[] = [];

  const traverse = (nodes: FolderTreeNode[] | null, parentSegments: string[]) => {
    if (!Array.isArray(nodes) || nodes.length === 0) {
      return;
    }
    nodes.forEach((node) => {
      if (!node || !node.id) {
        return;
      }
      const trimmedName = node.name?.trim?.();
      const name = trimmedName?.length ? trimmedName : 'Folder';
      const nextSegments = parentSegments.concat([name]);
      const label = nextSegments.join('/');
      entries.push({
        id: node.id,
        label,
        state: 'none',
        payload: {
          id: node.id,
          label,
          segments: nextSegments,
          depth: Math.max(nextSegments.length - 1, 0),
        },
      });
      if (Array.isArray(node.children) && node.children.length) {
        traverse(node.children, nextSegments);
      }
    });
  };

  traverse(Array.isArray(tree) ? tree : [], [DEFAULT_FOLDER_NAME]);

  entries.sort((a, b) => (a.label || '').localeCompare(b.label || '', undefined, { sensitivity: 'base' }));

  return [{ id: 'root', label: DEFAULT_FOLDER_NAME, state: 'none', payload: { id: 'root' } }, ...entries];
};

const buildTagAssignments = (
  selectedDocuments: Document[],
  tagLookupById: Map<DocumentId, TagOption> | null,
  tags: TagOption[] | null,
  total: number,
): SelectionAssignmentMenuItem[] => {
  if (!total) {
    return [];
  }

  const map = new Map<string, {
    id?: DocumentId;
    label: string;
    color: string | null;
    count: number;
    total: number;
  }>();

  const ensureEntry = (id?: DocumentId, label?: string, color: string | null = null) => {
    const key = id ?? label;
    if (!key || !label) {
      return null;
    }
    if (!map.has(key)) {
      map.set(key, {
        id,
        label,
        color,
        count: 0,
        total,
      });
    }
    return map.get(key) ?? null;
  };

  selectedDocuments.forEach((doc) => {
    (doc?.tags || []).forEach((tag) => {
      const lookupColor = tag?.id && tagLookupById instanceof Map ? tagLookupById.get(tag.id)?.color : null;
      const entry = ensureEntry(tag?.id, tag?.label, tag?.color ?? lookupColor ?? null);
      if (entry) {
        entry.count += 1;
      }
    });
  });

  (tags || []).forEach((tag) => {
    const lookupColor = tag?.id && tagLookupById instanceof Map ? tagLookupById.get(tag.id)?.color : null;
    ensureEntry(tag?.id, tag?.label, tag?.color ?? lookupColor ?? null);
  });

  return Array.from(map.values()).map((entry) => {
    const count = entry.count || 0;
    const state = count === total ? 'all' : count > 0 ? 'partial' : 'none';
    return {
      id: entry.id ?? entry.label,
      label: entry.label,
      color: entry.color ?? null,
      count,
      total,
      state,
      payload: entry,
    };
  });
};

const buildCorrespondentAssignments = (
  selectedDocuments: Document[],
  correspondents: CorrespondentOption[] | null,
  total: number,
): SelectionAssignmentMenuItem[] => {
  if (!total) {
    return [];
  }

  const map = new Map<string, {
    id?: DocumentId;
    label: string;
    count: number;
    total: number;
  }>();

  const ensureEntry = (id?: DocumentId, name?: string) => {
    const key = id ?? name;
    if (!key || !name) {
      return null;
    }
    if (!map.has(key)) {
      map.set(key, {
        id,
        label: name,
        count: 0,
        total,
      });
    }
    return map.get(key) ?? null;
  };

  selectedDocuments.forEach((doc) => {
    (doc?.correspondents || []).forEach((entry) => {
      const target = ensureEntry(entry?.id, entry?.name);
      if (target) {
        target.count += 1;
      }
    });
  });

  (correspondents || []).forEach((entry) => {
    ensureEntry(entry?.id, entry?.name || entry?.label);
  });

  return Array.from(map.values()).map((entry) => {
    const count = entry.count || 0;
    const state = count === total ? 'all' : count > 0 ? 'partial' : 'none';
    return {
      id: entry.id ?? entry.label,
      label: entry.label,
      count,
      total,
      state,
      payload: entry,
    };
  });
};

const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
  selectionCount = 0,
  selectedDocumentIds = [],
  selectedFolderIds = [],
  documentLookup,
  tags = [],
  tagLookupById,
  correspondents = [],
  folderOptions = [],
  onBulkTagAdd,
  onBulkTagRemove,
  onBulkCorrespondentAdd,
  onBulkCorrespondentRemove,
  onBulkReanalyze,
  onDeleteSelection,
  onClearSelection = null,
  onMoveDocumentsToFolder,
}) => {
  const { token, tenant } = useAppState() as { token?: string; tenant?: { id?: DocumentId } | null };
  const tenantId = tenant?.id ?? null;

  const documentLookupMap = useMemo(() => (
    documentLookup instanceof Map ? documentLookup : new Map<DocumentId, Document>()
  ), [documentLookup]);
  const tagLookupMap = tagLookupById instanceof Map ? tagLookupById : null;

  const [remoteFolderOptions, setRemoteFolderOptions] = useState<SelectionAssignmentMenuItem[] | null>(null);
  const folderTreeFetchRef = useRef<Promise<SelectionAssignmentMenuItem[]> | null>(null);

  useEffect(() => {
    setRemoteFolderOptions(null);
    folderTreeFetchRef.current = null;
  }, [tenantId, token]);

  const requestFolderTree = useCallback(async (): Promise<SelectionAssignmentMenuItem[]> => {
    if (!token) {
      setRemoteFolderOptions([]);
      return [];
    }

    if (Array.isArray(remoteFolderOptions)) {
      return remoteFolderOptions;
    }

    if (folderTreeFetchRef.current) {
      return folderTreeFetchRef.current;
    }

    const fetchPromise = (async () => {
      try {
        const data = await getFolderTree();
        const options = buildFolderTreeOptions(data);
        setRemoteFolderOptions(options);
        return options;
      } catch (error) {
        console.warn('[selection] Failed to load folder tree', error);
        setRemoteFolderOptions([]);
        return [];
      } finally {
        folderTreeFetchRef.current = null;
      }
    })();

    folderTreeFetchRef.current = fetchPromise;
    return fetchPromise;
  }, [remoteFolderOptions, token]);

  const handleMoveMenuOpen = useCallback(() => {
    requestFolderTree();
  }, [requestFolderTree]);

  const effectiveFolderOptions = useMemo<SelectionAssignmentMenuItem[]>(() => {
    if (remoteFolderOptions !== null) {
      return remoteFolderOptions;
    }
    return Array.isArray(folderOptions) ? folderOptions : [];
  }, [remoteFolderOptions, folderOptions]);

  const documentIdList = useMemo<DocumentId[]>(
    () => normalizeDocumentList(selectedDocumentIds),
    [selectedDocumentIds],
  );

  const folderIdList = useMemo<DocumentId[]>(
    () => normalizeDocumentList(selectedFolderIds),
    [selectedFolderIds],
  );

  const documentCount = documentIdList.length;
  const folderCount = folderIdList.length;
  const totalCount = selectionCount ?? documentCount + folderCount;

  const selectedDocuments = useMemo<Document[]>(() => {
    if (!documentIdList.length || !(documentLookupMap instanceof Map)) {
      return [];
    }
    return documentIdList
      .map((id) => documentLookupMap.get(id))
      .filter((doc): doc is Document => Boolean(doc));
  }, [documentIdList, documentLookupMap]);

  const selectedDocCount = selectedDocuments.length;

  const moveAssignments = useMemo<SelectionAssignmentMenuItem[]>(() => {
    if (!Array.isArray(effectiveFolderOptions)) {
      return [];
    }
    return effectiveFolderOptions
      .map<SelectionAssignmentMenuItem | null>((option) => {
        const id = (option?.id ?? option?.payload?.id ?? option?.value) as DocumentId | undefined;
        if (!id) {
          return null;
        }
        const label = option?.label || option?.payload?.label || option?.name || String(id);
        const segments = splitLabelSegments(label);
        const depth = Math.max(segments.length - 1, 0);
        return {
          id,
          label,
          state: 'none',
          payload: {
            id,
            label,
            segments,
            depth,
          },
        };
      })
      .filter((entry): entry is SelectionAssignmentMenuItem => Boolean(entry));
  }, [effectiveFolderOptions]);

  const tagAssignments = useMemo(
    () => buildTagAssignments(selectedDocuments, tagLookupMap, tags, selectedDocCount),
    [selectedDocuments, tagLookupMap, tags, selectedDocCount],
  );

  const correspondentAssignments = useMemo(
    () => buildCorrespondentAssignments(selectedDocuments, correspondents, selectedDocCount),
    [selectedDocuments, correspondents, selectedDocCount],
  );

  const renderFolderLabel = useCallback((item: SelectionAssignmentMenuItem) => {
    const payload = (item?.payload as { segments?: string[]; depth?: number }) || {};
    const segments = payload.segments || splitLabelSegments(item.label);
    const depth = payload.depth ?? Math.max(segments.length - 1, 0);
    const clampedDepth = Math.min(depth, 6);
    const indentWidth = clampedDepth > 0 ? clampedDepth * 0.9 : 0;
    const name = segments.length ? segments[segments.length - 1] : item?.label || 'Folder';
    const parentPath = segments.length > 1 ? segments.slice(0, -1).join(' / ') : '';

    return (
      <>
        {indentWidth ? (
          <span
            className="selection-assignment__indent"
            style={{ width: `${indentWidth}rem` }}
            aria-hidden="true"
          />
        ) : null}
        <span className="selection-assignment__folder-label">
          <span className="selection-assignment__folder-name">{name}</span>
          {parentPath ? (
            <span className="selection-assignment__folder-path">{parentPath}</span>
          ) : null}
        </span>
      </>
    );
  }, []);

  const handleToggleTagAssignment = useCallback(
    async (item: SelectionAssignmentMenuItem) => {
      if (!selectedDocCount || !item) {
        return;
      }
      if (item.state === 'all') {
        await onBulkTagRemove?.({ label: item.label || '', input: null, documentIds: documentIdList });
      } else {
        await onBulkTagAdd?.({ label: item.label || '', input: null, documentIds: documentIdList });
      }
    },
    [selectedDocCount, onBulkTagAdd, onBulkTagRemove, documentIdList],
  );

  const handleCreateTagAssignment = useCallback(
    async (label: string) => {
      if (!selectedDocCount || !label) {
        return;
      }
      await onBulkTagAdd?.({ label, input: null, documentIds: documentIdList });
    },
    [selectedDocCount, onBulkTagAdd, documentIdList],
  );

  const handleToggleCorrespondentAssignment = useCallback(
    async (item: SelectionAssignmentMenuItem) => {
      if (!selectedDocCount || !item) {
        return;
      }
      if (item.state === 'all') {
        if (!item.id) {
          return;
        }
        await onBulkCorrespondentRemove?.({
          assignments: [{ correspondent_id: item.id }],
          documentIds: documentIdList,
        });
      } else {
        await onBulkCorrespondentAdd?.({ name: item.label || '', input: null, documentIds: documentIdList });
      }
    },
    [selectedDocCount, onBulkCorrespondentAdd, onBulkCorrespondentRemove, documentIdList],
  );

  const handleCreateCorrespondentAssignment = useCallback(
    async (name: string) => {
      if (!selectedDocCount || !name) {
        return;
      }
      await onBulkCorrespondentAdd?.({ name, input: null, documentIds: documentIdList });
    },
    [selectedDocCount, onBulkCorrespondentAdd, documentIdList],
  );

  const handleMoveSelectionToFolder = useCallback(
    async (option: unknown) => {
      if (!documentIdList.length || !onMoveDocumentsToFolder) {
        return;
      }
      const candidate = option as { id?: DocumentId; value?: DocumentId } | DocumentId | null;
      const value = isRecord(candidate)
        ? (candidate?.id ?? candidate?.value ?? null)
        : candidate;
      if (!value) {
        return;
      }
      await onMoveDocumentsToFolder(documentIdList, value as DocumentId);
    },
    [documentIdList, onMoveDocumentsToFolder],
  );

  const summaryNode = totalCount > 0 ? (
    <SelectionSummary
      documentCount={documentCount}
      folderCount={folderCount}
      totalCount={totalCount}
    />
  ) : null;

  const showPrimaryButtons = Boolean(onBulkReanalyze || onDeleteSelection || onClearSelection);

  const moveMenu = onMoveDocumentsToFolder ? (
    <SelectionAssignmentMenu
      label="Move"
      triggerContent={(
        <span className="quick-add__chip-label" title="Move">
          <FolderOutlineIcon className="icon-inline" aria-hidden="true" />
          <span className="quick-add__chip-text" aria-hidden="true">Move</span>
        </span>
      )}
      items={moveAssignments}
      placeholder="Search folders…"
      emptyMessage="No folders"
      onToggle={(item) => handleMoveSelectionToFolder(item?.payload || item)}
      disabled={!documentCount}
      createLabel={null}
      showStateIndicators={false}
      showCounts={false}
      onOpenMenu={handleMoveMenuOpen}
      renderItemLabel={renderFolderLabel}
    />
  ) : null;

  const primaryButtons = showPrimaryButtons ? (
    <div className="panel-floating__buttons">
      {onBulkReanalyze ? (
        <button
          type="button"
          className="icon-button panel-floating-actions__button"
          onClick={() => onBulkReanalyze(documentIdList)}
          aria-label="Re-run analysis for selection"
          title="Re-run analysis for selection"
          disabled={documentIdList.length === 0}
        >
          <AnalyzeIcon className="icon-inline" />
        </button>
      ) : null}
      {onDeleteSelection ? (
        <button
          type="button"
          className="icon-button danger panel-floating-actions__button"
          onClick={onDeleteSelection}
          aria-label="Delete selected items"
          disabled={totalCount === 0}
        >
          <TrashIcon className="icon-inline" />
        </button>
      ) : null}
      {onClearSelection ? (
        <button
          type="button"
          className="icon-button panel-floating-actions__button"
          onClick={onClearSelection}
          aria-label="Clear selection"
          title="Clear selection"
          disabled={totalCount === 0}
        >
          <IconX className="icon-inline" />
        </button>
      ) : null}
    </div>
  ) : null;

  return (
    <>
      {summaryNode ? (
        <span className="panel-floating__label">{summaryNode}</span>
      ) : null}
      <div className="panel-floating-actions panel-floating-actions--assignments">
        {moveMenu}
        <SelectionAssignmentMenu
          label="Tags"
          triggerContent={(
            <span className="quick-add__chip-label" title="Tags">
              <TagIcon className="icon-inline" aria-hidden="true" />
              <span className="quick-add__chip-text" aria-hidden="true">Tags</span>
            </span>
          )}
          items={tagAssignments}
          placeholder="Search tags…"
          emptyMessage="No tags"
          createLabel="Create"
          onToggle={handleToggleTagAssignment}
          onCreate={handleCreateTagAssignment}
          disabled={!documentCount}
        />
        <SelectionAssignmentMenu
          label="Correspondents"
          triggerContent={(
            <span className="quick-add__chip-label" title="Correspondents">
              <CorrespondentIcon className="icon-inline" aria-hidden="true" />
              <span className="quick-add__chip-text" aria-hidden="true">Correspondents</span>
            </span>
          )}
          items={correspondentAssignments}
          placeholder="Search correspondents…"
          emptyMessage="No correspondents"
          createLabel="Create"
          onToggle={handleToggleCorrespondentAssignment}
          onCreate={handleCreateCorrespondentAssignment}
          disabled={!documentCount}
        />
      </div>
      {primaryButtons}
    </>
  );
};

export type SelectionFloatingPanelProps = Omit<SelectionFloatingActionsProps, 'selectedDocumentIds' | 'selectedFolderIds' | 'selectionCount'>;

export const SelectionFloatingPanel: React.FC<SelectionFloatingPanelProps> = ({ onClearSelection, ...rest }) => {
  const { selectedDocumentIds, selectedFolderIds, clearSelection } = useWorkspaceSelectionContext();
  const documentIds = Array.isArray(selectedDocumentIds) ? selectedDocumentIds : [];
  const folderIds = Array.isArray(selectedFolderIds) ? selectedFolderIds : [];
  const selectionCount = documentIds.length + folderIds.length;
  if (selectionCount === 0) {
    return null;
  }
  const handleClear = onClearSelection || clearSelection;
  return (
    <div className="panel-floating-region" aria-live="polite" aria-atomic="true">
      <div className="panel-floating">
        <SelectionFloatingActions
          selectionCount={selectionCount}
          selectedDocumentIds={documentIds}
          selectedFolderIds={folderIds}
          onClearSelection={handleClear}
          {...rest}
        />
      </div>
    </div>
  );
};


export default SelectionFloatingActions;
