import { type EditorBlockData, generateBlockId } from "@/lib/block-parser"
import type { EditorOperation } from "@/lib/operation-history"

export interface SplitResult {
  blocks: EditorBlockData[]
  newBlock: EditorBlockData | null
}

export interface MergeResult {
  blocks: EditorBlockData[]
  mergedContent: string
  joinPos: number
}

export interface DeleteResult {
  blocks: EditorBlockData[]
  nextId: string | null
}

export interface IndentResult {
  blocks: EditorBlockData[]
  childIndices: number[]
}

export interface ApplyResult {
  blocks: EditorBlockData[]
  focusInfo: { id: string; pos: "start" | "end" | number } | null
}

/**
 * Split block at `index` into two: the original retains `before`,
 * a new sibling gets `after`.
 */
export function splitBlocks(
  blocks: EditorBlockData[],
  index: number,
  before: string,
  after: string,
  idFn: () => string = generateBlockId,
): SplitResult {
  if (index < 0 || index >= blocks.length)
    return { blocks: [...blocks], newBlock: null }
  const block = blocks[index]
  if (!block) return { blocks: [...blocks], newBlock: null }
  const newBlock: EditorBlockData = {
    id: idFn(),
    depth: block.depth,
    content: after,
  }
  const newBlocks = blocks.map((b, i) =>
    i === index ? { ...b, content: before } : { ...b },
  )
  newBlocks.splice(index + 1, 0, newBlock)
  return { blocks: newBlocks, newBlock }
}

/**
 * Merge block at `index` into its predecessor.
 */
export function mergePrevious(
  blocks: EditorBlockData[],
  index: number,
): MergeResult {
  const result: MergeResult = {
    blocks: [...blocks],
    mergedContent: "",
    joinPos: 0,
  }
  if (index <= 0 || index >= blocks.length) return result
  const current = blocks[index]
  const prev = blocks[index - 1]
  if (!current || !prev) return result

  const prevContent = prev.content
  const appendedContent = current.content
  const joinPos = prevContent.length > 0 ? prevContent.length : 0
  const mergedContent = prevContent
    ? `${prevContent}${appendedContent}`
    : appendedContent

  const newBlocks = blocks.map((b, i) => {
    if (i === index - 1) return { ...b, content: mergedContent }
    return { ...b }
  })
  newBlocks.splice(index, 1)
  return { blocks: newBlocks, mergedContent, joinPos }
}

/**
 * Delete the block at `index`. Returns the ID of the block to focus next.
 * Does nothing if there is only one block.
 */
export function deleteIfEmpty(
  blocks: EditorBlockData[],
  index: number,
): DeleteResult {
  const result: DeleteResult = { blocks: [...blocks], nextId: null }
  if (blocks.length <= 1) return result
  if (index < 0 || index >= blocks.length) return result

  const nextId =
    index < blocks.length - 1
      ? (blocks[index + 1]?.id ?? null)
      : (blocks[index - 1]?.id ?? null)

  const newBlocks = blocks.filter((_, i) => i !== index)
  return { blocks: newBlocks, nextId }
}

/**
 * Indent the block at `index` and its children (blocks at higher depth
 * immediately following it).
 */
export function indentBlock(
  blocks: EditorBlockData[],
  index: number,
): IndentResult {
  if (index < 0 || index >= blocks.length)
    return { blocks: [...blocks], childIndices: [] }
  const block = blocks[index]
  if (!block || block.depth >= 10)
    return { blocks: [...blocks], childIndices: [] }

  const parentDepth = block.depth
  const childIndices: number[] = []
  for (let i = index + 1; i < blocks.length; i++) {
    const b = blocks[i]
    if (!b || b.depth <= parentDepth) break
    childIndices.push(i)
  }

  const newBlocks = blocks.map((b, i) => {
    if (i === index || childIndices.includes(i)) {
      return { ...b, depth: Math.min(b.depth + 1, 10) }
    }
    return { ...b }
  })

  return { blocks: newBlocks, childIndices }
}

/**
 * Outdent the block at `index` and its children.
 */
export function outdentBlock(
  blocks: EditorBlockData[],
  index: number,
): IndentResult {
  if (index < 0 || index >= blocks.length)
    return { blocks: [...blocks], childIndices: [] }
  const block = blocks[index]
  if (!block || block.depth <= 0)
    return { blocks: [...blocks], childIndices: [] }

  const parentDepth = block.depth
  const childIndices: number[] = []
  for (let i = index + 1; i < blocks.length; i++) {
    const b = blocks[i]
    if (!b || b.depth <= parentDepth) break
    childIndices.push(i)
  }

  const newBlocks = blocks.map((b, i) => {
    if (i === index || childIndices.includes(i)) {
      return { ...b, depth: Math.max(b.depth - 1, 0) }
    }
    return { ...b }
  })

  return { blocks: newBlocks, childIndices }
}

