import { nanoid } from "nanoid";

import type {
  CalibrationEvent,
  PerformanceSnapshot,
  PublicationRecord,
  RubricVersion,
  ScoringRun,
} from "../calibration/types";
import type {
  CaseInput,
  CaseListFilters,
  CaseMetadataPatch,
  OptimizationCase,
  OptimizationCaseStatus,
  OptimizationCaseType,
  OptimizationResultVersion,
  ResultVersionStatus,
} from "../cases/types";
import type {
  OptimizationFactCard,
  ImageInput,
  OptimizedArticle,
  PublishPlatform,
  QaReport,
} from "../domain/types";

import { createDatabase, getDefaultDatabasePath } from "./connection";
import { initializeSchema } from "./schema";

type JsonObject = Record<string, unknown>;

export interface BrandTemplate {
  id: string;
  brand_name: string;
  company_full_name: string;
  company_short_names: string[];
  product_names: string[];
  target_industries: string[];
  target_audience: string[];
  verified_claims: string[];
  forbidden_claims: string[];
  tone_rules: JsonObject;
  created_at: string;
  updated_at: string;
}

export type NewBrandTemplate = Omit<
  BrandTemplate,
  "id" | "created_at" | "updated_at"
>;

export interface ArticleJob {
  id: string;
  brand_template_id: string | null;
  case_id: string | null;
  source_title: string;
  source_body: string;
  image_inputs: ImageInput[];
  publish_platform: PublishPlatform;
  user_instructions: string;
  status: string;
  export_paths: Record<string, string>;
  created_at: string;
  updated_at: string;
}

export interface NewArticleJob {
  brand_template_id?: string | null;
  case_id?: string | null;
  source_title: string;
  source_body: string;
  image_inputs: ImageInput[];
  publish_platform: PublishPlatform;
  user_instructions: string;
}

interface BrandTemplateRow {
  id: string;
  brand_name: string;
  company_full_name: string;
  company_short_names: string;
  product_names: string;
  target_industries: string;
  target_audience: string;
  verified_claims: string;
  forbidden_claims: string;
  tone_rules: string;
  created_at: string;
  updated_at: string;
}

interface ArticleJobRow {
  id: string;
  brand_template_id: string | null;
  case_id: string | null;
  source_title: string;
  source_body: string;
  image_inputs: string;
  publish_platform: PublishPlatform;
  user_instructions: string;
  status: string;
  export_paths: string;
  created_at: string;
  updated_at: string;
}

interface FactCardRow {
  job_id: string;
  fact_card: string;
}

interface OptimizedArticleRow {
  job_id: string;
  revision: number;
  article: string;
}

interface QaReportRow {
  job_id: string;
  revision: number;
  report: string;
}

interface ScoringRunRow {
  id: string;
  result_version_id: string | null;
  case_type: OptimizationCaseType;
  job_id: string | null;
  revision: number | null;
  rubric_version_id: string;
  dimension_scores: string;
  composite_score: number;
  rationale: string;
  created_at: string;
}

interface PublicationRecordRow {
  id: string;
  result_version_id: string | null;
  job_id: string | null;
  revision: number | null;
  publish_target: string;
  url: string;
  published_at: string;
  status: "draft" | "published" | "archived";
  notes: string;
  created_at: string;
  updated_at: string;
}

interface PerformanceSnapshotRow {
  id: string;
  publication_id: string;
  source: "manual" | `adapter:${string}`;
  window_label: string;
  metrics: string;
  feedback_summary: string;
  raw_reference: string | null;
  snapshot_at: string;
}

interface OptimizationCaseRow {
  id: string;
  case_type: OptimizationCaseType;
  title: string;
  summary: string;
  status: OptimizationCaseStatus;
  customer_name: string;
  brand_name: string;
  project_tags: string;
  notes: string;
  publish_target: string;
  source_excerpt: string;
  result_excerpt: string;
  latest_result_version_id: string | null;
  latest_version_number: number | null;
  last_error_stage: string | null;
  last_error_summary: string | null;
  archived_at: string | null;
  created_at: string;
  updated_at: string;
}

