'use strict';

const { STATUS } = require('./constants');

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

function uniqStrings(arr) {
  const seen = new Set(); const out = [];
  for (const v of arr || []) {
    const name = String(v ?? '').trim();
    if (!name || seen.has(name)) continue;
    seen.add(name); out.push(name);
  }
  return out;
}

function normalizeTags(tags) {
  if (!tags) return [];
  if (Array.isArray(tags)) return uniqStrings(tags.map(t => typeof t === 'string' ? t : (t?.name || t?.label || t?.tag || '')));
  if (typeof tags === 'string') return uniqStrings(tags.split(/[,，\s]+/));
  return [];
}

function normalizePublicTags(tags) {
  if (!tags) return [];
  const list = Array.isArray(tags) ? tags : (typeof tags === 'string' ? tags.split(/[,，\s]+/) : []);
  const out = []; const seen = new Set();
  for (const t of list) {
    const name = String(typeof t === 'string' ? t : (t?.name || t?.label || t?.tag || '')).trim();
    if (!name || seen.has(name)) continue;
    seen.add(name);
    const count = typeof t === 'object' && t ? Number(t.count ?? t.total_cont ?? t.total ?? t.num ?? 0) || 0 : 0;
    out.push(count ? { name, count } : { name });
  }
  return out;
}

function myTagsOf(c) { return normalizeTags(c?.myTags || c?.my_tags || c?.tags || []); }
function publicTagsOf(c) { return normalizePublicTags(c?.publicTags || c?.public_tags || c?.subjectTags || c?.raw?.subject?.tags || []); }

function normalizeCollection(item, fallbackType) {
  const s = item.subject || {};
  const imgs = s.images || {};
  const st = item.subject_type || s.type || fallbackType?.subject_type || 0;
  const ct = item.type || fallbackType?.collection_type || 0;
  const title = s.name_cn || s.name || `Subject ${item.subject_id || s.id}`;
  const raw = item;
  return {
    subject_id: Number(item.subject_id || s.id), subject_type: Number(st), collection_type: Number(ct),
    title, name: s.name || '', name_cn: s.name_cn || '', date: s.date || '', eps: s.eps ?? '', volumes: s.volumes ?? '',
    bgm_score: s.score ?? s.rating?.score ?? '', rank: s.rank ?? '',
    summary: s.summary || s.short_summary || '', image: imgs.large || imgs.common || imgs.medium || imgs.grid || imgs.small || '', images: imgs,
    rate: item.rate ?? 0, comment: item.comment || '', tags: normalizeTags(item.tags), myTags: normalizeTags(item.tags), publicTags: normalizePublicTags(s.tags || item.publicTags || item.public_tags), ep_status: item.ep_status ?? '', vol_status: item.vol_status ?? '',
    private: !!item.private,
    collection_created_at: item.created_at || item.createdAt || item.collect_created_at || '',
    bangumi_updated_at: item.updated_at || item.updatedAt || '', raw
  };
}

function applySubjectDetail(n, detail) {
  if (!detail) return n;
  return {
    ...n,
    platform: detail.platform || '',
    nsfw: !!detail.nsfw,
    infobox: Array.isArray(detail.infobox) ? detail.infobox : [],
    meta_tags: Array.isArray(detail.meta_tags) ? detail.meta_tags : [],
    series: !!detail.series,
    locked: !!detail.locked,
    rating_detail: detail.rating || {},
    collection_stats: detail.collection || {},
    total_episodes: detail.total_episodes ?? n.eps ?? '',
    publicTags: detail.tags ? normalizePublicTags(detail.tags) : n.publicTags,
    subject_raw: detail,
    subject_fetched_at: nowISO(),
  };
}

function stableStringify(obj) {
  if (obj === null || typeof obj !== 'object') return JSON.stringify(obj);
  if (Array.isArray(obj)) return '[' + obj.map(stableStringify).join(',') + ']';
  return '{' + Object.keys(obj).sort().map(k => JSON.stringify(k) + ':' + stableStringify(obj[k])).join(',') + '}';
}

function hashString(str) { let h = 2166136261; for (let i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h = Math.imul(h, 16777619); } return (h >>> 0).toString(16); }

function comparable(n) {
  return {
    subject_id: n.subject_id, subject_type: n.subject_type, collection_type: n.collection_type, title: n.title, name: n.name, name_cn: n.name_cn,
    date: n.date, eps: n.eps, volumes: n.volumes, bgm_score: n.bgm_score, rank: n.rank, summary: n.summary, image: n.image, rate: n.rate, comment: n.comment,
    tags: myTagsOf(n), publicTags: publicTagsOf(n), ep_status: n.ep_status, vol_status: n.vol_status, private: n.private,
    collection_created_at: n.collection_created_at, bangumi_updated_at: n.bangumi_updated_at
  };
}

function makeSnapshot(n) { return { ...n, content_hash: hashString(stableStringify(comparable(n))) }; }

// --- change-detection for history entries (server-side simplified formatting) ---
function fmtDate(s) { if (!s) return ''; const d = new Date(s); return isNaN(d) ? String(s) : d.toLocaleString(); }
function fmtScore(v) { const n = Number(v); if (!n) return ''; return Number.isInteger(n) ? String(n) : n.toFixed(1).replace(/\.0$/, ''); }
function shortValue(v, max = 80) { const t = Array.isArray(v) ? v.join(' / ') : String(v ?? ''); return t.length > max ? t.slice(0, max) + '…' : t; }
function valuesSame(a, b) { return stableStringify(a ?? '') === stableStringify(b ?? ''); }

function collectionFieldDiff(oldC, newC) {
  const fields = [
    ['collection_type', '收藏状态', v => STATUS[v] || v || '-'],
    ['rate', '我的评分', v => v || '未评分'],
    ['ep_status', '章节进度', v => v || '-'],
    ['vol_status', '卷进度', v => v || '-'],
    ['comment', '吐槽/观后感', v => shortValue(v || '无吐槽', 120)],
    ['private', '私密状态', v => v ? '私密' : '公开'],
    ['tags', '我的标签', v => shortValue(normalizeTags(v), 120)],
    ['publicTags', '公共标签', v => shortValue(normalizePublicTags(v).map(x => x.count ? `${x.name}(${x.count})` : x.name), 120)],
    ['bangumi_updated_at', 'API 更新时间', v => fmtDate(v) || '-'],
    ['title', '标题', v => v || '-'],
    ['date', '上映日期', v => v || '-'],
    ['bgm_score', 'Bangumi 均分', v => fmtScore(v) || '-'],
    ['rank', '排名', v => v || '-']
  ];
  const changes = [];
  for (const [key, label, fmt] of fields) {
    const oldVal = key === 'tags' ? myTagsOf(oldC) : (key === 'publicTags' ? publicTagsOf(oldC) : oldC?.[key]);
    const newVal = key === 'tags' ? myTagsOf(newC) : (key === 'publicTags' ? publicTagsOf(newC) : newC?.[key]);
    if (!valuesSame(oldVal, newVal)) changes.push({ field: key, label, from: fmt(oldVal), to: fmt(newVal), old: oldVal ?? '', new: newVal ?? '' });
  }
  return changes;
}

module.exports = {
  nowISO, uniqStrings, normalizeTags, normalizePublicTags, myTagsOf, publicTagsOf,
  normalizeCollection, applySubjectDetail, stableStringify, hashString, comparable, makeSnapshot, collectionFieldDiff
};
