import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { nowIso } from "./_utils/time";
import type { MutationCtx, QueryCtx } from "./_generated/server";
import { getCanonicalDocumentByBusinessId } from "./_utils/documentRecord";

async function assertWorkspaceMember(ctx: QueryCtx | MutationCtx, userId: string, workspaceId: string) {
  const membership = await ctx.db
    .query("workspace_members")
    .withIndex("by_workspace_user", (q) => q.eq("workspace_id", workspaceId).eq("user_id", userId))
    .first();
  if (!membership) {
    throw new Error("无权访问该工作空间");
  }
  return membership;
}

const displayMode = v.union(v.literal("inline"), v.literal("embed"));

export const record = mutation({
  args: {
    userId: v.string(),
    workspaceId: v.string(),
    sourcePageId: v.string(),
    targetPageId: v.string(),
    sourceBlockId: v.union(v.string(), v.null()),
    alias: v.union(v.string(), v.null()),
    displayMode,
    isPreviewable: v.boolean(),
  },
  handler: async (ctx, args) => {
    await assertWorkspaceMember(ctx, args.userId, args.workspaceId);

    const ts = nowIso();
    const existing = await ctx.db
      .query("page_references")
      .withIndex("by_unique", (q) =>
        q
          .eq("workspace_id", args.workspaceId)
          .eq("source_page_id", args.sourcePageId)
          .eq("source_block_id", args.sourceBlockId)
          .eq("target_page_id", args.targetPageId)
          .eq("display_mode", args.displayMode),
      )
      .first();

    if (existing) {
      await ctx.db.patch(existing._id, {
        alias: args.alias ?? null,
        is_previewable: Boolean(args.isPreviewable),
        updated_at: ts,
      });
      return {
        id: existing.id,
        workspace_id: existing.workspace_id,
        source_page_id: existing.source_page_id,
        source_block_id: existing.source_block_id,
        target_page_id: existing.target_page_id,
        alias: args.alias ?? null,
        display_mode: existing.display_mode,
        is_previewable: Boolean(args.isPreviewable),
        created_at: existing.created_at,
        updated_at: ts,
      };
    }

    // 说明：id 使用可读的组合键，便于调试；并不要求前端依赖该规则。
    const id = `ref:${args.workspaceId}:${args.sourcePageId}:${args.sourceBlockId ?? "page"}:${args.targetPageId}:${args.displayMode}:${ts}`;

    await ctx.db.insert("page_references", {
      id,
      workspace_id: args.workspaceId,
      source_page_id: args.sourcePageId,
      source_block_id: args.sourceBlockId ?? null,
      target_page_id: args.targetPageId,
      alias: args.alias ?? null,
      display_mode: args.displayMode,
      is_previewable: Boolean(args.isPreviewable),
      created_by: args.userId,
      created_at: ts,
      updated_at: ts,
    });

    return {
      id,
      workspace_id: args.workspaceId,
      source_page_id: args.sourcePageId,
      source_block_id: args.sourceBlockId ?? null,
      target_page_id: args.targetPageId,
      alias: args.alias ?? null,
      display_mode: args.displayMode,
      is_previewable: Boolean(args.isPreviewable),
      created_at: ts,
      updated_at: ts,
    };
  },
});

export const listBacklinks = query({
  args: {
    userId: v.string(),
    workspaceId: v.string(),
    pageId: v.string(),
    limit: v.optional(v.number()),
    offset: v.optional(v.number()),
  },
  handler: async (ctx, args) => {
    await assertWorkspaceMember(ctx, args.userId, args.workspaceId);

    const limit = Math.max(1, Math.min(200, Math.floor(args.limit ?? 50)));
    const offset = Math.max(0, Math.floor(args.offset ?? 0));

    const rows = await ctx.db
      .query("page_references")
      .withIndex("by_workspace_target", (q) => q.eq("workspace_id", args.workspaceId).eq("target_page_id", args.pageId))
      .order("desc")
      .take(limit + offset + 200);

    const sliced = rows.slice(offset, offset + limit);
    const sourceIds = Array.from(new Set(sliced.map((r) => String((r as any).source_page_id ?? "")).filter(Boolean)));

    const sourceTitleById = new Map<string, string | null>();
    for (const sid of sourceIds) {
      const doc = await getCanonicalDocumentByBusinessId<any>(ctx, sid);
      sourceTitleById.set(sid, doc ? (doc.title ?? null) : null);
    }

    return sliced.map((row) => {
      const r = row as any;
      return {
        id: r.id,
        source_page_id: r.source_page_id,
        source_block_id: r.source_block_id ?? null,
        alias: r.alias ?? null,
        display_mode: r.display_mode,
        is_previewable: Boolean(r.is_previewable),
        created_at: r.created_at,
        updated_at: r.updated_at,
        source_title: sourceTitleById.get(String(r.source_page_id ?? "")) ?? null,
      };
    });
  },
});
