/**
 * Font upload service (scope doc §4 → User font upload) — orchestration over the
 * pure core helpers, the FontStore, and the browser seams.
 *
 * The deterministic work (format detection, family derivation, uniqueness) lives
 * in `@core/font-asset` + `@core/naming`; this layer reads the file bytes through
 * the infrastructure adapter, validates, commits to the store (the persistence
 * subscriber writes through to IndexedDB), registers the live `FontFace`, and
 * reports one summary toast. Deleting a font unregisters its face and removes the
 * record (the subscriber deletes it from IndexedDB).
 */

import {
  createFontAsset,
  detectFontFormat,
  familyNameFromFileName,
  MAX_FONT_BYTES,
  parseFontAxes,
} from '@core/font-asset';
import { makeUniqueName } from '@core/naming';
import { humanizeBytes } from '@core/storage-estimate';
import { readBinaryFile } from '../infrastructure/file-transfer';
import { registerFontAsset, unregisterFont } from '../infrastructure/font-faces';
import { notify } from '../stores/NotificationStore';
import { useFontStore } from '../stores/FontStore';

/** Why a picked file was skipped, for the summary toast. */
interface Skip {
  fileName: string;
  reason: string;
}

/**
 * Add one or more picked font files to the library. Each file is validated
 * independently (unsupported format / over the size cap are skipped, not fatal),
 * given a unique family name, stored, and registered. Reports how many were added
 * and why any were skipped — never throws on a bad file.
 */
export async function uploadFontFiles(files: FileList | File[]): Promise<void> {
  const list = Array.from(files);
  if (list.length === 0) return;

  const added: string[] = [];
  const skipped: Skip[] = [];

  for (const file of list) {
    const format = detectFontFormat(file.name);
    if (!format) {
      skipped.push({ fileName: file.name, reason: 'unsupported format' });
      continue;
    }

    let data: ArrayBuffer;
    try {
      data = await readBinaryFile(file);
    } catch {
      skipped.push({ fileName: file.name, reason: 'could not be read' });
      continue;
    }

    if (data.byteLength > MAX_FONT_BYTES) {
      skipped.push({
        fileName: file.name,
        reason: `larger than ${humanizeBytes(MAX_FONT_BYTES)}`,
      });
      continue;
    }

    // Unique within the library (and within this batch — the store grew on the
    // previous iteration, so re-read its families each time).
    const family = makeUniqueName(
      familyNameFromFileName(file.name),
      useFontStore.getState().fonts.map((f) => f.family),
    );
    // Detect variation axes (variable fonts) so the face registers with the
    // right weight/width ranges — uncompressed ttf/otf only; others stay static.
    const axes = parseFontAxes(data, format);
    const stored = useFontStore
      .getState()
      .add(createFontAsset({ family, data, format, fileName: file.name, axes }));
    registerFontAsset(stored);
    added.push(family);
  }

  reportUpload(added, skipped);
}

/** Remove a font: unregister its live face, then drop the record. */
export function removeFont(id: number): void {
  unregisterFont(id);
  useFontStore.getState().remove(id);
}

/** One summary toast covering what was added and what was skipped. */
function reportUpload(added: string[], skipped: Skip[]): void {
  const skips = skipped.length
    ? `Skipped ${skipped.map((s) => `${s.fileName} (${s.reason})`).join(', ')}.`
    : '';

  if (added.length === 0) {
    notify({
      kind: skipped.length ? 'error' : 'info',
      title: skipped.length ? "Couldn't add the font" : 'No fonts added',
      message:
        skips || 'Choose a .woff2, .woff, .ttf, or .otf file to add a font to your chart themes.',
    });
    return;
  }

  const noun = added.length === 1 ? 'font' : 'fonts';
  const message = `Added ${added.join(', ')}. ${skips}`.trim();
  notify({
    kind: skipped.length ? 'warning' : 'success',
    title: `Added ${added.length} ${noun}`,
    message,
  });
}
