// Draft autosave/recovery for the Dictation page (PR 1.4).
//
// Covers two failure modes that previously lost user work:
//   1. App or window closes mid-capture before the user hits Stop.
//   2. User stops, walks away, app exits before the SQLite write
//      surfaces its outcome — or the SQLite write fails outright.
//
// The payload is intentionally tiny — transcript + segments + language
// — so the localStorage write stays cheap on every keystroke. The
// autosave call site debounces to 1.5s; this module just owns the
// shape and the parse/serialise round trip.

import type { Segment } from "$lib/types/app";
import { parseStoredJson } from "$lib/utils/storage.js";

export const DICTATION_DRAFT_KEY = "kon.dictation.draft.v1";

/** 24 hours in ms — drafts older than this are ignored on mount. */
export const DRAFT_RECOVERY_MAX_AGE_MS = 24 * 60 * 60 * 1000;

export interface DictationDraftPayload {
  /** ISO-8601 string. Compared to Date.now() to gate the recovery banner. */
  updatedAt: string;
  transcript: string;
  segments: Segment[];
  language?: string;
}

/**
 * Build a draft payload from current dictation state. Returns `null` if
 * there is nothing worth persisting (empty transcript and no segments).
 * The caller is expected to call `clearDraft()` instead when this returns
 * null, so we don't pollute localStorage with empty drafts.
 */
export function buildDraftPayload(
  transcript: string,
  segments: Segment[],
  language: string | undefined,
  now: Date = new Date(),
): DictationDraftPayload | null {
  const hasTranscript = transcript.trim().length > 0;
  const hasSegments = Array.isArray(segments) && segments.length > 0;
  if (!hasTranscript && !hasSegments) return null;

  return {
    updatedAt: now.toISOString(),
    transcript,
    segments: Array.isArray(segments) ? segments : [],
    ...(language ? { language } : {}),
  };
}

function canUseStorage(): boolean {
  return typeof localStorage !== "undefined";
}

export function saveDraft(payload: DictationDraftPayload): void {
  if (!canUseStorage()) return;
  try {
    localStorage.setItem(DICTATION_DRAFT_KEY, JSON.stringify(payload));
  } catch {
    // Quota exceeded or storage disabled — silently drop. The user still
    // has the in-memory transcript; we just lose the recover-on-relaunch
    // safety net for this run. No toast; the failure mode is rare and
    // surfacing it would confuse more than it helps.
  }
}

export function clearDraft(): void {
  if (!canUseStorage()) return;
  try {
    localStorage.removeItem(DICTATION_DRAFT_KEY);
  } catch {
    /* same rationale as saveDraft */
  }
}

/**
 * Read the persisted draft, returning it only if it exists, parses
 * cleanly, and is fresher than `maxAgeMs` (default 24 h). Anything older
 * is treated as stale and the caller can show no banner.
 *
 * We do NOT auto-prune the stale entry here — leaving it in place is
 * harmless (it's still <a few KB) and avoids a write on every mount.
 */
export function loadRecoverableDraft(
  now: Date = new Date(),
  maxAgeMs: number = DRAFT_RECOVERY_MAX_AGE_MS,
): DictationDraftPayload | null {
  if (!canUseStorage()) return null;
  const raw = localStorage.getItem(DICTATION_DRAFT_KEY);
  const parsed = parseStoredJson<DictationDraftPayload>(raw);
  if (!parsed || typeof parsed.updatedAt !== "string") return null;

  const updatedAtMs = Date.parse(parsed.updatedAt);
  if (!Number.isFinite(updatedAtMs)) return null;
  if (now.getTime() - updatedAtMs > maxAgeMs) return null;

  // Defensive: if the stored draft has neither transcript nor segments
  // (shouldn't happen because saveDraft refuses empty payloads, but a
  // hand-edited or truncated entry could trip this), treat as no draft.
  const hasTranscript = typeof parsed.transcript === "string" && parsed.transcript.trim().length > 0;
  const hasSegments = Array.isArray(parsed.segments) && parsed.segments.length > 0;
  if (!hasTranscript && !hasSegments) return null;

  return {
    updatedAt: parsed.updatedAt,
    transcript: typeof parsed.transcript === "string" ? parsed.transcript : "",
    segments: Array.isArray(parsed.segments) ? parsed.segments : [],
    ...(typeof parsed.language === "string" ? { language: parsed.language } : {}),
  };
}

/**
 * Coarse "x ago" formatter for the recovery banner. Intentionally short
 * — we only need enough resolution for the user to recognise their own
 * recent work ("3 minutes ago", "2 hours ago"). Returns "just now" for
 * <30s and clamps to "yesterday" for things ~24h old (the recovery cap).
 */
export function formatTimeAgo(iso: string, now: Date = new Date()): string {
  const then = Date.parse(iso);
  if (!Number.isFinite(then)) return "";
  const deltaMs = Math.max(0, now.getTime() - then);
  const seconds = Math.floor(deltaMs / 1000);
  if (seconds < 30) return "just now";
  if (seconds < 60) return `${seconds} seconds ago`;
  const minutes = Math.floor(seconds / 60);
  if (minutes < 60) return `${minutes} ${minutes === 1 ? "minute" : "minutes"} ago`;
  const hours = Math.floor(minutes / 60);
  if (hours < 24) return `${hours} ${hours === 1 ? "hour" : "hours"} ago`;
  return "yesterday";
}
