/**
 * pres/components — reusable UI primitives the archetypes compose.
 *
 * Modern decks lean on small, repeatable components: pill buttons, tag
 * chips, KPI cards with delta arrows, section progress dots, decorative
 * arcs in corners, huge backdrop numerals behind content. These are
 * those primitives — every archetype can call them so the design
 * vocabulary stays consistent across slide types.
 *
 * Every component reads colour from the palette by SEMANTIC ROLE
 * (palette.primary, palette.highlight, etc) — never accepts raw hex —
 * so swapping palettes restyles every component everywhere.
 */
import { cardShape, shapeOpts } from './shapes.js';
import { textStyle } from './typography.js';
import { SLIDE_DIMENSIONS } from './grid.js';
import { shade } from './palettes.js';

/* ─── PILL BUTTON ────────────────────────────────────────────────────
 * A rounded-rect button you'd recognise from a SaaS landing page.
 * Used by closing slides, callouts, and the "Update" bar of UI mockup
 * slides. Auto-sizes to its label.                                    */
export function pillButton(slide, resolved, { x, y, label, kind = 'primary', size = 'md', icon }) {
  const palette = resolved.palette;
  const fill = kind === 'primary'   ? palette.primary
             : kind === 'highlight' ? palette.highlight
             : kind === 'accent'    ? palette.accent
             : kind === 'ghost'     ? palette.surface
             : palette.primary;
  const labelColor = kind === 'ghost' ? palette.text
                   : kind === 'highlight' ? palette.ink
                   : 'FFFFFF';
  const dims = size === 'sm' ? { h: 0.36, padX: 0.28, fz: 11 }
             : size === 'lg' ? { h: 0.6,  padX: 0.45, fz: 16 }
             :                 { h: 0.46, padX: 0.34, fz: 13 };

  // Estimate width from label length (PPTX has no measure-text API).
  const labelWidth = Math.max(0.7, label.length * dims.fz * 0.013);
  const w = labelWidth + dims.padX * 2;

  slide.addShape('roundRect', {
    x, y, w, h: dims.h,
    rectRadius: dims.h / 2,                 // proper pill
    fill: { color: fill },
    line: kind === 'ghost' ? { color: palette.subtle, width: 0.5 } : { type: 'none' },
    shadow: kind === 'primary' ? { type: 'outer', blur: 8, offset: 2, angle: 90, color: '000000', opacity: 0.15 } : undefined,
  });
  slide.addText(label, {
    x, y, w, h: dims.h,
    fontFace: resolved.typography.body.fontFace,
    fontSize: dims.fz,
    bold: true,
    color: labelColor,
    align: 'center',
    valign: 'middle',
    charSpacing: 0.5,
  });
  return { x, y, w, h: dims.h };
}

/* ─── TAG CHIP ──────────────────────────────────────────────────────
 * Smaller than a pill button, used for category labels, status badges,
 * "5 min read"-style metadata. Subtle background + highlight text.    */
export function tag(slide, resolved, { x, y, label, kind = 'subtle' }) {
  const palette = resolved.palette;
  const fill = kind === 'highlight' ? palette.highlight
             : kind === 'accent'    ? palette.accent
             : kind === 'primary'   ? palette.primary
             : shade(palette.bg, palette.family === 'tech' ? 15 : -10);
  const labelColor = kind === 'subtle' ? palette.text
                   : kind === 'highlight' ? palette.ink
                   : 'FFFFFF';
  const fz = 11;
  const padX = 0.18;
  const h = 0.3;
  const labelWidth = Math.max(0.4, label.length * fz * 0.012);
  const w = labelWidth + padX * 2;

  slide.addShape('roundRect', {
    x, y, w, h, rectRadius: h / 2,
    fill: { color: fill, transparency: kind === 'subtle' ? 20 : 0 },
    line: { type: 'none' },
  });
  slide.addText(label, {
    x, y, w, h,
    fontFace: resolved.typography.body.fontFace,
    fontSize: fz,
    bold: true,
    color: labelColor,
    align: 'center',
    valign: 'middle',
    charSpacing: 1,
  });
  return { x, y, w, h };
}

