import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
import type { DemoLocale, DemoMode, ProgressStep, ProgressStepStatus, TraceItem } from './types';
import MarketingFeatureDemo from './MarketingFeatureDemo';
import { parseDataUrl, streamMarketingGeneration } from './liveApi';

type MarketingStepTemplate = {
  id: string;
  label: string;
  runningDetail: string;
  doneDetail: string;
  kind: TraceItem['kind'];
  delayMs: number;
};

type MarketingProduct = {
  id: string;
  name: string;
  category: string;
  price: number;
  stock: number;
  unit: string;
};

type MarketingDeck = {
  headline: string;
  caption: string;
  cta: string;
  hashtags: string[];
  designNotes: string[];
};

type GeneratedImage = {
  id: string;
  dataUrl: string;
  mimeType: string;
  base64: string;
  label: string;
};

type MarketingStudioPanelProps = {
  mode: DemoMode;
  locale: DemoLocale;
  onWorkingChange: (working: boolean) => void;
  onWorkflowInit: (steps: ProgressStep[]) => void;
  onStepStatus: (stepId: string, status: ProgressStepStatus, detail?: string) => void;
  onTrace: (item: TraceItem) => void;
  onClearPanels: () => void;
  queuedPrompt: string | null;
  onConsumeQueuedPrompt: () => void;
};

const COPY = {
  en: {
    title: 'Marketing Studio',
    subtitle: 'Simulated Gemini marketing enhancement workflow (frontend-only demo)',
    productLabel: 'Product',
    briefLabel: 'Creative Brief',
    promptPlaceholder:
      'Describe the campaign angle, tone, and CTA. Example: Weekend promotion for family breakfast packs.',
    toneLabel: 'Tone',
    channelLabel: 'Channel',
    imageLabel: 'Reference Image',
    uploadButton: 'Upload Image',
    removeImage: 'Remove',
    noImage: 'No image uploaded. A synthetic product visual will be generated.',
    generating: 'Generating assets…',
    ready: 'Ready',
    generate: 'Generate Campaign',
    stop: 'Stop',
    reset: 'Reset Output',
    copyDeck: 'Copy Deck',
    copied: 'Campaign copy copied to clipboard.',
    outputTitle: 'Generated Campaign',
    outputSubtitle: 'Streaming copy + image assets',
    metadataTitle: 'Run Metadata',
    metadataModel: 'Model',
    metadataStartedAt: 'Started at',
    metadataDuration: 'Duration',
    metadataEvents: 'Events',
    metadataEventsWithImages: '{count} image + text stream',
    metadataEventsTextOnly: 'text stream',
    noOutput: 'Run generation to see marketing copy and visuals.',
    streamError: 'Generation halted before completion.',
    stoppedByUser: 'Generation stopped by user.',
    applyMock: 'Mock: attached generated image to product showcase.',
    tutorialPrompt: 'Create a polished campaign for this product with a direct CTA.',
  },
  'zh-TW': {
    title: '行銷工作室',
    subtitle: '前端模擬 Gemini 行銷增強流程（不連後端）',
    productLabel: '產品',
    briefLabel: '創意需求',
    promptPlaceholder: '描述活動主軸、語氣與 CTA。例如：週末家庭早餐檔期，強調產地直送。',
    toneLabel: '語氣',
    channelLabel: '投放渠道',
    imageLabel: '參考圖片',
    uploadButton: '上傳圖片',
    removeImage: '移除',
    noImage: '尚未上傳圖片，系統會產生模擬商品視覺。',
    generating: '生成中…',
    ready: '就緒',
    generate: '生成活動素材',
    stop: '停止',
    reset: '重置輸出',
    copyDeck: '複製文案',
    copied: '活動文案已複製到剪貼簿。',
    outputTitle: '生成結果',
    outputSubtitle: '即時文案串流 + 圖像素材',
    metadataTitle: '執行資訊',
    metadataModel: '模型',
    metadataStartedAt: '開始時間',
    metadataDuration: '耗時',
    metadataEvents: '事件',
    metadataEventsWithImages: '{count} 張圖片 + 文字串流',
    metadataEventsTextOnly: '文字串流',
    noOutput: '執行生成後可在此查看文案與圖像。',
    streamError: '生成流程尚未完整結束。',
    stoppedByUser: '已由使用者停止生成。',
    applyMock: '模擬：已將生成圖片套用到產品展示。',
    tutorialPrompt: '請為這個產品建立一套完整活動文案並附上明確 CTA。',
  },
} as const;

const CHANNEL_OPTIONS = {
  en: ['LINE Message', 'Instagram Post', 'Store Flyer', 'Marketplace Banner'],
  'zh-TW': ['LINE 訊息', '社群貼文', '店內海報', '市集橫幅'],
} as const;

