import {
  FileSystemUploadType,
  getInfoAsync,
  uploadAsync,
} from 'expo-file-system/legacy';
import { apiClient } from '@/api/client';
import {
  createParticle,
  createStreamParticle,
} from '@/lib/firestore-particles';
import {
  particlePath,
  toFirestoreChildrenPath,
  type ParticlePath,
} from '@/lib/particle-path';

interface UploadMediaParticleParams {
  networkId: string;
  /** Path of the destination container (stream — possibly with sub-segments). */
  targetPath: ParticlePath;
  fileUri: string;
  mimeType: string;
  durationMs: number;
  source: 'camera' | 'screen';
  createdByHumanId: string;
}

/**
 * Upload a recorded file and create the corresponding `media` particle in
 * Firestore. Order matches desktop's `use-recorder` flow exactly:
 *   prepareUpload → PUT → confirmUpload → createParticle.
 *
 * Returns the new particle's id, or throws on any failure (no half-states —
 * if any step fails the caller cancels and reports).
 */
export async function uploadMediaParticle({
  networkId,
  targetPath,
  fileUri,
  mimeType,
  durationMs,
  source,
  createdByHumanId,
}: UploadMediaParticleParams): Promise<string> {
  const info = await getInfoAsync(fileUri);
  if (!info.exists || info.size === undefined) {
    throw new Error('Recording file disappeared before upload.');
  }
  const sizeBytes = info.size;

  const namePrefix = mimeType.startsWith('audio/') ? 'voice' : 'video';
  const ext = extensionFromMime(mimeType);
  const name = `${namePrefix}-${Date.now()}${ext}`;

  const { object_id, upload_url, upload_headers } =
    await apiClient.prepareUpload({
      network_id: networkId,
      name,
      content_type: mimeType,
      content_length: sizeBytes,
    });

  const uploadResult = await uploadAsync(upload_url, fileUri, {
    httpMethod: 'PUT',
    uploadType: FileSystemUploadType.BINARY_CONTENT,
    headers: upload_headers,
  });

  if (uploadResult.status < 200 || uploadResult.status >= 300) {
    throw new Error(`Upload to depot failed (HTTP ${uploadResult.status}).`);
  }

  await apiClient.confirmUpload(object_id);

  const collectionPath = toFirestoreChildrenPath(targetPath);
  return createParticle(
    collectionPath,
    'media',
    {
      object_id,
      mime_type: mimeType,
      duration_ms: durationMs,
      size_bytes: sizeBytes,
      source,
    },
    createdByHumanId,
  );
}

interface CreateTextParticleParams {
  networkId: string;
  targetPath: ParticlePath;
  content: string;
  createdByHumanId: string;
}

export async function createTextParticle({
  targetPath,
  content,
  createdByHumanId,
}: CreateTextParticleParams): Promise<string> {
  const collectionPath = toFirestoreChildrenPath(targetPath);
  return createParticle(collectionPath, 'text', { content }, createdByHumanId);
}

interface CreateTaskParticleParams {
  targetPath: ParticlePath;
  title: string;
  notes?: string;
  createdByHumanId: string;
}

/**
 * Create a `task` particle. Mirrors createTextParticle — the checklist and
 * assignee are left empty and edited inline in the task card afterwards.
 */
export async function createTaskParticle({
  targetPath,
  title,
  notes,
  createdByHumanId,
}: CreateTaskParticleParams): Promise<string> {
  const collectionPath = toFirestoreChildrenPath(targetPath);
  return createParticle(
    collectionPath,
    'task',
    { title, done: false, ...(notes ? { notes } : {}) },
    createdByHumanId,
  );
}

function extensionFromMime(mime: string): string {
  if (mime === 'video/mp4') return '.mp4';
  if (mime === 'video/quicktime') return '.mov';
  if (mime === 'audio/mp4') return '.m4a';
  if (mime === 'audio/webm') return '.webm';
  return '';
}

// Helper kept here so callers can construct a fresh stream's child-path before
// the stream particle has been written.
export function streamChildrenPath(
  networkId: string,
  streamId: string,
): ParticlePath {
  return particlePath(networkId, [streamId]);
}

// --- New-stream flow ---

interface CreateStreamWithFirstParticleParams {
  networkId: string;
  name: string;
  /** ["network:{id}"] for everyone; ["human:{id}", ...] for specific people. */
  visibleTo: string[];
  createdByHumanId: string;
  /** First particle to write into the new stream. Required — empty streams are not useful. */
  firstParticle:
    | { type: 'text'; content: string }
    | {
        type: 'media';
        fileUri: string;
        mimeType: string;
        durationMs: number;
        source: 'camera' | 'screen';
      };
}

interface CreateStreamWithFirstParticleResult {
  streamId: string;
}

/**
 * Create a top-level stream particle plus its first child particle, in that
 * order. Mirrors desktop's "create new stream" submit path (compose-overlay
 * §handleStreamSubmit). On any failure the caller is responsible for retry —
 * we don't roll back the stream particle on child failure because Firestore
 * doesn't expose a multi-write transaction across these subcollections, and
 * an empty stream is harmless (the user can retry composing into it).
 */
export async function createStreamWithFirstParticle({
  networkId,
  name,
  visibleTo,
  createdByHumanId,
  firstParticle,
}: CreateStreamWithFirstParticleParams): Promise<CreateStreamWithFirstParticleResult> {
  // 1. The stream particle goes at the network root.
  const rootChildrenPath = toFirestoreChildrenPath(particlePath(networkId, []));
  const streamId = await createStreamParticle(
    rootChildrenPath,
    { name },
    createdByHumanId,
    visibleTo,
  );

  const streamPath = particlePath(networkId, [streamId]);

  // 2. The first child goes inside the new stream.
  if (firstParticle.type === 'text') {
    await createTextParticle({
      networkId,
      targetPath: streamPath,
      content: firstParticle.content,
      createdByHumanId,
    });
  } else {
    await uploadMediaParticle({
      networkId,
      targetPath: streamPath,
      fileUri: firstParticle.fileUri,
      mimeType: firstParticle.mimeType,
      durationMs: firstParticle.durationMs,
      source: firstParticle.source,
      createdByHumanId,
    });
  }

  return { streamId };
}
