import { ChangeEvent, useEffect, useRef, useState } from 'react';
import type { DemoLocale, DemoMode, ProgressStep, ProgressStepStatus, TraceItem } from './types';
import InvoiceFeatureDemo from './InvoiceFeatureDemo';
import { parseDataUrl, scanInvoiceImage } from './liveApi';
import type { LiveInvoiceExtraction } from './liveApi';

type InvoiceDirection = 'sale' | 'purchase';
type InvoiceStatus = 'draft' | 'open' | 'paid' | 'overdue';
type WorkspaceRole = 'farmer' | 'market';

type InvoiceLineItem = {
  description: string;
  quantity: number;
  unitPrice: number;
  lineTotal: number;
};

type InvoiceExtraction = {
  provider: 'gemini' | 'mock';
  model: string;
  confidence: number;
  warnings: string[];
  rawText: string;
  invoice: {
    direction: InvoiceDirection;
    status: InvoiceStatus;
    invoiceNumber: string;
    counterpartyName: string;
    description: string;
    currency: string;
    total: number;
    paid: number;
    issueDate: string;
    dueDate: string;
    deliveryDate: string;
    items: InvoiceLineItem[];
  };
};

type InvoiceRecord = {
  id: string;
  invoiceNumber: string;
  counterpartyName: string;
  total: number;
  currency: string;
  status: InvoiceStatus;
  createdAt: string;
};

type ScanJobStatus = 'queued' | 'scanning' | 'extracting' | 'validating' | 'ready' | 'failed';

type ScanJob = {
  id: string;
  source: string;
  status: ScanJobStatus;
  progress: number;
  createdAt: string;
  invoiceNumber?: string;
  error?: string;
};

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

type InvoiceDemoPanelProps = {
  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: 'Invoice AI Studio',
    subtitle: 'Simulated invoice OCR and structured extraction (frontend-only demo)',
    statusReady: 'Ready',
    statusRunning: 'Scanning…',
    roleLabel: 'Workspace Role',
    roleFarmer: 'Farmer',
    roleMarket: 'Market / Buyer',
    sourceLabel: 'Invoice Source',
    upload: 'Upload Invoice',
    removeFile: 'Remove File',
    noFile: 'No file uploaded. A synthetic invoice sample will be used.',
    createRecord: 'Create invoice record after extraction',
    noteLabel: 'Scan Notes',
    notePlaceholder: 'Optional context for this scan run (vendor name, amount hints, etc.).',
    run: 'Run Extraction',
    stop: 'Stop',
    reset: 'Reset Output',
    jobsTitle: 'Scan Queue',
    queueEmpty: 'No active scan jobs yet.',
    outputTitle: 'Extraction Output',
    outputSubtitle: 'JSON + human-friendly invoice preview',
    emptyOutput: 'Upload or run a synthetic scan to view extracted invoice fields.',
    liveUploadRequired: 'Live mode requires an uploaded invoice image.',
    stoppedByUser: 'Invoice scan was stopped by user.',
    recordCreated: 'Draft invoice record created in demo history.',
    reviewReady: 'Extraction completed. Review fields before creating a record.',
    fieldsTitle: 'Extracted Fields',
    lineItemsTitle: 'Line Items',
    rawTextTitle: 'Raw OCR Text',
    jsonTitle: 'Structured JSON',
    metadataTitle: 'Run Metadata',
    createdRecordTitle: 'Created Record',
    historyTitle: 'Recent Records',
    metricConfidence: 'Confidence',
    metricTotal: 'Invoice Total',
    metricItems: 'Line Items',
    metricWarnings: 'Warnings',
    jobStatus: {
      queued: 'Queued',
      scanning: 'Scanning image',
      extracting: 'Extracting fields',
      validating: 'Validating totals',
      ready: 'Ready',
      failed: 'Failed',
    },
    labels: {
      invoiceNumber: 'Invoice #',
      counterparty: 'Buyer / Supplier',
      status: 'Status',
      direction: 'Type',
      issueDate: 'Issue Date',
      dueDate: 'Due Date',
      deliveryDate: 'Delivery Date',
      currency: 'Currency',
      total: 'Total',
      paid: 'Paid',
      balance: 'Balance',
      description: 'Description',
      provider: 'Provider',
      model: 'Model',
      startedAt: 'Started At',
      duration: 'Duration',
      source: 'Source',
    },
  },
  'zh-TW': {
    title: '發票 AI 工作室',
    subtitle: '前端模擬發票 OCR 與結構化擷取流程（不連後端）',
    statusReady: '就緒',
    statusRunning: '掃描中…',
    roleLabel: '工作模式',
    roleFarmer: '農民',
    roleMarket: '通路 / 買家',
    sourceLabel: '發票來源',
    upload: '上傳發票',
    removeFile: '移除檔案',
    noFile: '尚未上傳檔案，將使用模擬發票樣本。',
    createRecord: '擷取完成後建立發票紀錄',
    noteLabel: '掃描備註',
    notePlaceholder: '可補充本次掃描背景（供應商、金額提示等）。',
    run: '開始擷取',
    stop: '停止',
    reset: '重置輸出',
    jobsTitle: '掃描佇列',
    queueEmpty: '目前沒有掃描工作。',
    outputTitle: '擷取結果',
    outputSubtitle: 'JSON 與可閱讀發票預覽',
    emptyOutput: '可先上傳圖片，或直接執行模擬掃描查看欄位結果。',
    liveUploadRequired: '即時模式需要先上傳發票圖片。',
    stoppedByUser: '掃描流程已由使用者停止。',
    recordCreated: '已在示範歷史中建立草稿發票。',
    reviewReady: '欄位擷取完成，請先檢查後再建立紀錄。',
    fieldsTitle: '欄位摘要',
    lineItemsTitle: '品項明細',
    rawTextTitle: '原始 OCR 文字',
    jsonTitle: '結構化 JSON',
    metadataTitle: '執行資訊',
    createdRecordTitle: '已建立紀錄',
    historyTitle: '最近建立紀錄',
    metricConfidence: '信心值',
    metricTotal: '發票總額',
    metricItems: '品項數',
    metricWarnings: '警告',
    jobStatus: {
      queued: '排隊中',
      scanning: '掃描影像',
      extracting: '欄位擷取',
      validating: '驗證總額',
      ready: '完成',
      failed: '失敗',
    },
    labels: {
      invoiceNumber: '發票號碼',
      counterparty: '買家 / 供應商',
      status: '狀態',
      direction: '類型',
      issueDate: '開立日期',
      dueDate: '到期日',
      deliveryDate: '配送日期',
      currency: '幣別',
      total: '總額',
      paid: '已付',
      balance: '未付',
      description: '備註',
      provider: '提供者',
      model: '模型',
      startedAt: '開始時間',
      duration: '耗時',
      source: '來源',
    },
  },
} 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 todayIso(): string {
  return new Date().toISOString().slice(0, 10);
}