/**
 * Insert a new block at `index`.
 */
export function insertBlock(
  blocks: EditorBlockData[],
  index: number,
  content: string,
  depth: number,
  idFn: () => string = generateBlockId,
): { blocks: EditorBlockData[]; newBlock: EditorBlockData } {
  const newBlock: EditorBlockData = { id: idFn(), depth, content }
  const newBlocks = [...blocks]
  newBlocks.splice(index, 0, newBlock)
  return { blocks: newBlocks, newBlock }
}

/**
 * Set the content of a block by ID.
 */
export function setBlockContent(
  blocks: EditorBlockData[],
  blockId: string,
  newContent: string,
): EditorBlockData[] {
  return blocks.map((b) =>
    b.id === blockId ? { ...b, content: newContent } : { ...b },
  )
}

/**
 * Move a block from one index to another.
 * Returns the reordered array. No-op if fromIndex === toIndex.
 */
export function moveBlock(
  blocks: EditorBlockData[],
  fromIndex: number,
  toIndex: number,
): EditorBlockData[] {
  if (fromIndex === toIndex) return blocks
  const newBlocks = [...blocks]
  const [moved] = newBlocks.splice(fromIndex, 1)
  if (moved) newBlocks.splice(toIndex, 0, moved)
  return newBlocks
}

export function applyOperationToBlocks(
  blocks: EditorBlockData[],
  op: EditorOperation,
): ApplyResult {
  switch (op.type) {
    case "split-block": {
      const block = blocks[op.index]
      if (!block) return { blocks: [...blocks], focusInfo: null }
      const newBlocks = blocks.map((b, i) =>
        i === op.index ? { ...b, content: op.beforeContent } : { ...b },
      )
      newBlocks.splice(op.index + 1, 0, {
        id: op.splitBlockId,
        depth: op.depth,
        content: op.afterContent,
      })
      return { blocks: newBlocks, focusInfo: { id: op.splitBlockId, pos: "start" } }
    }
    case "merge-block": {
      const prev = blocks[op.index - 1]
      if (!prev) return { blocks: [...blocks], focusInfo: null }
      const mergedContent = op.prevContent + op.appendedContent
      const newBlocks = blocks.map((b, i) =>
        i === op.index - 1 ? { ...b, content: mergedContent } : { ...b },
      )
      newBlocks.splice(op.index, 1)
      return { blocks: newBlocks, focusInfo: { id: prev.id, pos: op.prevContent.length + 1 } }
    }
    case "insert-block": {
      const newBlocks = [...blocks]
      newBlocks.splice(op.index, 0, {
        id: op.blockId,
        depth: op.depth,
        content: op.content,
      })
      return { blocks: newBlocks, focusInfo: { id: op.blockId, pos: "start" } }
    }
    case "delete-block": {
      if (blocks.length <= 1) return { blocks: [...blocks], focusInfo: null }
      const newBlocks = blocks.filter((_, i) => i !== op.index)
      const target = newBlocks[op.index] ?? newBlocks[op.index - 1]
      return {
        blocks: newBlocks,
        focusInfo: target ? { id: target.id, pos: "start" } : null,
      }
    }
    case "indent-block": {
      const target = blocks[op.index]
      if (!target || target.depth >= 10)
        return { blocks: [...blocks], focusInfo: null }
      const newBlocks = blocks.map((b, i) =>
        i === op.index || op.childrenPreviousDepths?.some((c) => c.index === i)
          ? { ...b, depth: Math.min(b.depth + 1, 10) }
          : { ...b },
      )
      return { blocks: newBlocks, focusInfo: null }
    }
    case "outdent-block": {
      const target = blocks[op.index]
      if (!target || target.depth <= 0)
        return { blocks: [...blocks], focusInfo: null }
      const newBlocks = blocks.map((b, i) =>
        i === op.index || op.childrenPreviousDepths?.some((c) => c.index === i)
          ? { ...b, depth: Math.max(b.depth - 1, 0) }
          : { ...b },
      )
      return { blocks: newBlocks, focusInfo: null }
    }
    case "set-block-content": {
      const setBlock = blocks.find((b) => b.id === op.blockId)
      if (!setBlock) return { blocks: [...blocks], focusInfo: null }
      return {
        blocks: blocks.map((b) =>
          b.id === op.blockId ? { ...b, content: op.newContent } : { ...b },
        ),
        focusInfo: { id: setBlock.id, pos: "end" },
      }
    }
    case "move-block": {
      return { blocks: moveBlock(blocks, op.fromIndex, op.toIndex), focusInfo: null }
    }
  }
}