const TONE_OPTIONS = {
  en: ['Friendly', 'Premium', 'Urgent', 'Seasonal'],
  'zh-TW': ['親切', '精品感', '限時感', '季節感'],
} as const;

const PRODUCTS = {
  en: [
    { id: 'p1', name: 'Free-range Eggs', category: 'Eggs', price: 118, stock: 48, unit: 'box' },
    { id: 'p2', name: 'Organic Greens Bundle', category: 'Vegetables', price: 220, stock: 18, unit: 'set' },
    { id: 'p3', name: 'Golden Sweet Corn', category: 'Produce', price: 95, stock: 64, unit: 'pack' },
  ],
  'zh-TW': [
    { id: 'p1', name: '放牧雞蛋', category: '蛋品', price: 118, stock: 48, unit: '盒' },
    { id: 'p2', name: '有機蔬菜組', category: '蔬菜', price: 220, stock: 18, unit: '組' },
    { id: 'p3', name: '黃金甜玉米', category: '農產', price: 95, stock: 64, unit: '包' },
  ],
} as const;

const now = () =>
  new Date().toLocaleTimeString([], {
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
  });

const sleep = (ms: number) =>
  new Promise<void>((resolve) => {
    setTimeout(resolve, ms);
  });

function makeId(prefix: string): string {
  return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
}

function splitTextChunks(text: string): string[] {
  const words = text.split(/\s+/).filter(Boolean);
  const chunks: string[] = [];
  let current = '';

  for (const word of words) {
    const next = current ? `${current} ${word}` : word;
    if (next.length > 28) {
      if (current) chunks.push(`${current} `);
      current = word;
    } else {
      current = next;
    }
  }

  if (current) chunks.push(`${current} `);
  return chunks;
}

function makeTrace(
  kind: TraceItem['kind'],
  title: string,
  detail: string,
  status: TraceItem['status'],
  payload?: Record<string, unknown>
): TraceItem {
  return {
    id: makeId('trace'),
    kind,
    title,
    detail,
    status,
    payload,
    timestamp: now(),
  };
}

function buildSteps(locale: DemoLocale): MarketingStepTemplate[] {
  if (locale === 'zh-TW') {
    return [
      {
        id: 'mkt-step-1',
        label: '組裝行銷需求',
        runningDetail: '整理產品資訊、語氣與渠道參數。',
        doneDetail: '行銷需求已完成組裝。',
        kind: 'planner',
        delayMs: 500,
      },
      {
        id: 'mkt-step-2',
        label: '圖片前處理',
        runningDetail: '分析參考圖片與視覺方向。',
        doneDetail: '視覺參考已就緒。',
        kind: 'validator',
        delayMs: 640,
      },
      {
        id: 'mkt-step-3',
        label: '串流生成文案',
        runningDetail: '逐段輸出標題、內文、CTA 與 Hashtag。',
        doneDetail: '文案串流輸出完成。',
        kind: 'tool',
        delayMs: 0,
      },
      {
        id: 'mkt-step-4',
        label: '生成圖片素材',
        runningDetail: '建立活動主視覺與備用版型。',
        doneDetail: '圖片素材已完成輸出。',
        kind: 'tool',
        delayMs: 0,
      },
      {
        id: 'mkt-step-5',
        label: '打包最終輸出',
        runningDetail: '整理可直接使用的活動結果。',
        doneDetail: '活動素材已可發布。',
        kind: 'renderer',
        delayMs: 420,
      },
    ];
  }

  return [
    {
      id: 'mkt-step-1',
      label: 'Assemble campaign brief',
      runningDetail: 'Collecting product context, tone, and channel.',
      doneDetail: 'Campaign brief assembled.',
      kind: 'planner',
      delayMs: 500,
    },
    {
      id: 'mkt-step-2',
      label: 'Pre-process visual input',
      runningDetail: 'Analyzing reference image and visual direction.',
      doneDetail: 'Visual context prepared.',
      kind: 'validator',
      delayMs: 640,
    },
    {
      id: 'mkt-step-3',
      label: 'Stream marketing copy',
      runningDetail: 'Streaming headline, caption, CTA, and hashtags.',
      doneDetail: 'Copy stream completed.',
      kind: 'tool',
      delayMs: 0,
    },
    {
      id: 'mkt-step-4',
      label: 'Generate visuals',
      runningDetail: 'Creating hero and alternate campaign images.',
      doneDetail: 'Image assets generated.',
      kind: 'tool',
      delayMs: 0,
    },
    {
      id: 'mkt-step-5',
      label: 'Package final output',
      runningDetail: 'Preparing publish-ready campaign deck.',
      doneDetail: 'Campaign output is ready.',
      kind: 'renderer',
      delayMs: 420,
    },
  ];
}