function addDaysIso(baseIso: string, days: number): string {
  const base = new Date(`${baseIso}T00:00:00`);
  base.setDate(base.getDate() + days);
  return base.toISOString().slice(0, 10);
}

function statusLabel(locale: DemoLocale, status: InvoiceStatus): string {
  if (locale === 'zh-TW') {
    if (status === 'paid') return '已付款';
    if (status === 'open') return '未結清';
    if (status === 'overdue') return '逾期';
    return '草稿';
  }
  if (status === 'paid') return 'Paid';
  if (status === 'open') return 'Open';
  if (status === 'overdue') return 'Overdue';
  return 'Draft';
}

function directionLabel(locale: DemoLocale, direction: InvoiceDirection): string {
  if (locale === 'zh-TW') {
    return direction === 'sale' ? '銷項' : '進項';
  }
  return direction === 'sale' ? 'Sale' : 'Purchase';
}

function formatMoney(locale: DemoLocale, amount: number, currency: string): string {
  const language = locale === 'zh-TW' ? 'zh-TW' : 'en-US';
  try {
    return new Intl.NumberFormat(language, { style: 'currency', currency, maximumFractionDigits: 2 }).format(amount);
  } catch (error) {
    const safeAmount = Number.isFinite(amount) ? amount.toFixed(2) : '0.00';
    return `${currency} ${safeAmount}`;
  }
}

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 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 > 34) {
      if (current) chunks.push(`${current} `);
      current = word;
    } else {
      current = next;
    }
  }

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