interface CaseInputRow {
  case_id: string;
  case_type: OptimizationCaseType;
  article_job_id: string | null;
  payload: string;
  created_at: string;
  updated_at: string;
}

interface OptimizationResultVersionRow {
  id: string;
  case_id: string;
  case_type: OptimizationCaseType;
  version: number;
  status: ResultVersionStatus;
  article_job_id: string | null;
  article_revision: number | null;
  result_summary: string;
  payload: string;
  process_summary: string;
  llm_audit_summary: string;
  error_stage: string | null;
  error_summary: string | null;
  created_at: string;
}

function nowIso() {
  return new Date().toISOString();
}

function serialize(value: unknown) {
  return JSON.stringify(value);
}

function parseJson<T>(value: string): T {
  return JSON.parse(value) as T;
}

function withDb<T>(dbPath: string | undefined, action: (db: ReturnType<typeof createDatabase>) => T) {
  const db = createDatabase(dbPath ?? getDefaultDatabasePath());
  initializeSchema(db);
  try {
    return action(db);
  } finally {
    db.close();
  }
}

function toBrandTemplate(row: BrandTemplateRow): BrandTemplate {
  return {
    ...row,
    company_short_names: parseJson<string[]>(row.company_short_names),
    product_names: parseJson<string[]>(row.product_names),
    target_industries: parseJson<string[]>(row.target_industries),
    target_audience: parseJson<string[]>(row.target_audience),
    verified_claims: parseJson<string[]>(row.verified_claims),
    forbidden_claims: parseJson<string[]>(row.forbidden_claims),
    tone_rules: parseJson<JsonObject>(row.tone_rules),
  };
}

function toArticleJob(row: ArticleJobRow): ArticleJob {
  return {
    ...row,
    image_inputs: parseJson<ImageInput[]>(row.image_inputs),
    export_paths: parseJson<Record<string, string>>(row.export_paths),
  };
}

function toScoringRun(row: ScoringRunRow): ScoringRun {
  return {
    ...row,
    dimension_scores: parseJson<Record<string, number>>(row.dimension_scores),
  };
}

function toPublicationRecord(row: PublicationRecordRow): PublicationRecord {
  return {
    ...row,
    platform: isPublishPlatform(row.publish_target)
      ? row.publish_target
      : undefined,
  };
}

function toPerformanceSnapshot(row: PerformanceSnapshotRow): PerformanceSnapshot {
  return {
    ...row,
    raw_reference: row.raw_reference ?? undefined,
    metrics: parseJson<PerformanceSnapshot["metrics"]>(row.metrics),
  };
}

function toOptimizationCase(row: OptimizationCaseRow): OptimizationCase {
  return {
    ...row,
    project_tags: parseJson<string[]>(row.project_tags),
  };
}

function toCaseInput(row: CaseInputRow): CaseInput {
  return {
    ...row,
    payload: parseJson<CaseInput["payload"]>(row.payload),
  };
}

function toOptimizationResultVersion(
  row: OptimizationResultVersionRow,
): OptimizationResultVersion {
  return {
    ...row,
    payload: row.payload
      ? parseJson<OptimizationResultVersion["payload"]>(row.payload)
      : null,
    process_summary: parseJson<OptimizationResultVersion["process_summary"]>(
      row.process_summary,
    ),
    llm_audit_summary: parseJson<OptimizationResultVersion["llm_audit_summary"]>(
      row.llm_audit_summary,
    ),
  };
}

function isPublishPlatform(value: string): value is PublishPlatform {
  return [
    "official_site",
    "media_article",
    "comparison_review",
    "recommendation_list",
  ].includes(value);
}

