import type { SidebarInitialData } from "@/components/sidebar/types";
import { buildKernelFileTreeProjection } from "@/lib/kernel-file-tree";
import type { SidebarDatasetListQueryResult } from "@/lib/sidebar-data";
import {
  buildSidebarDatasetListQueryResult,
  mapSidebarDatasetListQueryResultToInitialData,
} from "@/lib/sidebar-data";
import type { DocumentRecord } from "@/lib/documents";
import {
  buildSidebarTreeFromKernelProjection,
  type KernelSidebarProjectionItem,
} from "@/lib/kernel-sidebar";
import type { KernelFileTreeProjectionItem } from "@/lib/kernel-file-tree";
import {
  buildPageTreeProjectionItems,
  type PageTreeProjectionItem,
} from "@/lib/tree-projection";
import type { WorkspaceSummary } from "@/lib/workspaces";
import type { MediaAsset } from "@/types/media";

export type TreeStreamDocumentPatch =
  Partial<DocumentRecord> & Pick<DocumentRecord, "id">;

export type TreeStreamDeltaOp =
  | "noop"
  | "resync_required"
  | "upsert_document"
  | "upsert_documents"
  | "upsert_assets"
  | "remove_document"
  | "move_document"
  | "replace_documents"
  | "replace_sidebar";

export type TreeStreamDeltaEvent = {
  op: TreeStreamDeltaOp;
  node?: TreeStreamDocumentPatch | null;
  document?: TreeStreamDocumentPatch | null;
  documentId?: string | null;
  parentId?: string | null;
  sortOrder?: number | null;
  updatedAt?: string | null;
  upsertDocuments?: TreeStreamDocumentPatch[] | null;
  upsertAssets?: MediaAsset[] | null;
  documents?: DocumentRecord[] | null;
  sidebar?: SidebarDatasetListQueryResult | SidebarInitialData | null;
};

export type TreeRendererProjection = "sidebar_tree" | "page_tree" | "file_tree";

export type TreeRendererDeltaState = {
  projection: TreeRendererProjection;
  sidebar: SidebarInitialData;
  documents: DocumentRecord[];
  sidebarItems: KernelSidebarProjectionItem[];
  pageTreeItems: PageTreeProjectionItem[];
  fileTreeItems: KernelFileTreeProjectionItem[];
};

function cloneSidebarData(data: SidebarInitialData): SidebarInitialData {
  return {
    ...data,
    workspaces: [...data.workspaces],
    documents: [...data.documents],
    kernelFileTreeProjection: {
      ...data.kernelFileTreeProjection,
      items: [...data.kernelFileTreeProjection.items],
      edges: [...data.kernelFileTreeProjection.edges],
    },
    kernelSidebarProjection: {
      ...data.kernelSidebarProjection,
      items: [...data.kernelSidebarProjection.items],
      edges: [...data.kernelSidebarProjection.edges],
    },
    kernelSidebarTree: [...data.kernelSidebarTree],
    trashedDocuments: [...data.trashedDocuments],
    trashedMediaAssets: [...(data.trashedMediaAssets ?? [])],
    trashedMindmapAssets: [...(data.trashedMindmapAssets ?? [])],
    trashedTableAssets: [...(data.trashedTableAssets ?? [])],
    mindmapDocs: [...(data.mindmapDocs ?? [])],
    mindmapAssets: [...(data.mindmapAssets ?? [])],
    mindmapAssetChildren: { ...(data.mindmapAssetChildren ?? {}) },
    tableAssets: [...(data.tableAssets ?? [])],
    mediaAssets: [...(data.mediaAssets ?? [])],
  };
}

