import { zip, unzip } from 'fflate';
import type { AsyncZippable, AsyncZipOptions } from 'fflate';
import type { SetupConfig, GateConfig } from '../protocol/types';

// ── Manifest schema ────────────────────────────────────────────────────────

export interface SessionManifest {
  id:            string;
  created_at:    number;
  frame_count:   number;
  device_config: SetupConfig;
}

export interface ProjectManifest {
  schema_version: '1.0';
  exported_at:    string;
  project: {
    id:         string;
    name:       string;
    created_at: number;
  };
  device_config: SetupConfig;
  gate_config:   GateConfig;
  sessions:      SessionManifest[];
}

// ── Helpers ────────────────────────────────────────────────────────────────

const ENC = new TextEncoder();
const DEC = new TextDecoder();

function promiseZip(files: AsyncZippable): Promise<Uint8Array> {
  return new Promise((resolve, reject) => {
    zip(files, (err, data) => (err ? reject(err) : resolve(data)));
  });
}

function promiseUnzip(data: Uint8Array): Promise<Record<string, Uint8Array>> {
  return new Promise((resolve, reject) => {
    unzip(data, (err, files) => (err ? reject(err) : resolve(files)));
  });
}

// ── Public API ─────────────────────────────────────────────────────────────

/**
 * Pack a .tem bundle (DEFLATE-compressed ZIP).
 * bins: relative path → raw bin bytes, e.g. "frames/20260607_042316/000001.bin"
 */
export async function packBundle(
  manifest: ProjectManifest,
  bins: Record<string, Uint8Array>,
): Promise<Uint8Array> {
  const opts: AsyncZipOptions = { level: 6 };
  const files: AsyncZippable = {
    'project.json': [ENC.encode(JSON.stringify(manifest, null, 2)), opts],
  };
  for (const [path, data] of Object.entries(bins)) {
    files[path] = [data, opts];
  }
  return promiseZip(files);
}

/**
 * Unpack a .tem bundle.
 * Returns the manifest and raw bin bytes keyed by relative path.
 */
export async function parseBundle(data: Uint8Array): Promise<{
  manifest: ProjectManifest;
  bins: Record<string, Uint8Array>;
}> {
  const files = await promiseUnzip(data);

  const manifestBytes = files['project.json'];
  if (!manifestBytes) throw new Error('.tem 包缺少 project.json');

  const manifest = JSON.parse(DEC.decode(manifestBytes)) as ProjectManifest;
  if (manifest.schema_version !== '1.0') {
    throw new Error(`不支持的版本: schema_version=${manifest.schema_version}`);
  }

  const bins: Record<string, Uint8Array> = {};
  for (const [path, bytes] of Object.entries(files)) {
    if (path !== 'project.json') bins[path] = bytes;
  }

  return { manifest, bins };
}

/** Extract session ID and frame ID from a bundle bin path. */
export function parseBinPath(relPath: string): { sessionId: string; frameId: number } | null {
  // Expected: "frames/{sessionId}/{frameId:06d}.bin"
  const m = relPath.match(/^frames\/([^/]+)\/(\d+)\.bin$/);
  if (!m) return null;
  return { sessionId: m[1], frameId: parseInt(m[2], 10) };
}

/** Build the bundle-relative path for a frame bin. */
export function bundleBinPath(sessionId: string, frameId: number): string {
  return `frames/${sessionId}/${String(frameId).padStart(6, '0')}.bin`;
}
