/**
 * MemoryLearningForm.tsx — "Reflection 履歴" settings section
 *
 * ReflectionTimelinePanel — paged snapshot history + revert + 30-day metrics.
 * (Memory entry editing moved to User Folder → memory/ — MemoryPanel.tsx.)
 */

import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery, useMutation, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
import i18n from '../../i18n';

// ── API types ─────────────────────────────────────────────────────────────────

interface SnapshotIndexEntry {
  ts: string;
  snapshotId: string;
  jobId: string;
  pieceName: string;
  memoryChanges: number;
  pieceEdited: boolean;
  reverted: boolean;
  // outcome appears in detail, not in index — fetched lazily
}

interface SnapshotDetail {
  snapshotId: string;
  ts: string;
  originalJobId: string;
  userId: string;
  pieceName: string;
  outcome: string;
  reasoning: string;
  modelUsed?: string;
  tokensIn?: number;
  tokensOut?: number;
  ratingAtTime?: 'good' | 'bad' | null;
  memoryChanges: number;
  pieceEdited: boolean;
  rejections?: Array<{ code: string; name?: string }>;
  beforeFiles: Record<string, string>;
  afterFiles: Record<string, string>;
  pieceBeforeYaml?: string;
  pieceAfterYaml?: string;
  diff?: string;
}

interface HistoryPage {
  items: SnapshotIndexEntry[];
  nextCursor: string | null;
}

interface ReflectionMetrics {
  applied: number;
  partial: number;
  abstained: number;
  rejected: number;
  failed: number;
  tokensIn: number;
  tokensOut: number;
  pieceEdits: number;
}

// ── API helpers ───────────────────────────────────────────────────────────────

async function fetchHistoryPage(cursor?: string): Promise<HistoryPage> {
  const params = new URLSearchParams({ limit: '20' });
  if (cursor) params.set('before', cursor);
  const res = await fetch(`/api/local/reflection/history?${params}`);
  if (!res.ok) throw new Error(i18n.t('memoryLearning.err.loadHistory', { ns: 'settings', status: res.status }));
  return res.json();
}

async function fetchSnapshotDetail(snapshotId: string): Promise<SnapshotDetail> {
  const res = await fetch(`/api/local/reflection/history/${encodeURIComponent(snapshotId)}`);
  if (!res.ok) throw new Error(i18n.t('memoryLearning.err.loadSnapshot', { ns: 'settings', status: res.status }));
  return res.json();
}

async function revertSnapshot(snapshotId: string): Promise<{ reverted: boolean }> {
  const res = await fetch(
    `/api/local/reflection/history/${encodeURIComponent(snapshotId)}/revert`,
    { method: 'POST' },
  );
  if (!res.ok) {
    const data = await res.json().catch(() => ({ error: res.statusText }));
    throw new Error(data.error ?? res.statusText);
  }
  return res.json();
}

async function fetchMetrics(days: number = 30): Promise<ReflectionMetrics> {
  const res = await fetch(`/api/local/reflection/metrics?days=${days}`);
  if (!res.ok) throw new Error(i18n.t('memoryLearning.err.loadMetrics', { ns: 'settings', status: res.status }));
  return res.json();
}

// ── Shared UI primitives ──────────────────────────────────────────────────────

const OUTCOME_CLS: Record<string, string> = {
  applied:   'bg-emerald-100 dark:bg-emerald-500/15 text-emerald-800 dark:text-emerald-300',
  partial:   'bg-yellow-100 dark:bg-yellow-500/15 text-yellow-800 dark:text-yellow-300',
  abstained: 'bg-slate-100 text-slate-600',
  rejected:  'bg-red-100 dark:bg-red-500/15 text-red-700 dark:text-red-300',
  failed:    'bg-red-200 dark:bg-red-500/20 text-red-900 dark:text-red-300',
};

function OutcomeBadge({ outcome }: { outcome: string }) {
  const { t } = useTranslation('settings');
  const cls = OUTCOME_CLS[outcome] ?? 'bg-slate-100 text-slate-600';
  const label = OUTCOME_CLS[outcome] ? t(`memoryLearning.outcome.${outcome}`) : outcome;
  return (
    <span className={`inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold ${cls}`}>
      {label}
    </span>
  );
}