/* ─── GLASS CARD ────────────────────────────────────────────────────
 * Apple "liquid glass" surface. The recipe in pptxgenjs primitives:
 *   1. Two-stop subtle vertical gradient fill at high transparency
 *      (lighter at top, baseline at bottom) — fakes the glass-on-blur
 *      look without a real backdrop-filter.
 *   2. A 0.5pt inner stroke at 20% opacity in pure white — the bright
 *      edge that signals "this is glass" in every WWDC slide deck.
 *
 * On light themes the gradient inverts (slightly darker top, baseline
 * surface bottom) so the card still reads as raised. On dark themes
 * the stroke shines; on light it disappears (we hide it) so the card
 * doesn't have an awkward white outline.
 *
 *   glassCard(slide, resolved, { x, y, w, h, tint?: 'primary' })
 *
 * `tint` lets the agent push a faint colour wash into the glass — used
 * by hero stat tiles to subtly colour-code without losing the glass feel.
 */
export function glassCard(slide, resolved, { x, y, w, h, tint }) {
  const palette = resolved.palette;
  const isDark = palette.family === 'tech' && isDarkHexLocal(palette.bg);

  // Pre-paint the surface fill so the stroke sits on top.
  const topStop = isDark ? 'FFFFFF' : palette.subtle;
  const botStop = isDark ? palette.surface : palette.surface;
  slide.addShape(resolved.shape.cornerRadius > 0 ? 'roundRect' : 'rect', {
    x, y, w, h,
    ...(resolved.shape.cornerRadius > 0 ? { rectRadius: resolved.shape.cornerRadius } : {}),
    fill: {
      type: 'gradient',
      gradientType: 'linear',
      angle: 90,
      stops: [
        { pos: 0,   color: topStop, transparency: isDark ? 88 : 92 },
        { pos: 100, color: botStop, transparency: isDark ? 70 : 60 },
      ],
    },
    line: isDark
      ? { color: 'FFFFFF', width: 0.5, transparency: 78 }
      : { color: palette.subtle, width: 0.5, transparency: 78 },
    shadow: resolved.shape.shadow || undefined,
  });

  // Optional tint wash — a colour-keyed soft fill at very high
  // transparency, sitting on top of the glass base.
  if (tint) {
    const tintColor = palette[tint] || palette.primary;
    slide.addShape(resolved.shape.cornerRadius > 0 ? 'roundRect' : 'rect', {
      x, y, w, h,
      ...(resolved.shape.cornerRadius > 0 ? { rectRadius: resolved.shape.cornerRadius } : {}),
      fill: { color: tintColor, transparency: 90 },
      line: { type: 'none' },
    });
  }
}

