import i18n from '../i18n';
import { COLUMN_LABELS } from './urlState';

export function relativeTime(dateStr: string): string {
  const diff = Date.now() - new Date(dateStr).getTime();
  const mins = Math.floor(diff / 60000);
  if (mins < 1) return i18n.t('time.justNow', { ns: 'common' });
  if (mins < 60) return i18n.t('time.minutesAgo', { ns: 'common', count: mins });
  const hrs = Math.floor(mins / 60);
  if (hrs < 24) return i18n.t('time.hoursAgo', { ns: 'common', count: hrs });
  return i18n.t('time.daysAgo', { ns: 'common', count: Math.floor(hrs / 24) });
}

export function workerPill(workerId: string | null): string {
  if (!workerId) return '-';
  if (workerId.includes('148')) return `${workerId} (quality)`;
  if (workerId.includes('074')) return `${workerId} (fast)`;
  return workerId;
}

/**
 * Status badge colors. Returned values are CSS `light-dark()` pairs so the
 * inline-styled badges (style={{ background, color }}) adapt to the theme —
 * `color-scheme` is set on :root / [data-theme=dark] (see index.css), which is
 * what light-dark() resolves against. Dark = a subtle tinted chip + light text.
 */
export function statusTone(status: string): { bg: string; fg: string } {
  const ld = (l: string, d: string) => `light-dark(${l}, ${d})`;
  if (status === 'running') return { bg: ld('#dcfce7', 'rgba(34,197,94,.15)'), fg: ld('#166534', '#86efac') };
  if (status === 'waiting_human') return { bg: ld('#fef9c3', 'rgba(234,179,8,.15)'), fg: ld('#854d0e', '#fde047') };
  if (status === 'waiting_subtasks') return { bg: ld('#e0e7ff', 'rgba(99,102,241,.18)'), fg: ld('#3730a3', '#a5b4fc') };
  if (status === 'failed') return { bg: ld('#fee2e2', 'rgba(239,68,68,.15)'), fg: ld('#b91c1c', '#fca5a5') };
  if (status === 'succeeded') return { bg: ld('#dbeafe', 'rgba(59,130,246,.18)'), fg: ld('#1e40af', '#93c5fd') };
  if (status === 'retry') return { bg: ld('#fef3c7', 'rgba(245,158,11,.15)'), fg: ld('#92400e', '#fcd34d') };
  return { bg: ld('#e2e8f0', 'var(--surface-2)'), fg: ld('#475569', 'var(--muted)') };
}

export type Tone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';

/**
 * Shared light+dark Tailwind classes for semantic badge/chip tones. One source
 * of truth so badges stay consistent in both themes. `dark:` follows the
 * in-app [data-theme] (tailwind darkMode is the selector variant).
 */
const TONE_CLASSES: Record<Tone, string> = {
  neutral: 'bg-surface-2 text-slate-700 border-hairline',
  info: 'bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-500/15 dark:text-blue-300 dark:border-blue-500/30',
  success: 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-500/15 dark:text-emerald-300 dark:border-emerald-500/30',
  warning: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-500/15 dark:text-amber-300 dark:border-amber-500/30',
  danger: 'bg-red-50 text-red-700 border-red-200 dark:bg-red-500/15 dark:text-red-300 dark:border-red-500/30',
};

export function toneClasses(tone: Tone): string {
  return TONE_CLASSES[tone];
}

export function formatStatusLabel(status: string): string {
  return COLUMN_LABELS[status] ?? status;
}

export function matchText(value: string | undefined | null, query: string): boolean {
  if (!query.trim()) return true;
  return (value ?? '').toLowerCase().includes(query.trim().toLowerCase());
}

export function isTextPreviewable(name: string): boolean {
  return /\.(md|markdown|csv|txt|log|json|jsonl)$/i.test(name);
}

export function isImagePreviewable(name: string): boolean {
  return /\.(png|jpe?g|gif|webp|bmp)$/i.test(name);
}

