import type { PageLayoutCommandInput, PageTitleCommandInput } from "@/lib/documents/page-command-contract";
import { PAGE_COMMAND_NAMES } from "@/lib/documents/page-command-contract";
import { buildDocumentSavePayload, type DocumentSavePayload } from "@/lib/documents/save-contract";
import type { Json } from "@/types/supabase";

export type PageCommandMeta = {
  requestId?: string;
  traceId?: string;
  commandId?: string;
  commandName?: string;
};

type PageCommandErrorPayload = {
  error?: string;
};

export type PageHeadCommandResult = {
  ok: true;
  meta?: PageCommandMeta;
};

export type PageLayoutCommandResult = {
  ok: true;
  meta?: PageCommandMeta;
};

export type PageBodyPersistedMeta = {
  revision: number | null;
  conflictDetectionKey: string | null;
};

export type PageBodyPersistedState = PageBodyPersistedMeta & {
  workspaceId: string | null;
};

export type ExecutePageBodyCommandInput = {
  documentId: string;
  workspaceId: string | null;
  revision: number | null;
  conflictDetectionKey: string | null;
  blocks: Json;
  applyEditorSnapshot?: (blocks: Json) => void;
  onPersistedMetaChange?: (meta: PageBodyPersistedMeta) => void;
  fetchImpl?: typeof fetch;
  persistPageBody?: (payload: DocumentSavePayload) => Promise<PageBodyPersistedMeta>;
};

async function postPageCommand<TResult>(
  path: string,
  payload: unknown,
  fallbackMessage: string,
  fetchImpl: typeof fetch = fetch,
): Promise<TResult> {
  const response = await fetchImpl(path, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });

  const body = (await response.json().catch(() => null)) as TResult | PageCommandErrorPayload | null;
  if (!response.ok) {
    const message =
      body && typeof body === "object" && "error" in body && typeof body.error === "string"
        ? body.error
        : fallbackMessage;
    throw new Error(message);
  }

  return body as TResult;
}

export async function executePageHeadCommand(
  input: PageTitleCommandInput,
  fetchImpl?: typeof fetch,
): Promise<PageHeadCommandResult> {
  return postPageCommand<PageHeadCommandResult>(
    "/api/documents/title",
    {
      documentId: input.documentId,
      workspaceId: input.workspaceId ?? null,
      title: input.title,
      commandName: PAGE_COMMAND_NAMES.updateTitle,
    },
    "重命名失败，请稍后再试",
    fetchImpl,
  );
}

export async function executePageLayoutCommand(
  input: PageLayoutCommandInput,
  fetchImpl?: typeof fetch,
): Promise<PageLayoutCommandResult> {
  return postPageCommand<PageLayoutCommandResult>(
    "/api/documents/options",
    {
      documentId: input.documentId,
      workspaceId: input.workspaceId ?? null,
      options: input.pageOptions,
      commandName: PAGE_COMMAND_NAMES.updateLayout,
    },
    "更新页面选项失败，请稍后再试",
    fetchImpl,
  );
}

export async function executePageBodySavePayload(
  payload: DocumentSavePayload,
  fetchImpl: typeof fetch = fetch,
): Promise<PageBodyPersistedMeta> {
  const body = await postPageCommand<{
    ok?: boolean;
    revision?: number | null;
    conflictDetectionKey?: string | null;
  }>(
    "/api/documents/save",
    payload,
    "页面正文保存失败",
    fetchImpl,
  );

  return {
    revision:
      typeof body?.revision === "number" && Number.isInteger(body.revision) ? body.revision : payload.revision,
    conflictDetectionKey:
      typeof body?.conflictDetectionKey === "string" && body.conflictDetectionKey.trim()
        ? body.conflictDetectionKey.trim()
        : payload.conflictDetectionKey,
  };
}

export async function executePageBodyCommand(
  input: ExecutePageBodyCommandInput,
): Promise<PageBodyPersistedMeta> {
  const payload = buildDocumentSavePayload({
    documentId: input.documentId,
    workspaceId: input.workspaceId,
    revision: input.revision,
    conflictDetectionKey: input.conflictDetectionKey,
    content: input.blocks,
    editorDocument: undefined,
    tiptapDocument: undefined,
    blockCount: Array.isArray(input.blocks) ? input.blocks.length : null,
    snapshotCapturedAt: new Date().toISOString(),
  });

  const persistedMeta = input.persistPageBody
    ? await input.persistPageBody(payload)
    : await executePageBodySavePayload(payload, input.fetchImpl ?? fetch);
  input.applyEditorSnapshot?.(input.blocks);
  input.onPersistedMetaChange?.(persistedMeta);
  return persistedMeta;
}
