
import { useEffect, useMemo, useState } from 'preact/hooks';
import { registerModule, type AsukaModuleComponent } from '../../core/modules';
import { listRenderEngineObjects, type AsukaObjectSummary } from '../../core/renderEngine';
import { getI18n } from '../../core/i18n';
import { useT } from '../../hooks/useT';
import { useAsukaStore } from '../../store/asukaStore';
import { DEFAULT_PERMISSIONS, getEffectivePermissions } from '../../core/permissions';
import { onAsukaEvent } from '../../core/events';
import { getCommercialQuotaState } from '../../core/license/commercial-limits';
import { Card, CardContent } from '../core/Card';
import { Button } from '../core/Button';
import {
  DownloadHeader,
  DownloadIntroCard,
  DownloadMessages,
  ExportFormatFields,
  ExportTargetFields,
  PdfOptionsPanel,
  RasterOptionsFields,
  ServerHdOptionPanel,
  ZipOptionField,
} from '../domain/download';

type Scope = 'Canvas' | 'Views' | 'Drawing areas' | 'Layers and elements';
type Format = 'png' | 'jpeg' | 'jpg' | 'webp' | 'svg' | 'pdf' | 'json';
type ExportMode = 'current' | 'single' | 'all';

type ViewItem = { id: string; name?: string; width?: number; height?: number; dpi?: number; unit?: string };

type DrawingZoneItem = { id: string; name?: string; shape?: string; zone: any };

function compactLabelPart(value: unknown, fallback = '') {
  const text = typeof value === 'string' ? value.trim() : String(value ?? '').trim();
  return text || fallback;
}

function describeObject(obj: AsukaObjectSummary | any, index: number) {
  const metadata = (obj as any)?.metadata ?? {};
  const id = String((obj as any)?.id ?? `obj-${index + 1}`);
  const explicitName = compactLabelPart((obj as any)?.label ?? '', '');
  const source = metadata?.source && typeof metadata.source === 'object' ? metadata.source : {};
  const text = compactLabelPart(typeof source?.text === 'string' ? source.text.slice(0, 32) : '', '');
  const elementName = compactLabelPart(metadata?.elementName ?? '', '');
  const type = compactLabelPart((obj as any)?.kind ?? metadata?.type ?? 'object', 'object');
  const itemGroup = compactLabelPart(metadata?.itemGroup ?? '', '');
  const kind = elementName || metadata?.category === 'product' ? 'Element' : 'Layer';
  const mainName = explicitName || elementName || text || type;
  const details = [itemGroup, type].filter(Boolean).join(' · ');
  const label = details ? `${kind} · ${mainName} (${id}) · ${details}` : `${kind} · ${mainName} (${id})`;
  return { id, label, summary: obj };
}

function describeZone(zone: any, index: number): DrawingZoneItem & { label: string } {
  const id = String(zone?.id ?? `drawing-area-${index + 1}`);
  const shape = compactLabelPart(zone?.shape ?? 'rect', 'rect');
  const explicitName = compactLabelPart(zone?.name ?? zone?.label ?? '', '');
  const baseName = explicitName || `Drawing area ${index + 1}`;
  const label = `${baseName} (${id}) · ${shape}`;
  return { id, name: baseName, shape, zone, label };
}

function describeView(view: ViewItem, index: number) {
  const id = String(view?.id ?? `view-${index + 1}`);
  const baseName = compactLabelPart(view?.name ?? '', `View ${index + 1}`);
  return { value: id, label: `${baseName} (${id})` };
}

