import React, { ReactNode, useImperativeHandle, useRef } from 'react';
import classNames from 'classnames';
import { as, toRem } from 'folds';
import * as css from './layout.css';
import { useStreamLayoutDebug } from './streamDebug';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';

// Day-divider rows fall back to this `S400` MessageBase spacing variant
// (see RoomTimeline.renderDayDivider). Message rows force collapse=true
// in Message.tsx so their marginTop drops to 0 — the rail-bridge offsets
// still resolve against `StreamRailBridgeY = S400` and the small overlap
// over the tighter gap stays hidden by the dot halo.
export const STREAM_MESSAGE_SPACING = '400' as const;

// Rail-dot diameters. The base dot is 9px (see `StreamDotSize` /
// `StreamDotColumn` in layout.css.ts, which keep the rail X consistent). The
// neutral gray dot is 0.95× that; the «state» dots (green = read, gold =
// mention, red = failed — `dotProminent`) are 1.1× the neutral so they read as
// slightly larger on the rail. The dot stays in-flow, so a prominent dot just
// overflows the 9px column by ~0.4px into the gap (centred enough to read on
// the rail) — same harmless trick the larger day-dot already uses.
const STREAM_DOT_NEUTRAL = toRem(8.55);
const STREAM_DOT_PROMINENT = toRem(9.405);

// Stream layout — DM «VS Code chat» redesign
// (docs/plans/dm_stream_vscode_redesign.md).
//
// Visual structure (2-track CSS grid, see StreamRoot in layout.css.ts):
//   ┌─G─┬─dot─┬─G─┬───── content (1fr) ───────────────────┐
//                       nick · time    ← header line
//                       bubble / text
//
// The rail + dot lead (track 1), hugging the left screen edge; the nick
// and a muted timestamp ride the header line at the top of the content
// column (track 2), in that order — rail+dot → nick → time. The two gaps
// (screen→dot, dot→content) come from the StreamRoot recipe per breakpoint.

export type StreamLayoutProps = {
  time?: ReactNode;
  dotColor: string;
  dotOpacity: number;
  // `true` → green/gold/red «state» dot drawn 1.1× the neutral gray size.
  dotProminent?: boolean;
  // Drives the bubble chrome: own → plain text on the chat background (no
  // bubble); incoming → filled bubble (composer-matched surface). See
  // layout.css.ts `StreamBubble`.
  isOwn?: boolean;
  compact?: boolean;
  // Same-sender continuation row (the whole run after the first message, any
  // minute): drop the rail dot and the whole header line (nick + timestamp)
  // and stack the body tight under the previous one. The caller also passes
  // `header={undefined}` for collapsed rows — and since the time now rides the
  // header line rather than its own grid track, dropping the header drops the
  // time with it (no width reservation needed). See RoomTimeline `collapsed`.
  collapsed?: boolean;
  // Author name — rendered as a bold label ABOVE the bubble, leading the
  // header line (nick → time) on the dot baseline (DM «VS Code chat»
  // redesign). `undefined` for collapsed continuation rows. Media rows still
  // show the nick here too (above the media, no overlay).
  header?: ReactNode;
  railStart?: boolean;
  railEnd?: boolean;
  // Suppress the rail segment entirely on this row. Set for trailing
  // continuation rows that sit AFTER the last dot (the rail must stop at the
  // last dot, not bleed down through the dot-less tail of a run).
  railHidden?: boolean;
  // Image messages: bubble bg/border/padding collapse so the
  // StreamMediaImage child supplies the visible chrome.
  mediaMode?: boolean;
  // Reactions chip-row, rendered BELOW the bubble in the same content
  // column — outside the bubble's bg/border so reactions read as
  // floating chips on the page background, not as a part of the bubble.
  reactions?: ReactNode;
  // M3a: thread summary pill rendered BETWEEN the bubble and reactions
  // for thread roots in channels-mode. Same column as reactions so it
  // aligns with the bubble's left edge. Optional — caller passes
  // `undefined` outside channels-mode or when the root has no replies.
  threadSummary?: ReactNode;
};

export type StreamDayDividerProps = {
  label: ReactNode;
};

