import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
import { findChildrenByType, findParentNodeOfType, findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { mapSlice } from '../utils/slice';
import { getSupportedListTypesSet, isBulletOrOrderedList, isTaskList, convertBlockToInlineContent } from './list-utils';
const getContentSupportChecker = targetNodeType => {
  return node => {
    try {
      return targetNodeType.validContent(Fragment.from(node));
    } catch {
      return false;
    }
  };
};
export const createBlockTaskItem = ({
  attrs,
  content,
  marks,
  schema
}) => {
  const {
    blockTaskItem,
    paragraph
  } = schema.nodes;
  const newParagraph = paragraph.createChecked(null, content, marks === null || marks === void 0 ? void 0 : marks.filter(mark => blockTaskItem.allowsMarkType(mark.type)));
  return blockTaskItem.create(attrs !== null && attrs !== void 0 ? attrs : null, newParagraph);
};
export const transformListRecursively = (props, onhandleUnsupportedContent) => {
  const transformedItems = [];
  const {
    listNode,
    isSourceBulletOrOrdered,
    isTargetBulletOrOrdered,
    isSourceTask,
    isTargetTask,
    supportedListTypes,
    schema,
    targetNodeType
  } = props;
  const {
    taskList,
    listItem,
    taskItem,
    paragraph,
    blockTaskItem
  } = schema.nodes;

  // gating behind platform_editor_small_font_size to support task lists with font size applied,
  // but keep this solution general
  const isBlockTaskEnabled = !!blockTaskItem && expValEquals('platform_editor_small_font_size', 'isEnabled', true);

  /**
   * Extracts paragraph children from a blockTaskItem, preserving their marks.
   */
  const extractParagraphsFromBlockTaskItem = node => {
    const paragraphs = [];
    node.forEach(child => {
      if (child.type === paragraph) {
        paragraphs.push(child);
      }
    });
    return paragraphs;
  };
  listNode.forEach(child => {
    if (isSourceBulletOrOrdered && isTargetTask) {
      // Convert bullet/ordered => task
      if (child.type === listItem) {
        const inlineContent = [];
        const nestedTaskLists = [];
        let blockMarks = [];
        child.forEach(grandChild => {
          if (supportedListTypes.has(grandChild.type) && grandChild.type !== taskList) {
            nestedTaskLists.push(transformListRecursively({
              ...props,
              listNode: grandChild
            }, onhandleUnsupportedContent));
          } else if (!getContentSupportChecker(taskItem)(grandChild) && !grandChild.isTextblock) {
            onhandleUnsupportedContent === null || onhandleUnsupportedContent === void 0 ? void 0 : onhandleUnsupportedContent(grandChild);
          } else {
            if (isBlockTaskEnabled && grandChild.type === paragraph && grandChild.marks.length > 0) {
              blockMarks = grandChild.marks;
            }
            inlineContent.push(...convertBlockToInlineContent(grandChild, schema));
          }
        });
        if (isBlockTaskEnabled && blockMarks.length > 0) {
          transformedItems.push(createBlockTaskItem({
            content: inlineContent,
            marks: blockMarks,
            schema
          }));
        } else {
          transformedItems.push(taskItem.create(null, inlineContent.length > 0 ? inlineContent : null));
        }
        transformedItems.push(...nestedTaskLists);
      }
    } else if (isSourceTask && isTargetBulletOrOrdered) {
      // Convert task => bullet/ordered
      if (child.type === taskItem) {
        const inlineContent = [...child.content.content];

        // Transfer taskItem's block marks to the paragraph.
        // Use listItem.allowsMarkType since the paragraph will be inside a listItem
        // (which uses ParagraphWithFontSizeStage0 that allows fontSize).
        const paragraphMarks = isBlockTaskEnabled && child.marks.length > 0 ? child.marks.filter(mark => listItem.allowsMarkType(mark.type)) : undefined;
        const paragraphNode = paragraph.create(null, inlineContent.length > 0 ? inlineContent : null, paragraphMarks);
        transformedItems.push(listItem.create(null, [paragraphNode]));
      } else if (isBlockTaskEnabled && child.type === blockTaskItem) {
        // blockTaskItem wraps content in paragraphs — extract them directly,
        // preserving their fontSize marks
        const paragraphs = extractParagraphsFromBlockTaskItem(child);
        if (paragraphs.length > 0) {
          transformedItems.push(listItem.create(null, paragraphs));
        }
      } else if (child.type === taskList) {
        const transformedNestedList = transformListRecursively({
          ...props,
          listNode: child
        }, onhandleUnsupportedContent);
        const lastItem = transformedItems[transformedItems.length - 1];
        if ((lastItem === null || lastItem === void 0 ? void 0 : lastItem.type) === listItem) {
          // Attach nested list to previous item
          const updatedContent = [...lastItem.content.content, transformedNestedList];
          transformedItems[transformedItems.length - 1] = listItem.create(lastItem.attrs, updatedContent);
        } else {
          // No previous item, flatten nested items
          transformedItems.push(...transformedNestedList.content.content);
        }
      }
    } else if (isSourceBulletOrOrdered && isTargetBulletOrOrdered) {
      if (child.type === listItem) {
        const convertedNestedLists = [];
        child.forEach(grandChild => {
          if (supportedListTypes.has(grandChild.type) && grandChild.type !== targetNodeType) {
            const convertedNode = transformListRecursively({
              ...props,
              listNode: grandChild
            }, onhandleUnsupportedContent);
            convertedNestedLists.push(convertedNode);
          } else {
            convertedNestedLists.push(grandChild);
          }
        });
        transformedItems.push(listItem.create(null, convertedNestedLists));
      }
    }
  });
  return targetNodeType.create(null, transformedItems);
};

/**
 * Transform list structure between different list types
 */
export const transformListStructure = context => {
  const {
    tr,
    sourceNode,
    sourcePos,
    targetNodeType
  } = context;
  const nodes = tr.doc.type.schema.nodes;
  const unsupportedContent = [];
  const onhandleUnsupportedContent = content => {
    unsupportedContent.push(content);
  };
  try {
    const listNode = {
      node: sourceNode,
      pos: sourcePos
    };
    const {
      node: sourceList,
      pos: listPos
    } = listNode;
    // const { taskList, listItem, taskItem, paragraph } = nodes;

    const isSourceBulletOrOrdered = isBulletOrOrderedList(sourceList.type);
    const isTargetTask = isTaskList(targetNodeType);
    const isSourceTask = isTaskList(sourceList.type);
    const isTargetBulletOrOrdered = isBulletOrOrderedList(targetNodeType);
    const supportedListTypes = getSupportedListTypesSet(nodes);
    const newList = transformListRecursively({
      isSourceBulletOrOrdered,
      isSourceTask,
      isTargetBulletOrOrdered,
      isTargetTask,
      listNode: sourceList,
      schema: tr.doc.type.schema,
      supportedListTypes,
      targetNodeType
    }, onhandleUnsupportedContent);
    tr.replaceWith(listPos, listPos + sourceList.nodeSize, [newList, ...unsupportedContent]);
    return tr;
  } catch {
    return tr;
  }
};

/**
 * Transform between different list types
 */
export const transformBetweenListTypes = context => {
  const {
    tr,
    sourceNode,
    sourcePos,
    targetNodeType
  } = context;
  const {
    nodes
  } = tr.doc.type.schema;
  const sourceListType = sourceNode.type;
  const isSourceBulletOrOrdered = isBulletOrOrderedList(sourceListType);
  const isTargetTask = isTaskList(targetNodeType);
  const isSourceTask = isTaskList(sourceListType);
  const isTargetBulletOrOrdered = isBulletOrOrderedList(targetNodeType);

  // Check if we need structure transformation
  const needsStructureTransform = isSourceBulletOrOrdered && isTargetTask || isSourceTask && isTargetBulletOrOrdered;
  try {
    if (!needsStructureTransform) {
      // Simple type change for same structure lists (bullet <-> ordered)
      // Apply to the main list
      tr.setNodeMarkup(sourcePos, targetNodeType);

      // Apply to nested lists
      const listStart = sourcePos;
      const listEnd = sourcePos + sourceNode.nodeSize;
      const supportedListTypesSet = getSupportedListTypesSet(nodes);
      tr.doc.nodesBetween(listStart, listEnd, (node, pos, parent) => {
        // Only process nested lists (not the root list we already handled)
        if (supportedListTypesSet.has(node.type) && pos !== sourcePos) {
          const isNestedList = parent && (supportedListTypesSet.has(parent.type) || parent.type === nodes.listItem);
          if (isNestedList) {
            const shouldTransformNode = node.type === sourceListType || isBulletOrOrderedList(node.type) && isTargetBulletOrOrdered;
            if (shouldTransformNode) {
              tr.setNodeMarkup(pos, targetNodeType);
            }
          }
        }
        return true; // Continue traversing
      });
      return tr;
    } else {
      return transformListStructure(context);
    }
  } catch {
    return null;
  }
};

/**
 * Transform selection to task list
 * Handles the special structure where taskItem contains text directly (no paragraph wrapper)
 */
export const transformToTaskList = (tr, range, targetNodeType, targetAttrs, nodes) => {
  try {
    const {
      taskItem,
      paragraph,
      blockTaskItem
    } = nodes;
    // gating behind platform_editor_small_font_size to support task lists with font size applied,
    // but keep this solution general
    const isBlockTaskItemEnabled = !!blockTaskItem && expValEquals('platform_editor_small_font_size', 'isEnabled', true);
    const listItems = [];

    // Process each block in the range
    tr.doc.nodesBetween(range.start, range.end, node => {
      if (node.isBlock) {
        const inlineContent = [...node.content.content];
        if (inlineContent.length > 0) {
          if (isBlockTaskItemEnabled && node.type === paragraph && node.marks.length > 0) {
            listItems.push(createBlockTaskItem({
              attrs: targetAttrs,
              content: inlineContent,
              marks: node.marks,
              schema: tr.doc.type.schema
            }));
          } else {
            listItems.push(taskItem.create(targetAttrs, inlineContent));
          }
        }
      }
      return false;
    });
    if (listItems.length === 0) {
      return null;
    }

    // Create the new task list
    const newList = targetNodeType.create(targetAttrs, listItems);

    // Replace the range with the new list
    tr.replaceWith(range.start, range.end, newList);
    return tr;
  } catch {
    return null;
  }
};
export const transformTaskListToBlockNodes = context => {
  const {
    tr,
    targetNodeType,
    targetAttrs,
    sourceNode,
    sourcePos
  } = context;
  const {
    selection
  } = tr;
  const schema = selection.$from.doc.type.schema;
  const {
    blockTaskItem
  } = schema.nodes;

  // gating behind platform_editor_small_font_size to support task lists with font size applied,
  // but keep this solution general
  const isBlockTaskItemEnabled = !!blockTaskItem && expValEquals('platform_editor_small_font_size', 'isEnabled', true);
  if (isBlockTaskItemEnabled) {
    const blockTaskItemsResult = findChildrenByType(sourceNode, blockTaskItem);
    if (blockTaskItemsResult.length > 0 && targetNodeType === schema.nodes.paragraph) {
      // blockTaskItem content is (paragraph | extension)+
      // Extract paragraph children directly — they may carry block marks (e.g. fontSize)
      const targetNodes = [];
      for (const {
        node: blockItem
      } of blockTaskItemsResult) {
        blockItem.forEach(child => {
          if (child.type === schema.nodes.paragraph) {
            targetNodes.push(child);
          }
        });
      }
      if (targetNodes.length === 0) {
        return null;
      }
      const slice = new Slice(Fragment.fromArray(targetNodes), 0, 0);
      const rangeStart = sourcePos !== null ? sourcePos : selection.from;
      tr.replaceRange(rangeStart, rangeStart + sourceNode.nodeSize, slice);
      return tr;
    }
  }

  // Original logic for regular taskItem children
  const taskItemsResult = findChildrenByType(sourceNode, schema.nodes.taskItem);
  const taskItems = taskItemsResult.map(item => item.node);
  const taskItemFragments = taskItems.map(taskItem => taskItem.content);
  let targetNodes = [];

  // Convert fragments to headings if target is heading
  if (targetNodeType === schema.nodes.heading && targetAttrs) {
    // convert the fragments to headings
    const targetHeadingLevel = targetAttrs.level;
    targetNodes = taskItemFragments.map(fragment => schema.nodes.heading.createChecked({
      level: targetHeadingLevel
    }, fragment.content));
  }

  // Convert fragments to paragraphs if target is paragraphs
  if (targetNodeType === schema.nodes.paragraph) {
    // convert the fragments to paragraphs
    targetNodes = taskItemFragments.map(fragment => schema.nodes.paragraph.createChecked({}, fragment.content));
  }

  // Convert fragments to code block if target is code block
  if (targetNodeType === schema.nodes.codeBlock) {
    // convert the fragments to one code block
    const codeBlockContent = taskItemFragments.map(fragment => fragment.textBetween(0, fragment.size, '\n')).join('\n');
    targetNodes = [schema.nodes.codeBlock.createChecked({}, schema.text(codeBlockContent))];
  }

  // Replace the task list node with the new content in the transaction
  const slice = new Slice(Fragment.fromArray(targetNodes), 0, 0);
  const rangeStart = sourcePos !== null ? sourcePos : selection.from;
  tr.replaceRange(rangeStart, rangeStart + sourceNode.nodeSize, slice);
  return tr;
};
export const getFormattedNode = tr => {
  const {
    selection
  } = tr;
  const {
    nodes
  } = tr.doc.type.schema;

  // gating behind platform_editor_small_font_size to support task lists with font size applied,
  // but keep this solution general
  const isBlockTaskItemEnabled = !!nodes.blockTaskItem && expValEquals('platform_editor_small_font_size', 'isEnabled', true);

  // Find the node to format from the current selection
  let nodeToFormat;
  let nodePos = selection.from;

  // Try to find the current node from selection
  const selectedNode = findSelectedNodeOfType([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.expand, nodes.codeBlock, nodes.bulletList, nodes.orderedList, nodes.taskList, nodes.layoutSection])(selection);
  if (selectedNode) {
    nodeToFormat = selectedNode.node;
    nodePos = selectedNode.pos;
  } else {
    // Try to find parent node (including list parents)
    const parentNodeTypes = [nodes.blockquote, nodes.panel, nodes.expand, nodes.codeBlock, nodes.listItem, nodes.taskItem, nodes.layoutSection];
    if (isBlockTaskItemEnabled) {
      parentNodeTypes.push(nodes.blockTaskItem);
    }
    const parentNode = findParentNodeOfType(parentNodeTypes)(selection);
    if (parentNode) {
      nodeToFormat = parentNode.node;
      nodePos = parentNode.pos;
      const paragraphOrHeadingNode = findParentNodeOfType([nodes.paragraph, nodes.heading])(selection);
      // Special case: if we found a listItem/taskItem/blockTaskItem, check if we need the parent list instead
      if (parentNode.node.type === nodes.listItem || parentNode.node.type === nodes.taskItem || isBlockTaskItemEnabled && parentNode.node.type === nodes.blockTaskItem) {
        const listParent = findParentNodeOfType([nodes.bulletList, nodes.orderedList, nodes.taskList])(selection);
        if (listParent) {
          // For list transformations, we want the list parent, not the listItem
          nodeToFormat = listParent.node;
          nodePos = listParent.pos;
        }
      } else if (parentNode.node.type !== nodes.blockquote && paragraphOrHeadingNode) {
        nodeToFormat = paragraphOrHeadingNode.node;
        nodePos = paragraphOrHeadingNode.pos;
      }
    }
  }
  if (!nodeToFormat) {
    nodeToFormat = selection.$from.node();
    nodePos = selection.$from.pos;
  }
  return {
    node: nodeToFormat,
    pos: nodePos
  };
};

/**
 * Ensures every `listItem` in a slice starts with a paragraph.
 *
 * @param slice - The slice to transform
 * @param schema - The editor schema, used to create new paragraph nodes
 * @returns A new slice with the transformation applied
 */
export const transformSliceEnsureListItemParagraphFirst = (slice, schema) => {
  const {
    listItem,
    paragraph
  } = schema.nodes;
  if (!listItem || !paragraph) {
    return slice;
  }
  return mapSlice(slice, node => {
    if (node.type === listItem) {
      const firstChild = node.firstChild;
      if (firstChild && firstChild.type !== paragraph) {
        const emptyParagraph = paragraph.createAndFill();
        if (emptyParagraph) {
          const children = [emptyParagraph];
          for (let i = 0; i < node.childCount; i++) {
            children.push(node.child(i));
          }
          return node.copy(Fragment.from(children));
        }
      }
    }
    return node;
  });
};