import type { DocumentId, TagId } from '../types/identifiers';
import { TAG_MIME_TYPES, TAG_TEXT_MIME_TYPE } from '../constants/documents';

interface TagPayload {
  id: TagId;
  label: string;
  sourceDocId: DocumentId | null;
}

interface TagLike {
  id?: TagId;
  label?: string | null;
}

const serializePayload = (payload: TagPayload): string | null => {
  try {
    return JSON.stringify(payload);
  } catch (error) {
    console.warn('[tagTransfer] Failed to serialize payload', error);
    return null;
  }
};

export const createTagTransferPayload = (
  tag?: TagLike | null,
  sourceDocId: DocumentId | null = null,
): TagPayload | null => {
  if (!tag || tag.id == null) {
    return null;
  }

  return {
    id: tag.id,
    label: tag.label || '',
    sourceDocId: sourceDocId ?? null,
  };
};

export const writeTagTransferData = (
  dataTransfer: DataTransfer | null,
  tag: TagLike,
  sourceDocId: DocumentId | null = null,
): void => {
  if (!dataTransfer) {
    return;
  }

  const payload = createTagTransferPayload(tag, sourceDocId);
  if (!payload) {
    return;
  }

  const serialized = serializePayload(payload);
  if (!serialized) {
    return;
  }

  try {
    TAG_MIME_TYPES.forEach((type) => dataTransfer.setData(type, serialized));
    if (payload.label) {
      dataTransfer.setData(TAG_TEXT_MIME_TYPE, payload.label);
    }
  } catch (error) {
    console.warn('[tagTransfer] Failed to write drag data', error);
  }
};

export const readTagTransferData = (dataTransfer?: DataTransfer | null): string | null => {
  if (!dataTransfer) {
    return null;
  }

  for (let index = 0; index < TAG_MIME_TYPES.length; index += 1) {
    const type = TAG_MIME_TYPES[index];
    try {
      const raw = dataTransfer.getData(type);
      if (raw) {
        return raw;
      }
    } catch (error) {
      console.warn('[tagTransfer] Failed to read drag data for type', type, error);
    }
  }
  return null;
};

type DragEventLike = DragEvent | DataTransfer | {
  dataTransfer?: DataTransfer | null;
  type?: string;
  preventDefault?: () => void;
  stopPropagation?: () => void;
};

export const parseTagTransferPayload = (input: DataTransfer | DragEventLike | null): TagPayload | null => {
  let dataTransfer: DataTransfer | null = null;
  if (input instanceof DataTransfer) {
    dataTransfer = input;
  } else if (input && Object(input) === input && 'dataTransfer' in (input as Record<string, unknown>)) {
    const candidate = (input as { dataTransfer?: DataTransfer | null }).dataTransfer;
    if (candidate) {
      dataTransfer = candidate;
    }
  }
  const raw = readTagTransferData(dataTransfer || null);
  if (!raw) {
    return null;
  }

  try {
    return JSON.parse(raw) as TagPayload;
  } catch (error) {
    console.warn('[tagTransfer] Failed to parse drag payload', error);
  }

  return null;
};

export const isTagTransferEvent = (event?: DragEventLike | null): boolean => {
  if (!event) {
    return false;
  }
  let types: DOMStringList | ReadonlyArray<string> | undefined;
  if (event instanceof DataTransfer) {
    types = event.types;
  } else if (Object(event) === event && 'dataTransfer' in (event as Record<string, unknown>)) {
    const payload = (event as { dataTransfer?: DataTransfer | null }).dataTransfer;
    types = payload?.types;
  }
  if (!types) {
    return false;
  }
  const typeList = Array.isArray(types) ? [...types] : Array.from(types);
  return TAG_MIME_TYPES.some((type) => typeList.includes(type));
};

export { TAG_MIME_TYPES };
