import { z } from "zod";

import type {
  ArticleInput,
  CandidateFactCard,
  CheckStatus,
  ConfirmedFactCard,
  ImageInput,
  OptimizedArticle,
  PublishPlatform,
  QaCheck,
  QaReport,
  QualityRuleId,
} from "./types";

export const publishPlatformSchema = z.enum([
  "official_site",
  "media_article",
  "comparison_review",
  "recommendation_list",
]) satisfies z.ZodType<PublishPlatform>;

const checkStatusAliases: Record<string, CheckStatus> = {
  pass: "pass",
  passed: "pass",
  ok: "pass",
  "通过": "pass",
  "合格": "pass",
  warn: "warn",
  warning: "warn",
  "警告": "warn",
  "提醒": "warn",
  fail: "fail",
  failed: "fail",
  failure: "fail",
  "失败": "fail",
  "不通过": "fail",
};

function normalizeCheckStatus(value: unknown): unknown {
  const normalized = normalizedStringOrNull(value);
  if (!normalized) return value;
  return (
    checkStatusAliases[normalized.toLowerCase()] ??
    checkStatusAliases[normalized] ??
    normalized
  );
}

export const checkStatusSchema = z.preprocess(
  normalizeCheckStatus,
  z.enum(["pass", "warn", "fail"]),
) satisfies z.ZodType<CheckStatus>;

const qualityRuleIdAliases: Record<string, QualityRuleId> = {
  industry_alignment: "industry_alignment",
  "行业对齐": "industry_alignment",
  "行业一致性": "industry_alignment",
  image_text_match: "image_text_match",
  "图文匹配": "image_text_match",
  "图片文本匹配": "image_text_match",
  voice_consistency: "voice_consistency",
  "语气一致性": "voice_consistency",
  "口吻一致性": "voice_consistency",
  platform_fit: "platform_fit",
  "平台适配": "platform_fit",
  company_name_integrity: "company_name_integrity",
  "公司名一致性": "company_name_integrity",
  "公司名称一致性": "company_name_integrity",
  title_quality: "title_quality",
  "标题质量": "title_quality",
  body_quality: "body_quality",
  "正文质量": "body_quality",
  hallucination_risk: "hallucination_risk",
  "幻觉风险": "hallucination_risk",
  "虚构风险": "hallucination_risk",
  claim_consistency: "claim_consistency",
  "事实一致性": "claim_consistency",
  "主张一致性": "claim_consistency",
  context_sensitive_terms: "context_sensitive_terms",
  "语境敏感词": "context_sensitive_terms",
  "敏感词": "context_sensitive_terms",
};

function normalizeQualityRuleId(value: unknown): unknown {
  const normalized = normalizedStringOrNull(value);
  if (!normalized) return value;
  return qualityRuleIdAliases[normalized] ?? normalized;
}

export const qualityRuleIdSchema = z.preprocess(
  normalizeQualityRuleId,
  z.enum([
    "industry_alignment",
    "image_text_match",
    "voice_consistency",
    "platform_fit",
    "company_name_integrity",
    "title_quality",
    "body_quality",
    "hallucination_risk",
    "claim_consistency",
    "context_sensitive_terms",
  ]),
) satisfies z.ZodType<QualityRuleId>;

export const imageInputSchema = z.object({
  type: z.enum(["description", "link"]),
  content: z.string().trim().min(1),
}) satisfies z.ZodType<ImageInput>;

export const articleInputSchema = z.object({
  title: z.string().trim().min(1),
  body: z.string().trim().min(1),
  images: z.array(imageInputSchema).default([]),
  platform: publishPlatformSchema,
  user_instructions: z.string().trim().default(""),
}) satisfies z.ZodType<ArticleInput>;

function isPlainRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

function firstStringField(record: Record<string, unknown>, keys: string[]) {
  for (const key of keys) {
    const value = record[key];
    if (typeof value === "string" && value.trim().length > 0) {
      return value.trim();
    }
    if (typeof value === "number" && Number.isFinite(value)) {
      return String(value);
    }
  }
  return null;
}

