import { NextResponse } from "next/server";
import { promises as fs } from "fs";
import path from "path";
import JSZip from "jszip";
import type {
  Block,
  ProjectConfig,
  FormBlockProps,
  FormSelectOption,
  TextBlockProps,
  ButtonBlockProps,
  SectionBlockProps,
  VideoBlockProps,
  ListBlockProps,
} from "@/features/editor/state/editorStore";
import {
  normalizeVideoSourceUrl,
  resolveVideoPlatform,
  buildVideoEmbedUrl,
  computeVideoExportTokens,
} from "@/features/editor/utils/videoHelpers";
import { computeTextPbTokens } from "@/features/editor/utils/textHelpers";
import { computeButtonPbTokens } from "@/features/editor/utils/buttonHelpers";
import { computeImageExportStyles } from "@/features/editor/utils/imageHelpers";
import { computeSectionExportTokens } from "@/features/editor/utils/sectionHelpers";
import { computeListExportTokens } from "@/features/editor/utils/listHelpers";
import {
  computeFormInputExportTokens,
  computeFormSelectExportTokens,
  computeFormCheckboxExportTokens,
  computeFormRadioExportTokens,
  computeFormBlockExportTokens,
} from "@/features/editor/utils/formHelpers";
import { computeDividerExportTokens } from "@/features/editor/utils/dividerHelpers";

type ExportRequestBody = {
  blocks: Block[];
  projectConfig?: ProjectConfig;
};

const BUILDER_CSS_PATH = path.join(process.cwd(), "src", "styles", "builder.css");
const UPLOAD_DIR = path.join(process.cwd(), "uploads");

const escapeHtml = (value: string): string =>
  value
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");

const escapeAttr = (value: string): string => escapeHtml(value);