export function createBrandTemplate(
  dbPath: string | undefined,
  input: NewBrandTemplate,
) {
  return withDb(dbPath, (db) => {
    const createdAt = nowIso();
    const template: BrandTemplate = {
      id: `brand_${nanoid(10)}`,
      ...input,
      created_at: createdAt,
      updated_at: createdAt,
    };

    db.prepare(
      `insert into brand_templates (
        id, brand_name, company_full_name, company_short_names, product_names,
        target_industries, target_audience, verified_claims, forbidden_claims,
        tone_rules, created_at, updated_at
      ) values (
        @id, @brand_name, @company_full_name, @company_short_names, @product_names,
        @target_industries, @target_audience, @verified_claims, @forbidden_claims,
        @tone_rules, @created_at, @updated_at
      )`,
    ).run({
      ...template,
      company_short_names: serialize(template.company_short_names),
      product_names: serialize(template.product_names),
      target_industries: serialize(template.target_industries),
      target_audience: serialize(template.target_audience),
      verified_claims: serialize(template.verified_claims),
      forbidden_claims: serialize(template.forbidden_claims),
      tone_rules: serialize(template.tone_rules),
    });

    return template;
  });
}

export function listBrandTemplates(dbPath?: string) {
  return withDb(dbPath, (db) =>
    db
      .prepare("select * from brand_templates order by updated_at desc")
      .all()
      .map((row) => toBrandTemplate(row as BrandTemplateRow)),
  );
}

export function getBrandTemplate(dbPath: string | undefined, id: string) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare("select * from brand_templates where id = ?")
      .get(id) as BrandTemplateRow | undefined;
    return row ? toBrandTemplate(row) : null;
  });
}

export function createArticleJob(dbPath: string | undefined, input: NewArticleJob) {
  return withDb(dbPath, (db) => {
    const createdAt = nowIso();
    const job: ArticleJob = {
      id: `job_${nanoid(10)}`,
      brand_template_id: input.brand_template_id ?? null,
      case_id: input.case_id ?? null,
      source_title: input.source_title,
      source_body: input.source_body,
      image_inputs: input.image_inputs,
      publish_platform: input.publish_platform,
      user_instructions: input.user_instructions,
      status: "draft",
      export_paths: {},
      created_at: createdAt,
      updated_at: createdAt,
    };

    db.prepare(
      `insert into article_jobs (
        id, brand_template_id, case_id, source_title, source_body, image_inputs,
        publish_platform, user_instructions, status, export_paths, created_at, updated_at
      ) values (
        @id, @brand_template_id, @case_id, @source_title, @source_body, @image_inputs,
        @publish_platform, @user_instructions, @status, @export_paths, @created_at, @updated_at
      )`,
    ).run({
      ...job,
      image_inputs: serialize(job.image_inputs),
      export_paths: serialize(job.export_paths),
    });

    return job;
  });
}

export function getArticleJob(dbPath: string | undefined, id: string) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare("select * from article_jobs where id = ?")
      .get(id) as ArticleJobRow | undefined;
    return row ? toArticleJob(row) : null;
  });
}

export function updateArticleJob(
  dbPath: string | undefined,
  id: string,
  changes: Partial<Pick<ArticleJob, "brand_template_id" | "status" | "export_paths">>,
) {
  return withDb(dbPath, (db) => {
    const existing = getArticleJob(dbPath, id);
    if (!existing) return null;
    const updated = {
      brand_template_id: changes.brand_template_id ?? existing.brand_template_id,
      status: changes.status ?? existing.status,
      export_paths: changes.export_paths ?? existing.export_paths,
      updated_at: nowIso(),
    };
    db.prepare(
      `update article_jobs set
        brand_template_id = @brand_template_id,
        status = @status,
        export_paths = @export_paths,
        updated_at = @updated_at
       where id = @id`,
    ).run({
      id,
      ...updated,
      export_paths: serialize(updated.export_paths),
    });
    return getArticleJob(dbPath, id);
  });
}