function buildSidebarFromDocuments(input: {
  base: SidebarInitialData;
  documents: DocumentRecord[];
}): SidebarInitialData {
  const queryResult = buildSidebarDatasetListQueryResult({
    activeWorkspaceId: input.base.activeWorkspaceId,
    workspaces: input.base.workspaces as WorkspaceSummary[],
    documents: input.documents,
    trashedDocuments: input.base.trashedDocuments,
    mindmaps: (input.base.mindmapAssets ?? []).map((asset) => ({
      mindmap_id: asset.id,
      workspace_id: asset.workspace_id,
      document_id: asset.document_id,
      created_at: asset.created_at,
      updated_at: asset.updated_at,
      deleted_at: asset.deleted_at ?? null,
      deleted_by: asset.deleted_by ?? null,
    })),
    mediaAssets: input.base.mediaAssets as MediaAsset[] | null,
    trashedMediaAssets: input.base.trashedMediaAssets as MediaAsset[] | null,
    tables: (input.base.tableAssets ?? []).map((asset) => ({
      id: asset.id,
      workspace_id: asset.workspace_id,
      document_id: asset.document_id,
      title: asset.file_name ?? null,
      created_at: asset.created_at,
      updated_at: asset.updated_at,
      deleted_at: asset.deleted_at ?? null,
      deleted_by: asset.deleted_by ?? null,
      purged_at: asset.purged_at ?? null,
      is_archived: Boolean(asset.deleted_at),
    })),
  });

  if (
    (input.base.mediaAssets?.length ?? 0) > 0 ||
    (input.base.mindmapAssets?.length ?? 0) > 0 ||
    (input.base.tableAssets?.length ?? 0) > 0 ||
    Object.keys(input.base.mindmapAssetChildren ?? {}).length > 0
  ) {
    // 说明：stream delta 只替换 documents 时，仍要保留已有资源树语义；
    // 否则 mindmap 子附件会在 resync 前退化成普通 asset。
    const nextFileTreeProjection = buildKernelFileTreeProjection({
      documents: input.documents,
      mediaAssets: input.base.mediaAssets,
      mindmapAssets: input.base.mindmapAssets,
      tableAssets: input.base.tableAssets,
      mindmapAssetChildren: input.base.mindmapAssetChildren,
    });
    queryResult.kernel_file_tree_projection = nextFileTreeProjection;
    queryResult.kernelFileTreeProjection = nextFileTreeProjection;
    queryResult.mindmap_asset_children = { ...(input.base.mindmapAssetChildren ?? {}) };
  }

  return mapSidebarDatasetListQueryResultToInitialData(queryResult);
}

function normalizeUpsertDocument(event: TreeStreamDeltaEvent): TreeStreamDocumentPatch | null {
  const candidate = event.node ?? event.document ?? null;
  if (!candidate || typeof candidate !== "object") {
    return null;
  }
  return typeof candidate.id === "string" && candidate.id.trim()
    ? ({ ...candidate, id: candidate.id.trim() } as TreeStreamDocumentPatch)
    : null;
}

function normalizeDocumentId(event: TreeStreamDeltaEvent): string | null {
  const candidate = typeof event.documentId === "string" ? event.documentId.trim() : "";
  return candidate || null;
}

function isCompleteDocumentRecord(value: TreeStreamDocumentPatch): value is DocumentRecord {
  return (
    typeof value.workspace_id === "string" &&
    typeof value.access_scope === "string" &&
    typeof value.is_template === "boolean" &&
    typeof value.created_at === "string" &&
    "parent_id" in value &&
    "sort_order" in value &&
    "is_starred" in value &&
    "updated_at" in value
  );
}

function isCompleteMediaAsset(value: unknown): value is MediaAsset {
  if (!value || typeof value !== "object") {
    return false;
  }
  const asset = value as MediaAsset;
  return (
    typeof asset.id === "string" &&
    typeof asset.workspace_id === "string" &&
    typeof asset.document_id === "string" &&
    typeof asset.asset_type === "string" &&
    typeof asset.created_at === "string" &&
    typeof asset.updated_at === "string" &&
    "file_name" in value &&
    "file_url" in value &&
    "thumbnail_url" in value &&
    "file_size" in value &&
    "mime_type" in value
  );
}

function buildSidebarFromAssets(input: {
  base: SidebarInitialData;
  mediaAssets: MediaAsset[];
}): SidebarInitialData {
  const next = cloneSidebarData(input.base);
  next.mediaAssets = [...input.mediaAssets];
  next.kernelFileTreeProjection = buildKernelFileTreeProjection({
    documents: next.documents,
    mediaAssets: next.mediaAssets,
    mindmapAssets: next.mindmapAssets,
    tableAssets: next.tableAssets,
    mindmapAssetChildren: next.mindmapAssetChildren,
  });
  return next;
}

export function deriveTreeRendererDeltaState(input: {
  projection: TreeRendererProjection;
  sidebar: SidebarInitialData;
}): TreeRendererDeltaState {
  const sidebar = input.sidebar;
  const pageTreeSource =
    sidebar.kernelSidebarTree.length > 0
      ? sidebar.kernelSidebarTree
      : buildSidebarTreeFromKernelProjection({
          records: sidebar.documents,
          projection: sidebar.kernelSidebarProjection,
        });

  return {
    projection: input.projection,
    sidebar,
    documents: [...sidebar.documents],
    sidebarItems: [...sidebar.kernelSidebarProjection.items],
    pageTreeItems: buildPageTreeProjectionItems(pageTreeSource),
    fileTreeItems: [...sidebar.kernelFileTreeProjection.items],
  };
}

