import {
  BoundingBoxRect,
  ReceiptBoundingBoxes,
  ReceiptData,
  ProcessedReceipt,
} from "../schema/receipt";

/**
 * Normalisiert Bounding-Box-Werte auf den gültigen Prozentbereich (0-100%).
 */
export function clampPercent(value: number): number {
  if (Number.isNaN(value)) return 0;
  return Math.max(0, Math.min(100, Math.round(value * 100) / 100));
}

/**
 * Erzeugt ein normiertes BoundingBoxRect-Objekt mit Prozentwerten (0-100%).
 */
export function createBoundingBoxRect(
  x: number,
  y: number,
  width: number,
  height: number,
  page?: number
): BoundingBoxRect {
  const clampedX = clampPercent(x);
  const clampedY = clampPercent(y);
  const maxWidth = 100 - clampedX;
  const maxHeight = 100 - clampedY;

  return {
    x: clampedX,
    y: clampedY,
    width: clampPercent(Math.min(width, maxWidth)),
    height: clampPercent(Math.min(height, maxHeight)),
    page: page && page > 0 ? page : undefined,
  };
}

/**
 * Wandelt Pixel-Koordinaten bezogen auf eine Original-Bildgröße in Prozent um.
 */
export function pixelRectToPercent(
  pixelRect: { x: number; y: number; width: number; height: number },
  imageWidth: number,
  imageHeight: number,
  page?: number
): BoundingBoxRect {
  if (imageWidth <= 0 || imageHeight <= 0) {
    return createBoundingBoxRect(0, 0, 100, 100, page);
  }

  return createBoundingBoxRect(
    (pixelRect.x / imageWidth) * 100,
    (pixelRect.y / imageHeight) * 100,
    (pixelRect.width / imageWidth) * 100,
    (pixelRect.height / imageHeight) * 100,
    page
  );
}

/**
 * Prüft, ob ein Punkt (in Prozent 0-100) innerhalb einer Bounding Box liegt.
 */
export function isPointInsideBox(
  point: { x: number; y: number },
  box: BoundingBoxRect
): boolean {
  return (
    point.x >= box.x &&
    point.x <= box.x + box.width &&
    point.y >= box.y &&
    point.y <= box.y + box.height
  );
}

/**
 * Heuristic layout regions — NOT OCR coordinates.
 *
 * Only for tests and explicit callers. The document viewer must not overlay
 * these as if they came from the scan: they do not match the receipt image.
 *
 * Layout assumption:
 * - Header (0-24%): merchant, date, receipt number
 * - Body (25-60%): line items
 * - Footer (61-95%): tax, net, gross, payment
 */
export function generateDefaultBoundingBoxes(
  receipt: Partial<ReceiptData> | Partial<ProcessedReceipt>
): ReceiptBoundingBoxes {
  const lineItemsCount = Array.isArray(receipt.lineItems)
    ? receipt.lineItems.length
    : 0;
  const taxesCount = Array.isArray(receipt.taxBreakdown)
    ? receipt.taxBreakdown.length
    : 0;

  // 1. Kopfbereich: Händler & Datum & Belegnummer
  const merchantBox = createBoundingBoxRect(12, 6, 76, 7);
  const dateBox = createBoundingBoxRect(12, 14, 36, 5);
  const receiptNumberBox = receipt.receiptNumber
    ? createBoundingBoxRect(52, 14, 36, 5)
    : undefined;

  // 2. Rumpfbereich: Einzelpositionen (22% bis max 60%)
  const lineItemsBoxes: BoundingBoxRect[] = [];
  if (lineItemsCount > 0) {
    const startY = 22;
    const availableHeight = Math.min(38, Math.max(16, lineItemsCount * 5.5));
    const rowHeight = Math.min(6, availableHeight / lineItemsCount);

    for (let i = 0; i < lineItemsCount; i++) {
      lineItemsBoxes.push(
        createBoundingBoxRect(8, startY + i * rowHeight, 84, Math.max(3.5, rowHeight - 0.8))
      );
    }
  }

  // 3. Fußbereich: Steuern & Summen (standardmäßig in der unteren Hälfte ab 60%)
  const taxBreakdownBoxes: BoundingBoxRect[] = [];
  const taxStartY = lineItemsCount > 0 ? Math.max(60, Math.min(72, 24 + lineItemsCount * 6)) : 62;

  if (taxesCount > 0) {
    for (let i = 0; i < taxesCount; i++) {
      taxBreakdownBoxes.push(
        createBoundingBoxRect(10, taxStartY + i * 4.5, 80, 4)
      );
    }
  }

  const totalsStartY = taxBreakdownBoxes.length > 0
    ? taxStartY + taxBreakdownBoxes.length * 4.5 + 2
    : taxStartY + 2;

  const netBox = createBoundingBoxRect(35, totalsStartY, 55, 4.5);
  const totalAmountBox = createBoundingBoxRect(25, totalsStartY + 6, 65, 6.5);
  const paymentMethodBox = createBoundingBoxRect(15, totalsStartY + 14, 70, 4.5);

  return {
    merchant: merchantBox,
    date: dateBox,
    receiptNumber: receiptNumberBox,
    lineItems: lineItemsBoxes.length > 0 ? lineItemsBoxes : undefined,
    taxBreakdown: taxBreakdownBoxes.length > 0 ? taxBreakdownBoxes : undefined,
    netAmount: netBox,
    totalAmount: totalAmountBox,
    paymentMethod: paymentMethodBox,
  };
}

