import type { DocumentRecord } from "@/lib/documents";
import type { SidebarTreeNode } from "@/lib/kernel-sidebar";
import type {
  TreeProjectionAssetKind,
  TreeProjectionCapability,
  TreeProjectionItemBase,
  TreeProjectionNodeType,
  TreeProjectionResourceKind,
} from "@/lib/tree-protocol";
import type { MediaAsset } from "@/types/media";

export type KernelFileTreeProjectionRowKind =
  | "document"
  | "index"
  | "asset"
  | "asset_folder";

export type KernelFileTreeProjectionItem = TreeProjectionItemBase & {
  projectionKind: "file_tree";
  rowId: string;
  rowKind: KernelFileTreeProjectionRowKind;
};

export type KernelFileTreeProjectionEdge = {
  id: string;
  edgeType: "parent_of";
  workspaceId: string | null;
  fromNodeId: string;
  toNodeId: string;
};

export type KernelFileTreeProjection = {
  projectionId: string;
  projection: "file_tree";
  rootNodeId: string | null;
  items: KernelFileTreeProjectionItem[];
  edges: KernelFileTreeProjectionEdge[];
};

type BuildKernelFileTreeProjectionInput = {
  documents: DocumentRecord[];
  mediaAssets?: MediaAsset[] | null;
  mindmapAssets?: MediaAsset[] | null;
  tableAssets?: MediaAsset[] | null;
  mindmapAssetChildren?: Record<string, string[]> | null;
  rootNodeId?: string | null;
};

type NormalizedFileTreeAsset = {
  id: string;
  documentId: string;
  workspaceId: string | null;
  title: string;
  resourceKind: TreeProjectionResourceKind;
  assetKind: TreeProjectionAssetKind;
  iconHint: string;
  nodeType: TreeProjectionNodeType;
};

function readRowId(value: unknown): string | null {
  return typeof value === "string" && value.trim() ? value.trim() : null;
}

function toMillis(value: string | null | undefined): number {
  if (!value) return 0;
  const parsed = Date.parse(value);
  return Number.isFinite(parsed) ? parsed : 0;
}

function sortDocuments(records: DocumentRecord[]) {
  return [...records].sort((a, b) => {
    const orderA = a.sort_order ?? Number.MAX_SAFE_INTEGER;
    const orderB = b.sort_order ?? Number.MAX_SAFE_INTEGER;
    if (orderA !== orderB) {
      return orderA - orderB;
    }
    return toMillis(a.created_at) - toMillis(b.created_at);
  });
}

function dedupeDocuments(records: DocumentRecord[]) {
  const seen = new Set<string>();
  const unique: DocumentRecord[] = [];
  for (let index = records.length - 1; index >= 0; index -= 1) {
    const record = records[index]!;
    if (seen.has(record.id)) {
      continue;
    }
    seen.add(record.id);
    unique.push(record);
  }
  unique.reverse();
  return unique;
}

function buildDocumentChildren(records: DocumentRecord[]) {
  const childrenByParentId = new Map<string | null, DocumentRecord[]>();
  const recordIds = new Set(records.map((record) => record.id));

  for (const record of records) {
    const parentId =
      record.parent_id && recordIds.has(record.parent_id) ? record.parent_id : null;
    const bucket = childrenByParentId.get(parentId) ?? [];
    bucket.push(record);
    childrenByParentId.set(parentId, bucket);
  }

  for (const [parentId, bucket] of childrenByParentId.entries()) {
    childrenByParentId.set(parentId, sortDocuments(bucket));
  }

  return childrenByParentId;
}

function makeProjectionEdge(
  fromNodeId: string,
  toNodeId: string,
  workspaceId: string | null,
): KernelFileTreeProjectionEdge {
  return {
    id: `edge:${fromNodeId}:${toNodeId}:parent_of`,
    edgeType: "parent_of",
    workspaceId,
    fromNodeId,
    toNodeId,
  };
}

function buildDocumentCapabilities(childCount: number): TreeProjectionCapability[] {
  const capabilities: TreeProjectionCapability[] = [
    "open",
    "drag",
    "drop",
    "select",
    "create-child",
    "rename",
    "archive",
    "restore",
    "context-menu",
    "reorder",
  ];
  if (childCount > 0) {
    capabilities.unshift("expand");
  }
  return capabilities;
}

function buildAssetFolderCapabilities(childCount: number): TreeProjectionCapability[] {
  const capabilities: TreeProjectionCapability[] = [
    "open-asset",
    "select",
    "context-menu",
  ];
  if (childCount > 0) {
    capabilities.unshift("expand");
  }
  return capabilities;
}