function buildSteps(locale: DemoLocale, createRecord: boolean): InvoiceStepTemplate[] {
  if (locale === 'zh-TW') {
    return [
      {
        id: 'invoice-step-1',
        label: '驗證發票來源',
        runningDetail: '檢查檔案類型與可讀性。',
        doneDetail: '文件前處理完成。',
        kind: 'validator',
        delayMs: 460,
      },
      {
        id: 'invoice-step-2',
        label: '執行 OCR',
        runningDetail: '擷取原始文字內容。',
        doneDetail: 'OCR 掃描完成。',
        kind: 'tool',
        delayMs: 0,
      },
      {
        id: 'invoice-step-3',
        label: '結構化欄位擷取',
        runningDetail: '解析發票號碼、日期、金額與品項。',
        doneDetail: '欄位擷取完成。',
        kind: 'tool',
        delayMs: 560,
      },
      {
        id: 'invoice-step-4',
        label: '欄位驗證',
        runningDetail: '驗證總額、日期格式與狀態。',
        doneDetail: '欄位一致性驗證完成。',
        kind: 'validator',
        delayMs: 460,
      },
      {
        id: 'invoice-step-5',
        label: createRecord ? '建立草稿紀錄' : '封裝審核結果',
        runningDetail: createRecord ? '建立可編輯的草稿發票。' : '整理供人工審核的輸出。',
        doneDetail: createRecord ? '草稿發票已建立。' : '審核輸出已完成。',
        kind: 'renderer',
        delayMs: 420,
      },
    ];
  }

  return [
    {
      id: 'invoice-step-1',
      label: 'Validate invoice source',
      runningDetail: 'Checking file type and readability.',
      doneDetail: 'Document pre-processing complete.',
      kind: 'validator',
      delayMs: 460,
    },
    {
      id: 'invoice-step-2',
      label: 'Run OCR',
      runningDetail: 'Extracting raw invoice text.',
      doneDetail: 'OCR pass completed.',
      kind: 'tool',
      delayMs: 0,
    },
    {
      id: 'invoice-step-3',
      label: 'Extract structured fields',
      runningDetail: 'Parsing invoice #, dates, totals, and line items.',
      doneDetail: 'Structured extraction completed.',
      kind: 'tool',
      delayMs: 560,
    },
    {
      id: 'invoice-step-4',
      label: 'Validate fields',
      runningDetail: 'Checking totals, date formats, and status.',
      doneDetail: 'Validation checks completed.',
      kind: 'validator',
      delayMs: 460,
    },
    {
      id: 'invoice-step-5',
      label: createRecord ? 'Create draft record' : 'Package review output',
      runningDetail: createRecord ? 'Preparing editable draft invoice.' : 'Preparing review-ready output.',
      doneDetail: createRecord ? 'Draft invoice record created.' : 'Review package prepared.',
      kind: 'renderer',
      delayMs: 420,
    },
  ];
}

function buildItems(locale: DemoLocale): InvoiceLineItem[] {
  if (locale === 'zh-TW') {
    return [
      { description: '放牧雞蛋（10 入）', quantity: 20, unitPrice: 118, lineTotal: 2360 },
      { description: '有機青菜組', quantity: 12, unitPrice: 220, lineTotal: 2640 },
      { description: '甜玉米箱', quantity: 15, unitPrice: 95, lineTotal: 1425 },
    ];
  }

  return [
    { description: 'Free-range eggs (10 pack)', quantity: 20, unitPrice: 118, lineTotal: 2360 },
    { description: 'Organic greens bundle', quantity: 12, unitPrice: 220, lineTotal: 2640 },
    { description: 'Sweet corn box', quantity: 15, unitPrice: 95, lineTotal: 1425 },
  ];
}

function buildMockExtraction(locale: DemoLocale, role: WorkspaceRole, note: string): InvoiceExtraction {
  const issueDate = todayIso();
  const dueDate = addDaysIso(issueDate, 21);
  const deliveryDate = addDaysIso(issueDate, -2);
  const items = buildItems(locale);
  const subtotal = items.reduce((sum, item) => sum + item.lineTotal, 0);
  const fee = 240;
  const total = subtotal + fee;
  const paid = note.toLowerCase().includes('paid') || note.includes('已付款') ? total : 0;
  const direction: InvoiceDirection = role === 'farmer' ? 'sale' : 'purchase';

  const invoiceNumber =
    locale === 'zh-TW'
      ? `TW-FTL-${issueDate.replace(/-/g, '')}-${Math.floor(100 + Math.random() * 800)}`
      : `FTL-${issueDate.replace(/-/g, '')}-${Math.floor(100 + Math.random() * 800)}`;

  const counterpartyName =
    role === 'farmer'
      ? locale === 'zh-TW'
        ? '晨光食材通路'
        : 'Morning Harvest Distribution'
      : locale === 'zh-TW'
        ? '北埔綠田農場'
        : 'Beipu Greenfield Farm';

  const descriptionCore = locale === 'zh-TW' ? '本週配送批次結算。' : 'Weekly delivery batch settlement.';
  const description = note.trim() ? `${descriptionCore} ${note.trim()}` : descriptionCore;
  const warnings =
    locale === 'zh-TW'
      ? ['印章區略模糊，建議人工覆核發票號碼。']
      : ['Stamp region is slightly blurred; verify invoice number manually.'];

  const rawText =
    locale === 'zh-TW'
      ? [
          `發票號碼: ${invoiceNumber}`,
          `買方/賣方: ${counterpartyName}`,
          `開立日期: ${issueDate}`,
          `到期日期: ${dueDate}`,
          ...items.map((item) => `${item.description} x${item.quantity} @${item.unitPrice} = ${item.lineTotal}`),
          `運費: ${fee}`,
          `總計: ${total}`,
          `已付: ${paid}`,
        ].join('\n')
      : [
          `Invoice #: ${invoiceNumber}`,
          `Counterparty: ${counterpartyName}`,
          `Issue date: ${issueDate}`,
          `Due date: ${dueDate}`,
          ...items.map((item) => `${item.description} x${item.quantity} @${item.unitPrice} = ${item.lineTotal}`),
          `Handling fee: ${fee}`,
          `Total: ${total}`,
          `Paid: ${paid}`,
        ].join('\n');

  return {
    provider: 'gemini',
    model: 'gemini-2.5-flash (simulated)',
    confidence: 0.84 + Math.random() * 0.12,
    warnings,
    rawText,
    invoice: {
      direction,
      status: paid > 0 ? 'paid' : 'draft',
      invoiceNumber,
      counterpartyName,
      description,
      currency: 'TWD',
      total,
      paid,
      issueDate,
      dueDate,
      deliveryDate,
      items,
    },
  };
}

