/**
 * File I/O composable for DocxEditor — owns the hidden `.docx` and image
 * file-picker refs (Insert > Image inserts directly via the shared
 * `insertImageFromFile` flow, no dialog), the document-name change emit path,
 * the `.docx` download flow, and the load/save bridge over `useDocxEditor`'s
 * `loadBuffer` / `loadParsedDocument` / `saveBlob`. Re-emits `ready` after a
 * tick so host listeners that read comments/tracked-changes on the event see
 * the freshly-extracted arrays, not stale data.
 */

import { ref } from 'vue';
import type { EditorView } from 'prosemirror-view';
import { readDocxFileFromInput } from '@eigenpal/docx-editor-core/utils';
import { insertImageFromFile } from '@eigenpal/docx-editor-core/prosemirror/commands';
import type { Document } from '@eigenpal/docx-editor-core/types/document';

export interface UseFileIOOptions {
  /** From useDocxEditor — loads a .docx buffer into the editor. */
  loadBuffer: (buffer: ArrayBuffer | Uint8Array | Blob | File) => Promise<void>;
  /** From useDocxEditor — loads an already-parsed Document model. */
  loadParsedDocument: (doc: Document) => void;
  /** From useDocxEditor — returns the current Document, or null. */
  getDocument: () => Document | null;
  /** From useDocxEditor — serializes the current state to a .docx Blob. */
  saveBlob: () => Promise<Blob | null>;
  /** Fired after load+extract so the next tick sees comments/tracked-changes. */
  extractCommentsAndChanges: () => void;
  /** SFC's emit function — re-emits ready / rename / update:document / error. */
  emit: (event: string, ...args: unknown[]) => void;
  /** Accessor — read freshly inside the handler so prop updates are honored. */
  documentName: () => string | undefined;
  onDocumentNameChange?: (name: string) => void;
  /** Active editor view to insert images into — the header/footer being edited, else the body. */
  getActiveView: () => EditorView | null;
  /** Vue's `nextTick` — passed in so the composable doesn't require its own import wiring. */
  nextTick: () => Promise<void>;
}

export function useFileIO(opts: UseFileIOOptions) {
  const docxInputRef = ref<HTMLInputElement | null>(null);
  const imageInputRef = ref<HTMLInputElement | null>(null);

  // Insert > Image: open the OS picker (via `imageInputRef.click()` in the menu)
  // and insert directly through the shared core flow — no dialog. Mirrors React.
  function handleImageFileChange(event: Event) {
    const input = event.target as HTMLInputElement;
    const file = input.files?.[0];
    const view = opts.getActiveView();
    if (file && view) {
      insertImageFromFile(view, file, {
        onError: (e) =>
          opts.emit('error', e instanceof Error ? e : new Error('Failed to insert image')),
      });
    }
    input.value = ''; // allow re-selecting the same file
  }

  async function emitReadyAfterSidebarStateRefresh() {
    // Extract comments BEFORE emitting `ready` so host listeners that read
    // comments / tracked changes on the event see the new doc, not stale arrays.
    await opts.nextTick();
    opts.extractCommentsAndChanges();
    opts.emit('ready');
  }

  async function handleDocxFileChange(event: Event) {
    try {
      const result = await readDocxFileFromInput(event);
      if (!result) return;
      await opts.loadBuffer(result.buffer);
      opts.emit('update:document', opts.getDocument());
      opts.emit('rename', result.name);
      await emitReadyAfterSidebarStateRefresh();
    } catch (err) {
      opts.emit('error', err instanceof Error ? err : new Error('Failed to open document'));
    }
  }

  function handleDocumentNameChange(name: string) {
    opts.onDocumentNameChange?.(name);
    opts.emit('rename', name);
  }

  /**
   * File > Save in the menu bar should produce a downloadable .docx, not
   * just stash the Blob and forget. Falls back to "document.docx" when
   * the host doesn't supply a `documentName` prop.
   */
  async function downloadCurrentDocument() {
    const blob = await opts.saveBlob();
    if (!blob) return;
    const baseName = (opts.documentName() ?? '').trim() || 'document';
    const url = URL.createObjectURL(blob);
    const a = window.document.createElement('a');
    a.href = url;
    a.download = `${baseName.replace(/\.docx$/i, '')}.docx`;
    // The anchor never enters the DOM tree — `.click()` works without
    // appending in modern browsers, and skipping the append/remove dance
    // avoids a layout flash on tall pages.
    a.click();
    // Defer revoke so Safari has time to start the download.
    setTimeout(() => URL.revokeObjectURL(url), 0);
  }

  async function loadDocumentBuffer(buffer: Parameters<typeof opts.loadBuffer>[0]) {
    await opts.loadBuffer(buffer);
    opts.emit('update:document', opts.getDocument());
    await emitReadyAfterSidebarStateRefresh();
  }

  function loadDocument(doc: Document) {
    opts.loadParsedDocument(doc);
    opts.emit('update:document', doc);
    void emitReadyAfterSidebarStateRefresh();
  }

  async function save(): Promise<ArrayBuffer | null> {
    const blob = await opts.saveBlob();
    return blob ? blob.arrayBuffer() : null;
  }

  return {
    docxInputRef,
    imageInputRef,
    handleImageFileChange,
    handleDocxFileChange,
    handleDocumentNameChange,
    downloadCurrentDocument,
    emitReadyAfterSidebarStateRefresh,
    loadDocumentBuffer,
    loadDocument,
    save,
  };
}
