export interface LabelLayoutConfig {
  columns: number;
  enabled: boolean;
  horizontalGap: number;
  itemsPerPage: number;
  labelHeight: number;
  labelWidth: number;
  marginBottom: number;
  marginLeft: number;
  marginRight: number;
  marginTop: number;
  paperHeight: number;
  paperWidth: number;
  startOffset: number;
  verticalGap: number;
}

export interface LabelSize {
  height: number;
  width: number;
}

export interface LabelLayoutResult {
  html: string;
  pageCount: number;
}

export const defaultLabelLayoutConfig: LabelLayoutConfig = {
  columns: 1,
  enabled: false,
  horizontalGap: 0,
  itemsPerPage: 1,
  labelHeight: 0,
  labelWidth: 0,
  marginBottom: 0,
  marginLeft: 0,
  marginRight: 0,
  marginTop: 0,
  paperHeight: 210,
  paperWidth: 297,
  startOffset: 0,
  verticalGap: 0,
};

const POSITIVE_INTEGER_KEYS = ['columns', 'itemsPerPage'] as const;
const NON_NEGATIVE_NUMBER_KEYS = ['horizontalGap', 'marginBottom', 'marginLeft', 'marginRight', 'marginTop', 'startOffset', 'verticalGap'] as const;

function toFiniteNumber(value: unknown, fallback: number) {
  if (value === null || value === undefined || value === '') return fallback;
  const number = Number(value);
  return Number.isFinite(number) ? number : fallback;
}

export function normalizeLabelLayoutConfig(config?: Partial<LabelLayoutConfig>): LabelLayoutConfig {
  const normalized = { ...defaultLabelLayoutConfig, ...config };
  for (const key of POSITIVE_INTEGER_KEYS) {
    normalized[key] = Math.trunc(toFiniteNumber(normalized[key], defaultLabelLayoutConfig[key]));
  }
  for (const key of NON_NEGATIVE_NUMBER_KEYS) {
    normalized[key] = toFiniteNumber(normalized[key], defaultLabelLayoutConfig[key]);
  }
  normalized.paperHeight = toFiniteNumber(normalized.paperHeight, defaultLabelLayoutConfig.paperHeight);
  normalized.paperWidth = toFiniteNumber(normalized.paperWidth, defaultLabelLayoutConfig.paperWidth);
  normalized.labelHeight = toFiniteNumber(normalized.labelHeight, defaultLabelLayoutConfig.labelHeight);
  normalized.labelWidth = toFiniteNumber(normalized.labelWidth, defaultLabelLayoutConfig.labelWidth);
  normalized.startOffset = Math.trunc(normalized.startOffset);
  normalized.enabled = normalized.enabled === true;
  return normalized;
}

export function getLabelSize(printTemplate: any, config?: Partial<LabelLayoutConfig>): LabelSize {
  const panels = printTemplate?.panels;
  if (!Array.isArray(panels) || panels.length !== 1) throw new Error('标签拼版仅支持单面板打印模板');

  const width = Number(config?.labelWidth) > 0 ? Number(config?.labelWidth) : Number(panels[0]?.width);
  const height = Number(config?.labelHeight) > 0 ? Number(config?.labelHeight) : Number(panels[0]?.height);
  if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
    throw new Error('无法读取标签模板尺寸，请检查打印模板纸张设置');
  }
  return { height, width };
}

export function validateLabelLayout(config: LabelLayoutConfig, labelSize: LabelSize, labelCount = config.itemsPerPage) {
  if (!Number.isInteger(config.itemsPerPage) || config.itemsPerPage < 1) return '每页标签数必须为正整数';
  if (!Number.isInteger(config.columns) || config.columns < 1) return '每行标签数必须为正整数';
  if (config.columns > config.itemsPerPage) return '每行标签数不能大于每页标签数';
  if (!Number.isInteger(config.startOffset) || config.startOffset < 0) return '起始留空数必须为非负整数';
  if (config.startOffset >= config.itemsPerPage) return '起始留空数必须小于每页标签数';
  if (config.paperWidth <= 0 || config.paperHeight <= 0) return '纸张宽高必须大于 0';

  const spacingValues = [config.marginTop, config.marginRight, config.marginBottom, config.marginLeft, config.horizontalGap, config.verticalGap];
  if (spacingValues.some((value) => !Number.isFinite(value) || value < 0)) return '页边距和标签间距不能小于 0';

  const usedWidth = config.columns * labelSize.width + (config.columns - 1) * config.horizontalGap + config.marginLeft + config.marginRight;
  if (usedWidth > config.paperWidth + 0.001) return '标签列宽、水平间距和左右边距之和超过纸张宽度';

  const maximumSlots = Math.min(config.itemsPerPage, Math.max(1, labelCount)) + config.startOffset;
  const rows = Math.ceil(maximumSlots / config.columns);
  const usedHeight = rows * labelSize.height + (rows - 1) * config.verticalGap + config.marginTop + config.marginBottom;
  if (usedHeight > config.paperHeight + 0.001) return '标签行高、垂直间距和上下边距之和超过纸张高度';
  return '';
}

