/**
 * Deterministisches Einfügen von Section-Templates in einen MJML-Source.
 *
 * Wird in `pipeline.ts` NACH der LLM-Generation aufgerufen — die KI sieht die Sections
 * nicht und kann sie nicht umschreiben. Wir parsen MJML hier NICHT als XML (cheerio im
 * MJML-Modus ist unzuverlässig), sondern arbeiten auf String-Ebene mit charakteristischen
 * Markern.
 *
 * Idempotenz: jeder eingefügte Section-Block bekommt einen `<!-- mf-section:KIND:INSTANCEID -->`
 * Marker. Beim zweiten Lauf (z.B. Re-Render) prüfen wir diesen Marker und skippen die
 * Re-Insertion.
 */

export type SectionPosition = 'before_cta' | 'after_cta' | 'before_signature' | 'after_signature';

/** Synchron mit `SectionBodyMode` in `BriefingPanel.tsx` und `body_mode` in der DB. */
export type SectionBodyMode = 'tiptap' | 'mjml_raw' | 'html_raw';

export interface ResolvedSection {
  /** Stable Instance-Identifier — landet in Marker-Comment für Idempotenz. */
  instanceId: string;
  kind: string;
  position: SectionPosition;
  /**
   * Body-Modus:
   *  - 'tiptap'   → mjmlBlock enthält {{TIPTAP_HTML}}-Slot, contentHtml wird substituiert
   *  - 'mjml_raw' → mjmlBlock ist user-importiertes MJML, kein Slot
   *  - 'html_raw' → mjmlBlock ist <mj-raw>{sanitized HTML}</mj-raw>, kein Slot
   * Workspace-Tokens werden in allen Modi resolved.
   */
  bodyMode: SectionBodyMode;
  /** Roher MJML-Block — bei tiptap aus section_templates.mjmlBlock, bei raw vom User. */
  mjmlBlock: string;
  /** TipTap-Doc als HTML. Nur bei bodyMode='tiptap' relevant; sonst leer-String. */
  contentHtml: string;
  /**
   * Optionaler `<mj-style>...</mj-style>`-Block aus dem User-MJML-Header. Wird beim Inject
   * in den Mail-`<mj-head>` migriert (vor `</mj-head>`), damit User-CSS-Selektoren in der
   * finalen Mail wirken statt still verworfen zu werden. Nur bei bodyMode='mjml_raw' relevant.
   */
  headStyles?: string;
}

export interface InjectContext {
  brandPrimaryColor: string;
  brandName: string;
}

const SECTION_MARKER_PREFIX = 'mf-section:';

function makeMarker(kind: string, instanceId: string): string {
  return `<!-- ${SECTION_MARKER_PREFIX}${kind}:${instanceId} -->`;
}

/**
 * Resolved-Block in finales MJML umwandeln (Platzhalter-Replace).
 * Variablen die nicht im Kontext sind, bleiben als Literal stehen — das ist absichtlich
 * (z.B. `{{UNSUBSCRIBE_URL}}` darf NICHT hier resolved werden, das macht der Send-Worker).
 */
function resolvePlaceholders(block: string, ctx: InjectContext, contentHtml: string): string {
  return block
    .replace(/\{\{TIPTAP_HTML\}\}/g, contentHtml)
    .replace(/\{\{BRAND_PRIMARY_COLOR\}\}/g, ctx.brandPrimaryColor)
    .replace(/\{\{BRAND_NAME\}\}/g, ctx.brandName);
}

/**
 * Liefert alle <mj-section>...</mj-section>-Blöcke mit ihren Start-Indizes.
 * Wir nutzen `String.matchAll` statt einer Regex-Iteration mit lastIndex — TS-cleaner
 * und keine veränderlichen Regex-State.
 */
function findSectionBlocks(mjml: string): Array<{ start: number; end: number; body: string }> {
  const re = /<mj-section\b[\s\S]*?<\/mj-section>/g;
  const out: Array<{ start: number; end: number; body: string }> = [];
  for (const m of mjml.matchAll(re)) {
    if (m.index === undefined) continue;
    out.push({ start: m.index, end: m.index + m[0].length, body: m[0] });
  }
  return out;
}

/**
 * Findet den Insertion-Index im MJML-Source für eine gegebene Position.
 *
 * Die Anchor-Heuristik orientiert sich am Stub-Output (siehe pipeline.ts) und an
 * typischen Sonnet-MJML-Outputs. Heuristiken sind LWS-tolerant via Regex.
 *
 * Reihenfolge der Strategie:
 *  - `before_cta` / `after_cta`: erstes <mj-section> das ein <mj-button enthält.
 *  - `before_signature`: vorletzte content-Section (= die mit padding="8px 24px 24px"),
 *    fallback auf vor der ersten Footer-Section (background-color="#f4f4f5").
 *  - `after_signature`: vor der Footer-Section.
 *
 * USER-BEITRAGS-PUNKT (Plan §"Bewusste User-Beiträge" #3):
 * Wenn die Heuristik bei realen Mails öfter daneben liegt, hier anpassen.
 * Ich empfehle nach den ersten Test-Generations zu sehen welche Anchor-Strings zuverlässig sind.
 */