export const StreamDayDivider = as<'div', StreamDayDividerProps>(
  ({ className, label, ...props }, ref) => {
    // Must match the `compact` variant of message rows above/below so the
    // shared `--vojo-stream-gap` resolves to the same value in both grids.
    const compact = useScreenSizeContext() === ScreenSize.Mobile;
    return (
      <div
        className={classNames(css.StreamDayRoot({ compact }), className)}
        {...props}
        role="separator"
        aria-label={typeof label === 'string' ? label : undefined}
        ref={ref}
      >
        {/* Track 1: dot column, holds the rail line + the larger day-dot.
            Same fixed width + paddingLeft anchor as message rows, so the
            day-dot lands on the exact same X as the message dots. */}
        <span className={css.StreamDotColumn} aria-hidden>
          <span className={css.StreamRail} />
          <span className={css.StreamDayDot} />
        </span>
        {/* Track 2: `─── label ───` line. */}
        <div className={css.StreamDayLineWrap} aria-hidden>
          <span className={css.StreamDayLineSegment} />
          <span className={css.StreamDayLabel}>{label}</span>
          <span className={css.StreamDayLineSegment} />
        </div>
      </div>
    );
  }
);

export const StreamLayout = as<'div', StreamLayoutProps>(
  (
    {
      className,
      time,
      dotColor,
      dotOpacity,
      dotProminent,
      isOwn,
      compact,
      collapsed,
      header,
      railStart,
      railEnd,
      railHidden,
      mediaMode,
      reactions,
      threadSummary,
      children,
      ...props
    },
    ref
  ) => {
    const rootRef = useRef<HTMLDivElement>(null);
    const timeRef = useRef<HTMLSpanElement>(null);
    const railRef = useRef<HTMLSpanElement>(null);
    const dotRef = useRef<HTMLSpanElement>(null);
    const bubbleRef = useRef<HTMLDivElement>(null);
    const headerRef = useRef<HTMLDivElement>(null);

    useImperativeHandle(ref, () => rootRef.current as HTMLDivElement);

    // Dev-only, gated by localStorage opt-in (see streamDebug.ts).
    useStreamLayoutDebug(
      'message',
      {
        root: rootRef,
        timeColumn: timeRef,
        rail: railRef,
        dot: dotRef,
        content: bubbleRef,
        header: headerRef,
      },
      true
    );

    return (
      <div
        className={classNames(
          css.StreamRoot({ compact: !!compact, collapsed: !!collapsed }),
          className
        )}
        {...props}
        ref={rootRef}
      >
        {/* Track 1: rail + dot, leading the row at the left screen edge. */}
        <span className={css.StreamDotColumn} aria-hidden>
          {/* The rail is suppressed entirely on trailing continuation rows
              (after the last dot) so the line stops at the last dot instead of
              bleeding down through the dot-less tail of a run. */}
          {!railHidden && (
            <span
              className={classNames(
                css.StreamRail,
                railStart && railEnd && css.StreamRailSingle,
                railStart && !railEnd && css.StreamRailStart,
                railEnd && !railStart && css.StreamRailEnd
              )}
              ref={railRef}
            />
          )}
          {/* No dot on collapsed continuation rows — the rail passes straight
              through, anchored by the first message's dot above. */}
          {!collapsed && (
            <span
              className={classNames(css.StreamDotHalo, css.StreamHeaderDotHalo)}
              style={{
                width: dotProminent ? STREAM_DOT_PROMINENT : STREAM_DOT_NEUTRAL,
                height: dotProminent ? STREAM_DOT_PROMINENT : STREAM_DOT_NEUTRAL,
              }}
              ref={dotRef}
            >
              <span
                className={css.StreamDotFill}
                style={{ backgroundColor: dotColor, opacity: dotOpacity }}
              />
            </span>
          )}
        </span>
        {/* Track 2: header line (nick → muted time) + bubble + reactions. */}
        <div className={css.StreamColumn}>
          {/* The header prints once at the head of a same-sender run; every
              continuation row drops it (header is undefined when collapsed), so
              the nick + timestamp only show on the first message of a run. The
              nick colour is per-side: own = white/black, peer = brand purple. */}
          {header && (
            <div
              className={classNames(css.StreamName, isOwn ? css.StreamNameOwn : css.StreamNamePeer)}
              ref={headerRef}
            >
              {header}
              {time && (
                <span className={css.StreamHeaderTime} ref={timeRef}>
                  {time}
                </span>
              )}
            </div>
          )}
          <div
            className={css.StreamBubble({
              own: !!isOwn,
              compact: !!compact,
              mediaMode: !!mediaMode,
            })}
            ref={bubbleRef}
          >
            {children}
          </div>
          {threadSummary && <div className={css.StreamThreadSummary}>{threadSummary}</div>}
          {reactions && <div className={css.StreamReactions}>{reactions}</div>}
        </div>
      </div>
    );
  }
);