function appendRenderedHtml(container: HTMLElement, renderedHtml: unknown) {
  if (typeof renderedHtml === 'string') {
    const document = new DOMParser().parseFromString(renderedHtml, 'text/html');
    for (const child of document.body.childNodes) container.append(child.cloneNode(true));
    return;
  }
  if (renderedHtml instanceof Node) {
    container.append(renderedHtml.cloneNode(true));
    return;
  }

  const nodes = (renderedHtml as any)?.toArray?.() || (renderedHtml as any)?.get?.();
  if (Array.isArray(nodes)) {
    for (const node of nodes) {
      if (node instanceof Node) container.append(node.cloneNode(true));
    }
  }
}

function createSlot(document: Document, paper?: Element) {
  const slot = document.createElement('div');
  slot.className = paper ? 'jnpf-label-slot' : 'jnpf-label-slot is-empty';
  if (paper) slot.append(paper.cloneNode(true));
  return slot;
}

export function buildLabelLayoutHtml(renderedHtml: unknown, printTemplate: any, inputConfig?: Partial<LabelLayoutConfig>): LabelLayoutResult {
  if (typeof document === 'undefined' || typeof DOMParser === 'undefined') throw new Error('当前环境不支持标签拼版');

  const config = normalizeLabelLayoutConfig(inputConfig);
  const source = document.createElement('div');
  appendRenderedHtml(source, renderedHtml);
  const papers = [...source.querySelectorAll('.hiprint-printPaper')];
  if (!papers.length) throw new Error('打印模板未生成有效标签内容');

  const labelSize = getLabelSize(printTemplate, config);
  const error = validateLabelLayout(config, labelSize, papers.length);
  if (error) throw new Error(error);

  const root = document.createElement('div');
  root.className = 'jnpf-label-layout';
  const pageCount = Math.ceil(papers.length / config.itemsPerPage);

  for (let pageIndex = 0; pageIndex < pageCount; pageIndex++) {
    const sheet = document.createElement('section');
    sheet.className = 'jnpf-label-sheet';
    sheet.style.width = `${config.paperWidth}mm`;
    sheet.style.height = `${config.paperHeight}mm`;
    sheet.style.padding = `${config.marginTop}mm ${config.marginRight}mm ${config.marginBottom}mm ${config.marginLeft}mm`;
    sheet.style.gridTemplateColumns = `repeat(${config.columns}, ${labelSize.width}mm)`;
    sheet.style.gridAutoRows = `${labelSize.height}mm`;
    sheet.style.columnGap = `${config.horizontalGap}mm`;
    sheet.style.rowGap = `${config.verticalGap}mm`;

    if (pageIndex === 0) {
      for (let index = 0; index < config.startOffset; index++) sheet.append(createSlot(document));
    }
    const pagePapers = papers.slice(pageIndex * config.itemsPerPage, (pageIndex + 1) * config.itemsPerPage);
    for (const paper of pagePapers) sheet.append(createSlot(document, paper));
    root.append(sheet);
  }

  const style = document.createElement('style');
  style.textContent = `
@page { size: ${config.paperWidth}mm ${config.paperHeight}mm; margin: 0; }
.jnpf-label-layout { margin: 0; padding: 0; }
.jnpf-label-sheet {
  box-sizing: border-box;
  display: grid;
  align-content: start;
  justify-content: start;
  margin: 10px auto;
  overflow: hidden;
  background: #fff;
  break-after: page;
  page-break-after: always;
}
.jnpf-label-sheet:last-child { break-after: auto; page-break-after: auto; }
.jnpf-label-slot { box-sizing: border-box; overflow: hidden; }
.jnpf-label-slot > .hiprint-printPaper {
  margin: 0 !important;
  break-after: auto !important;
  page-break-after: auto !important;
}
.jnpf-label-slot.is-empty { visibility: hidden; }
@media print {
  .jnpf-label-sheet { margin: 0; }
}
`;
  root.prepend(style);
  return { html: root.outerHTML, pageCount };
}