function buildLeafCapabilities(openAsset: boolean): TreeProjectionCapability[] {
  const capabilities: TreeProjectionCapability[] = ["select", "context-menu"];
  capabilities.unshift(openAsset ? "open-asset" : "open");
  return capabilities;
}

function classifyGenericAssetKind(asset: MediaAsset): TreeProjectionAssetKind {
  const assetType = String(asset.asset_type ?? "").trim().toLowerCase();
  const name = String(asset.file_name ?? "").trim().toLowerCase();
  const mimeType = String(asset.mime_type ?? "").trim().toLowerCase();
  const ext = name.includes(".") ? name.split(".").pop() ?? "" : "";

  if (assetType === "mindmap") return "mindmap";
  if (assetType === "luckysheet") return "table";
  if (ext === "pdf" || mimeType.includes("pdf")) return "pdf";
  if (ext === "epub" || mimeType.includes("epub")) return "book";
  if (mimeType.startsWith("image/")) return "image";
  if (mimeType.startsWith("video/")) return "video";
  if (mimeType.startsWith("audio/")) return "audio";
  return "file";
}

function classifyResourceKind(assetKind: TreeProjectionAssetKind): TreeProjectionResourceKind {
  switch (assetKind) {
    case "mindmap":
      return "mindmap";
    case "table":
      return "table";
    case "book":
      return "book";
    case "pdf":
      return "pdf";
    default:
      return "asset";
  }
}

function classifyNodeType(assetKind: TreeProjectionAssetKind): TreeProjectionNodeType {
  switch (assetKind) {
    case "mindmap":
      return "mindmap";
    case "table":
      return "table";
    case "book":
      return "book";
    case "pdf":
      return "pdf";
    default:
      return "asset";
  }
}

function classifyIconHint(assetKind: TreeProjectionAssetKind): string {
  switch (assetKind) {
    case "mindmap":
      return "mindmap";
    case "table":
      return "table";
    case "book":
      return "book";
    case "pdf":
      return "pdf";
    case "image":
      return "image";
    case "video":
      return "video";
    case "audio":
      return "audio";
    default:
      return "file";
  }
}

function normalizeAsset(asset: MediaAsset): NormalizedFileTreeAsset {
  const assetKind = classifyGenericAssetKind(asset);
  return {
    id: asset.id,
    documentId: asset.document_id,
    workspaceId: asset.workspace_id ?? null,
    title: asset.file_name?.trim() || "附件",
    resourceKind: classifyResourceKind(assetKind),
    assetKind,
    iconHint: classifyIconHint(assetKind),
    nodeType: classifyNodeType(assetKind),
  };
}

function buildAssetCollections(input: BuildKernelFileTreeProjectionInput) {
  const assetsByDoc = new Map<string, NormalizedFileTreeAsset[]>();
  const assetById = new Map<string, NormalizedFileTreeAsset>();
  const seen = new Set<string>();

  for (const asset of [
    ...(input.mediaAssets ?? []),
    ...(input.mindmapAssets ?? []),
    ...(input.tableAssets ?? []),
  ]) {
    if (!asset?.id || seen.has(asset.id)) {
      continue;
    }
    seen.add(asset.id);
    const normalized = normalizeAsset(asset);
    const bucket = assetsByDoc.get(normalized.documentId) ?? [];
    bucket.push(normalized);
    assetsByDoc.set(normalized.documentId, bucket);
    assetById.set(normalized.id, normalized);
  }

  const childAssetIdsByParentId = new Map<string, string[]>();
  Object.entries(input.mindmapAssetChildren ?? {}).forEach(([parentAssetId, childIds]) => {
    childAssetIdsByParentId.set(
      parentAssetId,
      (childIds ?? []).filter((childId) => typeof childId === "string" && childId.trim().length > 0),
    );
  });

  return {
    assetsByDoc,
    assetById,
    childAssetIdsByParentId,
  };
}

function makeFallbackNode(item: KernelFileTreeProjectionItem): SidebarTreeNode {
  const documentId =
    item.resourceMeta.documentId ??
    (item.rowKind === "document" ? item.nodeId : item.parentNodeId ?? item.nodeId);
  return {
    access_scope: "private",
    id: documentId,
    workspace_id: item.resourceMeta.workspaceId ?? "",
    title: item.title,
    parent_id: item.rowKind === "document" ? item.parentNodeId : documentId,
    sort_order: item.position,
    is_starred: false,
    is_template: false,
    created_at: "",
    updated_at: null,
    children: [],
    kernel: {
      nodeType: "page",
      depth: item.depth,
      position: item.position,
      childCount: item.childCount,
      expandedByDefault: item.expandedByDefault,
    },
  };
}

