import React from 'react';
import type {
  TransferHandle,
  TransferOptions,
  TransferProgress,
  TransferResult,
} from '@shade/sdk';
import { useShadeRuntimeTheme } from '../../ShadeRuntimeProvider.js';
import { useShadeUpload, type UploadEntry } from '../../useShadeUpload.js';
import { DropZone } from './DropZone.js';
import { TransferRow } from './TransferRow.js';

export interface ShadeUploaderProps {
  /** Peer address to upload to. */
  to: string;
  multiple?: boolean;
  accept?: string;
  /** Override the default lane count (default 4 from `@shade/transfer`). */
  lanes?: number;
  /** Override per-chunk plaintext size. */
  chunkSize?: number;
  /** Extra options forwarded to `Shade.upload`. */
  uploadOptions?: Partial<TransferOptions>;
  /**
   * Render-prop for full UI replacement of the per-row entry. Returns whatever
   * you want to render in place of the default `TransferRow`.
   */
  renderRow?: (args: {
    handle: TransferHandle;
    progress: TransferProgress | null;
    name: string;
    done: boolean;
    error: unknown;
    onDismiss: () => void;
  }) => React.ReactNode;
  /** Render-prop for the empty state (no uploads in progress). */
  renderEmpty?: () => React.ReactNode;
  /** Render-prop for the drop zone. */
  renderDropZone?: (props: { onFiles: (files: File[]) => void }) => React.ReactNode;
  onComplete?: (result: TransferResult) => void;
  onError?: (error: unknown) => void;
  className?: string;
  style?: React.CSSProperties;
}

/**
 * Composite uploader: a drop zone + a list of in-flight transfers.
 *
 * Drop a file (or click to pick), and Shade handles encryption + chunking +
 * lanes + retry + integrity verification. Customize via the `renderRow`
 * render-prop or use the headless `useShadeUpload` hook directly.
 */
export function ShadeUploader({
  to,
  multiple = true,
  accept,
  lanes,
  chunkSize,
  uploadOptions,
  renderRow,
  renderEmpty,
  renderDropZone,
  onComplete,
  onError,
  className,
  style,
}: ShadeUploaderProps): React.ReactElement {
  const theme = useShadeRuntimeTheme();
  const { upload, uploads, dismiss } = useShadeUpload();

  const handleFiles = React.useCallback(
    async (files: File[]) => {
      for (const file of files) {
        try {
          const handle = await upload({
            to,
            input: file,
            ...(lanes !== undefined ? { lanes } : {}),
            ...(chunkSize !== undefined ? { chunkSize } : {}),
            ...uploadOptions,
            metadata: {
              name: file.name,
              ...(file.type !== '' ? { contentType: file.type } : {}),
              ...uploadOptions?.metadata,
            },
          });
          if (onComplete !== undefined) {
            void handle
              .done()
              .then(onComplete)
              .catch(() => {
                /* error surfaced via onError */
              });
          }
          if (onError !== undefined) {
            void handle.done().catch(onError);
          }
        } catch (err) {
          onError?.(err);
        }
      }
    },
    [upload, to, lanes, chunkSize, uploadOptions, onComplete, onError],
  );

  return (
    <div
      className={className}
      style={{
        display: 'flex',
        flexDirection: 'column',
        gap: 12,
        fontFamily: theme.font,
        color: theme.text,
        ...style,
      }}
    >
      {renderDropZone !== undefined ? (
        renderDropZone({ onFiles: handleFiles })
      ) : (
        <DropZone onFiles={handleFiles} multiple={multiple} {...(accept !== undefined ? { accept } : {})} />
      )}
      {uploads.length === 0 ? (
        renderEmpty?.() ?? null
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {uploads.map((entry: UploadEntry) => {
            const name = describeEntry(entry);
            const dismissEntry = (): void => dismiss(entry.handle.streamId);
            const cancelEntry = (): void => {
              void entry.handle.abort('user-cancel');
            };
            if (renderRow !== undefined) {
              return (
                <React.Fragment key={entry.handle.streamId}>
                  {renderRow({
                    handle: entry.handle,
                    progress: entry.progress,
                    name,
                    done: entry.done,
                    error: entry.error,
                    onDismiss: dismissEntry,
                  })}
                </React.Fragment>
              );
            }
            return (
              <TransferRow
                key={entry.handle.streamId}
                handle={entry.handle}
                progress={entry.progress}
                name={name}
                done={entry.done}
                error={entry.error}
                {...(entry.progress?.bytesTotal !== undefined ? { bytesTotal: entry.progress.bytesTotal } : {})}
                onCancel={cancelEntry}
                onDismiss={dismissEntry}
              />
            );
          })}
        </div>
      )}
    </div>
  );
}

function describeEntry(entry: UploadEntry): string {
  // The progress event includes bytesTotal but not the file name; we keep
  // a hint by reading the underlying stream metadata when available.
  return entry.handle.streamId.slice(0, 8);
}