const llmStringKeys = [
  "value",
  "text",
  "name",
  "title",
  "summary",
  "claim",
  "item",
  "topic",
  "audience",
  "industry",
  "company",
  "company_name",
  "brand",
  "product",
  "product_name",
  "content",
  "body",
  "markdown",
  "body_markdown",
  "reason",
  "description",
  "evidence",
  "source",
  "suggestion",
  "fix",
  "change",
  "changed",
  "after",
];

function normalizedStringOrNull(value: unknown) {
  const normalized = normalizeStringValue(value);
  return typeof normalized === "string" && normalized.trim().length > 0
    ? normalized.trim()
    : null;
}

function normalizeStringValue(value: unknown): unknown {
  if (typeof value === "string") return value.trim();
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
  if (Array.isArray(value)) {
    return value
      .map(normalizedStringOrNull)
      .filter((item): item is string => Boolean(item))
      .join("、");
  }
  if (!isPlainRecord(value)) return value;

  const direct = firstStringField(value, llmStringKeys);
  if (direct) return direct;

  const stringValues = Object.values(value)
    .map(normalizedStringOrNull)
    .filter((item): item is string => Boolean(item));
  return stringValues.join("；");
}

function normalizeStringList(value: unknown): unknown {
  if (value == null) return [];
  const items = Array.isArray(value) ? value : [value];
  return items
    .map(normalizedStringOrNull)
    .filter((item): item is string => Boolean(item));
}

const llmStringSchema = z.preprocess(normalizeStringValue, z.string().trim());

const requiredLlmStringSchema = z.preprocess(
  normalizeStringValue,
  z.string().trim().min(1),
);

const optionalLlmStringSchema = z.preprocess((value) => {
  if (value == null) return "";
  return normalizeStringValue(value);
}, z.string().trim().default(""));

const stringListSchema = z.preprocess(
  normalizeStringList,
  z.array(z.string().trim().min(1)).default([]),
);

function normalizeExperienceYears(value: unknown): unknown {
  let candidate = value;
  if (Array.isArray(candidate)) {
    candidate = candidate[0] ?? null;
  }
  if (isPlainRecord(candidate)) {
    for (const key of ["years", "year", "experience_years", "value"]) {
      const entry = candidate[key];
      if (typeof entry === "number" && Number.isFinite(entry)) return entry;
    }
    candidate =
      firstStringField(candidate, [
        "years",
        "year",
        "experience_years",
        "value",
        "text",
        "description",
      ]) ?? candidate;
  }
  if (typeof candidate !== "string") return candidate;

  const trimmed = candidate.trim();
  if (
    trimmed === "" ||
    /^(?:unknown|none|null|n\/a|not\s+specified|不详|不明确|未知|无)$/i.test(
      trimmed,
    )
  ) {
    return null;
  }
  const yearMatch = trimmed.match(/\d{1,3}/);
  return yearMatch ? Number(yearMatch[0]) : candidate;
}

const experienceYearsSchema = z.preprocess(
  normalizeExperienceYears,
  z.number().int().nonnegative().nullable().default(null),
);

const factCardBaseSchema = z.object({
  company_full_name: llmStringSchema,
  company_short_names: stringListSchema,
  brand_names: stringListSchema,
  product_names: stringListSchema,
  target_industry: llmStringSchema,
  target_audience: llmStringSchema,
  experience_years: experienceYearsSchema,
  core_claims: stringListSchema,
  forbidden_claims: stringListSchema,
  image_topics: stringListSchema,
  uncertain_items: stringListSchema,
});

export const candidateFactCardSchema = factCardBaseSchema
  .extend({
    is_ready_for_optimization: z.boolean().optional(),
  })
  .transform((card) => ({
    ...card,
    is_ready_for_optimization: card.uncertain_items.length === 0,
  })) satisfies z.ZodType<CandidateFactCard>;