function makeFallbackAsset(item: KernelFileTreeProjectionItem): MediaAsset {
  const assetKind = item.resourceMeta.assetKind ?? "unknown";
  const resourceKind = item.resourceMeta.resourceKind;
  const assetType =
    assetKind === "mindmap"
      ? "mindmap"
      : assetKind === "table" || resourceKind === "table"
        ? "luckysheet"
        : assetKind;
  return {
    id: item.resourceMeta.assetId ?? item.nodeId,
    workspace_id: item.resourceMeta.workspaceId ?? "",
    document_id: item.resourceMeta.documentId ?? "",
    asset_type: assetType,
    file_url: null,
    thumbnail_url: null,
    bucket: null,
    storage_path: null,
    file_name: item.title,
    file_size: null,
    mime_type: null,
    ocr_payload: undefined,
    ocr_strategy: null,
    ocr_text: null,
    ocr_status: null,
    signed_url: null,
    created_at: "",
    updated_at: "",
  };
}

export function getDocIdFromFileTreeItem(item: KernelFileTreeProjectionItem): string {
  if (item.resourceMeta.documentId) {
    return item.resourceMeta.documentId;
  }
  if (item.rowKind === "document") {
    return item.nodeId;
  }
  const rowId = readRowId(item.rowId);
  if (rowId?.startsWith("index:")) {
    return rowId.slice("index:".length);
  }
  return item.parentNodeId ?? item.nodeId;
}

export function resolveFileTreeRowNode(
  item: KernelFileTreeProjectionItem,
  nodeById?: Map<string, SidebarTreeNode>,
): SidebarTreeNode {
  const documentId = getDocIdFromFileTreeItem(item);
  return nodeById?.get(documentId) ?? makeFallbackNode(item);
}

export function resolveFileTreeRowAsset(
  item: KernelFileTreeProjectionItem,
  assetById?: Map<string, MediaAsset>,
): MediaAsset {
  const assetId = item.resourceMeta.assetId ?? item.nodeId;
  return assetById?.get(assetId) ?? makeFallbackAsset(item);
}

export function isKernelFileTreeProjection(value: unknown): value is KernelFileTreeProjection {
  return (
    Boolean(value) &&
    typeof value === "object" &&
    (value as KernelFileTreeProjection).projection === "file_tree" &&
    Array.isArray((value as KernelFileTreeProjection).items) &&
    Array.isArray((value as KernelFileTreeProjection).edges)
  );
}