export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig): string => {
  const baseTitleRaw = (projectConfig?.title ?? "").trim() || "Page Builder Export";
  const seoTitleRaw = (projectConfig?.seoTitle ?? "").trim();
  const pageTitleRaw = seoTitleRaw || baseTitleRaw;
  const pageTitle = escapeHtml(pageTitleRaw);

  const headExtraRaw = (projectConfig?.headHtml ?? "").trim();
  const headExtra = headExtraRaw ? `\n${headExtraRaw}\n` : "";

  const preset = projectConfig?.canvasPreset ?? "full";
  let maxWidth: string | null = null;
  const widthPx =
    typeof projectConfig?.canvasWidthPx === "number" && projectConfig.canvasWidthPx > 0
      ? projectConfig.canvasWidthPx
      : null;
  if (widthPx != null) {
    maxWidth = `${widthPx}px`;
  } else if (preset === "mobile") {
    maxWidth = "390px";
  } else if (preset === "tablet") {
    maxWidth = "768px";
  } else if (preset === "desktop") {
    maxWidth = "1200px";
  }
  const bgColor = (projectConfig?.canvasBgColorHex ?? "").trim();
  const widthPart = maxWidth != null ? `max-width:${maxWidth};` : "";
  const bgPart = bgColor ? `background-color:${bgColor};` : "";
  const basePart = "margin:0 auto;padding:24px;box-sizing:border-box;";
  const canvasStyle = `${widthPart}${bgPart}${basePart}`;

  const bodyBgRaw = (projectConfig?.bodyBgColorHex ?? "#020617").trim() || "#020617";
  const bodyStyle = `background-color:${bodyBgRaw};margin:0;padding:0;`;
  const trackingRaw = (projectConfig?.trackingScript ?? "").trim();
  const trackingHtml = trackingRaw ? `\n${trackingRaw}\n` : "";

  const seoDescriptionRaw = (projectConfig?.seoDescription ?? "").trim();
  const seoOgImageRaw = (projectConfig?.seoOgImageUrl ?? "").trim();
  const seoCanonicalRaw = (projectConfig?.seoCanonicalUrl ?? "").trim();
  const seoNoIndex = projectConfig?.seoNoIndex === true;

  const seoHeadParts: string[] = [];

  if (seoDescriptionRaw) {
    seoHeadParts.push(
      `    <meta name="description" content="${escapeAttr(seoDescriptionRaw)}" />`,
    );
  }

  if (pageTitleRaw) {
    seoHeadParts.push(
      `    <meta property="og:title" content="${escapeAttr(pageTitleRaw)}" />`,
    );
  }
  if (seoDescriptionRaw) {
    seoHeadParts.push(
      `    <meta property="og:description" content="${escapeAttr(seoDescriptionRaw)}" />`,
    );
  }
  seoHeadParts.push(`    <meta property="og:type" content="website" />`);

  if (seoOgImageRaw) {
    seoHeadParts.push(
      `    <meta property="og:image" content="${escapeAttr(seoOgImageRaw)}" />`,
    );
  }

  if (seoCanonicalRaw) {
    seoHeadParts.push(
      `    <meta property="og:url" content="${escapeAttr(seoCanonicalRaw)}" />`,
    );
  }

  // Twitter 카드 메타 태그
  seoHeadParts.push(
    `    <meta name="twitter:card" content="summary_large_image" />`,
  );
  if (pageTitleRaw) {
    seoHeadParts.push(
      `    <meta name="twitter:title" content="${escapeAttr(pageTitleRaw)}" />`,
    );
  }
  if (seoDescriptionRaw) {
    seoHeadParts.push(
      `    <meta name="twitter:description" content="${escapeAttr(seoDescriptionRaw)}" />`,
    );
  }
  if (seoOgImageRaw) {
    seoHeadParts.push(
      `    <meta name="twitter:image" content="${escapeAttr(seoOgImageRaw)}" />`,
    );
  }

  if (seoCanonicalRaw) {
    seoHeadParts.push(
      `    <link rel="canonical" href="${escapeAttr(seoCanonicalRaw)}" />`,
    );
  }

  if (seoNoIndex) {
    seoHeadParts.push(
      `    <meta name="robots" content="noindex, nofollow" />`,
    );
  }

  const seoHeadHtml = seoHeadParts.length > 0 ? `\n${seoHeadParts.join("\n")}` : "";

  const sectionBlocks = blocks.filter((b) => b.type === "section");
  const rootBlocks = blocks.filter((b) => !b.sectionId && b.type !== "section");

  // Form 컨트롤러(FormBlock)와 개별 필드 블록(formInput/formSelect/...) 를 연결하기 위한 맵.
  // - key: 필드 블록 id (fieldIds 에 포함된 id)
  // - value: 해당 필드가 속한 <form> 요소의 DOM id (예: form_form_1)
  const fieldIdToFormId = new Map<string, string>();
  for (const block of blocks) {
    if (block.type !== "form") continue;
    const props = (block.props ?? {}) as FormBlockProps;
    const fieldIds = Array.isArray(props.fieldIds) ? props.fieldIds : [];
    if (fieldIds.length === 0) continue;
    const formDomId = `form_${block.id}`;
    for (const fieldId of fieldIds) {
      if (typeof fieldId === "string" && fieldId.trim() !== "") {
        fieldIdToFormId.set(fieldId, formDomId);
      }
    }
  }

  const renderBlock = (block: Block): string => {
    if (block.type === "text") {
      const props = (block.props ?? {}) as TextBlockProps;
      const text = typeof props.text === "string" ? props.text : "";

      const tokens = computeTextPbTokens({
        text,
        align: props.align ?? "left",
        size: props.size ?? "base",
        fontSizeMode: props.fontSizeMode ?? "scale",
        fontSizeScale: props.fontSizeScale ?? undefined,
        fontSizeCustom: props.fontSizeCustom ?? undefined,
        lineHeightMode: props.lineHeightMode ?? "scale",
        lineHeightScale: props.lineHeightScale ?? undefined,
        lineHeightCustom: props.lineHeightCustom ?? undefined,
        fontWeightMode: props.fontWeightMode ?? "scale",
        fontWeightScale: props.fontWeightScale ?? undefined,
        fontWeightCustom: props.fontWeightCustom ?? undefined,
        colorMode: props.colorMode ?? undefined,
        colorPalette: props.colorPalette ?? undefined,
        colorCustom: props.colorCustom ?? undefined,
        backgroundColorCustom: (props as any).backgroundColorCustom ?? undefined,
        maxWidthMode: props.maxWidthMode ?? "scale",
        maxWidthScale: props.maxWidthScale ?? undefined,
        underline: props.underline ?? false,
        strike: props.strike ?? false,
        italic: props.italic ?? false,
      });

      const classes = [
        tokens.alignClass,
        tokens.sizeClass,
        tokens.leadingClass,
        tokens.weightClass,
        tokens.colorClass,
        tokens.maxWidthClass,
        ...tokens.extraClasses,
      ]
        .filter(Boolean)
        .join(" ");

      const styleAttr = tokens.inlineStyles.length > 0 ? ` style="${tokens.inlineStyles.join(";")}"` : "";

      return `<p class="${classes}"${styleAttr}>${escapeHtml(text)}</p>`;
    }

    if (block.type === "button") {
      const props = (block.props ?? {}) as ButtonBlockProps;
      const href = typeof props.href === "string" ? props.href : "#";
      const label = typeof props.label === "string" ? props.label : "";

      const tokens = computeButtonPbTokens({
        align: props.align ?? "left",
        size: props.size ?? "md",
        variant: props.variant ?? "solid",
        colorPalette: props.colorPalette ?? "primary",
        borderRadius: props.borderRadius ?? "md",
        fullWidth: props.fullWidth ?? false,
        widthMode: props.widthMode ?? undefined,
        widthPx: props.widthPx ?? undefined,
        paddingX: props.paddingX ?? undefined,
        paddingY: props.paddingY ?? undefined,
        fillColorCustom: props.fillColorCustom ?? undefined,
        strokeColorCustom: props.strokeColorCustom ?? undefined,
        textColorCustom: props.textColorCustom ?? undefined,
      });

      const styleAttr = tokens.inlineStyles.length > 0 ? ` style="${tokens.inlineStyles.join(";")}"` : "";

      const btnClasses = ["pb-btn-base", tokens.sizeClass, tokens.variantClass, tokens.radiusClass]
        .filter(Boolean)
        .join(" ");

      let innerHtml = escapeHtml(label);

      if (typeof (props as any).imageSrc === "string" && (props as any).imageSrc.trim() !== "") {
        const imgSrc = escapeAttr((props as any).imageSrc.trim());
        const altRaw = (props as any).imageAlt as string | undefined;
        const altValue = altRaw && altRaw.trim() !== "" ? altRaw.trim() : label;
        const imgAlt = escapeAttr(altValue);
        innerHtml = `<img src="${imgSrc}" alt="${imgAlt}" />${innerHtml}`;
      }

      return `<div class="${tokens.alignClass}"><a href="${escapeAttr(
        href,
      )}" class="${btnClasses}"${styleAttr}>${innerHtml}</a></div>`;
    }

    if (block.type === "video") {
      const props: any = block.props ?? {};
      const rawUrl = normalizeVideoSourceUrl(props.sourceUrl ?? "");

      const platform = resolveVideoPlatform(rawUrl, props.platform ?? "auto");
      const embedUrl = buildVideoEmbedUrl(rawUrl, platform, { enableVimeoEmbed: true });

      const tokens = computeVideoExportTokens(props, { escapeAttr });

      const outerStyleAttr =
        tokens.outerStyleParts.length > 0 ? ` style="${tokens.outerStyleParts.join(";")}"` : "";
      const wrapperStyleAttr =
        tokens.wrapperStyleParts.length > 0 ? ` style="${tokens.wrapperStyleParts.join(";")}"` : "";
      const videoStyleAttr =
        tokens.videoStyleParts.length > 0 ? ` style="${tokens.videoStyleParts.join(";")}"` : "";

      const isEmbed = platform === "youtube" || platform === "vimeo";

      if (isEmbed) {
        const titleRaw = typeof (props as any).titleText === "string" ? (props as any).titleText.trim() : "";
        const iframeTitle = titleRaw !== "" ? titleRaw : "비디오";

        return `<div${outerStyleAttr}><div class="pb-video-wrapper${tokens.aspectClass}"${wrapperStyleAttr}><iframe title="${escapeAttr(
          iframeTitle,
        )}" src="${escapeAttr(
          embedUrl,
        )}" class="pb-video-frame" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></div></div>`;
      }

      const controlsAttr = props.controls === false ? "" : " controls";
      const autoplayAttr = props.autoplay ? " autoplay" : "";
      const loopAttr = props.loop ? " loop" : "";
      const mutedAttr = props.muted ? " muted" : "";
      const posterRaw = typeof (props as any).posterImageSrc === "string" ? (props as any).posterImageSrc.trim() : "";
      const posterAttr = posterRaw !== "" ? ` poster="${escapeAttr(posterRaw)}"` : "";
      const ariaRaw = typeof (props as any).ariaLabel === "string" ? (props as any).ariaLabel.trim() : "";
      const ariaAttr = ariaRaw !== "" ? ` aria-label="${escapeAttr(ariaRaw)}"` : "";
      const startDataAttr =
        typeof (props as any).startTimeSec === "number" && (props as any).startTimeSec >= 0
          ? ` data-start-seconds="${(props as any).startTimeSec}"`
          : "";
      const endDataAttr =
        typeof (props as any).endTimeSec === "number" && (props as any).endTimeSec >= 0
          ? ` data-end-seconds="${(props as any).endTimeSec}"`
          : "";
      const captionRaw = typeof (props as any).captionText === "string" ? (props as any).captionText.trim() : "";
      const captionHtml = captionRaw !== "" ? `<p class="pb-video-caption">${escapeHtml(captionRaw)}</p>` : "";

      return `<div${outerStyleAttr}><div class="pb-video-wrapper${tokens.aspectClass}"${wrapperStyleAttr}><video class="pb-video-frame" src="${escapeAttr(
        rawUrl,
      )}"${posterAttr}${ariaAttr}${startDataAttr}${endDataAttr}${videoStyleAttr}${controlsAttr}${autoplayAttr}${loopAttr}${mutedAttr}></video></div>${captionHtml}</div>`;
    }

    if (block.type === "image") {
      const props: any = block.props ?? {};
      const src = typeof props.src === "string" ? props.src : "";
      const alt = typeof props.alt === "string" ? props.alt : "";

      const styles = computeImageExportStyles({
        backgroundColorCustom:
          typeof props.backgroundColorCustom === "string" ? props.backgroundColorCustom : undefined,
        widthMode: props.widthMode ?? "auto",
        widthPx: typeof props.widthPx === "number" ? props.widthPx : undefined,
        borderRadius: props.borderRadius ?? "md",
        borderRadiusPx: typeof props.borderRadiusPx === "number" ? props.borderRadiusPx : undefined,
      });

      const wrapperStyleAttr =
        styles.wrapperStyleParts.length > 0 ? ` style="${styles.wrapperStyleParts.join(";")}"` : "";
      const imgStyleAttr = styles.imgStyleParts.length > 0 ? ` style="${styles.imgStyleParts.join(";")}"` : "";

      return `<div${wrapperStyleAttr}><img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}"${imgStyleAttr} /></div>`;
    }

    if (block.type === "form") {
      const props = (block.props ?? {}) as FormBlockProps;
      const tokens = computeFormBlockExportTokens(block, blocks);
      const controllerFields = tokens.hasControllerFields ? tokens.controllerFields : [];

      const hasControllerFields = controllerFields.length > 0;

      // FormBlock 이 컨트롤러 필드 정보를 전혀 가지고 있지 않으면
      // Export 레이어에서는 아무 것도 렌더하지 않는다 (fallback fields 기반 pb-form 도 생성하지 않음).
      if (!hasControllerFields) {
        return "";
      }

      // 컨트롤러 전용 FormBlock: fieldIds 기반으로 개별 필드 블록과 연결되는 순수 컨트롤러 폼만 생성한다.
      // - Export 에서는 레이아웃/스타일 대신 id/메서드/액션만 가지는 최소한의 <form> 요소만 만든다.
      // - action 은 퍼블릭 페이지에서 우리 서버의 forms/submit 엔드포인트로 전송하기 위한 고정 경로다.
      const formId = `form_${block.id}`;
      const method = props.method ?? "POST";
      const methodAttr = ` method="${method.toLowerCase()}"`;
      const actionAttr = ` action="/api/forms/submit"`;

      // 정적 Export 에서도 FormBlock 설정(전송 대상/웹훅 설정 등)을 함께 전달할 수 있도록
      // preview 렌더러와 동일하게 __config hidden 필드에 FormBlockProps 전체를 JSON 으로 담아둔다.
      const configJson = JSON.stringify(props ?? {});
      const escapedConfig = escapeAttr(configJson);

      const parts: string[] = [];
      parts.push(
        `<form id="${escapeAttr(formId)}" class="pb-form-controller"${methodAttr}${actionAttr}>`,
      );
      parts.push(`<input type="hidden" name="__config" value="${escapedConfig}" />`);
      parts.push("</form>");
      return parts.join("");
    }

    if (block.type === "divider") {
      const props: any = block.props ?? {};
      const tokens = computeDividerExportTokens(props, { escapeAttr });
      return `<hr class="pb-divider" style="${tokens.style}" />`;
    }

    if (block.type === "list") {
      const props = (block.props ?? {}) as ListBlockProps;

      const tokens = computeListExportTokens(props);

      if (tokens.items.length === 0) {
        return "";
      }

      const lis = tokens.items.map((text) => `<li>${escapeHtml(text)}</li>`).join("");
      const listStyleAttr = tokens.listStyleParts.join(";");

      return `<${tokens.Tag} class="pb-list" style="${listStyleAttr}">${lis}</${tokens.Tag}>`;
    }

    if (block.type === "formInput") {
      const props: any = block.props ?? {};
      const name = typeof props.formFieldName === "string" ? props.formFieldName : block.id;
      const label =
        typeof props.label === "string" && props.label.trim() !== "" ? props.label : name;
      const type = props.inputType === "email" ? "email" : "text";
      const required = props.required ? " required" : "";

      const tokens = computeFormInputExportTokens(props, { escapeAttr });
      const formId = fieldIdToFormId.get(block.id);
      const formAttr = formId ? ` form="${escapeAttr(formId)}"` : "";

      return [
        '<div class="pb-form-field">',
        `<label class="pb-form-label"${tokens.labelStyleAttr}>${escapeHtml(label)}</label>`,
        `<input class="pb-input" type="${type}" name="${escapeAttr(name)}" placeholder="${escapeAttr(
          label,
        )}"${required}${tokens.inputStyleAttr}${formAttr} />`,
        "</div>",
      ].join("");
    }

    if (block.type === "formSelect") {
      const props: any = block.props ?? {};
      const name = typeof props.formFieldName === "string" ? props.formFieldName : block.id;
      const label =
        typeof props.label === "string" && props.label.trim() !== "" ? props.label : name;
      const required = props.required ? " required" : "";
      const options: FormSelectOption[] = Array.isArray(props.options) ? props.options : [];

      const tokens = computeFormSelectExportTokens(props, { escapeAttr });
      const formId = fieldIdToFormId.get(block.id);
      const formAttr = formId ? ` form="${escapeAttr(formId)}"` : "";

      const parts: string[] = [];
      parts.push('<div class="pb-form-field">');
      parts.push(`<label class="pb-form-label"${tokens.labelStyleAttr}>${escapeHtml(label)}</label>`);
      parts.push(
        `<select class="pb-select" name="${escapeAttr(name)}"${required}${tokens.selectStyleAttr}${formAttr}>`,
      );
      for (const opt of options) {
        parts.push(
          `<option value="${escapeAttr(opt.value)}">${escapeHtml(opt.label)}</option>`,
        );
      }
      parts.push("</select>");
      parts.push("</div>");

      return parts.join("");
    }

    if (block.type === "formCheckbox") {
      const props: any = block.props ?? {};
      const name = typeof props.formFieldName === "string" ? props.formFieldName : block.id;
      const label =
        typeof props.groupLabel === "string" && props.groupLabel.trim() !== ""
          ? props.groupLabel
          : name;
      const options = Array.isArray(props.options) ? props.options : [];

      const tokens = computeFormCheckboxExportTokens(props, { escapeAttr });
      const formId = fieldIdToFormId.get(block.id);
      const formAttr = formId ? ` form="${escapeAttr(formId)}"` : "";

      const parts: string[] = [];
      parts.push('<div class="pb-form-field">');
      parts.push(`<label class="pb-form-label"${tokens.labelStyleAttr}>${escapeHtml(label)}</label>`);
      for (const opt of options) {
        parts.push(
          `<label class="pb-form-option"${tokens.optionStyleAttr}><input type="checkbox" name="${escapeAttr(
            name,
          )}" value="${escapeAttr(opt.value)}"${formAttr} /> ${escapeHtml(opt.label)}</label>`,
        );
      }
      parts.push("</div>");

      return parts.join("");
    }

    if (block.type === "formRadio") {
      const props: any = block.props ?? {};
      const name = typeof props.formFieldName === "string" ? props.formFieldName : block.id;
      const label =
        typeof props.groupLabel === "string" && props.groupLabel.trim() !== ""
          ? props.groupLabel
          : name;
      const options = Array.isArray(props.options) ? props.options : [];

      const tokens = computeFormRadioExportTokens(props, { escapeAttr });
      const formId = fieldIdToFormId.get(block.id);
      const formAttr = formId ? ` form="${escapeAttr(formId)}"` : "";

      const parts: string[] = [];
      parts.push('<div class="pb-form-field">');
      parts.push(`<label class="pb-form-label"${tokens.labelStyleAttr}>${escapeHtml(label)}</label>`);
      for (const opt of options) {
        parts.push(
          `<label class="pb-form-option"${tokens.optionStyleAttr}><input type="radio" name="${escapeAttr(
            name,
          )}" value="${escapeAttr(opt.value)}"${formAttr} /> ${escapeHtml(opt.label)}</label>`,
        );
      }
      parts.push("</div>");

      return parts.join("");
    }

    return "";
  };

  const bodyParts: string[] = [];

  if (rootBlocks.length > 0) {
    bodyParts.push('<section class="pb-root"><div class="pb-root-inner">');
    for (const block of rootBlocks) {
      bodyParts.push(renderBlock(block));
    }
    bodyParts.push("</div></section>");
  }

  for (const section of sectionBlocks) {
    const props = (section.props ?? {}) as SectionBlockProps;
    const columns =
      Array.isArray(props.columns) && props.columns.length > 0
        ? props.columns
        : [{ id: `${section.id}_col`, span: 12 }];

    const tokens = computeSectionExportTokens(props);

    const sectionStyleAttr =
      tokens.sectionStyleParts.length > 0 ? ` style="${tokens.sectionStyleParts.join(";")}"` : "";

    bodyParts.push(
      `<section class="${tokens.sectionClasses}"${sectionStyleAttr}>${tokens.backgroundVideoHtml}<div class="pb-section-inner"><div class="pb-section-columns">`,
    );

    for (const column of columns) {
      const columnBlocks = blocks.filter(
        (b) => b.sectionId === section.id && b.columnId === column.id,
      );
      bodyParts.push('<div class="pb-section-column">');
      for (const block of columnBlocks) {
        bodyParts.push(renderBlock(block));
      }
      bodyParts.push("</div>");
    }

    bodyParts.push("</div></div></section>");
  }

  const bodyContent = bodyParts.join("\n");

  return `<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>${pageTitle}</title>
    <link rel="stylesheet" href="./builder.css" />${seoHeadHtml}${headExtra}
  </head>
  <body style="${bodyStyle}">
    <main>
      <div id="app-root" style="${canvasStyle}">
${bodyContent}
      </div>
    </main>
    <script src="./main.js"></script>${trackingHtml}
  </body>
</html>`;
};