function formatTs(ts: string): string {
  try {
    return new Date(ts).toLocaleString(undefined, {
      year: 'numeric', month: 'short', day: 'numeric',
      hour: '2-digit', minute: '2-digit',
    });
  } catch {
    return ts;
  }
}

// ── SnapshotCard ──────────────────────────────────────────────────────────────

function SnapshotCard({ item, onReverted }: { item: SnapshotIndexEntry; onReverted: () => void }) {
  const { t } = useTranslation('settings');
  const [expanded, setExpanded] = useState(false);
  const [confirmRevert, setConfirmRevert] = useState(false);
  const [revertDone, setRevertDone] = useState<boolean | null>(null);

  const detailQuery = useQuery<SnapshotDetail>({
    queryKey: ['snapshot-detail', item.snapshotId],
    queryFn: () => fetchSnapshotDetail(item.snapshotId),
    enabled: expanded,
    staleTime: 5 * 60 * 1000,
  });

  const revertMutation = useMutation({
    mutationFn: () => revertSnapshot(item.snapshotId),
    onSuccess: (result) => {
      setRevertDone(result.reverted);
      setConfirmRevert(false);
      if (result.reverted) onReverted();
    },
  });

  return (
    <div className={`border border-hairline rounded-md overflow-hidden ${item.reverted ? 'opacity-60' : ''}`}>
      {/* Header row — always visible */}
      <button
        type="button"
        onClick={() => setExpanded(p => !p)}
        className="w-full flex items-center gap-3 px-3 py-2.5 text-left hover:bg-surface/60 transition-colors"
      >
        <span className="text-2xs text-slate-400 flex-shrink-0 w-32 truncate" title={item.ts}>
          {formatTs(item.ts)}
        </span>
        <span className="text-2xs font-mono text-slate-700 truncate flex-1" title={item.pieceName}>
          {item.pieceName}
        </span>
        <span className="flex-shrink-0 flex items-center gap-1.5">
          {item.memoryChanges > 0 && (
            <span className="text-[10px] px-1.5 py-0.5 bg-blue-50 dark:bg-blue-500/15 text-blue-700 dark:text-blue-300 rounded">
              {item.memoryChanges} mem
            </span>
          )}
          {item.pieceEdited && (
            <span className="text-[10px] px-1.5 py-0.5 bg-purple-50 dark:bg-purple-500/15 text-purple-700 dark:text-purple-300 rounded">
              piece
            </span>
          )}
          {item.reverted && (
            <span className="text-[10px] px-1.5 py-0.5 bg-slate-100 text-slate-500 rounded">
              {t('memoryLearning.snapshot.reverted')}
            </span>
          )}
          {detailQuery.data && <OutcomeBadge outcome={detailQuery.data.outcome} />}
        </span>
        <span className="text-slate-400 text-xs flex-shrink-0">{expanded ? '▲' : '▼'}</span>
      </button>

      {/* Expanded detail */}
      {expanded && (
        <div className="border-t border-hairline bg-slate-50 px-3 py-3 space-y-3">
          {detailQuery.isLoading && (
            <div className="text-xs text-slate-400">{t('memoryLearning.snapshot.loadingDetail')}</div>
          )}
          {detailQuery.error && (
            <div className="text-xs text-red-600">
              {t('memoryLearning.snapshot.detailLoadError', { err: String(detailQuery.error) })}
            </div>
          )}
          {detailQuery.data && (() => {
            const d = detailQuery.data;
            return (
              <>
                <div className="flex items-center gap-3 flex-wrap">
                  <OutcomeBadge outcome={d.outcome} />
                  {d.modelUsed && (
                    <span className="text-[10px] text-slate-400">{d.modelUsed}</span>
                  )}
                  {(d.tokensIn || d.tokensOut) && (
                    <span className="text-[10px] text-slate-400">
                      {(d.tokensIn ?? 0).toLocaleString()} in / {(d.tokensOut ?? 0).toLocaleString()} out tokens
                    </span>
                  )}
                </div>

                {d.reasoning && (
                  <div>
                    <div className="text-[10px] font-medium text-slate-500 uppercase tracking-wide mb-1">
                      {t('memoryLearning.snapshot.reasoning')}
                    </div>
                    <p className="text-xs text-slate-700 whitespace-pre-wrap">{d.reasoning}</p>
                  </div>
                )}

                {d.rejections && d.rejections.length > 0 && (
                  <div>
                    <div className="text-[10px] font-medium text-slate-500 uppercase tracking-wide mb-1">
                      {t('memoryLearning.snapshot.rejectionsTitle')}
                    </div>
                    <ul className="space-y-0.5">
                      {d.rejections.map((r, i) => (
                        <li key={i} className="text-2xs text-red-700 dark:text-red-300">
                          {/* 既知コードは翻訳ラベル、未知コードは生 ID にフォールバック (#469) */}
                          <span>{t(`memoryLearning.rejection.${r.code}`, { defaultValue: r.code })}</span>
                          {r.name && <span className="text-slate-500 ml-1">({r.name})</span>}
                        </li>
                      ))}
                    </ul>
                  </div>
                )}

                {d.diff && (
                  <div>
                    <div className="text-[10px] font-medium text-slate-500 uppercase tracking-wide mb-1">
                      {t('memoryLearning.snapshot.changes')}
                    </div>
                    <pre className="text-2xs text-slate-700 bg-canvas border border-hairline rounded px-2 py-1.5 overflow-x-auto whitespace-pre-wrap">
                      {d.diff}
                    </pre>
                  </div>
                )}

                {/* Before / After file diff */}
                {(Object.keys(d.beforeFiles).length > 0 || Object.keys(d.afterFiles).length > 0) && (
                  <BeforeAfterDiff beforeFiles={d.beforeFiles} afterFiles={d.afterFiles} />
                )}

                {/* Piece diff */}
                {d.pieceEdited && d.pieceBeforeYaml && d.pieceAfterYaml && (
                  <div>
                    <div className="text-[10px] font-medium text-slate-500 uppercase tracking-wide mb-1">
                      {t('memoryLearning.snapshot.pieceDiff')}
                    </div>
                    <div className="grid grid-cols-2 gap-2">
                      <div>
                        <div className="text-[10px] text-slate-400 mb-0.5">{t('memoryLearning.before')}</div>
                        <pre className="text-[10px] bg-canvas border border-hairline rounded px-2 py-1 overflow-auto max-h-40 whitespace-pre-wrap">
                          {d.pieceBeforeYaml}
                        </pre>
                      </div>
                      <div>
                        <div className="text-[10px] text-slate-400 mb-0.5">{t('memoryLearning.after')}</div>
                        <pre className="text-[10px] bg-canvas border border-hairline rounded px-2 py-1 overflow-auto max-h-40 whitespace-pre-wrap">
                          {d.pieceAfterYaml}
                        </pre>
                      </div>
                    </div>
                  </div>
                )}

                {/* Revert controls */}
                {!item.reverted && (
                  <div className="pt-1">
                    {revertDone === true && (
                      <span className="text-xs text-emerald-700 dark:text-emerald-300">{t('memoryLearning.snapshot.revertedOk')}</span>
                    )}
                    {revertDone === false && (
                      <span className="text-xs text-slate-500">{t('memoryLearning.snapshot.alreadyReverted')}</span>
                    )}
                    {revertDone === null && !confirmRevert && (
                      <button
                        type="button"
                        onClick={() => setConfirmRevert(true)}
                        className="px-2.5 h-7 text-2xs text-amber-800 dark:text-amber-300 border border-amber-300 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-500/15 hover:bg-amber-100 dark:hover:bg-amber-500/15 rounded transition-colors"
                      >
                        {t('memoryLearning.snapshot.revertBtn')}
                      </button>
                    )}
                    {revertDone === null && confirmRevert && (
                      <div className="flex items-center gap-2">
                        <span className="text-xs text-amber-800 dark:text-amber-300">
                          {t('memoryLearning.snapshot.revertConfirmQ')}
                        </span>
                        <button
                          type="button"
                          onClick={() => revertMutation.mutate()}
                          disabled={revertMutation.isPending}
                          className="px-2.5 h-7 text-2xs font-semibold bg-red-600 text-white hover:bg-red-700 rounded disabled:opacity-50 transition-colors"
                        >
                          {revertMutation.isPending ? t('memoryLearning.snapshot.reverting') : t('memoryLearning.snapshot.revertConfirm')}
                        </button>
                        <button
                          type="button"
                          onClick={() => setConfirmRevert(false)}
                          className="px-2.5 h-7 text-2xs text-slate-600 border border-hairline bg-canvas hover:bg-surface rounded transition-colors"
                        >
                          {t('memoryLearning.cancel')}
                        </button>
                      </div>
                    )}
                    {revertMutation.isError && (
                      <div className="text-2xs text-red-600 mt-1">
                        {String(revertMutation.error)}
                      </div>
                    )}
                  </div>
                )}
              </>
            );
          })()}
        </div>
      )}
    </div>
  );
}