export function saveFactCard(
  dbPath: string | undefined,
  jobId: string,
  factCard: OptimizationFactCard,
) {
  return withDb(dbPath, (db) => {
    const timestamp = nowIso();
    db.prepare(
      `insert into fact_cards (
        job_id, source, fact_card, confirmed_by_user, created_at, updated_at
      ) values (
        @job_id, @source, @fact_card, @confirmed_by_user, @created_at, @updated_at
      )
      on conflict(job_id) do update set
        fact_card = excluded.fact_card,
        confirmed_by_user = excluded.confirmed_by_user,
        updated_at = excluded.updated_at`,
    ).run({
      job_id: jobId,
      source: factCard.confirmed_by_user
        ? "auto_extract_then_user_confirmed"
        : "auto_extract_for_optimization",
      fact_card: serialize(factCard),
      confirmed_by_user: factCard.confirmed_by_user ? 1 : 0,
      created_at: timestamp,
      updated_at: timestamp,
    });

    return { job_id: jobId, ...factCard };
  });
}

export function getFactCard(dbPath: string | undefined, jobId: string) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare("select job_id, fact_card from fact_cards where job_id = ?")
      .get(jobId) as FactCardRow | undefined;
    return row
      ? { job_id: row.job_id, ...parseJson<OptimizationFactCard>(row.fact_card) }
      : null;
  });
}

export function saveOptimizedArticle(
  dbPath: string | undefined,
  jobId: string,
  article: OptimizedArticle,
) {
  return withDb(dbPath, (db) => {
    const nextRevision =
      ((db
        .prepare(
          "select max(revision) as revision from optimized_articles where job_id = ?",
        )
        .get(jobId) as { revision: number | null }).revision ?? 0) + 1;
    const saved = { ...article, job_id: jobId, revision: nextRevision };

    db.prepare(
      `insert into optimized_articles (job_id, revision, article, created_at)
       values (?, ?, ?, ?)`,
    ).run(jobId, nextRevision, serialize(saved), nowIso());

    return saved;
  });
}

export function getLatestOptimizedArticle(dbPath: string | undefined, jobId: string) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare(
        `select job_id, revision, article from optimized_articles
         where job_id = ? order by revision desc limit 1`,
      )
      .get(jobId) as OptimizedArticleRow | undefined;
    return row ? parseJson<OptimizedArticle>(row.article) : null;
  });
}

export function saveQaReport(
  dbPath: string | undefined,
  jobId: string,
  revision: number,
  report: QaReport,
) {
  return withDb(dbPath, (db) => {
    const saved = { ...report, job_id: jobId, revision };
    db.prepare(
      `insert into qa_reports (job_id, revision, report, created_at)
       values (?, ?, ?, ?)
       on conflict(job_id, revision) do update set
        report = excluded.report`,
    ).run(jobId, revision, serialize(saved), nowIso());
    return saved;
  });
}

export function getLatestQaReport(dbPath: string | undefined, jobId: string) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare(
        `select job_id, revision, report from qa_reports
         where job_id = ? order by revision desc limit 1`,
      )
      .get(jobId) as QaReportRow | undefined;
    return row ? parseJson<QaReport>(row.report) : null;
  });
}

export function createOptimizationCase(
  dbPath: string | undefined,
  input: {
    case_type: OptimizationCaseType;
    title: string;
    summary: string;
    publish_target: string;
    source_excerpt: string;
  },
) {
  return withDb(dbPath, (db) => {
    const timestamp = nowIso();
    const optimizationCase: OptimizationCase = {
      id: `case_${nanoid(10)}`,
      case_type: input.case_type,
      title: input.title,
      summary: input.summary,
      status: "running",
      customer_name: "",
      brand_name: "",
      project_tags: [],
      notes: "",
      publish_target: input.publish_target,
      source_excerpt: input.source_excerpt,
      result_excerpt: "",
      latest_result_version_id: null,
      latest_version_number: null,
      last_error_stage: null,
      last_error_summary: null,
      archived_at: null,
      created_at: timestamp,
      updated_at: timestamp,
    };

    db.prepare(
      `insert into optimization_cases (
        id, case_type, title, summary, status, customer_name, brand_name,
        project_tags, notes, publish_target, source_excerpt, result_excerpt,
        latest_result_version_id, latest_version_number, last_error_stage,
        last_error_summary, archived_at, created_at, updated_at
      ) values (
        @id, @case_type, @title, @summary, @status, @customer_name, @brand_name,
        @project_tags, @notes, @publish_target, @source_excerpt, @result_excerpt,
        @latest_result_version_id, @latest_version_number, @last_error_stage,
        @last_error_summary, @archived_at, @created_at, @updated_at
      )`,
    ).run({
      ...optimizationCase,
      project_tags: serialize(optimizationCase.project_tags),
    });

    return optimizationCase;
  });
}

