import { useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Modal } from '../../ui/Modal';
import { useEditor } from '../context/EditorContext';
import {
  exportKonvaStageToDataUrl,
  exportThreeCanvasToDataUrl,
  downloadDataUrl,
  downloadBlob,
  createRoomPdf,
  sanitizeFilename,
  computeFitView,
  waitFrames,
} from './exportUtils';
import { exportRoomToJson } from './roomFormat';
import { wallDirectionLabel } from '../utils/projectionMapping';
import type Konva from 'konva';
import type { CameraPreset } from '../three/CameraControls';
import styles from './export-dialog.module.css';

export type ExportFormat = 'png' | 'pdf' | 'json';
export type ExportScope = 'current-view' | 'default-view' | 'room';

interface ExportDialogProps {
  readonly open: boolean;
  readonly onClose: () => void;
  readonly mainStageRef: React.RefObject<Konva.Stage | null>;
  readonly projectionStageRefs: React.RefObject<Map<string, Konva.Stage>>;
  /** Per-wall stages that are rendered off-screen at export resolution. */
  readonly exportProjectionStageRefs: React.RefObject<Map<string, Konva.Stage>>;
  readonly threeCanvasRef: React.RefObject<HTMLCanvasElement | null>;
  readonly preset3DRef: React.RefObject<((preset: CameraPreset) => void) | null>;
  readonly canvasSize: { readonly width: number; readonly height: number } | null;
  readonly is3DView: boolean;
  readonly viewMode?: '2d' | '3d' | 'projections';
}