export async function POST(request: Request) {
  let body: ExportRequestBody;
  try {
    body = (await request.json()) as ExportRequestBody;
  } catch {
    return NextResponse.json({ message: "잘못된 JSON 요청입니다." }, { status: 400 });
  }

  const blocks = Array.isArray(body.blocks) ? body.blocks : [];
  const projectConfig = body.projectConfig;

  let html = buildStaticHtml(blocks, projectConfig);

  const zip = new JSZip();

  const imageIds = new Set<string>();
  const imageRegex = /\/api\/image\/([^"'>\s)]+)/g;
  let match: RegExpExecArray | null;
  // eslint-disable-next-line no-cond-assign
  while ((match = imageRegex.exec(html)) !== null) {
    if (match[1]) {
      imageIds.add(match[1]);
    }
  }

  for (const id of imageIds) {
    const filePath = path.join(UPLOAD_DIR, id);
    try {
      const bytes = await fs.readFile(filePath);
      zip.file(`images/${id}`, bytes);

      const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
      const srcRegex = new RegExp(`/api/image/${escapedId}`, "g");
      html = html.replace(srcRegex, `./images/${id}`);
    } catch {
      // 업로드 디렉터리에 파일이 없는 경우에는 조용히 건너뛴다.
    }
  }

  const videoIds = new Set<string>();
  const videoRegex = /\/api\/video\/([^"'>\s]+)/g;
  let videoMatch: RegExpExecArray | null;
  // eslint-disable-next-line no-cond-assign
  while ((videoMatch = videoRegex.exec(html)) !== null) {
    if (videoMatch[1]) {
      videoIds.add(videoMatch[1]);
    }
  }

  for (const id of videoIds) {
    const filePath = path.join(UPLOAD_DIR, id);
    try {
      const bytes = await fs.readFile(filePath);
      zip.file(`videos/${id}`, bytes);

      const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
      const srcRegex = new RegExp(`/api/video/${escapedId}`, "g");
      html = html.replace(srcRegex, `./videos/${id}`);
    } catch {
      // 업로드 디렉터리에 파일이 없는 경우에는 조용히 건너뛴다.
    }
  }

  zip.file("index.html", html);

  try {
    const cssContent = await fs.readFile(BUILDER_CSS_PATH, "utf8");
    zip.file("builder.css", cssContent);
  } catch {
    // CSS 파일이 없어도 내보내기가 가능하도록 한다.
    zip.file("builder.css", "/* builder.css 를 찾을 수 없습니다. */\n");
  }

  const mainJs = [
    "(function(){",
    "  function pbInitFormControllers(){",
    "    var forms = document.querySelectorAll(\"form.pb-form-controller\");",
    "    if (!forms || !forms.length) { return; }",
    "    forms.forEach(function(form){",
    "      form.addEventListener(\"submit\", function(event){",
    "        if (!form) { return; }",
    "        if (event && typeof event.preventDefault === 'function') { event.preventDefault(); }",
    "        var formData = new FormData(form);",
    "        var configInput = form.querySelector('input[name=\"__config\"]');",
    "        var config = null;",
    "        if (configInput && configInput.value) {",
    "          try { config = JSON.parse(configInput.value); } catch (e) { config = null; }",
    "        }",
    "        var action = form.getAttribute('action') || '/api/forms/submit';",
    "        fetch(action, { method: 'POST', body: formData })",
    "          .then(function(res){ return res.json().catch(function(){ return {}; }); })",
    "          .then(function(data){",
    "            var ok = data && data.ok;",
    "            var message = data && data.message;",
    "            if (ok) {",
    "              var success = message || (config && config.successMessage) || '성공적으로 전송되었습니다.';",
    "              if (typeof alert === 'function') { alert(success); }",
    "              try { form.reset(); } catch (e) {}",
    "            } else {",
    "              var errorMsg = message || (config && config.errorMessage) || '전송 중 오류가 발생했습니다.';",
    "              if (typeof alert === 'function') { alert(errorMsg); }",
    "            }",
    "          })",
    "          .catch(function(){",
    "            var errorMsg = (config && config.errorMessage) || '전송 중 오류가 발생했습니다.';",
    "            if (typeof alert === 'function') { alert(errorMsg); }",
    "          });",
    "      });",
    "    });",
    "  }",
    "  if (typeof document !== 'undefined') {",
    "    if (document.readyState === 'loading') {",
    "      document.addEventListener('DOMContentLoaded', pbInitFormControllers);",
    "    } else {",
    "      pbInitFormControllers();",
    "    }",
    "  }",
    "  console.log('Page Builder static export loaded');",
    "})();",
  ].join("\n");
  zip.file("main.js", mainJs);

  const zipArrayBuffer = await zip.generateAsync({ type: "arraybuffer" });

  return new NextResponse(zipArrayBuffer, {
    status: 200,
    headers: {
      "Content-Type": "application/zip",
      "Content-Disposition": `attachment; filename=page-builder-export.zip`,
    },
  });
}