export function saveCaseInput(
  dbPath: string | undefined,
  input: Omit<CaseInput, "created_at" | "updated_at">,
) {
  return withDb(dbPath, (db) => {
    const timestamp = nowIso();
    const saved: CaseInput = {
      ...input,
      created_at: timestamp,
      updated_at: timestamp,
    };

    db.prepare(
      `insert into case_inputs (
        case_id, case_type, article_job_id, payload, created_at, updated_at
      ) values (
        @case_id, @case_type, @article_job_id, @payload, @created_at, @updated_at
      )
      on conflict(case_id) do update set
        case_type = excluded.case_type,
        article_job_id = excluded.article_job_id,
        payload = excluded.payload,
        updated_at = excluded.updated_at`,
    ).run({
      ...saved,
      payload: serialize(saved.payload),
    });

    return saved;
  });
}

export function listOptimizationCases(
  dbPath: string | undefined,
  filters: CaseListFilters,
) {
  return withDb(dbPath, (db) => {
    const where: string[] = [];
    const values: unknown[] = [];

    if (!filters.include_archived) {
      where.push("archived_at is null");
    }
    if (filters.case_type) {
      where.push("case_type = ?");
      values.push(filters.case_type);
    }
    if (filters.status) {
      where.push("status = ?");
      values.push(filters.status);
    }
    if (filters.publish_target) {
      where.push("publish_target = ?");
      values.push(filters.publish_target);
    }
    if (filters.project_tag) {
      where.push("project_tags like ?");
      values.push(`%"${filters.project_tag}"%`);
    }
    if (filters.created_from) {
      where.push("created_at >= ?");
      values.push(filters.created_from);
    }
    if (filters.created_to) {
      where.push("created_at <= ?");
      values.push(filters.created_to);
    }
    if (filters.q) {
      where.push(
        "(title like ? or summary like ? or source_excerpt like ? or result_excerpt like ? or customer_name like ? or brand_name like ? or notes like ?)",
      );
      const keyword = `%${filters.q}%`;
      values.push(keyword, keyword, keyword, keyword, keyword, keyword, keyword);
    }

    const clause = where.length > 0 ? `where ${where.join(" and ")}` : "";
    return db
      .prepare(`select * from optimization_cases ${clause} order by updated_at desc`)
      .all(...values)
      .map((row) => toOptimizationCase(row as OptimizationCaseRow));
  });
}

export function getOptimizationCaseDetail(
  dbPath: string | undefined,
  caseId: string,
) {
  return withDb(dbPath, (db) => {
    const caseRow = db
      .prepare("select * from optimization_cases where id = ?")
      .get(caseId) as OptimizationCaseRow | undefined;
    if (!caseRow) return null;

    const inputRow = db
      .prepare("select * from case_inputs where case_id = ?")
      .get(caseId) as CaseInputRow | undefined;
    const versionRows = db
      .prepare(
        `select * from optimization_result_versions
         where case_id = ?
         order by version desc`,
      )
      .all(caseId) as OptimizationResultVersionRow[];

    return {
      case: toOptimizationCase(caseRow),
      input: inputRow ? toCaseInput(inputRow) : null,
      versions: versionRows.map(toOptimizationResultVersion),
    };
  });
}