function isDarkHexLocal(hex) {
  const s = String(hex).replace(/^#/, '');
  if (!/^[0-9A-Fa-f]{6}$/.test(s)) return false;
  const r = parseInt(s.slice(0, 2), 16);
  const g = parseInt(s.slice(2, 4), 16);
  const b = parseInt(s.slice(4, 6), 16);
  return (0.299 * r + 0.587 * g + 0.114 * b) < 110;
}

/* ─── KPI CARD ──────────────────────────────────────────────────────
 * A surface-coloured card holding: small label, big number, optional
 * up/down delta with colour-coded arrow. The unit of a dashboard.    */
export function kpiCard(slide, resolved, { x, y, w, h, label, value, unit, delta, deltaDirection }) {
  const palette = resolved.palette;

  // Card background.
  slide.addShape(cardShape(resolved.shape), {
    x, y, w, h,
    ...shapeOpts(resolved.shape, 'card', palette.surface),
  });

  const pad = 0.25;

  // Label on top.
  slide.addText(String(label || '').toUpperCase(), {
    x: x + pad, y: y + pad, w: w - pad * 2, h: 0.3,
    fontFace: resolved.typography.eyebrow.fontFace,
    fontSize: resolved.typography.eyebrow.fontSize,
    bold: true,
    color: palette.subtle,
    align: 'left',
    valign: 'top',
    charSpacing: 2,
  });

  // Value (big) + unit (smaller, baseline-aligned). One-line layout —
  // the value must not wrap, and the unit sits next to it. We give
  // pptxgenjs `fit: 'shrink'` so if the value+unit are wider than the
  // card, the type shrinks instead of wrapping (which would otherwise
  // push the value into the delta row).
  const valY = y + 0.62;
  const valH = h - 1.3;
  const valFrame = { x: x + pad, y: valY, w: w - pad * 2, h: valH };
  // Conservative font sizing: smaller of (card-derived) and (~50% of
  // card height), so a wide-card / short-card never overflows.
  const valueFontSize = Math.min(Math.max(28, valH * 36), Math.max(28, w * 16));
  if (unit) {
    slide.addText([
      { text: String(value ?? ''), options: {
        fontFace: resolved.typography.display.fontFace,
        fontSize: valueFontSize,
        bold: true,
        color: palette.ink,
        charSpacing: -2,
      } },
      { text: ' ' + unit, options: {
        fontFace: resolved.typography.body.fontFace,
        fontSize: Math.max(14, valueFontSize * 0.36),
        bold: true,
        color: palette.subtle,
      } },
    ], { ...valFrame, align: 'left', valign: 'middle', fit: 'shrink', wrap: false });
  } else {
    slide.addText(String(value ?? ''), {
      ...valFrame,
      fontFace: resolved.typography.display.fontFace,
      fontSize: valueFontSize,
      bold: true,
      color: palette.ink,
      align: 'left',
      valign: 'middle',
      charSpacing: -2,
      fit: 'shrink',
      wrap: false,
    });
  }

  // Delta row with colour-coded arrow.
  if (delta !== undefined && delta !== null && delta !== '') {
    const dir = deltaDirection || (String(delta).trim().startsWith('-') ? 'down' : 'up');
    const dColor = dir === 'down' ? palette.highlight : palette.primary;
    const arrow = dir === 'down' ? '▼' : '▲';
    slide.addText(`${arrow}  ${delta}`, {
      x: x + pad, y: y + h - 0.45,
      w: w - pad * 2, h: 0.32,
      fontFace: resolved.typography.body.fontFace,
      fontSize: 13,
      bold: true,
      color: dColor,
      align: 'left',
      valign: 'middle',
    });
  }
}

/* ─── SECTION PROGRESS DOTS ─────────────────────────────────────────
 * A row of N small dots showing which section the deck is in. Filled
 * dot = current, hollow = other. Lives in the top-right of every
 * content slide via the slide master.                                */
export function sectionProgress(slide, resolved, { sections, currentIdx, x, y }) {
  const palette = resolved.palette;
  const dotR = 0.07;
  const gap = 0.18;
  for (let i = 0; i < sections; i++) {
    const filled = i === currentIdx;
    slide.addShape('ellipse', {
      x: x + i * (dotR * 2 + gap),
      y, w: dotR * 2, h: dotR * 2,
      fill: filled ? { color: palette.primary } : { color: palette.subtle, transparency: 60 },
      line: { type: 'none' },
    });
  }
}

/* ─── DECORATIVE CORNER ARC ─────────────────────────────────────────
 * A quarter-circle accent in a slide corner. Used by section-headers
 * + hero slides for editorial-style flourish without taking content
 * real-estate.                                                       */
export function cornerArc(slide, resolved, { corner = 'tr', size = 4, color, transparency = 70 }) {
  const palette = resolved.palette;
  const fill = color || palette.accent;
  const w = SLIDE_DIMENSIONS.width;
  const h = SLIDE_DIMENSIONS.height;
  // Place ellipse so only the desired quarter is visible on the slide.
  const positions = {
    tr: { x: w - size,     y: -size,        },
    tl: { x: -size,        y: -size,        },
    br: { x: w - size,     y: h - size,     },
    bl: { x: -size,        y: h - size,     },
  };
  const p = positions[corner] || positions.tr;
  slide.addShape('ellipse', {
    x: p.x, y: p.y,
    w: size * 2, h: size * 2,
    fill: { color: fill, transparency },
    line: { type: 'none' },
  });
}

/* ─── BIG BACKDROP NUMERAL ──────────────────────────────────────────
 * A huge ghosted number sitting behind content — used by section
 * headers and bigStat. Adds drama without competing with foreground
 * text.                                                              */
export function backdropNumeral(slide, resolved, { text, x, y, w, h, color, transparency = 88, size }) {
  const palette = resolved.palette;
  slide.addText(String(text), {
    x, y, w, h,
    fontFace: resolved.typography.display.fontFace,
    fontSize: size || 480,
    bold: true,
    color: color || palette.primary,
    align: 'left',
    valign: 'top',
    charSpacing: -10,
    transparency,
  });
}

/* ─── SIDE METADATA RAIL ────────────────────────────────────────────
 * A thin column down one edge with section name + small page number.
 * The kind of editorial flourish that signals "designed deck". Used
 * by the slide master.                                               */
export function sideRail(slide, resolved, { side = 'right', sectionName, pageLabel }) {
  const palette = resolved.palette;
  const m = resolved.spacing.pageMargin;
  const x = side === 'right' ? SLIDE_DIMENSIONS.width - m * 0.6 : m * 0.2;

  if (sectionName) {
    // Render vertical-looking text by spacing characters with breaks.
    slide.addText(String(sectionName).toUpperCase().split('').join('\n'), {
      x, y: m, w: 0.4, h: SLIDE_DIMENSIONS.height - m * 2 - 0.6,
      fontFace: resolved.typography.eyebrow.fontFace,
      fontSize: 9,
      bold: true,
      color: palette.subtle,
      align: 'center',
      valign: 'top',
      charSpacing: 2,
    });
  }

  if (pageLabel) {
    slide.addText(String(pageLabel), {
      x, y: SLIDE_DIMENSIONS.height - m - 0.4,
      w: 0.4, h: 0.3,
      fontFace: resolved.typography.eyebrow.fontFace,
      fontSize: 10,
      bold: true,
      color: palette.subtle,
      align: 'center',
      valign: 'middle',
    });
  }
}

/* ─── HEADER CHROME ─────────────────────────────────────────────────
 * Thin accent line + deck-section label + slide number at top of
 * content slides. The slide-master baseline; hero/section/closing
 * archetypes skip it.                                                */
export function headerChrome(slide, resolved, { sectionName, slideNumber, totalSlides }) {
  const palette = resolved.palette;
  const m = resolved.spacing.pageMargin;
  const w = SLIDE_DIMENSIONS.width;

  // Top hairline accent stripe.
  slide.addShape('rect', {
    x: 0, y: 0, w, h: 0.04,
    fill: { color: palette.primary },
    line: { type: 'none' },
  });

  // Section name top-left.
  if (sectionName) {
    slide.addText(String(sectionName).toUpperCase(), {
      x: m, y: 0.18, w: 6, h: 0.25,
      fontFace: resolved.typography.eyebrow.fontFace,
      fontSize: 9,
      bold: true,
      color: palette.subtle,
      align: 'left',
      charSpacing: 2,
    });
  }

  // Page number top-right.
  if (slideNumber) {
    const label = totalSlides ? `${String(slideNumber).padStart(2, '0')} / ${String(totalSlides).padStart(2, '0')}`
                              : String(slideNumber).padStart(2, '0');
    slide.addText(label, {
      x: w - m - 1.5, y: 0.18, w: 1.5, h: 0.25,
      fontFace: resolved.typography.mono.fontFace,
      fontSize: 9,
      bold: true,
      color: palette.subtle,
      align: 'right',
    });
  }
}

/* ─── FOOTER CHROME ─────────────────────────────────────────────────
 * Brand name (left) + section progress dots (right) at the bottom of
 * content slides. Tied to the slide master.                          */
export function footerChrome(slide, resolved, { brand, sectionTotal, currentSectionIdx }) {
  const palette = resolved.palette;
  const m = resolved.spacing.pageMargin;
  const w = SLIDE_DIMENSIONS.width;
  const h = SLIDE_DIMENSIONS.height;
  const footY = h - 0.35;

  if (brand) {
    slide.addText(String(brand), {
      x: m, y: footY, w: 4, h: 0.25,
      fontFace: resolved.typography.eyebrow.fontFace,
      fontSize: 9,
      bold: true,
      color: palette.subtle,
      align: 'left',
      charSpacing: 1.5,
    });
  }

  if (sectionTotal && sectionTotal > 0) {
    const dotsW = (sectionTotal * 0.07 * 2) + ((sectionTotal - 1) * 0.18);
    sectionProgress(slide, resolved, {
      sections: sectionTotal,
      currentIdx: currentSectionIdx || 0,
      x: w - m - dotsW,
      y: footY + 0.08,
    });
  }
}
