// #region Translate.LLMReportModel [C:3] [TYPE Model] [SEMANTICS llm,report,validation,screenshot,model]
// @ingroup Translate
// @BRIEF Screen model for the LLM validation report page — loads task details, logs, and thumbnail screenshots.
// @INVARIANT taskId is derived from route params; loadReport() must only fire when taskId is truthy.
// @INVARIANT screenshot loading is token-gated to prevent stale blob URLs from overwriting valid ones.
// @STATE idle     — No taskId available, nothing loaded.
// @STATE loading  — API calls in progress, skeleton visible.
// @STATE loaded   — Task data and logs fetched, report rendered.
// @STATE error    — API call failed, error message displayed.
// @ACTION loadReport()           — Fetches task + logs from API.
// @ACTION loadScreenshotBlobs()  — Fetches blob URLs for screenshot paths.
// @ACTION openScreenshot(path)   — Opens screenshot blob URL in new tab.
// @ACTION cleanupBlobs()         — Revokes all screenshot blob URLs.
// @ACTION refresh()              — Re-runs loadReport().
// @RELATION DEPENDS_ON -> [ApiModule]
// @RELATION CALLS -> [CotLogger]

import { api } from '$lib/api.js';
import { log } from '$lib/cot-logger';
import type { TaskLogEntry } from '$lib/api.js';
import { SvelteDate } from "svelte/reactivity";

// ── Types ───────────────────────────────────────────────────────

type ScreenState = 'idle' | 'loading' | 'loaded' | 'error';

interface TimingInfo {
  validation_duration_ms?: number;
  [key: string]: unknown;
}

interface CheckIssue {
  severity?: string;
  message?: string;
  location?: string;
}

interface TaskResult {
  status?: string;
  summary?: string;
  issues?: CheckIssue[];
  screenshot_paths?: string[];
  screenshot_path?: string;
  logs_sent_to_llm?: string[];
  timings?: TimingInfo;
}

interface TaskData {
  id: string;
  status?: string;
  started_at?: string;
  finished_at?: string;
  result?: TaskResult;
}

interface CheckResult {
  label: string;
  level: 'fail' | 'warn' | 'pass' | 'unknown';
  icon: string;
}

export class LLMReportModel {
  // ── Atoms ─────────────────────────────────────────────────────
  task: TaskData | null = $state(null);
  logs: TaskLogEntry[] = $state([]);
  screenState: ScreenState = $state('idle');
  error: string | null = $state(null);
  screenshotBlobUrls: Record<string, string> = $state({});
  screenshotLoadErrors: Record<string, string> = $state({});
  private _screenshotLoadToken: number = $state(0);

  // Externally set
  taskId: string = $state('');

  // ── Derived ───────────────────────────────────────────────────
  result = $derived<TaskResult>(this.task?.result || {});
  checkResult = $derived<CheckResult>(this._getDashboardCheckResult(this.result));
  timings = $derived<TimingInfo>(this.result?.timings || {});
  screenshotPaths = $derived<string[]>(
    Array.isArray(this.result?.screenshot_paths) && this.result.screenshot_paths.length > 0
      ? this.result.screenshot_paths
      : this.result?.screenshot_path
        ? [this.result.screenshot_path]
        : [],
  );
  sentLogs = $derived<string[]>(
    Array.isArray(this.result?.logs_sent_to_llm) ? this.result.logs_sent_to_llm : [],
  );

  // ── Actions ───────────────────────────────────────────────────

  async loadReport(): Promise<void> {
    if (!this.taskId) return;

    log('LLMReportModel', 'REASON', 'Loading LLM report', { taskId: this.taskId });

    this.screenState = 'loading';
    this.error = null;
    try {
      const [taskPayload, logsPayload] = await Promise.all([
        api.getTask(this.taskId),
        api.getTaskLogs(this.taskId, { limit: 1000 }),
      ]);
      this.task = taskPayload as TaskData;
      this.logs = Array.isArray(logsPayload) ? logsPayload : [];
      this.screenState = 'loaded';
      log('LLMReportModel', 'REFLECT', 'LLM report loaded', {
        taskId: this.taskId,
        status: this.task?.status,
        logCount: this.logs.length,
      });
    } catch (err: unknown) {
      this.error = err instanceof Error ? err.message : 'Failed to load LLM report';
      this.screenState = 'error';
      log('LLMReportModel', 'EXPLORE', 'Failed to load LLM report', { taskId: this.taskId }, String(this.error));
    }
  }

  async loadScreenshotBlobs(paths: string[]): Promise<void> {
    const currentToken = ++this._screenshotLoadToken;
    this._cleanupBlobUrls();
    this.screenshotLoadErrors = {};

    if (!Array.isArray(paths) || paths.length === 0) return;

    const nextUrls: Record<string, string> = {};
    const nextErrors: Record<string, string> = {};

    await Promise.all(
      paths.map(async (path) => {
        try {
          const blob = await api.getStorageFileBlob(path);
          nextUrls[path] = URL.createObjectURL(blob);
        } catch (err: unknown) {
          nextErrors[path] = err instanceof Error ? err.message : 'Failed to load screenshot';
        }
      }),
    );

    if (currentToken !== this._screenshotLoadToken) {
      for (const url of Object.values(nextUrls)) URL.revokeObjectURL(url);
      return;
    }

    this.screenshotBlobUrls = nextUrls;
    this.screenshotLoadErrors = nextErrors;
  }

  openScreenshot(path: string): void {
    const url = this.screenshotBlobUrls[path];
    if (!url) return;
    window.open(url, '_blank', 'noopener,noreferrer');
  }

  cleanupBlobs(): void {
    this._cleanupBlobUrls();
  }

  refresh(): void {
    this.loadReport();
  }

  // ── Private ───────────────────────────────────────────────────

  private _cleanupBlobUrls(): void {
    for (const url of Object.values(this.screenshotBlobUrls)) {
      if (url) URL.revokeObjectURL(url);
    }
    this.screenshotBlobUrls = {};
  }

  private _getDashboardCheckResult(resultPayload: TaskResult): CheckResult {
    const rawStatus = String(resultPayload?.status || '').toUpperCase();
    if (rawStatus === 'FAIL') return { label: 'FAIL', level: 'fail', icon: '!' };
    if (rawStatus === 'WARN') return { label: 'WARN', level: 'warn', icon: '!' };
    if (rawStatus === 'PASS') return { label: 'PASS', level: 'pass', icon: 'OK' };
    return { label: 'UNKNOWN', level: 'unknown', icon: '?' };
  }

  // ── Static utils (pure, no state access) ──────────────────────

  static formatDate(value: unknown): string {
    if (!value) return '-';
    const parsed = new SvelteDate(value as string);
    if (Number.isNaN(parsed.getTime())) return '-';
    return `${parsed.toLocaleDateString()} ${parsed.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}`;
  }

  static formatMs(value: unknown): string {
    const ms = Number(value);
    if (!Number.isFinite(ms) || ms < 0) return '-';
    if (ms < 1000) return `${ms} ms`;
    return `${(ms / 1000).toFixed(2)} s`;
  }

  static getCheckResultClasses(level: CheckResult['level']): string {
    if (level === 'fail') return 'bg-destructive-light text-destructive border-destructive-ring';
    if (level === 'warn') return 'bg-warning-light text-warning border-warning';
    if (level === 'pass') return 'bg-success-light text-success border-success';
    return 'bg-surface-muted text-text border-border';
  }
}
// #endregion Translate.LLMReportModel