export function isPdfPreviewable(name: string): boolean {
  return /\.pdf$/i.test(name);
}

export function isHtmlPreviewable(name: string): boolean {
  return /\.html?$/i.test(name);
}

// Office プレビュー対象。サーバ側 office-preview.ts の isSpreadsheetFile /
// isPresentationFile と拡張子集合を一致させること。
// Excel は exceljs が読める xlsx/xlsm のみ（csv はテキストプレビュー経路、xls は対象外）。
export function isSpreadsheetPreviewable(name: string): boolean {
  return /\.(xlsx|xlsm)$/i.test(name);
}

// PowerPoint は LibreOffice で PDF 化できる pptx/ppt。
export function isPresentationPreviewable(name: string): boolean {
  return /\.(pptx|ppt)$/i.test(name);
}

// Word は LibreOffice で PDF 化できる docx のみ（旧バイナリ .doc は再現精度が低く対象外）。
export function isDocumentPreviewable(name: string): boolean {
  return /\.docx$/i.test(name);
}

export function isOfficePreviewable(name: string): boolean {
  return isSpreadsheetPreviewable(name) || isPresentationPreviewable(name) || isDocumentPreviewable(name);
}

// office-preview の表示種別を拡張子から判定する。呼出側（useFilePreview / SpaceDetail /
// SpaceCalendar）はこのヘルパーで descriptor.kind を決める。isOfficePreviewable が true の
// ファイルにのみ使うこと（それ以外では 'document' に落ちる）。
export function officePreviewKind(name: string): 'spreadsheet' | 'presentation' | 'document' {
  if (isSpreadsheetPreviewable(name)) return 'spreadsheet';
  if (isPresentationPreviewable(name)) return 'presentation';
  return 'document';
}

export function isPreviewable(name: string): boolean {
  return isTextPreviewable(name) || isImagePreviewable(name) || isPdfPreviewable(name) || isHtmlPreviewable(name);
}

export type ActivityEventKind = 'movement_start' | 'movement_complete' | 'tool' | 'preview' | 'final' | 'ask' | 'preflight' | 'other';

export interface ActivityEvent {
  id: string;
  kind: ActivityEventKind;
  label: string;
  note: string;
  state: 'running' | 'done' | 'idle';
  timestamp: string | null;
  workerId: string | null;
  mode: string | null;
  /** Physical backend behind a proxy worker (from [backend:...] tags). */
  backendId: string | null;
}

export function formatActivityMeta(workerId: string | null, mode: string | null, backendId?: string | null): string {
  // For proxy workers (AAO gateway) the interesting name is the PHYSICAL
  // backend behind the gateway — show it as the worker, with the proxy
  // relegated to a "via" suffix. Lines without a backend tag (direct
  // workers, or logs written before the backend tag existed) keep the
  // plain worker name.
  const hasBackend = !!backendId && backendId !== workerId;
  const workerLabel = hasBackend
    ? `worker: ${workerPill(backendId!)}${workerId ? ` (via ${workerId})` : ''}`
    : workerId ? `worker: ${workerPill(workerId)}` : '';
  return [workerLabel, mode ? `mode: ${mode}` : ''].filter(Boolean).join(' · ');
}