export function updateOptimizationCaseMetadata(
  dbPath: string | undefined,
  caseId: string,
  changes: CaseMetadataPatch,
) {
  return withDb(dbPath, (db) => {
    const existing = db
      .prepare("select * from optimization_cases where id = ?")
      .get(caseId) as OptimizationCaseRow | undefined;
    if (!existing) return null;

    const updated = {
      title: changes.title ?? existing.title,
      customer_name: changes.customer_name ?? existing.customer_name,
      brand_name: changes.brand_name ?? existing.brand_name,
      project_tags:
        changes.project_tags === undefined
          ? existing.project_tags
          : serialize(changes.project_tags),
      notes: changes.notes ?? existing.notes,
      updated_at: nowIso(),
    };

    db.prepare(
      `update optimization_cases set
        title = @title,
        customer_name = @customer_name,
        brand_name = @brand_name,
        project_tags = @project_tags,
        notes = @notes,
        updated_at = @updated_at
       where id = @id`,
    ).run({ id: caseId, ...updated });

    const row = db
      .prepare("select * from optimization_cases where id = ?")
      .get(caseId) as OptimizationCaseRow;
    return toOptimizationCase(row);
  });
}

export function archiveOptimizationCase(
  dbPath: string | undefined,
  caseId: string,
) {
  return updateCaseArchiveState(dbPath, caseId, true);
}

export function restoreOptimizationCase(
  dbPath: string | undefined,
  caseId: string,
) {
  return updateCaseArchiveState(dbPath, caseId, false);
}

function updateCaseArchiveState(
  dbPath: string | undefined,
  caseId: string,
  archived: boolean,
) {
  return withDb(dbPath, (db) => {
    const existing = db
      .prepare("select * from optimization_cases where id = ?")
      .get(caseId) as OptimizationCaseRow | undefined;
    if (!existing) return null;

    const timestamp = nowIso();
    const restoredStatus = existing.latest_result_version_id
      ? "optimized"
      : existing.last_error_summary
        ? "failed"
        : "running";
    db.prepare(
      `update optimization_cases set
        status = ?,
        archived_at = ?,
        updated_at = ?
       where id = ?`,
    ).run(
      archived ? "archived" : restoredStatus,
      archived ? timestamp : null,
      timestamp,
      caseId,
    );

    const row = db
      .prepare("select * from optimization_cases where id = ?")
      .get(caseId) as OptimizationCaseRow;
    return toOptimizationCase(row);
  });
}

export function markOptimizationCaseFailed(
  dbPath: string | undefined,
  caseId: string,
  input: { error_stage: string; error_summary: string },
) {
  return withDb(dbPath, (db) => {
    const timestamp = nowIso();
    db.prepare(
      `update optimization_cases set
        status = 'failed',
        last_error_stage = ?,
        last_error_summary = ?,
        updated_at = ?
       where id = ?`,
    ).run(input.error_stage, input.error_summary, timestamp, caseId);

    const row = db
      .prepare("select * from optimization_cases where id = ?")
      .get(caseId) as OptimizationCaseRow | undefined;
    return row ? toOptimizationCase(row) : null;
  });
}

