import React from "react";
import {
  useCurrentFrame,
  useVideoConfig,
  spring,
  Img,
  staticFile,
} from "remotion";
import type { Word, CaptionStyle } from "../types";

interface Props {
  words: Word[];
  style: CaptionStyle;
  logoSrc?: string;
  faceY?: number | null; // normalized 0-1 (0=top, 1=bottom)
}

interface Chunk {
  words: Word[];
  start: number;
  end: number;
}

const MAX_CHARS_PER_CHUNK = 18;

function buildChunks(words: Word[], perChunk: number): Chunk[] {
  const chunks: Chunk[] = [];
  let i = 0;

  while (i < words.length) {
    let end = i;
    let charCount = 0;

    while (end < words.length && end - i < perChunk) {
      const nextWord = words[end].word.trim();
      const nextChars = charCount === 0 ? nextWord.length : charCount + 1 + nextWord.length;

      if (end > i && nextChars > MAX_CHARS_PER_CHUNK) {
        break;
      }

      charCount = nextChars;
      end += 1;
    }

    if (end === i) {
      end = i + 1;
    }

    const remaining = words.length - end;
    if (remaining === 1 && end - i > 2) {
      end -= 1;
    }

    const slice = words.slice(i, end);
    chunks.push({
      words: slice,
      start: slice[0].start,
      end: slice[slice.length - 1].end,
    });
    i = end;
  }

  return chunks;
}

function splitIntoLines(words: Word[]): [Word[], Word[]] {
  if (words.length <= 2) {
    return [words, []];
  }
  if (words.length === 3) {
    return [words.slice(0, 2), words.slice(2)];
  }
  return [words.slice(0, 2), words.slice(2)];
}

/**
 * Active pill rendered as an absolutely positioned background behind the word.
 * The word itself is always rendered as plain inline text so layout doesn't shift.
 */
const WordWithPill: React.FC<{
  word: Word;
  isActive: boolean;
  frame: number;
  fps: number;
}> = ({ word, isActive, frame, fps }) => {
  const wordEntryFrame = Math.round(word.start * fps);

  const pillOpacity = isActive
    ? spring({
        frame: frame - wordEntryFrame,
        fps,
        config: { damping: 14, stiffness: 200, mass: 0.4 },
      })
    : 0;

  return (
    <span style={{ position: "relative", display: "inline" }}>
      {/* Pill background — absolutely positioned, doesn't affect layout */}
      <span
        style={{
          position: "absolute",
          top: -4,
          left: -16,
          right: -16,
          bottom: -4,
          backgroundColor: "rgba(0, 0, 0, 0.85)",
          borderRadius: 18,
          boxShadow: "0 4px 20px rgba(0, 0, 0, 0.5)",
          opacity: pillOpacity,
          pointerEvents: "none",
        }}
      />
      {/* Word text — always inline, never shifts */}
      <span style={{ position: "relative", zIndex: 1 }}>{word.word}</span>
    </span>
  );
};

const CaptionLine: React.FC<{
  words: Word[];
  currentTime: number;
  frame: number;
  fps: number;
  style: CaptionStyle;
}> = ({ words, currentTime, frame, fps, style }) => {
  return (
    <div
      style={{
        textAlign: "center",
        fontFamily: style.fontFamily,
        fontSize: style.fontSize,
        fontWeight: 400,
        color: style.color,
        textShadow: "0 0 40px rgba(0, 0, 0, 0.15), 0 0 80px rgba(0, 0, 0, 0.1), 0 0 150px rgba(0, 0, 0, 0.08)",
        lineHeight: 1.25,
        whiteSpace: "nowrap",
      }}
    >
      {words.map((word, i) => {
        const isActive = currentTime >= word.start && currentTime < word.end;
        const prefix = i > 0 ? " " : "";

        return (
          <React.Fragment key={i}>
            {prefix}
            <WordWithPill
              word={word}
              isActive={isActive}
              frame={frame}
              fps={fps}
            />
          </React.Fragment>
        );
      })}
    </div>
  );
};

export const BrandedCaptions: React.FC<Props> = ({
  words,
  style,
  logoSrc,
  faceY,
}) => {
  const frame = useCurrentFrame();
  const { fps, height } = useVideoConfig();
  const currentTime = frame / fps;

  const chunks = buildChunks(words, style.wordsPerChunk);
  const activeChunk = chunks.find(
    (c) => currentTime >= c.start && currentTime < c.end
  );

  // Dynamic margin: if face is in the lower portion, push captions further down
  // faceY is normalized 0-1 (0=top, 1=bottom)
  // Default margin is style.marginBottom. If face center is below 0.55, reduce margin.
  let dynamicMargin = style.marginBottom;
  if (faceY != null && faceY > 0.55) {
    // Face is low — push captions to the very bottom
    dynamicMargin = Math.max(80, style.marginBottom - Math.round((faceY - 0.55) * height * 0.6));
  } else if (faceY != null && faceY < 0.35) {
    // Face is high — can bring captions up a bit
    dynamicMargin = style.marginBottom + 60;
  }

  return (
    <>
      {logoSrc && (
        <Img
          src={logoSrc.startsWith("http") ? logoSrc : staticFile(logoSrc)}
          style={{
            position: "absolute",
            top: 180,
            left: 108,
            width: 255,
            height: 126,
            objectFit: "contain",
          }}
        />
      )}

      {activeChunk && (() => {
        const [line1, line2] = splitIntoLines(activeChunk.words);

        return (
          <div
            style={{
              position: "absolute",
              bottom: dynamicMargin,
              left: 60,
              right: 60,
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
              gap: 8,
            }}
          >
            <CaptionLine
              words={line1}
              currentTime={currentTime}
              frame={frame}
              fps={fps}
              style={style}
            />
            {line2.length > 0 && (
              <CaptionLine
                words={line2}
                currentTime={currentTime}
                frame={frame}
                fps={fps}
                style={style}
              />
            )}
          </div>
        );
      })()}
    </>
  );
};