export function buildKernelFileTreeProjection(
  input: BuildKernelFileTreeProjectionInput,
): KernelFileTreeProjection {
  const uniqueDocuments = dedupeDocuments(input.documents);
  const documentById = new Map(uniqueDocuments.map((document) => [document.id, document]));
  const childrenByParentId = buildDocumentChildren(uniqueDocuments);
  const { assetsByDoc, assetById, childAssetIdsByParentId } = buildAssetCollections(input);
  const rootNodeId = input.rootNodeId?.trim() || null;
  const roots =
    rootNodeId && documentById.has(rootNodeId)
      ? [rootNodeId]
      : sortDocuments(
          uniqueDocuments.filter((document) => {
            const parentId = document.parent_id?.trim() || null;
            return !parentId || !documentById.has(parentId);
          }),
        ).map((document) => document.id);

  const items: KernelFileTreeProjectionItem[] = [];
  const edges: KernelFileTreeProjectionEdge[] = [];

  const walk = (documentId: string, depth: number) => {
    const document = documentById.get(documentId);
    if (!document) {
      return;
    }

    const childDocuments = childrenByParentId.get(documentId) ?? [];
    const documentAssets = assetsByDoc.get(documentId) ?? [];
    const nestedChildIds = new Set<string>();
    documentAssets.forEach((asset) => {
      const childIds = childAssetIdsByParentId.get(asset.id) ?? [];
      childIds.forEach((childId) => nestedChildIds.add(childId));
    });
    const directAssets = documentAssets.filter((asset) => !nestedChildIds.has(asset.id));
    const childCount = 1 + directAssets.length + childDocuments.length;
    const parentNodeId = depth === 0 ? null : document.parent_id ?? null;
    const workspaceId = document.workspace_id ?? null;

    items.push({
      rowId: `doc:${document.id}`,
      rowKind: "document",
      nodeId: document.id,
      parentNodeId,
      nodeType: "page",
      projectionKind: "file_tree",
      title: document.title ?? "无标题",
      depth,
      position: document.sort_order ?? null,
      childCount,
      expandable: childCount > 0,
      expandedByDefault: true,
      capabilities: buildDocumentCapabilities(childCount),
      resourceMeta: {
        resourceKind: "document",
        documentId: document.id,
        workspaceId,
        iconHint: "page",
      },
      iconHint: "page",
    });

    const indexNodeId = `index:${document.id}`;
    items.push({
      rowId: indexNodeId,
      rowKind: "index",
      nodeId: indexNodeId,
      parentNodeId: document.id,
      nodeType: "index",
      projectionKind: "file_tree",
      title: "index.md",
      depth: depth + 1,
      position: 0,
      childCount: 0,
      expandable: false,
      expandedByDefault: false,
      capabilities: buildLeafCapabilities(false),
      resourceMeta: {
        resourceKind: "index",
        documentId: document.id,
        workspaceId,
        iconHint: "index",
      },
      iconHint: "index",
    });
    edges.push(makeProjectionEdge(document.id, indexNodeId, workspaceId));

    let assetPosition = 1;
    directAssets.forEach((asset) => {
      const childIds = childAssetIdsByParentId.get(asset.id) ?? [];
      if (childIds.length > 0) {
        const folderNodeId = `asset-folder:${asset.id}`;
        items.push({
          rowId: folderNodeId,
          rowKind: "asset_folder",
          nodeId: folderNodeId,
          parentNodeId: document.id,
          nodeType: "mindmap",
          projectionKind: "file_tree",
          title: asset.title.replace(/\.json$/i, ""),
          depth: depth + 1,
          position: assetPosition,
          childCount: childIds.length,
          expandable: true,
          expandedByDefault: false,
          capabilities: buildAssetFolderCapabilities(childIds.length),
          resourceMeta: {
            resourceKind: asset.resourceKind,
            documentId: asset.documentId,
            assetId: asset.id,
            workspaceId: asset.workspaceId,
            assetKind: asset.assetKind,
            iconHint: "mindmap",
          },
          iconHint: "mindmap",
        });
        edges.push(makeProjectionEdge(document.id, folderNodeId, workspaceId));

        childIds.forEach((childAssetId, index) => {
          const childAsset = assetById.get(childAssetId);
          if (!childAsset) {
            return;
          }
          const childNodeId = `asset:${childAsset.id}`;
          items.push({
            rowId: childNodeId,
            rowKind: "asset",
            nodeId: childNodeId,
            parentNodeId: folderNodeId,
            nodeType: childAsset.nodeType,
            projectionKind: "file_tree",
            title: childAsset.title,
            depth: depth + 2,
            position: index,
            childCount: 0,
            expandable: false,
            expandedByDefault: false,
            capabilities: buildLeafCapabilities(true),
            resourceMeta: {
              resourceKind: childAsset.resourceKind,
              documentId: childAsset.documentId,
              assetId: childAsset.id,
              workspaceId: childAsset.workspaceId,
              assetKind: childAsset.assetKind,
              iconHint: childAsset.iconHint,
            },
            iconHint: childAsset.iconHint,
          });
          edges.push(makeProjectionEdge(folderNodeId, childNodeId, workspaceId));
        });

        assetPosition += 1;
        return;
      }

      const assetNodeId = `asset:${asset.id}`;
      items.push({
        rowId: assetNodeId,
        rowKind: "asset",
        nodeId: assetNodeId,
        parentNodeId: document.id,
        nodeType: asset.nodeType,
        projectionKind: "file_tree",
        title: asset.title,
        depth: depth + 1,
        position: assetPosition,
        childCount: 0,
        expandable: false,
        expandedByDefault: false,
        capabilities: buildLeafCapabilities(true),
        resourceMeta: {
          resourceKind: asset.resourceKind,
          documentId: asset.documentId,
          assetId: asset.id,
          workspaceId: asset.workspaceId,
          assetKind: asset.assetKind,
          iconHint: asset.iconHint,
        },
        iconHint: asset.iconHint,
      });
      edges.push(makeProjectionEdge(document.id, assetNodeId, workspaceId));
      assetPosition += 1;
    });

    childDocuments.forEach((childDocument) => {
      walk(childDocument.id, depth + 1);
    });
  };

  roots.forEach((documentId) => walk(documentId, 0));

  return {
    projectionId: `kernel_projection:file_tree:${rootNodeId ?? "root"}`,
    projection: "file_tree",
    rootNodeId,
    items,
    edges,
  };
}