export function createOptimizationResultVersion(
  dbPath: string | undefined,
  input: Omit<OptimizationResultVersion, "id" | "version" | "created_at">,
) {
  return withDb(dbPath, (db) => {
    const nextVersion =
      ((db
        .prepare(
          "select max(version) as version from optimization_result_versions where case_id = ?",
        )
        .get(input.case_id) as { version: number | null }).version ?? 0) + 1;
    const createdAt = nowIso();
    const resultVersion: OptimizationResultVersion = {
      id: `ver_${nanoid(10)}`,
      ...input,
      version: nextVersion,
      created_at: createdAt,
    };

    db.prepare(
      `insert into optimization_result_versions (
        id, case_id, case_type, version, status, article_job_id, article_revision,
        result_summary, payload, process_summary, llm_audit_summary,
        error_stage, error_summary, created_at
      ) values (
        @id, @case_id, @case_type, @version, @status, @article_job_id, @article_revision,
        @result_summary, @payload, @process_summary, @llm_audit_summary,
        @error_stage, @error_summary, @created_at
      )`,
    ).run({
      ...resultVersion,
      payload: resultVersion.payload ? serialize(resultVersion.payload) : "",
      process_summary: serialize(resultVersion.process_summary),
      llm_audit_summary: serialize(resultVersion.llm_audit_summary),
    });

    db.prepare(
      `update optimization_cases set
        status = @status,
        result_excerpt = @result_excerpt,
        latest_result_version_id = @latest_result_version_id,
        latest_version_number = @latest_version_number,
        last_error_stage = @last_error_stage,
        last_error_summary = @last_error_summary,
        updated_at = @updated_at
       where id = @case_id`,
    ).run({
      case_id: input.case_id,
      status: input.status === "optimized" ? "optimized" : "failed",
      result_excerpt: input.result_summary,
      latest_result_version_id: resultVersion.id,
      latest_version_number: resultVersion.version,
      last_error_stage: input.error_stage,
      last_error_summary: input.error_summary,
      updated_at: createdAt,
    });

    return resultVersion;
  });
}

export function getOptimizationResultVersion(
  dbPath: string | undefined,
  versionId: string,
) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare("select * from optimization_result_versions where id = ?")
      .get(versionId) as OptimizationResultVersionRow | undefined;
    return row ? toOptimizationResultVersion(row) : null;
  });
}

export function findResultVersionForArticleRevision(
  dbPath: string | undefined,
  jobId: string,
  revision: number,
) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare(
        `select * from optimization_result_versions
         where article_job_id = ? and article_revision = ?
         order by version desc
         limit 1`,
      )
      .get(jobId, revision) as OptimizationResultVersionRow | undefined;
    return row ? toOptimizationResultVersion(row) : null;
  });
}

export function saveRubricVersion(
  dbPath: string | undefined,
  rubric: RubricVersion,
) {
  return withDb(dbPath, (db) => {
    db.prepare(
      `insert into rubric_versions (
        id, version, name, dimensions, formula, is_active, created_at
      ) values (?, ?, ?, ?, ?, ?, ?)
      on conflict(id) do update set
        version = excluded.version,
        name = excluded.name,
        dimensions = excluded.dimensions,
        formula = excluded.formula,
        is_active = excluded.is_active`,
    ).run(
      rubric.id,
      rubric.version,
      rubric.name,
      serialize(rubric.dimensions),
      rubric.formula,
      rubric.is_active ? 1 : 0,
      rubric.created_at,
    );
    return rubric;
  });
}

