/**
 * Embedded document viewer panel for preview, metadata updates, and lifecycle actions.
 */
import { useEffect, useMemo, useState } from 'react';

import {
  contentMarkdownUrl,
  deleteDocument,
  getDocumentDetails,
  previewUrl,
  reprocessDocument,
  restoreDocument,
  trashDocument,
  updateDocumentMetadata,
} from '../lib/api';
import type { DmsDocument, DmsDocumentDetail } from '../types';
import PathInput from './PathInput';
import TagInput from './TagInput';

/**
 * Defines props for the selected document viewer panel.
 */
interface DocumentViewerProps {
  document: DmsDocument | null;
  isTrashView: boolean;
  existingTags: string[];
  existingPaths: string[];
  onDocumentUpdated: (document: DmsDocument) => void;
  onDocumentDeleted: (documentId: string) => void;
  requestConfirmation: (title: string, message: string, confirmLabel?: string) => Promise<boolean>;
}

/**
 * Renders selected document preview with editable metadata and lifecycle controls.
 */
export default function DocumentViewer({
  document,
  isTrashView,
  existingTags,
  existingPaths,
  onDocumentUpdated,
  onDocumentDeleted,
  requestConfirmation,
}: DocumentViewerProps): JSX.Element {
  const [documentDetail, setDocumentDetail] = useState<DmsDocumentDetail | null>(null);
  const [isLoadingDetails, setIsLoadingDetails] = useState<boolean>(false);
  const [originalFilename, setOriginalFilename] = useState<string>('');
  const [logicalPath, setLogicalPath] = useState<string>('');
  const [tags, setTags] = useState<string[]>([]);
  const [isSaving, setIsSaving] = useState<boolean>(false);
  const [isReprocessing, setIsReprocessing] = useState<boolean>(false);
  const [isTrashing, setIsTrashing] = useState<boolean>(false);
  const [isRestoring, setIsRestoring] = useState<boolean>(false);
  const [isDeleting, setIsDeleting] = useState<boolean>(false);
  const [isMetadataDirty, setIsMetadataDirty] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);

  /**
   * Syncs editable metadata fields whenever selection changes.
   */
  useEffect(() => {
    if (!document) {
      setDocumentDetail(null);
      setIsMetadataDirty(false);
      return;
    }
    setOriginalFilename(document.original_filename);
    setLogicalPath(document.logical_path);
    setTags(document.tags);
    setIsMetadataDirty(false);
    setError(null);
  }, [document?.id]);

  /**
   * Refreshes editable metadata from list updates only while form is clean.
   */
  useEffect(() => {
    if (!document || isMetadataDirty) {
      return;
    }
    setOriginalFilename(document.original_filename);
    setLogicalPath(document.logical_path);
    setTags(document.tags);
  }, [
    document?.id,
    document?.original_filename,
    document?.logical_path,
    document?.tags,
    isMetadataDirty,
  ]);

  /**
   * Loads full selected-document details for extracted text and metadata display.
   */
  useEffect(() => {
    if (!document) {
      return;
    }
    let cancelled = false;
    const loadDocumentDetails = async (): Promise<void> => {
      setIsLoadingDetails(true);
      try {
        const payload = await getDocumentDetails(document.id);
        if (!cancelled) {
          setDocumentDetail(payload);
        }
      } catch (caughtError) {
        if (!cancelled) {
          setError(caughtError instanceof Error ? caughtError.message : 'Failed to load document details');
        }
      } finally {
        if (!cancelled) {
          setIsLoadingDetails(false);
        }
      }
    };

    void loadDocumentDetails();
    return () => {
      cancelled = true;
    };
  }, [document?.id]);

  /**
   * Resolves whether selected document should render as an image element in preview.
   */
  const isImageDocument = useMemo(() => {
    if (!document) {
      return false;
    }
    return document.mime_type.startsWith('image/');
  }, [document]);

  /**
   * Extracts provider/transcription errors from document metadata for user visibility.
   */
  const transcriptionError = useMemo(() => {
    const value = documentDetail?.metadata_json?.transcription_error;
    return typeof value === 'string' ? value : '';
  }, [documentDetail]);

  /**
   * Extracts routing errors from metadata to surface classification issues.
   */
  const routingError = useMemo(() => {
    const value = documentDetail?.metadata_json?.routing_error;
    return typeof value === 'string' ? value : '';
  }, [documentDetail]);

  /**
   * Builds a compact routing status summary for user visibility.
   */
  const routingSummary = useMemo(() => {
    const value = documentDetail?.metadata_json?.routing;
    if (!value || typeof value !== 'object') {
      return '';
    }

    const routing = value as Record<string, unknown>;
    const confidence = typeof routing.confidence === 'number' ? routing.confidence : null;
    const similarity = typeof routing.neighbor_similarity === 'number' ? routing.neighbor_similarity : null;
    const confidenceThreshold =
      typeof routing.auto_apply_confidence_threshold === 'number'
        ? routing.auto_apply_confidence_threshold
        : null;
    const autoApplied = typeof routing.auto_applied === 'boolean' ? routing.auto_applied : null;
    const autoAppliedPath = typeof routing.auto_applied_path === 'boolean' ? routing.auto_applied_path : null;
    const autoAppliedTags = typeof routing.auto_applied_tags === 'boolean' ? routing.auto_applied_tags : null;
    const blockedReasonsRaw = routing.auto_apply_blocked_reasons;
    const blockedReasons =
      Array.isArray(blockedReasonsRaw) && blockedReasonsRaw.length > 0
        ? blockedReasonsRaw
            .map((reason) => String(reason))
            .map((reason) => {
              if (reason === 'missing_chosen_path') {
                return 'no chosen path';
              }
              if (reason === 'confidence_below_threshold') {
                return 'confidence below threshold';
              }
              if (reason === 'neighbor_similarity_below_threshold') {
                return 'neighbor similarity below threshold';
              }
              return reason;
            })
        : [];

    const parts: string[] = [];
    if (autoApplied !== null) {
      parts.push(`Auto Applied: ${autoApplied ? 'yes' : 'no'}`);
    }
    if (autoApplied) {
      const appliedTargets: string[] = [];
      if (autoAppliedPath) {
        appliedTargets.push('path');
      }
      if (autoAppliedTags) {
        appliedTargets.push('tags');
      }
      if (appliedTargets.length > 0) {
        parts.push(`Applied: ${appliedTargets.join(' + ')}`);
      }
    }
    if (confidence !== null) {
      if (confidenceThreshold !== null) {
        parts.push(`Confidence: ${confidence.toFixed(2)} / ${confidenceThreshold.toFixed(2)}`);
      } else {
        parts.push(`Confidence: ${confidence.toFixed(2)}`);
      }
    }
    if (similarity !== null) {
      parts.push(`Neighbor Similarity (info): ${similarity.toFixed(2)}`);
    }
    if (autoApplied === false && blockedReasons.length > 0) {
      parts.push(`Blocked: ${blockedReasons.join(', ')}`);
    }
    return parts.join(' | ');
  }, [documentDetail]);

  /**
   * Resolves whether routing already auto-applied path and tags.
   */
  const routingAutoApplyState = useMemo(() => {
    const value = documentDetail?.metadata_json?.routing;
    if (!value || typeof value !== 'object') {
      return {
        autoAppliedPath: false,
        autoAppliedTags: false,
      };
    }

    const routing = value as Record<string, unknown>;
    return {
      autoAppliedPath: routing.auto_applied_path === true,
      autoAppliedTags: routing.auto_applied_tags === true,
    };
  }, [documentDetail]);

  /**
   * Resolves whether any routing suggestion still needs manual application.
   */
  const hasPathSuggestion = Boolean(document?.suggested_path) && !routingAutoApplyState.autoAppliedPath;
  const hasTagSuggestions = (document?.suggested_tags.length ?? 0) > 0 && !routingAutoApplyState.autoAppliedTags;
  const canApplyAllSuggestions = hasPathSuggestion || hasTagSuggestions;

  /**
   * Applies suggested path value to editable metadata field.
   */
  const applySuggestedPath = (): void => {
    if (!hasPathSuggestion || !document?.suggested_path) {
      return;
    }
    setLogicalPath(document.suggested_path);
    setIsMetadataDirty(true);
  };

  /**
   * Applies one suggested tag to editable metadata field.
   */
  const applySuggestedTag = (tag: string): void => {
    if (!hasTagSuggestions || tags.includes(tag)) {
      return;
    }
    setTags([...tags, tag]);
    setIsMetadataDirty(true);
  };

  /**
   * Applies all suggested routing values into editable metadata fields.
   */
  const applyAllSuggestions = (): void => {
    if (hasPathSuggestion && document?.suggested_path) {
      setLogicalPath(document.suggested_path);
    }
    if (hasTagSuggestions && document?.suggested_tags.length) {
      const nextTags = [...tags];
      for (const tag of document.suggested_tags) {
        if (!nextTags.includes(tag)) {
          nextTags.push(tag);
        }
      }
      setTags(nextTags);
    }
    setIsMetadataDirty(true);
  };

  /**
   * Persists metadata changes to backend.
   */
  const handleSave = async (): Promise<void> => {
    if (!document) {
      return;
    }
    setIsSaving(true);
    setError(null);
    try {
      const updated = await updateDocumentMetadata(document.id, {
        original_filename: originalFilename,
        logical_path: logicalPath,
        tags,
      });
      setOriginalFilename(updated.original_filename);
      setLogicalPath(updated.logical_path);
      setTags(updated.tags);
      setIsMetadataDirty(false);
      onDocumentUpdated(updated);
      const payload = await getDocumentDetails(document.id);
      setDocumentDetail(payload);
    } catch (caughtError) {
      setError(caughtError instanceof Error ? caughtError.message : 'Failed to save metadata');
    } finally {
      setIsSaving(false);
    }
  };

  /**
   * Re-runs extraction and routing logic for the currently selected document.
   */
  const handleReprocess = async (): Promise<void> => {
    if (!document) {
      return;
    }
    setIsReprocessing(true);
    setError(null);
    try {
      const updated = await reprocessDocument(document.id);
      onDocumentUpdated(updated);
      const payload = await getDocumentDetails(document.id);
      setDocumentDetail(payload);
    } catch (caughtError) {
      setError(caughtError instanceof Error ? caughtError.message : 'Failed to reprocess document');
    } finally {
      setIsReprocessing(false);
    }
  };

  /**
   * Moves the selected document to trash state.
   */
  const handleTrash = async (): Promise<void> => {
    if (!document) {
      return;
    }
    setIsTrashing(true);
    setError(null);
    try {
      const updated = await trashDocument(document.id);
      onDocumentUpdated(updated);
    } catch (caughtError) {
      setError(caughtError instanceof Error ? caughtError.message : 'Failed to trash document');
    } finally {
      setIsTrashing(false);
    }
  };

  /**
   * Restores the selected document from trash.
   */
  const handleRestore = async (): Promise<void> => {
    if (!document) {
      return;
    }
    setIsRestoring(true);
    setError(null);
    try {
      const updated = await restoreDocument(document.id);
      onDocumentUpdated(updated);
    } catch (caughtError) {
      setError(caughtError instanceof Error ? caughtError.message : 'Failed to restore document');
    } finally {
      setIsRestoring(false);
    }
  };

  /**
   * Permanently deletes the selected document and associated files.
   */
  const handleDelete = async (): Promise<void> => {
    if (!document) {
      return;
    }
    const confirmed = await requestConfirmation(
      'Delete Document Permanently',
      'This removes the document record and stored file from the system.',
      'Delete Permanently',
    );
    if (!confirmed) {
      return;
    }

    setIsDeleting(true);
    setError(null);
    try {
      await deleteDocument(document.id);
      onDocumentDeleted(document.id);
    } catch (caughtError) {
      setError(caughtError instanceof Error ? caughtError.message : 'Failed to delete document');
    } finally {
      setIsDeleting(false);
    }
  };

  if (!document) {
    return (
      <aside className="document-viewer empty">
        <h2>Document Details</h2>
        <p>Select a document to preview and manage metadata.</p>
      </aside>
    );
  }

  const isTrashed = document.status === 'trashed' || isTrashView;
  const metadataDisabled = isTrashed || isSaving || isTrashing || isRestoring || isDeleting;

  return (
    <aside className="document-viewer">
      <h2>{document.original_filename}</h2>
      <p className="small">Status: {document.status}</p>
      <div className="viewer-preview">
        {isImageDocument ? (
          <img src={previewUrl(document.id)} alt={document.original_filename} />
        ) : (
          <iframe src={previewUrl(document.id)} title={document.original_filename} />
        )}
      </div>
      <label>
        File Name
        <input
          value={originalFilename}
          onChange={(event) => {
            setOriginalFilename(event.target.value);
            setIsMetadataDirty(true);
          }}
          disabled={metadataDisabled}
        />
      </label>
      <label>
        Destination Path
        <PathInput
          value={logicalPath}
          onChange={(value) => {
            setLogicalPath(value);
            setIsMetadataDirty(true);
          }}
          suggestions={existingPaths}
          disabled={metadataDisabled}
        />
      </label>
      <label>
        Tags
        <TagInput
          value={tags}
          onChange={(value) => {
            setTags(value);
            setIsMetadataDirty(true);
          }}
          suggestions={existingTags}
          disabled={metadataDisabled}
        />
      </label>
      {(document.suggested_path || document.suggested_tags.length > 0 || routingSummary || routingError) && (
        <section className="routing-suggestions-panel">
          <div className="routing-suggestions-header">
            <h3>Routing Suggestions</h3>
            {canApplyAllSuggestions && (
              <button
                type="button"
                className="secondary-action"
                onClick={applyAllSuggestions}
                disabled={metadataDisabled}
              >
                Apply All
              </button>
            )}
          </div>
          {routingError && <p className="small error">{routingError}</p>}
          {routingSummary && <p className="small">{routingSummary}</p>}
          {hasPathSuggestion && document.suggested_path && (
            <div className="routing-suggestion-group">
              <p className="small">Suggested Path</p>
              <button
                type="button"
                className="routing-pill"
                onClick={applySuggestedPath}
                disabled={metadataDisabled}
              >
                {document.suggested_path}
              </button>
            </div>
          )}
          {hasTagSuggestions && document.suggested_tags.length > 0 && (
            <div className="routing-suggestion-group">
              <p className="small">Suggested Tags</p>
              <div className="routing-pill-row">
                {document.suggested_tags.map((tag) => (
                  <button
                    key={tag}
                    type="button"
                    className="routing-pill"
                    onClick={() => applySuggestedTag(tag)}
                    disabled={metadataDisabled}
                  >
                    {tag}
                  </button>
                ))}
              </div>
            </div>
          )}
        </section>
      )}
      <section className="extracted-text-panel">
        <h3>Extracted Text</h3>
        {transcriptionError && <p className="small error">{transcriptionError}</p>}
        {isLoadingDetails ? (
          <p className="small">Loading extracted text...</p>
        ) : documentDetail?.extracted_text.trim() ? (
          <pre>{documentDetail.extracted_text}</pre>
        ) : (
          <p className="small">No extracted text available for this document yet.</p>
        )}
      </section>
      {error && <p className="error">{error}</p>}
      <div className="viewer-actions">
        {!isTrashed && (
          <button type="button" onClick={handleSave} disabled={metadataDisabled}>
            {isSaving ? 'Saving...' : 'Save Metadata'}
          </button>
        )}
        {!isTrashed && (
          <button
            type="button"
            className="secondary-action"
            onClick={handleReprocess}
            disabled={metadataDisabled || isReprocessing}
            title="Re-runs OCR/extraction, summary generation, routing suggestion, and indexing for this document."
          >
            {isReprocessing ? 'Reprocessing...' : 'Reprocess Document'}
          </button>
        )}
        {!isTrashed && (
          <button
            type="button"
            className="warning-action"
            onClick={handleTrash}
            disabled={metadataDisabled || isTrashing}
          >
            {isTrashing ? 'Trashing...' : 'Move To Trash'}
          </button>
        )}
        {isTrashed && (
          <button
            type="button"
            className="secondary-action"
            onClick={handleRestore}
            disabled={isRestoring || isDeleting}
          >
            {isRestoring ? 'Restoring...' : 'Restore Document'}
          </button>
        )}
        <button
          type="button"
          className="secondary-action"
          onClick={() => window.open(contentMarkdownUrl(document.id), '_blank', 'noopener,noreferrer')}
          disabled={isDeleting}
          title="Downloads recognized/extracted content as markdown for this document."
        >
          Download Recognized MD
        </button>
        {isTrashed && (
          <button
            type="button"
            className="danger-action"
            onClick={handleDelete}
            disabled={isDeleting || isRestoring}
          >
            {isDeleting ? 'Deleting...' : 'Delete Permanently'}
          </button>
        )}
      </div>
      <p className="viewer-inline-help">
        Reprocess runs OCR/extraction, updates summary, refreshes routing suggestions, and re-indexes search.
      </p>
    </aside>
  );
}