function findInsertionIndex(mjml: string, position: SectionPosition): number | null {
  const sections = findSectionBlocks(mjml);

  switch (position) {
    case 'before_cta': {
      const cta = sections.find((s) => s.body.includes('<mj-button'));
      return cta ? cta.start : null;
    }
    case 'after_cta': {
      const cta = sections.find((s) => s.body.includes('<mj-button'));
      return cta ? cta.end : null;
    }
    case 'before_signature': {
      // Im Stub: padding="8px 24px 24px" auf der CTA+Signatur-Section. In KI-Outputs
      // variiert das. Fallback: vor dem Footer (Section mit background-color="#f4f4f5").
      const sig = sections.find((s) => s.body.includes('padding="8px 24px 24px"'));
      if (sig) return sig.start;
      const footer = sections.find((s) => s.body.includes('background-color="#f4f4f5"'));
      if (footer) return footer.start;
      return null;
    }
    case 'after_signature': {
      // Vor dem Footer (background-color #f4f4f5 oder #fafafa). Falls kein Footer-Marker
      // gefunden wird, fallback auf direkt vor </mj-body>.
      const footer = sections.find(
        (s) =>
          s.body.includes('background-color="#f4f4f5"') ||
          s.body.includes('background-color="#fafafa"'),
      );
      if (footer) return footer.start;
      const bodyEnd = mjml.indexOf('</mj-body>');
      return bodyEnd >= 0 ? bodyEnd : null;
    }
    default:
      return null;
  }
}

/**
 * Merget `<mj-style>`-Blöcke aus User-Sections in den Mail-`<mj-head>` (vor `</mj-head>`).
 * Idempotent: wir markieren jeden Block mit `<!-- mf-style:INSTANCEID -->`, sodass ein
 * Re-Render nicht doppelt mergt.
 *
 * Falls die Mail keinen `<mj-head>` hat (extrem unwahrscheinlich, aber MJML erlaubt es),
 * wird ein Head direkt nach `<mjml>` angelegt.
 */
function mergeHeadStyles(mjml: string, sections: ResolvedSection[], ctx: InjectContext): string {
  const stylesToMerge: string[] = [];
  for (const s of sections) {
    if (!s.headStyles) continue;
    const styleMarker = `<!-- mf-style:${s.instanceId} -->`;
    if (mjml.includes(styleMarker)) continue; // Idempotenz
    // Workspace-Tokens auch in Styles auflösen (Border-Color etc. via {{BRAND_PRIMARY_COLOR}}).
    const resolved = resolvePlaceholders(s.headStyles, ctx, '');
    stylesToMerge.push(`${styleMarker}\n${resolved}`);
  }
  if (stylesToMerge.length === 0) return mjml;

  const block = stylesToMerge.join('\n');
  const headEnd = mjml.search(/<\/mj-head\s*>/i);
  if (headEnd >= 0) {
    return mjml.slice(0, headEnd) + block + '\n' + mjml.slice(headEnd);
  }
  // Kein <mj-head> — neuen anlegen direkt nach <mjml ...>.
  const mjmlOpen = mjml.match(/<mjml\b[^>]*>/i);
  if (!mjmlOpen || mjmlOpen.index === undefined) {
    console.warn('[inject-sections] no <mj-head> and no <mjml> root — skipping style migration');
    return mjml;
  }
  const insertAt = mjmlOpen.index + mjmlOpen[0].length;
  return mjml.slice(0, insertAt) + `\n<mj-head>\n${block}\n</mj-head>\n` + mjml.slice(insertAt);
}

export function injectExtraSections(
  mjml: string,
  sections: ResolvedSection[],
  ctx: InjectContext,
): string {
  if (!sections.length) return mjml;

  // Phase 1: <mj-style>-Blöcke aus User-Sections in den Mail-<mj-head> migrieren. Das passiert
  // VOR dem Body-Splice, damit der Head-Bereich in einem einzigen Pass aktualisiert wird und
  // die Anchor-Indices unten stabil bleiben (Head sitzt vor Body).
  let result = mergeHeadStyles(mjml, sections, ctx);

  // Phase 2: Body-Splice. Gruppieren nach Position, damit mehrere Sections an derselben
  // Position in der Reihenfolge der Briefing-Liste landen (kein Re-Sort am Ende).
  const byPosition: Record<SectionPosition, ResolvedSection[]> = {
    before_cta: [],
    after_cta: [],
    before_signature: [],
    after_signature: [],
  };
  for (const s of sections) byPosition[s.position].push(s);

  // Reihenfolge der Verarbeitung — wir bauen von hinten nach vorn, damit die Indizes
  // der vorderen Anchor-Punkte stabil bleiben.
  const order: SectionPosition[] = ['after_signature', 'before_signature', 'after_cta', 'before_cta'];

  for (const pos of order) {
    const list = byPosition[pos];
    if (list.length === 0) continue;

    const idx = findInsertionIndex(result, pos);
    if (idx === null) {
      // Position nicht gefunden — wir loggen aber crashen nicht. Section wird einfach
      // weggelassen, damit eine fehlerhafte Anchor-Heuristik nicht die ganze Mail
      // killt. Im UI sehen wir später am Preview was fehlt.
      console.warn(
        `[inject-sections] no insertion point for position=${pos}, skipping ${list.length} section(s)`,
      );
      continue;
    }

    // Sections die schon einen Marker haben → Idempotenz-Skip.
    const blocks: string[] = [];
    for (const s of list) {
      const marker = makeMarker(s.kind, s.instanceId);
      if (result.includes(marker)) continue;
      const resolved = resolvePlaceholders(s.mjmlBlock, ctx, s.contentHtml);
      blocks.push(`${marker}\n${resolved}\n`);
    }
    if (blocks.length === 0) continue;

    result = result.slice(0, idx) + blocks.join('') + result.slice(idx);
  }

  return result;
}