// ── BeforeAfterDiff ───────────────────────────────────────────────────────────

function BeforeAfterDiff({
  beforeFiles,
  afterFiles,
}: {
  beforeFiles: Record<string, string>;
  afterFiles: Record<string, string>;
}) {
  const { t } = useTranslation('settings');
  const allNames = Array.from(
    new Set([...Object.keys(beforeFiles), ...Object.keys(afterFiles)]),
  ).sort();

  if (allNames.length === 0) return null;

  const [selected, setSelected] = useState(allNames[0]);

  const before = beforeFiles[selected];
  const after = afterFiles[selected];
  const isAdded = !before && !!after;
  const isRemoved = !!before && !after;

  return (
    <div>
      <div className="text-[10px] font-medium text-slate-500 uppercase tracking-wide mb-1">
        {t('memoryLearning.diff.title')}
      </div>
      {allNames.length > 1 && (
        <div className="flex gap-1 mb-2 flex-wrap">
          {allNames.map(n => (
            <button
              key={n}
              type="button"
              onClick={() => setSelected(n)}
              className={`px-1.5 py-0.5 text-[10px] rounded border ${
                selected === n
                  ? 'border-accent bg-accent-soft text-accent font-semibold'
                  : 'border-hairline text-slate-500 hover:bg-surface'
              }`}
            >
              {n}
            </button>
          ))}
        </div>
      )}
      {isAdded && (
        <div className="text-2xs text-emerald-700 dark:text-emerald-300 bg-emerald-50 dark:bg-emerald-500/15 border border-emerald-200 dark:border-emerald-500/30 rounded px-2 py-1 mb-1">
          {t('memoryLearning.diff.added')}
        </div>
      )}
      {isRemoved && (
        <div className="text-2xs text-red-700 dark:text-red-300 bg-red-50 dark:bg-red-500/15 border border-red-200 dark:border-red-500/30 rounded px-2 py-1 mb-1">
          {t('memoryLearning.diff.removed')}
        </div>
      )}
      <div className="grid grid-cols-2 gap-2">
        {!isAdded && (
          <div>
            <div className="text-[10px] text-slate-400 mb-0.5">{t('memoryLearning.before')}</div>
            <pre className="text-[10px] bg-canvas border border-hairline rounded px-2 py-1 overflow-auto max-h-40 whitespace-pre-wrap">
              {before ?? t('memoryLearning.empty')}
            </pre>
          </div>
        )}
        {!isRemoved && (
          <div className={isAdded ? 'col-span-2' : ''}>
            <div className="text-[10px] text-slate-400 mb-0.5">{t('memoryLearning.after')}</div>
            <pre className="text-[10px] bg-canvas border border-hairline rounded px-2 py-1 overflow-auto max-h-40 whitespace-pre-wrap">
              {after ?? t('memoryLearning.empty')}
            </pre>
          </div>
        )}
      </div>
    </div>
  );
}