function toNumber(value: unknown, fallback = 0): number {
  return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
}

function normalizeLiveExtraction(payload: LiveInvoiceExtraction | undefined, locale: DemoLocale): InvoiceExtraction {
  const invoice = payload?.invoice || {};
  const items = Array.isArray(invoice.items) ? invoice.items : [];

  return {
    provider: payload?.provider === 'gemini' || payload?.provider === 'mock' ? payload.provider : 'gemini',
    model: payload?.model || (locale === 'zh-TW' ? 'gemini（live）' : 'gemini (live)'),
    confidence: toNumber(payload?.confidence, 0.5),
    warnings: Array.isArray(payload?.warnings) ? payload.warnings.map((item) => String(item)) : [],
    rawText: typeof payload?.rawText === 'string' ? payload.rawText : '',
    invoice: {
      direction: invoice.direction === 'purchase' ? 'purchase' : 'sale',
      status:
        invoice.status === 'open' || invoice.status === 'paid' || invoice.status === 'overdue'
          ? invoice.status
          : 'draft',
      invoiceNumber: typeof invoice.invoiceNumber === 'string' ? invoice.invoiceNumber : '',
      counterpartyName: typeof invoice.counterpartyName === 'string' ? invoice.counterpartyName : '',
      description: typeof invoice.description === 'string' ? invoice.description : '',
      currency: typeof invoice.currency === 'string' ? invoice.currency : 'TWD',
      total: toNumber(invoice.total),
      paid: toNumber(invoice.paid),
      issueDate: typeof invoice.issueDate === 'string' ? invoice.issueDate : '',
      dueDate: typeof invoice.dueDate === 'string' ? invoice.dueDate : '',
      deliveryDate: typeof invoice.deliveryDate === 'string' ? invoice.deliveryDate : '',
      items: items.map((item) => ({
        description: typeof item.description === 'string' ? item.description : '',
        quantity: toNumber(item.quantity),
        unitPrice: toNumber(item.unitPrice),
        lineTotal: toNumber(item.lineTotal),
      })),
    },
  };
}

