import { readJsonFile } from "../utils/fileStore.js";
import {
  type ResultReferenceKind,
  type ResultReferenceRecord,
  type ResultReferenceSource,
  type RetrievalContext,
  RESULT_REFERENCE_KIND,
  RESULT_REFERENCE_SOURCE,
  type ResultReferenceStore,
} from "./store.js";

type ResolveOptions = {
  expectedKind?: ResultReferenceKind;
};

type RegisterResultReferenceInput = {
  actorKey: string;
  clientSessionId: string;
  data: unknown;
  kind: ResultReferenceKind;
  projectId?: string;
  projectKey: string;
  schemaVersion: number;
  sessionId: string;
  source: ResultReferenceSource;
  traceId: string;
};

export type RenderJunctionPayload = {
  node_area_map: Record<string, string>;
  area_ids?: string[];
  area_colors?: Record<string, string>;
};

export class ResultReferenceResolver {
  constructor(private readonly store: ResultReferenceStore) {}

  // Resolver 负责按结果类型做结构校验，Store 只关心授权和落盘。
  async register(input: RegisterResultReferenceInput) {
    const normalizedData = normalizeDataForKind(
      input.kind,
      input.data,
      input.schemaVersion,
    );
    if (!normalizedData) {
      throw new Error(`invalid payload for result ref kind '${input.kind}'`);
    }
    return this.store.store({
      actorKey: input.actorKey,
      clientSessionId: input.clientSessionId,
      data: normalizedData,
      kind: input.kind,
      projectId: input.projectId,
      projectKey: input.projectKey,
      schemaVersion: input.schemaVersion,
      sessionId: input.sessionId,
      source: input.source,
      traceId: input.traceId,
    });
  }

  async registerRenderPayloadFile(
    filePath: string,
    input: Omit<RegisterResultReferenceInput, "data" | "kind" | "schemaVersion">,
  ) {
    const raw = await readJsonFile<unknown>(filePath);
    if (raw === null) {
      throw new Error(`render payload file not found: ${filePath}`);
    }

    const wrapper = normalizeRenderPayloadFile(raw, filePath);
    if (!wrapper) {
      throw new Error("render payload file must use the wrapped { metadata, location, data } format");
    }

    const payload = extractRenderJunctionPayload(wrapper.data);
    if (!payload) {
      throw new Error("render payload file does not contain a valid junction render payload");
    }

    return this.register({
      ...input,
      data: payload,
      kind: RESULT_REFERENCE_KIND.renderJunctionsPayload,
      schemaVersion: 1,
      source: RESULT_REFERENCE_SOURCE.agentGenerated,
    });
  }

  async getFullAuthorized(
    resultRef: string,
    context: RetrievalContext,
    options: ResolveOptions = {},
  ) {
    const record = await this.getResolvedRecord(resultRef, context, options);
    if (!record) {
      return null;
    }
    return {
      ok: true,
      result_ref: record.resultRef,
      result_size_bytes: record.sizeBytes,
      stored_at: record.createdAt,
      data: record.data,
      preview: record.preview,
      kind: record.kind,
      schema_version: record.schemaVersion,
      source: record.source,
    };
  }

  private async getResolvedRecord(
    resultRef: string,
    context: RetrievalContext,
    options: ResolveOptions,
  ): Promise<ResultReferenceRecord | null> {
    const record = await this.store.getAuthorizedRecord(resultRef, context);
    if (!record) {
      return null;
    }
    if (options.expectedKind && record.kind !== options.expectedKind) {
      return null;
    }
    const normalizedData = normalizeDataForKind(
      record.kind,
      record.data,
      record.schemaVersion,
    );
    if (!normalizedData) {
      return null;
    }
    return {
      ...record,
      data: normalizedData,
    };
  }
}

export const extractRenderJunctionPayload = (
  value: unknown,
): RenderJunctionPayload | null => {
  const candidate = unwrapReferencePayload(value);
  if (!candidate || !isRecord(candidate.node_area_map)) {
    return null;
  }

  // 节点渲染结果只保留前端真正需要的映射字段，剔除空值并统一转为字符串。
  const nodeAreaMap = normalizeStringRecord(candidate.node_area_map);
  if (Object.keys(nodeAreaMap).length === 0) {
    return null;
  }

  const areaIds = Array.isArray(candidate.area_ids)
    ? candidate.area_ids.map((entry) => String(entry).trim()).filter(Boolean)
    : undefined;

  const areaColors = isRecord(candidate.area_colors)
    ? normalizeStringRecord(candidate.area_colors)
    : undefined;

  return {
    node_area_map: nodeAreaMap,
    ...(areaIds && areaIds.length > 0 ? { area_ids: areaIds } : {}),
    ...(areaColors && Object.keys(areaColors).length > 0
      ? { area_colors: areaColors }
      : {}),
  };
};

const normalizeDataForKind = (
  kind: ResultReferenceKind,
  data: unknown,
  schemaVersion: number,
): unknown | null => {
  if (!Number.isInteger(schemaVersion) || schemaVersion < 1) {
    return null;
  }
  if (kind === RESULT_REFERENCE_KIND.renderJunctionsPayload) {
    return extractRenderJunctionPayload(data);
  }
  return data;
};

const normalizeRenderPayloadFile = (
  value: unknown,
  filePath: string,
): { data: unknown } | null => {
  if (!isRecord(value) || !("data" in value)) {
    return null;
  }
  if (!isRecord(value.metadata) || !isRecord(value.location)) {
    return null;
  }
  if (value.location.file_path !== filePath) {
    return null;
  }
  return { data: value.data };
};

const unwrapReferencePayload = (value: unknown): Record<string, unknown> | null => {
  if (!isRecord(value)) {
    return null;
  }
  if ("data" in value && value.data !== undefined && value.data !== null) {
    return isRecord(value.data) ? value.data : null;
  }
  return value;
};

const normalizeStringRecord = (value: Record<string, unknown>) =>
  Object.fromEntries(
    Object.entries(value)
      .map(([key, entry]) => [String(key), String(entry ?? "").trim()])
      .filter(([, entry]) => entry.length > 0),
  );

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