export function applyTreeStreamDelta(
  base: SidebarInitialData,
  event: TreeStreamDeltaEvent,
): SidebarInitialData {
  if (event.op === "noop") {
    return base;
  }

  if (event.op === "resync_required") {
    return base;
  }

  if (event.op === "replace_sidebar" && event.sidebar) {
    if ("activeWorkspaceId" in event.sidebar) {
      return cloneSidebarData(event.sidebar as SidebarInitialData);
    }
    return mapSidebarDatasetListQueryResultToInitialData(event.sidebar as SidebarDatasetListQueryResult);
  }

  if (event.op === "replace_documents" && Array.isArray(event.documents)) {
    return buildSidebarFromDocuments({
      base,
      documents: [...event.documents],
    });
  }

  if (event.op === "upsert_document") {
    const documentPatch = normalizeUpsertDocument(event);
    if (!documentPatch) {
      return base;
    }
    const nextDocuments = [...base.documents];
    const existingIndex = nextDocuments.findIndex((item) => item.id === documentPatch.id);
    if (existingIndex >= 0) {
      nextDocuments[existingIndex] = {
        ...nextDocuments[existingIndex],
        ...documentPatch,
      };
    } else {
      if (!isCompleteDocumentRecord(documentPatch)) {
        return base;
      }
      nextDocuments.push(documentPatch);
    }
    return buildSidebarFromDocuments({
      base,
      documents: nextDocuments,
    });
  }

  if (event.op === "upsert_documents") {
    const patches = Array.isArray(event.upsertDocuments) ? event.upsertDocuments : [];
    if (patches.length === 0) {
      return base;
    }
    let changed = false;
    const nextDocuments = [...base.documents];
    for (const rawPatch of patches) {
      if (!rawPatch || typeof rawPatch.id !== "string" || !rawPatch.id.trim()) {
        continue;
      }
      const documentPatch = {
        ...rawPatch,
        id: rawPatch.id.trim(),
      } as TreeStreamDocumentPatch;
      const existingIndex = nextDocuments.findIndex((item) => item.id === documentPatch.id);
      if (existingIndex >= 0) {
        nextDocuments[existingIndex] = {
          ...nextDocuments[existingIndex],
          ...documentPatch,
        };
        changed = true;
        continue;
      }
      if (!isCompleteDocumentRecord(documentPatch)) {
        continue;
      }
      nextDocuments.push(documentPatch);
      changed = true;
    }
    if (!changed) {
      return base;
    }
    return buildSidebarFromDocuments({
      base,
      documents: nextDocuments,
    });
  }

  if (event.op === "remove_document") {
    const documentId = normalizeDocumentId(event);
    if (!documentId) {
      return base;
    }
    const removedIds = new Set<string>([documentId]);
    let changed = true;
    while (changed) {
      changed = false;
      for (const document of base.documents) {
        if (document.parent_id && removedIds.has(document.parent_id) && !removedIds.has(document.id)) {
          removedIds.add(document.id);
          changed = true;
        }
      }
    }
    return buildSidebarFromDocuments({
      base,
      documents: base.documents.filter((item) => !removedIds.has(item.id)),
    });
  }

  if (event.op === "move_document") {
    const documentId = normalizeDocumentId(event);
    if (!documentId) {
      return base;
    }
    const existingIndex = base.documents.findIndex((item) => item.id === documentId);
    if (existingIndex < 0) {
      return base;
    }
    const nextDocuments = [...base.documents];
    const existing = nextDocuments[existingIndex]!;
    nextDocuments[existingIndex] = {
      ...existing,
      parent_id: "parentId" in event ? (event.parentId ?? null) : existing.parent_id,
      sort_order:
        typeof event.sortOrder === "number" && Number.isFinite(event.sortOrder)
          ? event.sortOrder
          : existing.sort_order,
      updated_at:
        typeof event.updatedAt === "string" && event.updatedAt.trim()
          ? event.updatedAt.trim()
          : existing.updated_at,
    };
    return buildSidebarFromDocuments({
      base,
      documents: nextDocuments,
    });
  }

  if (event.op === "upsert_assets") {
    const assets = Array.isArray(event.upsertAssets) ? event.upsertAssets : [];
    if (assets.length === 0) {
      return base;
    }
    let changed = false;
    const nextAssets = [...(base.mediaAssets ?? [])];
    for (const asset of assets) {
      if (!isCompleteMediaAsset(asset)) {
        continue;
      }
      const existingIndex = nextAssets.findIndex((item) => item.id === asset.id);
      if (existingIndex >= 0) {
        nextAssets[existingIndex] = {
          ...nextAssets[existingIndex],
          ...asset,
        };
      } else {
        nextAssets.push(asset);
      }
      changed = true;
    }
    if (!changed) {
      return base;
    }
    return buildSidebarFromAssets({
      base,
      mediaAssets: nextAssets,
    });
  }

  return base;
}

export function applyTreeStreamDeltaToProjectionState(input: {
  projection: TreeRendererProjection;
  base: SidebarInitialData;
  event: TreeStreamDeltaEvent;
}): TreeRendererDeltaState {
  return deriveTreeRendererDeltaState({
    projection: input.projection,
    sidebar: applyTreeStreamDelta(input.base, input.event),
  });
}