export const confirmedFactCardSchema = factCardBaseSchema
  .extend({
    company_full_name: z.string().trim().min(1),
    uncertain_items: z.array(z.never()).length(0),
    confirmed_by_user: z.literal(true),
    is_ready_for_optimization: z.boolean().optional(),
  })
  .transform((card) => ({
    ...card,
    is_ready_for_optimization: true as const,
  })) satisfies z.ZodType<ConfirmedFactCard>;

export const imageSuggestionSchema = z.object({
  source: z.string().trim().min(1),
  suggestion: z.string().trim().min(1),
});

function normalizeImageSuggestions(value: unknown): unknown {
  if (!Array.isArray(value)) return [];

  return value.flatMap((item) => {
    if (!isPlainRecord(item)) return [];
    const source = normalizedStringOrNull(
      item.source ?? item.image ?? item.name ?? item.title,
    );
    const suggestion = normalizedStringOrNull(
      item.suggestion ?? item.description ?? item.reason ?? item.fix ?? item.text,
    );
    if (!source || !suggestion) return [];
    return [{ source, suggestion }];
  });
}

const imageSuggestionsSchema = z.preprocess(
  normalizeImageSuggestions,
  z.array(imageSuggestionSchema).default([]),
);

function normalizeChangedSection(value: unknown) {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return value;
  }

  const record = value as Record<string, unknown>;
  const section = firstStringField(record, [
    "section",
    "name",
    "title",
    "field",
    "target",
    "part",
  ]);
  const detail = firstStringField(record, [
    "change",
    "changed",
    "summary",
    "reason",
    "description",
    "fix",
    "after",
  ]);

  if (section && detail && section !== detail) {
    return `${section}: ${detail}`;
  }
  if (section || detail) {
    return section ?? detail;
  }

  const stringValues = Object.values(record)
    .filter((entry): entry is string => typeof entry === "string")
    .map((entry) => entry.trim())
    .filter(Boolean);

  return stringValues.join("; ");
}

const changedSectionSchema = z.preprocess(
  normalizeChangedSection,
  z.string().trim().min(1),
);

export const optimizedArticleSchema = z.object({
  job_id: z.string().trim().min(1).optional(),
  revision: z.number().int().positive().optional(),
  title: requiredLlmStringSchema,
  summary: requiredLlmStringSchema,
  body_markdown: requiredLlmStringSchema,
  image_suggestions: imageSuggestionsSchema,
  changed_sections: z.array(changedSectionSchema).default([]),
  requires_user_confirmation: stringListSchema,
}) satisfies z.ZodType<OptimizedArticle>;

function normalizeTargetAgent(value: unknown): unknown {
  const normalized = normalizedStringOrNull(value);
  if (!normalized) return null;
  const aliases: Record<string, string | null> = {
    none: null,
    null: null,
    "无": null,
    "无需": null,
    title: "title",
    "标题": "title",
    body: "body",
    "正文": "body",
    fact_card: "fact_card",
    factcard: "fact_card",
    "事实卡": "fact_card",
  };
  const normalizedKey = normalized.toLowerCase();
  if (Object.prototype.hasOwnProperty.call(aliases, normalizedKey)) {
    return aliases[normalizedKey];
  }
  if (Object.prototype.hasOwnProperty.call(aliases, normalized)) {
    return aliases[normalized];
  }
  return normalized;
}

const targetAgentSchema = z.preprocess(
  normalizeTargetAgent,
  z.string().trim().min(1).nullable().default(null),
);

export const qaCheckSchema = z.object({
  rule_id: qualityRuleIdSchema,
  status: checkStatusSchema,
  evidence: requiredLlmStringSchema,
  reason: requiredLlmStringSchema,
  suggested_fix: optionalLlmStringSchema,
  target_agent: targetAgentSchema,
}) satisfies z.ZodType<QaCheck>;

export const qaReportSchema = z.object({
  job_id: z.string().trim().min(1).optional(),
  revision: z.number().int().positive().optional(),
  overall_status: checkStatusSchema,
  checks: z.array(qaCheckSchema).default([]),
}) satisfies z.ZodType<QaReport>;
