import { getBlocksFromDocumentContent, replaceBlockInTree } from "@/lib/blocks";
import type { Json } from "@/types/supabase";

type TreeBlockLike = {
  id: string;
  type?: string;
  props?: Record<string, unknown>;
  content?: unknown;
  children?: TreeBlockLike[];
};

export type BlockLike = {
  id: string;
  type?: string;
  props?: Record<string, unknown>;
  content?: unknown;
  children?: unknown;
};

export type BlockInsertSpec = {
  type: "paragraph" | "heading";
  text: string;
  level?: number;
};

export type BlockSnapshot = Json;

const isRecord = (value: unknown): value is Record<string, unknown> =>
  typeof value === "object" && value !== null && !Array.isArray(value);

const normalizeBlocks = (content: unknown): BlockLike[] => {
  return getBlocksFromDocumentContent(content) as BlockLike[];
};

const createTextContent = (text: string): unknown[] => [{ type: "text", text }];

const generateId = () => {
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
    return crypto.randomUUID();
  }
  return `bn_${Math.random().toString(36).slice(2, 10)}`;
};

const buildBlockFromSpec = (spec: BlockInsertSpec): BlockLike => {
  const base: BlockLike = {
    id: generateId(),
    type: spec.type,
    props: {},
    content: createTextContent(String(spec.text ?? "").trim()),
    children: [],
  };
  if (spec.type === "heading") {
    const level = Number(spec.level ?? 2);
    base.props = { level: Math.max(1, Math.min(5, Number.isFinite(level) ? Math.floor(level) : 2)) };
  }
  return base;
};

const cloneBlock = (block: BlockLike): BlockLike => ({
  ...block,
  props: isRecord(block.props) ? { ...block.props } : block.props,
  content: Array.isArray(block.content) ? [...block.content] : block.content,
  children: Array.isArray(block.children)
    ? (block.children as BlockLike[]).map((child) => cloneBlock(child))
    : block.children,
});

export const findBlockContainerById = (
  blocks: BlockLike[],
  targetId: string,
): { container: BlockLike[]; index: number } | null => {
  const id = String(targetId || "").trim();
  if (!id) return null;
  for (let index = 0; index < blocks.length; index += 1) {
    const block = blocks[index];
    if (String(block?.id ?? "").trim() === id) {
      return { container: blocks, index };
    }
    const children = Array.isArray(block?.children)
      ? (block.children as BlockLike[]).filter(
          (item): item is BlockLike => Boolean(item && typeof item.id === "string"),
        )
      : [];
    const found = findBlockContainerById(children, id);
    if (found) return found;
  }
  return null;
};

export const readInlineBlockText = (block: BlockLike): string => {
  const inline = Array.isArray(block.content) ? (block.content as Array<{ text?: unknown }>) : [];
  return inline.map((node) => (typeof node?.text === "string" ? node.text : "")).join("").trim();
};

export function createBlockSnapshotOpsAdapter(input: {
  baseBlocks?: unknown;
  loadBlocks?: () => Promise<{ blocks: unknown[]; source: string }>;
}) {
  const loadSnapshot = async () => {
    if (input.baseBlocks) {
      return {
        blocks: normalizeBlocks(input.baseBlocks),
        source: "client" as const,
      };
    }
    if (input.loadBlocks) {
      const loaded = await input.loadBlocks();
      return {
        blocks: normalizeBlocks(loaded.blocks),
        source: "route" as const,
      };
    }
    return {
      blocks: [] as BlockLike[],
      source: "empty" as const,
    };
  };

  return {
    getSnapshot: loadSnapshot,
    async insertBlocks(input: {
      afterBlockId?: string;
      beforeBlockId?: string;
      blocks: BlockInsertSpec[];
    }) {
      const snapshot = await loadSnapshot();
      const blocks = snapshot.blocks.map((block) => cloneBlock(block));
      const created = input.blocks.map(buildBlockFromSpec);
      const targetId = input.beforeBlockId || input.afterBlockId;
      const found = targetId ? findBlockContainerById(blocks, targetId) : null;
      if (targetId && !found) {
        throw new Error(`未找到 blockId：${targetId}`);
      }

      if (!found) {
        blocks.push(...created);
      } else {
        const insertAt = input.beforeBlockId ? found.index : found.index + 1;
        found.container.splice(insertAt, 0, ...created);
      }

      return {
        data: blocks as BlockSnapshot,
        inserted: created.map((block) => String(block.id ?? "")),
        source: snapshot.source,
      };
    },
    async replaceRange(input: {
      blockId: string;
      text: string;
      mode: "replace" | "append" | "prepend";
    }) {
      const snapshot = await loadSnapshot();
      const blocks = snapshot.blocks.map((block) => cloneBlock(block));
      const found = findBlockContainerById(blocks, input.blockId);
      if (!found) {
        throw new Error(`未找到 blockId：${input.blockId}`);
      }

      const block = found.container[found.index];
      const prevText = readInlineBlockText(block);
      const nextText =
        input.mode === "append"
          ? `${prevText}${input.text}`
          : input.mode === "prepend"
            ? `${input.text}${prevText}`
            : input.text;

      const replaced = replaceBlockInTree(blocks, input.blockId, {
        ...(block as TreeBlockLike),
        content: createTextContent(nextText),
      } as TreeBlockLike);
      if (!replaced.ok) {
        throw new Error(`未找到 blockId：${input.blockId}`);
      }

      return {
        data: replaced.nextBlocks as BlockSnapshot,
        source: snapshot.source,
      };
    },
  };
}
