import type { QuotationDocumentData } from '../types';
import {
  formatPdfCurrency,
  formatPdfDecimal,
  normalizePdfNumber,
} from './pdfme-transforms';
import type {
  PaginationModel,
  PdfmeTableModel,
  ProductItemRow,
  ProductItemSection,
  RuntimeIssue,
} from './runtime-contracts';

const PRODUCT_ITEM_TABLE_HEADERS = [
  'Item',
  'Description',
  'Qty',
  'Unit',
  'Unit Price',
  'Discount',
  'Total',
] as const;

const PRODUCT_ITEM_TABLE_WIDTHS = [10, 40, 10, 10, 12, 8, 10] as const;
const PRODUCT_ITEM_TABLE_ALIGNMENTS = [
  'center',
  'left',
  'right',
  'center',
  'right',
  'right',
  'right',
] as const;

const DEFAULT_AVAILABLE_CONTENT_HEIGHT = 648;
const DEFAULT_BASE_ROW_HEIGHT = 24;
const DEFAULT_EXTRA_LINE_HEIGHT = 10;
const DEFAULT_DESCRIPTION_WRAP = 72;
const DEFAULT_HEADER_ROWS = 1;
const DEFAULT_FOOTER_ROWS = 0;
const DEFAULT_ORPHAN_ROW_THRESHOLD = 1;

function estimateDescriptionLineCount(description: string): number {
  const normalized = description.replace(/\r\n/g, '\n').trim();

  if (!normalized) {
    return 1;
  }

  return normalized.split('\n').reduce((total, line) => {
    const lineLength = line.trim().length;
    const wrappedLength = Math.max(1, Math.ceil(lineLength / DEFAULT_DESCRIPTION_WRAP));
    return total + wrappedLength;
  }, 0);
}

function createIssue(
  code: RuntimeIssue['code'],
  message: string,
  item: QuotationDocumentData['items'][number],
  rowIndex: number,
): RuntimeIssue {
  return {
    code,
    severity: 'warning',
    message,
    details: {
      itemId: item.id,
      rowIndex,
      itemNumber: item.itemNumber,
    },
  };
}

function normalizeDescription(item: QuotationDocumentData['items'][number]): string {
  const descriptionParts = [item.description?.trim(), item.notes?.trim()].filter(
    (value): value is string => Boolean(value),
  );

  return descriptionParts.join('\n').trim();
}

function buildProductItemRow(
  item: QuotationDocumentData['items'][number],
  rowIndex: number,
  currencyCode: string,
): { row: ProductItemRow; issues: RuntimeIssue[] } {
  const issues: RuntimeIssue[] = [];
  const description = normalizeDescription(item);
  const quantity = normalizePdfNumber(item.quantity);
  const unitPrice = normalizePdfNumber(item.unitPrice);
  const discount = normalizePdfNumber(item.discount);
  const total = normalizePdfNumber(item.totalPrice);

  if (!description) {
    issues.push(
      createIssue(
        'MISSING_PRODUCT_ITEM_DESCRIPTION',
        'Product item is missing a description.',
        item,
        rowIndex,
      ),
    );
  }

  if (quantity === null) {
    issues.push(
      createIssue(
        'INVALID_PRODUCT_ITEM_QUANTITY',
        'Product item quantity is invalid.',
        item,
        rowIndex,
      ),
    );
  }

  if (unitPrice === null) {
    issues.push(
      createIssue(
        'INVALID_PRODUCT_ITEM_PRICE',
        'Product item unit price is invalid.',
        item,
        rowIndex,
      ),
    );
  }

  if (total === null) {
    issues.push(
      createIssue(
        'INVALID_PRODUCT_ITEM_TOTAL',
        'Product item total is invalid.',
        item,
        rowIndex,
      ),
    );
  }

  const estimatedLineCount = estimateDescriptionLineCount(description || '-');
  const estimatedRowHeight =
    DEFAULT_BASE_ROW_HEIGHT +
    Math.max(0, estimatedLineCount - 1) * DEFAULT_EXTRA_LINE_HEIGHT;

  return {
    row: {
      sourceItemId: item.id,
      rowIndex,
      item: String(item.itemNumber ?? rowIndex + 1),
      description: description || '-',
      quantity: formatPdfDecimal(quantity, { minimumFractionDigits: 0, maximumFractionDigits: 3 }),
      unit: item.unitLabel?.trim() || '-',
      unitPrice: formatPdfCurrency(unitPrice, currencyCode),
      discount: formatPdfCurrency(discount ?? 0, currencyCode),
      total: formatPdfCurrency(total, currencyCode),
      estimatedLineCount,
      estimatedRowHeight,
      raw: {
        itemNumber: item.itemNumber ?? null,
        quantity,
        unitPrice,
        discount,
        total,
      },
    },
    issues,
  };
}

