import { DestroyRef, effect, inject } from '@angular/core';
import { CategoryParams, UploadAdapter } from './upload.adapter';
import { UploadShellService } from './upload-shell.service';
import { problemDetail } from '@shared/infrastructure/api-error';
import { SUBMIT_FAILED } from '@shared/application/submit';
import { DeliveryChannel, UploadMsg, UploadState, inFlight, rejectReason } from './upload.machine';

export interface UploadControllerDeps {
  wizardId: string;
  getUpload: () => UploadState;
  dispatch: (m: UploadMsg) => void;
  /** Optional answer-derived params; when they change, categories re-fetch. */
  getCategoryParams?: () => CategoryParams;
}

/**
 * The effectful glue between the `<app-document-upload>` organism's events and the
 * pure upload reducer + transport. Both wizards instantiate one (in a field
 * initializer, like `createStore`). Holds the only state a reducer can't: the live
 * `File` blobs keyed by localId (needed to retry an upload). Loads categories,
 * reports background-sync availability, and polls on tab refocus.
 */
export function createUploadController(deps: UploadControllerDeps) {
  const adapter = inject(UploadAdapter);
  const shell = inject(UploadShellService);
  const files = new Map<string, File>();
  const categoriesRes = adapter.categoriesResource(deps.wizardId, deps.getCategoryParams);

  // Runs after the host's `Seed`/restore microtask (effects fire in CD, not field
  // init), so these dispatches aren't wiped by a state reseed.
  effect(() => {
    deps.dispatch({ type: 'BackgroundSyncAvailability', available: shell.backgroundSyncAvailable });
    const status = categoriesRes.status();
    if (status === 'resolved' || status === 'local') {
      deps.dispatch({ type: 'CategoriesLoaded', categories: categoriesRes.value() ?? [] });
    } else if (status === 'error') {
      deps.dispatch({
        type: 'CategoriesLoadFailed',
        reason: problemDetail(categoriesRes.error(), SUBMIT_FAILED),
      });
    }
  });

  const onFocus = () => void shell.pollReturning(inFlight(deps.getUpload()), deps.dispatch);
  window.addEventListener('focus', onFocus);
  inject(DestroyRef).onDestroy(() => window.removeEventListener('focus', onFocus));

  function start(categoryId: string, file: File) {
    const localId = crypto.randomUUID();
    files.set(localId, file);
    deps.dispatch({
      type: 'FileSelected',
      categoryId,
      localId,
      fileName: file.name,
      fileSizeMb: file.size / 1e6,
    });
    shell.upload({ localId, categoryId, wizardId: deps.wizardId, file }, deps.dispatch);
  }

  return {
    onFileSelected(categoryId: string, selected: File[]) {
      const cat = deps.getUpload().categories.find((c) => c.categoryId === categoryId);
      if (!cat) return;
      if (!cat.multiple && selected.length > 1) {
        deps.dispatch({ type: 'FileRejected', categoryId, reason: 'multiple' });
        return;
      }
      for (const file of selected) {
        const reason = rejectReason(cat, { type: file.type, sizeMb: file.size / 1e6 });
        if (reason) deps.dispatch({ type: 'FileRejected', categoryId, reason });
        else start(categoryId, file);
      }
    },
    onRemove(localId: string) {
      shell.cancel([localId]);
      files.delete(localId);
      deps.dispatch({ type: 'UploadRemoved', localId });
    },
    onRetry(localId: string) {
      const file = files.get(localId);
      const up = deps.getUpload().uploads.find((u) => u.localId === localId);
      if (!file || !up) return;
      deps.dispatch({ type: 'UploadRetried', localId });
      shell.upload(
        { localId, categoryId: up.categoryId, wizardId: deps.wizardId, file },
        deps.dispatch,
      );
    },
    onDelete(e: { localId: string; documentId: string }) {
      shell.delete(e.localId, e.documentId, deps.dispatch);
    },
    onChannelChange(categoryId: string, channel: DeliveryChannel) {
      const ids = deps
        .getUpload()
        .uploads.filter((u) => u.categoryId === categoryId)
        .map((u) => u.localId);
      shell.cancel(ids);
      deps.dispatch({ type: 'DeliveryChannelChanged', categoryId, channel });
    },
  };
}
