/**
 * Repeating-section (w15:repeatingSection) support — add and remove repeated
 * items, the way Word's "+" affordance does. A repeating section is a block
 * content control whose `w:sdtPr` carries `<w15:repeatingSection>`; its direct
 * children are item controls each carrying `<w15:repeatingSectionItem>`.
 *
 * Adding clones an existing item (with a fresh, unique `w:id`) and inserts it
 * after; removing drops one item but keeps at least one. The w15 elements ride
 * in the captured raw `w:sdtPr` (they're unmodeled), so we detect and patch the
 * raw string rather than re-serializing.
 */

import type { Document, BlockContent, BlockSdt, SdtProperties } from '../types/document';
import {
  ContentControlNotFoundError,
  applyToFirst,
  rebuild,
  type ContentControlFilter,
  type ControlOp,
} from './contentControls';

/** Raw `w:sdtPr` declares a repeating section (the container). */
export function rawIsRepeatingSection(raw: string | undefined): boolean {
  // `repeatingSection` not followed by "Item" (the item element starts the same).
  return /<w15:repeatingSection[\s/>]/.test(raw ?? '');
}

/** Raw `w:sdtPr` declares a repeating-section item. */
export function rawIsRepeatingSectionItem(raw: string | undefined): boolean {
  return /<w15:repeatingSectionItem[\s/>]/.test(raw ?? '');
}

/** The control's raw `w:sdtPr` declares it a repeating section (the container). */
export function isRepeatingSection(props: SdtProperties): boolean {
  return rawIsRepeatingSection(props.rawPropertiesXml);
}

/** The control's raw `w:sdtPr` declares it a repeating-section item. */
export function isRepeatingSectionItem(props: SdtProperties): boolean {
  return rawIsRepeatingSectionItem(props.rawPropertiesXml);
}

/** Raised when an operation targets something that isn't a repeating section/item. */
export class RepeatingSectionError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'RepeatingSectionError';
  }
}

/** Highest `w:id` used by any control in the document, for minting a fresh one. */
function maxControlId(blocks: BlockContent[]): number {
  let max = 0;
  const walk = (bs: BlockContent[]): void => {
    for (const b of bs) {
      if (b.type === 'blockSdt') {
        if (typeof b.properties.id === 'number') max = Math.max(max, b.properties.id);
        walk(b.content);
      } else if (b.type === 'table') {
        for (const row of b.rows) for (const cell of row.cells) walk(cell.content);
      }
    }
  };
  walk(blocks);
  return max;
}

/** Patch (or leave) the `<w:id w:val="…">` inside a raw `w:sdtPr` string. */
export function patchRawId(raw: string | undefined, id: number): string | undefined {
  return setRawId(raw, id);
}
function setRawId(raw: string | undefined, id: number): string | undefined {
  if (!raw) return raw;
  if (/<w:id\b/.test(raw)) return raw.replace(/(<w:id\b[^>]*\bw:val=")[^"]*(")/, `$1${id}$2`);
  // Insert into the open <w:sdtPr> tag; a self-closing <w:sdtPr/> has no
  // content slot, so leave it untouched rather than appending outside it.
  return raw.replace(/(<w:sdtPr\b[^>]*[^/]>)/, `$1<w:id w:val="${id}"/>`);
}

/** Deep-clone an item control, assigning fresh unique ids to it and any nested controls. */
function cloneItem(item: BlockSdt, nextId: () => number): BlockSdt {
  const clone = structuredClone(item) as BlockSdt;
  const reassign = (sdt: BlockSdt): void => {
    const id = nextId();
    sdt.properties = {
      ...sdt.properties,
      id,
      rawPropertiesXml: setRawId(sdt.properties.rawPropertiesXml, id),
    };
    for (const child of sdt.content) if (child.type === 'blockSdt') reassign(child);
  };
  reassign(clone);
  return clone;
}

/** Find the section matching `filter`; throws if missing or not a repeating section. */
function locateSection(doc: Document, filter: ContentControlFilter): BlockSdt {
  let found: BlockSdt | undefined;
  const walk = (blocks: BlockContent[]): void => {
    for (const b of blocks) {
      if (found) return;
      if (b.type === 'blockSdt') {
        const p = b.properties;
        const ok =
          (filter.tag === undefined || p.tag === filter.tag) &&
          (filter.alias === undefined || p.alias === filter.alias) &&
          (filter.id === undefined || p.id === filter.id);
        if (ok) found = b;
        else walk(b.content);
      }
    }
  };
  walk(doc.package.document.content);
  if (!found) throw new ContentControlNotFoundError(filter);
  if (!isRepeatingSection(found.properties)) {
    throw new RepeatingSectionError(
      `Control ${filter.tag ?? filter.alias ?? filter.id} is not a repeating section.`
    );
  }
  return found;
}

/** Item children of a repeating section, with their index in `section.content`. */
function itemIndices(section: BlockSdt): number[] {
  const out: number[] = [];
  section.content.forEach((b, i) => {
    if (b.type === 'blockSdt' && isRepeatingSectionItem(b.properties)) out.push(i);
  });
  return out;
}

/**
 * Add a new repeating-section item, cloned from an existing one and inserted
 * after it. `afterIndex` is the item ordinal to clone/insert after (default:
 * the last item). Returns a new {@link Document}.
 */
export function addRepeatingSectionItem(
  doc: Document,
  filter: ContentControlFilter,
  options: { afterIndex?: number } = {}
): Document {
  const section = locateSection(doc, filter);
  const items = itemIndices(section);
  if (items.length === 0) {
    throw new RepeatingSectionError('Repeating section has no item to clone.');
  }
  const ord = options.afterIndex ?? items.length - 1;
  const srcContentIdx = items[Math.max(0, Math.min(ord, items.length - 1))];
  const template = section.content[srcContentIdx] as BlockSdt;

  let nextId = maxControlId(doc.package.document.content);
  const clone = cloneItem(template, () => ++nextId);

  const state = { done: false };
  const op: ControlOp = (s) => {
    const next = [...s.content];
    next.splice(srcContentIdx + 1, 0, clone);
    return [{ ...s, content: next }];
  };
  const content = applyToFirst(doc.package.document.content, filter, op, state);
  return rebuild(doc, content);
}

/**
 * Remove the repeating-section item at ordinal `index`. Keeps at least one item
 * (Word does not allow removing the last). Returns a new {@link Document}.
 */
export function removeRepeatingSectionItem(
  doc: Document,
  filter: ContentControlFilter,
  index: number
): Document {
  const section = locateSection(doc, filter);
  const items = itemIndices(section);
  if (items.length <= 1) {
    throw new RepeatingSectionError('Cannot remove the last item of a repeating section.');
  }
  if (index < 0 || index >= items.length) {
    throw new RepeatingSectionError(`Item index ${index} out of range (0..${items.length - 1}).`);
  }
  const removeContentIdx = items[index];
  const state = { done: false };
  const op: ControlOp = (s) => {
    const next = s.content.filter((_, i) => i !== removeContentIdx);
    return [{ ...s, content: next }];
  };
  const content = applyToFirst(doc.package.document.content, filter, op, state);
  return rebuild(doc, content);
}