function createDeck(locale: DemoLocale, product: MarketingProduct, prompt: string, tone: string, channel: string): MarketingDeck {
  if (locale === 'zh-TW') {
    const cue = prompt.trim() ? `，聚焦「${prompt.trim().slice(0, 28)}」` : '';
    return {
      headline: `${product.name} 新鮮上架，今天就下單${cue}`,
      caption: `來自在地農場的 ${product.name}，口感穩定、品質透明。${tone}語氣搭配 ${channel} 推廣，強調價格 NT$${product.price}/${product.unit} 與現貨 ${product.stock} ${product.unit}。`,
      cta: '立即私訊預購，本週優先出貨。',
      hashtags: ['#產地直送', '#Farm2Market', '#今日新鮮', '#限時供應'],
      designNotes: ['白底乾淨陳列，強調產品本體', '加入暖色光感提升食慾', '版面留出 CTA 區塊可放價格與庫存'],
    };
  }

  const cue = prompt.trim() ? `, focused on "${prompt.trim().slice(0, 32)}"` : '';
  return {
    headline: `${product.name} is now in stock${cue}`,
    caption: `Freshly prepared ${product.category.toLowerCase()} with consistent quality and direct farm sourcing. Use a ${tone.toLowerCase()} tone for ${channel} and highlight NT$${product.price}/${product.unit} with ${product.stock} units available.`,
    cta: 'Reserve your batch now for this week\'s priority delivery.',
    hashtags: ['#Farm2Market', '#FarmFresh', '#DirectFromFarm', '#WeeklyDrop'],
    designNotes: ['Use clean white background and product-focused framing', 'Add gentle warm highlights for freshness', 'Reserve a clear CTA block for price and stock'],
  };
}

function deckToMarkdown(locale: DemoLocale, deck: MarketingDeck): string {
  if (locale === 'zh-TW') {
    return [
      `# ${deck.headline}`,
      '',
      `**文案**: ${deck.caption}`,
      '',
      `**CTA**: ${deck.cta}`,
      '',
      `**Hashtag**: ${deck.hashtags.join(' ')}`,
      '',
      '**設計重點**:',
      ...deck.designNotes.map((note) => `- ${note}`),
    ].join('\n');
  }

  return [
    `# ${deck.headline}`,
    '',
    `**Caption**: ${deck.caption}`,
    '',
    `**CTA**: ${deck.cta}`,
    '',
    `**Hashtags**: ${deck.hashtags.join(' ')}`,
    '',
    '**Design Notes**:',
    ...deck.designNotes.map((note) => `- ${note}`),
  ].join('\n');
}