export function saveScoringRun(dbPath: string | undefined, run: ScoringRun) {
  return withDb(dbPath, (db) => {
    db.prepare(
      `insert into scoring_runs (
        id, result_version_id, case_type, job_id, revision, rubric_version_id, dimension_scores,
        composite_score, rationale, created_at
      ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
    ).run(
      run.id,
      run.result_version_id ?? null,
      run.case_type ?? "article",
      run.job_id,
      run.revision,
      run.rubric_version_id,
      serialize(run.dimension_scores),
      run.composite_score,
      run.rationale,
      run.created_at,
    );
    return run;
  });
}

export function getLatestScoringRun(
  dbPath: string | undefined,
  jobId: string,
  revision: number,
) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare(
        `select * from scoring_runs
         where job_id = ? and revision = ?
         order by created_at desc
         limit 1`,
      )
      .get(jobId, revision) as ScoringRunRow | undefined;
    return row ? toScoringRun(row) : null;
  });
}

export function getLatestScoringRunForResultVersion(
  dbPath: string | undefined,
  resultVersionId: string,
) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare(
        `select * from scoring_runs
         where result_version_id = ?
         order by created_at desc
         limit 1`,
      )
      .get(resultVersionId) as ScoringRunRow | undefined;
    return row ? toScoringRun(row) : null;
  });
}

export function createPublicationRecord(
  dbPath: string | undefined,
  input: Omit<
    PublicationRecord,
    "id" | "created_at" | "updated_at" | "result_version_id" | "publish_target"
  > & {
    result_version_id?: string | null;
    publish_target?: string;
  },
) {
  return withDb(dbPath, (db) => {
    const timestamp = nowIso();
    const publishTarget = input.publish_target ?? input.platform ?? "未指定";
    const record: PublicationRecord = {
      id: `pub_${nanoid(10)}`,
      ...input,
      result_version_id: input.result_version_id ?? null,
      publish_target: publishTarget,
      platform: isPublishPlatform(publishTarget) ? publishTarget : input.platform,
      created_at: timestamp,
      updated_at: timestamp,
    };
    db.prepare(
      `insert into publication_records (
        id, result_version_id, job_id, revision, publish_target, url, published_at, status,
        notes, created_at, updated_at
      ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
    ).run(
      record.id,
      record.result_version_id,
      record.job_id,
      record.revision,
      record.publish_target,
      record.url,
      record.published_at,
      record.status,
      record.notes,
      record.created_at,
      record.updated_at,
    );
    return record;
  });
}

export function listPublicationRecords(dbPath: string | undefined, jobId: string) {
  return withDb(dbPath, (db) =>
    db
      .prepare("select * from publication_records where job_id = ? order by published_at desc")
      .all(jobId)
      .map((row) => toPublicationRecord(row as PublicationRecordRow)),
  );
}

export function listPublicationRecordsForResultVersion(
  dbPath: string | undefined,
  resultVersionId: string,
) {
  return withDb(dbPath, (db) =>
    db
      .prepare(
        "select * from publication_records where result_version_id = ? order by published_at desc",
      )
      .all(resultVersionId)
      .map((row) => toPublicationRecord(row as PublicationRecordRow)),
  );
}

export function getPublicationRecord(dbPath: string | undefined, id: string) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare("select * from publication_records where id = ?")
      .get(id) as PublicationRecordRow | undefined;
    return row ? toPublicationRecord(row) : null;
  });
}

export function savePerformanceSnapshot(
  dbPath: string | undefined,
  snapshot: PerformanceSnapshot,
) {
  return withDb(dbPath, (db) => {
    db.prepare(
      `insert into performance_snapshots (
        id, publication_id, source, window_label, metrics,
        feedback_summary, raw_reference, snapshot_at
      ) values (?, ?, ?, ?, ?, ?, ?, ?)`,
    ).run(
      snapshot.id,
      snapshot.publication_id,
      snapshot.source,
      snapshot.window_label,
      serialize(snapshot.metrics),
      snapshot.feedback_summary,
      snapshot.raw_reference ?? null,
      snapshot.snapshot_at,
    );
    return snapshot;
  });
}

export function listPerformanceSnapshots(
  dbPath: string | undefined,
  publicationId: string,
) {
  return withDb(dbPath, (db) =>
    db
      .prepare(
        "select * from performance_snapshots where publication_id = ? order by snapshot_at desc",
      )
      .all(publicationId)
      .map((row) => toPerformanceSnapshot(row as PerformanceSnapshotRow)),
  );
}

export function saveCalibrationEvent(
  dbPath: string | undefined,
  event: CalibrationEvent,
) {
  return withDb(dbPath, (db) => {
    db.prepare(
      `insert into calibration_events (
        id, publication_id, scoring_run_id, performance_snapshot_id,
        direction, observations, recommended_action, created_at
      ) values (?, ?, ?, ?, ?, ?, ?, ?)`,
    ).run(
      event.id,
      event.publication_id,
      event.scoring_run_id,
      event.performance_snapshot_id,
      event.direction,
      serialize(event.observations),
      event.recommended_action,
      event.created_at,
    );
    return event;
  });
}
