/**
 * AutofillDropzone - Compact dropzone for uploading source documents to autofill form fields.
 *
 * Supports multiple files: files are processed sequentially through the LLM API,
 * and results are applied progressively so the user sees rows added to data tables
 * one file at a time.
 *
 * Flow (per file):
 * 1. File is uploaded via file_manager.upload()
 * 2. POST request to autofill_api_endpoint with file info + field schemas
 * 3. Response data is checked for overwrite conflicts (scalar values only; arrays are always appended)
 * 4. If conflicts exist on the FIRST file, show confirmation dialog
 * 5. On confirm (or no conflicts), call on_autofill_complete(data, attachment)
 * 6. Move to next file
 */
'use client';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useRef, useState, useCallback, useEffect } from 'react';
import { cn } from '../../utils/cn.js';
import { HiSparkles } from 'react-icons/hi2';
import { FaSpinner } from 'react-icons/fa';
import { OverwriteConfirmDialog } from './overwrite_confirm_dialog.js';
function derive_status(jobs) {
    if (jobs.length === 0)
        return 'idle';
    const active = jobs.filter((j) => j.status !== 'done' && j.status !== 'error');
    if (active.length === 0) {
        const last = jobs[jobs.length - 1];
        if (last.status === 'error')
            return 'error';
        if (last.status === 'done') {
            return Object.keys(last.extracted_data).length > 0 ? 'done' : 'done_empty';
        }
        return 'idle';
    }
    if (active.some((j) => j.status === 'queued' || j.status === 'uploading'))
        return 'uploading';
    return 'extracting';
}
function derive_progress(jobs) {
    if (jobs.length === 0)
        return null;
    const total = jobs.length;
    const done = jobs.filter((j) => j.status === 'done' || j.status === 'error').length;
    if (done === total)
        return null;
    return { current: done, total };
}
export function AutofillDropzone({ group_id, fields, autofill_api_endpoint, prompt_area, prompt_key, file_manager_upload, file_manager_get_download_url, on_autofill_complete, form_data, locked = false, upload_manager, }) {
    const file_input_ref = useRef(null);
    const [status, set_status] = useState('idle');
    const [error_message, set_error_message] = useState(null);
    const [info_message, set_info_message] = useState(null);
    const [is_dragging, set_is_dragging] = useState(false);
    // Multi-file progress tracking
    const [progress, set_progress] = useState(null);
    // Overwrite dialog state
    const [show_overwrite_dialog, set_show_overwrite_dialog] = useState(false);
    const [conflicts, set_conflicts] = useState([]);
    // For multi-file, we store a resolve callback so the sequential loop can wait for user decision
    const overwrite_resolve_ref = useRef(null);
    // Manager-mode state + subscription
    const use_manager = !!upload_manager;
    const [manager_jobs, set_manager_jobs] = useState([]);
    useEffect(() => {
        if (!upload_manager)
            return;
        set_manager_jobs(upload_manager.get_jobs_for_group(group_id));
        const refresh = () => set_manager_jobs(upload_manager.get_jobs_for_group(group_id));
        const unsubs = [
            upload_manager.on('job:created', ({ job }) => { if (job.group_id === group_id)
                refresh(); }),
            upload_manager.on('job:status_changed', ({ job }) => { if (job.group_id === group_id)
                refresh(); }),
            upload_manager.on('job:progress', ({ job }) => { if (job.group_id === group_id)
                refresh(); }),
            upload_manager.on('job:completed', ({ job }) => { if (job.group_id === group_id)
                refresh(); }),
            upload_manager.on('job:error', ({ job }) => { if (job.group_id === group_id)
                refresh(); }),
        ];
        return () => unsubs.forEach((u) => u());
    }, [upload_manager, group_id]);
    /**
     * Detect which fields have existing values that would be overwritten.
     * Array values (data tables) are always appended, never treated as conflicts.
     */
    const detect_conflicts = useCallback((data) => {
        const result = [];
        for (const [field_id, new_value] of Object.entries(data)) {
            if (new_value === null || new_value === undefined || new_value === '')
                continue;
            // Arrays (data table rows) are appended, not overwritten — skip conflict check
            if (Array.isArray(new_value))
                continue;
            const current_value = form_data[field_id];
            if (current_value !== null && current_value !== undefined && current_value !== '' && current_value !== false) {
                const field_schema = fields.find((f) => f.field_id === field_id);
                result.push({
                    field_id,
                    label: field_schema?.label || field_id,
                    current_value,
                    new_value,
                });
            }
        }
        return result;
    }, [form_data, fields]);
    // Refs for callbacks to avoid stale closures in the async sequential loop.
    // Without these, process_files captures the callback from when it started,
    // missing state updates from earlier files in the batch.
    const on_autofill_complete_ref = useRef(on_autofill_complete);
    const detect_conflicts_ref = useRef(detect_conflicts);
    useEffect(() => {
        on_autofill_complete_ref.current = on_autofill_complete;
        detect_conflicts_ref.current = detect_conflicts;
    });
    /**
     * Upload a single file and extract data via the autofill API.
     * Returns { data, attachment } on success, or null on failure.
     * Does NOT apply data — the caller is responsible for that.
     */
    const upload_and_extract = useCallback(async (file) => {
        // Step 1: Upload
        let attachment;
        try {
            attachment = await file_manager_upload(file, {
                entity_type: 'form_field',
                entity_id: group_id,
                visibility: 'public',
            });
        }
        catch (err) {
            set_error_message(err instanceof Error ? err.message : `Upload failed: ${file.name}`);
            return null;
        }
        // Step 2: Read file as base64
        let file_b64;
        try {
            const array_buffer = await file.arrayBuffer();
            const bytes = new Uint8Array(array_buffer);
            let binary = '';
            for (let i = 0; i < bytes.length; i++) {
                binary += String.fromCharCode(bytes[i]);
            }
            file_b64 = btoa(binary);
        }
        catch {
            // If base64 conversion fails, the API will fall back to download_url
        }
        // Step 3: Call autofill API
        const download_url = file_manager_get_download_url(attachment.file_id, 'public');
        const request_body = {
            file_id: attachment.file_id,
            file_name: attachment.file_name,
            mime_type: attachment.mime_type,
            download_url,
            file_b64,
            group_id,
            prompt_area,
            prompt_key,
            fields,
        };
        let response_data;
        try {
            const response = await fetch(autofill_api_endpoint, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(request_body),
            });
            if (!response.ok) {
                throw new Error(`Autofill API returned ${response.status}`);
            }
            response_data = await response.json();
        }
        catch (err) {
            set_error_message(err instanceof Error ? err.message : `Extraction failed: ${file.name}`);
            return null;
        }
        if (!response_data.success) {
            set_error_message(response_data.error || `Extraction failed: ${file.name}`);
            return null;
        }
        return {
            data: response_data.data,
            attachment,
            message: response_data.message,
        };
    }, [group_id, fields, autofill_api_endpoint, prompt_area, prompt_key, file_manager_upload, file_manager_get_download_url]);
    /**
     * Process multiple files sequentially.
     * Each file is uploaded, extracted, and results applied progressively.
     */
    const process_files = useCallback(async (files) => {
        if (upload_manager) {
            upload_manager.submit_batch({
                files,
                group_id,
                group_label: group_id,
                metadata: {
                    autofill_api_endpoint,
                    prompt_area,
                    prompt_key,
                    fields,
                    existing_form_data: form_data,
                    file_manager_upload,
                    file_manager_get_download_url,
                },
            });
            return;
        }
        set_error_message(null);
        set_info_message(null);
        const total = files.length;
        let completed = 0;
        let had_errors = false;
        let had_empty = false;
        let last_info_message = null;
        // Track whether the user already approved overwrites for this batch
        let overwrites_approved = false;
        set_progress({ current: 0, total });
        for (const file of files) {
            completed++;
            set_progress({ current: completed, total });
            // Show extracting status for each file
            set_status('extracting');
            const result = await upload_and_extract(file);
            if (!result) {
                had_errors = true;
                // Continue to next file — error_message is already set
                continue;
            }
            // Handle "no data found" message
            if (result.message) {
                last_info_message = result.message;
                had_empty = true;
                // Still apply the data (which may be empty) and attach the file
                on_autofill_complete_ref.current(result.data, result.attachment);
                continue;
            }
            // Check for scalar overwrite conflicts (only if not already approved for this batch)
            if (!overwrites_approved) {
                const found_conflicts = detect_conflicts_ref.current(result.data);
                if (found_conflicts.length > 0) {
                    // Show dialog and wait for user decision
                    set_conflicts(found_conflicts);
                    set_show_overwrite_dialog(true);
                    set_status('idle');
                    const action = await new Promise((resolve) => {
                        overwrite_resolve_ref.current = resolve;
                    });
                    set_show_overwrite_dialog(false);
                    overwrite_resolve_ref.current = null;
                    if (action === 'cancel') {
                        // Skip this file's scalar data, but still attach the file and apply array data
                        const array_only_data = {};
                        for (const [key, val] of Object.entries(result.data)) {
                            if (Array.isArray(val))
                                array_only_data[key] = val;
                        }
                        if (Object.keys(array_only_data).length > 0) {
                            on_autofill_complete_ref.current(array_only_data, result.attachment);
                        }
                        continue;
                    }
                    // User approved — don't ask again for this batch
                    overwrites_approved = true;
                    set_status('extracting');
                }
            }
            // Apply data — use ref to always call latest callback (avoids stale closure)
            on_autofill_complete_ref.current(result.data, result.attachment);
        }
        // Final status
        set_progress(null);
        if (had_errors) {
            set_status('error');
            // error_message is already set from the last failure
        }
        else if (had_empty && !had_errors) {
            set_info_message(last_info_message || 'No matching data found in some documents');
            set_status('done_empty');
            setTimeout(() => set_status('idle'), 5000);
        }
        else {
            set_status('done');
            setTimeout(() => set_status('idle'), 2000);
        }
    }, [upload_manager, group_id, autofill_api_endpoint, prompt_area, prompt_key, fields, form_data, file_manager_upload, file_manager_get_download_url, upload_and_extract]);
    // Overwrite confirmation handlers (resolve the promise)
    const handle_overwrite_confirm = useCallback(() => {
        overwrite_resolve_ref.current?.('confirm');
    }, []);
    const handle_overwrite_cancel = useCallback(() => {
        overwrite_resolve_ref.current?.('cancel');
    }, []);
    // Drag handlers
    const handle_drag_enter = useCallback((e) => {
        e.preventDefault();
        e.stopPropagation();
        if (!locked)
            set_is_dragging(true);
    }, [locked]);
    const handle_drag_leave = useCallback((e) => {
        e.preventDefault();
        e.stopPropagation();
        set_is_dragging(false);
    }, []);
    const handle_drag_over = useCallback((e) => {
        e.preventDefault();
        e.stopPropagation();
    }, []);
    const display_status = use_manager ? derive_status(manager_jobs) : status;
    const display_progress = use_manager ? derive_progress(manager_jobs) : progress;
    const handle_drop = useCallback((e) => {
        e.preventDefault();
        e.stopPropagation();
        set_is_dragging(false);
        if (locked || display_status !== 'idle')
            return;
        const file_list = e.dataTransfer.files;
        if (file_list.length > 0) {
            process_files(Array.from(file_list));
        }
    }, [locked, display_status, process_files]);
    const handle_input_change = useCallback((e) => {
        const file_list = e.target.files;
        if (file_list && file_list.length > 0 && !locked && display_status === 'idle') {
            process_files(Array.from(file_list));
        }
        if (file_input_ref.current)
            file_input_ref.current.value = '';
    }, [locked, display_status, process_files]);
    const handle_click = useCallback(() => {
        if (!locked && display_status === 'idle') {
            file_input_ref.current?.click();
        }
    }, [locked, display_status]);
    const handle_retry = useCallback(() => {
        set_status('idle');
        set_error_message(null);
        set_progress(null);
    }, []);
    const is_busy = display_status === 'uploading' || display_status === 'extracting';
    const is_disabled = locked || is_busy;
    // Build status label
    const get_status_label = () => {
        if (!is_busy)
            return '';
        if (display_progress && display_progress.total > 1) {
            return `Processing file ${display_progress.current} of ${display_progress.total}...`;
        }
        return 'Extracting data...';
    };
    return (_jsxs(_Fragment, { children: [_jsxs("div", { className: cn('cls_autofill_dropzone flex flex-col items-center justify-center gap-1 px-4 py-5 rounded-md border-2 border-dashed transition-colors cursor-pointer select-none bg-blue-50/60 dark:bg-blue-950/20', is_disabled && 'opacity-50 cursor-not-allowed', is_dragging && !is_disabled
                    ? 'border-primary bg-primary/10'
                    : 'border-blue-300 hover:border-primary hover:bg-blue-100/60 dark:border-blue-700 dark:hover:bg-blue-900/30', display_status === 'done' && 'border-green-400 bg-green-50 dark:bg-green-900/10', display_status === 'done_empty' && 'border-amber-400 bg-amber-50 dark:bg-amber-900/10', display_status === 'error' && 'border-destructive bg-destructive/5'), onDragEnter: handle_drag_enter, onDragLeave: handle_drag_leave, onDragOver: handle_drag_over, onDrop: handle_drop, onClick: handle_click, role: "button", tabIndex: is_disabled ? -1 : 0, "aria-disabled": is_disabled, "aria-label": "AI fill from document", children: [_jsx("input", { ref: file_input_ref, type: "file", className: "hidden", multiple: true, "data-testid": "autofill-file-input", onChange: handle_input_change, disabled: is_disabled }), _jsx("div", { className: "flex-shrink-0", children: is_busy ? (_jsx(FaSpinner, { className: "h-5 w-5 text-primary animate-spin" })) : (_jsx(HiSparkles, { className: "h-5 w-5 text-blue-400 dark:text-blue-500" })) }), _jsxs("div", { className: "text-center", children: [display_status === 'idle' && (_jsx("span", { className: "text-sm text-muted-foreground", children: "Drag and drop document, or click to select file" })), is_busy && (_jsx("span", { className: "text-sm text-primary font-medium", children: get_status_label() })), display_status === 'done' && (_jsx("span", { className: "text-sm text-green-600 dark:text-green-400 font-medium", children: "Autofill complete" })), display_status === 'done_empty' && (_jsx("span", { className: "text-sm text-amber-600 dark:text-amber-400 font-medium", children: info_message || 'No matching data found in document' })), display_status === 'error' && (_jsxs("div", { className: "flex items-center justify-center gap-2", children: [_jsx("span", { className: "text-sm text-destructive truncate", children: error_message || 'An error occurred' }), _jsx("button", { type: "button", onClick: (e) => {
                                            e.stopPropagation();
                                            handle_retry();
                                        }, className: "text-xs text-primary underline hover:no-underline flex-shrink-0", children: "Retry" })] }))] })] }), !use_manager && (_jsx(OverwriteConfirmDialog, { open: show_overwrite_dialog, on_open_change: (open) => {
                    if (!open)
                        handle_overwrite_cancel();
                }, conflicts: conflicts, on_confirm: handle_overwrite_confirm, on_cancel: handle_overwrite_cancel }))] }));
}
AutofillDropzone.displayName = 'AutofillDropzone';
//# sourceMappingURL=autofill_dropzone.js.map