/** True when at least one box was provided by extraction / the user, not the heuristic. */
export function hasExtractedBoundingBoxes(
  boxes?: ReceiptBoundingBoxes | null
): boolean {
  if (!boxes) return false;
  return Boolean(
    boxes.merchant ||
      boxes.date ||
      boxes.receiptNumber ||
      boxes.totalAmount ||
      boxes.netAmount ||
      boxes.paymentMethod ||
      (boxes.lineItems && boxes.lineItems.length > 0) ||
      (boxes.taxBreakdown && boxes.taxBreakdown.length > 0)
  );
}

/**
 * Gibt die entsprechende Bounding Box für einen Feld-Identifier zurück.
 * Unterstützt Keys wie:
 * - "merchant"
 * - "date"
 * - "receiptNumber"
 * - "totalAmount" | "gross"
 * - "netAmount" | "net"
 * - "paymentMethod"
 * - "lineItem:0" | "lineItems[0]" | "lineItems:0"
 * - "tax:19" | "taxBreakdown:0" | "taxBreakdown[0]"
 */
export function getFieldBoundingBox(
  boundingBoxes: ReceiptBoundingBoxes | undefined,
  fieldKey: string
): BoundingBoxRect | null {
  if (!boundingBoxes) return null;

  const normalized = fieldKey.trim();

  if (normalized === "merchant" || normalized.startsWith("merchant.")) {
    return boundingBoxes.merchant ?? null;
  }
  if (normalized === "date" || normalized.startsWith("date.")) {
    return boundingBoxes.date ?? null;
  }
  if (normalized === "receiptNumber") {
    return boundingBoxes.receiptNumber ?? null;
  }
  if (normalized === "totalAmount" || normalized === "gross" || normalized.startsWith("totalAmount.")) {
    return boundingBoxes.totalAmount ?? null;
  }
  if (normalized === "netAmount" || normalized === "net") {
    return boundingBoxes.netAmount ?? null;
  }
  if (normalized === "paymentMethod") {
    return boundingBoxes.paymentMethod ?? null;
  }

  // Line item match: e.g. lineItems[1] or lineItem:1 or lineItems:1
  const lineItemMatch = normalized.match(/lineItem[s]?[\[:]?(\d+)/i);
  if (lineItemMatch && boundingBoxes.lineItems) {
    const idx = parseInt(lineItemMatch[1], 10);
    return boundingBoxes.lineItems[idx] ?? null;
  }

  // Tax breakdown match: e.g. taxBreakdown[0] or taxBreakdown:0
  const taxMatch = normalized.match(/taxBreakdown[\[:]?(\d+)/i);
  if (taxMatch && boundingBoxes.taxBreakdown) {
    const idx = parseInt(taxMatch[1], 10);
    return boundingBoxes.taxBreakdown[idx] ?? null;
  }

  // Generic fallback if tax rate specified e.g. tax:19
  if (normalized.startsWith("tax:") && boundingBoxes.taxBreakdown && boundingBoxes.taxBreakdown.length > 0) {
    return boundingBoxes.taxBreakdown[0] ?? null;
  }

  return null;
}

/**
 * Ermittelt eine menschenlesbare Bezeichnung für ein Feld (Standard: Englisch, optional Deutsch).
 */
export function getFieldLabel(fieldKey: string, langOrIsDe?: boolean | string): string {
  const isDe = typeof langOrIsDe === "boolean" ? langOrIsDe : langOrIsDe === "de";
  const normalized = fieldKey.trim();

  if (normalized.startsWith("merchant")) return isDe ? "Händler / Aussteller" : "Merchant / Issuer";
  if (normalized.startsWith("date")) return isDe ? "Belegdatum" : "Date";
  if (normalized === "receiptNumber") return isDe ? "Belegnummer" : "Receipt Number";
  if (normalized === "totalAmount" || normalized === "gross") return isDe ? "Brutto Gesamt" : "Gross Total";
  if (normalized === "netAmount" || normalized === "net") return isDe ? "Netto" : "Net";
  if (normalized === "paymentMethod") return isDe ? "Zahlungsart" : "Payment Method";
  if (normalized === "category" || normalized === "suggestedCategory") return isDe ? "Kategorie" : "Category";
  if (normalized === "documentType" || normalized === "docType") return isDe ? "Belegart" : "Document Type";

  const lineItemMatch = normalized.match(/lineItem[s]?[\[:]?(\d+)/i);
  if (lineItemMatch) {
    const idx = parseInt(lineItemMatch[1], 10);
    return isDe ? `Position ${idx + 1}` : `Item ${idx + 1}`;
  }

  if (normalized.startsWith("tax:")) {
    const rate = normalized.slice(4);
    return isDe ? `MwSt. ${rate}%` : `Tax ${rate}%`;
  }

  return fieldKey;
}

/**
 * Findet das zu einem Klick auf die Bild-Koordinaten passende Feld.
 */
export function findFieldAtCoordinates(
  boxes: ReceiptBoundingBoxes,
  point: { x: number; y: number },
  isDe: boolean = false
): { fieldKey: string; label: string; box: BoundingBoxRect } | null {
  // Check in order of specificity (line items first, then totals, then header)
  if (boxes.lineItems) {
    for (let i = 0; i < boxes.lineItems.length; i++) {
      const box = boxes.lineItems[i];
      if (box && isPointInsideBox(point, box)) {
        return { fieldKey: `lineItem:${i}`, label: isDe ? `Position ${i + 1}` : `Item ${i + 1}`, box };
      }
    }
  }

  if (boxes.taxBreakdown) {
    for (let i = 0; i < boxes.taxBreakdown.length; i++) {
      const box = boxes.taxBreakdown[i];
      if (box && isPointInsideBox(point, box)) {
        return { fieldKey: `taxBreakdown:${i}`, label: isDe ? `Steuer ${i + 1}` : `Tax ${i + 1}`, box };
      }
    }
  }

  if (boxes.totalAmount && isPointInsideBox(point, boxes.totalAmount)) {
    return { fieldKey: "totalAmount", label: isDe ? "Brutto Gesamt" : "Gross Total", box: boxes.totalAmount };
  }

  if (boxes.netAmount && isPointInsideBox(point, boxes.netAmount)) {
    return { fieldKey: "netAmount", label: isDe ? "Netto" : "Net", box: boxes.netAmount };
  }

  if (boxes.paymentMethod && isPointInsideBox(point, boxes.paymentMethod)) {
    return { fieldKey: "paymentMethod", label: isDe ? "Zahlungsart" : "Payment Method", box: boxes.paymentMethod };
  }

  if (boxes.merchant && isPointInsideBox(point, boxes.merchant)) {
    return { fieldKey: "merchant", label: isDe ? "Händler / Aussteller" : "Merchant / Issuer", box: boxes.merchant };
  }

  if (boxes.date && isPointInsideBox(point, boxes.date)) {
    return { fieldKey: "date", label: isDe ? "Belegdatum" : "Date", box: boxes.date };
  }

  if (boxes.receiptNumber && isPointInsideBox(point, boxes.receiptNumber)) {
    return { fieldKey: "receiptNumber", label: isDe ? "Belegnummer" : "Receipt Number", box: boxes.receiptNumber };
  }

  return null;
}