export default function InvoiceDemoPanel({
  mode,
  locale,
  onWorkingChange,
  onWorkflowInit,
  onStepStatus,
  onTrace,
  onClearPanels,
  queuedPrompt,
  onConsumeQueuedPrompt,
}: InvoiceDemoPanelProps) {
  const copy = COPY[locale];
  const [role, setRole] = useState<WorkspaceRole>('farmer');
  const [createRecord, setCreateRecord] = useState(true);
  const [scanNote, setScanNote] = useState('');
  const [uploadedFile, setUploadedFile] = useState<{
    name: string;
    previewUrl: string;
    mimeType: string;
    sizeKb: number;
  } | null>(null);
  const [isScanning, setIsScanning] = useState(false);
  const [rawTextStream, setRawTextStream] = useState('');
  const [extraction, setExtraction] = useState<InvoiceExtraction | null>(null);
  const [createdRecord, setCreatedRecord] = useState<InvoiceRecord | null>(null);
  const [recordHistory, setRecordHistory] = useState<InvoiceRecord[]>([]);
  const [jobs, setJobs] = useState<ScanJob[]>([]);
  const [error, setError] = useState<string | null>(null);
  const [note, setNote] = useState<string | null>(null);
  const [meta, setMeta] = useState<{ startedAt: string; durationMs: number; model: string; provider: string; source: string } | null>(null);

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

  const runStep = async (
    runToken: number,
    template: InvoiceStepTemplate,
    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 setJobState = (jobId: string, patch: Partial<ScanJob>) => {
    setJobs((prev) => prev.map((job) => (job.id === jobId ? { ...job, ...patch } : job)));
  };

  const stopScan = () => {
    if (!isScanning) return;
    runTokenRef.current += 1;
    if (currentStepRef.current) {
      onStepStatus(currentStepRef.current, 'error', copy.stoppedByUser);
    }
    onTrace(makeTrace('tool', locale === 'zh-TW' ? '掃描中止' : 'Scan stopped', copy.stoppedByUser, 'error'));
    setJobs((prev) =>
      prev.map((job, index) => {
        if (index > 0) return job;
        if (job.status === 'ready' || job.status === 'failed') return job;
        return { ...job, status: 'failed', error: copy.stoppedByUser, progress: 100 };
      })
    );
    setError(copy.stoppedByUser);
    setIsScanning(false);
    onWorkingChange(false);
  };

  const resetOutput = () => {
    setRawTextStream('');
    setExtraction(null);
    setCreatedRecord(null);
    setError(null);
    setMeta(null);
    setNote(null);
    onClearPanels();
  };

  const runExtraction = async (forcedNote?: string) => {
    const activeNote = (forcedNote ?? scanNote).trim();
    const source = uploadedFile?.name || (locale === 'zh-TW' ? '模擬發票樣本' : 'Synthetic invoice sample');
    const liveCreateRecord = mode === 'live' ? false : createRecord;
    const steps = buildSteps(locale, liveCreateRecord);
    const runToken = runTokenRef.current + 1;
    runTokenRef.current = runToken;
    const startedAtMs = Date.now();

    const jobId = makeId('job');
    const pendingJob: ScanJob = {
      id: jobId,
      source,
      status: 'queued',
      progress: 2,
      createdAt: now(),
    };
    setJobs((prev) =>
      [pendingJob, ...prev].slice(0, 6)
    );

    onClearPanels();
    setIsScanning(true);
    onWorkingChange(true);
    setRawTextStream('');
    setExtraction(null);
    setCreatedRecord(null);
    setError(null);
    setMeta(null);
    setNote(null);

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

    try {
      if (mode === 'live') {
        if (!uploadedFile) {
          throw new Error(copy.liveUploadRequired);
        }

        setJobState(jobId, { status: 'scanning', progress: 16 });
        const sourceReady = await runStep(runToken, steps[0], {
          source: uploadedFile.mimeType,
        });
        if (!sourceReady) return;

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

        const imagePayload = parseDataUrl(uploadedFile.previewUrl);
        const response = await scanInvoiceImage({
          dataBase64: imagePayload.base64,
          mimeType: imagePayload.mimeType,
          locale,
          createInvoice: liveCreateRecord,
        });
        const payload = normalizeLiveExtraction(response.extraction, locale);
        if (!mountedRef.current || runTokenRef.current !== runToken) return;

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

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

        setJobState(jobId, { status: 'extracting', progress: 62 });
        const extracted = await runStep(runToken, steps[2], {
          fields: 12,
          items: payload.invoice.items.length,
        });
        if (!extracted) return;
        setExtraction(payload);

        setJobState(jobId, { status: 'validating', progress: 82 });
        const validated = await runStep(runToken, steps[3], {
          total: payload.invoice.total,
          paid: payload.invoice.paid,
          warnings: payload.warnings.length,
        });
        if (!validated) return;

        const packaged = await runStep(runToken, steps[4], {
          createRecord: liveCreateRecord,
          invoiceNumber: payload.invoice.invoiceNumber,
          mode: 'live',
        });
        if (!packaged) return;

        setNote(copy.reviewReady);
        setJobState(jobId, {
          status: 'ready',
          progress: 100,
          invoiceNumber: payload.invoice.invoiceNumber,
        });
        setMeta({
          startedAt: new Date(startedAtMs).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
          durationMs: Date.now() - startedAtMs,
          model: payload.model,
          provider: payload.provider,
          source,
        });
        return;
      }

      const payload = buildMockExtraction(locale, role, activeNote);
      setJobState(jobId, { status: 'scanning', progress: 16 });
      const sourceReady = await runStep(runToken, steps[0], {
        source: uploadedFile ? uploadedFile.mimeType : 'synthetic',
      });
      if (!sourceReady) return;

      setJobState(jobId, { status: 'extracting', progress: 34 });
      const ocrStep = steps[1];
      currentStepRef.current = ocrStep.id;
      onStepStatus(ocrStep.id, 'in_progress', ocrStep.runningDetail);
      onTrace(makeTrace(ocrStep.kind, ocrStep.label, ocrStep.runningDetail, 'running'));

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

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

      setJobState(jobId, { status: 'extracting', progress: 62 });
      const extracted = await runStep(runToken, steps[2], {
        fields: 12,
        items: payload.invoice.items.length,
      });
      if (!extracted) return;
      setExtraction(payload);

      setJobState(jobId, { status: 'validating', progress: 82 });
      const validated = await runStep(runToken, steps[3], {
        total: payload.invoice.total,
        paid: payload.invoice.paid,
        warnings: payload.warnings.length,
      });
      if (!validated) return;

      const packaged = await runStep(runToken, steps[4], {
        createRecord: liveCreateRecord,
        invoiceNumber: payload.invoice.invoiceNumber,
      });
      if (!packaged) return;

      if (liveCreateRecord) {
        const record: InvoiceRecord = {
          id: makeId('record'),
          invoiceNumber: payload.invoice.invoiceNumber,
          counterpartyName: payload.invoice.counterpartyName,
          total: payload.invoice.total,
          currency: payload.invoice.currency,
          status: payload.invoice.status,
          createdAt: now(),
        };
        setCreatedRecord(record);
        setRecordHistory((prev) => [record, ...prev].slice(0, 6));
        setNote(copy.recordCreated);
      } else {
        setNote(copy.reviewReady);
      }

      setJobState(jobId, {
        status: 'ready',
        progress: 100,
        invoiceNumber: payload.invoice.invoiceNumber,
      });
      setMeta({
        startedAt: new Date(startedAtMs).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
        durationMs: Date.now() - startedAtMs,
        model: payload.model,
        provider: payload.provider,
        source,
      });
    } catch (runtimeError) {
      const message = runtimeError instanceof Error ? runtimeError.message : copy.stoppedByUser;
      setError(message);
      if (currentStepRef.current) {
        onStepStatus(currentStepRef.current, 'error', message);
      }
      setJobState(jobId, { status: 'failed', progress: 100, error: message });
      onTrace(makeTrace('tool', locale === 'zh-TW' ? '擷取錯誤' : 'Extraction error', message, 'error'));
    } finally {
      if (runTokenRef.current === runToken) {
        setIsScanning(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 previewUrl = 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);
    });

    setUploadedFile({
      name: file.name,
      previewUrl,
      mimeType: file.type || 'image/*',
      sizeKb: Math.max(1, Math.round(file.size / 1024)),
    });
  };

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

    const consume = async () => {
      setScanNote(queuedPrompt);
      onConsumeQueuedPrompt();
      await runExtraction(queuedPrompt);
    };

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

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

  useEffect(() => {
    if (mode === 'live') {
      setCreateRecord(false);
    }
  }, [mode]);

  const balance = extraction ? extraction.invoice.total - extraction.invoice.paid : 0;

  return (
    <div className="invoice-demo-root">
      <InvoiceFeatureDemo locale={locale} isBusy={isScanning} />

      <header className="invoice-demo-header">
        <div>
          <h3>{copy.title}</h3>
          <p>{copy.subtitle}</p>
        </div>
        <div className={`status-pill ${isScanning ? 'busy' : 'idle'}`}>{isScanning ? copy.statusRunning : copy.statusReady}</div>
      </header>

      <div className="invoice-demo-layout">
        <section className="invoice-input-panel">
          <div className="invoice-field-grid">
            <label className="invoice-field-block">
              <span>{copy.roleLabel}</span>
              <select value={role} onChange={(event) => setRole(event.target.value as WorkspaceRole)} disabled={isScanning}>
                <option value="farmer">{copy.roleFarmer}</option>
                <option value="market">{copy.roleMarket}</option>
              </select>
            </label>
          </div>

          <div className="invoice-field-block">
            <span>{copy.sourceLabel}</span>
            <input ref={fileInputRef} type="file" accept="image/*" className="hidden-input" onChange={handleUpload} />

            <button
              type="button"
              className={`invoice-dropzone ${uploadedFile ? 'has-file' : ''}`}
              onClick={() => fileInputRef.current?.click()}
              disabled={isScanning}
            >
              <strong>{uploadedFile ? uploadedFile.name : copy.upload}</strong>
              <small>
                {uploadedFile ? `${uploadedFile.mimeType} • ${uploadedFile.sizeKb} KB` : locale === 'zh-TW' ? '點擊上傳發票圖片' : 'Click to upload invoice image'}
              </small>
            </button>

            {uploadedFile ? (
              <div className="invoice-file-preview">
                <img src={uploadedFile.previewUrl} alt={uploadedFile.name} />
                <div className="invoice-file-meta">
                  <span>{uploadedFile.name}</span>
                  <button
                    type="button"
                    className="ghost-btn"
                    onClick={() => {
                      setUploadedFile(null);
                      if (fileInputRef.current) fileInputRef.current.value = '';
                    }}
                    disabled={isScanning}
                  >
                    {copy.removeFile}
                  </button>
                </div>
              </div>
            ) : (
              <p className="invoice-help-text">{copy.noFile}</p>
            )}
          </div>

          <div className="invoice-toggle-row">
            <label>
              <input
                type="checkbox"
                checked={createRecord}
                onChange={(event) => setCreateRecord(event.target.checked)}
                disabled={isScanning || mode === 'live'}
              />
              <span>{copy.createRecord}</span>
            </label>
          </div>

          <label className="invoice-field-block">
            <span>{copy.noteLabel}</span>
            <textarea
              rows={4}
              value={scanNote}
              onChange={(event) => setScanNote(event.target.value)}
              placeholder={copy.notePlaceholder}
              disabled={isScanning}
            />
          </label>

          <div className="invoice-action-row">
            <button type="button" className="primary-btn" onClick={() => void runExtraction()} disabled={isScanning}>
              {copy.run}
            </button>
            {isScanning ? (
              <button type="button" className="ghost-btn" onClick={stopScan}>
                {copy.stop}
              </button>
            ) : null}
            <button type="button" className="ghost-btn" onClick={resetOutput} disabled={isScanning}>
              {copy.reset}
            </button>
          </div>

          <section className="invoice-jobs-block">
            <h4>{copy.jobsTitle}</h4>
            {jobs.length === 0 ? (
              <p className="invoice-help-text">{copy.queueEmpty}</p>
            ) : (
              <ul className="invoice-job-list">
                {jobs.map((job) => (
                  <li key={job.id} className={`invoice-job-card ${job.status}`}>
                    <header>
                      <strong>{job.source}</strong>
                      <span>{job.createdAt}</span>
                    </header>
                    <p className="invoice-job-status">{copy.jobStatus[job.status]}</p>
                    <div className="invoice-job-meter">
                      <span style={{ width: `${Math.min(100, Math.max(0, job.progress))}%` }} />
                    </div>
                    {job.invoiceNumber ? <small>#{job.invoiceNumber}</small> : null}
                    {job.error ? <small>{job.error}</small> : null}
                  </li>
                ))}
              </ul>
            )}
          </section>
        </section>

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

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

          {!extraction && !isScanning ? <div className="invoice-empty">{copy.emptyOutput}</div> : null}

          {extraction ? (
            <>
              <div className="invoice-metric-grid">
                <article className="invoice-metric-card">
                  <span>{copy.metricConfidence}</span>
                  <strong>{Math.round(extraction.confidence * 100)}%</strong>
                </article>
                <article className="invoice-metric-card">
                  <span>{copy.metricTotal}</span>
                  <strong>{formatMoney(locale, extraction.invoice.total, extraction.invoice.currency)}</strong>
                </article>
                <article className="invoice-metric-card">
                  <span>{copy.metricItems}</span>
                  <strong>{extraction.invoice.items.length}</strong>
                </article>
                <article className="invoice-metric-card">
                  <span>{copy.metricWarnings}</span>
                  <strong>{extraction.warnings.length}</strong>
                </article>
              </div>

              <article className="invoice-preview-card">
                <h5>{copy.fieldsTitle}</h5>
                <dl className="invoice-preview-grid">
                  <div>
                    <dt>{copy.labels.invoiceNumber}</dt>
                    <dd>{extraction.invoice.invoiceNumber}</dd>
                  </div>
                  <div>
                    <dt>{copy.labels.counterparty}</dt>
                    <dd>{extraction.invoice.counterpartyName}</dd>
                  </div>
                  <div>
                    <dt>{copy.labels.status}</dt>
                    <dd>{statusLabel(locale, extraction.invoice.status)}</dd>
                  </div>
                  <div>
                    <dt>{copy.labels.direction}</dt>
                    <dd>{directionLabel(locale, extraction.invoice.direction)}</dd>
                  </div>
                  <div>
                    <dt>{copy.labels.issueDate}</dt>
                    <dd>{extraction.invoice.issueDate}</dd>
                  </div>
                  <div>
                    <dt>{copy.labels.dueDate}</dt>
                    <dd>{extraction.invoice.dueDate}</dd>
                  </div>
                  <div>
                    <dt>{copy.labels.deliveryDate}</dt>
                    <dd>{extraction.invoice.deliveryDate}</dd>
                  </div>
                  <div>
                    <dt>{copy.labels.currency}</dt>
                    <dd>{extraction.invoice.currency}</dd>
                  </div>
                  <div>
                    <dt>{copy.labels.total}</dt>
                    <dd>{formatMoney(locale, extraction.invoice.total, extraction.invoice.currency)}</dd>
                  </div>
                  <div>
                    <dt>{copy.labels.paid}</dt>
                    <dd>{formatMoney(locale, extraction.invoice.paid, extraction.invoice.currency)}</dd>
                  </div>
                  <div>
                    <dt>{copy.labels.balance}</dt>
                    <dd>{formatMoney(locale, balance, extraction.invoice.currency)}</dd>
                  </div>
                  <div>
                    <dt>{copy.labels.description}</dt>
                    <dd>{extraction.invoice.description}</dd>
                  </div>
                </dl>
              </article>

              <article className="invoice-preview-card">
                <h5>{copy.lineItemsTitle}</h5>
                <div className="invoice-items-scroll">
                  <table className="invoice-items-table">
                    <thead>
                      <tr>
                        <th>{locale === 'zh-TW' ? '品項' : 'Description'}</th>
                        <th>{locale === 'zh-TW' ? '數量' : 'Qty'}</th>
                        <th>{locale === 'zh-TW' ? '單價' : 'Unit Price'}</th>
                        <th>{locale === 'zh-TW' ? '小計' : 'Line Total'}</th>
                      </tr>
                    </thead>
                    <tbody>
                      {extraction.invoice.items.map((item) => (
                        <tr key={`${item.description}-${item.quantity}`}>
                          <td>{item.description}</td>
                          <td>{item.quantity}</td>
                          <td>{formatMoney(locale, item.unitPrice, extraction.invoice.currency)}</td>
                          <td>{formatMoney(locale, item.lineTotal, extraction.invoice.currency)}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              </article>

              <article className="invoice-preview-card">
                <h5>{copy.rawTextTitle}</h5>
                <pre className="invoice-raw-text">{rawTextStream || extraction.rawText}</pre>
              </article>

              <details className="invoice-json-card">
                <summary>{copy.jsonTitle}</summary>
                <pre>{JSON.stringify(extraction, null, 2)}</pre>
              </details>

              {meta ? (
                <article className="invoice-meta-card">
                  <h5>{copy.metadataTitle}</h5>
                  <dl>
                    <div>
                      <dt>{copy.labels.provider}</dt>
                      <dd>{meta.provider}</dd>
                    </div>
                    <div>
                      <dt>{copy.labels.model}</dt>
                      <dd>{meta.model}</dd>
                    </div>
                    <div>
                      <dt>{copy.labels.startedAt}</dt>
                      <dd>{meta.startedAt}</dd>
                    </div>
                    <div>
                      <dt>{copy.labels.duration}</dt>
                      <dd>{meta.durationMs} ms</dd>
                    </div>
                    <div>
                      <dt>{copy.labels.source}</dt>
                      <dd>{meta.source}</dd>
                    </div>
                  </dl>
                </article>
              ) : null}
            </>
          ) : null}

          {createdRecord ? (
            <article className="invoice-record-card">
              <h5>{copy.createdRecordTitle}</h5>
              <p>
                <strong>{createdRecord.invoiceNumber}</strong> • {createdRecord.counterpartyName}
              </p>
              <p>
                {formatMoney(locale, createdRecord.total, createdRecord.currency)} • {statusLabel(locale, createdRecord.status)}
              </p>
            </article>
          ) : null}

          {recordHistory.length > 0 ? (
            <article className="invoice-record-card">
              <h5>{copy.historyTitle}</h5>
              <ul className="invoice-history-list">
                {recordHistory.map((record) => (
                  <li key={record.id}>
                    <span>#{record.invoiceNumber}</span>
                    <strong>{formatMoney(locale, record.total, record.currency)}</strong>
                  </li>
                ))}
              </ul>
            </article>
          ) : null}
        </section>
      </div>
    </div>
  );
}