export function ExportDialog({
  open,
  onClose,
  mainStageRef,
  projectionStageRefs,
  exportProjectionStageRefs,
  threeCanvasRef,
  preset3DRef,
  canvasSize,
  is3DView,
  viewMode = '2d',
}: ExportDialogProps) {
  const { t } = useTranslation();
  const { state, dispatch } = useEditor();
  const { room, walls } = state;

  const [format, setFormat] = useState<ExportFormat>('png');
  const [scope, setScope] = useState<ExportScope>('default-view');
  const [includeGrid, setIncludeGrid] = useState(false);
  const [pixelRatio, setPixelRatio] = useState(2);
  const [isExporting, setIsExporting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  /**
   * Build the set of points used to compute the 2D fit view. Includes the
   * room polygon plus every annotation's anchor point (with a small buffer
   * for its text) so annotations placed outside the room boundary stay
   * visible in exported views.
   */
  const buildFitPoints = useCallback(() => {
    // Include annotation anchor points so annotations placed outside the
    // room polygon don't get clipped. Skip annotations attached to
    // electrical items — they belong to the wall-projection view and are
    // hidden on the 2D view, so we don't need to reserve space for them.
    const electricalIds = new Set(state.electricalItems.map((e) => e.id));
    const pts: { x: number; y: number }[] = state.room.shape.map((p) => ({ x: p.x, y: p.y }));
    for (const ann of state.annotations) {
      if (ann.attachedToId && electricalIds.has(ann.attachedToId)) continue;
      pts.push({ x: ann.x, y: ann.y });
    }
    return pts;
  }, [state.room.shape, state.annotations, state.electricalItems]);

  /**
   * Capture the main 2D Konva stage with the fit-to-room view applied.
   * Saves the user's current zoom/pan, dispatches the computed fit view,
   * waits for layers to re-render, captures, then restores.
   */
  const captureMainStageAtFit = useCallback(
    async (
      renderGrid: boolean,
      pixelRatioOverride?: number,
      /** When true, force the capture stage to an A4-landscape-matched
       *  aspect (1.517:1) so the exported image fills the PDF image box
       *  without height-cropping or leaving empty bands above/below. */
      matchPdfAspect = false,
    ): Promise<string | null> => {
      const stage = mainStageRef.current;
      if (!stage) return null;
      if (state.room.shape.length === 0) return null;

      const stageW = stage.width();
      const stageH = stage.height();
      // PDF page image box ≈ 273 × 180 mm on A4 landscape → 1.517:1.
      // Match it to avoid leaving large empty bands in the PDF.
      const PDF_W = 1820;
      const PDF_H = 1200;
      // Use a synthetic size when the stage is hidden (0×0) or very small.
      const fallbackW = matchPdfAspect ? PDF_W : 1600;
      const fallbackH = matchPdfAspect ? PDF_H : 1200;
      const targetW = matchPdfAspect
        ? PDF_W
        : stageW > 100
        ? stageW
        : canvasSize?.width && canvasSize.width > 100
        ? canvasSize.width
        : fallbackW;
      const targetH = matchPdfAspect
        ? PDF_H
        : stageH > 100
        ? stageH
        : canvasSize?.height && canvasSize.height > 100
        ? canvasSize.height
        : fallbackH;

      let resized = false;
      const container = stage.container();
      const parent = container?.parentElement;
      const origParentStyle = parent?.getAttribute('style') ?? '';
      if (matchPdfAspect || stageW === 0 || stageH === 0) {
        resized = true;
        if (parent) {
          parent.setAttribute(
            'style',
            `position:absolute;width:${targetW}px;height:${targetH}px;overflow:hidden;pointer-events:none;opacity:0;`,
          );
        }
        stage.width(targetW);
        stage.height(targetH);
      }

      const fit = computeFitView(buildFitPoints(), targetW, targetH);
      if (!fit) {
        if (resized) {
          stage.width(stageW);
          stage.height(stageH);
          if (parent) parent.setAttribute('style', origParentStyle);
        }
        return null;
      }

      const savedZoom = state.zoom;
      const savedOffset = state.panOffset;
      dispatch({ type: 'SET_VIEW', zoom: fit.zoom, offset: { x: fit.panX, y: fit.panY } });
      await waitFrames(3);
      stage.batchDraw();

      const dataUrl = exportKonvaStageToDataUrl(stage, {
        pixelRatio: pixelRatioOverride ?? pixelRatio,
        includeGrid: renderGrid,
      });

      dispatch({ type: 'SET_VIEW', zoom: savedZoom, offset: savedOffset });
      if (resized) {
        stage.width(stageW);
        stage.height(stageH);
        if (parent) parent.setAttribute('style', origParentStyle);
      }
      return dataUrl;
    },
    [mainStageRef, canvasSize, state.room.shape, state.zoom, state.panOffset, dispatch, pixelRatio, buildFitPoints],
  );

  /**
   * Capture the 3D canvas with a tight top-down framing. `tightFraming`
   * picks the export-only preset that fills the frame with the room instead
   * of the loose default birds-eye used by the interactive view.
   * `jpegForPdf` encodes as JPEG — photographic 3D renders compress an
   * order of magnitude smaller than PNG with little visible quality loss.
   */
  const capture3DBirdsEye = useCallback(
    async (opts: { tightFraming?: boolean; jpegForPdf?: boolean } = {}): Promise<string | null> => {
      const trigger = preset3DRef.current;
      if (trigger) {
        trigger(opts.tightFraming ? 'birds-eye-export' : 'birds-eye');
        // Wait long enough for the preset to settle and one full render pass.
        await waitFrames(12);
      }
      let canvas = threeCanvasRef.current;
      if (!canvas) {
        canvas = document.querySelector('canvas[data-engine]') as HTMLCanvasElement | null;
      }
      if (!canvas || canvas.width === 0 || canvas.height === 0) return null;
      return exportThreeCanvasToDataUrl(canvas, opts.jpegForPdf ? 'image/jpeg' : 'image/png', 0.85);
    },
    [preset3DRef, threeCanvasRef],
  );

  /**
   * Capture every wall projection as a high-resolution image. Uses the
   * dedicated hidden per-wall renderer (one stage per wall) so every wall is
   * always present even when the visible projection panel is in tab mode.
   * Waits briefly on the first call so the hidden render pass can settle.
   */
  const captureAllProjections = useCallback(
    async (pixelRatioOverride?: number): Promise<{ label: string; dataUrl: string }[]> => {
      const out: { label: string; dataUrl: string }[] = [];
      const projMap = exportProjectionStageRefs.current;
      if (!projMap) return out;

      // The hidden export stages mount only while the dialog is open — give
      // them a few frames to register their refs and draw their first frame.
      for (let attempt = 0; attempt < 20 && projMap.size < walls.length; attempt += 1) {
        await waitFrames(2);
      }
      await waitFrames(2);

      const ratio = pixelRatioOverride ?? pixelRatio;
      for (const wall of walls) {
        const projStage = projMap.get(wall.id);
        if (projStage && projStage.width() > 0 && projStage.height() > 0) {
          projStage.batchDraw();
          const label = wallDirectionLabel(wall);
          const dataUrl = exportKonvaStageToDataUrl(projStage, { pixelRatio: ratio });
          out.push({ label, dataUrl });
        }
      }
      return out;
    },
    [exportProjectionStageRefs, walls, pixelRatio],
  );

  const handleExport = useCallback(async () => {
    setIsExporting(true);
    setError(null);

    try {
      const baseName = sanitizeFilename(room.name || 'room');

      if (format === 'png') {
        if (scope === 'current-view') {
          if (viewMode === '3d') {
            let canvas = threeCanvasRef.current;
            if (!canvas) {
              canvas = document.querySelector('canvas[data-engine]') as HTMLCanvasElement | null;
            }
            if (!canvas) {
              setError(t('export.error.3dNotAvailable'));
              return;
            }
            const dataUrl = exportThreeCanvasToDataUrl(canvas);
            downloadDataUrl(dataUrl, `${baseName}_3d.png`);
          } else if (viewMode === 'projections') {
            const projMap = projectionStageRefs.current;
            if (projMap && projMap.size > 0) {
              for (const wall of walls) {
                const projStage = projMap.get(wall.id);
                if (projStage && projStage.width() > 0) {
                  const label = wallDirectionLabel(wall);
                  const safeName = sanitizeFilename(label);
                  const dataUrl = exportKonvaStageToDataUrl(projStage, { pixelRatio });
                  downloadDataUrl(dataUrl, `${baseName}_wall_${safeName}.png`);
                }
              }
            } else {
              setError(t('export.error.2dNotAvailable'));
            }
          } else {
            const stage = mainStageRef.current;
            if (!stage || stage.width() === 0) {
              setError(t('export.error.2dNotAvailable'));
              return;
            }
            const dataUrl = exportKonvaStageToDataUrl(stage, { pixelRatio, includeGrid });
            downloadDataUrl(dataUrl, `${baseName}_2d.png`);
          }
        } else if (scope === 'default-view') {
          // Default view: fit-to-room for 2D, birds-eye for 3D,
          // all projections (always fit) for projections mode.
          if (viewMode === '3d') {
            const dataUrl = await capture3DBirdsEye();
            if (!dataUrl) {
              setError(t('export.error.3dNotAvailable'));
              return;
            }
            downloadDataUrl(dataUrl, `${baseName}_3d_top.png`);
          } else if (viewMode === 'projections') {
            const projs = await captureAllProjections();
            if (projs.length === 0) {
              setError(t('export.error.2dNotAvailable'));
              return;
            }
            for (const p of projs) {
              const safeName = sanitizeFilename(p.label);
              downloadDataUrl(p.dataUrl, `${baseName}_wall_${safeName}.png`);
            }
          } else {
            const dataUrl = await captureMainStageAtFit(includeGrid);
            if (!dataUrl) {
              setError(t('export.error.2dNotAvailable'));
              return;
            }
            downloadDataUrl(dataUrl, `${baseName}_2d.png`);
          }
        } else {
          // All room views: 2D default + all projections
          const dataUrl2d = await captureMainStageAtFit(includeGrid);
          if (dataUrl2d) {
            downloadDataUrl(dataUrl2d, `${baseName}_2d.png`);
          }
          const projs = await captureAllProjections();
          for (const p of projs) {
            const safeName = sanitizeFilename(p.label);
            downloadDataUrl(p.dataUrl, `${baseName}_wall_${safeName}.png`);
          }
        }
      } else if (format === 'json') {
        const jsonStr = exportRoomToJson(state);
        const blob = new Blob([jsonStr], { type: 'application/json' });
        downloadBlob(blob, `${baseName}.json`);
      } else {
        // PDF: always capture 3D (tight top-down) + 2D default + all projections.
        // Room3DView lazy-loads when the export dialog opens, so the preset
        // trigger ref may not exist yet. Poll for it, then snap the camera
        // to the export-only tight preset.
        for (let i = 0; i < 60 && !preset3DRef.current; i += 1) {
          await waitFrames(2);
        }
        preset3DRef.current?.('birds-eye-export');

        // Source stages are already rendered at 400 px/m — capturing at
        // pixelRatio=1 still produces ~300-350 DPI on an A4 page and keeps
        // the PDF file size an order of magnitude smaller than pixelRatio=2.
        const PDF_PIXEL_RATIO = 1;
        const topDownDataUrl = await captureMainStageAtFit(includeGrid, PDF_PIXEL_RATIO, true);
        const projectionDataUrls = await captureAllProjections(PDF_PIXEL_RATIO);

        // Re-trigger the preset after other captures settle, and poll for a
        // valid 3D canvas (it may still be mounting on first export).
        preset3DRef.current?.('birds-eye-export');
        await waitFrames(20);
        let canvas3d: HTMLCanvasElement | null = null;
        for (let i = 0; i < 60; i += 1) {
          canvas3d = threeCanvasRef.current;
          if (!canvas3d) {
            const host = document.querySelector('[data-export-3d-host]');
            canvas3d = host ? (host.querySelector('canvas') as HTMLCanvasElement | null) : null;
          }
          if (canvas3d && canvas3d.width > 0 && canvas3d.height > 0) break;
          await waitFrames(2);
        }
        const view3dDataUrl =
          canvas3d && canvas3d.width > 0 && canvas3d.height > 0
            ? exportThreeCanvasToDataUrl(canvas3d, 'image/jpeg', 0.95)
            : null;

        const pdf = await createRoomPdf(room.name, topDownDataUrl, projectionDataUrls, view3dDataUrl);
        const blob = pdf.output('blob');
        downloadBlob(blob, `${baseName}.pdf`);
      }

      onClose();
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : t('export.error.failed');
      setError(message);
    } finally {
      setIsExporting(false);
    }
  }, [
    format,
    scope,
    includeGrid,
    pixelRatio,
    viewMode,
    state,
    room,
    walls,
    mainStageRef,
    projectionStageRefs,
    threeCanvasRef,
    preset3DRef,
    captureMainStageAtFit,
    capture3DBirdsEye,
    captureAllProjections,
    onClose,
    t,
  ]);

  const footer = (
    <div className={styles.footerButtons}>
      <button className={styles.cancelBtn} onClick={onClose} type="button">
        {t('common.cancel')}
      </button>
      <button
        className={styles.exportBtn}
        onClick={handleExport}
        disabled={isExporting}
        type="button"
      >
        {isExporting ? t('export.exporting') : t('export.exportBtn')}
      </button>
    </div>
  );

  return (
    <Modal open={open} onClose={onClose} title={t('export.title')} footer={footer}>
      <div className={styles.content}>
        {/* Format */}
        <div className={styles.fieldGroup}>
          <span className={styles.fieldLabel}>{t('export.format')}</span>
          <div className={styles.radioGroup}>
            <label className={styles.radioLabel}>
              <input
                type="radio"
                name="format"
                value="png"
                checked={format === 'png'}
                onChange={() => setFormat('png')}
              />
              {t('export.png')}
            </label>
            <label className={styles.radioLabel}>
              <input
                type="radio"
                name="format"
                value="pdf"
                checked={format === 'pdf'}
                onChange={() => setFormat('pdf')}
              />
              {t('export.pdf')}
            </label>
            <label className={styles.radioLabel}>
              <input
                type="radio"
                name="format"
                value="json"
                checked={format === 'json'}
                onChange={() => setFormat('json')}
              />
              {t('export.json')}
            </label>
          </div>
        </div>

        {/* Scope (only for PNG) */}
        {format === 'png' && (
          <div className={styles.fieldGroup}>
            <span className={styles.fieldLabel}>{t('export.scope')}</span>
            <div className={styles.radioGroup}>
              <label className={styles.radioLabel}>
                <input
                  type="radio"
                  name="scope"
                  value="current-view"
                  checked={scope === 'current-view'}
                  onChange={() => setScope('current-view')}
                />
                {t('export.currentView')}
              </label>
              <label className={styles.radioLabel}>
                <input
                  type="radio"
                  name="scope"
                  value="default-view"
                  checked={scope === 'default-view'}
                  onChange={() => setScope('default-view')}
                />
                {t('export.defaultView')}
              </label>
              <label className={styles.radioLabel}>
                <input
                  type="radio"
                  name="scope"
                  value="room"
                  checked={scope === 'room'}
                  onChange={() => setScope('room')}
                />
                {t('export.allRoomViews')}
              </label>
            </div>
          </div>
        )}

        {format === 'pdf' && (
          <div className={styles.fieldGroup}>
            <span className={styles.fieldLabel}>{t('export.pdfIncludesLabel')}</span>
            <ul className={styles.pdfIncludesList}>
              <li>{t('export.pdfIncludes3DTop')}</li>
              <li>{t('export.pdfIncludes2DDefault')}</li>
              <li>{t('export.pdfIncludesProjections')}</li>
            </ul>
          </div>
        )}

        {/* Options */}
        <div className={styles.fieldGroup}>
          <span className={styles.fieldLabel}>{t('export.options')}</span>
          {!is3DView && (
            <label className={styles.checkboxLabel}>
              <input
                type="checkbox"
                checked={includeGrid}
                onChange={(e) => setIncludeGrid(e.target.checked)}
              />
              {t('export.includeGrid')}
            </label>
          )}
          <label className={styles.checkboxLabel}>
            {t('export.scaleFactor')}
            <input
              type="number"
              className={styles.scaleInput}
              min={1}
              max={4}
              step={1}
              value={pixelRatio}
              onChange={(e) => setPixelRatio(Number(e.target.value))}
            />
            x
          </label>
        </div>

        {error && <div className={styles.errorMessage}>{error}</div>}
        {isExporting && <div className={styles.statusMessage}>{t('export.generating')}</div>}
      </div>
    </Modal>
  );
}