function sanitizeDownloadName(value: unknown, fallback = 'download') {
  const text = String(value ?? '').trim() || fallback;
  return text.replace(/[\\/:*?"<>|]+/g, '-').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || fallback;
}

function saveBlobAs(blob: Blob, filename: string) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  a.remove();
  window.setTimeout(() => { try { URL.revokeObjectURL(url); } catch {} }, 0);
}


function normalizePositiveNumber(value: unknown): number | null {
  if (typeof value === 'number' && Number.isFinite(value) && value > 0) return value;
  if (typeof value === 'string' && value.trim()) {
    const parsed = Number(value.replace(',', '.'));
    return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
  }
  return null;
}

function downloadUnitToPx(value: number | null, unit: 'px' | 'cm' | 'mm' | 'in'): number | null {
  if (!Number.isFinite(Number(value)) || Number(value) <= 0) return null;
  const n = Number(value);
  if (unit === 'in') return n * 96;
  if (unit === 'cm') return (n * 96) / 2.54;
  if (unit === 'mm') return (n * 96) / 25.4;
  return n;
}

function roundOutputPixel(value: number): number {
  return Math.max(1, Math.round(Number(value) || 1));
}

const DownloadModule: AsukaModuleComponent = ({ context }) => {
  const permissionsState = useAsukaStore((s: any) => ({ permissions: s.permissions ?? DEFAULT_PERMISSIONS, permissionsByView: s.permissionsByView ?? {}, activeViewId: s.activeViewId ?? '' }));
  const permissions = getEffectivePermissions(permissionsState.permissions, permissionsState.permissionsByView?.[permissionsState.activeViewId] ?? null);
  const t = useT();
  const engine = context.getEngine?.() ?? null;
  const activeRenderEngineKind = useAsukaStore((state: any) => state.activeRenderEngineKind ?? 'fabric');
  const canUseLicenseFeature = useAsukaStore((state: any) => state.canUseLicenseFeature);
  const license = useAsukaStore((state: any) => state.license ?? {});
  const licenseResolutionTick = useAsukaStore((state: any) => state.licenseResolution?.resolvedAt ?? '');
  const views = useAsukaStore((state: any) => (state.views ?? []) as ViewItem[]);
  const activeViewId = useAsukaStore((state: any) => String(state.activeViewId ?? ''));

  const [scope, setScope] = useState<Scope>('Canvas');
  const [mode, setMode] = useState<ExportMode>('current');
  const [format, setFormat] = useState<Format>('png');
  const [name, setName] = useState('download');
  const [target, setTarget] = useState('');
  const [dpiPreset, setDpiPreset] = useState<'screen' | 'print'>('screen');
  const [dpi, setDpi] = useState(96);
  const [width, setWidth] = useState('');
  const [unit, setUnit] = useState<'px' | 'cm' | 'mm' | 'in'>('px');
  const [cropMark, setCropMark] = useState(false);
  const [zipAll, setZipAll] = useState(true);
  const [marksOffset, setMarksOffset] = useState('0.125');
  const [marksBleed, setMarksBleed] = useState('0.125');
  const [marksMargin, setMarksMargin] = useState('0.2');
  const [marksUnit, setMarksUnit] = useState<'px' | 'cm' | 'mm' | 'in'>('in');
  const [busy, setBusy] = useState(false);
  const [exportError, setExportError] = useState<string | null>(null);
  const [contentTick, setContentTick] = useState(0);
  const [pdfReadyTick, setPdfReadyTick] = useState(0);
  const [useServerHd, setUseServerHd] = useState(false);

  useEffect(() => {
    const bump = () => setContentTick((v) => v + 1);
    const disposers: Array<() => void> = [];
    disposers.push(engine?.events.subscribe((event) => {
      if (event.type !== 'viewport') bump();
    }) ?? (() => {}));
    disposers.push(onAsukaEvent('after:views.switch', bump));
    disposers.push(onAsukaEvent('after:layout.openModule', bump));

    return () => {
      disposers.forEach((dispose) => { try { dispose?.(); } catch {} });
    };
  }, [engine, activeViewId]);

  void licenseResolutionTick;
  const canUseHdExport = canUseLicenseFeature?.('hdExport') !== false;
  const canUseServerHdExport = canUseLicenseFeature?.('serverHdExport') === true;
  const storeExportRuntime = useAsukaStore((state: any) => state.exportRuntime ?? null);
  const exportRuntime = storeExportRuntime;
  const serverHdAvailable = canUseHdExport && canUseServerHdExport && !!exportRuntime?.serverHd?.export;
  const canUseBatchExport = canUseLicenseFeature?.('batchExport') !== false;
  const exportQuota = getCommercialQuotaState(license as any, 'exports');
  const exportQuotaReached = exportQuota.reached;

  useEffect(() => {
    if (canUseHdExport) return;
    if (dpiPreset !== 'screen') setDpiPreset('screen');
    if (dpi > 96) setDpi(96);
  }, [canUseHdExport, dpiPreset, dpi]);

  const zones = useMemo(() => {
    void activeRenderEngineKind;
    void activeViewId;
    void contentTick;
    try {
      const list = engine?.areas?.list() ?? [];
      return (list as any[]).map((z, index) => describeZone(z, index));
    } catch {
      return [];
    }
  }, [engine, activeRenderEngineKind, activeViewId, contentTick]);

  const objects = useMemo(() => {
    void activeViewId;
    void contentTick;
    try {
      const list = listRenderEngineObjects(engine, { includeTechnical: false, source: 'download-module' }) as any[];
      return list.map((obj, index) => describeObject(obj, index));
    } catch {
      return [];
    }
  }, [engine, activeViewId, contentTick]);

  const resolvedView = useMemo(() => views.find((v) => String(v?.id ?? '') === String(target)) ?? null, [views, target]);
  const resolvedTarget = useMemo(() => {
    if (scope === 'Drawing areas') {
      const zone = zones.find((z) => z.id === target)?.zone ?? null;
      return zone ? { __asukaDownloadKind: 'zone', zoneId: String(zone.id), id: String(zone.id) } : null;
    }
    if (scope === 'Layers and elements') {
      const objectId = objects.find((o) => o.id === target)?.id ?? '';
      return objectId ? { __asukaDownloadKind: 'object', objectId, id: objectId } : null;
    }
    if (scope === 'Views') {
      return resolvedView ? { __asukaDownloadKind: 'view', viewId: String(resolvedView.id) } : null;
    }
    return null;
  }, [scope, target, zones, objects, resolvedView]);

  const targetOptions = useMemo(() => {
    if (scope === 'Views') return views.map((v, index) => describeView(v, index));
    if (scope === 'Drawing areas') return zones.map((z) => ({ value: z.id, label: z.label }));
    if (scope === 'Layers and elements') return objects.map((o) => ({ value: o.id, label: o.label }));
    return [{ value: '', label: t('download.currentView', 'Current view') }];
  }, [scope, views, zones, objects, t]);

  const availableModes = useMemo(() => {
    const items = scope === 'Canvas'
      ? [{ value: 'current', label: t('download.currentView', 'Current view') }]
      : scope === 'Views'
        ? [
            { value: 'current', label: t('download.currentView', 'Current view') },
            { value: 'single', label: t('download.singleView', 'Single view') },
            { value: 'all', label: t('download.allViews', 'All views') },
          ]
        : scope === 'Drawing areas'
          ? [
              { value: 'single', label: t('download.singleArea', 'Single area') },
              { value: 'all', label: t('download.allAreas', 'All drawing areas') },
            ]
          : [
              { value: 'single', label: t('download.singleObject', 'Single object') },
              { value: 'all', label: t('download.allObjects', 'All layers and elements') },
            ];

    return items.filter((item) => canUseBatchExport || item.value !== 'all');
  }, [scope, t, canUseBatchExport]);


  const pdfAvailable = useMemo(() => {
    void pdfReadyTick;
    try {
      return !!engine?.exports.isFormatAvailable('pdf');
    } catch {
      return false;
    }
  }, [engine, pdfReadyTick]);

  const canLoadPdf = useMemo(() => {
    try {
      return !!engine?.exports.canLoadSupport('pdf');
    } catch {
      return false;
    }
  }, [engine]);

  const canEnsurePdf = typeof engine?.exports.ensureSupport === 'function';
  const canOfferPdf = canUseHdExport && (pdfAvailable || canLoadPdf || canEnsurePdf);

  const formatOptions = useMemo(() => {
    if (activeRenderEngineKind === '3d') {
      return [
        { value: 'png', label: 'PNG preview' },
        { value: 'json', label: '3D scene JSON' },
      ] as Array<{ value: Format; label: string }>;
    }
    const items = [
      { value: 'png', label: 'PNG' },
      { value: 'jpeg', label: 'JPEG' },
      { value: 'webp', label: 'WEBP' },
      { value: 'svg', label: 'SVG' },
      { value: 'json', label: 'JSON' },
    ] as Array<{ value: Format; label: string }>;
    if (canOfferPdf) items.splice(4, 0, { value: 'pdf', label: 'PDF' });
    return items;
  }, [activeRenderEngineKind, canOfferPdf]);

  useEffect(() => {
    if (format !== 'pdf') return;
    if (canOfferPdf) return;
    setFormat('png');
  }, [format, canOfferPdf]);

  useEffect(() => {
    if (format !== 'json') return;
    if (useServerHd) setUseServerHd(false);
  }, [format, useServerHd]);

  useEffect(() => {
    if (activeRenderEngineKind !== '3d') return;
    if (format !== 'png' && format !== 'json') setFormat('png');
    if (scope !== 'Canvas' && !(scope === 'Views' && (mode === 'current' || String(target) === String(activeViewId)))) {
      setScope('Canvas');
      setMode('current');
      setTarget('');
    }
  }, [activeRenderEngineKind, activeViewId, format, mode, scope, target]);

  const ensurePdfExportReady = async () => {
    if (format !== 'pdf') return true;
    if (!canUseHdExport) return false;
    if (pdfAvailable) return true;
    if (!canLoadPdf && !canEnsurePdf) return false;
    await engine?.exports.ensureSupport('pdf', { autoLoad: true });
    setPdfReadyTick((v) => v + 1);
    try {
      return !!engine?.exports.isFormatAvailable('pdf');
    } catch {
      return true;
    }
  };

  const usesPaidExport = format === 'pdf' || useServerHd;
  const hasDownloadScopePermission = (() => {
    if (!(permissions as any)?.canDownload) return false;
    if (scope === 'Canvas') return (permissions as any)?.canDownloadCurrentView !== false;
    if (scope === 'Views') return mode === 'all' ? (permissions as any)?.canDownloadAllViews !== false : (permissions as any)?.canDownloadCurrentView !== false;
    if (scope === 'Drawing areas') return (permissions as any)?.canDownloadDrawingZones !== false;
    if (scope === 'Layers and elements') return (permissions as any)?.canDownloadObjects !== false;
    return false;
  })();
  const canSubmit = hasDownloadScopePermission && !busy && (!usesPaidExport || !exportQuotaReached) && (!useServerHd || mode !== 'all') && (
    scope === 'Canvas' ||
    mode === 'current' ||
    mode === 'all' ||
    !!resolvedTarget
  );



  const effectiveDpi = canUseHdExport ? (dpiPreset === 'print' ? 300 : 96) : 96;

  useEffect(() => {
    if (availableModes.some((item) => item.value === mode)) return;
    setMode(scope === 'Canvas' || scope === 'Views' ? 'current' : 'single');
  }, [availableModes, mode, scope]);

  const requestedWidth = normalizePositiveNumber(width);

  const baseOptions = {
    format,
    name: name.trim() || 'download',
    width: requestedWidth,
    unit,
    dpi: effectiveDpi,
    transparent: format === 'png',
    quality: dpiPreset === 'print' ? 1 : 0.92,
    cropMark,
    marks: {
      offset: Number(marksOffset || 0),
      bleed: Number(marksBleed || 0),
      margin: Number(marksMargin || 0),
      unit: marksUnit,
    },
  };

  const getServerHdBaseSize = () => {
    const fallbackCanvas = () => {
      const activeView = views.find((view) => String(view?.id ?? '') === String(activeViewId)) ?? resolvedView;
      return {
        width: Math.max(1, Number(activeView?.width ?? 1) || 1),
        height: Math.max(1, Number(activeView?.height ?? 1) || 1),
        source: 'view',
      };
    };

    if (scope === 'Views') {
      const view = mode === 'single' && resolvedView
        ? resolvedView
        : views.find((v) => String(v?.id ?? '') === String(activeViewId)) ?? resolvedView;
      if (view) {
        return {
          width: Math.max(1, Number(view.width ?? fallbackCanvas().width) || 1),
          height: Math.max(1, Number(view.height ?? fallbackCanvas().height) || 1),
          source: 'view',
        };
      }
    }

    if (scope === 'Drawing areas' && resolvedTarget) {
      const zone = zones.find((z) => z.id === String((resolvedTarget as any).zoneId ?? (resolvedTarget as any).id ?? ''))?.zone ?? null;
      if (zone) {
        return {
          width: Math.max(1, Number(zone.width ?? zone.w ?? 1) || 1),
          height: Math.max(1, Number(zone.height ?? zone.h ?? 1) || 1),
          source: 'drawing-area',
        };
      }
    }

    if (scope === 'Layers and elements' && resolvedTarget) {
      const objectId = String((resolvedTarget as any).objectId ?? (resolvedTarget as any).id ?? '');
      const bounds = engine?.objects.getBounds(objectId) ?? null;
      if (bounds) {
        return {
          width: Math.max(1, Number(bounds.width ?? 1) || 1),
          height: Math.max(1, Number(bounds.height ?? 1) || 1),
          source: 'object',
        };
      }
    }

    return fallbackCanvas();
  };

  const buildServerHdSizeOptions = (scale: number) => {
    const baseSize = getServerHdBaseSize();
    const requestedWidthPx = downloadUnitToPx(requestedWidth, unit);
    const dpiRatio = Math.max(1, Number(effectiveDpi || 96) || 96) / 96;
    const outputWidth = requestedWidthPx
      ? roundOutputPixel(requestedWidthPx * dpiRatio)
      : roundOutputPixel(baseSize.width * Math.max(1, Number(scale) || 1));
    const outputHeight = roundOutputPixel((baseSize.height / Math.max(1, baseSize.width)) * outputWidth);
    return {
      baseWidth: baseSize.width,
      baseHeight: baseSize.height,
      sourceWidth: baseSize.width,
      sourceHeight: baseSize.height,
      sourceSize: baseSize,
      requestedWidth,
      requestedUnit: unit,
      requestedWidthPx,
      widthPx: requestedWidthPx,
      outputWidth,
      outputHeight,
      pixelWidth: outputWidth,
      pixelHeight: outputHeight,
      renderWidth: outputWidth,
      renderHeight: outputHeight,
    };
  };

  const buildServerHdTarget = () => {
    const baseSize = getServerHdBaseSize();
    const base = {
      scope,
      mode,
      activeViewId: activeViewId || null,
      viewId: activeViewId || null,
      baseWidth: baseSize.width,
      baseHeight: baseSize.height,
      width: baseSize.width,
      height: baseSize.height,
      sourceSize: baseSize,
    } as Record<string, unknown>;
    if (scope === 'Views') {
      const viewId = mode === 'single' && resolvedView ? String(resolvedView.id) : activeViewId;
      return { ...base, scope: 'Views', viewId: viewId || null, activeViewId: activeViewId || null };
    }
    if (scope === 'Drawing areas') {
      return { ...base, scope: 'Drawing areas', printAreaId: resolvedTarget ? String((resolvedTarget as any).zoneId ?? (resolvedTarget as any).id ?? '') : null };
    }
    if (scope === 'Layers and elements') {
      return { ...base, scope: 'Layers and elements', objectId: resolvedTarget ? String((resolvedTarget as any).objectId ?? (resolvedTarget as any).id ?? '') : null };
    }
    return base;
  };

  const consumePdfExportIfNeeded = async () => {
    if (format !== 'pdf') return;
    await exportRuntime?.consumePaidExport?.({
      type: 'pdf',
      reason: 'pdf_export',
      metadata: {
        source: 'download-module',
        scope,
        mode,
        target: target || activeViewId || null,
      },
    });
  };

  const runClientExport = async (task: Promise<unknown> | unknown) => {
    await task;
    await consumePdfExportIfNeeded();
  };

  const runActiveEngineExport = async () => {
    if (!engine || activeRenderEngineKind !== '3d') throw new Error(t('engine.3d.notReady', 'The 3D engine is not ready yet.'));
    const baseName = sanitizeDownloadName(name || 'asuka-3d-preview');
    if (format === 'json') {
      const scene = await engine.lifecycle.exportView(activeViewId);
      saveBlobAs(new Blob([JSON.stringify(scene ?? {}, null, 2)], { type: 'application/json;charset=utf-8' }), `${baseName}.json`);
      return;
    }
    const blob = await engine.exports.png({ ...baseOptions, format: 'png' });
    saveBlobAs(blob, `${baseName}.png`);
  };

  const submit = async () => {
    if (!engine || !canSubmit) return;
    setExportError(null);
    setBusy(true);
    try {
      if (format === 'pdf') {
        const ready = await ensurePdfExportReady();
        if (!ready) throw new Error(t('download.pdfUnavailable', 'PDF export is not available right now.'));
      }
      if (useServerHd) {
        if (!serverHdAvailable) throw new Error('[AsukaDesigner] Server-side HD export is not configured.');
        const serverHdScale = dpiPreset === 'print' ? 4 : 2;
        const serverHdSizeOptions = buildServerHdSizeOptions(serverHdScale);
        await exportRuntime.serverHd.export({
          target: buildServerHdTarget(),
          options: {
            ...baseOptions,
            format,
            name: baseOptions.name || 'download',
            dpi: effectiveDpi || 300,
            scale: serverHdScale,
            ...serverHdSizeOptions,
            transparent: format === 'png',
          },
          metadata: {
            source: 'download-module',
            reason: 'server_hd_export',
            scope,
            mode,
          },
        });
        return;
      }
      if (activeRenderEngineKind === '3d') {
        if (format === 'json') await runActiveEngineExport();
        else await runClientExport(engine.exports.download({ __asukaDownloadKind: 'canvas' }, baseOptions));
        return;
      }
      if (scope === 'Canvas') {
        await runClientExport(engine.exports.download({ __asukaDownloadKind: 'canvas' }, baseOptions));
        return;
      }
      if (scope === 'Views') {
        if (mode === 'current') {
          await runClientExport(engine.exports.download({ __asukaDownloadKind: 'view', viewId: activeViewId }, { ...baseOptions, name: baseOptions.name || 'view' }));
          return;
        }
        if (mode === 'single' && resolvedView) {
          await runClientExport(engine.exports.download({ __asukaDownloadKind: 'view', viewId: String(resolvedView.id) }, { ...baseOptions, name: baseOptions.name || String(resolvedView.name || resolvedView.id) }));
          return;
        }
        if (mode === 'all') {
          if (!canUseBatchExport) return;
          await runClientExport(engine.exports.downloadAllViews({ ...baseOptions, zip: zipAll }));
          return;
        }
      }
      if (scope === 'Drawing areas') {
        if (mode === 'all') {
          if (!canUseBatchExport) return;
          await runClientExport(engine.exports.downloadAllAreas({ ...baseOptions, zip: zipAll }));
          return;
        }
        if (resolvedTarget) {
          await runClientExport(engine.exports.download(resolvedTarget, baseOptions));
        }
        return;
      }
      if (scope === 'Layers and elements') {
        if (mode === 'all') {
          if (!canUseBatchExport) return;
          await runClientExport(engine.exports.downloadAllObjects({ ...baseOptions, zip: zipAll }));
          return;
        }
        if (resolvedTarget) {
          await runClientExport(engine.exports.download(resolvedTarget, baseOptions));
        }
      }
    } catch (error: any) {
      void 0;
      setExportError(error?.message || t('download.exportFailed', 'Export failed. Please try again.'));
    } finally {
      setBusy(false);
    }
  };

  const showRasterOptions = format === 'png' || format === 'jpeg' || format === 'jpg' || format === 'webp' || format === 'pdf';
  const needsTarget = scope !== 'Canvas' && mode === 'single';
  const canZip = canUseBatchExport && mode === 'all' && format !== 'json';
  const showBatchExportNotice = scope !== 'Canvas' && !canUseBatchExport;

  return (
    <Card>
      <DownloadHeader title={t('download.title', 'Download')} eyebrow={t('download.export', 'Export')} />
      <CardContent className="ask-flex ask-flex-col ask-gap-4">
        <DownloadIntroCard
          title={canUseHdExport ? t('download.productionExports', 'Production export') : t('download.standardExport', 'Standard export')}
          description={canUseHdExport
            ? t('download.productionExportsHint', 'Export standard files, print-ready files, PDF, or a server-rendered HD file when available.')
            : t('download.standardExportHint', 'Export standard image files for preview and everyday use.')}
        />

        <ExportFormatFields
          format={format}
          formatOptions={formatOptions}
          showRasterOptions={showRasterOptions}
          dpiPreset={dpiPreset}
          canUseHdExport={canUseHdExport}
          labels={{
            format: t('download.format', 'Format'),
            exportQuality: t('download.exportQuality', 'Export quality'),
            standardQuality: t('download.standardQuality', 'Standard'),
            printReady: t('download.printReady', 'Print-ready'),
          }}
          onFormatChange={setFormat}
          onDpiPresetChange={(next) => {
            const resolved = !canUseHdExport && next === 'print' ? 'screen' : next;
            setDpiPreset(resolved);
            setDpi(resolved === 'print' ? 300 : 96);
          }}
        />

        <DownloadMessages
          pdfSupportMessage={format === 'pdf' && !pdfAvailable && canLoadPdf
            ? t('download.pdfSupportLoadsAutomatically', 'PDF support will load automatically when you export.')
            : null}
        />

        {serverHdAvailable && canUseHdExport && format !== 'json' ? (
          <ServerHdOptionPanel
            enabled={useServerHd}
            mode={mode}
            labels={{
              title: t('download.serverHdExportSimple', 'Use server HD render'),
              hint: t('download.serverHdExportSimpleHint', 'Recommended for the highest quality production file.'),
              singleOnly: t('download.serverHdSingleOnly', 'Server HD render is available for one export at a time.'),
            }}
            onEnabledChange={setUseServerHd}
          />
        ) : null}

        <ExportTargetFields
          scope={scope}
          mode={mode}
          name={name}
          target={target}
          availableModes={availableModes}
          targetOptions={targetOptions}
          needsTarget={needsTarget}
          showBatchExportNotice={showBatchExportNotice}
          labels={{
            scope: t('download.scope', 'Scope'),
            name: t('common.name', 'Name'),
            mode: t('download.mode', 'Mode'),
            target: t('download.target', 'Target'),
            chooseTarget: t('download.chooseTarget', 'Choose a target'),
            canvas: t('download.canvas', 'Canvas'),
            views: t('download.views', 'Views'),
            drawingAreas: t('drawing.areas', 'Drawing areas'),
            layersElements: t('download.layersElements', 'Layers and elements'),
            batchExportPremium: t('download.batchExportPremium', 'Exporting multiple items is available with production export.'),
          }}
          onScopeChange={(next) => {
            setScope(next);
            setTarget('');
            setMode(next === 'Canvas' ? 'current' : next === 'Views' ? 'current' : 'single');
          }}
          onModeChange={(next) => {
            setMode(next);
            if (next !== 'single') setTarget('');
          }}
          onNameChange={setName}
          onTargetChange={setTarget}
        />

        {showRasterOptions ? (
          <RasterOptionsFields
            width={width}
            unit={unit}
            labels={{
              width: t('common.width', 'Width'),
              unit: t('download.unit', 'Unit'),
              autoWidth: t('download.autoWidth', 'Auto'),
              pixels: t('units.pixels', 'Pixels'),
              centimeters: t('units.centimeters', 'Centimeters'),
              millimeters: t('units.millimeters', 'Millimeters'),
              inches: t('units.inches', 'Inches'),
            }}
            onWidthChange={setWidth}
            onUnitChange={setUnit}
          />
        ) : null}

        {format === 'pdf' ? (
          <PdfOptionsPanel
            cropMark={cropMark}
            marksOffset={marksOffset}
            marksBleed={marksBleed}
            marksMargin={marksMargin}
            marksUnit={marksUnit}
            labels={{
              cropMarks: t('download.cropMarks', 'Crop marks'),
              offset: t('download.cropMarksOffset', 'Offset'),
              bleed: t('download.bleed', 'Bleed'),
              margin: t('download.margin', 'Margin'),
              unit: t('download.cropMarksUnit', 'Unit'),
              pixels: t('units.pixels', 'Pixels'),
              centimeters: t('units.centimeters', 'Centimeters'),
              millimeters: t('units.millimeters', 'Millimeters'),
              inches: t('units.inches', 'Inches'),
            }}
            onCropMarkChange={setCropMark}
            onMarksOffsetChange={setMarksOffset}
            onMarksBleedChange={setMarksBleed}
            onMarksMarginChange={setMarksMargin}
            onMarksUnitChange={setMarksUnit}
          />
        ) : null}

        {canZip ? (
          <ZipOptionField checked={zipAll} label={t('download.zip', 'ZIP multi-files')} onCheckedChange={setZipAll} />
        ) : null}

        <DownloadMessages
          premiumUnavailableMessage={usesPaidExport && exportQuotaReached
            ? t('download.premiumExportUnavailable', 'This production export is not available right now.')
            : null}
          exportError={exportError}
        />

        <Button variant="primary" className="ask-w-full" onClick={submit} disabled={!canSubmit}>
          {busy ? t('common.loading', 'Loading…') : t('common.submit', 'Export')}
        </Button>
      </CardContent>
    </Card>
  );
};

registerModule({
  id: 'download',
  label: getI18n().t('download.title', 'Download'),
  icon: 'download',
  displayMode: 'floating',
  keepMounted: true,
  supportedEngines: ['fabric', '3d'],
  requiredCapabilities: ['exportPng'],
  render: DownloadModule,
});

export default DownloadModule;