// ── MetricsSummary ────────────────────────────────────────────────────────────

function MetricsSummary() {
  const { t } = useTranslation('settings');
  const { data, isLoading, error } = useQuery<ReflectionMetrics>({
    queryKey: ['reflection-metrics', 30],
    queryFn: () => fetchMetrics(30),
    staleTime: 5 * 60 * 1000,
  });

  if (isLoading) {
    return <div className="text-xs text-slate-400 px-4 py-3">{t('memoryLearning.metrics.loading')}</div>;
  }

  if (error) {
    return (
      <div className="text-xs text-red-600 px-4 py-3">
        {t('memoryLearning.metrics.loadError', { err: String(error) })}
      </div>
    );
  }

  if (!data) return null;

  const totalRuns = data.applied + data.partial + data.abstained + data.rejected + data.failed;
  const appliedPct = totalRuns > 0 ? Math.round(((data.applied + data.partial) / totalRuns) * 100) : 0;
  const abstainPct = totalRuns > 0 ? Math.round((data.abstained / totalRuns) * 100) : 0;
  const totalTokens = data.tokensIn + data.tokensOut;

  return (
    <div className="px-4 py-3 bg-slate-50 border-t border-hairline rounded-b-lg">
      <div className="text-[10px] font-semibold text-slate-500 uppercase tracking-wide mb-2">
        {t('memoryLearning.metrics.summary30')}
      </div>
      <div className="grid grid-cols-2 sm:grid-cols-5 gap-2">
        {[
          { label: t('memoryLearning.metrics.totalRuns'), value: String(totalRuns) },
          { label: t('memoryLearning.metrics.appliedRate'), value: `${appliedPct}%` },
          { label: t('memoryLearning.metrics.abstainRate'), value: `${abstainPct}%` },
          { label: t('memoryLearning.metrics.tokens'), value: totalTokens > 1000 ? `${Math.round(totalTokens / 1000)}k` : String(totalTokens) },
          { label: t('memoryLearning.metrics.pieceEdits'), value: String(data.pieceEdits) },
        ].map(({ label, value }) => (
          <div
            key={label}
            className="bg-canvas border border-hairline rounded px-2 py-1.5 text-center"
          >
            <div className="text-2xs font-semibold text-slate-800">{value}</div>
            <div className="text-[10px] text-slate-400 mt-0.5">{label}</div>
          </div>
        ))}
      </div>
      {totalRuns === 0 && (
        <p className="text-2xs text-slate-400 mt-2">
          {t('memoryLearning.metrics.noRuns')}
        </p>
      )}
    </div>
  );
}

