import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Layout from './components/Layout';
import Toolbar from './components/Toolbar';
import EditorPanel from './components/EditorPanel';
import ActionDialog, {
  type ActionDialogAction,
} from './components/ActionDialog';
import HelpDialog from './components/HelpDialog';
import SnippetToolbox from './components/SnippetToolbox';
import { useWorkbenchState } from './workspace/useWorkbenchState';
import type { WorkbenchDocumentKind } from './workspace/workspaceTypes';
import type { CodeMirrorEditorHandle } from './editor/editorTypes';
import { openTextFile, saveTextFile } from './file/fileService';
import { validateXml, formatXml } from './validation/xmlValidation';
import { validateXslt } from './validation/xsltValidation';
import {
  hasErrors,
  type DiagnosticMessage,
} from './validation/validationTypes';
import { runTransformation } from './transform/transformService';
import { createTransformationRun } from './transform/nativeXsltEngine';
import type { TransformEngineId } from './transform/transformTypes';
import { createApproximateTrace } from './transform/traceAnalyzer';

interface DialogState {
  title: string;
  content: React.ReactNode;
  actions: ActionDialogAction[];
  onCancel?: () => void;
}

const App: React.FC = () => {
  const {
    state,
    setState,
    updateDocumentText,
    replaceDocument,
    markDocumentSaved,
    setLastTransformation,
    resetWorkspace,
  } = useWorkbenchState();

  const xmlInputEditorRef = useRef<CodeMirrorEditorHandle | null>(null);
  const xsltEditorRef = useRef<CodeMirrorEditorHandle | null>(null);
  const outputEditorRef = useRef<CodeMirrorEditorHandle | null>(null);

  const [dialog, setDialog] = useState<DialogState | null>(null);
  const [helpOpen, setHelpOpen] = useState(false);
  const [busy, setBusy] = useState(false);
  const [statusMessage, setStatusMessage] = useState<string | null>(null);
  const [runtimeDiagnostics, setRuntimeDiagnostics] = useState<
    DiagnosticMessage[]
  >([]);
  const [validationTouched, setValidationTouched] = useState(false);

  const xmlDiagnostics = useMemo(
    () => validateXml(state.xmlInput.text, 'XML input'),
    [state.xmlInput.text]
  );

  const xsltDiagnostics = useMemo(
    () => validateXslt(state.xsltCode.text, state.selectedEngine),
    [state.xsltCode.text, state.selectedEngine]
  );

  const outputDiagnostics = useMemo(() => {
    if (!state.xmlOutput.text.trim()) return [];
    return validateXml(state.xmlOutput.text, 'XML output');
  }, [state.xmlOutput.text]);

  const closeDialog = useCallback(() => {
    dialog?.onCancel?.();
    setDialog(null);
  }, [dialog]);

  const askForConfirmation = useCallback(
    (options: {
      title: string;
      content: React.ReactNode;
      proceedLabel?: string;
      danger?: boolean;
    }): Promise<boolean> => {
      return new Promise((resolve) => {
        const finish = (confirmed: boolean) => {
          setDialog(null);
          resolve(confirmed);
        };

        setDialog({
          title: options.title,
          content: options.content,
          onCancel: () => resolve(false),
          actions: [
            {
              label: 'Cancel',
              variant: 'secondary',
              autoFocus: true,
              onClick: () => finish(false),
            },
            {
              label: options.proceedLabel ?? 'Proceed',
              variant: options.danger ? 'danger' : 'primary',
              onClick: () => finish(true),
            },
          ],
        });
      });
    },
    []
  );

  const confirmOverwrite = useCallback(
    async (kind: WorkbenchDocumentKind, reason: string): Promise<boolean> => {
      const documentState = state[kind];
      if (!documentState.text.trim() && !documentState.dirty) return true;

      return askForConfirmation({
        title: `Overwrite ${documentState.label}?`,
        content: (
          <p>
            {reason} This will replace the current contents of{' '}
            <strong>{documentState.label}</strong>.
          </p>
        ),
        proceedLabel: 'Overwrite',
        danger: true,
      });
    },
    [askForConfirmation, state]
  );

  const handleOpenHelp = useCallback(() => {
    setHelpOpen(true);
  }, []);

  const handleCloseHelp = useCallback(() => {
    setHelpOpen(false);
  }, []);

  const handleOpenFile = async (kind: WorkbenchDocumentKind) => {
    try {
      const opened = await openTextFile(kind);
      if (!opened) return;

      const documentState = state[kind];

      if (documentState.dirty) {
        const mayProceed = await askForConfirmation({
          title: `Overwrite ${documentState.label}?`,
          content: (
            <p>
              Opening <strong>{opened.name}</strong> will replace the current
              unsaved contents of <strong>{documentState.label}</strong>.
            </p>
          ),
          proceedLabel: 'Overwrite',
          danger: true,
        });

        if (!mayProceed) return;
      }

      replaceDocument(kind, opened.text, opened.name, false);
      setStatusMessage(`Loaded ${opened.name}.`);
    } catch (error) {
      if (isAbortError(error)) return;
      setStatusMessage(
        error instanceof Error ? error.message : 'Failed to open file.'
      );
    }
  };

  const handleSaveFile = async (kind: WorkbenchDocumentKind) => {
    try {
      const documentState = state[kind];
      const suggestedName = documentState.fileName ?? defaultFileName(kind);
      const savedName = await saveTextFile({
        suggestedName,
        text: documentState.text,
        kind,
      });

      if (savedName) {
        markDocumentSaved(kind, savedName);
        setStatusMessage(`Saved ${savedName}.`);
      }
    } catch (error) {
      if (isAbortError(error)) return;
      setStatusMessage(
        error instanceof Error ? error.message : 'Failed to save file.'
      );
    }
  };

  const handleApplyTransformation = async () => {
    setValidationTouched(true);
    setRuntimeDiagnostics([]);

    const diagnostics = [...xmlDiagnostics, ...xsltDiagnostics];
    if (hasErrors(diagnostics)) {
      setRuntimeDiagnostics(diagnostics);
      setStatusMessage('Please fix validation errors before transforming.');
      return;
    }

    if (state.options.askBeforeOverwritingOutput) {
      const mayOverwriteOutput = await confirmOverwrite(
        'xmlOutput',
        'Applying the transformation writes a new result.'
      );
      if (!mayOverwriteOutput) return;
    }

    setBusy(true);
    try {
      const request = {
        xmlText: state.xmlInput.text,
        xsltText: state.xsltCode.text,
        engine: state.selectedEngine,
      };

      const result = await runTransformation(request);
      setRuntimeDiagnostics(result.diagnostics);

      if (hasErrors(result.diagnostics)) {
        setStatusMessage(
          'Transformation failed because the input or stylesheet is invalid.'
        );
        return;
      }

      const output = state.options.prettifyOutputAfterTransform
        ? tryFormatXml(result.output)
        : result.output;

      replaceDocument('xmlOutput', output, state.xmlOutput.fileName, true);
      const run = await createTransformationRun(request, output);

      setLastTransformation(run);
      setStatusMessage(`Transformation completed with ${result.engine}.`);
    } catch (error) {
      setRuntimeDiagnostics([
        {
          severity: 'error',
          source: 'Transformation engine',
          message:
            error instanceof Error ? error.message : 'Transformation failed.',
        },
      ]);
      setStatusMessage('Transformation failed.');
    } finally {
      setBusy(false);
    }
  };

  const handleMoveOutputToInput = async () => {
    if (!state.xmlOutput.text.trim()) {
      setStatusMessage('The output editor is empty. Nothing to move.');
      return;
    }

    const mayProceed = await confirmOverwrite(
      'xmlInput',
      'Moving output to input is useful for chained transformations.'
    );
    if (!mayProceed) return;

    replaceDocument('xmlInput', state.xmlOutput.text, 'from-output.xml', true);
    setStatusMessage('Moved output to XML input.');
  };

  const handleFormat = async (kind: WorkbenchDocumentKind) => {
    const documentState = state[kind];

    try {
      const formatted = formatXml(documentState.text);
      if (formatted === documentState.text) {
        setStatusMessage(`${documentState.label} is already formatted.`);
        return;
      }

      const mayProceed = await askForConfirmation({
        title: `Format ${documentState.label}?`,
        content: (
          <p>
            Formatting rewrites whitespace in{' '}
            <strong>{documentState.label}</strong>.
          </p>
        ),
        proceedLabel: 'Format',
      });

      if (!mayProceed) return;
      replaceDocument(kind, formatted, documentState.fileName, true);
      setStatusMessage(`Formatted ${documentState.label}.`);
    } catch (error) {
      setStatusMessage(
        error instanceof Error ? error.message : 'Could not format XML.'
      );
    }
  };

  const handleValidate = () => {
    setValidationTouched(true);
    const allDiagnostics = [
      ...xmlDiagnostics,
      ...xsltDiagnostics,
      ...outputDiagnostics,
    ];
    setRuntimeDiagnostics(allDiagnostics);

    if (allDiagnostics.length === 0) {
      setStatusMessage('All current documents are well-formed.');
      return;
    }

    setStatusMessage(
      hasErrors(allDiagnostics)
        ? 'Validation found errors.'
        : 'Validation completed with warnings.'
    );
  };

  const handleReset = async () => {
    const mayProceed = await askForConfirmation({
      title: 'Reset workspace?',
      content: (
        <p>
          This restores the example XML and XSLT, clears the output, and removes
          the saved LocalStorage workspace.
        </p>
      ),
      proceedLabel: 'Reset workspace',
      danger: true,
    });

    if (!mayProceed) return;
    resetWorkspace();
    setRuntimeDiagnostics([]);
    setValidationTouched(false);
    setStatusMessage('Workspace reset.');
  };

  const handleEngineChange = (engine: TransformEngineId) => {
    setState((current) => ({ ...current, selectedEngine: engine }));
  };

  const handlePrettifyOutputAfterTransformChange = (enabled: boolean) => {
    setState((current) => ({
      ...current,
      options: {
        ...current.options,
        prettifyOutputAfterTransform: enabled,
      },
    }));
  };

  const handleAskBeforeOverwritingOutputChange = (enabled: boolean) => {
    setState((current) => ({
      ...current,
      options: {
        ...current.options,
        askBeforeOverwritingOutput: enabled,
      },
    }));
  };
  const traceItems = useMemo(() => {
    if (!state.xmlOutput.text.trim()) return [];
    return createApproximateTrace(
      state.xmlInput.text,
      state.xsltCode.text,
      state.xmlOutput.text
    );
  }, [state.xmlInput.text, state.xsltCode.text, state.xmlOutput.text]);

  const displayedRuntimeDiagnostics = validationTouched
    ? runtimeDiagnostics
    : [];

  useEffect(() => {
    const handleGlobalKeyDown = (event: KeyboardEvent) => {
      if (event.defaultPrevented || event.repeat || event.isComposing) {
        return;
      }

      if (event.key === 'F1') {
        event.preventDefault();
        event.stopPropagation();
        handleOpenHelp();
        return;
      }

      const isApplyShortcut =
        event.key === 'Enter' &&
        event.ctrlKey &&
        !event.altKey &&
        !event.shiftKey &&
        !event.metaKey;

      if (isApplyShortcut) {
        event.preventDefault();
        event.stopPropagation();

        if (!busy) {
          void handleApplyTransformation();
        }
      }
    };

    window.addEventListener('keydown', handleGlobalKeyDown, true);

    return () => {
      window.removeEventListener('keydown', handleGlobalKeyDown, true);
    };
  }, [busy, handleApplyTransformation, handleOpenHelp]);

  return (
    <Layout onOpenHelp={() => handleOpenHelp()}>
      <div className="workspace-grid">
        <EditorPanel
          ref={xmlInputEditorRef}
          title="XML input"
          value={state.xmlInput.text}
          fileName={state.xmlInput.fileName}
          dirty={state.xmlInput.dirty}
          diagnostics={xmlDiagnostics}
          onChange={(value) => updateDocumentText('xmlInput', value)}
          onOpen={() => void handleOpenFile('xmlInput')}
          onSave={() => void handleSaveFile('xmlInput')}
          onUndo={() => xmlInputEditorRef.current?.undo()}
          onRedo={() => xmlInputEditorRef.current?.redo()}
          onCopy={() => void xmlInputEditorRef.current?.copySelection()}
          onCut={() => void xmlInputEditorRef.current?.cutSelection()}
          onPaste={() => void xmlInputEditorRef.current?.pasteText()}
          onPrettify={() => void handleFormat('xmlInput')}
          placeholderText="Paste or open XML here."
        />

        <EditorPanel
          ref={xsltEditorRef}
          title="XSL transformation code"
          value={state.xsltCode.text}
          fileName={state.xsltCode.fileName}
          dirty={state.xsltCode.dirty}
          diagnostics={xsltDiagnostics}
          onChange={(value) => updateDocumentText('xsltCode', value)}
          onOpen={() => void handleOpenFile('xsltCode')}
          onSave={() => void handleSaveFile('xsltCode')}
          onUndo={() => xsltEditorRef.current?.undo()}
          onRedo={() => xsltEditorRef.current?.redo()}
          onCopy={() => void xsltEditorRef.current?.copySelection()}
          onCut={() => void xsltEditorRef.current?.cutSelection()}
          onPaste={() => void xsltEditorRef.current?.pasteText()}
          onPrettify={() => void handleFormat('xsltCode')}
          placeholderText="Write or open XSLT here."
          actions={
            <SnippetToolbox
              onInsertSnippet={(snippet) =>
                xsltEditorRef.current?.insertText(snippet)
              }
            />
          }
        />

        <EditorPanel
          ref={outputEditorRef}
          title="XML output"
          value={state.xmlOutput.text}
          fileName={state.xmlOutput.fileName}
          dirty={state.xmlOutput.dirty}
          readOnly={false}
          diagnostics={outputDiagnostics}
          onChange={(value) => updateDocumentText('xmlOutput', value)}
          onOpen={null}
          onSave={() => void handleSaveFile('xmlOutput')}
          onUndo={() => outputEditorRef.current?.undo()}
          onRedo={() => outputEditorRef.current?.redo()}
          onCopy={() => void outputEditorRef.current?.copySelection()}
          onCut={() => void outputEditorRef.current?.cutSelection()}
          onPaste={() => void outputEditorRef.current?.pasteText()}
          onPrettify={() => void handleFormat('xmlOutput')}
          placeholderText="Transformation output appears here."
        />
      </div>

      <Toolbar
        selectedEngine={state.selectedEngine}
        onEngineChange={handleEngineChange}
        onApplyTransformation={() => void handleApplyTransformation()}
        onMoveOutputToInput={() => void handleMoveOutputToInput()}
        onValidate={handleValidate}
        onReset={() => void handleReset()}
        prettifyOutputAfterTransform={state.options.prettifyOutputAfterTransform}
        onPrettifyOutputAfterTransformChange={
          handlePrettifyOutputAfterTransformChange
        }
        askBeforeOverwritingOutput={state.options.askBeforeOverwritingOutput}
        onAskBeforeOverwritingOutputChange={
          handleAskBeforeOverwritingOutputChange
        }
        busy={busy}
      />

      {statusMessage && <div className="status-banner">{statusMessage}</div>}

      {displayedRuntimeDiagnostics.length > 0 && (
        <section className="card runtime-diagnostics">
          <h2>Validation / runtime messages</h2>
          <ul className="diagnostics-list">
            {displayedRuntimeDiagnostics.map((diagnostic, index) => (
              <li
                key={`${diagnostic.source}-${diagnostic.message}-${index}`}
                className={`diagnostic diagnostic-${diagnostic.severity}`}
              >
                <strong>{diagnostic.source}</strong>
                <span>{diagnostic.message}</span>
              </li>
            ))}
          </ul>
        </section>
      )}


      <section className="trace-panel card">
        <div className="trace-header">
          <div>
            <h2>Explain transformation</h2>
            <p>
              MVP approximation: result nodes are compared with simple
              stylesheet template matches. Full execution tracing requires an
              instrumented engine later.
            </p>
          </div>
          {state.lastTransformation && (
            <small>
              Last run:{' '}
              {new Date(
                state.lastTransformation.transformedAt
              ).toLocaleString()}
            </small>
          )}
        </div>

        {traceItems.length === 0 ? (
          <p className="diagnostics-empty">
            Run a transformation to see approximate result explanations.
          </p>
        ) : (
          <div className="trace-table-wrap">
            <table className="trace-table">
              <thead>
                <tr>
                  <th>Result</th>
                  <th>Likely source</th>
                  <th>Likely template</th>
                  <th>Confidence</th>
                </tr>
              </thead>
              <tbody>
                {traceItems.map((item) => (
                  <tr key={item.resultPath}>
                    <td>
                      <code>{item.resultPath}</code>
                    </td>
                    <td>
                      <code>{item.likelySourcePath ?? '—'}</code>
                    </td>
                    <td>
                      <code>{item.likelyTemplate ?? '—'}</code>
                    </td>
                    <td>{item.confidence}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
      </section>

      <ActionDialog
        open={Boolean(dialog)}
        title={dialog?.title ?? ''}
        actions={dialog?.actions ?? []}
        onClose={closeDialog}
      >
        {dialog?.content}
      </ActionDialog>

      {helpOpen && (
        <HelpDialog
          open={helpOpen}
          onClose={handleCloseHelp}
        />
      )}
    </Layout>
  );
};

function tryFormatXml(text: string): string {
  try {
    return text.trim() ? formatXml(text) : text;
  } catch {
    return text;
  }
}

function defaultFileName(kind: WorkbenchDocumentKind): string {
  switch (kind) {
    case 'xmlInput':
      return 'input.xml';
    case 'xsltCode':
      return 'transform.xsl';
    case 'xmlOutput':
      return 'output.xml';
  }
}

function isAbortError(error: unknown): boolean {
  return error instanceof DOMException && error.name === 'AbortError';
}

export default App;