export function parseActivityLog(logText: string): ActivityEvent[] {
  const lines = logText.split('\n').map(line => line.trim()).filter(Boolean);
  const events: ActivityEvent[] = [];

  for (const [index, rawLine] of lines.entries()) {
    const timestampMatch = /^\[([^\]]+)\]\s+/.exec(rawLine);
    const timestamp = timestampMatch?.[1] ?? null;
    const workerId = /\[worker:([^\]]+)\]/.exec(rawLine)?.[1] ?? null;
    const mode = /\[mode:([^\]]+)\]/.exec(rawLine)?.[1] ?? null;
    const backendId = /\[backend:([^\]]+)\]/.exec(rawLine)?.[1] ?? null;
    const line = rawLine
      .replace(/^\[[^\]]+\]\s+/, '')
      .replace(/\[worker:[^\]]+\]\s*/g, '')
      .replace(/\[mode:[^\]]+\]\s*/g, '')
      .replace(/\[backend:[^\]]+\]\s*/g, '')
      .trim();

    const base = { id: `${timestamp ?? 'line'}-${index}`, timestamp, workerId, mode, backendId };

    const movementStart = /^\[([^\]]+)\] (?:start|ステップ開始)$/.exec(line);
    if (movementStart) {
      events.push({ ...base, kind: 'movement_start', label: movementStart[1]!, note: 'started', state: 'running' });
      continue;
    }

    const movementComplete = /^\[([^\]]+)\] (?:complete ->|完了 →) (.+)$/.exec(line);
    if (movementComplete) {
      events.push({ ...base, kind: 'movement_complete', label: movementComplete[1]!, note: `next: ${movementComplete[2]}`, state: 'done' });
      continue;
    }

    const movementPreview = /^\[([^\]]+)\] preview: (.+)$/.exec(line);
    if (movementPreview) {
      events.push({ ...base, kind: 'preview', label: movementPreview[1]!, note: movementPreview[2]!, state: 'running' });
      continue;
    }

    const preview = /^preview:\s*(.+)$/i.exec(line);
    if (preview) {
      events.push({ ...base, kind: 'preview', label: 'assistant', note: preview[1]!, state: 'running' });
      continue;
    }

    const final = /^(?:final:|最終結果:)\s*([a-z_]+)/i.exec(line);
    if (final) {
      const normalized = final[1]!.toLowerCase();
      events.push({ ...base, kind: 'final', label: 'final', note: normalized, state: normalized === 'completed' ? 'done' : 'running' });
      continue;
    }

    const ask = /^(?:ask:|\[ASK\])\s*(.+)$/i.exec(line);
    if (ask) {
      events.push({ ...base, kind: 'ask', label: 'ask', note: ask[1]!, state: 'idle' });
      continue;
    }

    // tool 正規表現より前に preflight をマッチさせる。
    // ok 行は短縮版 ("preflight: 12,400/128,000 tokens") として書かれる。
    // blocked 行はフル ("[llm-preflight:blocked] ...") のまま出る。
    const preflightShort = /^preflight:\s*(.+)$/.exec(line);
    if (preflightShort) {
      events.push({ ...base, kind: 'preflight', label: 'preflight', note: preflightShort[1]!, state: 'idle' });
      continue;
    }
    const preflightBlocked = /^\[llm-preflight:blocked\]\s*(.+)$/.exec(line);
    if (preflightBlocked) {
      events.push({ ...base, kind: 'preflight', label: 'preflight (blocked)', note: preflightBlocked[1]!, state: 'idle' });
      continue;
    }

    const tool = /^([A-Za-z][A-Za-z0-9_]+):\s*(.+)$/.exec(line);
    if (tool) {
      events.push({ ...base, kind: 'tool', label: tool[1]!, note: tool[2]!, state: 'idle' });
      continue;
    }

    events.push({ ...base, kind: 'other', label: 'log', note: line, state: 'idle' });
  }

  return events;
}

export function formatActivityTimestamp(timestamp: string | null): string {
  if (!timestamp) return '';
  const date = new Date(timestamp);
  if (Number.isNaN(date.getTime())) return '';
  return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}

export function activityKindLabel(kind: ActivityEventKind): string {
  switch (kind) {
    case 'movement_start':
    case 'movement_complete': return 'STEP';
    case 'tool': return 'TOOL';
    case 'preview': return 'PREVIEW';
    case 'final': return 'FINAL';
    case 'ask': return 'ASK';
    case 'preflight': return 'LLM';
    default: return 'LOG';
  }
}

export function activityEventTitle(event: ActivityEvent): string {
  switch (event.kind) {
    case 'preview': return `${event.label} response`;
    case 'final': return 'Run finished';
    case 'ask': return 'Need user input';
    default: return event.label;
  }
}