// ── ReflectionTimelinePanel ───────────────────────────────────────────────────

const OUTCOME_FILTER_OPTIONS = [
  { value: 'applied' },
  { value: 'partial' },
  { value: 'abstained' },
  { value: 'rejected' },
  { value: 'failed' },
];

function ReflectionTimelinePanel() {
  const { t } = useTranslation('settings');
  const qc = useQueryClient();

  // Filters (client-side — the backend doesn't support filtering natively)
  const [outcomeFilter, setOutcomeFilter] = useState<string[]>([]);
  const [includeReverted, setIncludeReverted] = useState(true);

  const {
    data,
    isLoading,
    error,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery<HistoryPage>({
    queryKey: ['reflection-history'],
    queryFn: ({ pageParam }) =>
      fetchHistoryPage(typeof pageParam === 'string' ? pageParam : undefined),
    initialPageParam: undefined as string | undefined,
    getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
  });

  const allItems: SnapshotIndexEntry[] = (data?.pages ?? []).flatMap(p => p.items);

  // Client-side filtering — outcome is on the detail, but index has `reverted`
  const filteredItems = allItems.filter(item => {
    if (!includeReverted && item.reverted) return false;
    // Outcome filtering is only possible after detail is loaded; skip if no filter set
    return true;
  });

  const handleReverted = () => {
    void qc.invalidateQueries({ queryKey: ['reflection-history'] });
    void qc.invalidateQueries({ queryKey: ['reflection-metrics'] });
  };

  return (
    <div className="rounded-lg border border-hairline">
      <div className="px-4 py-3 border-b border-hairline bg-surface rounded-t-lg">
        <h3 className="text-sm font-semibold text-slate-800">{t('memoryLearning.timeline.title')}</h3>
        <p className="text-2xs text-slate-500 mt-0.5">
          {t('memoryLearning.timeline.subtitle')}
        </p>

        {/* Filters */}
        <div className="flex flex-wrap items-center gap-3 mt-2">
          <label className="flex items-center gap-1.5 text-2xs text-slate-600 cursor-pointer">
            <input
              type="checkbox"
              checked={includeReverted}
              onChange={e => setIncludeReverted(e.target.checked)}
              className="rounded"
            />
            {t('memoryLearning.timeline.showReverted')}
          </label>

          <div className="flex items-center gap-1.5 flex-wrap">
            <span className="text-2xs text-slate-500">{t('memoryLearning.timeline.resultLabel')}</span>
            {OUTCOME_FILTER_OPTIONS.map(opt => (
              <label key={opt.value} className="flex items-center gap-1 text-2xs text-slate-600 cursor-pointer">
                <input
                  type="checkbox"
                  checked={outcomeFilter.length === 0 || outcomeFilter.includes(opt.value)}
                  onChange={e => {
                    if (e.target.checked) {
                      setOutcomeFilter(prev =>
                        prev.length === 0 ? [] : prev.filter(v => v !== opt.value).concat(opt.value),
                      );
                    } else {
                      setOutcomeFilter(prev => {
                        const next = prev.length === 0
                          ? OUTCOME_FILTER_OPTIONS.map(o => o.value).filter(v => v !== opt.value)
                          : prev.filter(v => v !== opt.value);
                        return next;
                      });
                    }
                  }}
                />
                {t(`memoryLearning.outcome.${opt.value}`)}
              </label>
            ))}
            {outcomeFilter.length > 0 && (
              <button
                type="button"
                onClick={() => setOutcomeFilter([])}
                className="text-[10px] text-accent underline"
              >
                {t('memoryLearning.timeline.reset')}
              </button>
            )}
          </div>
        </div>
      </div>

      <div className="p-3 space-y-2">
        {isLoading && (
          <div className="text-xs text-slate-400 text-center py-4">{t('memoryLearning.loading')}</div>
        )}

        {error && (
          <div className="text-xs text-red-600 px-2">
            {t('memoryLearning.timeline.loadError', { err: String(error) })}
          </div>
        )}

        {!isLoading && filteredItems.length === 0 && (
          <div className="text-center py-6">
            <p className="text-xs text-slate-400">{t('memoryLearning.timeline.emptyTitle')}</p>
            <p className="text-2xs text-slate-400 mt-1">
              {t('memoryLearning.timeline.emptyHint')}
            </p>
          </div>
        )}

        {filteredItems.map(item => (
          <SnapshotCard key={item.snapshotId} item={item} onReverted={handleReverted} />
        ))}

        {hasNextPage && (
          <div className="flex justify-center pt-1">
            <button
              type="button"
              onClick={() => void fetchNextPage()}
              disabled={isFetchingNextPage}
              className="px-3 h-8 text-xs text-slate-600 border border-hairline bg-canvas hover:bg-surface rounded-md disabled:opacity-50 transition-colors"
            >
              {isFetchingNextPage ? t('memoryLearning.loading') : t('memoryLearning.timeline.loadMore')}
            </button>
          </div>
        )}
      </div>

      <MetricsSummary />
    </div>
  );
}

// ── MemoryLearningForm (root export) ──────────────────────────────────────────

export function MemoryLearningForm() {
  const { t } = useTranslation('settings');
  return (
    <div className="space-y-6">
      <h2 className="text-base font-semibold text-slate-800">{t('memoryLearning.rootTitle')}</h2>
      <p className="text-xs text-slate-500 -mt-4">
        {t('memoryLearning.rootIntro')}
      </p>

      <ReflectionTimelinePanel />
    </div>
  );
}