function parseDeckFromLiveText(locale: DemoLocale, text: string): MarketingDeck {
  const fallback = locale === 'zh-TW'
    ? {
        headline: '已生成行銷內容',
        caption: text.trim().slice(0, 220) || '已從後端串流取得文案結果。',
        cta: '立即查看並套用',
        hashtags: ['#Farm2Market'],
        designNotes: ['已使用後端即時生成結果。'],
      }
    : {
        headline: 'Marketing content generated',
        caption: text.trim().slice(0, 220) || 'Live backend copy has been streamed.',
        cta: 'Review and apply now',
        hashtags: ['#Farm2Market'],
        designNotes: ['Generated from live backend stream.'],
      };

  const normalized = text
    .split('\n')
    .map((line) => line.trim())
    .filter(Boolean);

  if (!normalized.length) return fallback;

  const hashTags = Array.from(
    new Set(
      (text.match(/#[\p{L}\p{N}_-]+/gu) || [])
        .map((tag) => tag.trim())
        .filter(Boolean)
    )
  );

  const headline = normalized[0]?.replace(/^#+\s*/, '') || fallback.headline;
  const caption = normalized.slice(1, 4).join(' ') || fallback.caption;

  const ctaLine = normalized.find((line) =>
    locale === 'zh-TW' ? /(立即|現在|下單|預購|CTA|行動)/.test(line) : /(cta|order|buy now|reserve|shop)/i.test(line)
  );

  const noteLines = normalized
    .filter((line) => line.startsWith('-') || line.startsWith('•'))
    .map((line) => line.replace(/^[-•]\s*/, ''))
    .slice(0, 3);

  return {
    headline,
    caption,
    cta: ctaLine || fallback.cta,
    hashtags: hashTags.length ? hashTags.slice(0, 6) : fallback.hashtags,
    designNotes: noteLines.length ? noteLines : fallback.designNotes,
  };
}

function makePalette(seed: number): [string, string, string] {
  const palettes: Array<[string, string, string]> = [
    ['#1f8f4e', '#7dcf9b', '#edf8e6'],
    ['#0f6d8b', '#88d7e6', '#e8f7fb'],
    ['#d97706', '#f6b56d', '#fff4e6'],
    ['#6b7c2f', '#c8da89', '#f5f9e6'],
  ];
  return palettes[seed % palettes.length];
}

function generateMockImageDataUrl(product: MarketingProduct, deck: MarketingDeck, index: number, locale: DemoLocale): string {
  const canvas = document.createElement('canvas');
  canvas.width = 960;
  canvas.height = 640;
  const ctx = canvas.getContext('2d');
  if (!ctx) return '';
  const isZh = locale === 'zh-TW';

  const [primary, secondary, light] = makePalette(index + product.name.length);
  const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
  gradient.addColorStop(0, light);
  gradient.addColorStop(0.58, secondary);
  gradient.addColorStop(1, primary);
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.globalAlpha = 0.2;
  for (let i = 0; i < 6; i += 1) {
    ctx.beginPath();
    ctx.fillStyle = i % 2 ? '#ffffff' : primary;
    const radius = 60 + i * 22;
    ctx.arc(120 + i * 130, 100 + (i % 3) * 170, radius, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;

  ctx.fillStyle = 'rgba(255,255,255,0.88)';
  ctx.fillRect(70, 70, canvas.width - 140, canvas.height - 140);

  ctx.fillStyle = '#163024';
  ctx.font = 'bold 46px "Sora", sans-serif';
  ctx.fillText(deck.headline.slice(0, 28), 112, 180);

  ctx.fillStyle = '#345747';
  ctx.font = '28px "IBM Plex Sans", sans-serif';
  ctx.fillText(product.name, 112, 238);

  ctx.fillStyle = '#1c6e43';
  ctx.font = 'bold 36px "Sora", sans-serif';
  ctx.fillText(`NT$ ${product.price}/${product.unit}`, 112, 308);

  ctx.fillStyle = '#445b51';
  ctx.font = '24px "IBM Plex Sans", sans-serif';
  ctx.fillText(deck.cta.slice(0, 44), 112, 372);

  ctx.fillStyle = '#0f6d8b';
  ctx.font = 'bold 20px "IBM Plex Sans", sans-serif';
  ctx.fillText(deck.hashtags.slice(0, 3).join('  '), 112, 434);

  ctx.fillStyle = 'rgba(31,143,78,0.14)';
  ctx.fillRect(620, 120, 240, 360);
  ctx.fillStyle = '#1f8f4e';
  ctx.font = 'bold 22px "Sora", sans-serif';
  ctx.fillText(isZh ? '農鏈市集' : 'FARM2MARKET', 646, 170);
  ctx.font = '18px "IBM Plex Sans", sans-serif';
  ctx.fillText(isZh ? (index % 2 === 0 ? '主視覺版' : '社群版') : index % 2 === 0 ? 'Hero Variant' : 'Social Variant', 646, 206);
  ctx.fillText(isZh ? `庫存：${product.stock}` : `Stock: ${product.stock}`, 646, 246);

  return canvas.toDataURL('image/png');
}

export default function MarketingStudioPanel({
  mode,
  locale,
  onWorkingChange,
  onWorkflowInit,
  onStepStatus,
  onTrace,
  onClearPanels,
  queuedPrompt,
  onConsumeQueuedPrompt,
}: MarketingStudioPanelProps) {
  const copy = COPY[locale];
  const products = PRODUCTS[locale];
  const channels = CHANNEL_OPTIONS[locale];
  const tones = TONE_OPTIONS[locale];

  const [selectedProductId, setSelectedProductId] = useState<string>(products[0].id);
  const [prompt, setPrompt] = useState('');
  const [tone, setTone] = useState<string>(tones[0]);
  const [channel, setChannel] = useState<string>(channels[0]);
  const [uploadedImage, setUploadedImage] = useState<{ name: string; previewUrl: string } | null>(null);
  const [isGenerating, setIsGenerating] = useState(false);
  const [streamText, setStreamText] = useState('');
  const [deck, setDeck] = useState<MarketingDeck | null>(null);
  const [images, setImages] = useState<GeneratedImage[]>([]);
  const [error, setError] = useState<string | null>(null);
  const [meta, setMeta] = useState<{ model: string; durationMs: number; startedAt: string } | null>(null);
  const [note, setNote] = useState<string | null>(null);

  const fileInputRef = useRef<HTMLInputElement | null>(null);
  const abortRef = useRef<AbortController | null>(null);
  const runTokenRef = useRef(0);
  const mountedRef = useRef(true);
  const currentStepRef = useRef<string | null>(null);

  const selectedProduct = useMemo(
    () => products.find((product) => product.id === selectedProductId) ?? products[0],
    [products, selectedProductId]
  );

  const canGenerate = Boolean(selectedProduct) && !isGenerating;

  const runStep = async (
    runToken: number,
    template: MarketingStepTemplate,
    payload?: Record<string, unknown>
  ): Promise<boolean> => {
    if (!mountedRef.current || runTokenRef.current !== runToken) return false;
    currentStepRef.current = template.id;
    onStepStatus(template.id, 'in_progress', template.runningDetail);
    onTrace(makeTrace(template.kind, template.label, template.runningDetail, 'running'));

    if (template.delayMs > 0) {
      await sleep(template.delayMs);
    }

    if (!mountedRef.current || runTokenRef.current !== runToken) return false;
    onStepStatus(template.id, 'completed', template.doneDetail);
    onTrace(makeTrace(template.kind, template.label, template.doneDetail, 'ok', payload));
    return true;
  };

  const stopGeneration = () => {
    if (!isGenerating) return;
    runTokenRef.current += 1;
    abortRef.current?.abort();
    abortRef.current = null;
    if (currentStepRef.current) {
      onStepStatus(currentStepRef.current, 'error', copy.stoppedByUser);
    }
    onTrace(makeTrace('tool', locale === 'zh-TW' ? '生成中止' : 'Generation stopped', copy.stoppedByUser, 'error'));
    setError(copy.stoppedByUser);
    setIsGenerating(false);
    onWorkingChange(false);
  };

  const resetOutput = () => {
    setStreamText('');
    setDeck(null);
    setImages([]);
    setError(null);
    setMeta(null);
    setNote(null);
    onClearPanels();
  };

  const runGeneration = async (initialPrompt?: string) => {
    if (!selectedProduct) return;

    const activePrompt = (initialPrompt ?? prompt).trim();
    const steps = buildSteps(locale);
    const runToken = runTokenRef.current + 1;
    runTokenRef.current = runToken;
    const startedAtMs = Date.now();

    onClearPanels();
    setIsGenerating(true);
    onWorkingChange(true);
    setError(null);
    setStreamText('');
    setImages([]);
    setDeck(null);
    setMeta(null);
    setNote(null);

    onWorkflowInit(
      steps.map((step) => ({
        id: step.id,
        label: step.label,
        detail: step.runningDetail,
        status: 'pending',
      }))
    );

    try {
      const prepared = await runStep(runToken, steps[0], {
        product: selectedProduct.name,
        tone,
        channel,
      });
      if (!prepared) return;

      const imageReady = await runStep(runToken, steps[1], {
        hasReferenceImage: Boolean(uploadedImage),
      });
      if (!imageReady) return;

      if (mode === 'live') {
        const textStep = steps[2];
        const imageStep = steps[3];
        const controller = new AbortController();
        abortRef.current = controller;
        currentStepRef.current = textStep.id;
        onStepStatus(textStep.id, 'in_progress', textStep.runningDetail);
        onTrace(makeTrace(textStep.kind, textStep.label, textStep.runningDetail, 'running'));

        let imageStepStarted = false;
        let streamedImages = 0;
        let streamedText = '';

        await streamMarketingGeneration({
          prompt: activePrompt,
          locale,
          product: {
            name: selectedProduct.name,
            category: selectedProduct.category,
            description: selectedProduct.name,
          },
          image: uploadedImage ? parseDataUrl(uploadedImage.previewUrl) : undefined,
          signal: controller.signal,
          onEvent: (event) => {
            if (!mountedRef.current || runTokenRef.current !== runToken) return;

            if (event.type === 'text') {
              streamedText += event.text;
              setStreamText((prev) => prev + event.text);
              return;
            }

            if (event.type === 'image') {
              streamedImages += 1;
              if (!imageStepStarted) {
                imageStepStarted = true;
                currentStepRef.current = imageStep.id;
                onStepStatus(imageStep.id, 'in_progress', imageStep.runningDetail);
                onTrace(makeTrace(imageStep.kind, imageStep.label, imageStep.runningDetail, 'running'));
              }
              const dataUrl = `data:${event.mimeType || 'image/png'};base64,${event.base64}`;
              setImages((prev) => [
                ...prev,
                {
                  id: makeId('img'),
                  dataUrl,
                  base64: event.base64,
                  mimeType: event.mimeType || 'image/png',
                  label:
                    locale === 'zh-TW'
                      ? streamedImages === 1
                        ? '主視覺'
                        : `社群版型 ${streamedImages - 1}`
                      : streamedImages === 1
                      ? 'Hero visual'
                      : `Social variation ${streamedImages - 1}`,
                },
              ]);
              onTrace(
                makeTrace(
                  'tool',
                  locale === 'zh-TW' ? '圖片片段' : 'Image chunk',
                  locale === 'zh-TW' ? `已接收第 ${streamedImages} 張圖片` : `Received image ${streamedImages}`,
                  'ok'
                )
              );
              return;
            }

            if (event.type === 'error') {
              throw new Error(event.message || copy.streamError);
            }
          },
        });

        if (!mountedRef.current || runTokenRef.current !== runToken) return;
        abortRef.current = null;

        onStepStatus(textStep.id, 'completed', textStep.doneDetail);
        onTrace(makeTrace(textStep.kind, textStep.label, textStep.doneDetail, 'ok', { chars: streamedText.length }));

        if (imageStepStarted) {
          onStepStatus(imageStep.id, 'completed', imageStep.doneDetail);
          onTrace(makeTrace(imageStep.kind, imageStep.label, imageStep.doneDetail, 'ok', { images: streamedImages }));
        } else {
          onStepStatus(imageStep.id, 'completed', locale === 'zh-TW' ? '本次未回傳圖片。' : 'No image chunks in this run.');
          onTrace(
            makeTrace(
              imageStep.kind,
              imageStep.label,
              locale === 'zh-TW' ? '本次只回傳文字。' : 'Text-only response returned.',
              'ok'
            )
          );
        }

        const finalDeck = parseDeckFromLiveText(locale, streamedText);
        setDeck(finalDeck);

        const packaged = await runStep(runToken, steps[4], {
          output: {
            text: true,
            images: streamedImages,
            mode: 'live',
          },
        });
        if (!packaged) return;

        setMeta({
          model: 'gemini-2.5-flash-image (live)',
          startedAt: new Date(startedAtMs).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
          durationMs: Date.now() - startedAtMs,
        });
        return;
      }

      const deckPayload = createDeck(locale, selectedProduct, activePrompt, tone, channel);
      const markdown = deckToMarkdown(locale, deckPayload);
      const chunks = splitTextChunks(markdown);

      const textStep = steps[2];
      currentStepRef.current = textStep.id;
      onStepStatus(textStep.id, 'in_progress', textStep.runningDetail);
      onTrace(makeTrace(textStep.kind, textStep.label, textStep.runningDetail, 'running'));

      for (let i = 0; i < chunks.length; i += 1) {
        if (!mountedRef.current || runTokenRef.current !== runToken) return;
        setStreamText((prev) => prev + chunks[i]);
        if (i % 5 === 0) {
          onTrace(
            makeTrace('tool', locale === 'zh-TW' ? '文案片段' : 'Copy chunk', locale === 'zh-TW' ? `已輸出第 ${i + 1} 段` : `Chunk ${i + 1} streamed`, 'ok')
          );
        }
        await sleep(90);
      }

      if (!mountedRef.current || runTokenRef.current !== runToken) return;
      setDeck(deckPayload);
      onStepStatus(textStep.id, 'completed', textStep.doneDetail);
      onTrace(makeTrace(textStep.kind, textStep.label, textStep.doneDetail, 'ok', { chars: markdown.length }));

      const imageStep = steps[3];
      currentStepRef.current = imageStep.id;
      onStepStatus(imageStep.id, 'in_progress', imageStep.runningDetail);
      onTrace(makeTrace(imageStep.kind, imageStep.label, imageStep.runningDetail, 'running'));

      await sleep(480);
      if (!mountedRef.current || runTokenRef.current !== runToken) return;

      const imageOne = generateMockImageDataUrl(selectedProduct, deckPayload, 0, locale);
      const imageTwo = generateMockImageDataUrl(selectedProduct, deckPayload, 1, locale);

      const generated: GeneratedImage[] = [imageOne, imageTwo].map((dataUrl, index) => ({
        id: makeId('img'),
        dataUrl,
        mimeType: 'image/png',
        base64: dataUrl.split(',')[1] || '',
        label: locale === 'zh-TW' ? (index === 0 ? '主視覺' : '社群版型') : index === 0 ? 'Hero visual' : 'Social variation',
      }));

      setImages(generated);
      onTrace(
        makeTrace('tool', locale === 'zh-TW' ? '圖片事件' : 'Image event', locale === 'zh-TW' ? '已串流 2 張圖片事件。' : '2 image events streamed.', 'ok', {
          count: 2,
          mimeType: 'image/png',
        })
      );

      await sleep(300);
      if (!mountedRef.current || runTokenRef.current !== runToken) return;
      onStepStatus(imageStep.id, 'completed', imageStep.doneDetail);
      onTrace(makeTrace(imageStep.kind, imageStep.label, imageStep.doneDetail, 'ok'));

      const packaged = await runStep(runToken, steps[4], {
        output: {
          text: true,
          images: generated.length,
        },
      });
      if (!packaged) return;

      setMeta({
        model: 'gemini-2.5-flash-image (simulated)',
        startedAt: new Date(startedAtMs).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
        durationMs: Date.now() - startedAtMs,
      });
    } catch (runtimeError) {
      const message =
        runtimeError instanceof Error
          ? runtimeError.name === 'AbortError'
            ? copy.stoppedByUser
            : runtimeError.message
          : copy.streamError;
      setError(message);
      if (currentStepRef.current) {
        onStepStatus(currentStepRef.current, 'error', message);
      }
      onTrace(makeTrace('tool', locale === 'zh-TW' ? '生成錯誤' : 'Generation error', message, 'error'));
    } finally {
      if (runTokenRef.current === runToken) {
        abortRef.current = null;
        setIsGenerating(false);
        onWorkingChange(false);
      }
    }
  };

  const handleUpload = async (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file) return;
    const readErrorMessage = locale === 'zh-TW' ? '無法讀取檔案' : 'Unable to read file';

    const dataUrl = await new Promise<string>((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => {
        if (typeof reader.result !== 'string') {
          reject(new Error(readErrorMessage));
          return;
        }
        resolve(reader.result);
      };
      reader.onerror = () => reject(new Error(readErrorMessage));
      reader.readAsDataURL(file);
    });

    setUploadedImage({ name: file.name, previewUrl: dataUrl });
  };

  const handleDownloadImage = (image: GeneratedImage, index: number) => {
    const link = document.createElement('a');
    link.href = image.dataUrl;
    link.download = `marketing-${index + 1}.png`;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  };

  const handleCopyDeck = async () => {
    if (!deck) return;
    const text = deckToMarkdown(locale, deck);
    await navigator.clipboard.writeText(text);
    setNote(copy.copied);
  };

  const handleMockApply = () => {
    setNote(copy.applyMock);
  };

  useEffect(() => {
    if (!queuedPrompt) return;

    const consume = async () => {
      setPrompt(queuedPrompt);
      onConsumeQueuedPrompt();
      await runGeneration(queuedPrompt);
    };

    void consume();
  }, [queuedPrompt]);

  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
      runTokenRef.current += 1;
      abortRef.current?.abort();
      abortRef.current = null;
      onWorkingChange(false);
    };
  }, []);

  useEffect(() => {
    setSelectedProductId((prev) => {
      if (products.some((product) => product.id === prev)) return prev;
      return products[0].id;
    });
    setTone(TONE_OPTIONS[locale][0]);
    setChannel(CHANNEL_OPTIONS[locale][0]);
  }, [locale]);

  return (
    <div className="marketing-studio-root">
      <MarketingFeatureDemo locale={locale} isBusy={isGenerating} />

      <header className="marketing-studio-header">
        <div>
          <h3>{copy.title}</h3>
          <p>{copy.subtitle}</p>
        </div>
        <div className={`status-pill ${isGenerating ? 'busy' : 'idle'}`}>{isGenerating ? copy.generating : copy.ready}</div>
      </header>

      <div className="marketing-studio-layout">
        <section className="marketing-controls">
          <div className="marketing-field-block">
            <label>{copy.productLabel}</label>
            <div className="marketing-product-grid">
              {products.map((product) => {
                const selected = product.id === selectedProductId;
                return (
                  <button
                    key={product.id}
                    type="button"
                    className={`marketing-product-card ${selected ? 'selected' : ''}`}
                    onClick={() => setSelectedProductId(product.id)}
                  >
                    <strong>{product.name}</strong>
                    <small>{product.category}</small>
                    <span>
                      NT$ {product.price}/{product.unit}
                    </span>
                    <em>{locale === 'zh-TW' ? `庫存 ${product.stock}` : `Stock ${product.stock}`}</em>
                  </button>
                );
              })}
            </div>
          </div>

          <div className="marketing-config-row">
            <label>
              <span>{copy.toneLabel}</span>
              <select value={tone} onChange={(event) => setTone(event.target.value)} disabled={isGenerating}>
                {tones.map((option) => (
                  <option key={option} value={option}>
                    {option}
                  </option>
                ))}
              </select>
            </label>
            <label>
              <span>{copy.channelLabel}</span>
              <select value={channel} onChange={(event) => setChannel(event.target.value)} disabled={isGenerating}>
                {channels.map((option) => (
                  <option key={option} value={option}>
                    {option}
                  </option>
                ))}
              </select>
            </label>
          </div>

          <div className="marketing-field-block">
            <label>{copy.briefLabel}</label>
            <textarea
              rows={6}
              value={prompt}
              onChange={(event) => setPrompt(event.target.value)}
              placeholder={copy.promptPlaceholder}
              disabled={isGenerating}
            />
          </div>

          <div className="marketing-field-block">
            <label>{copy.imageLabel}</label>
            <input ref={fileInputRef} type="file" accept="image/*" className="hidden-input" onChange={handleUpload} />
            <div className="marketing-upload-row">
              <button type="button" className="ghost-btn" onClick={() => fileInputRef.current?.click()} disabled={isGenerating}>
                {copy.uploadButton}
              </button>
              {uploadedImage ? (
                <button
                  type="button"
                  className="ghost-btn"
                  onClick={() => {
                    setUploadedImage(null);
                    if (fileInputRef.current) fileInputRef.current.value = '';
                  }}
                  disabled={isGenerating}
                >
                  {copy.removeImage}
                </button>
              ) : null}
            </div>
            {uploadedImage ? (
              <div className="marketing-upload-preview">
                <img src={uploadedImage.previewUrl} alt={uploadedImage.name} />
                <small>{uploadedImage.name}</small>
              </div>
            ) : (
              <p className="marketing-help-text">{copy.noImage}</p>
            )}
          </div>

          <div className="marketing-action-row">
            <button type="button" className="primary-btn" disabled={!canGenerate} onClick={() => void runGeneration()}>
              {copy.generate}
            </button>
            {isGenerating ? (
              <button type="button" className="ghost-btn" onClick={stopGeneration}>
                {copy.stop}
              </button>
            ) : null}
            <button type="button" className="ghost-btn" onClick={resetOutput} disabled={isGenerating}>
              {copy.reset}
            </button>
          </div>
        </section>

        <section className="marketing-output">
          <header>
            <h4>{copy.outputTitle}</h4>
            <p>{copy.outputSubtitle}</p>
          </header>

          {note ? <div className="marketing-note">{note}</div> : null}
          {error ? <div className="marketing-error">{error}</div> : null}

          {!streamText && images.length === 0 && !isGenerating ? (
            <div className="marketing-empty">{copy.noOutput}</div>
          ) : null}

          {streamText ? (
            <article className="marketing-stream-card">
              <div className="marketing-stream-actions">
                <strong>{locale === 'zh-TW' ? '串流文案' : 'Streaming Copy'}</strong>
                <button type="button" className="ghost-btn" onClick={() => void handleCopyDeck()} disabled={!deck}>
                  {copy.copyDeck}
                </button>
              </div>
              <pre>{streamText}</pre>
            </article>
          ) : null}

          {deck ? (
            <article className="marketing-deck-grid">
              <section>
                <h5>{locale === 'zh-TW' ? '標題' : 'Headline'}</h5>
                <p>{deck.headline}</p>
              </section>
              <section>
                <h5>{locale === 'zh-TW' ? 'CTA' : 'CTA'}</h5>
                <p>{deck.cta}</p>
              </section>
              <section>
                <h5>{locale === 'zh-TW' ? 'Hashtag' : 'Hashtags'}</h5>
                <p>{deck.hashtags.join(' ')}</p>
              </section>
              <section>
                <h5>{locale === 'zh-TW' ? '設計重點' : 'Design Notes'}</h5>
                <ul>
                  {deck.designNotes.map((noteItem) => (
                    <li key={noteItem}>{noteItem}</li>
                  ))}
                </ul>
              </section>
            </article>
          ) : null}

          {images.length > 0 ? (
            <div className="marketing-image-grid">
              {images.map((image, index) => (
                <article key={image.id}>
                  <img src={image.dataUrl} alt={image.label} />
                  <div className="marketing-image-meta">
                    <strong>{image.label}</strong>
                    <div>
                      <button type="button" className="ghost-btn" onClick={() => handleDownloadImage(image, index)}>
                        {locale === 'zh-TW' ? '下載' : 'Download'}
                      </button>
                      <button type="button" className="ghost-btn" onClick={handleMockApply}>
                        {locale === 'zh-TW' ? '套用' : 'Apply'}
                      </button>
                    </div>
                  </div>
                </article>
              ))}
            </div>
          ) : null}

          {meta ? (
            <article className="marketing-meta-card">
              <h5>{copy.metadataTitle}</h5>
              <dl>
                <div>
                  <dt>{copy.metadataModel}</dt>
                  <dd>{meta.model}</dd>
                </div>
                <div>
                  <dt>{copy.metadataStartedAt}</dt>
                  <dd>{meta.startedAt}</dd>
                </div>
                <div>
                  <dt>{copy.metadataDuration}</dt>
                  <dd>{meta.durationMs} ms</dd>
                </div>
                <div>
                  <dt>{copy.metadataEvents}</dt>
                  <dd>
                    {images.length > 0
                      ? copy.metadataEventsWithImages.replace('{count}', String(images.length))
                      : copy.metadataEventsTextOnly}
                  </dd>
                </div>
              </dl>
            </article>
          ) : null}
        </section>
      </div>
    </div>
  );
}