function buildTableModel(rows: ProductItemRow[]): PdfmeTableModel {
  return {
    headers: [...PRODUCT_ITEM_TABLE_HEADERS],
    rows: rows.map((row) => [
      row.item,
      row.description,
      row.quantity,
      row.unit,
      row.unitPrice,
      row.discount,
      row.total,
    ]),
    columnWidths: [...PRODUCT_ITEM_TABLE_WIDTHS],
    alignments: [...PRODUCT_ITEM_TABLE_ALIGNMENTS],
  };
}

function buildPagination(rows: ProductItemRow[]): PaginationModel {
  if (rows.length === 0) {
    return {
      rowCount: 0,
      estimatedPages: 0,
      rowsPerPage: [],
      headerRows: DEFAULT_HEADER_ROWS,
      footerRows: DEFAULT_FOOTER_ROWS,
      repeatHeader: true,
      orphanRowThreshold: DEFAULT_ORPHAN_ROW_THRESHOLD,
      availableContentHeight: DEFAULT_AVAILABLE_CONTENT_HEIGHT,
      pageBreakIndices: [],
    };
  }

  const rowsPerPage: number[] = [];
  const pageBreakIndices: number[] = [];
  let consumedHeight = 0;
  let currentPageRowCount = 0;

  rows.forEach((row, index) => {
    const nextHeight = consumedHeight + row.estimatedRowHeight;

    if (currentPageRowCount > 0 && nextHeight > DEFAULT_AVAILABLE_CONTENT_HEIGHT) {
      rowsPerPage.push(currentPageRowCount);
      pageBreakIndices.push(index);
      consumedHeight = row.estimatedRowHeight;
      currentPageRowCount = 1;
      return;
    }

    consumedHeight = nextHeight;
    currentPageRowCount += 1;
  });

  if (currentPageRowCount > 0) {
    rowsPerPage.push(currentPageRowCount);
  }

  if (
    rowsPerPage.length > 1 &&
    rowsPerPage[rowsPerPage.length - 1] <= DEFAULT_ORPHAN_ROW_THRESHOLD
  ) {
    rowsPerPage[rowsPerPage.length - 2] -= 1;
    rowsPerPage[rowsPerPage.length - 1] += 1;

    if (pageBreakIndices.length > 0) {
      pageBreakIndices[pageBreakIndices.length - 1] -= 1;
    }
  }

  return {
    rowCount: rows.length,
    estimatedPages: rowsPerPage.length,
    rowsPerPage,
    headerRows: DEFAULT_HEADER_ROWS,
    footerRows: DEFAULT_FOOTER_ROWS,
    repeatHeader: true,
    orphanRowThreshold: DEFAULT_ORPHAN_ROW_THRESHOLD,
    availableContentHeight: DEFAULT_AVAILABLE_CONTENT_HEIGHT,
    pageBreakIndices,
  };
}

export function buildProductItemSectionModel(args: {
  items: QuotationDocumentData['items'];
  currencyCode: string | null | undefined;
}): Pick<ProductItemSection, 'rows' | 'tableModel' | 'pagination' | 'issues'> {
  const currencyCode = args.currencyCode?.trim() || 'THB';
  const issues: RuntimeIssue[] = [];
  const rows = args.items.map((item, rowIndex) => {
    const built = buildProductItemRow(item, rowIndex, currencyCode);
    issues.push(...built.issues);
    return built.row;
  });

  return {
    rows,
    tableModel: buildTableModel(rows),
    pagination: buildPagination(rows),
    issues,
  };
}
