/**
 * AI 组件草稿 → 画布数据（语义层的逆运算）
 * <p>
 * 与 use-designer 的 collectSchema 构成往返：collectSchema 从画布收集 {type, props}，
 * 本模块把 {type, props} 装回画布。两者必须逐项对齐，否则「AI 生成 → 保存 → 再打开」
 * 会掉组件或错位属性。
 * <p>
 * 刻意放在 core/ 而不是设计器里：它不需要 editor 实例，是纯函数，可单测，
 * 也避免 components/ 反向依赖 #/api。
 */
import type { QualityComponentDefinition, QualityProps } from './types';

import { buildQualityContent } from './factory';
import { getQualityComponent } from './registry';
import { validateQualityProps } from './validator';

/**
 * AI 草稿里的一个组件。
 * <p>
 * 结构完全扁平：没有 children。props 的 key 由前端积木清单定义，
 * 所以这里是宽松的 Record 而不是强类型——合法 key 的权威在注册表。
 */
export interface QualityDraftComponent {
  type?: string;
  props?: Record<string, unknown>;
}

/** 被跳过的草稿组件 */
export interface QualityAssemblySkip {
  type: string;
  /** 展示名；未注册的 type 没有展示名，回退为 type 本身 */
  label: string;
  reason: string;
}

export interface QualityAssemblyResult {
  /** 可直接交给 loadProjectData 的 GrapesJS 项目数据 */
  grapes: Record<string, unknown>;
  /** 识别到了但装不进画布的组件 */
  skipped: QualityAssemblySkip[];
  /** 装进去了、但属性不完整或不合法的组件，提示用 */
  problems: string[];
}

/**
 * 把草稿组件装配成画布数据。
 * <p>
 * 单个组件坏掉不影响其余：未知类型进 skipped，属性有问题的进 problems，
 * 都不会让整批失败——AI 的产物本来就不可信，全有或全无只会让用户白等一次识别。
 */
export function assembleQualityCanvas(
  components?: QualityDraftComponent[],
): QualityAssemblyResult {
  const skipped: QualityAssemblySkip[] = [];
  const problems: string[] = [];
  const nodes: Record<string, unknown>[] = [];

  (components ?? []).forEach((draft, index) => {
    const position = `第 ${index + 1} 个组件`;
    const type = draft?.type ?? '';
    const definition = getQualityComponent(type);
    if (!definition) {
      skipped.push({
        type,
        label: type || '未命名组件',
        reason: `${position}：类型「${type || '空'}」不在当前设计器的组件清单内，已跳过`,
      });
      return;
    }

    const normalized = normalizeDraftProps(definition, draft?.props);
    normalized.problems.forEach((message) => problems.push(`${position}：${message}`));

    // buildQualityContent 做的正是往返所需的四件事：defaults 打底、buildContent 生成结构、
    // propsToAttributes 只输出声明过的 key 并字符串化、写入 data-quality-type。
    const content = buildQualityContent(definition, normalized.props) as Record<string, unknown>;
    // traits 不进画布数据：载入后由 restoreQualityTraits 按 data-quality-type 重建，
    // 与「打开一份已保存的版本」走的是同一条路（traits 本来也不参与项目数据序列化）。
    delete content.traits;
    nodes.push(content);

    validateQualityProps(definition, normalized.props).forEach((message) =>
      problems.push(`${position}：${message}`),
    );
  });

  return {
    // pages 必须非空，否则 loadSchema 会走空白页兜底分支，装配好的组件全部丢掉
    grapes: {
      pages: [{ component: { style: { padding: '0' }, components: nodes } }],
    },
    skipped,
    problems,
  };
}

/**
 * 把 AI 给的属性收敛成语义层能承载的原始值。
 * <p>
 * 只管一件事：值必须是 boolean / number / string。模型偶尔会把字段写成嵌套对象或数组
 * （比如把检验项数据行整段塞进 itemsPath），这类值经 coerceFieldValue 的 String() 会变成
 * "[object Object]" 写进画布，属性面板里显示一串乱码，还会静默盖掉默认值——所以在这里拦掉并说明。
 * <p>
 * 不检查 key 是否声明过：未声明的 key 由 propsToAttributes 丢弃，本来也进不了画布。
 */
function normalizeDraftProps(
  definition: QualityComponentDefinition,
  props?: Record<string, unknown>,
): { props: QualityProps; problems: string[] } {
  const normalized: QualityProps = {};
  const problems: string[] = [];
  if (!props) {
    return { props: normalized, problems };
  }
  definition.propertySchema.forEach((field) => {
    const value = props[field.key];
    // null 与 undefined 同义：模型没给值，交给 defaults 兜底
    if (value === undefined || value === null) {
      return;
    }
    if (
      typeof value === 'boolean' ||
      typeof value === 'number' ||
      typeof value === 'string'
    ) {
      normalized[field.key] = value;
      return;
    }
    problems.push(
      `${definition.label}的「${field.label}」收到的是结构化数据，语义层只承载单个值，已按默认值处理`,
    );
  });
  return { props: normalized, problems };
}
